From 5a6ed4e8eadf3af3d141b246f124d67b62a1357c Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Tue, 21 Feb 2023 12:13:36 -0500 Subject: add upstream 12.1 --- zen/argon2.cpp | 6 ++-- zen/basic_math.h | 20 +++++++------- zen/file_access.cpp | 52 +++++++++++++++++------------------ zen/file_io.cpp | 8 +++--- zen/file_io.h | 2 +- zen/file_path.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++++++++--- zen/file_path.h | 4 ++- zen/globals.h | 25 +++++++++++++++-- zen/http.cpp | 2 +- zen/resolve_path.cpp | 43 ++++------------------------- zen/serialize.h | 6 ++-- zen/socket.h | 4 +-- zen/stream_buffer.h | 4 +-- zen/string_base.h | 15 +++++++++- zen/string_tools.h | 4 +-- zen/sys_error.cpp | 10 +++---- zen/sys_info.cpp | 34 +++++++++++------------ zen/thread.h | 2 +- zen/time.h | 35 +++++++++++++++++++++-- zen/utf.h | 2 +- zen/zlib_wrap.cpp | 4 +-- 21 files changed, 231 insertions(+), 129 deletions(-) (limited to 'zen') diff --git a/zen/argon2.cpp b/zen/argon2.cpp index d78dc26c..f48abe5e 100644 --- a/zen/argon2.cpp +++ b/zen/argon2.cpp @@ -4,9 +4,9 @@ // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** -/* The code in this file, except for zen::zargon2() is from: +/* The code in this file, except for zen::zargon2(), is from PuTTY: - PuTTY is copyright 1997-2022 Simon Tatham. + PuTTY is copyright 1997-2022 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, @@ -972,7 +972,7 @@ std::string zen::zargon2(zen::Argon2Flavor flavour, uint32_t mem, uint32_t passe std::string output(taglen, '\0'); argon2_internal(parallel, taglen, mem, passes, static_cast(flavour), {.ptr = password.data(), .len = password.size()}, - {.ptr = salt.data(), .len = salt.size()}, + {.ptr = salt .data(), .len = salt .size()}, {.ptr = "", .len = 0}, {.ptr = "", .len = 0}, reinterpret_cast(output.data())); return output; diff --git a/zen/basic_math.h b/zen/basic_math.h index 7258128f..77ce7b7e 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -29,8 +29,8 @@ template auto intDivFloor(N numerator, D denominator); template T power(T value); -double radToDeg(double rad); //convert unit [rad] into [°] -double degToRad(double degree); //convert unit [°] into [rad] +double radToDeg(double rad); //convert unit [rad] into [°] +double degToRad(double degree); //convert unit [°] into [rad] template double arithmeticMean(InputIterator first, InputIterator last); @@ -84,13 +84,13 @@ std::pair minMaxElement(InputIterator first, Input { //by factor 1.5 to 3 faster than boost::minmax_element (=two-step algorithm) for built-in types! - InputIterator lowest = first; - InputIterator largest = first; + InputIterator itMin = first; + InputIterator itMax = first; if (first != last) { - auto minVal = *lowest; //nice speedup on 64 bit! - auto maxVal = *largest; // + auto minVal = *itMin; //nice speedup on 64 bit! + auto maxVal = *itMax; // for (;;) { ++first; @@ -100,17 +100,17 @@ std::pair minMaxElement(InputIterator first, Input if (compLess(maxVal, val)) { - largest = first; + itMax = first; maxVal = val; } else if (compLess(val, minVal)) { - lowest = first; + itMin = first; minVal = val; } } } - return {lowest, largest}; + return {itMin, itMax}; } @@ -143,7 +143,7 @@ auto roundToGrid(T val, InputIterator first, InputIterator last) template inline bool isNull(T value) { - return abs(value) <= std::numeric_limits::epsilon(); //epsilon is 0 für integral types => less-equal + return abs(value) <= std::numeric_limits::epsilon(); //epsilon is 0 für integral types => less-equal } diff --git a/zen/file_access.cpp b/zen/file_access.cpp index d06202ba..ef6cdc80 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -157,11 +157,15 @@ int64_t zen::getFreeDiskSpace(const Zstring& folderPath) //throw FileError uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError { - struct stat fileInfo = {}; - if (::stat(filePath.c_str(), &fileInfo) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), "stat"); + try + { + struct stat fileInfo = {}; + if (::stat(filePath.c_str(), &fileInfo) != 0) + THROW_LAST_SYS_ERROR("stat"); - return fileInfo.st_size; + return fileInfo.st_size; + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), e.toString()); } } @@ -169,8 +173,8 @@ uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError Zstring zen::getTempFolderPath() //throw FileError { - if (const char* tempPath = ::getenv("TMPDIR")) //no extended error reporting - return tempPath; + if (const std::optional tempDirPath = getEnvironmentVar("TMPDIR")) + return *tempDirPath; //TMPDIR not set on CentOS 7, WTF! return P_tmpdir; //usually resolves to "/tmp" } @@ -268,20 +272,7 @@ namespace //wrapper for file system rename function: void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool replaceExisting) //throw FileError, ErrorMoveUnsupported, ErrorTargetExisting { - auto throwException = [&](int ec) - { - const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L'\n' + fmtPath(pathFrom)), L"%y", L'\n' + fmtPath(pathTo)); - const std::wstring errorDescr = formatSystemError("rename", ec); - - if (ec == EXDEV) - throw ErrorMoveUnsupported(errorMsg, errorDescr); - - assert(!replaceExisting || ec != EEXIST); - if (!replaceExisting && ec == EEXIST) - throw ErrorTargetExisting(errorMsg, errorDescr); - - throw FileError(errorMsg, errorDescr); - }; + auto getErrorMsg = [&] { return replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L'\n' + fmtPath(pathFrom)), L"%y", L'\n' + fmtPath(pathTo)); }; //rename() will never fail with EEXIST, but always (atomically) overwrite! //=> equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists or ::MoveFileEx() + MOVEFILE_REPLACE_EXISTING @@ -291,22 +282,31 @@ void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool r { struct stat sourceInfo = {}; if (::lstat(pathFrom.c_str(), &sourceInfo) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(pathFrom)), "stat"); + throw FileError(getErrorMsg(), formatSystemError("lstat(source)", errno)); struct stat targetInfo = {}; - if (::lstat(pathTo.c_str(), &targetInfo) == 0) + if (::lstat(pathTo.c_str(), &targetInfo) != 0) + { + if (errno != ENOENT) + throw FileError(getErrorMsg(), formatSystemError("lstat(target)", errno)); + } + else { if (sourceInfo.st_dev != targetInfo.st_dev || sourceInfo.st_ino != targetInfo.st_ino) - throwException(EEXIST); //that's what we're really here for + throw ErrorTargetExisting(getErrorMsg(), replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(pathTo)))); //else: continue with a rename in case //caveat: if we have a hardlink referenced by two different paths, the source one will be unlinked => fine, but not exactly a "rename"... } - //else: not existing or access error (hopefully ::rename will also fail!) } if (::rename(pathFrom.c_str(), pathTo.c_str()) != 0) - throwException(errno); + { + if (errno == EXDEV) + throw ErrorMoveUnsupported(getErrorMsg(), formatSystemError("rename", errno)); + + throw FileError(getErrorMsg(), formatSystemError("rename", errno)); + } } @@ -661,7 +661,7 @@ FileCopyResult zen::copyNewFile(const Zstring& sourceFile, const Zstring& target //close output file handle before setting file time; also good place to catch errors when closing stream! fileOut.close(); //throw FileError //========================================================================================================== - //take over fileOut ownership => from this point on, WE are responsible for calling removeFilePlain() on failure!! + //take over fileOut ownership => from this point on, WE are responsible for calling removeFilePlain() on error!! // not needed *currently*! see below: ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); } catch (FileError&) {}); //=========================================================================================================== std::optional errorModTime; diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 910b75e7..2e4ab60a 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -71,7 +71,7 @@ void FileBase::close() //throw FileError throw SysError(L"Contract error: close() called more than once."); if (::close(hFile_) != 0) THROW_LAST_SYS_ERROR("close"); - hFile_ = invalidFileHandle; //do NOT set on failure! => ~FileOutputPlain() still wants to (try to) delete the file! + hFile_ = invalidFileHandle; //do NOT set on error! => ~FileOutputPlain() still wants to (try to) delete the file! } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), e.toString()); } } @@ -153,7 +153,7 @@ FileInputPlain::FileInputPlain(FileHandle handle, const Zstring& filePath) : size_t FileInputPlain::tryRead(void* buffer, size_t bytesToRead) //throw FileError, ErrorFileLocked { if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); + throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); assert(bytesToRead % getBlockSize() == 0); try { @@ -263,7 +263,7 @@ void FileOutputPlain::reserveSpace(uint64_t expectedSize) //throw FileError size_t FileOutputPlain::tryWrite(const void* buffer, size_t bytesToWrite) //throw FileError { if (bytesToWrite == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); + throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); assert(bytesToWrite % getBlockSize() == 0 || bytesToWrite < getBlockSize()); try { @@ -306,7 +306,7 @@ std::string zen::getFileContent(const Zstring& filePath, const IoCallback& notif } -void zen::setFileContent(const Zstring& filePath, const std::string& byteStream, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X +void zen::setFileContent(const Zstring& filePath, const std::string_view byteStream, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X { const Zstring tmpFilePath = getPathWithTempName(filePath); diff --git a/zen/file_io.h b/zen/file_io.h index 46ffa843..838b5021 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -175,7 +175,7 @@ Zstring getPathWithTempName(const Zstring& filePath) //generate (hopefully) uniq [[nodiscard]] std::string getFileContent(const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X //overwrites if existing + transactional! :) -void setFileContent(const Zstring& filePath, const std::string& bytes, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X +void setFileContent(const Zstring& filePath, const std::string_view bytes, const IoCallback& notifyUnbufferedIO /*throw X*/); //throw FileError, X } #endif //FILE_IO_H_89578342758342572345 diff --git a/zen/file_path.cpp b/zen/file_path.cpp index d06ab6bd..7ef78569 100644 --- a/zen/file_path.cpp +++ b/zen/file_path.cpp @@ -36,13 +36,13 @@ std::optional zen::parsePathComponents(const Zstring& itemPath) pc = doParse(3 /*sepCountVolumeRoot*/, false /*rootWithSep*/); if (!pc && startsWith(itemPath, "/media/")) //Ubuntu: e.g. /media/zenju/DEVICE_NAME - if (const char* username = ::getenv("USER")) //no ownership transfer + no extended error reporting - if (startsWith(itemPath, std::string("/media/") + username + "/")) + if (const std::optional username = getEnvironmentVar("USER")) + if (startsWith(itemPath, std::string("/media/") + *username + "/")) pc = doParse(4 /*sepCountVolumeRoot*/, false /*rootWithSep*/); if (!pc && startsWith(itemPath, "/run/media/")) //CentOS, Suse: e.g. /run/media/zenju/DEVICE_NAME - if (const char* username = ::getenv("USER")) - if (startsWith(itemPath, std::string("/run/media/") + username + "/")) + if (const std::optional username = getEnvironmentVar("USER")) + if (startsWith(itemPath, std::string("/run/media/") + *username + "/")) pc = doParse(5 /*sepCountVolumeRoot*/, false /*rootWithSep*/); if (!pc && startsWith(itemPath, "/run/user/")) //Ubuntu, e.g.: /run/user/1000/gvfs/smb-share:server=192.168.62.145,share=folder @@ -154,3 +154,73 @@ std::weak_ordering zen::compareNativePath(const Zstring& lhs, const Zstring& rhs } +namespace +{ +std::unordered_map getAllEnvVars() +{ + assert(runningOnMainThread()); + + std::unordered_map envVars; + if (char** line = environ) + for (; *line; ++line) + { + const std::string_view l(*line); + envVars.emplace(beforeFirst(l, '=', IfNotFoundReturn::all), + afterFirst(l, '=', IfNotFoundReturn::none)); + } + return envVars; +} + +constinit Global> globalEnvVars; +GLOBAL_RUN_ONCE( + //*INDENT-OFF* + //mitigate static initialization order fiasco: (whatever comes first) + if (!globalEnvVars.get()) + globalEnvVars.set(std::make_unique>(getAllEnvVars())) + //*INDENT-ON* +); +} + + +std::optional zen::getEnvironmentVar(const ZstringView name) +{ + /* const char* buffer = ::getenv(name); => NO! *not* thread-safe: returns pointer to internal memory! + might change after setenv(), allegedly possible even after another getenv()! + + getenv_s() to the rescue!? not implemented on GCC, apparently *still* not threadsafe!!! + + => *eff* this: make a global copy during start up! */ + std::shared_ptr> envVars = globalEnvVars.get(); + if (!envVars) //access during static init or shutdown? + { + if (globalEnvVars.wasDestroyed()) + { + assert(false); + return {}; //SOL! + } + //mitigate static initialization order fiasco: (whatever comes first) + globalEnvVars.set(std::make_unique>(getAllEnvVars())); + envVars = globalEnvVars.get(); + } + + auto it = envVars->find(name); + if (it == envVars->end()) + return {}; + + Zstring value = it->second; + + //some postprocessing (good idea!? Is this even needed!? + warn_static("let's find out!") +#if 0 + trim(value); //remove leading, trailing blanks + + //remove leading, trailing double-quotes + if (startsWith(value, Zstr('"')) && + endsWith (value, Zstr('"')) && + value.length() >= 2) + value = Zstring(value.c_str() + 1, value.length() - 2); +#endif + return value; +} + + diff --git a/zen/file_path.h b/zen/file_path.h index d67a49d0..960ec52f 100644 --- a/zen/file_path.h +++ b/zen/file_path.h @@ -19,7 +19,7 @@ struct PathComponents Zstring rootPath; //itemPath = rootPath + (FILE_NAME_SEPARATOR?) + relPath Zstring relPath; // }; -std::optional parsePathComponents(const Zstring& itemPath); //no value on failure +std::optional parsePathComponents(const Zstring& itemPath); //no value on error std::optional getParentFolderPath(const Zstring& itemPath); inline Zstring getItemName(const Zstring& itemPath) { return afterLast(itemPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); } @@ -44,6 +44,8 @@ inline bool equalNativePath(const Zstring& lhs, const Zstring& rhs) { return com struct LessNativePath { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return compareNativePath(lhs, rhs) < 0; } }; //------------------------------------------------------------------------------------------ +std::optional getEnvironmentVar(const ZstringView name); + } diff --git a/zen/globals.h b/zen/globals.h index 9e22f56b..5d4a7041 100644 --- a/zen/globals.h +++ b/zen/globals.h @@ -9,6 +9,7 @@ #include #include +#include #include "scope_guard.h" @@ -16,7 +17,7 @@ namespace zen { /* Solve static destruction order fiasco by providing shared ownership and serialized access to global variables - => there may be accesses to "Global::get()" during process shutdown e.g. _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message! + => e.g. accesses to "Global::get()" during process shutdown: _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message! => use trivially-destructible POD only!!! ATTENTION: function-static globals have the compiler generate "magic statics" == compiler-genenerated locking code which will crash or leak memory when accessed after global is "dead" @@ -54,7 +55,13 @@ public: ~Global() { static_assert(std::is_trivially_destructible_v, "this memory needs to live forever"); - set(nullptr); + + pod_.spinLock.lock(); + std::shared_ptr* oldInst = std::exchange(pod_.inst, nullptr); + pod_.destroyed = true; + pod_.spinLock.unlock(); + + delete oldInst; } std::shared_ptr get() //=> return std::shared_ptr to let instance life time be handled by caller (MT usage!) @@ -76,17 +83,29 @@ public: pod_.spinLock.lock(); ZEN_ON_SCOPE_EXIT(pod_.spinLock.unlock()); - std::swap(pod_.inst, tmpInst); + if (!pod_.destroyed) + std::swap(pod_.inst, tmpInst); + else + assert(false); } delete tmpInst; } + bool wasDestroyed() + { + pod_.spinLock.lock(); + ZEN_ON_SCOPE_EXIT(pod_.spinLock.unlock()); + + return pod_.destroyed; + } + private: struct Pod { PodSpinMutex spinLock; //rely entirely on static zero-initialization! => avoid potential contention with worker thread during Global<> construction! //serialize access: can't use std::mutex: has non-trival destructor std::shared_ptr* inst = nullptr; + bool destroyed = false; } pod_; }; diff --git a/zen/http.cpp b/zen/http.cpp index 7eb3fb76..e1a828c1 100644 --- a/zen/http.cpp +++ b/zen/http.cpp @@ -499,7 +499,7 @@ bool zen::isValidEmail(const std::string_view& email) return false; //--------------------------------------------------------------------- - //not going to parse and validate this! + //we're not going to parse and validate this! const bool quoted = (startsWith(local, '"') && endsWith(local, '"')) || contains(local, '\\'); //e.g. "t\@st@email.com" if (!quoted) diff --git a/zen/resolve_path.cpp b/zen/resolve_path.cpp index 8b81e184..daaf91ff 100644 --- a/zen/resolve_path.cpp +++ b/zen/resolve_path.cpp @@ -10,48 +10,15 @@ #include "file_access.h" #include - // #include //getenv() - #include //getuid() - #include //getpwuid_r() + #include //getcwd() using namespace zen; namespace { -std::optional getEnvironmentVar(const Zchar* name) -{ - assert(runningOnMainThread()); //getenv() is not thread-safe! - - const char* buffer = ::getenv(name); //no ownership transfer + no extended error reporting - if (!buffer) - return {}; - Zstring value(buffer); - - //some postprocessing: - trim(value); //remove leading, trailing blanks - - //remove leading, trailing double-quotes - if (startsWith(value, Zstr('"')) && - endsWith (value, Zstr('"')) && - value.length() >= 2) - value = Zstring(value.c_str() + 1, value.length() - 2); - - return value; -} - - Zstring resolveRelativePath(const Zstring& relativePath) { - assert(runningOnMainThread()); - /* MSDN: "Multithreaded applications and shared library code should not use the GetFullPathName function - and should avoid using relative path names. The current directory state written by the - SetCurrentDirectory function is stored as a global variable in each process, - therefore multithreaded applications cannot reliably use this value without possible data corruption from other threads, [...]" - - => Just plain wrong, there is no data corruption. What MSDN really means: GetFullPathName() is *perfectly* thread-safe, but depends - on the current directory, which is a process-scope global: https://devblogs.microsoft.com/oldnewthing/20210816-00/?p=105562 */ - if (relativePath.empty()) return relativePath; @@ -99,7 +66,7 @@ Zstring resolveRelativePath(const Zstring& relativePath) //returns value if resolved -std::optional tryResolveMacro(const Zstring& macro) //macro without %-characters +std::optional tryResolveMacro(const ZstringView macro) //macro without %-characters { Zstring timeStr; auto resolveTimePhrase = [&](const Zchar* phrase, const Zchar* format) -> bool @@ -142,7 +109,7 @@ std::optional tryResolveMacro(const Zstring& macro) //macro without %-c } //try to resolve as environment variables - if (std::optional value = getEnvironmentVar(macro.c_str())) + if (std::optional value = getEnvironmentVar(macro)) return *value; return {}; @@ -201,14 +168,14 @@ std::vector zen::getPathPhraseAliases(const Zstring& itemPath) { //environment variables: C:\Users\ -> %UserProfile% - auto substByMacro = [&](const Zchar* macroName, const Zstring& macroPath) + auto substByMacro = [&](const ZstringView macroName, const Zstring& macroPath) { //should use a replaceCpy() that considers "local path" case-sensitivity (if only we had one...) if (contains(itemPath, macroPath)) pathAliases.push_back(makePathPhrase(replaceCpyAsciiNoCase(itemPath, macroPath, Zstring() + MACRO_SEP + macroName + MACRO_SEP))); }; - for (const Zchar* envName : + for (const ZstringView envName : { "HOME", //Linux: /home/ Mac: /Users/ //"USER", -> any benefit? diff --git a/zen/serialize.h b/zen/serialize.h index a996b118..8ccecd53 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -256,7 +256,7 @@ BinContainer unbufferedLoad(Function tryRead /*(void* buffer, size_t bytesToRead { static_assert(sizeof(typename BinContainer::value_type) == 1); //expect: bytes if (blockSize == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); + throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); BinContainer buf; for (;;) @@ -285,7 +285,7 @@ void unbufferedSave(const BinContainer& cont, { static_assert(sizeof(typename BinContainer::value_type) == 1); //expect: bytes if (blockSize == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); + throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); const size_t bufPosEnd = cont.size(); size_t bufPos = 0; @@ -311,7 +311,7 @@ void unbufferedStreamCopy(Function1 tryRead /*(void* buffer, size_t bytesToRead) blockSizeOut = std::bit_ceil(blockSizeOut); #endif if (blockSizeIn == 0 || blockSizeOut == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); + throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); const size_t bufCapacity = blockSizeOut - 1 + blockSizeIn; const size_t alignment = ::sysconf(_SC_PAGESIZE); //-1 on error => posix_memalign() will fail diff --git a/zen/socket.h b/zen/socket.h index df8b768b..4ccde190 100644 --- a/zen/socket.h +++ b/zen/socket.h @@ -143,7 +143,7 @@ namespace size_t tryReadSocket(SocketType socket, void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF! { if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); + throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); int bytesReceived = 0; for (;;) @@ -168,7 +168,7 @@ size_t tryReadSocket(SocketType socket, void* buffer, size_t bytesToRead) //thro size_t tryWriteSocket(SocketType socket, const void* buffer, size_t bytesToWrite) //throw SysError; may return short! CONTRACT: bytesToWrite > 0 { if (bytesToWrite == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); + throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); int bytesWritten = 0; for (;;) diff --git a/zen/stream_buffer.h b/zen/stream_buffer.h index ee9e18fd..64cb76ca 100644 --- a/zen/stream_buffer.h +++ b/zen/stream_buffer.h @@ -149,7 +149,7 @@ private: size_t tryReadImpl(std::unique_lock& ul, void* buffer, size_t bytesToRead) //throw ; may return short; only 0 means EOF! CONTRACT: bytesToRead > 0! { if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); + throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); assert(isLocked(lockStream_)); assert(!errorRead_); @@ -170,7 +170,7 @@ private: size_t tryWriteWhileImpl(std::unique_lock& ul, const void* buffer, size_t bytesToWrite) //throw ; may return short! CONTRACT: bytesToWrite > 0 { if (bytesToWrite == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); + throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); assert(isLocked(lockStream_)); assert(!eof_ && !errorWrite_); diff --git a/zen/string_base.h b/zen/string_base.h index 98544ab3..b19b4851 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -663,7 +663,20 @@ void Zbase::pop_back() template class SP> struct std::hash> { - size_t operator()(const zen::Zbase& str) const { return zen::hashString(str); } + using is_transparent = int; //allow heterogenous lookup! + + template + size_t operator()(const String& str) const { return zen::hashString(str); } +}; + + +template class SP> +struct std::equal_to> +{ + using is_transparent = int; //enable heterogenous lookup! + + template + bool operator()(const String1& lhs, const String2& rhs) const { return zen::equalString(lhs, rhs); } }; #endif //STRING_BASE_H_083217454562342526 diff --git a/zen/string_tools.h b/zen/string_tools.h index ca086efd..03563d41 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -624,13 +624,13 @@ namespace impl template inline int saferPrintf(char* buffer, size_t bufferSize, const char* format, const Num& number) //there is no such thing as a "safe" printf ;) { - return std::snprintf(buffer, bufferSize, format, number); //C99: returns number of chars written if successful, < 0 or >= bufferSize on failure + return std::snprintf(buffer, bufferSize, format, number); //C99: returns number of chars written if successful, < 0 or >= bufferSize on error } template inline int saferPrintf(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const Num& number) { - return std::swprintf(buffer, bufferSize, format, number); //C99: returns number of chars written if successful, < 0 on failure (including buffer too small) + return std::swprintf(buffer, bufferSize, format, number); //C99: returns number of chars written if successful, < 0 on error (including buffer too small) } } diff --git a/zen/sys_error.cpp b/zen/sys_error.cpp index fa7352f0..90d9ee2e 100644 --- a/zen/sys_error.cpp +++ b/zen/sys_error.cpp @@ -248,13 +248,13 @@ std::wstring zen::formatGlibError(const std::string& functionName, GError* error std::wstring zen::getSystemErrorDescription(ErrorCode ec) //return empty string on error { - const ErrorCode currentError = getLastError(); //not necessarily == ec - ZEN_ON_SCOPE_EXIT(errno = currentError); + const ErrorCode ecCurrent = getLastError(); //not necessarily == ec + ZEN_ON_SCOPE_EXIT(errno = ecCurrent); - std::wstring errorMsg; - errorMsg = utfTo(::g_strerror(ec)); //... vs strerror(): "marginally improves thread safety, and marginally improves consistency" + std::wstring errorMsg = utfTo(::g_strerror(ec)); //... vs strerror(): "marginally improves thread safety, and marginally improves consistency" - return trimCpy(errorMsg); //Windows messages seem to end with a space... + trim(errorMsg); //Windows messages seem to end with a space... + return errorMsg; } diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp index 244343f2..55465711 100644 --- a/zen/sys_info.cpp +++ b/zen/sys_info.cpp @@ -26,12 +26,12 @@ using namespace zen; Zstring zen::getLoginUser() //throw FileError { - auto tryGetNonRootUser = [](const char* varName) -> const char* + auto tryGetNonRootUser = [](const char* varName) -> std::optional { - if (const char* buf = ::getenv(varName)) //no ownership transfer + no extended error reporting - if (strLength(buf) > 0 && !equalString(buf, "root")) - return buf; - return nullptr; + if (const std::optional username = getEnvironmentVar(varName)) + if (!username->empty() && *username != "root") + return *username; + return {}; }; if (const uid_t userIdNo = ::getuid(); //never fails @@ -65,9 +65,9 @@ Zstring zen::getLoginUser() //throw FileError //BUT: getlogin() can fail with ENOENT on Linux Mint: https://freefilesync.org/forum/viewtopic.php?t=8181 //getting a little desperate: variables used by installer.sh - if (const char* username = tryGetNonRootUser("USER")) return username; - if (const char* username = tryGetNonRootUser("SUDO_USER")) return username; - if (const char* username = tryGetNonRootUser("LOGNAME")) return username; + if (const std::optional username = tryGetNonRootUser("USER")) return *username; + if (const std::optional username = tryGetNonRootUser("SUDO_USER")) return *username; + if (const std::optional username = tryGetNonRootUser("LOGNAME")) return *username; //apparently the current user really IS root: https://freefilesync.org/forum/viewtopic.php?t=8405 @@ -221,8 +221,8 @@ Zstring zen::getUserHome() //throw FileError /* https://linux.die.net/man/3/getpwuid: An application that wants to determine its user's home directory should inspect the value of HOME (rather than the value getpwuid(getuid())->pw_dir) since this allows the user to modify their notion of "the home directory" during a login session. */ - if (const char* homePath = ::getenv("HOME")) //no ownership transfer + no extended error reporting - return homePath; + if (const std::optional homeDirPath = getEnvironmentVar("HOME")) + return *homeDirPath; //root(0) => consider as request for elevation, NOT impersonation! //=> "HOME=/root" :( @@ -234,10 +234,10 @@ Zstring zen::getUserHome() //throw FileError passwd buf2 = {}; passwd* pwEntry = nullptr; if (const int rv = ::getpwnam_r(loginUser.c_str(), //const char *name - &buf2, //struct passwd* pwd - buf.data(), //char* buf - buf.size(), //size_t buflen - &pwEntry); //struct passwd** result + &buf2, //struct passwd* pwd + buf.data(), //char* buf + buf.size(), //size_t buflen + &pwEntry); //struct passwd** result rv != 0 || !pwEntry) { //"If an error occurs, errno is set appropriately" => why the fuck, then also return errno as return value!? @@ -252,9 +252,9 @@ Zstring zen::getUserHome() //throw FileError Zstring zen::getUserDataPath() //throw FileError { if (::getuid() != 0) //nofail; non-root - if (const char* xdgCfgPath = ::getenv("XDG_CONFIG_HOME"); //no ownership transfer + no extended error reporting - xdgCfgPath && xdgCfgPath[0] != 0) - return xdgCfgPath; + if (const std::optional xdgCfgPath = getEnvironmentVar("XDG_CONFIG_HOME"); + xdgCfgPath&& !xdgCfgPath->empty()) + return *xdgCfgPath; //root(0) => consider as request for elevation, NOT impersonation return appendPath(getUserHome(), ".config"); //throw FileError diff --git a/zen/thread.h b/zen/thread.h index 25a6463a..2464f8be 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -153,7 +153,7 @@ class ThreadGroup { public: ThreadGroup(size_t threadCountMax, const Zstring& groupName) : threadCountMax_(threadCountMax), groupName_(groupName) - { if (threadCountMax == 0) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); } + { if (threadCountMax == 0) throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); } ThreadGroup (ThreadGroup&& tmp) noexcept = default; //noexcept *required* to support move for reallocations in std::vector and std::swap!!! ThreadGroup& operator=(ThreadGroup&& tmp) noexcept = default; //don't use swap() but end worker_ life time immediately diff --git a/zen/time.h b/zen/time.h index 6ca3be3b..ee43566b 100644 --- a/zen/time.h +++ b/zen/time.h @@ -41,7 +41,7 @@ TimeComp getCompileTime(); //returns TimeComp() on error formatTime(Zstr("%Y|%m|%d")); -> "2011|10|29" formatTime(formatDateTag); -> "2011-10-29" formatTime(formatTimeTag); -> "17:55:34" */ -Zstring formatTime(const Zchar* format, const TimeComp& tc = getLocalTime()); //format as specified by "std::strftime", returns empty string on failure +Zstring formatTime(const Zchar* format, const TimeComp& tc = getLocalTime()); //format as specified by "std::strftime", returns empty string on error //the "format" parameter of formatTime() is partially specialized with the following type tags: const Zchar* const formatDateTag = Zstr("%x"); //locale-dependent date representation: e.g. 8/23/2001 @@ -59,7 +59,8 @@ template TimeComp parseTime(const String& format, const String2& str); //similar to ::strptime() //---------------------------------------------------------------------------------------------------------------------------------- - +//format: [-][[d.]HH:]MM:SS e.g. -1.23:45:67 +Zstring formatTimeSpan(int64_t timeInSec, bool hourOptional = false); @@ -385,6 +386,36 @@ TimeComp parseTime(const String& format, const String2& str) return output; } + + +inline +Zstring formatTimeSpan(int64_t timeInSec, bool hourOptional) +{ + Zstring timespanStr; + + if (timeInSec < 0) + { + timeInSec = -timeInSec; //need to fix LLONG_MIN? + timespanStr = Zstr('-'); + } + + //check *before* subtracting days! + const Zchar* timeSpanFmt = hourOptional && timeInSec < 3600 ? Zstr("%M:%S") : formatIsoTimeTag; + + const int secsPerDay = 24 * 3600; + const int64_t days = numeric::intDivFloor(timeInSec, secsPerDay); + if (days > 0) + { + timeInSec -= days * secsPerDay; + timespanStr += numberTo(days) + Zstr("."); //don't need zen::formatNumber(), do we? + } + + //format time span as if absolute UTC time + const TimeComp& tc = getUtcTime(timeInSec); //returns TimeComp() on error + timespanStr += formatTime(timeSpanFmt, tc); //returns empty string on error + + return timespanStr; +} } #endif //TIME_H_8457092814324342453627 diff --git a/zen/utf.h b/zen/utf.h index 56b1ff55..6f7c39cc 100644 --- a/zen/utf.h +++ b/zen/utf.h @@ -16,7 +16,7 @@ namespace zen template TargetString utfTo(const SourceString& str); -const char BYTE_ORDER_MARK_UTF8[] = "\xEF\xBB\xBF"; +const std::string_view BYTE_ORDER_MARK_UTF8 = "\xEF\xBB\xBF"; template bool isValidUtf(const UtfString& str); //check for UTF-8 encoding errors diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp index 28b85c5c..7e680131 100644 --- a/zen/zlib_wrap.cpp +++ b/zen/zlib_wrap.cpp @@ -6,7 +6,7 @@ #include "zlib_wrap.h" //Windows: use the SAME zlib version that wxWidgets is linking against! //C:\Data\Projects\wxWidgets\Source\src\zlib\zlib.h -//Linux/macOS: use zlib system header for both wxWidgets and libcurl (zlib is required for HTTP, SFTP) +//Linux/macOS: use zlib system header for wxWidgets, libcurl (HTTP), libssh2 (SFTP) // => don't compile wxWidgets with: --with-zlib=builtin #include #include "scope_guard.h" @@ -178,7 +178,7 @@ public: size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream! { if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo(__LINE__)); + throw std::logic_error(std::string(__FILE__) + '[' + numberTo(__LINE__) + "] Contract violation!"); gzipStream_.next_out = static_cast(buffer); gzipStream_.avail_out = static_cast(bytesToRead); -- cgit