summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
Diffstat (limited to 'zen')
-rw-r--r--zen/argon2.cpp6
-rw-r--r--zen/basic_math.h20
-rw-r--r--zen/file_access.cpp52
-rw-r--r--zen/file_io.cpp8
-rw-r--r--zen/file_io.h2
-rw-r--r--zen/file_path.cpp78
-rw-r--r--zen/file_path.h4
-rw-r--r--zen/globals.h25
-rw-r--r--zen/http.cpp2
-rw-r--r--zen/resolve_path.cpp43
-rw-r--r--zen/serialize.h6
-rw-r--r--zen/socket.h4
-rw-r--r--zen/stream_buffer.h4
-rw-r--r--zen/string_base.h15
-rw-r--r--zen/string_tools.h4
-rw-r--r--zen/sys_error.cpp10
-rw-r--r--zen/sys_info.cpp34
-rw-r--r--zen/thread.h2
-rw-r--r--zen/time.h35
-rw-r--r--zen/utf.h2
-rw-r--r--zen/zlib_wrap.cpp4
21 files changed, 231 insertions, 129 deletions
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<uint32_t>(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<uint8_t*>(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 <class N, class D> auto intDivFloor(N numerator, D denominator);
template <size_t N, class T>
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 <class InputIterator>
double arithmeticMean(InputIterator first, InputIterator last);
@@ -84,13 +84,13 @@ std::pair<InputIterator, InputIterator> 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<InputIterator, InputIterator> 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 <class T> inline
bool isNull(T value)
{
- return abs(value) <= std::numeric_limits<T>::epsilon(); //epsilon is 0 für integral types => less-equal
+ return abs(value) <= std::numeric_limits<T>::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<Zstring> 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<FileError> 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<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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<PathComponents> 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<Zstring> 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<Zstring> 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<Zstring, Zstring> getAllEnvVars()
+{
+ assert(runningOnMainThread());
+
+ std::unordered_map<Zstring, Zstring> 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<std::unordered_map<Zstring, Zstring>> globalEnvVars;
+GLOBAL_RUN_ONCE(
+ //*INDENT-OFF*
+ //mitigate static initialization order fiasco: (whatever comes first)
+ if (!globalEnvVars.get())
+ globalEnvVars.set(std::make_unique<std::unordered_map<Zstring, Zstring>>(getAllEnvVars()))
+ //*INDENT-ON*
+);
+}
+
+
+std::optional<Zstring> 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<std::unordered_map<Zstring, Zstring>> 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<std::unordered_map<Zstring, Zstring>>(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<PathComponents> parsePathComponents(const Zstring& itemPath); //no value on failure
+std::optional<PathComponents> parsePathComponents(const Zstring& itemPath); //no value on error
std::optional<Zstring> 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<Zstring> 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 <atomic>
#include <memory>
+#include <utility>
#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<T>::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<T>::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<Pod>, "this memory needs to live forever");
- set(nullptr);
+
+ pod_.spinLock.lock();
+ std::shared_ptr<T>* oldInst = std::exchange(pod_.inst, nullptr);
+ pod_.destroyed = true;
+ pod_.spinLock.unlock();
+
+ delete oldInst;
}
std::shared_ptr<T> 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<T>* 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 <zen/sys_info.h>
- // #include <stdlib.h> //getenv()
- #include <unistd.h> //getuid()
- #include <pwd.h> //getpwuid_r()
+ #include <unistd.h> //getcwd()
using namespace zen;
namespace
{
-std::optional<Zstring> 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<Zstring> tryResolveMacro(const Zstring& macro) //macro without %-characters
+std::optional<Zstring> tryResolveMacro(const ZstringView macro) //macro without %-characters
{
Zstring timeStr;
auto resolveTimePhrase = [&](const Zchar* phrase, const Zchar* format) -> bool
@@ -142,7 +109,7 @@ std::optional<Zstring> tryResolveMacro(const Zstring& macro) //macro without %-c
}
//try to resolve as environment variables
- if (std::optional<Zstring> value = getEnvironmentVar(macro.c_str()))
+ if (std::optional<Zstring> value = getEnvironmentVar(macro))
return *value;
return {};
@@ -201,14 +168,14 @@ std::vector<Zstring> zen::getPathPhraseAliases(const Zstring& itemPath)
{
//environment variables: C:\Users\<user> -> %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/<user> Mac: /Users/<user>
//"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<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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<std::mutex>& ul, void* buffer, size_t bytesToRead) //throw <write error>; 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<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
assert(isLocked(lockStream_));
assert(!errorRead_);
@@ -170,7 +170,7 @@ private:
size_t tryWriteWhileImpl(std::unique_lock<std::mutex>& ul, const void* buffer, size_t bytesToWrite) //throw <read error>; may return short! CONTRACT: bytesToWrite > 0
{
if (bytesToWrite == 0)
- throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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<Char, SP>::pop_back()
template <class Char, template <class> class SP>
struct std::hash<zen::Zbase<Char, SP>>
{
- size_t operator()(const zen::Zbase<Char, SP>& str) const { return zen::hashString<size_t>(str); }
+ using is_transparent = int; //allow heterogenous lookup!
+
+ template <class String>
+ size_t operator()(const String& str) const { return zen::hashString<size_t>(str); }
+};
+
+
+template <class Char, template <class> class SP>
+struct std::equal_to<zen::Zbase<Char, SP>>
+{
+ using is_transparent = int; //enable heterogenous lookup!
+
+ template <class String1, class String2>
+ 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 <class Num> 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 <class Num> 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<std::wstring>(::g_strerror(ec)); //... vs strerror(): "marginally improves thread safety, and marginally improves consistency"
+ std::wstring errorMsg = utfTo<std::wstring>(::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<Zstring>
{
- 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<Zstring> 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<Zstring> username = tryGetNonRootUser("USER")) return *username;
+ if (const std::optional<Zstring> username = tryGetNonRootUser("SUDO_USER")) return *username;
+ if (const std::optional<Zstring> 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<Zstring> 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<Zstring> 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<std::string>(__LINE__)); }
+ { if (threadCountMax == 0) throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__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 <class String, class String2>
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<Zstring>(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 <class TargetString, class SourceString>
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 <class UtfString>
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 <zlib.h>
#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<std::string>(__LINE__));
+ throw std::logic_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Contract violation!");
gzipStream_.next_out = static_cast<Bytef*>(buffer);
gzipStream_.avail_out = static_cast<uInt>(bytesToRead);
bgstack15