Precommit (#1)

* first commit

* cleanup
This commit is contained in:
tompzf
2025-11-04 13:28:06 +01:00
committed by GitHub
parent dba45dc636
commit 6ed4b1534e
898 changed files with 256340 additions and 0 deletions

View File

@@ -0,0 +1,375 @@
#include "ascreader.h"
#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <WinSock2.h>
#include <Windows.h>
#include <processthreadsapi.h>
#elif defined __unix__
#include <pthread.h>
#endif
namespace asc
{
CAscReader::CAscReader() : m_itCurrent(m_lstMessages.begin())
{}
CAscReader::~CAscReader()
{
StopPlayback();
}
bool CAscReader::Read(const std::filesystem::path& rpathFile)
{
// Clear current messages
m_lstMessages.clear();
JumpBegin();
// Open and read the file
std::ifstream fstream(rpathFile);
if (!fstream.is_open()) return false;
// Read the file line by line
std::string ssLine;
enum class EState {header, body, footer} eState = EState::header;
while (std::getline(fstream, ssLine))
{
switch (eState)
{
case EState::header:
if (ssLine.compare(0, 18, "Begin Triggerblock") == 0)
eState = EState::body;
break;
case EState::body:
if (ssLine.compare(0, 16, "End TriggerBlock") == 0)
eState = EState::footer;
else
ProcessSample(ssLine);
break;
default:
break;
}
}
fstream.close();
// Set the current iterator
JumpBegin();
return true;
}
std::pair<SCanMessage, bool> CAscReader::Get() const
{
if (m_itCurrent == m_lstMessages.end())
return std::make_pair(SCanMessage(), false);
else
return std::make_pair(*m_itCurrent, true);
}
uint32_t CAscReader::GetLoopCount() const
{
return m_uiLoopCount;
}
void CAscReader::JumpBegin()
{
m_itCurrent = m_lstMessages.begin();
}
void CAscReader::JumpEnd()
{
m_itCurrent = m_lstMessages.end();
}
CAscReader& CAscReader::operator++()
{
if (m_itCurrent != m_lstMessages.end())
++m_itCurrent;
return *this;
}
CAscReader& CAscReader::operator++(int)
{
if (m_itCurrent != m_lstMessages.end())
++m_itCurrent;
return *this;
}
CAscReader& CAscReader::operator--()
{
if (m_itCurrent != m_lstMessages.begin())
--m_itCurrent;
return *this;
}
CAscReader& CAscReader::operator--(int)
{
if (m_itCurrent != m_lstMessages.begin())
--m_itCurrent;
return *this;
}
bool CAscReader::IsBOF() const
{
return m_itCurrent == m_lstMessages.begin();
}
bool CAscReader::IsEOF() const
{
return m_itCurrent == m_lstMessages.end();
}
void CAscReader::StartPlayback(std::function<void(const SCanMessage&)> fnCallback, bool bRepeat /*= true*/)
{
StopPlayback();
if (!fnCallback) return;
if (m_lstMessages.empty()) return;
m_bPlaybackThread = false;
m_bPlayback = true;
m_threadPlayback = std::thread(&CAscReader::PlaybackThreadFunc, this, fnCallback, std::ref(m_uiLoopCount), bRepeat);
while (!m_bPlaybackThread) std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
void CAscReader::StopPlayback()
{
m_bPlayback = false;
if (m_threadPlayback.joinable()) m_threadPlayback.join();
}
void CAscReader::ResetPlayback()
{
StopPlayback();
JumpBegin();
}
bool CAscReader::PlaybackRunning() const
{
return m_bPlayback;
}
void CAscReader::ProcessSample(const std::string& rssSample)
{
std::istringstream sstream(rssSample);
SCanMessage sMsg{};
// Expecting a time
if (!(sstream >> sMsg.dTimestamp))
return; // All usable mesaurements must start with a timestamp
// Skip whitespace
while (std::isspace(sstream.peek())) sstream.get();
// If the next timestamp is followed by the "CANFD" keyword, the sample is a CAN-FD sample
if (std::isalpha(sstream.peek()))
{
std::string ssKeyword;
if (!(sstream >> ssKeyword))
return; // Unexpected
if (ssKeyword != "CANFD")
return; // Not a CAN-FD sample
sMsg.bCanFd = true;
// Expecting a channel
if (!(sstream >> sMsg.uiChannel))
return; // Expected a channel (otherwise the measurement might contain meta data)
// Determine the direction
std::string ssDirection;
if (!(sstream >> ssDirection))
return; // Expected a direction
if (ssDirection == "Rx")
sMsg.eDirection = SCanMessage::EDirection::rx;
else if (ssDirection == "Tx")
sMsg.eDirection = SCanMessage::EDirection::tx;
else
return; // Invalid direction
// Expecting a message ID in hex format
if (!(sstream >> std::hex >> sMsg.uiId))
return; // Expected an ID (otherwise the measurement might contain meta data)
// Skip whitespace
while (std::isspace(sstream.peek())) sstream.get();
// Optional 'x' for an extended ID
if (std::tolower(sstream.peek()) == 'x')
{
sMsg.bExtended = true;
sstream.get(); // Skip the character, since already processed
}
// Get bit rate switch (BRS)
size_t nBRS = 0;
if (!(sstream >> nBRS) || nBRS > 1)
return; // Unexpected or invalid BRS
// Get error state indicator (ESI)
size_t nESI = 0;
if (!(sstream >> nESI) || nESI > 1)
return; // Unexpected or invalid ESI
// Get data length code (DLC)
size_t nDLC;
if (!(sstream >> nDLC) || nDLC > 15)
return; // Unexpected or invalid DLC
// Get the data length
if (!(sstream >> std::dec >> sMsg.uiLength) || sMsg.uiLength > 64)
return; // Length expected or invalid length
// Check for proper DLC vs. length
const size_t rgnDLCLength[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64 };
if (rgnDLCLength[nDLC] != sMsg.uiLength) return; // Invalid length
}
else // standard CAN
{
// Expecting a channel
if (!(sstream >> sMsg.uiChannel))
return; // Expected a channel (otherwise the measurement might contain meta data)
// Expecting a message ID in hex format
if (!(sstream >> std::hex >> sMsg.uiId))
return; // Expected an ID (otherwise the measurement might contain meta data)
// Skip whitespace
while (std::isspace(sstream.peek())) sstream.get();
// Optional 'x' for an extended ID
if (std::tolower(sstream.peek()) == 'x')
{
sMsg.bExtended = true;
sstream.get(); // Skip the character, since already processed
}
// Determine the direction
std::string ssDirection;
if (!(sstream >> ssDirection))
return; // Expected a direction
if (ssDirection == "Rx")
sMsg.eDirection = SCanMessage::EDirection::rx;
else if (ssDirection == "Tx")
sMsg.eDirection = SCanMessage::EDirection::tx;
else
return; // Invalid direction
// Determine whether the measurement contains data from a data frame (remote frames are not processed).
char cLocation = '\0';
if (!(sstream >> cLocation) || std::tolower(cLocation) != 'd')
return; // Not supported location
// Get the data length
if (!(sstream >> std::dec >> sMsg.uiLength) || sMsg.uiLength > 8)
return; // Length expected or invalid length
}
// Read the data
for (uint32_t uiIndex = 0; uiIndex < sMsg.uiLength; uiIndex++)
{
uint32_t uiVal = 0;
if (!(sstream >> std::hex >> uiVal))
return; // Expected data byte
sMsg.rguiData[uiIndex] = static_cast<uint8_t>(uiVal);
}
// Add the message to the vector
m_lstMessages.push_back(std::move(sMsg));
// Ignore the rest of the line (additional information could have been logged).
}
void CAscReader::PlaybackThreadFunc(std::function<void(const SCanMessage&)> fnCallback, std::atomic<uint32_t>& loopCount, bool bRepeat)
{
#ifdef _WIN32
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
#elif defined __unix__
pthread_attr_t thAttr;
pthread_attr_init(&thAttr);
int iPolicy = 0;
pthread_attr_getschedpolicy(&thAttr, &iPolicy);
int iMaxPrioForPolicy = sched_get_priority_max(iPolicy);
pthread_setschedprio(pthread_self(), iMaxPrioForPolicy);
pthread_attr_destroy(&thAttr);
#endif
// Indicate that playback thread has started.
m_bPlaybackThread = true;
// when data set is repeated, we need the offset compared to the first run
double dLoopOffset = 0.00000;
// Define the offset
double dOffset = 0.00000;
if (!IsBOF())
{
operator--();
auto prSample = Get();
if (!prSample.second)
{
m_bPlayback = false;
std::cout << "ASC PLAYBACK: Unexpected situation. Not BOF, but also no previous sample." << std::endl;
return; // Should not occur
}
dOffset = prSample.first.dTimestamp;
operator++();
}
loopCount++;
auto timepointStartOfFirstLoop = std::chrono::high_resolution_clock::now();
auto timepointStart = std::chrono::high_resolution_clock::now();
while (m_bPlayback)
{
if (IsEOF())
{
if (!bRepeat) break; // Done
// Data set will be repeated, callculate offset compared to the first run
auto timepointStartOfNextLoop = std::chrono::high_resolution_clock::now();
dLoopOffset = std::chrono::duration<double>(timepointStartOfNextLoop - timepointStartOfFirstLoop).count();
// Restart
loopCount++;
dOffset = 0.00000;
JumpBegin();
}
// Get a sample
auto prSample = Get();
if (!prSample.second)
break; // Should not occur
// Need to sleep?
while (m_bPlayback)
{
// Determine the current time relative to start time.
auto timepoint = std::chrono::high_resolution_clock::now();
double dTime = std::chrono::duration<double>(timepoint - timepointStart).count() + dOffset;
// If the sample is overdue... do not sleep
if ((prSample.first.dTimestamp + dLoopOffset) < dTime)
break;
// If the sample will be overdue in 10 ms, yield the current thread only (this will not put the thread in idle mode).
//if (prSample.first.dTimestamp < dTime + 0.010)
// std::this_thread::yield();
//else // Sleep shortly... this might cause a sleep of more than 1ms dependable on the load of the system
std::this_thread::sleep_for(std::chrono::milliseconds(0));
}
// Send the sample
fnCallback(prSample.first);
// Next sample
operator++();
}
m_bPlayback = false;
}
} // namespace asc

View File

@@ -0,0 +1,162 @@
#ifndef ASC_FILE_READER_H
#define ASC_FILE_READER_H
#include <list>
#include <functional>
#include <filesystem>
#include <thread>
namespace asc
{
/**
* @brief CAN message structure
*/
struct SCanMessage
{
double dTimestamp; ///< Timestamp in seconds
uint32_t uiChannel; ///< CAN channel (first channel starts with 0).
uint32_t uiId; ///< CAN ID
bool bExtended; ///< Set when the CAN ID is extended (29 bits) or not when standard
///< (11 bits).
bool bCanFd; ///< Set when the message contains CAN-FD data (more than 8 bytes).
/// Direction enumeration
enum class EDirection { rx, tx } eDirection; ///< Direction RX or TX.
uint32_t uiLength; ///< Length of the CAN data.
uint8_t rguiData[64]; ///< Array with the CAN data.
};
/**
* @brief This class allows reading the Vector ASC file format.
* @attention This class assumes no concurrency between reading a file, navigation and playback. No thread synchronization is
* implemented.
*/
class CAscReader
{
public:
/**
* @brief Constructor
*/
CAscReader();
/**
* @brief Destructor
*/
~CAscReader();
/**
* @brief Read a file (this will replace the current samples with the samples of the file).
* @param[in] rpathFile Reference to the file path.
* @return Returns 'true' on success or 'false' when not.
*/
bool Read(const std::filesystem::path& rpathFile);
/**
* @brief Get the current sample. If the current position is EOF, no sample is returned.
* @return A pair consisting of the current sample and a boolean indicating that the sample is valid.
*/
std::pair<SCanMessage, bool> Get() const;
/**
* @brief Get the number of loops the data set was sent
* @return Number of loops the data set was sent including the current loop
*/
uint32_t GetLoopCount() const;
/**
* @brief Jump to the first sample.
*/
void JumpBegin();
/**
* @brief Jump beyond the last sample.
*/
void JumpEnd();
/**
* @{
* @brief Increase the current position to the next sample.
* @return Reference to this class.
*/
CAscReader& operator++();
CAscReader& operator++(int);
/**
* @}
*/
/**
* @{
* @brief Decrease the current position to the previous sample.
* @return Reference to this class.
*/
CAscReader& operator--();
CAscReader& operator--(int);
/**
* @}
*/
/**
* @brief Does the current position point to the first sample?
* @return Returns whether the current position points to the first sample.
*/
bool IsBOF() const;
/**
* @brief Does the current position point beyond the last sample?
* @return Returns whether the current position points beyond the last sample.
*/
bool IsEOF() const;
/**
* @brief Start playback using the timestamp of the samples to come as close to the original recording time.
* @param[in] fnCallback The function being called with the current sample.
* @param[in] bRepeat When set, the playback continuous at the beginning when reaching the end of the data set.
*/
void StartPlayback(std::function<void(const SCanMessage&)> fnCallback, bool bRepeat = true);
/**
* @brief Stop the current playback.
* @remarks This does not change the current position (pointing to the next sample following the one just sent.
*/
void StopPlayback();
/**
* @brief Reset the playback to the beginning of the data set. This is equal to calling StopPlayback and JumpBegin.
*/
void ResetPlayback();
/**
* @brief Returns whether playback is running at the moment.
* @return Returns whether playback is running.
*/
bool PlaybackRunning() const;
private:
/**
* @brief Process a measurement value sample (one line within the ASC trigger block section).
* @details The ASC measurement uses the following format for one CAN measurement value:
*
* \verbatim
* For CAN: Timestamp Busnumber CanId ["x"] "Rx|Tx" "d" Number-of-bytes Byte1, Byte2, ..., Byte8 [...]
* For CAN-FD: Timestamp CANFD <channel> <dir> <id> <brs> <esi> <dlc> <data_len> <data> [ ... ]
* \endverbatim
*
* @param[in] rssSample Reference to the measurement value sample.
*/
void ProcessSample(const std::string& rssSample);
/**
* @brief Playback thread function. If the current position is BOF, the playback starts from time = 0.0000, otherwise the
* playback starts from the current position.
*/
void PlaybackThreadFunc(std::function<void(const SCanMessage&)> fnCallback, std::atomic<uint32_t>& loopCount, bool bRepeat);
std::list<SCanMessage> m_lstMessages; ///< Vector with messages
std::list<SCanMessage>::iterator m_itCurrent; ///< Current iterator position
std::thread m_threadPlayback; ///< Playback thread.
bool m_bPlaybackThread = false; ///< Set when running playback thread
bool m_bPlayback = false; ///< Set when running playback
std::atomic<uint32_t> m_uiLoopCount{ 0 }; ///< Counter how often the data set was sent
};
}
#endif // !defined ASC_FILE_READER_H

View File

@@ -0,0 +1,96 @@
#include "ascwriter.h"
#include <fstream>
namespace asc
{
CAscWriter::CAscWriter()
{
StartTimer();
}
void CAscWriter::AddSample(const SCanMessage& rsSample)
{
SCanMessage sSampleCopy(rsSample);
if (sSampleCopy.dTimestamp == 0.0000000)
sSampleCopy.dTimestamp =
std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - m_timepointStart).count();
else
{
if (!m_lstMessages.empty() && m_lstMessages.back().dTimestamp > sSampleCopy.dTimestamp) return;
}
m_lstMessages.push_back(std::move(sSampleCopy));
}
bool CAscWriter::HasSamples() const
{
return !m_lstMessages.empty();
}
void CAscWriter::StartTimer()
{
if (m_lstMessages.empty())
{
m_timepointStart = std::chrono::high_resolution_clock::now();
m_timepointSystem = std::chrono::system_clock::now();
}
}
void CAscWriter::Clear()
{
m_lstMessages.clear();
m_timepointStart = std::chrono::high_resolution_clock::now();
}
bool CAscWriter::Write(const std::filesystem::path& rpathFile)
{
// Open and write the file
std::ofstream fstream(rpathFile, std::ios::out | std::ios::trunc);
if (!fstream.is_open()) return false;
// Fill in header
time_t tmSystemTime = std::chrono::system_clock::to_time_t(m_timepointSystem);
fstream << "date " << std::put_time(std::localtime(&tmSystemTime), "%c") << std::endl;
fstream << "base hex timestamps absolute" << std::endl;
// Stream trigger block
fstream << "Begin Triggerblock " << std::put_time(std::localtime(&tmSystemTime), "%c") << std::endl;
fstream << " 0.000000 Start of measurement" << std::endl;
for (const SCanMessage& rsSample : m_lstMessages)
{
std::stringstream sstreamTimestamp;
sstreamTimestamp << std::fixed << std::setprecision(6) << rsSample.dTimestamp;
fstream << std::string(11ull - sstreamTimestamp.str().length(), ' ') << sstreamTimestamp.str();
if (rsSample.bCanFd)
{
fstream << " " << "CANFD" << " " << rsSample.uiChannel << " " <<
(rsSample.eDirection == SCanMessage::EDirection::rx ? "Rx" : "Tx") <<
std::hex << std::uppercase << rsSample.uiId << (rsSample.bExtended ? "x" : "") << " 1 0 ";
const size_t rgnDLCLength[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64 };
size_t nDLC = 0;
for (size_t n = 0; n < 16; n++)
if (rgnDLCLength[n] == rsSample.uiLength) nDLC = n;
fstream << nDLC << " " << rsSample.uiLength;
}
else // Normal CAN
{
fstream << " " << rsSample.uiChannel;
std::stringstream sstreamID;
sstreamID << std::hex << std::uppercase << rsSample.uiId;
fstream << " " << sstreamID.str() << (rsSample.bExtended ? "x" : "");
fstream << std::string(16ull - sstreamID.str().length(), ' ') <<
(rsSample.eDirection == SCanMessage::EDirection::rx ? "Rx" : "Tx");
fstream << " " << "d";
fstream << " " << rsSample.uiLength;
}
for (uint32_t uiIndex = 0; uiIndex < rsSample.uiLength; uiIndex++)
fstream << " " << std::hex << std::setw(2) << std::setfill('0') << std::uppercase << static_cast<size_t>(rsSample.rguiData[uiIndex]);
fstream << std::endl;
}
fstream << "End TriggerBlock" << std::endl;
// Done
fstream.close();
return true;
}
} // namespace asc

View File

@@ -0,0 +1,63 @@
#ifndef ASC_FILE_WRITER_H
#define ASC_FILE_WRITER_H
#include "ascreader.h"
#include <list>
#include <chrono>
namespace asc
{
/**
* @brief This class allows writing the Vector ASC file format.
* @attention This class assumes no concurrency between writing the file and adding samples. No thread synchronization is
* implemented.
*/
class CAscWriter
{
public:
/**
* @brief Constructor
*/
CAscWriter();
/**
* @brief Start the timer for automatic timestamp creation.
* @remarks Resets the start time only if there are no samples are available.
*/
void StartTimer();
/**
* @brief Add a sample.
* @attention The sample will not be added if the timestamp is smaller than the last sample.
* @param[in] rsSample Reference to the CAN sample structure. If the timestamp is empty, the timestamp is created automatically
* using the integrated timer.
*/
void AddSample(const SCanMessage& rsSample);
/**
* @brief Returns whether at least one sample is stored.
* @return Returns 'true' when samples are available; 'false' when not.
*/
bool HasSamples() const;
/**
* @brief Clear the content.
*/
void Clear();
/**
* @brief Write to a file (the file gets overwritten if existing).
* @param[in] rpathFile Reference to the file path.
* @return Returns 'true' on success or 'false' when not.
*/
bool Write(const std::filesystem::path& rpathFile);
private:
std::list<SCanMessage> m_lstMessages; ///< Queue with messages
std::chrono::high_resolution_clock::time_point m_timepointStart; ///< Starting timepoint for automatic timestamp
///< generation.
std::chrono::system_clock::time_point m_timepointSystem; ///< Starting timepoint for time/date generation.
};
}
#endif // !defined ASC_FILE_WRITER_H

306
global/base64.h Normal file
View File

@@ -0,0 +1,306 @@
#ifndef BASE64_H
#define BASE64_H
#include <string>
#include <stdexcept>
static const std::string Base64Chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
/**
* @brief check whether its a Base64 format
* @param[in] c character to be looked for
* @return true if Base64 format
*/
inline bool IsBase64(const unsigned char c)
{
return (isalnum(c) || (c == '+') || (c == '/'));
}
/**
* @brief Encode to Base64 format
* @param[in] plainText text to be encoded to Base64
* @return text encoded to format
*/
inline std::string Base64EncodePlainText(const std::string& plainText)
{
const char* buffer = plainText.c_str();
std::size_t bytesLeft = plainText.size();
std::string ret;
int i = 0;
unsigned char char_array_3[3]{ 0 };
unsigned char char_array_4[4]{ 0 };
while (bytesLeft--)
{
char_array_3[i++] = *reinterpret_cast<const unsigned char*>((buffer++));
if (i == 3)
{
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (i = 0; (i < 4); i++)
{
ret += Base64Chars[char_array_4[i]];
}
i = 0;
}
}
if (i)
{
for (int j = i; j < 3; j++)
{
char_array_3[j] = '\0';
}
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (int j = 0; (j < i + 1); j++)
{
ret += Base64Chars[char_array_4[j]];
}
while ((i++ < 3))
{
ret += '=';
}
}
return ret;
}
/**
* @brief Decode Base64 formatstring
* @param[in] encoded_string to be decoded
* @return decoded string
*/
inline std::string Base64DecodePlainText(const std::string& encoded_string)
{
size_t in_len = encoded_string.size();
size_t i = 0;
size_t in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
while (in_len-- && (encoded_string[in_] != '=') && IsBase64(encoded_string[in_]))
{
char_array_4[i++] = encoded_string[in_]; in_++;
if (i == 4)
{
for (i = 0; i < 4; i++)
{
char_array_4[i] = static_cast<uint8_t>(Base64Chars.find(char_array_4[i]));
}
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
{
ret+=(reinterpret_cast<char*>(char_array_3)[i]);
}
i = 0;
}
}
if (i)
{
for (size_t j = i; j < 4; j++)
{
char_array_4[j] = 0;
}
for (size_t j = 0; j < 4; j++)
{
char_array_4[j] = static_cast<uint8_t>(Base64Chars.find(char_array_4[j]));
}
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (size_t j = 0; (j < i - 1); j++)
{
ret += (reinterpret_cast<char*>(char_array_3)[j]);
}
}
return ret;
}
/**
* @brief Encode to Base64 format
* @param[in] data buffer to be encoded to Base64
* @param[in] datalength buffer length to be encoded to Base64
* @return text encoded to format
*/
inline std::string Base64Encode(const char* data, std::size_t datalength)
{
const char* buffer = data;
std::size_t bytesLeft = datalength;
std::string ret;
int i = 0;
unsigned char char_array_3[3]{ 0 };
unsigned char char_array_4[4]{ 0 };
while (bytesLeft--)
{
char_array_3[i++] = *(buffer++);
if (i == 3)
{
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (i = 0; (i < 4); i++)
{
ret += Base64Chars[char_array_4[i]];
}
i = 0;
}
}
if (i)
{
for (int j = i; j < 3; j++)
{
char_array_3[j] = '\0';
}
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (int j = 0; (j < i + 1); j++)
{
ret += Base64Chars[char_array_4[j]];
}
while ((i++ < 3))
{
ret += '=';
}
}
return ret;
}
/**
* @brief Decode Base64 buffer
* @param[in] data buffer to be encoded to Base64
* @return decoded string
*/
template <class TConvert>
inline std::string Base64Encode(const TConvert& data)
{
return Base64Encode(reinterpret_cast<const char*>(&data), sizeof(data));
}
/**
* @brief Decode Base64 formatstring
* @param[in] encoded_string to be decoded
* @return decoded string
*/
inline std::vector<unsigned char> Base64Decode(const std::string& encoded_string)
{
size_t in_len = encoded_string.size();
size_t i = 0;
size_t in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::vector<unsigned char> ret;
while (in_len-- && (encoded_string[in_] != '=') && IsBase64(encoded_string[in_]))
{
char_array_4[i++] = encoded_string[in_]; in_++;
if (i == 4)
{
for (i = 0; i < 4; i++)
{
char_array_4[i] = static_cast<uint8_t>(Base64Chars.find(char_array_4[i]));
}
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (i = 0; (i < 3); i++)
{
ret.push_back(char_array_3[i]);
}
i = 0;
}
}
if (i)
{
for (size_t j = i; j < 4; j++)
{
char_array_4[j] = 0;
}
for (size_t j = 0; j < 4; j++)
{
char_array_4[j] = static_cast<uint8_t>(Base64Chars.find(char_array_4[j]));
}
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (size_t j = 0; (j < i - 1); j++)
{
ret.push_back(char_array_3[j]);
}
}
return ret;
// memcpy_s(&datablock, sizeof(datablock), ret.data(), ret.size());
}
/**
* @brief Decode Base64 formatstring
* @param[in] encoded_string to be decoded
* @return decoded string
*/
template <class TConvert>
inline TConvert DecodeBase64(const std::string& encoded_string)
{
TConvert out{};
std::vector<unsigned char> rawData = Base64Decode(encoded_string);
if (sizeof(out) == rawData.size())
{
memcpy_s(&out, sizeof(out), rawData.data(), rawData.size());
}
else
{
TConvert invalid{};
return invalid;
}
return out;
}
/**
* @brief Decode Base64 formatstring
* @param[in] encoded_string to be decoded
* @return decoded string
*/
template <>
inline std::string DecodeBase64(const std::string& encoded_string)
{
std::vector<unsigned char> rawData = Base64Decode(encoded_string);
std::string out(reinterpret_cast<char*>(rawData.data()), rawData.size());
return out;
}
#endif

View File

@@ -0,0 +1,726 @@
#include "cmdlnparser.h"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#pragma push_macro("interface")
#ifdef interface
#undef interface
#endif
#include <Windows.h>
#pragma pop_macro("interface")
#else
#include <unistd.h>
#include <linux/limits.h>
#include <sys/ioctl.h>
#endif
#include <algorithm>
#include <cstring>
CArgumentDefBase::~CArgumentDefBase()
{
}
bool CArgumentDefBase::CompareNameAndAssign(CArgumentIterator& rargit, const std::string& rssArgument,
const std::string& rssOptionName, bool bPartial) const
{
if (!m_ptrArgProvide) return false; // No variable definition assigned.
// Determine wherther the supplied argument is an option or a sub-option
size_t nPos = 0;
bool bOption = false;
bool bSubOption = false;
if (rssArgument.size() >= 2 && rssArgument[0] == '-' && rssArgument[1] == '-')
{
bSubOption = true;
nPos += 2;
}
else if (rssArgument.size() >= 1)
{
if (rssArgument[0] == '-')
bOption = true;
#ifdef _WIN32
if (rssArgument[0] == '/')
bOption = true;
#endif
if (bOption) nPos++;
}
// Differentiate between default and option arguments... default arguments do not have a name
if (!rssOptionName.empty()) // Option
{
if (!bOption && !bSubOption) return false;
// Determine the first non-alpha-numeric character that is not part of the name.
size_t nNameStartPos = nPos;
std::string ssArgNameCS, ssArgNameCI;
while (nPos < rssArgument.size())
{
char c = rssArgument[nPos];
if (!std::isalnum(c) && c != '_' && c != '?')
break;
ssArgNameCS += c;
ssArgNameCI += static_cast<char>(std::tolower(c));
nPos++;
}
// Check for correct length
if (!bPartial && rssOptionName.size() != (nPos - nNameStartPos))
return false;
// Check the name
bool bFound = false;
std::string ssArgDefNameCI = rssOptionName;
for (char& rc : ssArgDefNameCI) rc = static_cast<char>(std::tolower(rc));
if (m_rCLParser.CheckParseFlag(CCommandLine::EParseFlags::assignment_next_arg) || CheckFlag(EArgumentFlags::flag_option) ||
CheckFlag(EArgumentFlags::bool_option))
{
// Full fit needed
bFound |= CheckFlag(EArgumentFlags::case_sensitive) ?
ssArgNameCS == rssOptionName :
ssArgNameCI == ssArgDefNameCI;
}
else
{
// Partial fit allowed
size_t nMaxNameLen = bPartial ? rssOptionName.size() : (nPos - nNameStartPos);
bFound |= CheckFlag(EArgumentFlags::case_sensitive) ?
ssArgNameCS.substr(0, nMaxNameLen) == rssOptionName :
ssArgNameCI.substr(0, nMaxNameLen) == ssArgDefNameCI;
if (bFound)
nPos = nNameStartPos + nMaxNameLen;
}
// Found? If not, jump out of here
if (!bFound) return false;
}
// Get the value
std::string ssValue;
if (m_rCLParser.CheckParseFlag(CCommandLine::EParseFlags::assignment_next_arg) && !CheckFlag(EArgumentFlags::flag_option))
{
if (!CheckFlag(EArgumentFlags::bool_option))
{
auto optArg = rargit.GetNext();
if (!optArg)
{
SArgumentParseException exception("Missing arument value!");
exception.AddIndex(rargit.GetIndexOfLastArg());
exception.AddArgument(rssArgument);
throw exception;
}
ssValue = *optArg;
}
}
else
ssValue = helper::trim(rssArgument.substr(nPos));
// Check whether the argument was previously assigned and doesn't allow multiple assignments.
if (m_ptrArgProvide->IsArgumentAssigned() && !m_ptrArgProvide->AllowMultiArgumentAssign())
throw SArgumentParseException("Cannot supply more than one value for the option/argument!");
// Assign.
m_ptrArgProvide->ArgumentAssign(ssValue);
// Option is available at the command line.
m_bAvailableOnCommandLine = true;
return true;
}
void CArgumentDefBase::AddExample(const std::string& rssExample)
{
if (rssExample.empty()) return;
m_lstExamples.push_back(rssExample);
}
CCommandLine::CCommandLine(uint32_t uiFlags /* = static_cast<uint32_t>(EParseFlags::assignment_character)*/) :
m_uiParseFlags(uiFlags)
{}
CCommandLine::~CCommandLine()
{}
std::filesystem::path CCommandLine::GetApplicationPath() const
{
std::filesystem::path path;
#ifdef _WIN32
wchar_t szAppPath[MAX_PATH];
GetModuleFileNameW(NULL, szAppPath, MAX_PATH);
path = szAppPath;
#else
char szAppPath[PATH_MAX];
ssize_t nCount = readlink("/proc/self/exe", szAppPath, PATH_MAX);
path = std::string(szAppPath, (nCount > 0) ? nCount : 0);
#endif
return path;
}
void CCommandLine::DefineGroup(const std::string& rssTitle, const std::string& rssDescription /*= std::string{}*/)
{
if (rssTitle.empty()) throw SArgumentParseException("No group title provided!");
m_ptrCurrentGroup = std::make_shared<SGroupDef>();
m_ptrCurrentGroup->ssTitle = rssTitle;
if (!rssDescription.empty()) m_ptrCurrentGroup->ssDescription = rssDescription;
}
void CCommandLine::PrintFixedWidth(size_t nWidth)
{
m_nFixedWidth = nWidth;
}
size_t CCommandLine::PrintFixedWidth() const
{
return m_nFixedWidth;
}
void CCommandLine::PrintMaxWidth(size_t nWidth)
{
m_nFixedWidth = 0;
m_nMaxWidth = nWidth;
}
size_t CCommandLine::PrintMaxWidth() const
{
return m_nFixedWidth ? m_nFixedWidth : m_nMaxWidth;
}
void CCommandLine::PrintSyntax(bool bEnable)
{
m_bSyntaxPrint = bEnable;
}
bool CCommandLine::PrintSyntax() const
{
return m_bSyntaxPrint;
}
void CCommandLine::PrintHelp(std::ostream& rstream, const std::string& rssHelpText /*= std::string{}*/,
size_t nArgumentGroup /*= 0*/) const
{
// Auto detection of the printable width?
size_t nPrintWidth = m_nFixedWidth;
if (!nPrintWidth)
{
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO sScreenBufferInfo = {};
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &sScreenBufferInfo))
nPrintWidth = static_cast<size_t>(sScreenBufferInfo.srWindow.Right) - static_cast<size_t>(sScreenBufferInfo.srWindow.Left) + 1;
#else
struct winsize ws;
ioctl(0, TIOCGWINSZ, &ws);
nPrintWidth = ws.ws_col;
#endif
if (m_nMaxWidth > 0) // ignore if it's 0
{
// Max width?
if (nPrintWidth > m_nMaxWidth)
nPrintWidth = m_nMaxWidth;
}
// Minimum is fixed...
if (nPrintWidth < 40)
nPrintWidth = 40;
}
// Print the help text if provided.
if (!rssHelpText.empty()) PrintHelpText(rstream, rssHelpText, nPrintWidth);
// Get the help text of the default argument
std::string ssDefaultArg;
if (m_ptrDefaultArg)
ssDefaultArg = m_ptrDefaultArg->GetHelpText();
// Print the syntax
size_t nOptionCnt = m_lstOptionArgs.size();
if (m_bSyntaxPrint)
{
rstream << std::endl << "Syntax: " << GetApplicationPath().filename().u8string();
if (!ssDefaultArg.empty())
rstream << " <" << ssDefaultArg << ">";
if (nOptionCnt)
rstream << " [options...]";
rstream << std::endl;
}
// Print the options
if (nOptionCnt)
{
// Determine the length of the longest argument
size_t nStartOfTextBlock = 0;
auto fnLongestArgName = [&](const std::shared_ptr<CArgumentDefBase>& rptrArgument) -> void
{
// Make a combined argument from argument parts
std::string ssArgument;
for (const CArgumentDefBase::SOptionName& rsOptionName : rptrArgument->GetOptionNames())
{
if (!ssArgument.empty())
ssArgument += std::string(", ");
if (rsOptionName.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument))
ssArgument += "-";
else
ssArgument += "--";
ssArgument += rsOptionName.ssName;
}
nStartOfTextBlock =
std::max((ssArgument + rptrArgument->GetArgumentVar()->GetArgumentOptionMarkup()).size(), nStartOfTextBlock);
};
for (const std::shared_ptr<CArgumentDefBase>& rptrArgument : m_lstOptionArgs)
{
if (!rptrArgument->PartOfArgumentGroup(nArgumentGroup)) continue;
fnLongestArgName(rptrArgument);
}
// Add the space at the beginning and two spaces between argument name and the text
nStartOfTextBlock += 3;
// Print the option arguments
std::string ssGroup;
auto fnPrintArg = [&](const std::shared_ptr<CArgumentDefBase>& rptrArgument) -> void
{
// Get the group information
std::shared_ptr<SGroupDef> ptrGroup = rptrArgument->GetGroup();
std::string ssTitle;
if (ptrGroup) ssTitle = ptrGroup->ssTitle;
if (ssTitle.empty()) ssTitle = "General options";
std::string ssDescription;
if (ptrGroup) ssDescription = ptrGroup->ssDescription;
// If there is a new group, print the information
if (ssTitle != ssGroup)
{
rstream << std::endl;
rstream << ssTitle << ":" << std::endl;
ssGroup = ssTitle;
if (!ssDescription.empty())
rstream << ssDescription << std::endl;
}
// Make a combined argument from argument parts
std::string ssArgument;
for (const CArgumentDefBase::SOptionName& rsOptionName : rptrArgument->GetOptionNames())
{
if (!ssArgument.empty())
ssArgument += std::string(", ");
if (rsOptionName.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument))
ssArgument += "-";
else
ssArgument += "--";
ssArgument += rsOptionName.ssName;
}
// The argument
rstream << " ";
rstream << ssArgument << rptrArgument->GetArgumentVar()->GetArgumentOptionMarkup() << " ";
// Calculate the starting point of the current help text (including dash and spaces)
size_t nTextStart =
std::max(nStartOfTextBlock, (ssArgument + rptrArgument->GetArgumentVar()->GetArgumentOptionMarkup()).size() + 3);
// Whitespace between the argument and the help text
for (size_t n = 3 + (ssArgument + rptrArgument->GetArgumentVar()->GetArgumentOptionMarkup()).size();
n < nTextStart; n++)
rstream << " ";
// If the markup is different than the argument, add the markup to the helptext
std::string ssHelpText = rptrArgument->GetHelpText() + "\n";
// Add the option details for this argument
std::string ssOptionDetails =
rptrArgument->GetArgumentVar()->GetArgumentOptionDetails(nPrintWidth - nStartOfTextBlock - 1);
if (ssOptionDetails.size())
{
ssHelpText += ssOptionDetails;
ssHelpText += "\n";
}
// If there are examples, add the examples to the helptext
if (rptrArgument->GetExamples().size())
{
ssHelpText += (rptrArgument->GetExamples().size() == 1 ? "Example:\n" : "Examples:\n");
for (const std::string& rssExample : rptrArgument->GetExamples())
{
ssHelpText += rssExample + "\n";
}
}
// Print the block of text
PrintBlock(rstream, ssHelpText, nStartOfTextBlock, nTextStart, nPrintWidth - 1);
};
for (const std::shared_ptr<CArgumentDefBase>& rptrArgument : m_lstOptionArgs)
{
if (!rptrArgument->PartOfArgumentGroup(nArgumentGroup)) continue;
fnPrintArg(rptrArgument);
}
}
}
void CCommandLine::PrintHelpText(std::ostream& rstream, const std::string& rssHelpText, size_t nPrintWidth /*= 0*/)
{
// Auto detection of the printable width?
if (!nPrintWidth)
{
#ifdef _WIN32
CONSOLE_SCREEN_BUFFER_INFO sScreenBufferInfo = {};
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &sScreenBufferInfo))
nPrintWidth = static_cast<size_t>(sScreenBufferInfo.srWindow.Right) - static_cast<size_t>(sScreenBufferInfo.srWindow.Left) + 1;
#else
struct winsize ws;
ioctl(0, TIOCGWINSZ, &ws);
nPrintWidth = ws.ws_col;
#endif
if (nPrintWidth < 40)
nPrintWidth = 80;
}
// Print the help text if provided.
size_t nPos = 0;
size_t nLen = rssHelpText.empty() ? 0 : rssHelpText.size();
size_t nScreenPos = 0;
std::string ssSpace;
std::string ssWord;
std::string ssIndent;
auto fnPrintWord = [&]()
{
// Something to do?
if (!ssWord.size()) return;
// Indentation detection
if (!nScreenPos)
{
if (ssSpace.size())
ssIndent = ssSpace;
else
ssIndent.clear();
}
// Newline needed?
size_t nLastPos = nScreenPos + ssSpace.size() + ssWord.size();
if (nLastPos >= nPrintWidth - 1)
{
rstream << std::endl << ssIndent;
nScreenPos = ssIndent.size();
ssSpace.clear();
}
else
{
rstream << ssSpace;
nScreenPos += ssSpace.size();
ssSpace.clear();
}
rstream << ssWord;
nScreenPos += ssWord.size();
ssWord.clear();
};
// Build spaces and words and stream them
while (nPos < nLen)
{
char c = rssHelpText[nPos++];
if (std::strchr("\t\n\r\f\v ", c))
{
// Is there a word? Print the word...
if (!ssWord.empty())
fnPrintWord();
// Add the space....
switch (c)
{
case '\n': // Newline
rstream << std::endl;
nScreenPos = 0;
ssSpace.clear();
break;
case ' ': // Space
ssSpace += c;
break;
case '\t': // Tab - convert to space
ssSpace += std::string(4 - nScreenPos % 4, ' ');
break;
default: // Skip all other space characters
break;
}
continue;
}
// Collect all characters for the word
ssWord += c;
}
fnPrintWord();
}
void CCommandLine::DumpArguments(std::ostream& rstream, bool bAll /*= true*/) const
{
rstream << "Dumping arguments:\n";
// Determine the length of the longest argument
size_t nStartOfTextBlock = 9; // Minimum is 9
auto fnLongestArgName = [&](const std::shared_ptr<CArgumentDefBase>& rptrArgument) -> void
{
// Make a combined argument from argument parts
std::string ssArgument;
for (const CArgumentDefBase::SOptionName& rsOptionName : rptrArgument->GetOptionNames())
{
if (!ssArgument.empty())
ssArgument += std::string(", ");
if (rsOptionName.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument))
ssArgument += "-";
else
ssArgument += "--";
ssArgument += rsOptionName.ssName;
}
nStartOfTextBlock = std::max(ssArgument.size(), nStartOfTextBlock);
};
for (const std::shared_ptr<CArgumentDefBase>& rptrArgument : m_lstOptionArgs)
fnLongestArgName(rptrArgument);
// Add a double space (" ") at the beginning and the dash and space ("- ") between argument name and the value
nStartOfTextBlock += 4;
// Maximum start block is 30
nStartOfTextBlock = std::min(static_cast<size_t>(30), nStartOfTextBlock);
// Print header
rstream << " Argument";
for (size_t n = 10; n < nStartOfTextBlock; n++)
rstream << " ";
rstream << " | Set | Value\n";
for (size_t n = 0; n < nStartOfTextBlock; n++)
rstream << "-";
rstream << "+-----+------------------------------\n";
// Print the arguments
auto fnDumpArgs = [&](const std::shared_ptr<CArgumentDefBase>& rptrArgument) -> void
{
// Make a combined argument from argument parts
std::string ssArgument;
for (const CArgumentDefBase::SOptionName& rsOptionName : rptrArgument->GetOptionNames())
{
if (!ssArgument.empty())
ssArgument += std::string(", ");
if (rsOptionName.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument))
ssArgument += "-";
else
ssArgument += "--";
ssArgument += rsOptionName.ssName;
}
if (!bAll && !rptrArgument->GetArgumentVar()->IsArgumentAssigned()) return;
// The argument
std::string ssArgumentName = ssArgument.empty() ? std::string("<default>") : (std::string("-") + ssArgument);
rstream << " " << ssArgumentName;
// Calculate the starting point of the current help text
size_t nTextStart = std::max(nStartOfTextBlock, ssArgumentName.size() + 3);
// Whitespace between the argument and the help text
for (size_t n = 1 + ssArgumentName.size(); n < nTextStart; n++)
rstream << " ";
// Print whether the argument was set or not
rstream << (rptrArgument->GetArgumentVar()->IsArgumentAssigned() ? "| yes | " : "| no | ");
// The value
rstream << rptrArgument->GetArgumentVar()->GetArgumentValueString() << std::endl;
};
if (m_ptrDefaultArg) fnDumpArgs(m_ptrDefaultArg);
for (const std::shared_ptr<CArgumentDefBase>& rptrArgument : m_lstOptionArgs)
fnDumpArgs(rptrArgument);
}
std::vector<std::string> CCommandLine::IncompatibleArguments(size_t nArgumentGroup, bool bFull /*= true*/) const
{
std::vector<std::string> vecIncompatible;
for (auto& prArgument : m_lstSupplied)
{
// Only valid for options, not for default arguments
if (prArgument.first.get().CheckFlag(EArgumentFlags::default_argument)) continue;
// Is the argument compatible?
if (prArgument.first.get().PartOfArgumentGroup(nArgumentGroup)) continue;
// Full option requested?
if (bFull)
{
vecIncompatible.push_back(prArgument.second);
continue;
}
// Make lower case string
auto fnMakeLower = [](const std::string& rss)
{
std::string rssTemp;
rssTemp.reserve(rss.size());
for (char c : rss)
rssTemp += static_cast<char>(std::tolower(c));
return rssTemp;
};
// Find the option text
std::string ssOptionText;
std::string ssArgumentLC = fnMakeLower(prArgument.second);
for (auto& rsOptionText : prArgument.first.get().GetOptionNames())
{
std::string ssOptionNameLC = fnMakeLower(rsOptionText.ssName);
std::string ssOptionLC = std::string("-") + ssOptionNameLC;
if (rsOptionText.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument) &&
ssArgumentLC.substr(0, ssOptionLC.size()) == ssOptionLC)
{
ssOptionText = prArgument.second.substr(0, ssOptionLC.size());
break;
}
#ifdef _WIN32
ssOptionLC = std::string("/") + ssOptionNameLC;
if (rsOptionText.uiFlags & static_cast<uint32_t>(EArgumentFlags::option_argument) &&
ssArgumentLC.substr(0, ssOptionLC.size()) == ssOptionLC)
{
ssOptionText = prArgument.second.substr(0, ssOptionLC.size());
break;
}
#endif
ssOptionLC = std::string("--") + ssOptionNameLC;
if (rsOptionText.uiFlags & static_cast<uint32_t>(EArgumentFlags::sub_option_argument) &&
ssArgumentLC.substr(0, ssOptionLC.size()) == ssOptionLC)
{
ssOptionText = prArgument.second.substr(0, ssOptionLC.size());
break;
}
}
// Just in case... should have been
if (ssOptionText.empty())
ssOptionText = prArgument.second;
// Store the options
vecIncompatible.push_back(ssOptionText);
}
return vecIncompatible;
}
void PrintBlock(std::ostream& rstream, const std::string& rssText, size_t nIndentPos, size_t nCurrentPos, size_t nMaxPos)
{
// Scan through the help text and add the text word for word, adding \n if necessary
size_t nScanPos = 0, nPrintPos = 0;
do
{
// Determine whether the next text is white space
bool bDoProcess = false;
bool bEOL = false;
switch (rssText[nScanPos])
{
case ' ':
case '\t':
case '\n':
bDoProcess = true;
break;
default:
break;
}
// Special case, the last character
if (nScanPos == rssText.size() - 1)
{
if (!bDoProcess) // Include when this is not whitespace
nScanPos++;
bEOL = true;
bDoProcess = true;
}
// When not whitespace, check the next scan
if (!bDoProcess)
{
nScanPos++;
continue;
}
// Check whether the text still fits (or if not, whether we are not exactly at
// the beginning of a new text block
if (nCurrentPos + (nScanPos - nPrintPos) > nMaxPos &&
nCurrentPos != nIndentPos)
{
// Insert a newline
rstream << std::endl;
// And fill up whitespace
for (size_t n = 0; n < nIndentPos; n++)
rstream << " ";
nCurrentPos = nIndentPos;
}
// Print the text until the end of the line or the scan pos
size_t nTextLen = std::min(nScanPos - nPrintPos, nMaxPos - nCurrentPos);
rstream << rssText.substr(nPrintPos, nTextLen);
nPrintPos += nTextLen;
nScanPos = nPrintPos;
nCurrentPos += nTextLen;
if (nScanPos >= rssText.size()) continue;
// Print the whitespace at nScanPos
char chTemp = rssText[nScanPos]; // Needed since the scan position changes
switch (chTemp)
{
case ' ':
// Increase the position
nCurrentPos++;
// Beyond the end of the line?
if (nCurrentPos < nMaxPos)
rstream << " ";
break;
case '\t':
// Beyond the end of the line?
if (nCurrentPos + (8 - nCurrentPos % 8) > nMaxPos)
nCurrentPos = nMaxPos + 1;
nPrintPos++;
nScanPos++;
break;
case '\n':
rstream << std::endl;
nPrintPos++;
nScanPos++;
// Do not indent any more with the last character
for (size_t n = 0; !bEOL && n < nIndentPos; n++)
rstream << " ";
nCurrentPos = nIndentPos;
break;
default:
break;
}
// Newline needed?
if (nCurrentPos > nMaxPos)
{
rstream << std::endl;
// Do not indent any more with the last character
for (size_t n = 0; !bEOL && n < nIndentPos; n++)
rstream << " ";
nCurrentPos = nIndentPos;
}
// Print the tab and increase the scan position
switch (chTemp)
{
case '\t':
nCurrentPos += 8 - nCurrentPos % 8;
rstream << "\t";
break;
case '\n':
break;
case ' ':
nPrintPos++;
nScanPos++;
break;
default:
break;
}
// Hypothetical comparison; is always true. This is wanted. Suppress CppCheck warning
// cppcheck-suppress knownConditionTrueFalse
} while (nScanPos < rssText.size());
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1190
global/dbcparser/dbcparser.h Normal file

File diff suppressed because it is too large Load Diff

207
global/debug_log.h Normal file
View File

@@ -0,0 +1,207 @@
#ifndef DEBUG_LOG_H
#define DEBUG_LOG_H
#include <iostream>
#include <ostream>
#include <fstream>
#include <mutex>
#include <filesystem>
#include <string>
#include <sstream>
#include "exec_dir_helper.h"
/**
* @brief Enable debug log by defining it and assigning it the value 1.
*/
#define ENABLE_DEBUG_LOG 1
/**
* @brief Debug namespace
*/
namespace debug
{
/**
* @brief Logger class, creating a log file at the location of the executable and using the same filename as the executable
* with the .log extension.
*/
class CLogger
{
public:
/**
* @brief Constructor, creating the file. If the file exists, it will be overwritten.
*/
CLogger()
{
m_pathLogFile = GetExecDirectory() / GetExecFilename().replace_extension(".log");
std::unique_lock<std::mutex> lock(m_mtxLogger);
std::ofstream fstream(m_pathLogFile, std::ios_base::out | std::ios_base::trunc);
if (fstream.is_open())
{
fstream << "Starting log of " << GetExecFilename().generic_u8string() << std::endl;
fstream.close();
}
std::clog << "Starting log of " << GetExecFilename().generic_u8string() << std::flush << std::endl;
}
/**
* @brief Destructor
*/
~CLogger()
{
std::unique_lock<std::mutex> lock(m_mtxLogger);
std::ofstream fstream(m_pathLogFile, std::ios_base::out | std::ios_base::app);
if (fstream.is_open())
{
fstream << "End of log..." << std::endl;
fstream.close();
}
std::clog << "End of log..." << std::flush << std::endl;
}
/**
* @brief Log an entry. The text will be indented dependable on the depth of the logging.
* @param[in] rss Reference to the string to log.
*/
void Log(const std::string& rss)
{
std::unique_lock<std::mutex> lock(m_mtxLogger);
std::ofstream fstream(m_pathLogFile, std::ios_base::out | std::ios_base::app);
std::string ssIndent(m_nDepth, '>');
if (!ssIndent.empty()) ssIndent += ' ';
if (fstream.is_open())
{
fstream << ssIndent << rss << std::endl;
fstream.close();
}
std::clog << ssIndent << rss << std::flush << std::endl;
}
/**
* @brief Increase the depth. This will indent the log.
*/
void IncrDepth()
{
m_nDepth++;
}
/**
* @brief Decrease the depth. This might decrease the indentation.
*/
void DecrDepth()
{
if (m_nDepth)
m_nDepth--;
}
private:
std::filesystem::path m_pathLogFile; ///< Path to the log file.
std::mutex m_mtxLogger; ///< Protect against multiple log entries at the same time.
size_t m_nDepth; ///< Depth level for indentation.
};
/**
* @brief Global logger function. A call to this function will cause an instance to be available.
* @return Reference to the logger.
*/
inline CLogger& GetLogger()
{
static CLogger logger;
return logger;
}
/**
* @brief Function logger class logging the entering and leaving of the function if places at the beginning of the function.
*/
class CFuncLogger
{
public:
/**
* @brief Constructor creating a log entry for entering the function
* @param[in] rssFunc Reference to the function name.
* @param[in] rssFile Reference to the source file the function is implemented in.
* @param[in] nLine Reference to the line the function logger object is created.
*/
CFuncLogger(const std::string& rssFunc, const std::string& rssFile, size_t nLine) : m_ssFunc(rssFunc)
{
GetLogger().Log(std::string("Enter function:") + rssFunc + " - file " + rssFile + " - line " + std::to_string(nLine));
GetLogger().IncrDepth();
}
/**
* @brief Destructor creating a log entry for leaving the function.
*/
~CFuncLogger()
{
GetLogger().DecrDepth();
GetLogger().Log(std::string("Leave function:") + m_ssFunc);
}
/**
* @brief Log a function checkpoint.
* @param[in] nLine The line number of this checkpoint.
*/
void Checkpoint(size_t nLine)
{
GetLogger().Log(std::string("Checkpoint #") + std::to_string(m_nCounter++) + " - line " + std::to_string(nLine));
}
/**
* @brief Log a string.
* @param[in] rss Reference to the string to log.
*/
void Log(const std::string& rss) const
{
GetLogger().Log(rss);
}
private:
std::string m_ssFunc; ///< Name of the function.
size_t m_nCounter = 1; ///< Checkpoint counter.
};
} // namespace debug
#if ENABLE_DEBUG_LOG == 1
#ifdef _MSC_VER
/**
* @brief Macro to create a function logger object using the function name, the file name and the line number from the compiler.
*/
#define FUNC_LOGGER() debug::CFuncLogger logger(__FUNCSIG__, __FILE__, __LINE__)
#else
/**
* @brief Macro to create a function logger object using the function name, the file name and the line number from the compiler.
*/
#define FUNC_LOGGER() debug::CFuncLogger logger(__PRETTY_FUNCTION__, __FILE__, __LINE__)
#endif
/**
* @brief Macro to set a checkpoint providing the line number from the compiler.
*/
#define CHECKPOINT() logger.Checkpoint(__LINE__)
/**
* @brief Log a message
*/
#define FUNC_LOG(msg) logger.Log(msg)
#else
/**
* @brief Macro to create a function logger object using the function name, the file name and the line number from the compiler
* (disabled).
*/
#define FUNC_LOGGER()
/**
* @brief Macro to set a checkpoint providing the line number from the compiler (disabled).
*/
#define CHECKPOINT()
/**
* @brief Log a message (disabled)
*/
#define FUNC_LOG(msg)
#endif
#endif // !defined DEBUG_LOG_H

96
global/exec_dir_helper.h Normal file
View File

@@ -0,0 +1,96 @@
/**
*
* @file exec_dir_helper.h
* @brief This file contains helper functions e.g. filesystem
* @version 0.1
* @date 2022.11.14
* @author Thomas.pfleiderer@zf.com
* @copyright Copyright ZF Friedrichshaven AG (c) 2022
*
*/
#ifndef EXEC_DIR_HELPER_H
#define EXEC_DIR_HELPER_H
#include <filesystem>
#include <string>
#ifdef _WIN32
#pragma push_macro("interface")
#undef interface
#pragma push_macro("GetObject")
#undef GetObject
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <WinSock2.h>
#include <Windows.h>
#include <array>
// Resolve conflict
#pragma pop_macro("GetObject")
#pragma pop_macro("interface")
#ifdef GetClassInfo
#undef GetClassInfo
#endif
#elif defined __linux__
#include <linux/limits.h>
#include <unistd.h>
#else
#error OS is not supported
#endif
/**
* @brief Get the absolute path to the currently run executable
* @return Absolute std::filesystem::path to the currently run executable
*/
inline std::filesystem::path GetExecDirectory()
{
static std::filesystem::path pathExeDir;
if (!pathExeDir.empty()) return pathExeDir;
#ifdef _WIN32
// Windows specific
std::wstring ssPath(32768, '\0');
GetModuleFileNameW(NULL, ssPath.data(), static_cast<DWORD>(ssPath.size() - 1));
#elif defined __linux__
// Linux specific
std::string ssPath(PATH_MAX + 1, '\0');
const ssize_t nCount = readlink("/proc/self/exe", ssPath.data(), PATH_MAX);
if (nCount < 0 || nCount >= PATH_MAX)
return pathExeDir; // some error
ssPath.at(nCount) = '\0';
#else
#error OS is not supported!
#endif
pathExeDir = std::filesystem::path{ssPath.c_str()}.parent_path() / ""; // To finish the folder path with (back)slash
return pathExeDir;
}
/**
* @brief Get the filename of the currently run executable
* @return The filename to the currently run executable
*/
inline std::filesystem::path GetExecFilename()
{
static std::filesystem::path pathExeFilename;
if (!pathExeFilename.empty()) return pathExeFilename;
#ifdef _WIN32
// Windows specific
std::wstring ssPath(32768, '\0');
GetModuleFileNameW(NULL, ssPath.data(), static_cast<DWORD>(ssPath.size() - 1));
#elif defined __linux__
// Linux specific
std::string ssPath(PATH_MAX + 1, '\0');
const ssize_t nCount = readlink("/proc/self/exe", ssPath.data(), PATH_MAX);
if (nCount < 0 || nCount >= PATH_MAX)
return pathExeFilename; // some error
ssPath.at(nCount) = '\0';
#else
#error OS is not supported!
#endif
pathExeFilename = std::filesystem::path{ssPath.c_str()}.filename();
return pathExeFilename;
}
#endif // !defined EXEC_DIR_HELPER_H

143
global/filesystem_helper.h Normal file
View File

@@ -0,0 +1,143 @@
#ifndef FILESYSTEM_HELPER
#define FILESYSTEM_HELPER
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <set>
#include <regex>
/**
* @brief Check for a match of the path with the pattern containing zero or more wildcards.
* @param[in] rpath Reference to the path to compare.
* @param[in] rpathPattern Reference to the path containing zero or more wildcards.
* @return Returns 'true' when the paths match, 'false' when not.
*/
inline bool MatchFileWithWildcards(const std::filesystem::path& rpath, const std::filesystem::path& rpathPattern)
{
std::regex regexStarReplace("\\*");
std::regex regexQuestionmarkReplace("\\?");
std::string ssWildcardPattern = std::regex_replace(
std::regex_replace(rpathPattern.generic_u8string(), regexStarReplace, ".*"),
regexQuestionmarkReplace, ".");
std::regex regexWildcardPattern("^" + ssWildcardPattern + "$");
return std::regex_match(rpath.generic_u8string(), regexWildcardPattern);
}
/**
* @brief Find the files using a split-up vector containing the path chunks and potential wildcard patterns.
* @param[in] rvecFiles Reference to the list of files being found. This list will be extended iteratively.
* @param[in] rpathBase Reference to the base path to match with the path/pattern chunks from the vector starting at the specified
* index.
* @param[in] rvecPatternChunks Reference with the split search path containing the path/pattern chunks.
* @param[in] nIndex The current index that must match with the sub-directories and files in the base path.
* @param[in] rsetSearch Set of search paths being updated to prevent searching a path multiple times. Searching multiple times
* cannot be excluded when using generic wildcards that could represent zero or more sub-directories "*".
*/
void FindFilesWithWildcards(std::vector<std::filesystem::path>& rvecFiles, const std::filesystem::path& rpathBase,
const std::vector<std::string>& rvecPatternChunks, size_t nIndex, std::set<std::filesystem::path>& rsetSearch)
{
// Done if itPos points to the end of the split vector
if (nIndex >= rvecPatternChunks.size())
{
// If this is a file, add the file to the list
if (std::filesystem::is_regular_file(rpathBase))
rvecFiles.push_back(rpathBase);
else if (std::filesystem::is_directory(rpathBase))
{
// Recursively iterate through the directories and add all files.
for (auto const& dir_entry : std::filesystem::recursive_directory_iterator{ rpathBase })
{
if (std::filesystem::is_regular_file(dir_entry.path()))
rvecFiles.push_back(dir_entry.path());
}
}
return;
}
const std::string& rssPatternChunk = rvecPatternChunks[nIndex];
// Further processing is only possible if the pathBase is a directory...
if (!std::filesystem::is_directory(rpathBase))
{
// In some rare cases, the path cannot be identified as directory, but still is one. Add the next chunk.
FindFilesWithWildcards(rvecFiles, rpathBase / rssPatternChunk, rvecPatternChunks, nIndex + 1, rsetSearch);
return;
}
// Check whether the current position contains wildcards; if not, add it to the base path and go one deeper.
if (rvecPatternChunks[nIndex].find_first_of("*?") == std::string::npos)
{
FindFilesWithWildcards(rvecFiles, rpathBase / rssPatternChunk, rvecPatternChunks, nIndex + 1, rsetSearch);
return;
}
// Due to the use of wildcards, it could happen, that directories get searched multiple times. If searched before, skip another
// search...
std::filesystem::path pathSearchPath = rpathBase / rssPatternChunk;
if (rsetSearch.find(pathSearchPath) != rsetSearch.end()) return;
rsetSearch.insert(pathSearchPath);
// Iterator through the base directory to see if a matching path can be made
std::filesystem::path pathPattern = rpathBase;
pathPattern /= rssPatternChunk;
for (auto const& dir_entry : std::filesystem::directory_iterator{ rpathBase })
{
// Does the pattern fit?
if (MatchFileWithWildcards(dir_entry.path(), pathPattern))
{
// Special case: if the current string is "*" this might also be valid for any child path. Check this first...
if (rssPatternChunk == "*")
FindFilesWithWildcards(rvecFiles, dir_entry.path(), rvecPatternChunks, nIndex, rsetSearch);
// Increase the position of our pattern, check whether the path first
FindFilesWithWildcards(rvecFiles, dir_entry.path(), rvecPatternChunks, nIndex + 1, rsetSearch);
// Special case: if the current string is "*" the rest of the pattern might fit the current path (doing as if the
// wildcard doesn't exist).
if (rssPatternChunk == "*")
FindFilesWithWildcards(rvecFiles, rpathBase, rvecPatternChunks, nIndex + 1, rsetSearch);
}
}
}
///
/// @brief Find the files using the provided path.
/// @details Search for the files matching the providing search path. The search path could contain wildcards ('*' and '?').
/// Examples of path search criteria:
/// @code
/// /usr/* - all files in /usr and all sub-directories
/// /usr/*.sdv - all .sdv-files in /usr
/// /usr/*/*.sdv - all .sdv-files in /usr and all sub-directories
/// /usr/*/abc/*.sdv - looks in /usr for all .sdv-files in a sub-directory abc
/// /usr/abc*/*.sdv - looks in /usr for all directories starting with abc and takes the .sdv-files from this directories
/// @endcode
/// @param[in] rpathSearch Search path with zero or more wildcards. The path is assumed to be an absolute path.
/// @return Returns a vector with all the found files (full path).
///
inline std::vector<std::filesystem::path> FindFilesWithWildcards(const std::filesystem::path& rpathSearch)
{
// NOTE: The std::filesystem::path type has an iterator to iterate through the path. Unfortunately it is not possible to clone
// an iterator and iterate independently further... the clone operators on the same iterator as the original.
// The solution is to build a vector with the path-chunks and a vector index, which in any case is cloneable.
// Split the path
std::vector<std::filesystem::path> vecFiles;
std::vector<std::string> vecPatternChunks;
for (const std::filesystem::path& rpathChunk : rpathSearch)
vecPatternChunks.push_back(rpathChunk.u8string());
if (vecPatternChunks.empty()) return vecFiles;
// Build the root search path without the wildcard
std::set<std::filesystem::path> setSearch;
FindFilesWithWildcards(vecFiles, vecPatternChunks[0], vecPatternChunks, 1, setSearch);
return vecFiles;
}
#endif // !defined FILESYSTEM_HELPER

177
global/flags.h Normal file
View File

@@ -0,0 +1,177 @@
#ifndef FLAGS_HELPER_H
#define FLAGS_HELPER_H
#include <type_traits>
#include <initializer_list>
/**
* @brief Helper namespace
*/
namespace hlpr
{
/**
* @brief Generic flags type class based on flags defined in an enum.
* @tparam TEnum The enum type that is used to define flags.
*/
template <typename TEnum>
struct flags
{
// Only works with enums.
static_assert(std::is_enum_v<TEnum>);
/// The enum base type.
using enum_type = std::underlying_type_t<TEnum>;
/**
* @brief Default constructor
*/
flags() = default;
/**
* @brief Copy constructor
* @param[in] rflags Reference to the flags to copy from.
*/
flags(const flags& rflags) = default;
/**
* @brief Move constructor
* @param[in] rflags Reference to the flags to move from.
*/
flags(flags&& rflags) : _tValue(rflags._tValue) { rflags._tValue = 0; }
/**
* @brief Constructor with flags value assignment.
* @param[in] tValue The flags value to assign.
*/
explicit flags(enum_type tValue) : _tValue(tValue) {}
/**
* @brief Constructor with single value assignment.
* @param[in] eValue The single value to assign.
*/
flags(TEnum eValue) : _tValue(static_cast<enum_type>(eValue)) {}
/**
* @brief Constructor with initializer list
* @param[in] init The initializer list.
*/
flags(std::initializer_list<TEnum> init)
{
for (TEnum eValue : init)
_tValue |= static_cast<enum_type>(eValue);
}
/**
* @brief Flags class assignment.
* @param[in] rflags Reference to the flags to copy from.
* @return Returns a reference to this class.
*/
flags& operator=(const flags& rflags) = default;
/**
* @brief Flags move assignment.
* @param[in] rflags Reference to the flags to move from.
* @return Returns a reference to this class.
*/
flags& operator=(flags&& rflags) { _tValue = rflags._tValue; rflags._tValue = 0; return *this; }
/**
* @brief Flags value assignment.
* @param[in] tValue The flags value to assign.
* @return Returns a reference to this class.
*/
flags& operator=(enum_type tValue) { _tValue = tValue; return *this; }
/**
* @brief Single value assignment.
* @param[in] eValue The single value to assign.
* @return Returns a reference to this class.
*/
flags& operator=(TEnum eValue) { _tValue = static_cast<enum_type>(eValue); return *this; }
/**
* @brief Cast operator
* @return The flags value.
*/
operator enum_type() const { return _tValue; }
/**
* @brief Add a flag.
* @param[in] eValue The flag value to set.
* @return Reference to this class.
*/
flags& operator+=(TEnum eValue) { _tValue |= static_cast<enum_type>(eValue); return *this; }
/**
* @brief Remove a flag.
* @param[in] eValue The flag value to remove.
* @return Reference to this class.
*/
flags& operator-=(TEnum eValue) { _tValue &= ~static_cast<enum_type>(eValue); return *this; }
/**
* @brief Add a flag to a value copy.
* @param[in] eValue The flag value to set.
* @return The result flags.
*/
flags operator+(TEnum eValue) { flags flagsCopy(_tValue); flagsCopy.add(eValue); return flagsCopy; }
/**
* @brief Remove a flag from a value copy.
* @param[in] eValue The flag value to remove.
* @return The result flags.
*/
flags operator-(TEnum eValue) { flags flagsCopy(_tValue); flagsCopy.remove(eValue); return flagsCopy; }
/**
* @brief Check the flags for the availability of the flag.
* @param[in] eValue The flag to check for.
* @return Returns whether the flag was set or not.
*/
bool operator&(TEnum eValue) const { return (_tValue & static_cast<enum_type>(eValue)) ? true : false; }
/**
* @brief Return the flags value.
* @return The flags value.
*/
enum_type get() const { return _tValue; }
/**
* @brief Add a flag.
* @param[in] eValue The flag value to set.
*/
void add(TEnum eValue) { _tValue |= static_cast<enum_type>(eValue); }
/**
* @brief Remove a flag.
* @param[in] eValue The flag value to remove.
*/
void remove(TEnum eValue) { _tValue &= ~static_cast<enum_type>(eValue); }
/**
* @brief Check the flags for the availability of the flag.
* @param[in] eValue The flag to check for.
* @return Returns whether the flag was set or not.
*/
bool check(TEnum eValue) const { return _tValue & static_cast<enum_type>(eValue); }
/**
* @brief Check the flags for the availability of at least one flag.
* @param[in] flagsValue The flags to use for the check.
* @return Returns whether at least one flag was set.
*/
bool check_any(flags flagsValue) const { return _tValue & flagsValue._tValue; }
/**
* @brief Check the flags for the availability of all supplied flags.
* @remarks Check for the supplied flags. Additional flags might also be set, but have no influence on the outcome.
* @param[in] flagsValue The flags to use for the check.
* @return Returns whether all supplied flags were set.
*/
bool check_all(flags flagsValue) const { return (_tValue & flagsValue._tValue) == flagsValue._tValue; }
enum_type _tValue = 0; ///< The flag value.
};
}
#endif // !defined FLAGS_HELPER_H

252
global/ipc_named_mutex.h Normal file
View File

@@ -0,0 +1,252 @@
#ifndef IPC_NAMED_MUTEX_H
#define IPC_NAMED_MUTEX_H
#ifdef _WIN32
// Resolve conflict
#pragma push_macro("interface")
#undef interface
#ifndef NOMINMAX
#define NOMINMAX
#endif
//#include <WinSock2.h>
#include <Windows.h>
// Resolve conflict
#pragma pop_macro("interface")
#ifdef GetClassInfo
#undef GetClassInfo
#endif
#elif defined __unix__
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#else
#error OS is not supported!
#endif
#include <string>
#include <cstdlib>
/**
* @brief Inter-process-communication namespace
*/
namespace ipc
{
/**
* @brief Named mutex allowing interprocess synchronization. This mutex class can be used with the standard C++ library.
*/
class named_mutex
{
public:
#ifdef _WIN32
/// Native handle type.
using native_handle_type = HANDLE;
#elif defined __unix__
/// Native handle type.
using native_handle_type = sem_t*;
#else
#error OS is not supported!
#endif
/**
* @brief Named mutex constructor. Opens or creates a named mutex. This mutex is unlocked.
* @param[in] szName Pointer to the zero terminated name of the mutex. Can be null to automatically generate a name.
*/
named_mutex(const char* szName = nullptr) noexcept;
/**
* @brief Copy constructor is deleted.
*/
named_mutex(const named_mutex&) = delete;
/**
* @brief Move constructor.
* @param[in] rmtx Reference to the mutex to move from.
*/
named_mutex(named_mutex&& rmtx) noexcept;
/**
* @brief Destructor
*/
~named_mutex();
/**
* @brief Assignment operator is deleted.
* @return Reference to this class.
*/
named_mutex& operator=(const named_mutex&) = delete;
/**
* @brief Move operator.
* @param[in] rmtx Reference to the mutex to move from.
* @return Reference to this class.
*/
named_mutex& operator=(named_mutex&& rmtx) noexcept;
/**
* @brief Locks the mutex, blocks if the mutex is not available.
*/
void lock();
/**
* @brief Tries to lock the mutex, returns if the mutex is not available.
* @return Returns 'true' when the lock was successful; 'false' when not.
*/
bool try_lock();
/**
* @brief Unlocks the mutex.
*/
void unlock();
/**
* @brief Return the mutex name.
* @return Reference to the name of the mutex.
*/
const std::string& name() const;
/**
* @brief Return the underlying implementation-defined native handle object.
* @return Handle to the mutex.
*/
native_handle_type native_handle();
private:
std::string m_ssName; ///< Name of the mutex.
native_handle_type m_handle; ///< Mutex handle.
};
#ifdef _WIN32
inline named_mutex::named_mutex(const char* szName) noexcept : m_handle(nullptr)
{
if (szName)
m_ssName = szName;
else
{
std::srand(static_cast<unsigned>(std::time(nullptr))); // Use current time as seed for random generator
m_ssName = std::string("NAMED_MUTEX_") + std::to_string(std::rand());
}
m_handle = CreateMutexA(nullptr, FALSE, (std::string("Global\\") + m_ssName).c_str());
}
inline named_mutex::named_mutex(named_mutex&& rmtx) noexcept : m_handle(rmtx.m_handle)
{
rmtx.m_handle = nullptr;
}
inline named_mutex::~named_mutex()
{
if (m_handle && m_handle != INVALID_HANDLE_VALUE)
CloseHandle(m_handle);
}
// False positive warning of CppCheck: m_handle is not assigned a value. This is not the case. Suppress the warning.
// cppcheck-suppress operatorEqVarError
inline named_mutex& named_mutex::operator=(named_mutex&& rmtx) noexcept
{
if (m_handle && m_handle != INVALID_HANDLE_VALUE)
CloseHandle(m_handle);
m_handle = rmtx.m_handle;
rmtx.m_handle = nullptr;
return *this;
}
inline void named_mutex::lock()
{
if (m_handle && m_handle != INVALID_HANDLE_VALUE)
WaitForSingleObject(m_handle, INFINITE);
}
inline bool named_mutex::try_lock()
{
if (m_handle && m_handle != INVALID_HANDLE_VALUE)
return WaitForSingleObject(m_handle, 0) == WAIT_OBJECT_0;
else
return false;
}
inline void named_mutex::unlock()
{
if (m_handle && m_handle != INVALID_HANDLE_VALUE)
ReleaseMutex(m_handle);
}
inline const std::string& named_mutex::name() const
{
return m_ssName;
}
inline named_mutex::native_handle_type named_mutex::native_handle()
{
return m_handle;
}
#elif defined __unix__
inline named_mutex::named_mutex(const char* szName) noexcept : m_handle(nullptr)
{
if (szName)
m_ssName = szName;
else
{
std::srand(static_cast<unsigned>(std::time(nullptr))); // Use current time as seed for random generator
m_ssName = std::string("NAMED_MUTEX_") + std::to_string(std::rand());
}
m_handle = sem_open(m_ssName.c_str(), O_CREAT, 0777 /*O_RDWR*/, 1);
}
inline named_mutex::named_mutex(named_mutex&& rmtx) noexcept : m_handle(rmtx.m_handle)
{
rmtx.m_handle = nullptr;
}
inline named_mutex::~named_mutex()
{
if (m_handle)
sem_close(m_handle);
}
// False positive warning of CppCheck: m_handle is not assigned a value. This is not the case. Suppress the warning.
// cppcheck-suppress operatorEqVarError
inline named_mutex& named_mutex::operator=(named_mutex&& rmtx) noexcept
{
if (m_handle)
sem_close(m_handle);
m_handle = rmtx.m_handle;
rmtx.m_handle = nullptr;
return *this;
}
inline void named_mutex::lock()
{
if (m_handle)
sem_wait(m_handle);
}
inline bool named_mutex::try_lock()
{
return m_handle ? sem_trywait(m_handle) >= 0 : false;
}
inline void named_mutex::unlock()
{
if (m_handle)
sem_post(m_handle);
}
inline const std::string& named_mutex::name() const
{
return m_ssName;
}
inline named_mutex::native_handle_type named_mutex::native_handle()
{
return m_handle;
}
#else
#error OS is not supported!
#endif
}
#endif // !defined IPC_NAMED_MUTEX_H

112
global/localmemmgr.h Normal file
View File

@@ -0,0 +1,112 @@
#ifndef LOCAL_MEM_MGR_H
#define LOCAL_MEM_MGR_H
#define SDV_CORE_H
#define NO_SDV_CORE_FUNC
#include "../export/interfaces/core.h"
#include "../export//support/interface_ptr.h"
#include <support/mem_access.h>
#include <support/interface_ptr.h>
/**
* \brief Local memory manager class allowing the use of the SDV support classes without having to start the framework.
* \attention Do not use the local memory manager together with the framework's memory manager.
*/
class CLocalMemMgr : public sdv::core::IMemoryAlloc, public sdv::IInterfaceAccess,
public sdv::internal::IInternalMemAlloc
{
public:
/**
* \brief Constructor assigning this class to the local services.
*/
CLocalMemMgr()
{}
/**
* \brief Destructor removing this class from the local services.
*/
~CLocalMemMgr()
{}
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::IInterfaceAccess)
SDV_INTERFACE_ENTRY(sdv::core::IMemoryAlloc)
END_SDV_INTERFACE_MAP()
/**
* @brief Allocate a memory block of the provided length. Overload of sdv::core::IMemoryAlloc::Allocate.
* @param[in] uiLength The length of the memory block to allocate.
* @return Smart pointer to the allocated memory or NULL when memory allocation was not possible.
*/
virtual sdv::pointer<uint8_t> Allocate(uint32_t uiLength) override
{
return sdv::internal::make_ptr<uint8_t>(this, uiLength);
}
protected:
/**
* @brief Allocate memory. Overload of sdv::internal::IInternalMemAlloc::Alloc.
* @param[in] nSize The size of the memory to allocate (in bytes).
* @return Pointer to the memory allocation or NULL when memory allocation failed.
*/
virtual void* Alloc(size_t nSize) override
{
void* p = malloc(nSize);
return p;
}
/**
* @brief Reallocate memory. Overload of sdv::internal::IInternalMemAlloc::Realloc.
* @param[in] pData Pointer to a previous allocation or NULL when no previous allocation was available.
* @param[in] nSize The size of the memory to allocate (in bytes).
* @return Pointer to the memory allocation or NULL when memory allocation failed.
*/
virtual void* Realloc(void* pData, size_t nSize) override
{
void* p = realloc(pData, nSize);
return p;
}
/**
* @brief Free a memory allocation. Overload of sdv::internal::IInternalMemAlloc::Free.
* @param[in] pData Pointer to a previous allocation.
*/
virtual void Free(void* pData) override
{
free(pData);
}
};
#ifndef NO_SDV_LOCAL_CORE_FUNC
namespace sdv
{
namespace core
{
/**
* @brief Access to the core.
* @return Smart pointer to the core services interface.
*/
inline TInterfaceAccessPtr GetCore()
{
static CLocalMemMgr memmgr;
return &memmgr;
}
/**
* @brief Access to specific interface of the core.
* @tparam TInterface Type of interface to return.
* @return Pointer to the interface or NULL when the interface was not exposed.
*/
template <typename TInterface>
inline TInterface* GetCore()
{
return GetCore().GetInterface<TInterface>();
}
}
}
#endif
#endif // !defined(LOCAL_MEM_MGR_H)

638
global/path_match.h Normal file
View File

@@ -0,0 +1,638 @@
#ifndef WILDCARD_MATCH_H
#define WILDCARD_MATCH_H
#include <filesystem>
#include <string>
#include <vector>
#include <regex>
#include <stdexcept>
#include <stack>
#include <list>
/**
* @brief Check for a match between a relative path and a pattern containing zero or more wildcards.
* @details Check for the relative path matching a relative path-pattern possibly containing wildcard patterns. The following rules
* to patterns apply:
* - Definition of path-part: paths are split into directories optionally followed by a filename. As separator '/' is used.
* - A directory name of '.' has a special meaning directing to the current directory.
* - A directory name of '..' has a special meaning directing to a directory one up.
* - Directory and file name can be separated with dots (for example "dir1.2/file0.bin").
* - Within the path-part following zero or more characters, use '?' for any one character that might fit. The '?' might be
* followed by zero or more characters and wildcards.
* - Within the path-part following zero or more characters, use '*' for zero or more character until the next character or
* separator. The '*' might be followed by zero or more characters and wildcards.
* - It should be possible to use a directory as a pattern (without the file name) - then all the files of the directory are
* matching (but not the files of the subdirectories).
* - If the path-part equals '**', zero or more path-parts can be skipped to fit the rest of the path. This rule doesn't apply if
* the part-part equals '*' and is the last part in the path.
* Examples are:
* - "subdir1" - directory only; all files within the directory (but not any sub-directories) match.
* - "subdir1" - all files within the directory match.
* - "subdir2/file2?.*n" - all files located in subdir2 starting with "file2" followed by a single digit or character with the
* extension ending with "n" match.
* - "subdir?_*" - all files from a directory starting with the name "subdir" followed by a single digit or character, followed
* with a "_" and zero or more characters.
* - "**\/file*.bin" - all files starting with the name "file" followed by zero or more characters and the extension ".bin" in this
* and any subdirectory.
* @param[in] rpathRel Reference to the relative path to check for a match.
* @param[in] rssPattern Reference to the string containing the pattern to match.
* @return Returns whether the path matches.
*/
inline bool CheckWildcardPathMatch(const std::filesystem::path& rpathRel, const std::string& rssPattern)
{
std::filesystem::path pathPattern = std::filesystem::u8path(rssPattern);
auto itPathRel = rpathRel.begin();
auto itPathPattern = pathPattern.begin();
// Iterate through the paths
bool bLastPartAsterisk = false; // Special case when the last checked part is a single asterisk.
while (itPathRel != rpathRel.end() && itPathPattern != pathPattern.end())
{
// Get the parts and increase
std::string ssPathPart = itPathRel->generic_u8string();
std::string ssPatternPart = itPathPattern->generic_u8string();
itPathRel++;
itPathPattern++;
// Compose a new path from the rest of the path starting at the provided iterator.
auto fnBuildNewPath = [](std::filesystem::path::iterator& ritPath, const std::filesystem::path& rpath)
{
std::filesystem::path pathNew;
while (ritPath != rpath.end())
{
pathNew /= *ritPath;
++ritPath;
}
return pathNew;
};
// Special case... skipping zero or more path parts.
if (ssPatternPart == "**")
{
// The pattern "**/.." or even multiple higher directories like "**/../.." would indicate that any number of directories
// can be skipped, but at least the amount of directories need to be present to be able to go higher. By building
// potential relative paths (below), skip the amount of paths going higher.
size_t nSkipParent = 0;
while (itPathPattern != pathPattern.end() && itPathPattern->generic_u8string() == "..")
{
itPathPattern++;
nSkipParent++;
}
// Build a new pattern from the rest of the pattern.
std::filesystem::path pathNewPattern = fnBuildNewPath(itPathPattern, pathPattern);
// Build a list of new potential relative paths.
// NOTE: More efficient would be to execute the matching algorithm for each path-part until the end of the path.
// Since the path iterator doesn't allow copying like it does with the container classes, it doesn't work to reuse the
// ierator to build each new path on the fly. Instead, first create a list of potential paths and then execute the
// matching process.
std::vector<std::filesystem::path> vecNewRelPaths;
itPathRel--; // Include the current path part as well.
while (itPathRel != rpathRel.end())
{
// Skip the amount of parent parts if ".." was detected.
if (nSkipParent)
{
itPathRel++;
nSkipParent--;
continue;
}
// Add to all existing paths in the vector
for (auto& rpath : vecNewRelPaths)
rpath /= *itPathRel;
// Add as a separate part to the vector
vecNewRelPaths.push_back(*itPathRel);
// Next part
itPathRel++;
}
// When the amount of parents to be skipped is higher than 0, there is no parent to skip anymore.
if (nSkipParent) return false;
// Iterate through the list of potential relative paths and check for at least one fit...
for (const auto& rpathNewRel : vecNewRelPaths)
{
// Check for this path. If it matches... it is valid.
if (CheckWildcardPathMatch(rpathNewRel, pathNewPattern.generic_u8string()))
return true;
}
// If none fit, there is no match.
return false;
}
// Special case... ignore current test.
if (ssPatternPart == ".")
{
// Redo the last path iteration and continue with the matching process.
itPathRel--;
continue;
}
// Special case... use parent path.
if (ssPatternPart == "..")
{
// If the path also has a higher directory, everything is good
if (ssPathPart == "..") continue;
// Redo the last path iteration.
itPathRel--;
// Does a higher part path exists?
if (itPathRel == rpathRel.begin())
{
// A higher directory doesn't exist. Create a new pattern starting with "..".
std::filesystem::path pathNewPattern = fnBuildNewPath(itPathPattern, pathPattern);
// Create a new path from the path starting with "..".
std::filesystem::path pathNew = ".." / rpathRel;
// Redo the matching process.
// NOTE: Since ".." was prepended, only generic patterns with "*" and "**" can match.
return CheckWildcardPathMatch(pathNew, pathNewPattern.generic_u8string());
}
else
{
// Redo the path before the last path iteration as well.
itPathRel--;
continue;
}
}
// Special case... skipping the current part
if (ssPatternPart == "*")
{
// Are there any pattern parts following; if not, the last pattern part is an asterisk.
if (itPathPattern == pathPattern.end())
bLastPartAsterisk = true;
else if (itPathPattern->generic_u8string() == "..")
{
// The combination "*/.." cancels itself.
itPathRel--;
itPathPattern++;
continue;
}
}
// Iterate through the path parts
size_t nPathPartPos = 0;
size_t nPatternPartPos = 0;
while (nPathPartPos != ssPathPart.size() && nPatternPartPos != ssPatternPart.size())
{
// Get the characters and increase
char cPathPart = ssPathPart[nPathPartPos];
char cPatternPart = ssPatternPart[nPatternPartPos];
nPathPartPos++;
nPatternPartPos++;
// Classify and check with the pattern.
switch (cPatternPart)
{
case '?':
// Any character is allowed.
break;
case '*':
// Skip zero or more wildcard-characters following the asterisk.
while (nPatternPartPos != ssPatternPart.size() &&
(ssPatternPart[nPatternPartPos] == '*' || ssPatternPart[nPatternPartPos] == '?'))
nPatternPartPos++;
// When there are no more characters following, the rest of the path part fits.
if (nPatternPartPos == ssPatternPart.size())
{
nPathPartPos = ssPathPart.size();
break;
}
// Skip zero or more characters in the path part until the next fitting characters.
cPatternPart = ssPatternPart[nPatternPartPos];
nPathPartPos--; // Start with the current path
while (nPathPartPos != ssPathPart.size() && ssPathPart[nPathPartPos] != cPatternPart)
nPathPartPos++;
// When the path part pos has reached the end, there is no match.
if (nPathPartPos == ssPathPart.size())
return false;
// Go to the next character
nPathPartPos++;
nPatternPartPos++;
break;
default:
// Character needs to fit
if (cPathPart != cPatternPart) return false;
break;
}
}
// If the path part has reached the end, but the pattern still has space, this might indicate a mismatch. But only when the
// rest of the pattern doesn't contain one or more asterisk '*' until the end of the path. Simply skip these characters.
while (nPatternPartPos != ssPatternPart.size() && ssPatternPart[nPatternPartPos] == '*')
nPatternPartPos++;
// When both have reached the end, the path parts are identical.
if (nPathPartPos == ssPathPart.size() && nPatternPartPos == ssPatternPart.size())
continue;
// No match
return false;
}
// When both have reached the end of iteration, we have a match of files.
if (itPathRel == rpathRel.end() && itPathPattern == pathPattern.end())
return true;
// When only itPathPattern has reached the end of iteration, we have a match of directories... All files in the directory (but
// not in any sub-directories) are valid. The exception would be if the pattern was "*", which is forced here to match files
// only. In that case bLastPartAsterisk would be true.
if (itPathRel != rpathRel.end() && itPathPattern == pathPattern.end() && !bLastPartAsterisk)
{
itPathRel++;
return itPathRel == rpathRel.end();
}
// No match...
return false;
}
/**
* @brief Normalizes a regex path pattern string.
*
* This function processes a path string to resolve current ('.') and parent ('..')
* directory indicators according to standard path canonicalization rules, plus
* custom rules for regex generation.
*
* Rules applied:
* 1. Path segments of '.' are removed (e.g., "a/./b" -> "a/b").
* 2. A directory followed by '..' is resolved (e.g., "a/b/../c" -> "a/c"). Special case if the parent is any directory ".*\\.\\./"
* and ".*\/\\.\\./". Both will result in any directory ".*\/".
* 3. If a path attempts to navigate above the root (e.g., "../a" or "a/../../b"), this only works if the following directory is
* any directory (starts with ".*").
* @param[in] rssPattern Reference to the regex pattern string to normalize.
* @return The normalized regex pattern string.
*/
inline std::string NormalizeRegexPattern(const std::string& rssPattern)
{
// Throw regex group mismatch exception
auto fnThrowGroupException = [](char c)
{
switch (c)
{
case '(':
case ')':
throw std::regex_error(std::regex_constants::error_paren);
case '[':
case ']':
throw std::regex_error(std::regex_constants::error_brack);
case '{':
case '}':
throw std::regex_error(std::regex_constants::error_brace);
default:
break;
}
};
// Return the matching group character (parenthesis, brakcets or braces)
auto fnGroupMatch = [](char cOpenGroup) -> char
{
switch (cOpenGroup)
{
case '(': return ')';
case ')': return '(';
case '[': return ']';
case ']': return '[';
case '{': return '}';
case '}': return '{';
default: return cOpenGroup;
}
};
// Split the pattern in chunks
std::list<std::string> lstPattern;
std::string ssPart;
size_t nPos = 0;
std::stack<char> stackGroup;
while (nPos < rssPattern.size())
{
char c = rssPattern[nPos];
nPos++;
switch (c)
{
case '(': // Group (...)
case '[': // Group [...]
case '{': // Group {...}
stackGroup.push(c);
ssPart += c;
break;
case ')': // Group (...)
case ']': // Group [...]
case '}': // Group {...}
if (stackGroup.empty()) fnThrowGroupException(c);
if (fnGroupMatch(stackGroup.top()) != c) fnThrowGroupException(stackGroup.top());
stackGroup.pop();
ssPart += c;
break;
case '\\': // Escape
ssPart += c;
if (nPos >= rssPattern.size()) throw std::regex_error(std::regex_constants::error_escape);
c = rssPattern[nPos];
ssPart += c;
nPos++;
break;
case '/': // Separator
ssPart += c;
if (stackGroup.empty())
{
lstPattern.push_back(std::move(ssPart));
ssPart.clear();
}
break;
default:
ssPart += c;
break;
}
}
if (!stackGroup.empty()) fnThrowGroupException(stackGroup.top());
if (!ssPart.empty())
lstPattern.push_back(std::move(ssPart));
// Erase all entries to the current directory
lstPattern.erase(std::remove(lstPattern.begin(), lstPattern.end(), "\\./"), lstPattern.end());
// Handle parent directories
auto itPart = lstPattern.begin();
while (itPart != lstPattern.end())
{
// Next
auto itCurrent = itPart;
itPart++;
// Check for any directory followed by a parent directory - change to any directory: ".*\\.\\./"
if (*itCurrent == ".*\\.\\./")
{
*itCurrent = ".*/";
continue;
}
// Check for parent directory
if (*itCurrent == "\\.\\./")
{
// Check whether there is a directory before
if (itCurrent != lstPattern.begin())
{
// Get the directory before
auto itBefore = itCurrent;
itBefore--;
// Anything with .* in it will be truncated: "abc.*/", ".*jojo/", ".*/"
nPos = itBefore->rfind(".*");
if (nPos != std::string::npos)
{
*itBefore = itBefore->substr(nPos, 2) + '/';
lstPattern.erase(itCurrent);
continue;
}
// All others are considered a single directory and will be removed
lstPattern.erase(itBefore);
lstPattern.erase(itCurrent);
continue;
}
else if (itPart != lstPattern.end())
{
// Get the directory behind
auto itBehind = itPart;
itPart++;
// Get the directory after. This can only work if the directory after can be anything: starting with ".*/", ".*anything" or "[^/]+/"
if (*itBehind == ".*/")
{
lstPattern.erase(itCurrent);
*itBehind = ".*"; // Remove trailing slash
continue;
}
if (itBehind->substr(0, 2) == ".*")
{
lstPattern.erase(itCurrent);
continue;
}
else if (*itBehind == "[^/]+/")
{
lstPattern.erase(itCurrent);
lstPattern.erase(itBehind);
continue;
}
// Invalid use of "\\.\\."
throw std::regex_error(std::regex_constants::error_complexity);
}
else
{
// Invalid use of "\\.\\."
throw std::regex_error(std::regex_constants::error_complexity);
}
}
}
// Rebuild the pattern
std::string ssPatternNew;
for (const std::string& rssPart : lstPattern)
ssPatternNew += rssPart;
return ssPatternNew;
}
/**
* @brief Checks if a path matches a given regex pattern.
* @details The matching is performed against the entire path.
* @remarks For the regex patterns, it is assumed that navigation directories like "." and ".." are to be matched as literal text.
* Therefore, in the regex pattern, dots should be escaped (e.g., `\.` and `\.\.`).
* @param[in] rpathRel Reference to the existing relative filesystem path to check.
* @param[in] rssPattern Reference to the pattern expressed as a regular expression.
* @return Returns if the path matches the pattern.
*/
inline bool CheckRegexPathMatch(const std::filesystem::path& rpathRel, const std::string& rssPattern)
{
try
{
// Convert paths to generic, portable string format (with '/' separators)
const std::string ssPath = rpathRel.generic_u8string();
// Use the ECMAScript grammar, which is the default and most common.
const std::regex rgx(NormalizeRegexPattern(rssPattern), std::regex::ECMAScript);
// std::regex_match checks if the entire string is a match for the pattern.
return std::regex_match(ssPath, rgx);
}
catch (const std::regex_error& e)
{
// In case of an invalid regex pattern, log the error and return false.
std::cerr << "Regex error: " << e.what() << " for pattern: " << rssPattern << std::endl;
return false;
}
}
/**
* @brief Algorithm to be used for matching the path to the pattern.
*/
enum class EPathMatchAlgorithm
{
wildcards, ///< Use wildcards like '?', '*' and '**'
regex, ///< Use regular expressins
};
/**
* @brief Collect a vector of paths from a wildcard pattern.
* @param[in] rpathBase Reference to the base path to start collecting.
* @param[in] rssPattern Reference to the string containing the pattern to match files with.
* @param[in] eAlgorithm The algorithm to use for path collection.
* @return Returns a vector with paths that are matching the pattern.
*/
inline std::vector<std::filesystem::path> CollectWildcardPath(const std::filesystem::path& rpathBase,
const std::string& rssPattern, EPathMatchAlgorithm eAlgorithm = EPathMatchAlgorithm::wildcards)
{
std::vector<std::filesystem::path> pathMatches;
try
{
std::filesystem::path pathBaseCopy = rpathBase;
std::string ssPatternCopy = rssPattern;
bool bUseAbsolutePath = false;
if (pathBaseCopy.empty())
{
// Get the first position of a path not including wildcard and/or regex special characters
size_t nPos = rssPattern.find_first_of("[](){}*?.\\+");
// Get the directory separator before that
nPos = rssPattern.substr(0, nPos).find_last_of('/');
// The base pattern is everything before the '/'.
if (nPos != std::string::npos)
{
pathBaseCopy = rssPattern.substr(0, nPos);
ssPatternCopy = rssPattern.substr(nPos + 1);
bUseAbsolutePath = true;
}
}
else if (std::filesystem::path(rssPattern).is_absolute())
{
// Separator processing function.
size_t nSearchPos = 0;
auto fnProcessSeparator = [&rssPattern, &nSearchPos]() -> bool
{
#ifdef _WIN32
if (nSearchPos >= rssPattern.size() || (rssPattern[nSearchPos] != '/' && rssPattern[nSearchPos] != '\\'))
#else
if (nSearchPos >= rssPattern.size() || rssPattern[nSearchPos] != '/')
#endif
return false;
nSearchPos++;
return true;
};
// Remove the base from the pattern.
bool bExplicitSeparator = false;
for (const auto& rpathBasePart : rpathBase)
{
// Skip separator in base path
#ifdef _WIN32
if (rpathBasePart == "/" || rpathBasePart == "\\")
#else
if (rpathBasePart == "/")
#endif
{
fnProcessSeparator();
bExplicitSeparator = true;
continue;
}
// Separator needed?
if (!bExplicitSeparator && nSearchPos && !fnProcessSeparator())
return {};
bExplicitSeparator = false;
// Check for the base part inside the pattern
std::string ssPathPart = rpathBasePart.generic_u8string();
if (rssPattern.substr(nSearchPos, ssPathPart.size()) != ssPathPart)
return {};
nSearchPos += ssPathPart.size();
}
// Skip trailing slash if existing
#ifdef _WIN32
if (nSearchPos < rssPattern.size() &&
(rssPattern[nSearchPos] == '/' || (rssPattern[nSearchPos] == '\\' && eAlgorithm == EPathMatchAlgorithm::wildcards)))
#else
if (nSearchPos < rssPattern.size() && rssPattern[nSearchPos] == '/')
#endif
nSearchPos++;
// Copy the pattern following the parg
ssPatternCopy = rssPattern.substr(nSearchPos);
// If the pattern was absolute and the represents exactly the base path, search for all files in the top most directory.
if (ssPatternCopy.empty())
{
// Add wildcards or regex structure for all files in all directories
switch (eAlgorithm)
{
case EPathMatchAlgorithm::regex:
ssPatternCopy = "[^/]+";
break;
case EPathMatchAlgorithm::wildcards:
default:
ssPatternCopy = "*";
break;
}
}
}
// If there is no pattern, then all content of the directory of the base path is required.
if (ssPatternCopy.empty())
{
// Add wildcards or regex structure for all files in all directories
switch (eAlgorithm)
{
case EPathMatchAlgorithm::regex:
ssPatternCopy = ".*";
break;
case EPathMatchAlgorithm::wildcards:
default:
ssPatternCopy = "**";
break;
}
}
// Iterate through the path files of the base path.
for (auto const& rDirEntry : std::filesystem::recursive_directory_iterator(pathBaseCopy))
{
if (!rDirEntry.is_regular_file()) continue;
std::filesystem::path pathRel = rDirEntry.path().lexically_relative(pathBaseCopy);
std::filesystem::path pathToAdd = bUseAbsolutePath ? rDirEntry.path() : pathRel;
switch (eAlgorithm)
{
case EPathMatchAlgorithm::wildcards:
if (CheckWildcardPathMatch(pathRel, ssPatternCopy))
pathMatches.push_back(pathToAdd);
break;
case EPathMatchAlgorithm::regex:
if (CheckRegexPathMatch(pathRel, ssPatternCopy))
pathMatches.push_back(pathToAdd);
break;
default:
break;
}
}
}
catch (std::filesystem::filesystem_error& rexception)
{
std::cerr << rexception.what();
}
return pathMatches;
}
#endif // WILDCARD_MATCH_H

104
global/process_watchdog.h Normal file
View File

@@ -0,0 +1,104 @@
#ifndef PROCESS_WATCHDOG_H
#define PROCESS_WATCHDOG_H
#include <thread>
#include <cstdint>
#include <iostream>
#ifdef _WIN32
// Prevent reassignment of "interface"
#pragma push_macro("interface")
#undef interface
#ifndef NOMINMAX
#define NOMINMAX
#endif
// Include windows headers
#include <WinSock2.h>
#include <Windows.h>
#include <objbase.h>
// Use previous assignment of "interface"
#pragma pop_macro("interface")
// Remove "GetObject" assignment
#ifdef GetObject
#undef GetObject
#endif
// Remove "GetClassInfo" assignment
#ifdef GetClassInfo
#undef GetClassInfo
#endif
#elif defined __unix__
#include <signal.h>
#include <unistd.h>
#else
#error The OS is not supported!
#endif
/**
* @brief Process watchdog class; ends the process when runtime duration has superseded (as is the case when a deadlock has
* occurred).
*/
class CProcessWatchdog
{
public:
/**
* @brief Constructor
* @param[in] iWatchdogTimeS The duration the watchdog is monitoring before process termination in seconds (default 300s).
*/
CProcessWatchdog(int64_t iWatchdogTimeS = 300ll)
{
m_threadWatchdog = std::thread(&CProcessWatchdog::WatchdogThreadFunc, this, iWatchdogTimeS );
}
/**
* @brief Destructor; cancels the watch thread.
*/
~CProcessWatchdog()
{
m_bTerminateWatchdog = true;
if (m_threadWatchdog.joinable())
m_threadWatchdog.join();
}
private:
/**
* @brief Watch thread function.
* @param[in] iWatchdogTimeS The duration the watchdog is monitoring before process termination in seconds (default 120s).
*/
void WatchdogThreadFunc(int64_t iWatchdogTimeS = 120ll)
{
// Run for the most the set time; then terminate...
auto tpStart = std::chrono::steady_clock::now();
while (!m_bTerminateWatchdog)
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
auto tpNow = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(tpNow - tpStart).count() > iWatchdogTimeS)
{
std::cerr << "WATCHDOG TERMINATION ENFORCED!!!" << std::endl;
std::cerr.flush();
#ifdef _WIN32
// Get the current process handle
HANDLE hProcess = GetCurrentProcess();
// Terminate the current process with exit code -100
TerminateProcess(hProcess, static_cast<UINT>(-100));
#elif defined __unix__
// Send SIGTERM signal to the current process
kill(getpid(), SIGTERM);
#else
#error The OS is not supported!
#endif
}
}
}
bool m_bTerminateWatchdog = false; ///< When set, allows the thread to terminate.
std::thread m_threadWatchdog; ///< The watchdog thread.
};
#endif // !defined PROCESS_WATCHDOG_H

View File

@@ -0,0 +1,221 @@
#include "scheduler.h"
#include <cassert>
CTaskScheduler::CTaskScheduler(size_t nMinIdle /*= 4*/, size_t nMaxBusy /*= 32*/) :
m_nMinIdle(nMinIdle), m_nMaxBusy(nMaxBusy)
{
// Check min/max values
assert(nMinIdle > 0);
if (!m_nMinIdle) m_nMinIdle = 1;
assert(nMinIdle > 0);
if (!m_nMaxBusy) m_nMaxBusy = 1;
// Start the minimal required idle threads
for (size_t n = 0; n < nMinIdle; n++)
m_queueIdleThreads.push(
m_lstThreads.insert(m_lstThreads.end(), std::make_shared<CThread>()));
m_nMaxThreads = m_lstThreads.size();
}
CTaskScheduler::~CTaskScheduler()
{
WaitForExecution();
}
bool CTaskScheduler::Schedule(std::function<void()> fnTask, hlpr::flags<EScheduleFlags> flags /*= 0*/)
{
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
// Get a thread - either from the idle queue or new.
std::list<std::shared_ptr<CThread>>::iterator itThread = m_lstThreads.end();
if (m_queueIdleThreads.empty())
{
// No thread is available. Allowed to make one?
if (m_lstThreads.size() < m_nMaxBusy)
itThread = m_lstThreads.insert(m_lstThreads.end(), std::make_shared<CThread>());
if (m_lstThreads.size() > m_nMaxThreads)
m_nMaxThreads = m_lstThreads.size();
}
else
{
itThread = m_queueIdleThreads.front();
m_queueIdleThreads.pop();
}
// Is there a valid thread? The schedule the task. Otherwise queue the task.
if (itThread != m_lstThreads.end())
{
lock.unlock();
Execute(itThread, fnTask);
}
else
{
// Queuing allowed?
if (flags & EScheduleFlags::no_queue) return false; // No scheduling possible.
// Add the task to the queue
if (flags & EScheduleFlags::priority)
m_dequeTasks.push_front(fnTask);
else
m_dequeTasks.push_back(fnTask);
}
// Successful scheduled.
return true;
}
void CTaskScheduler::WaitForExecution()
{
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
// Temporarily reduce the idle level to zero.
size_t nMinIdleTemp = m_nMinIdle;
m_nMinIdle = 0;
// Wait until the thread list is empty.
while (!m_lstThreads.empty())
{
// Erase all idle threads.
while (!m_queueIdleThreads.empty())
{
// False positive of CppCheck: content of m_queueIdleThreads represents the iterator of m_lstThreads
// cppcheck-suppress mismatchingContainerIterator
m_lstThreads.erase(m_queueIdleThreads.front());
m_queueIdleThreads.pop();
}
// Allow execution threads to finalize its processing
lock.unlock();
// Wait shortly
std::this_thread::sleep_for(std::chrono::milliseconds(10));
// Check again
lock.lock();
}
// Restore the idle thread amount
m_nMinIdle = nMinIdleTemp;
}
size_t CTaskScheduler::GetThreadCount() const
{
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
return m_lstThreads.size();
}
size_t CTaskScheduler::GetMaxThreadCount() const
{
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
return m_nMaxThreads;
}
size_t CTaskScheduler::GetBusyThreadCount() const
{
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
return m_lstThreads.size() - m_queueIdleThreads.size();
}
size_t CTaskScheduler::GetIdleThreadCount() const
{
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
return m_queueIdleThreads.size();
}
void CTaskScheduler::Execute(std::list<std::shared_ptr<CThread>>::iterator itThread, std::function<void()> fnTask)
{
std::shared_ptr<CThread>& rptrThread = *itThread;
// Call execute.
rptrThread->Execute([this, fnTask, itThread]()
{
// Start with the supplied task.
std::function<void()> fnTaskLocal = fnTask;
// Execute tasks for as long as there are any
do
{
fnTaskLocal();
// Any other task?
std::unique_lock<std::mutex> lock(m_mtxQueueAccess);
if (m_dequeTasks.empty()) break;
fnTaskLocal = std::move(m_dequeTasks.front());
m_dequeTasks.pop_front();
} while (true);
// Prepare for adding this thread to the idle queue. Remove any redundant threads.
// Attention: it is not possible to terminate this thread, since it still runs the thread func and relies on member
// variables (which would otherwise be cleared). It is, however, possible to remove all other threads.
std::unique_lock<std::mutex> lock2(m_mtxQueueAccess);
while (!m_queueIdleThreads.empty() && m_queueIdleThreads.size() >= m_nMinIdle)
{
// False positive of CppCheck: content of m_queueIdleThreads represents the iterator of m_lstThreads
// cppcheck-suppress mismatchingContainerIterator
m_lstThreads.erase(m_queueIdleThreads.front());
m_queueIdleThreads.pop();
}
// Add this thread to the idle queue.
m_queueIdleThreads.push(itThread);
});
}
CTaskScheduler::CThread::CThread()
{
// Wait for the start
std::unique_lock<std::mutex> lockStart(m_mtxSyncStart);
m_thread = std::thread(&CThread::ThreadFunc, this);
while (!m_bStarted)
m_cvStarted.wait_for(lockStart, std::chrono::milliseconds(10));
}
CTaskScheduler::CThread::~CThread()
{
// Lock to prevent execution still to take place. Then shutdown.
std::unique_lock<std::mutex> lock(m_mtxSyncExecute);
m_bShutdown = true;
m_cvExecute.notify_all();
lock.unlock();
if (m_thread.joinable())
m_thread.join();
}
void CTaskScheduler::CThread::Execute(std::function<void()> fnTask)
{
// Assign the task and notify for execution.
std::unique_lock<std::mutex> lock(m_mtxSyncExecute);
m_fnTask = fnTask;
m_cvExecute.notify_all();
lock.unlock();
}
void CTaskScheduler::CThread::ThreadFunc()
{
// Thread has started (needed, since the condition variable doesn't keep its state).
m_bStarted = true;
// Notify the thread has started
std::unique_lock<std::mutex> lockStart(m_mtxSyncStart);
m_cvStarted.notify_all();
lockStart.unlock();
// Notify the thread has executed
std::unique_lock<std::mutex> lockExecute(m_mtxSyncExecute);
while (!m_bShutdown)
{
// Wait for an execution task to take place.
m_cvExecute.wait_for(lockExecute, std::chrono::milliseconds(10));
// In case there is no task scheduled.
if (!m_fnTask) continue;
// Execute the task
m_fnTask();
// Task is done, clear the task function
m_fnTask = {};
}
}

View File

@@ -0,0 +1,143 @@
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <thread>
#include <mutex>
#include <queue>
#include <deque>
#include <functional>
#include <memory>
#include <condition_variable>
#include <cstdint>
#include <list>
#include "../flags.h"
/**
* @brief Job scheduler that uses a dynamic threadpool to schedule the task.
*/
class CTaskScheduler
{
public:
/**
* @brief Constructor
* @param[in] nMinIdle The amount of idle threads that should stay present if there is nothing to process at the moment.
* @param[in] nMaxBusy The maximum amount of threads that is used for processing.
*/
CTaskScheduler(size_t nMinIdle = 4, size_t nMaxBusy = 32);
/**
* @brief Destructor
*/
~CTaskScheduler();
/**
* @brief Schedule flags to influence the scheduling.
*/
enum class EScheduleFlags
{
normal = 0x0, ///< If thread level threshold has been reached, queue the call at the end of the queue.
priority = 0x1, ///< If thread level threshold has been reached, insert the call at the begin of the queue.
no_queue = 0x2, ///< If thread level threshold has been reached, fail the schedule call.
};
/**
* @brief Schedule the asynchronous execution of the task.
* @details If an idle thread is available, the thread will execute the task. If no thread is available and the maximum thread
* level hasn't been reached, a new thread will be started that schedules the task. If the maximum thread level has been
* exceeded the task will be placed in the task list based on its priority and allowance. If queue is not allowed (set by the
* no_queue flag), the scheduling will fail.
* @param[in] fnTask The task to schedule.
* @param[in] flags Zero or more flags to use for scheduling the task.
* @return Returns whether the scheduling was successful or not.
*/
bool Schedule(std::function<void()> fnTask, hlpr::flags<EScheduleFlags> flags = EScheduleFlags::normal);
/**
* @brief Wait until the execution of all threads has been finalized. This will also remove all idle threads.
* @attention Do not call from a task function - that will cause a deadlock.
*/
void WaitForExecution();
/**
* @brief Get the current amount of threads (idle + processing).
* @return The amount of threads.
*/
size_t GetThreadCount() const;
/**
* @brief Get the maximum amount of threads that were processing at one time.
* @return The amount of threads.
*/
size_t GetMaxThreadCount() const;
/**
* @brief Get the current amount of processing threads.
* @return The amount of threads.
*/
size_t GetBusyThreadCount() const;
/**
* @brief Get the current amount of idle threads.
* @return The amount of threads.
*/
size_t GetIdleThreadCount() const;
private:
/**
* @brief Helper class for the thread scheduling
*/
struct CThread
{
public:
/**
* @brief Constructor starting the thread.
*/
CThread();
/**
* @brief Destructor stopping the thread.
*/
~CThread();
/**
* @brief Schedule an execution.
* @param[in] fnTask The execution task.
*/
void Execute(std::function<void()> fnTask);
private:
/**
* @brief The thread function to execute.
*/
void ThreadFunc();
std::thread m_thread; ///< The thread that executes the tasks.
bool m_bShutdown = false; ///< Set when the thread should terminate.
bool m_bStarted = false; ///< Set when the thread has started.
std::function<void()> m_fnTask; ///< The task to execute (will be updated with new tasks before execution).
std::mutex m_mtxSyncStart; ///< The startup synchronization mutex.
std::condition_variable m_cvStarted; ///< Triggered by the thread to indicate that it has started.
std::mutex m_mtxSyncExecute; ///< The execute synchronization mutex.
std::condition_variable m_cvExecute; ///< Triggers the thread to indicate that there is a task to execute or
///< shutdown has been requested.
};
/**
* @brief Execute a task using the provided thread. After the execution, other tasks from the task list will be executed as
* well. If after all executions are finalized and if there are enough idle threads, removes the thread from
* the list. Otherwise adds the thread to the idle queue.
* @param[in] itThread The thread to use for the execution.
* @param[in] fnTask The task to execute.
*/
void Execute(std::list<std::shared_ptr<CThread>>::iterator itThread, std::function<void()> fnTask);
size_t m_nMinIdle = 4; ///< The minimal required amount of idle threads
size_t m_nMaxBusy = 32; ///< The maximum allowed amount of busy threads
size_t m_nMaxThreads = 0; ///< The maximum amount threads at the same time.
mutable std::mutex m_mtxQueueAccess; ///< Sync access to queue, list and double-ended-queue.
std::queue<std::list<std::shared_ptr<CThread>>::iterator> m_queueIdleThreads; ///< Idle thread queue.
std::list<std::shared_ptr<CThread>> m_lstThreads; ///< List with all threads.
std::deque<std::function<void()>> m_dequeTasks; ///< Double ended task queue.
};
#endif // !defined THREAD_POOL_H

268
global/timetracker.h Normal file
View File

@@ -0,0 +1,268 @@
#ifndef TIME_TRACKER_H
#define TIME_TRACKER_H
#include "exec_dir_helper.h"
#include <string>
#include <sstream>
#include <vector>
#include <chrono>
#include <list>
#include <map>
#include <queue>
#include <fstream>
/**
* @brief Track function to trigger one time entry.
* @param tag The tag name to use to identify this entry.
* @details Use the "track_time" macro within a function to activate. The tag is a name used to identify the code position that
* should be tracked. For each track_time call, a time entry relative to the start of the application will be written (resolution
* is micro-seconds). The tracking file will be written on shutdown of the application and is called the same as the application
* with "_tracking.csv" attached to it.
* Example:
* @code
* void MyFunc()
* {
* track_time(enter);
*
* // ...
*
* track_timer(leave);
* }
* @endcode
*/
#define track_time(tag) GetTimeTracker().Trigger(__FUNCTION__, #tag)
/**
* @brief Time tracker class used internally by the tracker.
*/
class CTimeTracker
{
public:
/**
* @brief Default constructor.
*/
CTimeTracker();
/**
* @brief Destructor.
*/
~CTimeTracker();
/**
* @brief Trigger function to store a trigger timepoint.
* @attention Both function and tag name must be a static string in a data segment, so the strings are valid even if the
* function that caused the trigger is not within scope any more.
* @param[in] szFunction Pointer to the function name.
* @param[in] szTag Tag to identify the trigger with.
*/
void Trigger(const char* szFunction, const char* szTag);
private:
/**
* @brief Trigger value structure
*/
struct STriggerEntry
{
/**
* @brief Constructor of a trigger value.
* @param[in] szFuncParam String to function name
* @param[in] szTagParam String to tag name
*/
STriggerEntry(const char* szFuncParam, const char* szTagParam) :
szFunction(szFuncParam), szTag(szTagParam), tpTrigger(std::chrono::high_resolution_clock::now())
{}
const char* szFunction; ///< Function name (is a static string in data segment).
const char* szTag; ///< Function tag (is a static string in data segment).
std::chrono::high_resolution_clock::time_point tpTrigger; ///< Trigger timepoint.
};
std::time_t m_timeStartTime; ///< Start time of the tracker (system clock time).
std::chrono::high_resolution_clock::time_point m_tpStartTime; ///< Start time of tracker (high resolution clock).
mutable std::mutex m_mtxTriggers; ///< Queue protection.
std::queue<STriggerEntry> m_queuTriggers; ///< Queue with trigger values.
};
inline CTimeTracker& GetTimeTracker()
{
static CTimeTracker tracker_global;
return tracker_global;
}
inline CTimeTracker::CTimeTracker() :
m_timeStartTime(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())),
m_tpStartTime(std::chrono::high_resolution_clock::now())
{}
inline CTimeTracker::~CTimeTracker()
{
if (m_queuTriggers.empty()) return;
// Trigger list translated to the system time in microsecs.
using TTriggerList = std::list<uint64_t>;
struct STagInfo
{
STagInfo(const char* szFunctionParam, const char* szTagParam) :
szFunction(szFunctionParam), szTag(szTagParam)
{}
const char* szFunction;
const char* szTag;
TTriggerList lstTriggers;
TTriggerList::iterator itPosition;
};
using TOrderedTagList = std::list<STagInfo>;
TOrderedTagList lstOrderedTags;
TOrderedTagList lstDeferredTags;
// Process the trigger queue
while (!m_queuTriggers.empty())
{
// Get the entry
STriggerEntry sEntry = std::move(m_queuTriggers.front());
m_queuTriggers.pop();
// Calculate the time since start time (use system clock as reference)
uint64_t uiTimeMicrosecs = std::chrono::duration_cast<std::chrono::microseconds>(sEntry.tpTrigger - m_tpStartTime).count();
//uiTimeMicrosecs += static_cast<uint64_t>(m_timeStartTime) * 1000000ull;
// Determine the order
STagInfo* psTagInfo = nullptr;
if (lstOrderedTags.empty())
{
// First entry... add to the list
lstOrderedTags.push_back(STagInfo(sEntry.szFunction, sEntry.szTag));
psTagInfo = &lstOrderedTags.back();
}
else
{
// Check whether the tag represents the first entry of the order list. If so, add all the deferred list entries.
if (sEntry.szTag == lstOrderedTags.front().szTag && !lstDeferredTags.empty())
{
// Add the deferred tags to the end of the list
for (STagInfo& rsTag : lstDeferredTags)
lstOrderedTags.push_back(std::move(rsTag));
lstDeferredTags.clear();
}
// Search for the tag to exist in the ordered tags list.
auto itTag = std::find_if(lstOrderedTags.begin(), lstOrderedTags.end(), [&](const STagInfo& rsTag) { return rsTag.szFunction == sEntry.szFunction && rsTag.szTag == sEntry.szTag; });
if (itTag == lstOrderedTags.end())
{
// Tag is not in the ordered tags list; add to deferred tags list of not already in there
itTag = std::find_if(lstDeferredTags.begin(), lstDeferredTags.end(), [&](const STagInfo& rsTag) { return rsTag.szFunction == sEntry.szFunction && rsTag.szTag == sEntry.szTag; });
if (itTag == lstDeferredTags.end())
{
lstDeferredTags.push_back(STagInfo(sEntry.szFunction, sEntry.szTag));
psTagInfo = &lstDeferredTags.back();
}
else
psTagInfo = &(*itTag);
}
else
{
// Tag is in the ordered tags list. Are there any tags on the deferred list? Add them before.
for (STagInfo& rsTag : lstDeferredTags)
lstOrderedTags.insert(itTag, std::move(rsTag));
lstDeferredTags.clear();
psTagInfo = &(*itTag);
}
}
// Add the trigger value
if (psTagInfo)
psTagInfo->lstTriggers.push_back(uiTimeMicrosecs);
else
std::cout << "INTERNAL ERROR: tag info pointer is invalid!";
}
// Run through deferred tags lists to add them still at the end of the ordered tags lists
for (STagInfo& rsTag : lstDeferredTags)
lstOrderedTags.push_back(std::move(rsTag));
// Create CSV file
std::filesystem::path path = (GetExecDirectory() / GetExecFilename().replace_extension("")).generic_u8string() + "_tracker.csv";
std::ofstream stream(path);
if (!stream.is_open())
{
std::cout << "Cannot write tracker information to '" << path.generic_u8string() << "'..." << std::endl;
return;
}
std::cout << "Writing tracker data to '" << path.generic_u8string() << "'..." << std::endl;
// Iterate through the tags and add the header lines + assign the initial iterator position
std::stringstream sstreamFirstLine;
std::stringstream sstreamSecondLine;
std::stringstream sstreamData;
for (STagInfo& rsTagInfo : lstOrderedTags)
{
// First line is the function name
if (!sstreamFirstLine.str().empty())
sstreamFirstLine << "; ";
sstreamFirstLine << rsTagInfo.szFunction;
// Second line is the tag
if (!sstreamSecondLine.str().empty())
sstreamSecondLine << "; ";
sstreamSecondLine << rsTagInfo.szTag;
// Assign iterator
rsTagInfo.itPosition = rsTagInfo.lstTriggers.begin();
}
stream << sstreamFirstLine.str() << std::endl <<
sstreamSecondLine.str() << std::endl;
// Add the data by running though the position iterators so long until all iterators are pointing to end...
uint64_t uiMax = 0;
do
{
// Determine the maximum time that includes at the most one value of each tag - check the time of the trigger value
// following the current.
uiMax = static_cast<uint64_t>(-1);
for (const STagInfo& rsTagInfo : lstOrderedTags)
{
if (rsTagInfo.itPosition != rsTagInfo.lstTriggers.end())
{
// Get the current timestamp to determine the minimal amount to include this timestamp
uint64_t uiPotentialMax = *rsTagInfo.itPosition + 1;
auto itNextPosition = rsTagInfo.itPosition;
itNextPosition++;
// If the next timestamp is larger than the current timestamp, take the next timestamp
if (itNextPosition != rsTagInfo.lstTriggers.end() && uiPotentialMax < *itNextPosition)
uiPotentialMax = *itNextPosition;
// If the max timestamp should not exceed the next coming timestamp.
if (uiMax > uiPotentialMax)
uiMax = uiPotentialMax;
}
}
// Run through the values and stream those values that are smaller than the next position.
bool bFirst = true;
for (STagInfo& rsTagInfo : lstOrderedTags)
{
if (!bFirst)
stream << "; ";
bFirst = false;
if (rsTagInfo.itPosition != rsTagInfo.lstTriggers.end() && *rsTagInfo.itPosition < uiMax)
{
stream << *rsTagInfo.itPosition;
rsTagInfo.itPosition++;
}
}
stream << std::endl;
} while (uiMax != static_cast<uint64_t>(-1));
// Finalize the stream
stream.close();
}
inline void CTimeTracker::Trigger(const char* szFunction, const char* szTag)
{
std::unique_lock<std::mutex> lock(m_mtxTriggers);
m_queuTriggers.push(STriggerEntry(szFunction, szTag));
}
#endif // !defined TIME_TRACKER_H

77
global/trace.h Normal file
View File

@@ -0,0 +1,77 @@
#ifndef TRACE_H
#define TRACE_H
#include <chrono>
#include <ctime>
#include <sstream>
#include <iomanip>
#ifdef __GNUC__
#include <unistd.h>
#endif
#ifndef ENABLE_TRACE
/**
* @brief Enable the trace macro by setting to a non-zero value.
*/
#define ENABLE_TRACE 0
#endif
/**
* @brief Return the current time as a string.
* @return The current time.
*/
inline std::string GetTimestamp()
{
const auto current_time_point {std::chrono::system_clock::now()};
const auto current_time {std::chrono::system_clock::to_time_t(current_time_point)};
const auto current_localtime {*std::localtime (&current_time)};
const auto current_time_since_epoch {current_time_point.time_since_epoch()};
const auto current_milliseconds {std::chrono::duration_cast<std::chrono::milliseconds> (current_time_since_epoch).count() % 1000};
std::ostringstream stream;
stream << "PID#" << std::dec << getpid() << " " << std::put_time(&current_localtime, "%H:%M:%S") << "." << std::setw(3) << std::setfill('0') << current_milliseconds << ": ";
return stream.str();
}
#include <cstring>
#include <iostream>
#if ENABLE_TRACE != 0
#ifdef _MSC_VER
#define TRACE(...) Trace(GetTimestamp(), __FUNCTION__, ": ", __VA_ARGS__)
#elif defined __GNUC__
#define TRACE(...) Trace(GetTimestamp(), __PRETTY_FUNCTION__, ": ", ##__VA_ARGS__)
#else
#error Other compiler are not supported.
#endif
inline void Trace(std::stringstream& /*rsstream*/)
{}
template <typename TArg, typename... TArgs>
inline void Trace(std::stringstream& rsstream, TArg tArg, TArgs... tArgs)
{
rsstream << tArg;
Trace(rsstream, tArgs...);
}
template <typename... TArgs>
inline void Trace(TArgs... tArgs)
{
std::stringstream sstream;
Trace(sstream, tArgs...);
sstream << std::endl;
std::cout << sstream.str();
}
#else // ENABLE_TRACE == 0
inline void Trace()
{}
#ifdef __GNUC__
#endif
#define TRACE(...) Trace()
#endif
#endif // !defined TRACE_H

View File

@@ -0,0 +1,571 @@
#include "trace_fifo.h"
// Include the platform support
#define INCLUDE_TRACE_FIFO_PLATFORM
#include "trace_fifo_windows.cpp"
#include "trace_fifo_posix.cpp"
#undef INCLUDE_TRACE_FIFO_PLATFORM
#ifdef _WIN32
#include <io.h>
#pragma push_macro("O_TEXT")
#define O_TEXT _O_TEXT
#pragma push_macro("dup")
#define dup _dup
#pragma push_macro("dup2")
#define dup2 _dup2
#pragma push_macro("fileno")
#define fileno _fileno
#pragma push_macro("close")
#define close _close
#pragma push_macro("pipe")
#define pipe _pipe
#pragma push_macro("read")
#define read _read
#pragma push_macro("eof")
#define eof _eof
#else
#include <unistd.h>
#include <poll.h>
#endif
#include <fcntl.h>
#include <errno.h>
#include <string>
#include <chrono>
CTraceFifoBase::CTraceFifoBase(uint32_t uiInstanceID, size_t nSize) :
m_uiInstanceID(uiInstanceID), m_nDefaultSize(nSize)
{}
CTraceFifoBase::~CTraceFifoBase()
{}
CTraceFifoBase::CTraceFifoBase(CTraceFifoBase&& rfifo) noexcept:
m_uiInstanceID(rfifo.m_uiInstanceID), m_nSize(rfifo.m_nSize), m_pBuffer(rfifo.m_pBuffer), m_psHdr(rfifo.m_psHdr)
{
rfifo.m_pBuffer = nullptr;
rfifo.m_psHdr = nullptr;
}
CTraceFifoBase& CTraceFifoBase::operator=(CTraceFifoBase&& rfifo) noexcept
{
Close();
m_uiInstanceID = rfifo.m_uiInstanceID;
m_nSize = rfifo.m_nSize;
m_pBuffer = rfifo.m_pBuffer;
m_psHdr = rfifo.m_psHdr;
rfifo.m_pBuffer = nullptr;
rfifo.m_psHdr = nullptr;
return *this;
}
bool CTraceFifoBase::SetInstanceID(uint32_t uiInstanceID)
{
if (IsOpened()) return false;
m_uiInstanceID = uiInstanceID;
return true;
}
uint32_t CTraceFifoBase::GetInstanceID() const
{
return m_uiInstanceID;
}
void CTraceFifoBase::SetDefaultSize(size_t nSize)
{
m_nDefaultSize = nSize;
}
size_t CTraceFifoBase::GetDefaultSize() const
{
return m_nDefaultSize;
}
size_t CTraceFifoBase::GetViewSize() const
{
return m_nSize;
}
size_t CTraceFifoBase::GetDataBufferSize() const
{
return IsInitialized() ? m_nSize - sizeof(SSharedMemBufHeader) : 0;
}
void CTraceFifoBase::InitializeBuffer(void* pView, size_t nBufferSize, bool bReadOnly)
{
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
m_pBuffer = reinterpret_cast<uint8_t*>(pView);
m_psHdr = reinterpret_cast<SSharedMemBufHeader*>(pView);
m_nSize = nBufferSize;
if (!IsInitialized())
{
if (!bReadOnly)
{
std::copy_n("SDV_MON\0", 8, m_psHdr->rgszSignature);
m_psHdr->uiInstanceID = m_uiInstanceID;
m_psHdr->uiTxOffs = 0u;
m_bInitConfirmed = true;
}
}
}
bool CTraceFifoBase::IsInitialized() const
{
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
// Bypass of initialization?
if (m_bInitConfirmed) return true;
bool bRet = m_pBuffer && m_psHdr && m_psHdr->uiInstanceID == m_uiInstanceID &&
std::equal(m_psHdr->rgszSignature, m_psHdr->rgszSignature + 8, "SDV_MON\0");
m_bInitConfirmed = bRet;
return bRet;
}
void CTraceFifoBase::Terminate()
{
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
m_pBuffer = nullptr;
m_psHdr = nullptr;
m_nSize = 0;
m_bInitConfirmed = false;
}
#ifdef _MSC_VER
// Static code analysis: not releasing lock in this function is not a failure.
#pragma warning(push)
#pragma warning(disable : 26115)
#endif
std::unique_lock<std::recursive_mutex> CTraceFifoBase::CreateAccessLockObject() const
{
return std::unique_lock<std::recursive_mutex>(m_mtxAccess);
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
void* CTraceFifoBase::GetView()
{
return IsInitialized() ? m_pBuffer : 0;
}
uint8_t* CTraceFifoBase::GetDataPtr()
{
return IsInitialized() ? m_pBuffer + sizeof(SSharedMemBufHeader) : 0;
}
const uint8_t* CTraceFifoBase::GetDataPtr() const
{
return IsInitialized() ? m_pBuffer + sizeof(SSharedMemBufHeader) : 0;
}
size_t CTraceFifoBase::GetWritePos() const
{
return IsInitialized() ? m_psHdr->uiTxOffs : 0;
}
void CTraceFifoBase::SetWritePos(size_t nTxPos)
{
if (IsInitialized() && nTxPos < GetDataBufferSize())
m_psHdr->uiTxOffs = static_cast<uint32_t>(nTxPos);
}
CTraceFifoReader::CTraceFifoReader(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16384*/) :
CTraceFifoImpl(uiInstanceID, nSize)
{}
CTraceFifoReader::~CTraceFifoReader()
{
Close();
}
CTraceFifoReader::CTraceFifoReader(CTraceFifoReader&& rfifo) noexcept : CTraceFifoImpl(static_cast<CTraceFifoImpl&&>(rfifo)), m_nRxOffs(rfifo.m_nRxOffs)
{
rfifo.m_nRxOffs = 0;
}
CTraceFifoReader& CTraceFifoReader::operator=(CTraceFifoReader&& rfifo) noexcept
{
CTraceFifoImpl::operator=(static_cast<CTraceFifoImpl&&>(rfifo));
rfifo.m_nRxOffs = 0;
return *this;
}
bool CTraceFifoReader::Open(size_t nTimeout /*= 1000*/, uint32_t uiFlags /*= 0u*/)
{
if (IsOpened()) return true;
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
bool bRet = CTraceFifoImpl::Open(nTimeout, uiFlags | static_cast<uint32_t>(ETraceFifoOpenFlags::read_only));
if (bRet) m_nRxOffs = GetWritePos();
return bRet && IsOpened();
}
inline void CTraceFifoReader::Close()
{
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
CTraceFifoImpl::Close();
Terminate();
}
std::string CTraceFifoReader::WaitForMessage(size_t nTimeout /*= 1000*/)
{
if (!IsOpened())
{
Open(nTimeout);
if (!IsOpened()) return {}; // Must be connected
}
auto tpStart = std::chrono::high_resolution_clock::now();
do
{
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
if (!IsInitialized()) break;
// Count the characters in the string, not including the null-character. Returns nMaxLen when no null-character has been
// found.
auto fnCountStr = [](const uint8_t* szStr, size_t nMaxLen)
{
for (size_t nCount = 0; nCount < nMaxLen; nCount++)
if (!szStr[nCount]) return nCount;
return nMaxLen;
};
// If Rx is higher than Tx, read at the most until the end of the buffer and the rest from the start. If the Tx is smaller
// than Tx, read at the most until Tx.
size_t nBuffSize = GetDataBufferSize();
uint8_t* pBuffer = GetDataPtr();
size_t nLocalRx = m_nRxOffs;
std::string ssMsg;
if (nLocalRx > GetWritePos())
{
// Detect the length
size_t uiMaxLen1 = nBuffSize - m_nRxOffs;
size_t nLen1 = fnCountStr(pBuffer + m_nRxOffs, uiMaxLen1);
size_t nMaxLen2 = GetWritePos();
size_t nLen2 = nLen1 == uiMaxLen1 ? fnCountStr(pBuffer, nMaxLen2) : 0;
if (nLen1 || nLen2 != nMaxLen2)
{
ssMsg.resize(nLen1 + nLen2);
if (nLen1)
std::copy(pBuffer + m_nRxOffs, pBuffer + m_nRxOffs + nLen1, ssMsg.begin());
if (nLen2)
std::copy(pBuffer, pBuffer + nLen2, ssMsg.begin() + nLen1);
// Update Rx
m_nRxOffs = nLen2 ? nLen2 + 1 : (nLen1 == uiMaxLen1 ? 1 : m_nRxOffs + nLen1 + 1);
// Return result
return ssMsg;
}
}
else if (m_nRxOffs < GetWritePos())
{
// Copy the string
size_t nMaxLen = GetWritePos() - m_nRxOffs;
size_t nLen = fnCountStr(pBuffer + m_nRxOffs, nMaxLen);
if (nLen != nMaxLen)
{
// Copy characters and include null-character.
ssMsg.resize(nLen);
std::copy(pBuffer + m_nRxOffs, pBuffer + m_nRxOffs + nLen, ssMsg.begin());
// Update Rx
m_nRxOffs += nLen + 1;
// Return result
return ssMsg;
}
}
else if (!nTimeout)
return {};
else
std::this_thread::sleep_for(std::chrono::milliseconds(1));
} while (std::chrono::duration_cast<std::chrono::duration<size_t, std::milli>>(
std::chrono::high_resolution_clock::now() - tpStart).count() < nTimeout);
return {};
}
CTraceFifoWriter::CTraceFifoWriter(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16384*/) :
CTraceFifoImpl(uiInstanceID, nSize)
{}
CTraceFifoWriter::~CTraceFifoWriter()
{
Close();
}
CTraceFifoWriter::CTraceFifoWriter(CTraceFifoWriter&& rfifo) noexcept : CTraceFifoImpl(static_cast<CTraceFifoImpl&&>(rfifo))
{}
CTraceFifoWriter& CTraceFifoWriter::operator=(CTraceFifoWriter&& rfifo) noexcept
{
CTraceFifoImpl::operator=(static_cast<CTraceFifoImpl&&>(rfifo));
return *this;
}
bool CTraceFifoWriter::Open(size_t nTimeout /*= 1000*/, uint32_t uiFlags /*= 0u*/)
{
if (IsOpened()) return true;
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
return CTraceFifoImpl::Open(nTimeout, uiFlags & ~static_cast<uint32_t>(ETraceFifoOpenFlags::read_only));
}
void CTraceFifoWriter::Close()
{
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
CTraceFifoImpl::Close();
Terminate();
}
void CTraceFifoWriter::Publish(const std::string& rssMessage)
{
if (!IsOpened())
{
Open();
if (!IsOpened()) return; // Must be connected
}
std::unique_lock<std::recursive_mutex> lock(CreateAccessLockObject());
// Message size is limited
size_t nBuffSize = GetDataBufferSize();
uint8_t* pBuffer = GetDataPtr();
if (rssMessage.size() + 2u > nBuffSize) return;
// Two chunks to copy:
// 1. From start of message to end of message or end of buffer (whatever is lower).
// 2. When leftover, from start of buffer until end of message.
size_t nStart = GetWritePos();
size_t nLen = rssMessage.size() + 1u;
size_t nStop = nStart + nLen;
size_t nLen1 = std::min(nBuffSize - nStart, nLen);
size_t nLen2 = nLen - nLen1;
// Copy the message
if (nLen1) std::copy_n(rssMessage.c_str(), nLen1, pBuffer + GetWritePos());
if (nLen2) std::copy_n(rssMessage.c_str() + nLen1, nLen2, pBuffer);
SetWritePos(nStop > nBuffSize ? nLen2 : nStop);
}
CTraceFifoStreamBuffer::CTraceFifoStreamBuffer(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16384*/) :
CTraceFifoWriter(uiInstanceID, nSize)
{}
CTraceFifoStreamBuffer::~CTraceFifoStreamBuffer()
{
Close();
}
void CTraceFifoStreamBuffer::InterceptStream(std::ostream& rstream)
{
m_mapBindings.emplace(&rstream, std::make_unique<SInterceptBinding>(rstream, *this));
}
void CTraceFifoStreamBuffer::RevertInterception(std::ostream& rstream)
{
auto itBinding = m_mapBindings.find(&rstream);
if (itBinding != m_mapBindings.end())
m_mapBindings.erase(itBinding);
}
void CTraceFifoStreamBuffer::Close()
{
sync();
m_mapBindings.clear();
CTraceFifoWriter::Close();
}
int CTraceFifoStreamBuffer::sync()
{
Publish(str());
if (!m_mapBindings.empty())
m_mapBindings.begin()->second->streamOrginal << str();
str(std::string()); // Clear the string buffer
return 0;
}
CTraceFifoStreamBuffer::SInterceptBinding::SInterceptBinding(std::ostream& rstream, CTraceFifoStreamBuffer& rstreamBuffer) :
rstreamIntercepted(rstream), streamOrginal(rstream.rdbuf(&rstreamBuffer))
{}
CTraceFifoStreamBuffer::SInterceptBinding::~SInterceptBinding()
{
rstreamIntercepted.rdbuf(streamOrginal.rdbuf(nullptr));
}
CTraceFifoStdBuffer::CTraceFifoStdBuffer(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16384*/) :
CTraceFifoWriter(uiInstanceID, nSize)
{}
CTraceFifoStdBuffer::~CTraceFifoStdBuffer()
{
Close();
}
bool CTraceFifoStdBuffer::Open(size_t nTimeout /*= 1000*/, uint32_t uiFlags /*= 0u*/)
{
// Close before...
Close();
// Open the fifo
bool bRet = CTraceFifoWriter::Open(nTimeout, uiFlags);
if (!bRet)
{
std::cout << "Help -1" << std::endl;
return false;
}
// Create two pipes for the standard streams stdout and stderr.
#ifdef _WIN32
bRet = pipe(m_rgPipeStdOut, 16384, O_TEXT) == 0;
if (bRet) bRet = pipe(m_rgPipeStdErr, 16384, O_TEXT) == 0;
#else
bRet = pipe(m_rgPipeStdOut) == 0;
if (bRet) bRet = pipe(m_rgPipeStdErr) == 0;
#endif
// Duplicate the StdOut and StdErr descriptors
if (bRet)
{
m_iOldStdOut = dup(fileno(stdout));
m_iOldStdErr = dup(fileno(stderr));
if (m_iOldStdOut == -1 || m_iOldStdErr == -1)
bRet = false;
}
// Assign the pipes to the StdOut and StdErr
if (bRet) bRet = dup2(m_rgPipeStdOut[nWriteIndex], fileno(stdout)) >= 0;
if (bRet) bRet = dup2(m_rgPipeStdErr[nWriteIndex], fileno(stderr)) >= 0;
// Start the dispatch thread
m_bShutdown = false;
if (bRet) m_threadDispatch = std::thread(&CTraceFifoStdBuffer::DispatchThreadFunc, this);
if (!bRet)
{
Close();
return false;
}
return true;
}
#ifdef _MSC_VER
// Prevent static code analysis warning for unused return value of _dup2.
#pragma warning(push)
#pragma warning(disable : 6031)
#endif
void CTraceFifoStdBuffer::Close()
{
// Prevent multiple re-entries
if (!m_bShutdown && (m_iOldStdOut != -1 || m_iOldStdErr != -1))
{
// Flush all streams
std::cout.flush();
std::clog.flush();
std::cerr.flush();
fflush(stdout);
fflush(stderr);
// Wait for processing finishing the dispatching
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
// Reassign the existing descriptors of StdOut and StdErr.
if (m_iOldStdOut != -1)
dup2(m_iOldStdOut, fileno(stdout));
if (m_iOldStdErr != -1)
dup2(m_iOldStdErr, fileno(stderr));
// Shutdown the thread
m_bShutdown = true;
if (m_threadDispatch.joinable())
m_threadDispatch.join();
}
// Close the duplictaed desciptors
if (m_iOldStdOut != -1)
close(m_iOldStdOut);
if (m_iOldStdErr != -1)
close(m_iOldStdErr);
if (m_rgPipeStdOut[nReadIndex] != -1)
close(m_rgPipeStdOut[nReadIndex]);
if (m_rgPipeStdOut[nWriteIndex] != -1)
close(m_rgPipeStdOut[nWriteIndex]);
if (m_rgPipeStdErr[nReadIndex] != -1)
close(m_rgPipeStdErr[nReadIndex]);
if (m_rgPipeStdErr[nWriteIndex] != -1)
close(m_rgPipeStdErr[nWriteIndex]);
m_iOldStdOut = -1;
m_iOldStdErr = -1;
m_rgPipeStdOut[nReadIndex] = -1;
m_rgPipeStdOut[nWriteIndex] = -1;
m_rgPipeStdErr[nReadIndex] = -1;
m_rgPipeStdErr[nWriteIndex] = -1;
// Close the fifo
CTraceFifoWriter::Close();
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
void CTraceFifoStdBuffer::DispatchThreadFunc()
{
sdv::pointer<char> ptrBuffer;
ptrBuffer.resize(8192);
while (!m_bShutdown)
{
// Read the StdOut pipe
while (!m_bShutdown && m_rgPipeStdOut[nReadIndex] != -1)
{
#ifdef _WIN32
if (eof(m_rgPipeStdOut[nReadIndex])) break;
#else
struct pollfd sPoll{};
sPoll.fd = m_rgPipeStdOut[nReadIndex];
sPoll.events = POLLIN;
if (poll(&sPoll, 1, 1) <= 0) break;
#endif
int iBytesRead = read(m_rgPipeStdOut[nReadIndex], ptrBuffer.get(), static_cast<unsigned int>(ptrBuffer.size()));
if (iBytesRead <= 0) break;
std::string ssMsg(ptrBuffer.get(), static_cast<size_t>(iBytesRead));
Publish(ssMsg);
}
// Read the StdErr pipe
while (!m_bShutdown && m_rgPipeStdErr[nReadIndex] != -1)
{
#ifdef _WIN32
if (eof(m_rgPipeStdErr[nReadIndex])) break;
#else
struct pollfd sPoll{};
sPoll.fd = m_rgPipeStdErr[nReadIndex];
sPoll.events = POLLIN;
if (poll(&sPoll, 1, 1) <= 0) break;
#endif
int iBytesRead = read(m_rgPipeStdErr[nReadIndex], ptrBuffer.get(), static_cast<unsigned int>(ptrBuffer.size()));
if (iBytesRead <= 0) break;
std::string ssMsg(ptrBuffer.get(), static_cast<size_t>(iBytesRead));
Publish(ssMsg);
}
// Wait 10 ms
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
#ifdef _WIN32
#pragma pop_macro("O_TEXT")
#pragma pop_macro("dup")
#pragma pop_macro("dup2")
#pragma pop_macro("fileno")
#pragma pop_macro("close")
#pragma pop_macro("pipe")
#pragma pop_macro("read")
#pragma pop_macro("eof")
#endif

View File

@@ -0,0 +1,473 @@
#ifndef TRACE_FIFO_H
#define TRACE_FIFO_H
#include <cstdint>
#include <mutex>
#include <string>
#include <sstream>
#include <map>
/**
* @brief trace fifo open flags
*/
enum class ETraceFifoOpenFlags : uint32_t
{
open_only = 1, ///< Open only. Do not create a new shared memory area.
force_create = 2, ///< Create a new shared memory area, removing any previous connection.
read_only = 4, ///< Open the shared memory for read-only access.
};
/**
* @brief Trace fifo class allowing the publishing and monitoring of trace messages.
*/
class CTraceFifoBase
{
public:
/**
* @brief Default constructor
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
* @param[in] nSize Default size of the fifo.
*/
CTraceFifoBase(uint32_t uiInstanceID, size_t nSize);
/**
* @brief Default destructor
* @remarks Automatically closes the fifo if opened before.
*/
virtual ~CTraceFifoBase();
/**
* @brief Copy constructor is not available.
* @param[in] rfifo Reference to the fifo to copy.
*/
CTraceFifoBase(const CTraceFifoBase& rfifo) = delete;
/**
* @brief Move constructor.
* @param[in] rfifo Reference to the fifo.
*/
CTraceFifoBase(CTraceFifoBase&& rfifo) noexcept;
/**
* @brief Copy assignment is not available.
* @param[in] rfifo Reference to the fifo.
* @return Returns a reference to this fifo.
*/
CTraceFifoBase& operator=(const CTraceFifoBase& rfifo) = delete;
/**
* @brief Move assignment.
* @param[in] rfifo Reference to the fifo.
* @return Returns a reference to this fifo.
*/
CTraceFifoBase& operator=(CTraceFifoBase&& rfifo) noexcept;
/**
* @brief Open the fifo. Implemented by derived class.
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
* established.
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
* @return Returns true when connected; false otherwise.
*/
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) = 0;
/**
* @brief Cancel any running task and close an open fifo. Implemented by derived class.
*/
virtual void Close() = 0;
/**
* @brief Is the fifo open for reading and writing? Implemented by derived class.
* @return Returns true when opened; false otherwise.
*/
virtual bool IsOpened() const = 0;
/**
* @brief Set a new instance ID for the communication.
* @remarks The instance ID can only be set when the fifo is not opened yet.
* @param[in] uiInstanceID The new instance ID.
* @return Returns whether setting the instance ID was successful.
*/
bool SetInstanceID(uint32_t uiInstanceID);
/**
* @brief Return the instance ID to use for the communication.
* @return The instance ID.
*/
uint32_t GetInstanceID() const;
/**
* @brief Set a new default size to be used during creation of the fifo.
* @param[in] nSize The new default size of the buffer.
*/
void SetDefaultSize(size_t nSize);
/**
* @brief Get the default size used during creation of the fifo.
* @return The default size of the buffer.
*/
size_t GetDefaultSize() const;
/**
* @brief Get the size of the view.
* @return The view size.
*/
size_t GetViewSize() const;
/**
* @brief Get the size of the buffer without any headers.
* @return Returns the size of the buffer or 0 when the buffer is not initialized.
*/
size_t GetDataBufferSize() const;
protected:
/**
* @brief Initialize the buffer after successful opening.
* @param[in] pView Pointer to the shared memory view.
* @param[in] nBufferSize The size of the buffer allocated.
* @param[in] bReadOnly When set, the buffer is initialized as read-only. Multiple read-only and only one writable buffer access
* are allowed.
*/
void InitializeBuffer(void* pView, size_t nBufferSize, bool bReadOnly);
/**
* @brief Check whether the buffer is initialized (signature and instance ID are set).
* @return Returns true when the buffer has been initialized; false when not.
*/
bool IsInitialized() const;
/**
* @brief Clear the buffer pointers
*/
void Terminate();
/**
* @brief Create a lock object for exclusive access of the buffer.
* @return The lock object that allows access.
*/
std::unique_lock<std::recursive_mutex> CreateAccessLockObject() const;
/**
* @brief Get access to the view.
* @return Get a pointer to the buffer. Returns NULL when the buffer is not initialized.
*/
void* GetView();
/**
* @{
* @brief Get access to the data portion of the buffer.
* @return Get a pointer to the data. Returns NULL when the buffer is not initialized.
*/
uint8_t* GetDataPtr();
const uint8_t* GetDataPtr() const;
/**
* @}
*/
/**
* @brief Get the write position.
* @return The current write position within the buffer.
*/
size_t GetWritePos() const;
/**
* @brief Set the write position. Can only be used by writable buffer (causing an access violation otherwise).
* @param[in] nTxPos The new write position.
*/
void SetWritePos(size_t nTxPos);
private:
/**
* @brief Shared memory header structure; at the front of the shared memory buffer.
*/
struct SSharedMemBufHeader
{
char rgszSignature[8]; ///< Signature "SDV_MON\0"
uint32_t uiInstanceID; ///< Instance ID of the server instance
volatile uint32_t uiTxOffs; ///< Tx position
};
mutable volatile bool m_bInitConfirmed = false; ///< When set, bypasses the header checking.
uint32_t m_uiInstanceID = 1000; ///< Instance ID to use while connecting.
size_t m_nSize = 0; ///< Size of the fifo.
size_t m_nDefaultSize = 0; ///< Requested size.
uint8_t* m_pBuffer = nullptr; ///< Pointer to the buffer.
SSharedMemBufHeader* m_psHdr = nullptr; ///< Shared memory header at from of the buffer.
mutable std::recursive_mutex m_mtxAccess; ///< Protect against sudden closure.
};
// Include the implementation classes classes for the trace fifo
#include "trace_fifo_windows.h"
#include "trace_fifo_posix.h"
/**
* @brief Reader class for the trace fifo. Multiple readers can coexist.
*/
class CTraceFifoReader : public CTraceFifoImpl
{
public:
/**
* @brief Default constructor
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
* @param[in] nSize Default size of the fifo.
*/
CTraceFifoReader(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);
/**
* @brief Default destructor
* @remarks Automatically closes the fifo if opened before.
*/
virtual ~CTraceFifoReader() override;
/**
* @brief Copy constructor is not available.
* @param[in] rfifo Reference to the fifo to copy.
*/
CTraceFifoReader(const CTraceFifoReader& rfifo) = delete;
/**
* @brief Move constructor.
* @param[in] rfifo Reference to the fifo.
*/
CTraceFifoReader(CTraceFifoReader&& rfifo) noexcept;
/**
* @brief Copy assignment is not available.
* @param[in] rfifo Reference to the fifo.
* @return Returns a reference to this fifo.
*/
CTraceFifoReader& operator=(const CTraceFifoReader& rfifo) = delete;
/**
* @brief Move assignment.
* @param[in] rfifo Reference to the fifo.
* @return Returns a reference to this fifo.
*/
CTraceFifoReader& operator=(CTraceFifoReader&& rfifo) noexcept;
/**
* @brief Open the fifo. Override of CTraceFifoBase::Open.
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
* established.
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
* @return Returns true when connected; false otherwise.
*/
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
// cppcheck-suppress virtualCallInConstructor
/**
* @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
*/
virtual void Close() override;
/**
* @brief Wait for a message.
* @remarks Automatically opens the fifo if not opened before.
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a message has been
* received.
* @return The received message or an empty message if a timeout occurred or the publisher had sent an empty message.
*/
std::string WaitForMessage(size_t nTimeout = 1000);
private:
size_t m_nRxOffs = 0; ///< Reader position
};
/**
* @brief Writer class for a trace fifo. Only one writer should be present.
*/
class CTraceFifoWriter : public CTraceFifoImpl
{
public:
/**
* @brief Default constructor
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
* @param[in] nSize Default size of the fifo.
*/
CTraceFifoWriter(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);
/**
* @brief Default destructor
* @remarks Automatically closes the fifo if opened before.
*/
virtual ~CTraceFifoWriter() override;
/**
* @brief Copy constructor is not available.
* @param[in] rfifo Reference to the fifo to copy.
*/
CTraceFifoWriter(const CTraceFifoWriter& rfifo) = delete;
/**
* @brief Move constructor.
* @param[in] rfifo Reference to the fifo.
*/
CTraceFifoWriter(CTraceFifoWriter&& rfifo) noexcept;
/**
* @brief Copy assignment is not available.
* @param[in] rfifo Reference to the fifo.
* @return Returns a reference to this fifo.
*/
CTraceFifoWriter& operator=(const CTraceFifoWriter& rfifo) = delete;
/**
* @brief Move assignment.
* @param[in] rfifo Reference to the fifo.
* @return Returns a reference to this fifo.
*/
CTraceFifoWriter& operator=(CTraceFifoWriter&& rfifo) noexcept;
/**
* @brief Open the fifo. Override of CTraceFifoBase::Open.
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
* established.
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
* @return Returns true when connected; false otherwise.
*/
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
// cppcheck-suppress virtualCallInConstructor
/**
* @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
*/
virtual void Close() override;
/**
* @brief Publish a message. If the buffer is full, the oldest message is removed.
* @remarks Automatically opens the fifo if not opened before.
* @param[in] rssMessage Reference to the message to publish.
*/
void Publish(const std::string& rssMessage);
};
/**
* @brief Trace fifo stream buffer object.
* @remarks This implementation works for applications within the scope of the C++ library source compilation into one binary unit.
* This is due to the templated nature of the C++ library allowing very little reuse of compiled code across binary units. This
* means, that although interception will work in one unit (e.g. executable or static/shared library), it might not intercept
* messages from another unit.
*/
class CTraceFifoStreamBuffer : public CTraceFifoWriter, public std::stringbuf
{
public:
/**
* @brief Constructor
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
* @param[in] nSize Default size of the fifo.
*/
CTraceFifoStreamBuffer(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);
/**
* @brief Destructor
*/
virtual ~CTraceFifoStreamBuffer() override;
/**
* @brief Assign this buffer to a stream object. Any message will be streamed to both the original stream as well as the trace
* fifo.
* @attention The stream object needs to stay in scope until the assignment is removed or this class is destroyed.
* @attention Works for text only.
* @remarks Removal of this assignment is done automatically during destruction of this class.
* @param[in] rstream Reference to the stream to intercept the communication for.
*/
void InterceptStream(std::ostream& rstream);
/**
* @brief Remove the interception of a stream.
* @param[in] rstream Reference to the stream to revert the interception for.
*/
void RevertInterception(std::ostream& rstream);
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
// cppcheck-suppress virtualCallInConstructor
/**
* @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
*/
virtual void Close() override;
/**
* @brief Synchronizes the buffers with the associated character sequence.
* @return Returns 0 if successful; -1 if not.
*/
int sync();
private:
/**
* @brief Stream interception binding.
*/
struct SInterceptBinding
{
/**
* @brief Constructor doing the binding.
*/
SInterceptBinding(std::ostream& rstream, CTraceFifoStreamBuffer& rstreamBuffer);
//SInterceptBinding(SInterceptBinding& rsBinding) : rstreamIntercepted(rsBinding.rstreamIntercepted), streamOrginal(rsBinding.streamOrginal.rdbuf()) {}
/**
* @brief Destructor undoing the binding.
*/
~SInterceptBinding();
std::ostream& rstreamIntercepted; ///< The intercepted stream
std::ostream streamOrginal; ///< Stream object redirecting the buffer.
};
std::map<std::ostream*, std::unique_ptr<SInterceptBinding>> m_mapBindings; ///< Map with intercepted stream bindings.
};
/**
* @brief Interception and dispatching of all messages from STDOUT and STDERR to the trace fifo.
*/
class CTraceFifoStdBuffer : public CTraceFifoWriter
{
public:
/**
* @brief Constructor
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
* @param[in] nSize Default size of the fifo.
*/
CTraceFifoStdBuffer(uint32_t uiInstanceID = 1000u, size_t nSize = 16384);
/**
* @brief Destructor
*/
virtual ~CTraceFifoStdBuffer() override;
/**
* @brief Open the fifo and direct all messages from StdOut and StdErr to the trace fifo. Override of CTraceFifoBase::Open.
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
* established.
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
* @return Returns true when connected; false otherwise.
*/
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
// cppcheck-suppress virtualCallInConstructor
/**
* @brief Revert the redirection and close the open fifo. Override of CTraceFifoBase::Close.
*/
virtual void Close() override;
private:
/**
* @brief Read from the pipe and forward the text to the trace fifo. Read until shutdown flag is switch on.
*/
void DispatchThreadFunc();
const size_t nReadIndex = 0; ///< The read index of the pipe descriptor.
const size_t nWriteIndex = 1; ///< The write index of the pipe descriptor.
bool m_bShutdown = false; ///< Shutdown flag for the pipe reader thread.
int m_rgPipeStdOut[2] = { -1, -1 }; ///< StdOut pipe with read and write descriptors.
int m_rgPipeStdErr[2] = { -1, -1 }; ///< StdErr pipe with read and write descriptors.
int m_iOldStdOut = -1; ///< Old descriptor for the StdOut
int m_iOldStdErr = -1; ///< Old descriptor for the StdErr
std::thread m_threadDispatch; ///< Dispatch thread
};
#endif // !defined TRACE_FIFO_H

View File

@@ -0,0 +1,148 @@
#if defined __unix__
#ifndef INCLUDE_TRACE_FIFO_PLATFORM
#error Do not include this file directly. The file is included by trace_fifo.cpp.
#endif
#include "trace_fifo_posix.h"
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>
#include <string>
#include <thread>
#include <chrono>
#include <algorithm>
#include <mutex>
#include <support/string.h>
CTraceFifoPosix::CTraceFifoPosix(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16*1024*/) :
CTraceFifoBase(uiInstanceID, nSize)
{}
CTraceFifoPosix::~CTraceFifoPosix()
{
Close();
}
CTraceFifoPosix::CTraceFifoPosix(CTraceFifoPosix&& rfifo) :
CTraceFifoBase(static_cast<CTraceFifoBase&&>(rfifo)), m_iFileDescr(rfifo.m_iFileDescr)
{
rfifo.m_iFileDescr = 0;
}
CTraceFifoPosix& CTraceFifoPosix::operator=(CTraceFifoPosix&& rfifo)
{
Close();
CTraceFifoBase::operator=(static_cast<CTraceFifoBase&&>(rfifo));
m_iFileDescr = rfifo.m_iFileDescr;
rfifo.m_iFileDescr = 0;
return *this;
}
bool CTraceFifoPosix::Open(size_t nTimeout /*= 1000*/, uint32_t uiFlags /*= 0u*/)
{
std::string ssSharedMemName = "SDV_LOG_MONITOR_" + std::to_string(GetInstanceID());
auto lock = CreateAccessLockObject();
auto tpStart = std::chrono::high_resolution_clock::now();
bool bOpenOnly = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::open_only);
bool bForceCreate = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::force_create);
bool bReadOnly = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::read_only);
if (bOpenOnly && bForceCreate) return false;
// In case of a force-create-flag, unlink a potential previous allocation.
if (bForceCreate) shm_unlink(ssSharedMemName.c_str());
do
{
if (IsOpened()) break;
// Try creating the file mapping object
if (!m_iFileDescr)
{
int iMapAccess = O_RDWR | O_CREAT;
if (bOpenOnly)
iMapAccess = bReadOnly ? O_RDONLY : O_RDWR;
else if (bForceCreate)
iMapAccess |= O_EXCL;
m_iFileDescr = shm_open(ssSharedMemName.c_str(), iMapAccess, S_IRUSR | S_IWUSR);
}
if (m_iFileDescr == -1)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
// Check for the size of the memory. If zero, the memory is not initialized yet.
struct stat sStatistics{};
int iResult = fstat(m_iFileDescr, &sStatistics);
if (iResult == -1)
{
Close();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
// Extend shared memory object as by default it's initialized with size 0
if (!sStatistics.st_size)
{
// Readonly files cannot be truncated
if (bReadOnly)
{
Close();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
iResult = ftruncate(m_iFileDescr, static_cast<off_t>(GetDefaultSize()));
if (iResult != -1)
iResult = fstat(m_iFileDescr, &sStatistics);
if (iResult == -1 || !sStatistics.st_size)
{
Close();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
}
// Map the file into memory
int iViewAccess = PROT_READ;
if (!bOpenOnly && !bReadOnly)
iViewAccess |= PROT_WRITE;
void* pView = mmap(NULL, sStatistics.st_size, iViewAccess, MAP_SHARED, m_iFileDescr, 0);
if (pView == MAP_FAILED)
{
Close();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
// Needs initialization?
InitializeBuffer(pView, sStatistics.st_size, bReadOnly);
}
while (std::chrono::duration_cast<std::chrono::duration<size_t, std::milli>>(
std::chrono::high_resolution_clock::now() - tpStart).count() < nTimeout);
return IsOpened();
}
void CTraceFifoPosix::Close()
{
if (GetView()) munmap(GetView(), GetViewSize());
if (m_iFileDescr)
close(m_iFileDescr);
m_iFileDescr = 0;
Terminate();
}
bool CTraceFifoPosix::IsOpened() const
{
return m_iFileDescr ? true : false;
}
#endif // defined __unix__

View File

@@ -0,0 +1,82 @@
#if !defined TRACE_FIFO_POSIX_H && defined __unix__
#define TRACE_FIFO_POSIX_H
#ifndef TRACE_FIFO_H
#error Do not include this file directly. Include trace_fifo.h instead.
#endif
/**
* @brief Trace fifo shared memory class for Posix.
*/
class CTraceFifoPosix : public CTraceFifoBase
{
public:
/**
* @brief Default constructor
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
* @param[in] nSize Size of the fifo.
*/
CTraceFifoPosix(uint32_t uiInstanceID = 1000u, size_t nSize = 16*1024);
/**
* @brief Default destructor
* @remarks Automatically closes the fifo if opened before.
*/
virtual ~CTraceFifoPosix();
/**
* @brief Copy constructor is not available.
* @param[in] rfifo Reference to the fifo to copy.
*/
CTraceFifoPosix(const CTraceFifoPosix& rfifo) = delete;
/**
* @brief Move constructor.
* @param[in] rfifo Reference to the fifo.
*/
CTraceFifoPosix(CTraceFifoPosix&& rfifo);
/**
* @brief Copy assignment is not available.
* @param[in] rfifo Reference to the fifo.
* @return Returns a reference to this fifo.
*/
CTraceFifoPosix& operator=(const CTraceFifoPosix& rfifo) = delete;
/**
* @brief Move assignment.
* @param[in] rfifo Reference to the fifo.
* @return Returns a reference to this fifo.
*/
CTraceFifoPosix& operator=(CTraceFifoPosix&& rfifo);
/**
* @brief Open the fifo. Overload of CTraceFifoBase::Open.
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
* established.
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
* @return Returns true when connected; false otherwise.
*/
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
// cppcheck-suppress virtualCallInConstructor
/**
* @brief Cancel any running task and close an open fifo. Overload of CTraceFifoBase::Close.
*/
virtual void Close() override;
/**
* @brief Is the fifo open for reading and writing? Overload of CTraceFifoBase::IsOpened.
* @return Returns true when opened; false otherwise.
*/
virtual bool IsOpened() const override;
private:
int m_iFileDescr = 0; ///< File descriptor of the shared memory.
};
/// The Posix implementation of the trace fifo.
using CTraceFifoImpl = CTraceFifoPosix;
#endif // !defined TRACE_FIFO_POSIX_H

View File

@@ -0,0 +1,135 @@
#if defined _WIN32
#ifndef INCLUDE_TRACE_FIFO_PLATFORM
#error Do not include this file directly. The file is included by trace_fifo.cpp.
#endif
#include "trace_fifo_windows.h"
#include <string>
#include <thread>
#include <chrono>
#include <algorithm>
#include <mutex>
#include <support/string.h>
CTraceFifoWindows::CTraceFifoWindows(uint32_t uiInstanceID /*= 1000u*/, size_t nSize /*= 16*1024*/) :
CTraceFifoBase(uiInstanceID, nSize)
{}
CTraceFifoWindows::~CTraceFifoWindows()
{
Close();
}
CTraceFifoWindows::CTraceFifoWindows(CTraceFifoWindows&& rfifo) noexcept:
CTraceFifoBase(static_cast<CTraceFifoBase&&>(rfifo)), m_hMapFile(rfifo.m_hMapFile)
{
rfifo.m_hMapFile = INVALID_HANDLE_VALUE;
}
CTraceFifoWindows& CTraceFifoWindows::operator=(CTraceFifoWindows&& rfifo) noexcept
{
Close();
CTraceFifoBase::operator=(static_cast<CTraceFifoBase&&>(rfifo));
m_hMapFile = rfifo.m_hMapFile;
rfifo.m_hMapFile = INVALID_HANDLE_VALUE;
return *this;
}
#ifdef _MSC_VER
// Prevent bogus warning about uninitialized memory for the variable *hFile.
#pragma warning(push)
#pragma warning(disable : 6001)
#endif
bool CTraceFifoWindows::Open(size_t nTimeout /*= 1000*/, uint32_t uiFlags /*= 0u*/)
{
std::string ssSharedMemName = "SDV_TRACE_MONITOR_" + std::to_string(GetInstanceID());
auto tpStart = std::chrono::high_resolution_clock::now();
bool bOpenOnly = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::open_only);
bool bForceCreate = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::force_create);
bool bReadOnly = uiFlags & static_cast<uint32_t>(ETraceFifoOpenFlags::read_only);
if (bOpenOnly && bForceCreate) return false;
do
{
if (IsOpened()) break;
// Try creating the file mapping object
if (m_hMapFile == nullptr || m_hMapFile == INVALID_HANDLE_VALUE)
{
if (bOpenOnly)
{
DWORD dwMapAccess = bReadOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS;
m_hMapFile = OpenFileMappingA(dwMapAccess, false, ssSharedMemName.c_str());
}
else
{
DWORD dwMapAccess = PAGE_READWRITE;
m_hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, dwMapAccess, 0, static_cast<DWORD>(GetDefaultSize()), ssSharedMemName.c_str());
if (m_hMapFile != nullptr && m_hMapFile != INVALID_HANDLE_VALUE && bForceCreate && GetLastError() == ERROR_ALREADY_EXISTS)
{
CloseHandle(m_hMapFile);
m_hMapFile = INVALID_HANDLE_VALUE;
}
}
}
if (m_hMapFile == nullptr || m_hMapFile == INVALID_HANDLE_VALUE)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
// Map the file into memory
DWORD dwViewAccess = bReadOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS;
void* pView = MapViewOfFile(m_hMapFile, dwViewAccess, 0, 0, /*GetDefaultSize()*/0);
if (!pView)
{
CloseHandle(m_hMapFile);
m_hMapFile = INVALID_HANDLE_VALUE;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
// Request the size of the mapping - this will update the size...
MEMORY_BASIC_INFORMATION sMemInfo{};
if (VirtualQuery(pView, &sMemInfo, sizeof(sMemInfo)) != sizeof(sMemInfo) || !sMemInfo.RegionSize)
{
UnmapViewOfFile(pView);
CloseHandle(m_hMapFile);
m_hMapFile = INVALID_HANDLE_VALUE;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
continue;
}
// Needs Initialize
InitializeBuffer(pView, sMemInfo.RegionSize, bReadOnly);
}
while (std::chrono::duration_cast<std::chrono::duration<size_t, std::milli>>(
std::chrono::high_resolution_clock::now() - tpStart).count() < nTimeout);
return IsOpened();
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
void CTraceFifoWindows::Close()
{
if (GetView()) UnmapViewOfFile(GetView());
if (m_hMapFile && m_hMapFile != INVALID_HANDLE_VALUE)
CloseHandle(m_hMapFile);
m_hMapFile = INVALID_HANDLE_VALUE;
Terminate();
}
bool CTraceFifoWindows::IsOpened() const
{
return m_hMapFile && m_hMapFile != INVALID_HANDLE_VALUE;
}
#endif // defined _WIN32

View File

@@ -0,0 +1,95 @@
#if !defined TRACE_FIFO_WINDOWS_H && defined _WIN32
#define TRACE_FIFO_WINDOWS_H
// Resolve conflict
#pragma push_macro("interface")
#undef interface
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <WinSock2.h>
#include <Windows.h>
#include <process.h>
// Resolve conflict
#pragma pop_macro("interface")
#ifdef GetClassInfo
#undef GetClassInfo
#endif
/**
* @brief Trace fifo shared memory class for Windows.
*/
class CTraceFifoWindows : public CTraceFifoBase
{
public:
/**
* @brief Default constructor
* @param[in] uiInstanceID The instance ID to use for sending/monitoring the messages.
* @param[in] nSize Size of the fifo.
*/
CTraceFifoWindows(uint32_t uiInstanceID = 1000u, size_t nSize = 16*1024);
/**
* @brief Default destructor
* @remarks Automatically closes the fifo if opened before.
*/
virtual ~CTraceFifoWindows() override;
/**
* @brief Copy constructor is not available.
* @param[in] rfifo Reference to the fifo to copy.
*/
CTraceFifoWindows(const CTraceFifoWindows& rfifo) = delete;
/**
* @brief Move constructor.
* @param[in] rfifo Reference to the fifo.
*/
CTraceFifoWindows(CTraceFifoWindows&& rfifo) noexcept;
/**
* @brief Copy assignment is not available.
* @param[in] rfifo Reference to the fifo.
* @return Returns a reference to this fifo.
*/
CTraceFifoWindows& operator=(const CTraceFifoWindows& rfifo) = delete;
/**
* @brief Move assignment.
* @param[in] rfifo Reference to the fifo.
* @return Returns a reference to this fifo.
*/
CTraceFifoWindows& operator=(CTraceFifoWindows&& rfifo) noexcept;
/**
* @brief Open the fifo. Override of CTraceFifoBase::Open.
* @param[in] nTimeout Timeout to return from this function. A timeout of 0xffffffff does not return until a connection has been
* established.
* @param[in] uiFlags Zero or more flags of ETraceFifoOpenFlags enum.
* @return Returns true when connected; false otherwise.
*/
virtual bool Open(size_t nTimeout = 1000, uint32_t uiFlags = 0u) override;
// Ignore cppcheck warning for not using dynamic binding when being called through the destructor.
// cppcheck-suppress virtualCallInConstructor
/**
* @brief Cancel any running task and close an open fifo. Override of CTraceFifoBase::Close.
*/
virtual void Close() override;
/**
* @brief Is the fifo open for reading and writing? Override of CTraceFifoBase::IsOpened.
* @return Returns true when opened; false otherwise.
*/
virtual bool IsOpened() const override;
private:
HANDLE m_hMapFile = INVALID_HANDLE_VALUE; ///< Handle to the shared memory buffer.
};
/// The Windows implementation of the trace fifo.
using CTraceFifoImpl = CTraceFifoWindows;
#endif // !defined TRACE_FIFO_WINDOWS_H