diff --git a/src/utils/Compatability.h b/src/stubserver/Compatibility.h similarity index 67% rename from src/utils/Compatability.h rename to src/stubserver/Compatibility.h index e19d539..9e6f251 100644 --- a/src/utils/Compatability.h +++ b/src/stubserver/Compatibility.h @@ -1,7 +1,7 @@ /* * Compatability.h * - * Copyright (C) 2015 Holger Grosenick + * Copyright (C) 2015-2022 Holger Grosenick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,18 +17,31 @@ * along with this program. If not, see . */ -#ifndef UTILS_COMPATABILITY_H_ -#define UTILS_COMPATABILITY_H_ +#ifndef UTILS_COMPATIBILITY_H_ +#define UTILS_COMPATIBILITY_H_ /** * Some basic defines to handle differences between Windows (Visual Studio 2013) and Linux. */ #ifdef _WIN32 -#define NOEXCEPT +//--- Windows --- #include + +// Compatibility implemented in DateTime.cpp +extern "C" { +int gettimeofday(struct timeval* tp, struct timezone* tzp); +} + +#define SSCANF sscanf_s +#define strncasecmp _strnicmp +#define strcasecmp _stricmp + #else -#define NOEXCEPT noexcept +//--- Linux --- + +#define SSCANF sscanf + #endif -#endif /* UTILS_COMPATABILITY_H_ */ +#endif /* UTILS_COMPATIBILITY_H_ */ diff --git a/src/stubserver/SimulatedDevice.cpp b/src/stubserver/SimulatedDevice.cpp index ec516a9..126f7e1 100644 --- a/src/stubserver/SimulatedDevice.cpp +++ b/src/stubserver/SimulatedDevice.cpp @@ -872,8 +872,6 @@ SimulatedDevice::SimulatedDevice(BrickStack *_brickStack, const char *_uidStr, c , isV2(false) , traceLv(0) { - char msg[200]; - std::string key(uidStr + ".properties"); const char *str = props.get(key); if (str && *str) @@ -896,9 +894,8 @@ SimulatedDevice::SimulatedDevice(BrickStack *_brickStack, const char *_uidStr, c deviceTypeId = gAllDeviceIdentifiers[i].deviceIdentifier; } if (deviceTypeId == 0) { - sprintf(msg, "Unkown device type '%s' for uid %s", str, _uidStr); cleanup(); // cleanup removes the properties -> 'str' would be empty - throw Exception(msg); + throw Exception("Unkown device type '%s' for uid %s", str, _uidStr); } deviceTypeName = str; label = getProperty("label"); @@ -936,8 +933,7 @@ SimulatedDevice::SimulatedDevice(BrickStack *_brickStack, const char *_uidStr, c } else { cleanup(); - sprintf(msg, "Invalid position char '%c' for uid %s", p, _uidStr); - throw Exception(msg); + throw Exception("Invalid position char '%c' for uid %s", p, _uidStr); } // Main brick which is not connected to another brick has parent id '0' @@ -966,12 +962,10 @@ SimulatedDevice::SimulatedDevice(BrickStack *_brickStack, const char *_uidStr, c // parts are already checked above ... if (false == isBrick && (position < 'A' || position > 'H')) { - sprintf(msg, "ERROR: invalid position char '%c' (%d) for BRICKLET %s (must be A..D)", position, position, uidStr.c_str()); - throw utils::Exception(msg); + throw Exception("ERROR: invalid position char '%c' (%d) for BRICKLET %s (must be A..D)", position, position, uidStr.c_str()); } if (true == isBrick && (position < '0' || position > '9') && position != 'I') { - sprintf(msg, "ERROR: invalid position char '%c' (%d) for BRICK %s (must be 0..9,I)", position, position, uidStr.c_str()); - throw utils::Exception(msg); + throw Exception("ERROR: invalid position char '%c' (%d) for BRICK %s (must be 0..9,I)", position, position, uidStr.c_str()); } } catch (const std::exception &e) { @@ -1020,22 +1014,20 @@ void SimulatedDevice::clearVisualizationClient() const { const char *SimulatedDevice::getProperty(const std::string &key, int minLength) { const char *res = properties->get(uidStr + "." + key); - if (res == NULL || *res == 0) + if (!res || *res == 0) res = properties->get(key); - if (res == NULL) { + if (!res) { if (minLength <= 0) return ""; } - if (res == NULL || static_cast(strlen(res)) < minLength) + if (!res || static_cast(strlen(res)) < minLength) { - char msg[128]; if (!res) - sprintf(msg, "Property '%s' for uid %s does not exist, check properties", - key.c_str(), uidStr.c_str()); - else - sprintf(msg, "Property '%s' for uid %s must have length %u, but has %d", - key.c_str(), uidStr.c_str(), minLength, res ? static_cast(strlen(res)) : 0); - throw Exception(msg); + throw Exception("Property '%s' for uid %s does not exist, check properties", + key.c_str(), uidStr.c_str()); + + throw Exception("Property '%s' for uid %s must have length %u, but has %d", + key.c_str(), uidStr.c_str(), minLength, res ? static_cast(strlen(res)) : 0); } return res; } @@ -1094,7 +1086,6 @@ void SimulatedDevice::checkCallbacks() */ void SimulatedDevice::connect(SimulatedDevice* child) { - char msg[128]; bool positions[128]; unsigned index; @@ -1103,8 +1094,7 @@ void SimulatedDevice::connect(SimulatedDevice* child) for (auto it : children) { if (it->uid == child->uid) { - sprintf(msg, "Device with uid %s already connected!", it->getUidStr().c_str()); - throw std::logic_error(msg); + throw Exception("Device with uid %s already connected!", it->getUidStr().c_str()); } index = it->position; positions[index] = true; @@ -1125,14 +1115,12 @@ void SimulatedDevice::connect(SimulatedDevice* child) } if (index > maxIndex) { - sprintf(msg, "Device with uid %s uses position '%c' which is an invalid value, max port value is '%c'!", - child->getUidStr().c_str(), index, maxIndex); - throw std::logic_error(msg); + throw Exception("Device with uid %s uses position '%c' which is an invalid value, max port value is '%c'!", + child->getUidStr().c_str(), index, maxIndex); } if (positions[index]) { - sprintf(msg, "Device with uid %s uses position '%c' which is already connected!", - child->getUidStr().c_str(), index); - throw std::logic_error(msg); + throw Exception("Device with uid %s uses position '%c' which is already connected!", + child->getUidStr().c_str(), index); } children.push_back(child); } diff --git a/src/stubserver/VisualizationClient.h b/src/stubserver/VisualizationClient.h index 91eb386..bceb151 100644 --- a/src/stubserver/VisualizationClient.h +++ b/src/stubserver/VisualizationClient.h @@ -424,6 +424,34 @@ class SensorState : public VisibleDeviceState SensorState(); SensorState(int _min, int _max); + /** + * Returns the value of led1. + */ + uint8_t getLed1() const { + return led1; + } + + /** + * Returns the value of led2. + */ + uint8_t getLed2() const { + return led2; + } + + /** + * Returns the value of led3. + */ + uint8_t getLed3() const { + return led3; + } + + /** + * Returns the value of led1. + */ + uint8_t getLed4() const { + return led4; + } + /** * Returns the current "sensor" value. */ diff --git a/src/utils/AsyncTask.cpp b/src/utils/AsyncTask.cpp index adf84ce..aee9a23 100644 --- a/src/utils/AsyncTask.cpp +++ b/src/utils/AsyncTask.cpp @@ -62,7 +62,15 @@ AsyncTask::~AsyncTask() { */ void AsyncTask::callRunMethod() { - run(); + try { + run(); + } + catch (const std::exception &e) { + Log::error("callRunMethod()", e); + } + catch (...) { + Log::error("Unspecific exception in callRunMethod() !"); + } setActive(false); } diff --git a/src/utils/ChildProcess.cpp b/src/utils/ChildProcess.cpp index 0141d51..a70e2d6 100644 --- a/src/utils/ChildProcess.cpp +++ b/src/utils/ChildProcess.cpp @@ -1,7 +1,7 @@ /* * ChildProcess.cpp * - * Copyright (C) 2013-2021 Holger Grosenick + * Copyright (C) 2013-2022 Holger Grosenick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -78,7 +78,7 @@ struct Redirect if (pipeHandles[0] >= 0 || pipeHandles[1] >= 0) throw std::logic_error("Child stream already open, cannot change any more!"); if (pipe(pipeHandles) < 0) - throw utils::RuntimeError("pipe() failed"); + throw utils::IOException("pipe", ""); type = Redirect::PIPE; } @@ -309,7 +309,7 @@ void ChildProcess::setWorkDir(const char *_workDir) void ChildProcess::splitCmdLine(const char *cmdLine, std::vector &out) { if (!cmdLine || *cmdLine == 0 ) // invalid argument - throw Exception("Invalid argument: 'cmdLine' is NULL or empty"); + throw std::invalid_argument("'cmdLine' is NULL or empty"); out.clear(); // remove all previous entries const char *c = cmdLine; @@ -352,10 +352,33 @@ void ChildProcess::splitCmdLine(const char *cmdLine, std::vector &o else ++c; // set to next valid char +// printf("ADD: %s\n", arg); out.push_back(std::string(arg)); } while (!done); } +/** + * Start the child process and waits until it finishes in the given time period. + * If 'ms' is zero then we wait forever until the process finishes. + *

+ * Internally this is the sequence of + * - start() + * - waitFor() + * - getRc() + *

+ * Returns: + * -1 : if ms is larger than zero and the process did not finish yet + * negative values: process terminated with signal / exception + * other : normal return code of the process + */ +int ChildProcess::run(unsigned ms) +{ + start(); + if (waitFor(ms)) + return getRc(); + return -1; +} + /** * Now really start the child process. */ @@ -492,11 +515,7 @@ void ChildProcess::tryToStart() else { // PID < 0: start of process failed ... active = false; - - int err = errno; - char buffer[2048]; - sprintf(buffer, "Start of '%s' failed", programAndArgs[0].c_str()); - throw utils::RuntimeError(buffer, err); + throw utils::IOException("execv", programAndArgs[0].c_str()); } } @@ -540,7 +559,6 @@ void ChildProcess::validateRedirect() { if (active) throw Exception("Child process already active, cannot change redirect any more!"); - } /** diff --git a/src/utils/ChildProcess.h b/src/utils/ChildProcess.h index 284aeac..c12809d 100644 --- a/src/utils/ChildProcess.h +++ b/src/utils/ChildProcess.h @@ -1,7 +1,7 @@ /* * ChildProcess.h * - * Copyright (C) 2013 Holger Grosenick + * Copyright (C) 2013-2022 Holger Grosenick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,6 +36,15 @@ struct Redirect; /** * This class is used for starting other processes asynchronously. * The status of the process can be asked via the class methods. + *

+ * Usual way to start a ChildProcess is the call sequence:
+ * - start() + * - optionally use redirect methods like "stdout()" + * - waitFor() + * - getRc + * + * OR just + * - run() */ class ChildProcess { @@ -64,12 +73,32 @@ class ChildProcess { setWorkDir(workDir.c_str()); } + /** + * Start the child process and waits until it finishes in the given time period. + * If 'ms' is zero then we wait forever until the process finishes. + *

+ * When run() is used, redirecting stdout / stderr is not possible ! + *

+ * Internally this is the sequence of + * - start() + * - waitFor() + * - getRc() + *

+ * Returns: + * -1 : if ms is larger than zero and the process did not finish yet + * negative values: process terminated with signal / exception + * other : normal return code of the process + */ + int run(unsigned ms = 0); + /** * Start the child process asynchronously and return. If starting the process fails, an * exception is thrown. *

* If a child has finished, the start() method maybe called again. Be aware that existing * redirects get invalidated when the child terminates. + *

+ * After start() you have to use waitFor() to check if the process has finished. */ void start(); diff --git a/src/utils/Compatibility.h b/src/utils/Compatibility.h index 30e742c..9e6f251 100644 --- a/src/utils/Compatibility.h +++ b/src/utils/Compatibility.h @@ -25,6 +25,7 @@ */ #ifdef _WIN32 +//--- Windows --- #include // Compatibility implemented in DateTime.cpp @@ -33,8 +34,11 @@ int gettimeofday(struct timeval* tp, struct timezone* tzp); } #define SSCANF sscanf_s +#define strncasecmp _strnicmp +#define strcasecmp _stricmp #else +//--- Linux --- #define SSCANF sscanf diff --git a/src/utils/DateTime.cpp b/src/utils/DateTime.cpp index 2b15668..466743b 100644 --- a/src/utils/DateTime.cpp +++ b/src/utils/DateTime.cpp @@ -49,6 +49,18 @@ int gettimeofday(struct timeval* tp, struct timezone*) #include "Exceptions.h" +// Be aware that these sources are in UTF-8 !! +// the LCD has a different charset: 0xE1 is 'ä' +static const char MONTH_DE[13][6] = { + "Jan", "Feb", "Mär", "Apr", "Mai", "Jun", + "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", "???" +}; +//static const char MONTH_EN[13][6] = { +// "Jan", "Feb", "Mar", "Apr", "May", "Jun", +// "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" +//}; + + namespace utils { /** @@ -119,11 +131,33 @@ DateTime::DateTime(const std::chrono::system_clock::time_point &now) * * If secondOffset is for example 86400, then one day is added. */ -DateTime::DateTime(const DateTime &other, int secondOffset) - : secondsSinceEpoch(other.secondsSinceEpoch + secondOffset) +DateTime::DateTime(const DateTime &other, int offset, OffsetType ot) + : secondsSinceEpoch(other.secondsSinceEpoch) , microsecs(other.microsecs) { + if (ot == OffsetType::SECONDS) + secondsSinceEpoch += offset; + else if (ot == OffsetType::DAYS) + secondsSinceEpoch += offset * (24 * 60 * 60); + makeTime(); + + if (ot == OffsetType::YEARS) { + init(time.tm_year + offset, time.tm_mon, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec); + } + else if (ot == OffsetType::MONTH) { + int yo = offset / 12; + int mo = offset % 12; + if (time.tm_mon + offset >= 13) { + ++yo; + mo -= 12; + } + else if (time.tm_mon + offset < 0) { + --yo; + mo += 12; + } + init(time.tm_year + yo, time.tm_mon + mo, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec); + } } /** @@ -237,6 +271,28 @@ void DateTime::makeTime() time.tm_mon += 1; // 1 .. 12 } + +/** + * Returns a month name with 3 chars + */ +const char* DateTime::monthName3() const +{ + Month mon = month(); + if (mon >= JAN && mon <= DEC) + return MONTH_DE[mon - 1]; + throw Exception("Invalid month value: %d", static_cast(mon)); +} + +/** + * Returns a month name with 3 chars + */ +const char* DateTime::monthName3(Month mon) +{ + if (mon >= JAN && mon <= DEC) + return MONTH_DE[mon - 1]; + throw Exception("Invalid month value: %d", static_cast(mon)); +} + /** * Is this DateTime before another DateTime (microseconds included!). */ diff --git a/src/utils/DateTime.h b/src/utils/DateTime.h index 117dd2a..4410e6e 100644 --- a/src/utils/DateTime.h +++ b/src/utils/DateTime.h @@ -49,6 +49,13 @@ class DateTime { void init(unsigned year, unsigned month, unsigned dayOfMonth, unsigned hour = 0, unsigned minute = 0, unsigned second = 0); public: + /** + * Type of offset for the constructor which supports and offset + */ + enum class OffsetType { + SECONDS, DAYS, MONTH, YEARS + }; + /** * Determine current date time with microseconds. */ @@ -78,7 +85,7 @@ class DateTime { * * If secondOffset is for example 86400, then one day is added. */ - explicit DateTime(const DateTime &other, int secondOffset); + explicit DateTime(const DateTime &other, int offset, OffsetType ot = OffsetType::SECONDS); /** * Init with single fields:
@@ -138,6 +145,16 @@ class DateTime { return static_cast(time.tm_mon); } + /** + * Returns a month name with 3 chars + */ + const char* monthName3() const; + + /** + * Returns a month name with 3 chars + */ + static const char* monthName3(Month m); + /** * Returns the day in month 1 .. 31 */ diff --git a/src/utils/Exceptions.cpp b/src/utils/Exceptions.cpp index db30edc..fd49bad 100644 --- a/src/utils/Exceptions.cpp +++ b/src/utils/Exceptions.cpp @@ -17,6 +17,7 @@ * along with this program. If not, see . */ +#include #include #include @@ -25,6 +26,9 @@ namespace utils { +// max message length for exceptions +#define __MSG_LEN 1024 + //-------------------------------------------------------------------------- // utils::Exception //-------------------------------------------------------------------------- @@ -32,36 +36,35 @@ Exception::Exception(const std::string &m) : msg(m) { } -Exception::Exception(const char *m) - : msg(m) -{ } +Exception::Exception(const char *m, ...) +{ + va_list args; + va_start(args, m); + setMessage(m, args); + va_end(args); +} -Exception::Exception(const char *m, int arg) - : msg() +Exception::~Exception() { } + + +/** + * set message via printf style format + */ +void Exception::setMessage(const char *m, va_list args) { - char buffer[1024]; + char buffer[__MSG_LEN]; - snprintf(buffer, sizeof(buffer), m, arg); + ::vsnprintf(buffer, sizeof(buffer), m, args); buffer[sizeof(buffer) - 1] = 0; msg = buffer; } -Exception::~Exception() { } - const char *Exception::what() const noexcept { return msg.c_str(); } -//-------------------------------------------------------------------------- -// utils::ConnectionLostException -//-------------------------------------------------------------------------- -ConnectionLostException::ConnectionLostException(const std::string &m) -: Exception(m) -{ } - - //-------------------------------------------------------------------------- // utils::FileOpenError //-------------------------------------------------------------------------- @@ -94,9 +97,9 @@ KeyNotFound::KeyNotFound(const std::string &messagePrefix, const std::string &ke OutOfRange::OutOfRange(const std::string &hint, unsigned current, unsigned _max) : Exception() { - char buffer[512]; - snprintf(buffer, sizeof(buffer), "%s: %u is out of range, max = %u", - hint.length() > 0 && hint.length() < 150 ? "" : "Value", current, _max); + char buffer[__MSG_LEN]; + ::snprintf(buffer, sizeof(buffer), "%s: %u is out of range, max = %u", + hint.length() > 0 && hint.length() < 150 ? "" : "Value", current, _max); buffer[sizeof(buffer) - 1] = 0; setMessage(hint + buffer); } @@ -104,9 +107,9 @@ OutOfRange::OutOfRange(const std::string &hint, unsigned current, unsigned _max) OutOfRange::OutOfRange(const std::string &hint, unsigned current, unsigned _min, unsigned _max) : Exception() { - char buffer[512]; - snprintf(buffer, sizeof(buffer), "%s: %u is out of range, expected range is %u .. %u", - hint.length() > 0 && hint.length() < 150 ? "" : "Value", current, _min, _max); + char buffer[__MSG_LEN]; + ::snprintf(buffer, sizeof(buffer), "%s: %u is out of range, expected range is %u .. %u", + hint.length() > 0 && hint.length() < 150 ? "" : "Value", current, _min, _max); buffer[sizeof(buffer) - 1] = 0; setMessage(hint + buffer); } @@ -133,10 +136,13 @@ RuntimeError::RuntimeError(const char *msg, int _errno) //-------------------------------------------------------------------------- // utils::ValueFormatError //-------------------------------------------------------------------------- -ValueFormatError::ValueFormatError(const std::string &m) - : Exception(m) -{ } - +ValueFormatError::ValueFormatError(const char *m, ...) +{ + va_list args; + va_start(args, m); + setMessage(m, args); + va_end(args); +} //-------------------------------------------------------------------------- // utils::IOException diff --git a/src/utils/Exceptions.h b/src/utils/Exceptions.h index f5a32c1..f2c4153 100644 --- a/src/utils/Exceptions.h +++ b/src/utils/Exceptions.h @@ -1,7 +1,7 @@ /* * Exceptions.h * - * Copyright (C) 2013 Holger Grosenick + * Copyright (C) 2013-2022 Holger Grosenick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,11 +22,18 @@ #include #include +#include #include "Compatibility.h" namespace utils { +#ifdef __GNUC__ +#define __PRINTF_CHECK __attribute__ ((__format__ (__printf__, 2, 3))) +#else +#define __PRINTF_CHECK +#endif + /** * Base exception class which holds a message text: this is returned @@ -42,6 +49,9 @@ class Exception : public std::exception protected: Exception() { } + // set message via printf style format in derived classes + void setMessage(const char *msg, va_list args); + void setMessage(const std::string &m) { msg = m; } @@ -52,8 +62,11 @@ class Exception : public std::exception public: explicit Exception(const std::string &msg); - explicit Exception(const char *msg); - explicit Exception(const char *msg, int arg); + + /** + * Use a printf style format to build the error message + */ + explicit Exception(const char *msg, ...) __PRINTF_CHECK; // std::~exception is already virtual ... virtual ~Exception(); @@ -68,7 +81,7 @@ class Exception : public std::exception class ConnectionLostException : public Exception { public: - explicit ConnectionLostException(const std::string &msg); + explicit ConnectionLostException(const std::string &msg) : Exception(msg) { } }; @@ -81,6 +94,7 @@ class ConnectionLostException : public Exception class IOException : public Exception { public: + // message: func + '(' + args + "): " + errno IOException(const std::string &func, const std::string &args); IOException(int _errno, const std::string &func, const std::string &args); }; @@ -113,9 +127,9 @@ class FileOpenError : public IOException class ValueFormatError : public Exception { public: - explicit ValueFormatError(const std::string &msg); - explicit ValueFormatError(const char *msg, int arg) - : Exception(msg, arg) { } + explicit ValueFormatError(const std::string &msg) : Exception(msg) { } + + explicit ValueFormatError(const char *msg, ...) __PRINTF_CHECK; }; /** @@ -125,8 +139,8 @@ class ValueFormatError : public Exception class RuntimeError : public Exception { public: - explicit RuntimeError(const char *msg); explicit RuntimeError(const std::string &msg); + explicit RuntimeError(const char *msg); explicit RuntimeError(const char *msg, int _errno); }; diff --git a/src/utils/File.cpp b/src/utils/File.cpp index d73f17f..ea19b7e 100644 --- a/src/utils/File.cpp +++ b/src/utils/File.cpp @@ -331,9 +331,16 @@ std::string File::getAbsolutePath() const if (!exists()) throw std::logic_error("Cannot determine the absolute path of a non-existing file"); - std::filesystem::path p; - p = fullname; - return std::filesystem::absolute(p).string(); + char actualpath [PATH_MAX+1]; +#ifdef _WIN32 + char* ptr = _fullpath(actualpath, fullname.c_str(), PATH_MAX); +#else + char *ptr = realpath(fullname.c_str(), actualpath); +#endif + if (!ptr) { + throw std::logic_error("realpath() returned an error"); + } + return actualpath; } /** diff --git a/src/utils/File.h b/src/utils/File.h index 425b698..b87368c 100644 --- a/src/utils/File.h +++ b/src/utils/File.h @@ -330,7 +330,7 @@ class File : public Object *

* If the file is not found, an exception is thrown. * - * @param envName - name of the environment variable + * @param envName - name of the environment variable which holds the search path (e.g. "PATH") * @param filename - name of the file to look for * @throws utils::KeyNotFound if the environment variable does not exist or the specified * file was not found along the path. diff --git a/src/utils/FileVisitor.cpp b/src/utils/FileVisitor.cpp index 39f841b..bad7c29 100644 --- a/src/utils/FileVisitor.cpp +++ b/src/utils/FileVisitor.cpp @@ -1,7 +1,7 @@ /* * FileVisitor.cpp * - * Copyright (C) 2014 Holger Grosenick + * Copyright (C) 2014-2022 Holger Grosenick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ */ #include +#include "Compatibility.h" #include "File.h" #include "FileFilter.h" #include "FileVisitor.h" @@ -62,4 +63,56 @@ FileVisitor::VisitResult FileCollector::afterVisitDirectory(const File &) return VisitResult::CONTINUE; } +// --------------------------------------------------------------------------------------------------- + +/** + * Search the file without wildcards, filename is handled case sensitive by default. + */ +FileFinder::FileFinder(const std::string &toFind, bool caseSensitive) + : toFind(toFind) + , caseSensitive(caseSensitive) + , visitCount(0) +{ +} + +/** + * Search the file without wildcards, filename is handled case sensitive by default. + */ +FileFinder::FileFinder(const char *toFind, bool caseSensitive) + : toFind(toFind) + , caseSensitive(caseSensitive) + , visitCount(0) +{ +} + +FileFinder::VisitResult FileFinder::visitFile(const File &f) +{ + ++visitCount; + if (caseSensitive) { + if (f.getName() == toFind) { + result = f.getAbsolutePath(); + return FileFinder::VisitResult::STOP; + } + } + else { + if (strcasecmp(f.getName().c_str(), toFind.c_str()) == 0) { + result = f.getAbsolutePath(); + return FileFinder::VisitResult::STOP; + } + } + return VisitResult::CONTINUE; +} + +FileFinder::VisitResult FileFinder::visitDirectory(const File &dir) +{ + ++visitCount; + return VisitResult::CONTINUE; +} + +FileFinder::VisitResult FileFinder::afterVisitDirectory(const File &dir) +{ + return VisitResult::CONTINUE; +} + + } /* namespace utils */ diff --git a/src/utils/FileVisitor.h b/src/utils/FileVisitor.h index 97c2f04..c15b830 100644 --- a/src/utils/FileVisitor.h +++ b/src/utils/FileVisitor.h @@ -137,6 +137,56 @@ class FileCollector : public FileVisitor virtual VisitResult afterVisitDirectory(const File &dir) override; }; + +/** + * Recurses into all directories and searches the given file. + */ +class FileFinder : public FileVisitor +{ + const std::string toFind; + const bool caseSensitive; + unsigned visitCount; + std::string result; + +public: + /** + * Search the file without wildcards, filename is handled case sensitive by default. + */ + explicit FileFinder(const std::string &toFind, bool caseSensitive = true); + + /** + * Search the file without wildcards, filename is handled case sensitive by default. + */ + explicit FileFinder(const char *toFind, bool caseSensitive = true); + + /** + * Return the full path to the file or an empty string if not found. + */ + const std::string& getResult() const { + return result; + } + + /** + * If the result is not empty, then we found the file. + */ + bool found() const { + return ! result.empty(); + } + + /** + * Returns the number of files and directories checked (more for test purpose). + */ + unsigned getVisitCount() const { + return visitCount; + } + + virtual VisitResult visitFile(const File &f) override; + + virtual VisitResult visitDirectory(const File &dir) override; + + virtual VisitResult afterVisitDirectory(const File &dir) override; +}; + } /* namespace utils */ #endif /* FILEVISITOR_H_ */ diff --git a/src/utils/Log.h b/src/utils/Log.h index 28f9869..baf1f53 100644 --- a/src/utils/Log.h +++ b/src/utils/Log.h @@ -138,8 +138,11 @@ class Log { static void error(const std::string& msg, const char *arg); static void error(const std::string& msg, int arg); - // exception trace - static void error(const char* msg, const std::exception &ex); + /** + * Exception trace: + * Exception of type 't' in: 'func': ex.what() + */ + static void error(const char* func, const std::exception &ex); static void error(const std::string& func, const std::exception &ex); /** diff --git a/src/utils/Properties.cpp b/src/utils/Properties.cpp index 0797ea2..8e535c6 100644 --- a/src/utils/Properties.cpp +++ b/src/utils/Properties.cpp @@ -1,7 +1,7 @@ /* * Properties.cpp * - * Copyright (C) 2013-2021 Holger Grosenick + * Copyright (C) 2013-2022 Holger Grosenick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -97,7 +97,7 @@ Properties::Properties(const std::string &data, char separator) if (c == '\\') { c = data[++i]; if (c == 'x' && i+2 < last) { - c = hexValue(data[i+1], data[i+2]); + c = hex2int(data[i+1], data[i+2]); i+=2; } os << c; @@ -341,10 +341,7 @@ bool Properties::getBool(const std::string &key) const return true; if (strcmp(v, "false") != 0) { - char buffer[256]; - snprintf(buffer, sizeof(buffer), "Invalid bool value: '%s', key was '%s'", v, key.c_str()); - buffer[sizeof(buffer) - 1] = 0; - throw ValueFormatError(buffer); + throw ValueFormatError("Invalid bool value: '%s', key was '%s'", v, key.c_str()); } return false; } @@ -422,14 +419,16 @@ double Properties::getDouble(const std::string &key, double defaultValue) const */ void Properties::put(const char *key, const char *value) { - std::pair res = values.insert(std::make_pair(key, value)); + // std::pair + auto res = values.insert(std::make_pair(key, value)); if (false == res.second) (res.first)->second = value; } void Properties::put(const std::string &key, const std::string &value) { - std::pair res = values.insert(std::make_pair(key, value)); + // std::pair + auto res = values.insert(std::make_pair(key, value)); if (false == res.second) (res.first)->second = value; } diff --git a/src/utils/SignalHandlers.cpp b/src/utils/SignalHandlers.cpp index 21377c4..de60940 100644 --- a/src/utils/SignalHandlers.cpp +++ b/src/utils/SignalHandlers.cpp @@ -30,24 +30,28 @@ namespace utils { // global flag to indicate that the application should finish #define MAX_SINGALS 64 static bool calledSignal[MAX_SINGALS]; - +static int signalFromPid; /** * Signal handler to terminate the application if 'shouldFinish' is checked regularly. */ -static void sigTermHandler(int sig, siginfo_t *, void *) +#ifndef _WIN32 +static void sigTermHandler(int sig, siginfo_t *info, void *) { static unsigned count = 0; if (++count == 5) { - Log() << "sigTermHandler(" << sig << ") called 5 times => hard exit ..."; + Log() << "sigTermHandler(" << sig << ") triggered 5 times => hard exit ..."; exit(16); } + signalFromPid = info ? info->si_pid : 0; if (count == 1) - Log() << "sigTermHandler(" << sig << ") called => finish ..."; + Log() << "sigTermHandler(" << sig << ") triggered from pid " + << signalFromPid << " => should finish ..."; else - Log() << "sigTermHandler(" << sig << ") called (" << count << " times) => finish ..."; + Log() << "sigTermHandler(" << sig << ") triggered (" << count << " times) from pid " + << signalFromPid << " => should finish ..."; calledSignal[SIGTERM] = true; } @@ -56,9 +60,11 @@ static void sigTermHandler(int sig, siginfo_t *, void *) /** * Signal handler for all other signals that should just set a flag. */ -static void anySignalHandler(int sig, siginfo_t *, void *) +static void anySignalHandler(int sig, siginfo_t *info, void *) { - // Log() << "anySignalHandler(" << sig << ") called"; + Log() << "anySignalHandler(" << sig << ") triggered from pid " << signalFromPid; + + signalFromPid = info ? info->si_pid : 0; if (sig > 0 && sig < MAX_SINGALS) calledSignal[sig] = true; } @@ -68,9 +74,10 @@ static void anySignalHandler(int sig, siginfo_t *, void *) */ static void sigKillHandler(int sig, siginfo_t *, void *) { - Log() << "CAUGHT CRITICAL SIGNAL: signal=" << sig << '(' << SignalHandlers::signal2char(sig) << ") => abort!"; + Log() << "CAUGHT CRITICAL SIGNAL " << sig << " (" << SignalHandlers::signal2char(sig) << ") => abort!"; exit(16); } +#endif /** * Activate some defaults for signal handlers: @@ -171,6 +178,14 @@ bool SignalHandlers::gotSignal(Signal sigNum) return calledSignal[osSignal]; } +/** + * Returns the process id (PID) which sent the latest signal to this process. + */ +int SignalHandlers::pidOfLatestSignal() +{ + return signalFromPid; +} + // set signal handler for example for SIGUSR1 or similar void SignalHandlers::setHandler(Signal sigNum, void (*function)(int, void *sigInfo, void *u_context)) @@ -194,31 +209,36 @@ void SignalHandlers::setHandler(Signal sigNum, void (*function)(int, void *sigIn */ const char *SignalHandlers::signal2char(int signum) { - if (signum == SIGBUS) - return "SIGBUS"; - if (signum == SIGSEGV) - return "SIGSEGV"; - if (signum == SIGPIPE) - return "SIGPIPE"; + static char sigStr[64]; + if (signum == SIGILL) return "SIGILL"; if (signum == SIGFPE) return "SIGFPE"; + if (signum == SIGSEGV) + return "SIGSEGV"; + if (signum == SIGINT) + return "SIGINT"; + if (signum == SIGTERM) + return "SIGTERM"; +#ifndef _WIN32 + if (signum == SIGBUS) + return "SIGBUS"; + if (signum == SIGPIPE) + return "SIGPIPE"; if (signum == SIGKILL) return "SIGKILL"; if (signum == SIGCHLD) return "SIGCHLD"; if (signum == SIGUSR1) return "SIGUSR1"; -#ifdef SIGUSR2 if (signum == SIGUSR2) return "SIGUSR2"; #endif - if (signum == SIGINT) - return "SIGINT"; - if (signum == SIGTERM) - return "SIGTERM"; - return "unknown SIG"; + + snprintf(sigStr, sizeof(sigStr), "SIG(%d)", signum); + sigStr[sizeof(sigStr) - 1] = 0; + return sigStr; } /** @@ -228,16 +248,18 @@ const char *SignalHandlers::signal2char(int signum) int SignalHandlers::signal2osSignal(Signal s) { switch (s) { - case SIG_HANGUP: - return SIGHUP; case SIG_INT: return SIGINT; + case SIG_TERM: + return SIGTERM; +#ifndef _WIN32 + case SIG_HANGUP: + return SIGHUP; case SIG_USER1: return SIGUSR1; case SIG_USER2: return SIGUSR2; - case SIG_TERM: - return SIGTERM; +#endif } Log::log("SignalHandlers::signal2osSignal: not supported signal", s); return -1; diff --git a/src/utils/SignalHandlers.h b/src/utils/SignalHandlers.h index 8dee805..a6dc073 100644 --- a/src/utils/SignalHandlers.h +++ b/src/utils/SignalHandlers.h @@ -100,6 +100,11 @@ class SignalHandlers { */ static int signal2osSignal(Signal s); + /** + * Returns the process id (PID) which sent the latest signal to this process. + */ + static int pidOfLatestSignal(); + /** * Returns true if Ctrl-C was pressed / SIGINT was triggered: soft exit. * This method is equal to "hasSignal(SIG_TERM) || hasSignal(SIG_INT)". diff --git a/src/utils/StringUtil.cpp b/src/utils/StringUtil.cpp index 1e6ae4b..0ccbb72 100644 --- a/src/utils/StringUtil.cpp +++ b/src/utils/StringUtil.cpp @@ -274,7 +274,7 @@ void toString(char buf[512], double d, unsigned digits, char sep) char fmt[32]; if (!buf) - throw RuntimeError("buffer may not be NULL"); + throw std::invalid_argument("buffer may not be NULL"); if (digits > 20) throw OutOfRange("Number of digits may not be larger than 20", digits, 20); diff --git a/src/utils/ValueProvider.cpp b/src/utils/ValueProvider.cpp index c55336f..c410abc 100644 --- a/src/utils/ValueProvider.cpp +++ b/src/utils/ValueProvider.cpp @@ -408,9 +408,7 @@ size_t StoredValueProvider::checkSequence() if (it.timeOffset < act) { - char error[128]; - snprintf(error, sizeof(error), "Relative time in row %u is lower than previous one!", row); - throw Exception(error); + throw Exception("Relative time in row %u is lower than previous one!", row); } act = it.timeOffset; } @@ -541,9 +539,7 @@ void StoredValueProvider::parseLine(std::string &line, unsigned lineNo) value = strtol(val.c_str(), &endPtr, base); if (endPtr == val.c_str()) { - char error[128]; - snprintf(error, sizeof(error), "Invalid line format in value of line %u", lineNo); - throw Exception(error); + throw Exception("Invalid line format in value of line %u", lineNo); } // add more offset to all following values? diff --git a/src/utils/ValueProviderCSV.cpp b/src/utils/ValueProviderCSV.cpp index c55bb46..26e9c76 100644 --- a/src/utils/ValueProviderCSV.cpp +++ b/src/utils/ValueProviderCSV.cpp @@ -86,7 +86,6 @@ CSVValueProvider::CSVValueProvider(const std::string &filename) unsigned numCols = csv.getNumCols(); const std::vector< const char* > &cols = csv.getColumns(); std::vector columnData; - char msg[512]; uint64_t offset = 0; //printf("NumCols = %u in %s\n", numCols, filename); @@ -94,9 +93,7 @@ CSVValueProvider::CSVValueProvider(const std::string &filename) while (csv.loadLine()) { unsigned size = static_cast(cols.size()); if (size != numCols) { - snprintf(msg, sizeof(msg), "Invalid number of columns (%u, expected is %u) in line %u of %s", size, numCols, csv.getLine(), filename.c_str()); - msg[sizeof(msg) - 1] = 0; - throw utils::ValueFormatError(msg); + throw utils::ValueFormatError("Invalid number of columns (%u, expected is %u) in line %u of %s", size, numCols, csv.getLine(), filename.c_str()); } if (values.empty() && strchr(cols[0], ':') != NULL) { @@ -116,13 +113,11 @@ CSVValueProvider::CSVValueProvider(const std::string &filename) uint64_t v = parseTimestamp(cols[0]); if (v > 0) { v -= offset; - columnData.push_back(v); + columnData.push_back(static_cast(v)); // printf("Store offset %ld in line %d\n", v, csv.getLine()); } else { - snprintf(msg, sizeof(msg), "Invalid timestamp conversion in line %u of %s", csv.getLine(), filename.c_str()); - msg[sizeof(msg) - 1] = 0; - throw utils::ValueFormatError(msg); + throw utils::ValueFormatError("Invalid timestamp conversion in line %u of %s", csv.getLine(), filename.c_str()); } } else { @@ -151,10 +146,7 @@ size_t CSVValueProvider::checkSequence() ++row; if (it[0] < act) { - char msg[200]; - snprintf(msg, sizeof(msg), "Relative time in data row %u is lower than previous one (%d < %d)!", row, it[0], act); - msg[sizeof(msg) - 1] = 0; - throw Exception(msg); + throw Exception("Relative time in data row %u is lower than previous one (%d < %d)!", row, it[0], act); } act = it[0]; } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index f05c998..1d23e05 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -22,6 +22,8 @@ #include #else #include +#include +#include #include #include #include @@ -30,9 +32,14 @@ #include #include #include +#include +#include +#include #include +#include #include "Log.h" +#include "StringUtil.h" #include "utils.h" @@ -41,6 +48,7 @@ static const char BASE58_ALPHABET[] = \ "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; +namespace fs = std::filesystem; namespace utils { @@ -151,8 +159,105 @@ unsigned int base58Decode(const char *str) } +/** + * Create a shell file which contains the current environment and the commandline + * that was called. + * + * argPos - the position of the command line option that was used to trigger + * dumping the command line, this argument and the next one are skipped + * in the dump (use -1 to skip nothing) + * argc, argv, env - as passed to main() + * + * returns true if the dump was OK, false in case of file errors. + */ +bool dumpAsShell(int argPos, int argc, char const* const* argv, char const* const* env) +{ + const char *file = argv[argPos + 1]; + if (!file || !(*file)) + return false; + + // wrong arguments + if (!argv) + return false; + + // start writing the file + std::ofstream os(file, std::ofstream::out); + if (!os.is_open()) { + Log::perror(file); + return false; + } + + // make the file readable + exec for the owner only, no other user + fs::permissions(file, fs::perms::owner_all, fs::perm_options::replace); + + if (env) { + // these variables should not be dumped, they are re-created in normal ssh shell + std::list excludes; + excludes.push_back("_"); + excludes.push_back("HOME"); // user specific + excludes.push_back("HOST"); // current host + excludes.push_back("LESS"); // only for 'less' + excludes.push_back("LS_"); // only for 'less' + excludes.push_back("LOG_NAME"); // user specific + excludes.push_back("SHLVL"); // shell specific + excludes.push_back("SSH_"); // ssh, never overwrite ! + excludes.push_back("USER"); // user specific + excludes.push_back("XTERM"); // user specific + + // sort the env list and remove vars which are mentioned in the exclude list + const char *shell = nullptr; + std::list envList; + + for (int i = 0; env[i]; ++i) { + bool skip = false; + std::string e = env[i]; + + // find the shell name + if (strings::startsWith(e, "SHELL=")) { + shell = strchr(env[i], '='); + if (shell) + ++shell; + } + + // check for excludes + for (auto &s : excludes) { + if (strings::startsWith(e, s)) { + skip = true; + break; + } + } + if (!skip) + envList.push_back(env[i]); + } + + envList.sort(); + if (shell) { + os << "#!" << shell << std::endl; + + } + os << std::endl; + for (auto e : envList) { + os << "export " << e << std::endl; + } + } + + os << std::endl; + for (int i = 0; i < argc && argv[i]; ++i) { + if (i == argPos) { + // skip the arg to dump the environment and call, so that if the shell + // is started again, this should not dump again. + ++i; + } + else { + os << argv[i] << ' '; + } + } + os << std::endl << std::endl; + return true; +} + // returns the current process id like "getpid()" on Linux -uint64_t getProcessId() +int getProcessId() { return getpid(); } @@ -163,7 +268,7 @@ uint64_t getProcessId() * * If a non hex char was input: an exception is thrown ! */ -int hexValue(char c1) +int hex2int(char c1) { if (c1 >= '0' && c1 <= '9') return c1 - '0'; @@ -180,8 +285,94 @@ int hexValue(char c1) /** * Return the hexValue of two hex chars: hexValue(c1)*16 + hexValue(c2) */ -int hexValue(char c1, char c2) { - return hexValue(c1)*16 + hexValue(c2); +int hex2int(char c1, char c2) { + return hex2int(c1)*16 + hex2int(c2); +} + +/** + * Return the hexValue of a string with hex sequence chars + */ +int64_t hex2int(const char *in) +{ + if (!in) + return 0; + + int64_t result = 0; + while (*in) { + result *= 16; + result += hex2int(*in); + ++in; + } + return result; +} + +/** + * Convert a numeric value with base 10 into a string. + */ +template char* convertToAscii(char *dest, int size, T value) +{ + if (value == 0) { + dest[0] = '0'; + dest[1] = 0; + return dest; + } + + int i = 0; + bool sign = false; + char buffer[128]; + + // only check if the type is signed + if (std::is_signed::value) { + sign = value < 0; + } + + // now convert + while (value) { + int mod = (value % 10); + buffer[i] = '0' + (mod < 0 ? -mod : mod); + ++i; + value /= 10; + + if (i >= size) { + char msg[256]; + sprintf(msg, "convertToAscii: value exceeds '%u' chars", size); + throw std::overflow_error(msg); + } + } + + // copy back in reverse (correct order). + int d = 0; + if (sign) { + // value was negative: start with minus + dest[d++] = '-'; + } + + // copy back in correct order + while (i > 0) { + dest[d++] = buffer[--i]; + } + dest[d] = 0; + + return dest; +} + +/** + * Integer to ascii + */ +char* itoa(char dest[16], int32_t value) { + return convertToAscii(dest, 16, value); +} + +char* uitoa(char dest[16], uint32_t value) { + return convertToAscii(dest, 16, value); +} + +char* ltoa(char dest[32], int64_t value) { + return convertToAscii(dest, 32, value); +} + +char* ultoa(char dest[32], uint64_t value) { + return convertToAscii(dest, 32, value); } // sleep some milliseconds @@ -240,4 +431,28 @@ bool redirectStdout(const char *logName) return result; } +/** + * Similar to the standard snprintf, but with one difference: + * if the buffer is too small, an exception is thrown. + */ +int snprintf(char *str, size_t size, const char *format, ...) +{ + str[size-1] = 0; + + va_list args; + va_start(args, format); + size_t res = ::vsnprintf(str, size, format, args); + va_end(args); + + if (str[size-1] != 0 || res >= size) { + char value1[32]; + char value2[32]; + ltoa(value1, size); + ltoa(value2, res); + throw std::length_error(std::string("utils::snprintf overflow, size=") + value1 + " result=" + value2); + } + + return res; +} + } diff --git a/src/utils/utils.h b/src/utils/utils.h index 570fcd9..eeae0a5 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -1,7 +1,7 @@ /* * utils.h * - * Copyright (C) 2013 Holger Grosenick + * Copyright (C) 2013-2022 Holger Grosenick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +31,19 @@ void msleep(int ms); void usleep(int us); // returns the current process id, like "getpid()" on Linux -uint64_t getProcessId(); +int getProcessId(); + +// convert long to char with 10 a base, use a 32 byte buffer which will be null terminated at the end +char* ltoa(char dest[32], int64_t value); +char* ultoa(char dest[32], uint64_t value); +char* itoa(char dest[16], int32_t value); +char* uitoa(char dest[16], uint32_t value); + +/** + * Similar to the standard snprintf, but with one difference: + * if the buffer is too small, an exception is thrown. + */ +int snprintf(char *str, size_t size, const char *format, ...); /** * Convert an int into a base58 encoded string. @@ -39,6 +51,19 @@ uint64_t getProcessId(); const std::string base58Encode(unsigned int value); unsigned int base58Decode(const char *value); +/** + * Create a shell file which contains the current environment and the commandline + * that was called. + * + * argPos - the position of the command line option that was used to trigger + * dumping the command line, this argument and the next one are skipped + * in the dump (use -1 to skip nothing) + * argc, argv, env - as passed to main() + * + * returns true if the dump was OK, false in case of file errors. + */ +bool dumpAsShell(int argPos, int argc, char const* const* argv, char const* const* env); + /** * Open a log-file: redirect stdout + stderr into a file. * Do nothing if the log-name is NULL or empty. @@ -64,12 +89,23 @@ void bits2bool(unsigned statesIn, bool statesOut[], unsigned num); * * If a non hex char was input: an exception is thrown ! */ -int hexValue(char c1); +int hex2int(char c1); /** * Return the hexValue of two hex chars: hexValue(c1)*16 + hexValue(c2) */ -int hexValue(char c1, char c2); +int hex2int(char c1, char c2); + +/** + * Return the hexValue of a string with hex sequence chars. + * + * Be aware: + * the result is int64 => ffffffff is a positive value. + */ +int64_t hex2int(const char *in); +inline int64_t hex2int(const std::string &in) { + return hex2int(in.c_str()); +} }