diff options
author | B Stack <bgstack15@gmail.com> | 2021-02-02 11:44:31 -0500 |
---|---|---|
committer | B Stack <bgstack15@gmail.com> | 2021-02-02 11:44:31 -0500 |
commit | d299ddd2f27a437f0fc0cb49abdfd6dd8e3d94f8 (patch) | |
tree | 4d7c950512836f473a6a8cbb521c61e800db6584 /zen | |
parent | Merge branch '11.5' into 'master' (diff) | |
download | FreeFileSync-d299ddd2f27a437f0fc0cb49abdfd6dd8e3d94f8.tar.gz FreeFileSync-d299ddd2f27a437f0fc0cb49abdfd6dd8e3d94f8.tar.bz2 FreeFileSync-d299ddd2f27a437f0fc0cb49abdfd6dd8e3d94f8.zip |
add upstream 11.6
Diffstat (limited to 'zen')
-rw-r--r-- | zen/basic_math.h | 74 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 2 | ||||
-rw-r--r-- | zen/file_access.cpp | 28 | ||||
-rw-r--r-- | zen/file_io.cpp | 4 | ||||
-rw-r--r-- | zen/file_io.h | 2 | ||||
-rw-r--r-- | zen/format_unit.cpp | 24 | ||||
-rw-r--r-- | zen/json.h | 9 | ||||
-rw-r--r-- | zen/process_exec.cpp (renamed from zen/shell_execute.cpp) | 83 | ||||
-rw-r--r-- | zen/process_exec.h (renamed from zen/shell_execute.h) | 10 | ||||
-rw-r--r-- | zen/resolve_path.cpp | 293 | ||||
-rw-r--r-- | zen/resolve_path.h | 34 | ||||
-rw-r--r-- | zen/shutdown.cpp | 16 | ||||
-rw-r--r-- | zen/socket.h | 12 | ||||
-rw-r--r-- | zen/string_tools.h | 6 | ||||
-rw-r--r-- | zen/symlink_target.h | 2 | ||||
-rw-r--r-- | zen/sys_error.h | 2 | ||||
-rw-r--r-- | zen/sys_info.cpp | 47 | ||||
-rw-r--r-- | zen/sys_info.h | 5 | ||||
-rw-r--r-- | zen/sys_version.cpp | 12 | ||||
-rw-r--r-- | zen/zstring.h | 8 |
20 files changed, 526 insertions, 147 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h index 2171ee24..a4feb83e 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -16,7 +16,6 @@ namespace numeric { -template <class T> T abs(T value); template <class T> auto dist(T a, T b); template <class T> int sign(T value); //returns one of {-1, 0, 1} template <class T> bool isNull(T value); //...definitively fishy... @@ -24,10 +23,9 @@ template <class T> bool isNull(T value); //...definitively fishy... template <class T, class InputIterator> //precondition: range must be sorted! auto nearMatch(const T& val, InputIterator first, InputIterator last); -int64_t round(double d); //"little rounding function" - -template <class N, class D> -auto integerDivideRoundUp(N numerator, D denominator); +template <class N, class D> auto intDivRound(N numerator, D denominator); +template <class N, class D> auto intDivCeil (N numerator, D denominator); +template <class N, class D> auto intDivFloor(N numerator, D denominator); template <size_t N, class T> T power(T value); @@ -67,16 +65,6 @@ double norm2(InputIterator first, InputIterator last); //################# inline implementation ######################### template <class T> inline -T abs(T value) -{ - //static_assert(std::is_signed_v<T>); - if (value < 0) - return -value; //operator "?:" caveat: may be different type than "value" - else - return value; -} - -template <class T> inline auto dist(T a, T b) //return type might be different than T, e.g. std::chrono::duration instead of std::chrono::time_point { return a > b ? a - b : b - a; @@ -160,23 +148,59 @@ bool isNull(T value) } -inline -int64_t round(double d) +template <class N, class D> inline +auto intDivRound(N num, D den) { - assert(d - 0.5 >= std::numeric_limits<int64_t>::min() && //if double is larger than what int can represent: - d + 0.5 <= std::numeric_limits<int64_t>::max()); //=> undefined behavior! + using namespace zen; + static_assert(IsInteger<N>::value && IsInteger<D>::value); + static_assert(IsSignedInt<N>::value == IsSignedInt<D>::value); //until further + assert(den != 0); + if constexpr (IsSignedInt<N>::value) + { + if ((num < 0) != (den < 0)) + return (num - den / 2) / den; + } + return (num + den / 2) / den; +} + - return static_cast<int64_t>(d < 0 ? d - 0.5 : d + 0.5); +template <class N, class D> inline +auto intDivCeil(N num, D den) +{ + using namespace zen; + static_assert(IsInteger<N>::value && IsInteger<D>::value); + static_assert(IsSignedInt<N>::value == IsSignedInt<D>::value); //until further + assert(den != 0); + if constexpr (IsSignedInt<N>::value) + { + if ((num < 0) != (den < 0)) + return num / den; + + if (num < 0 && den < 0) + num += 2; //return (num + den + 1) / den + } + return (num + den - 1) / den; } template <class N, class D> inline -auto integerDivideRoundUp(N numerator, D denominator) +auto intDivFloor(N num, D den) { - static_assert(zen::IsInteger<N>::value); - static_assert(zen::IsInteger<D>::value); - assert(numerator >= 0 && denominator > 0); - return (numerator + denominator - 1) / denominator; + using namespace zen; + static_assert(IsInteger<N>::value && IsInteger<D>::value); + static_assert(IsSignedInt<N>::value == IsSignedInt<D>::value); //until further + assert(den != 0); + if constexpr (IsSignedInt<N>::value) + { + if ((num < 0) != (den < 0)) + { + if (num < 0) + num += 2; //return (num - den + 1) / den + + return (num - den - 1) / den; + } + } + return num / den; } diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 62493084..dc416b34 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -9,7 +9,7 @@ #include <set> #include "thread.h" #include "scope_guard.h" -#include "basic_math.h" +//#include "basic_math.h" #include <map> #include <sys/inotify.h> diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 3269bef4..4c9af652 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -66,8 +66,12 @@ std::optional<PathComponents> zen::parsePathComponents(const Zstring& itemPath) 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 - if (startsWith(itemPath, "/run/user/" + numberTo<std::string>(::getuid()) + "/gvfs/")) //::getuid() never fails - pc = doParse(6 /*sepCountVolumeRoot*/, false /*rootWithSep*/); + { + Zstring tmp(itemPath.begin() + strLength("/run/user/"), itemPath.end()); + tmp = beforeFirst(tmp, "/gvfs/", IfNotFoundReturn::none); + if (!tmp.empty() && std::all_of(tmp.begin(), tmp.end(), [](char c) { return isDigit(c); })) + /**/pc = doParse(6 /*sepCountVolumeRoot*/, false /*rootWithSep*/); + } if (!pc && startsWith(itemPath, "/")) @@ -187,6 +191,8 @@ VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError if (::stat(itemPath.c_str(), &fileInfo) != 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "stat"); + warn_static("NOT STABLE!") + return fileInfo.st_dev; } @@ -203,8 +209,8 @@ uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError Zstring zen::getTempFolderPath() //throw FileError { - if (const char* buf = ::getenv("TMPDIR")) //no extended error reporting - return buf; + if (const char* tempPath = ::getenv("TMPDIR")) //no extended error reporting + return tempPath; //TMPDIR not set on CentOS 7, WTF! return P_tmpdir; //usually resolves to "/tmp" } @@ -520,19 +526,21 @@ void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTarget { auto getErrorMsg = [&] { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)); }; - //don't allow creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/ + //don't allow creating irregular folders! const Zstring dirName = afterLast(dirPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::all); + + //e.g. "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/ if (std::all_of(dirName.begin(), dirName.end(), [](Zchar c) { return c == Zstr('.'); })) /**/throw FileError(getErrorMsg(), replaceCpy<std::wstring>(L"Invalid folder name %x.", L"%x", fmtPath(dirName))); #if 0 //not appreciated: https://freefilesync.org/forum/viewtopic.php?t=7509 - //not critical, but will visually confuse user sooner or later: - if (startsWith(dirName, Zstr(' ')) || - endsWith (dirName, Zstr(' '))) - throw FileError(getErrorMsg(), replaceCpy<std::wstring>(L"Folder name %x starts/ends with space character.", L"%x", fmtPath(dirName))); + if (startsWith(dirName, Zstr(' ')) || //Windows can access these just fine once created! + endsWith (dirName, Zstr(' '))) // + throw FileError(getErrorMsg(), replaceCpy<std::wstring>(L"Invalid folder name. %x starts/ends with space character.", L"%x", fmtPath(dirName))); #endif - const mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777, default for newly created directories + + const mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777 => consider umask! if (::mkdir(dirPath.c_str(), mode) != 0) { diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 33c41fbc..e081335d 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -167,7 +167,7 @@ FileBase::FileHandle openHandleForWrite(const Zstring& filePath) //throw FileErr //checkForUnsupportedType(filePath); -> not needed, open() + O_WRONLY should fail fast const int fdFile = ::open(filePath.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC | /*access == FileOutput::ACC_OVERWRITE ? O_TRUNC : */ O_EXCL, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666 + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666 => umask will be applied implicitly! if (fdFile == -1) { const int ec = errno; //copy before making other system calls! @@ -296,7 +296,7 @@ void FileOutput::reserveSpace(uint64_t expectedSize) //throw FileError if (expectedSize < 1024) //https://www.sciencedirect.com/topics/computer-science/master-file-table return; - //don't use ::posix_fallocate! horribly inefficient if FS doesn't support it + changes file size + //don't use ::posix_fallocate which uses horribly inefficient fallback if FS doesn't support it (EOPNOTSUPP) and changes files size! //FALLOC_FL_KEEP_SIZE => allocate only, file size is NOT changed! if (::fallocate(getHandle(), //int fd, FALLOC_FL_KEEP_SIZE, //int mode, diff --git a/zen/file_io.h b/zen/file_io.h index 4210cc57..a7385241 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -110,6 +110,8 @@ public: void write(const void* buffer, size_t bytesToWrite) { tmpFile_.write(buffer, bytesToWrite); } //throw FileError, X + FileOutput& refTempFile() { return tmpFile_; } + void commit() //throw FileError, X { tmpFile_.finalize(); //throw FileError, X diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 0d75a9d4..28943de7 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -26,20 +26,20 @@ using namespace zen; std::wstring zen::formatTwoDigitPrecision(double value) { //print two digits: 0,1 | 1,1 | 11 - if (numeric::abs(value) < 9.95) //9.99 must not be formatted as "10.0" + if (std::abs(value) < 9.95) //9.99 must not be formatted as "10.0" return printNumber<std::wstring>(L"%.1f", value); - return numberTo<std::wstring>(numeric::round(value)); + return numberTo<std::wstring>(std::llround(value)); } std::wstring zen::formatThreeDigitPrecision(double value) { //print three digits: 0,01 | 0,11 | 1,11 | 11,1 | 111 - if (numeric::abs(value) < 9.995) //9.999 must not be formatted as "10.00" + if (std::abs(value) < 9.995) //9.999 must not be formatted as "10.00" return printNumber<std::wstring>(L"%.2f", value); - if (numeric::abs(value) < 99.95) //99.99 must not be formatted as "100.0" + if (std::abs(value) < 99.95) //99.99 must not be formatted as "100.0" return printNumber<std::wstring>(L"%.1f", value); - return numberTo<std::wstring>(numeric::round(value)); + return numberTo<std::wstring>(std::llround(value)); } @@ -47,7 +47,7 @@ std::wstring zen::formatFilesizeShort(int64_t size) { //if (size < 0) return _("Error"); -> really? - if (numeric::abs(size) <= 999) + if (std::abs(size) <= 999) return _P("1 byte", "%x bytes", static_cast<int>(size)); double sizeInUnit = static_cast<double>(size); @@ -55,19 +55,19 @@ std::wstring zen::formatFilesizeShort(int64_t size) auto formatUnit = [&](const std::wstring& unitTxt) { return replaceCpy(unitTxt, L"%x", formatThreeDigitPrecision(sizeInUnit)); }; sizeInUnit /= 1024; - if (numeric::abs(sizeInUnit) < 999.5) + if (std::abs(sizeInUnit) < 999.5) return formatUnit(_("%x KB")); sizeInUnit /= 1024; - if (numeric::abs(sizeInUnit) < 999.5) + if (std::abs(sizeInUnit) < 999.5) return formatUnit(_("%x MB")); sizeInUnit /= 1024; - if (numeric::abs(sizeInUnit) < 999.5) + if (std::abs(sizeInUnit) < 999.5) return formatUnit(_("%x GB")); sizeInUnit /= 1024; - if (numeric::abs(sizeInUnit) < 999.5) + if (std::abs(sizeInUnit) < 999.5) return formatUnit(_("%x TB")); sizeInUnit /= 1024; @@ -116,7 +116,7 @@ std::wstring roundToBlock(double timeInHigh, const int blockSizeLow = granularity * timeInHigh < 1 ? numeric::nearMatch(granularity * timeInLow, std::begin(stepsLow), std::end(stepsLow)): numeric::nearMatch(granularity * timeInHigh, std::begin(stepsHigh), std::end(stepsHigh)) * unitLowPerHigh; - const int roundedtimeInLow = static_cast<int>(numeric::round(timeInLow / blockSizeLow) * blockSizeLow); + const int roundedtimeInLow = std::lround(timeInLow / blockSizeLow) * blockSizeLow; std::wstring output = formatUnitTime(roundedtimeInLow / unitLowPerHigh, unitHigh); if (unitLowPerHigh > blockSizeLow) @@ -192,7 +192,7 @@ WeekDay impl::getFirstDayOfWeekImpl() //throw SysError /* testing: change locale via command line --------------------------------------- LC_TIME=en_DK.utf8 => Monday - LC_TIME=en_US.utf8 => Sunday */ + LC_TIME=en_US.utf8 => Sunday */ const char* firstDay = ::nl_langinfo(_NL_TIME_FIRST_WEEKDAY); //[1-Sunday, 7-Saturday] ASSERT_SYSERROR(firstDay && 1 <= *firstDay && *firstDay <= 7); @@ -95,15 +95,15 @@ namespace json_impl { namespace { -std::string jsonEscape(const std::string& str) +[[nodiscard]] std::string jsonEscape(const std::string& str) { std::string output; for (const char c : str) switch (c) { //*INDENT-OFF* - case '"': output += "\\\""; break; //escaping mandatory case '\\': output += "\\\\"; break; // + case '"': output += "\\\""; break; //escaping mandatory case '\b': output += "\\b"; break; // case '\f': output += "\\f"; break; // @@ -128,7 +128,7 @@ std::string jsonEscape(const std::string& str) } -std::string jsonUnescape(const std::string& str) +[[nodiscard]] std::string jsonUnescape(const std::string& str) { std::string output; std::basic_string<impl::Char16> utf16Buf; @@ -152,7 +152,6 @@ std::string jsonUnescape(const std::string& str) for (auto it = str.begin(); it != str.end(); ++it) { const char c = *it; - if (c == '\\') { ++it; @@ -166,8 +165,8 @@ std::string jsonUnescape(const std::string& str) switch (c2) { //*INDENT-OFF* - case '"': case '\\': + case '"': case '/': writeOut(c2); break; case 'b': writeOut('\b'); break; case 'f': writeOut('\f'); break; diff --git a/zen/shell_execute.cpp b/zen/process_exec.cpp index 241b9786..bbc87c51 100644 --- a/zen/shell_execute.cpp +++ b/zen/process_exec.cpp @@ -4,7 +4,7 @@ // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** -#include "shell_execute.h" +#include "process_exec.h" #include <chrono> #include "guid.h" #include "file_access.h" @@ -17,50 +17,32 @@ using namespace zen; -std::vector<Zstring> zen::parseCommandline(const Zstring& cmdLine) +Zstring zen::escapeCommandArg(const Zstring& arg) { - std::vector<Zstring> args; - //"Parsing C++ Command-Line Arguments": https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments - //we do the job ourselves! both wxWidgets and ::CommandLineToArgvW() parse "C:\" "D:\" as single line C:\" D:\" - //-> "solution": we just don't support protected quotation mark! - - auto itStart = cmdLine.end(); //end() means: no token - for (auto it = cmdLine.begin(); it != cmdLine.end(); ++it) - if (*it == Zstr(' ')) //space commits token +//*INDENT-OFF* + Zstring output; + for (const Zchar c : arg) + switch (c) { - if (itStart != cmdLine.end()) - { - args.emplace_back(itStart, it); - itStart = cmdLine.end(); //expect consecutive blanks! - } - } - else - { - //start new token - if (itStart == cmdLine.end()) - itStart = it; - - if (*it == Zstr('"')) - { - it = std::find(it + 1, cmdLine.end(), Zstr('"')); - if (it == cmdLine.end()) - break; - } + case '"': output += "\\\""; break; //Windows: not needed; " cannot be used as file name + case '\\': output += "\\\\"; break; //Windows: path separator! => don't escape + case '`': output += "\\`"; break; //yes, used in some paths => Windows: no escaping required + default: output += c; break; } - if (itStart != cmdLine.end()) - args.emplace_back(itStart, cmdLine.end()); - - for (Zstring& str : args) - if (str.size() >= 2 && startsWith(str, Zstr('"')) && endsWith(str, Zstr('"'))) - str = Zstring(str.c_str() + 1, str.size() - 2); +//*INDENT-ON* + if (contains(output, Zstr(' '))) + output = Zstr('"') + output + Zstr('"'); //Windows: escaping a single blank instead would not work - return args; + return output; } -std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cmdLine, std::optional<int> timeoutMs) //throw SysError, SysErrorTimeOut +namespace +{ +std::pair<int /*exit code*/, std::string> processExecuteImpl(const Zstring& filePath, const std::vector<Zstring>& arguments, + std::optional<int> timeoutMs) //throw SysError, SysErrorTimeOut { const Zstring tempFilePath = appendSeparator(getTempFolderPath()) + //throw FileError Zstr("FFS-") + utfTo<Zstring>(formatAsHexString(generateGUID())); @@ -135,9 +117,12 @@ std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cm if (::dup(fdLifeSignW) == -1) //O_CLOEXEC does NOT propagate with dup() THROW_LAST_SYS_ERROR("dup(fdLifeSignW)"); + std::vector<const char*> argv{ filePath.c_str() }; + for (const Zstring& arg : arguments) + argv.push_back(arg.c_str()); + argv.push_back(nullptr); - const char* argv[] = { "sh", "-c", cmdLine.c_str(), nullptr }; - /*int rv =*/::execv("/bin/sh", const_cast<char**>(argv)); //only returns if an error occurred + /*int rv =*/::execv(argv[0], const_cast<char**>(&argv[0])); //only returns if an error occurred //safe to cast away const: https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html // "The statement about argv[] and envp[] being constants is included to make explicit to future // writers of language bindings that these objects are completely constant. Due to a limitation of @@ -196,10 +181,10 @@ std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cm fd_set rfd = {}; //includes FD_ZERO FD_SET(fdLifeSignR, &rfd); - if (const int rv = ::select(fdLifeSignR + 1, //int nfds, - &rfd, //fd_set* readfds, - nullptr, //fd_set* writefds, - nullptr, //fd_set* exceptfds, + if (const int rv = ::select(fdLifeSignR + 1, //int nfds + &rfd, //fd_set* readfds + nullptr, //fd_set* writefds + nullptr, //fd_set* exceptfds &tv); //struct timeval* timeout rv < 0) THROW_LAST_SYS_ERROR("select"); @@ -221,8 +206,7 @@ std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cm guardTmpFile.dismiss(); FileInput streamIn(fdTempFile, tempFilePath, nullptr /*notifyUnbufferedIO*/); //takes ownership! - const std::wstring output = utfTo<std::wstring>(bufferedLoad<std::string>(streamIn)); //throw FileError - + std::string output = bufferedLoad<std::string>(streamIn); //throw FileError if (!WIFEXITED(statusCode)) //signalled, crashed? throw SysError(formatSystemError("waitpid", WIFSIGNALED(statusCode) ? @@ -237,6 +221,14 @@ std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cm return { exitCode, output }; } +} + + +std::pair<int /*exit code*/, Zstring> zen::consoleExecute(const Zstring& cmdLine, std::optional<int> timeoutMs) //throw SysError, SysErrorTimeOut +{ + const auto& [exitCode, output] = processExecuteImpl("/bin/sh", {"-c", cmdLine.c_str()}, timeoutMs); //throw SysError, SysErrorTimeOut + return {exitCode, copyStringTo<Zstring>(output)}; +} void zen::openWithDefaultApp(const Zstring& itemPath) //throw FileError @@ -248,7 +240,8 @@ void zen::openWithDefaultApp(const Zstring& itemPath) //throw FileError if (const auto& [exitCode, output] = consoleExecute(cmdLine, std::nullopt /*timeoutMs*/); //throw SysError, (SysErrorTimeOut) exitCode != 0) - throw SysError(formatSystemError(utfTo<std::string>(cmdTemplate), replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output)); + throw SysError(formatSystemError(utfTo<std::string>(cmdTemplate), + replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), utfTo<std::wstring>(output))); } catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(itemPath)), e.toString()); } } diff --git a/zen/shell_execute.h b/zen/process_exec.h index b80cf2ba..1c18c3f4 100644 --- a/zen/shell_execute.h +++ b/zen/process_exec.h @@ -12,13 +12,15 @@ namespace zen { -std::vector<Zstring> parseCommandline(const Zstring& cmdLine); +Zstring escapeCommandArg(const Zstring& arg); DEFINE_NEW_SYS_ERROR(SysErrorTimeOut) -[[nodiscard]] std::pair<int /*exit code*/, std::wstring> consoleExecute(const Zstring& cmdLine, std::optional<int> timeoutMs); //throw SysError, SysErrorTimeOut -/* limitations: Windows: cmd.exe returns exit code 1 if file not found (instead of throwing SysError) => nodiscard! - Linux/macOS: SysErrorTimeOut leaves zombie process behind */ +[[nodiscard]] std::pair<int /*exit code*/, Zstring> consoleExecute(const Zstring& cmdLine, std::optional<int> timeoutMs); //throw SysError, SysErrorTimeOut +/* Windows: - cmd.exe returns exit code 1 if file not found (instead of throwing SysError) => nodiscard! + - handles elevation when CreateProcess() would fail with ERROR_ELEVATION_REQUIRED! + - no support for UNC path and Unicode on Win7; apparently no issue on Win10! + Linux/macOS: SysErrorTimeOut leaves zombie process behind if timeoutMs is used */ void openWithDefaultApp(const Zstring& itemPath); //throw FileError } diff --git a/zen/resolve_path.cpp b/zen/resolve_path.cpp new file mode 100644 index 00000000..76999500 --- /dev/null +++ b/zen/resolve_path.cpp @@ -0,0 +1,293 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#include "resolve_path.h" +//#include <set> //not necessarily included by <map>! +//#include <map> +#include "time.h" +#include "thread.h" +//#include "utf.h" +//#include "scope_guard.h" +//#include "globals.h" +#include "file_access.h" + + #include <stdlib.h> //getenv() + #include <unistd.h> //getcwd + +using namespace zen; + + +namespace +{ +std::optional<Zstring> getEnvironmentVar(const Zstring& name) +{ + assert(runningOnMainThread()); //getenv() is not thread-safe! + + const char* buffer = ::getenv(name.c_str()); //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()); //GetFullPathName() is documented to NOT be thread-safe! + /* 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, */ + + if (relativePath.empty()) + return relativePath; + + Zstring pathTmp = relativePath; + //https://linux.die.net/man/2/path_resolution + if (!startsWith(pathTmp, FILE_NAME_SEPARATOR)) //absolute names are exactly those starting with a '/' + { + /* basic support for '~': strictly speaking this is a shell-layer feature, so "realpath()" won't handle it + https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html + + 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 (startsWith(pathTmp, "~/") || pathTmp == "~") + { + if (const std::optional<Zstring> homeDir = getEnvironmentVar("HOME")) + { + if (startsWith(pathTmp, "~/")) + pathTmp = appendSeparator(*homeDir) + afterFirst(pathTmp, '/', IfNotFoundReturn::none); + else //pathTmp == "~" + pathTmp = *homeDir; + } + //else: error! no further processing! + } + else + { + //we cannot use ::realpath() which only resolves *existing* relative paths! + if (char* dirPath = ::getcwd(nullptr, 0)) + { + ZEN_ON_SCOPE_EXIT(::free(dirPath)); + pathTmp = appendSeparator(dirPath) + pathTmp; + } + } + } + //get rid of some cruft (just like GetFullPathName()) + replace(pathTmp, "/./", '/'); + if (endsWith(pathTmp, "/.")) + pathTmp.pop_back(); //keep the "/" => consider pathTmp == "/." + + //what about "/../"? might be relative to symlinks => preserve! + + return pathTmp; +} + + + + +//returns value if resolved +std::optional<Zstring> tryResolveMacro(const Zstring& macro) //macro without %-characters +{ + Zstring timeStr; + auto resolveTimePhrase = [&](const Zchar* phrase, const Zchar* format) -> bool + { + if (!equalAsciiNoCase(macro, phrase)) + return false; + + timeStr = formatTime(format); + return true; + }; + + //https://en.cppreference.com/w/cpp/chrono/c/strftime + //there exist environment variables named %TIME%, %DATE% so check for our internal macros first! + if (resolveTimePhrase(Zstr("Date"), Zstr("%Y-%m-%d"))) return timeStr; + if (resolveTimePhrase(Zstr("Time"), Zstr("%H%M%S"))) return timeStr; + if (resolveTimePhrase(Zstr("TimeStamp"), Zstr("%Y-%m-%d %H%M%S"))) return timeStr; //e.g. "2012-05-15 131513" + if (resolveTimePhrase(Zstr("Year"), Zstr("%Y"))) return timeStr; + if (resolveTimePhrase(Zstr("Month"), Zstr("%m"))) return timeStr; + if (resolveTimePhrase(Zstr("MonthName"), Zstr("%b"))) return timeStr; //e.g. "Jan" + if (resolveTimePhrase(Zstr("Day"), Zstr("%d"))) return timeStr; + if (resolveTimePhrase(Zstr("Hour"), Zstr("%H"))) return timeStr; + if (resolveTimePhrase(Zstr("Min"), Zstr("%M"))) return timeStr; + if (resolveTimePhrase(Zstr("Sec"), Zstr("%S"))) return timeStr; + if (resolveTimePhrase(Zstr("WeekDayName"), Zstr("%a"))) return timeStr; //e.g. "Mon" + if (resolveTimePhrase(Zstr("Week"), Zstr("%V"))) return timeStr; //ISO 8601 week of the year + + if (equalAsciiNoCase(macro, Zstr("WeekDay"))) + { + const int weekDayStartSunday = stringTo<int>(formatTime(Zstr("%w"))); //[0 (Sunday), 6 (Saturday)] => not localized! + //alternative 1: use "%u": ISO 8601 weekday as number with Monday as 1 (1-7) => newer standard than %w + //alternative 2: ::mktime() + std::tm::tm_wday + + const int weekDayStartMonday = (weekDayStartSunday + 6) % 7; //+6 == -1 in Z_7 + // [0-Monday, 6-Sunday] + + const int weekDayStartLocal = ((weekDayStartMonday + 7 - static_cast<int>(getFirstDayOfWeek())) % 7) + 1; + //[1 (local first day of week), 7 (local last day of week)] + + return numberTo<Zstring>(weekDayStartLocal); + } + + //try to resolve as environment variables + if (std::optional<Zstring> value = getEnvironmentVar(macro)) + return *value; + + + return {}; +} + +const Zchar MACRO_SEP = Zstr('%'); +} + + +//returns expanded or original string +Zstring zen::expandMacros(const Zstring& text) +{ + if (contains(text, MACRO_SEP)) + { + Zstring prefix = beforeFirst(text, MACRO_SEP, IfNotFoundReturn::none); + Zstring rest = afterFirst (text, MACRO_SEP, IfNotFoundReturn::none); + if (contains(rest, MACRO_SEP)) + { + Zstring potentialMacro = beforeFirst(rest, MACRO_SEP, IfNotFoundReturn::none); + Zstring postfix = afterFirst (rest, MACRO_SEP, IfNotFoundReturn::none); //text == prefix + MACRO_SEP + potentialMacro + MACRO_SEP + postfix + + if (std::optional<Zstring> value = tryResolveMacro(potentialMacro)) + return prefix + *value + expandMacros(postfix); + else + return prefix + MACRO_SEP + potentialMacro + expandMacros(MACRO_SEP + postfix); + } + } + return text; +} + + +namespace +{ + + +//expand volume name if possible, return original input otherwise +Zstring expandVolumeName(Zstring pathPhrase) // [volname]:\folder [volname]\folder [volname]folder -> C:\folder +{ + //we only expect the [.*] pattern at the beginning => do not touch dir names like "C:\somedir\[stuff]" + trim(pathPhrase, true, false); + if (startsWith(pathPhrase, Zstr('['))) + { + const size_t posEnd = pathPhrase.find(Zstr(']')); + if (posEnd != Zstring::npos) + { + Zstring volName = Zstring(pathPhrase.c_str() + 1, posEnd - 1); + Zstring relPath = Zstring(pathPhrase.c_str() + posEnd + 1); + + if (startsWith(relPath, FILE_NAME_SEPARATOR)) + relPath = afterFirst(relPath, FILE_NAME_SEPARATOR, IfNotFoundReturn::none); + else if (startsWith(relPath, Zstr(":\\"))) //Win-only + relPath = afterFirst(relPath, Zstr('\\'), IfNotFoundReturn::none); + return "/.../[" + volName + "]/" + relPath; + } + } + return pathPhrase; +} + + +void getFolderAliasesRecursive(const Zstring& pathPhrase, std::set<Zstring, LessNativePath>& output) +{ + + //3. environment variables: C:\Users\<user> -> %UserProfile% + { + std::vector<std::pair<Zstring, Zstring>> macroList; + + //get list of useful variables + auto addEnvVar = [&](const Zstring& envName) + { + if (std::optional<Zstring> value = getEnvironmentVar(envName)) + macroList.emplace_back(envName, *value); + }; + addEnvVar("HOME"); //Linux: /home/<user> Mac: /Users/<user> + //addEnvVar("USER"); -> any benefit? + //substitute paths by symbolic names + for (const auto& [macroName, macroPath] : macroList) + { + //should use a replaceCpy() that considers "local path" case-sensitivity (if only we had one...) + const Zstring pathSubst = replaceCpyAsciiNoCase(pathPhrase, macroPath, MACRO_SEP + macroName + MACRO_SEP); + if (pathSubst != pathPhrase) + output.insert(pathSubst); + } + } + + //4. replace (all) macros: %UserProfile% -> C:\Users\<user> + { + const Zstring pathExp = expandMacros(pathPhrase); + if (pathExp != pathPhrase) + if (output.insert(pathExp).second) + getFolderAliasesRecursive(pathExp, output); //recurse! + } +} +} + + +std::vector<Zstring> zen::getFolderPathAliases(const Zstring& folderPathPhrase) +{ + const Zstring dirPath = trimCpy(folderPathPhrase); + if (dirPath.empty()) + return {}; + + std::set<Zstring, LessNativePath> tmp; + getFolderAliasesRecursive(dirPath, tmp); + + tmp.erase(dirPath); + tmp.erase(Zstring()); + + return { tmp.begin(), tmp.end() }; +} + + +//coordinate changes with acceptsFolderPathPhraseNative()! +Zstring zen::getResolvedFilePath(const Zstring& pathPhrase) //noexcept +{ + Zstring path = pathPhrase; + + path = expandMacros(path); //expand before trimming! + + //remove leading/trailing whitespace before allowing misinterpretation in applyLongPathPrefix() + trim(path); //attention: don't remove all whitespace from right, e.g. 0xa0 may be used as part of a folder name + + + path = expandVolumeName(path); //may block for slow USB sticks and idle HDDs! + + /* need to resolve relative paths: + WINDOWS: + - \\?\-prefix requires absolute names + - Volume Shadow Copy: volume name needs to be part of each file path + - file icon buffer (at least for extensions that are actually read from disk, like "exe") + - Use of relative path names is not thread safe! (e.g. SHFileOperation) + WINDOWS/LINUX: + - detection of dependent directories, e.g. "\" and "C:\test" */ + path = resolveRelativePath(path); + + //remove trailing slash, unless volume root: + if (std::optional<PathComponents> pc = parsePathComponents(path)) + { + if (pc->relPath.empty()) + path = pc->rootPath; + else + path = appendSeparator(pc->rootPath) + pc->relPath; + } //keep this brace for GCC: -Wparentheses + + return path; +} + + diff --git a/zen/resolve_path.h b/zen/resolve_path.h new file mode 100644 index 00000000..f2c427f1 --- /dev/null +++ b/zen/resolve_path.h @@ -0,0 +1,34 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef RESOLVE_PATH_H_817402834713454 +#define RESOLVE_PATH_H_817402834713454 + +#include <vector> +#include "zstring.h" + + +namespace zen +{ +/* + - expand macros + - trim whitespace + - expand volume path by name + - convert relative paths into absolute + + => may block for slow USB sticks and idle HDDs + => not thread-safe, see ::GetFullPathName()! +*/ +Zstring getResolvedFilePath(const Zstring& pathPhrase); //noexcept + +//macro substitution only +Zstring expandMacros(const Zstring& text); + +std::vector<Zstring> getFolderPathAliases(const Zstring& folderPathPhrase); //may block for slow USB sticks when resolving [<volume name>] + +} + +#endif //RESOLVE_PATH_H_817402834713454 diff --git a/zen/shutdown.cpp b/zen/shutdown.cpp index 8e8456df..f46caf6b 100644 --- a/zen/shutdown.cpp +++ b/zen/shutdown.cpp @@ -5,7 +5,7 @@ // ***************************************************************************** #include "shutdown.h" - #include <zen/shell_execute.h> + #include <zen/process_exec.h> using namespace zen; @@ -19,9 +19,10 @@ void zen::shutdownSystem() //throw FileError { //https://linux.die.net/man/2/reboot => needs admin rights! //"systemctl" should work without admin rights: - const auto& [exitCode, output] = consoleExecute("systemctl poweroff", std::nullopt /*timeoutMs*/); //throw SysError, (SysErrorTimeOut) - if (!trimCpy(output).empty()) //see comment in suspendSystem() - throw SysError(output); + auto [exitCode, output] = consoleExecute("systemctl poweroff", std::nullopt /*timeoutMs*/); //throw SysError, (SysErrorTimeOut) + trim(output); + if (!output.empty()) //see comment in suspendSystem() + throw SysError(utfTo<std::wstring>(output)); } catch (const SysError& e) { throw FileError(_("Unable to shut down the system."), e.toString()); } @@ -33,10 +34,11 @@ void zen::suspendSystem() //throw FileError try { //"systemctl" should work without admin rights: - const auto& [exitCode, output] = consoleExecute("systemctl suspend", std::nullopt /*timeoutMs*/); //throw SysError, (SysErrorTimeOut) + auto [exitCode, output] = consoleExecute("systemctl suspend", std::nullopt /*timeoutMs*/); //throw SysError, (SysErrorTimeOut) + trim(output); //why does "systemctl suspend" return exit code 1 despite apparent success!?? - if (!trimCpy(output).empty()) //at least we can assume "no output" on success - throw SysError(output); + if (!output.empty()) //at least we can assume "no output" on success + throw SysError(utfTo<std::wstring>(output)); } catch (const SysError& e) { throw FileError(_("Unable to shut down the system."), e.toString()); } diff --git a/zen/socket.h b/zen/socket.h index 62386801..2dd1ff4c 100644 --- a/zen/socket.h +++ b/zen/socket.h @@ -94,9 +94,9 @@ size_t tryReadSocket(SocketType socket, void* buffer, size_t bytesToRead) //thro int bytesReceived = 0; for (;;) { - bytesReceived = ::recv(socket, //_In_ SOCKET s, - static_cast<char*>(buffer), //_Out_ char *buf, - static_cast<int>(bytesToRead), //_In_ int len, + bytesReceived = ::recv(socket, //_In_ SOCKET s + static_cast<char*>(buffer), //_Out_ char* buf + static_cast<int>(bytesToRead), //_In_ int len 0); //_In_ int flags if (bytesReceived >= 0 || errno != EINTR) break; @@ -119,9 +119,9 @@ size_t tryWriteSocket(SocketType socket, const void* buffer, size_t bytesToWrite int bytesWritten = 0; for (;;) { - bytesWritten = ::send(socket, //_In_ SOCKET s, - static_cast<const char*>(buffer), //_In_ const char *buf, - static_cast<int>(bytesToWrite), //_In_ int len, + bytesWritten = ::send(socket, //_In_ SOCKET s + static_cast<const char*>(buffer), //_In_ const char* buf + static_cast<int>(bytesToWrite), //_In_ int len 0); //_In_ int flags if (bytesWritten >= 0 || errno != EINTR) break; diff --git a/zen/string_tools.h b/zen/string_tools.h index 83d87244..883c45b8 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -70,12 +70,12 @@ enum class SplitOnEmpty }; template <class S, class T> std::vector<S> split(const S& str, const T& delimiter, SplitOnEmpty soe); -template <class S> S trimCpy(S str, bool fromLeft = true, bool fromRight = true); +template <class S> [[nodiscard]] S trimCpy(S str, bool fromLeft = true, bool fromRight = true); template <class S> void trim (S& str, bool fromLeft = true, bool fromRight = true); template <class S, class Function> void trim(S& str, bool fromLeft, bool fromRight, Function trimThisChar); -template <class S, class T, class U> void replace (S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true); -template <class S, class T, class U> S replaceCpy(S str, const T& oldTerm, const U& newTerm, bool replaceAll = true); +template <class S, class T, class U> [[nodiscard]] S replaceCpy(S str, const T& oldTerm, const U& newTerm, bool replaceAll = true); +template <class S, class T, class U> void replace (S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true); //high-performance conversion between numbers and strings template <class S, class Num> S numberTo(const Num& number); diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 59003284..42010fd2 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -48,7 +48,7 @@ zen::SymlinkRawContent getSymlinkRawContent_impl(const Zstring& linkPath) //thro const ssize_t bytesWritten = ::readlink(linkPath.c_str(), &buffer[0], BUFFER_SIZE); if (bytesWritten < 0) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), "readlink"); - if (bytesWritten >= static_cast<ssize_t>(BUFFER_SIZE)) //detect truncation, not an error for readlink! + if (bytesWritten >= static_cast<ssize_t>(BUFFER_SIZE)) //detect truncation; not an error for readlink! throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), formatSystemError("readlink", L"", L"Buffer truncated.")); return {Zstring(&buffer[0], bytesWritten)}; //readlink does not append 0-termination! diff --git a/zen/sys_error.h b/zen/sys_error.h index f4b867be..99cf9316 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -43,7 +43,7 @@ private: #define THROW_LAST_SYS_ERROR(functionName) \ - do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false) + do { const ErrorCode ecInternal = getLastError(); throw zen::SysError(formatSystemError(functionName, ecInternal)); } while (false) /* Example: ASSERT_SYSERROR(expr); diff --git a/zen/sys_info.cpp b/zen/sys_info.cpp index 70658a68..f6045f7e 100644 --- a/zen/sys_info.cpp +++ b/zen/sys_info.cpp @@ -8,14 +8,15 @@ #include "crc.h" #include "file_access.h" #include "sys_version.h" +#include "symlink_target.h" - #include "symlink_target.h" #include "file_io.h" #include <ifaddrs.h> #include <net/if.h> //IFF_LOOPBACK #include <netpacket/packet.h> //sockaddr_ll + #include "process_exec.h" #include <unistd.h> //getuid() #include <pwd.h> //getpwuid_r() @@ -24,7 +25,7 @@ using namespace zen; std::wstring zen::getUserName() //throw FileError { - const uid_t userIdNo = ::getuid(); //never fails + const uid_t userIdNo = ::getuid(); //"real user ID"; never fails std::vector<char> buffer(std::max<long>(10000, ::sysconf(_SC_GETPW_R_SIZE_MAX))); //::sysconf may return long(-1) struct passwd buffer2 = {}; @@ -110,26 +111,38 @@ std::wstring zen::getOsDescription() //throw FileError -Zstring zen::getDesktopPath() //throw FileError +Zstring zen::getRealProcessPath() //throw FileError { - try - { - const char* path = ::getenv("HOME"); //no extended error reporting - if (!path) - throw SysError(L"Cannot find HOME environment variable."); - - return appendSeparator(path) + "Desktop"; - } - catch (const SysError& e) - { - throw FileError(_("Cannot get process information."), e.toString() ); - } + return getSymlinkRawContent("/proc/self/exe").targetPath; //throw FileError + //path does not contain symlinks => no need for ::realpath() } -Zstring zen::getProcessPath() //throw FileError +Zstring zen::getUserDownloadsPath() //throw FileError { - return getSymlinkRawContent("/proc/self/exe").targetPath; //throw FileError + try + { + Zstring cmdLine; + if (getuid() == 0) //nofail; root(0) => consider as request for elevation, NOT impersonation + { + const char* loginUser = getlogin(); //https://linux.die.net/man/3/getlogin + if (!loginUser) + THROW_LAST_SYS_ERROR("getlogin"); + + cmdLine = Zstring("sudo -u ") + loginUser + " xdg-user-dir DOWNLOAD"; //sudo better be installed :> + } + else + cmdLine = "xdg-user-dir DOWNLOAD"; + + const auto& [exitCode, output] = consoleExecute(cmdLine, std::nullopt /*timeoutMs*/); //throw SysError + if (exitCode != 0) + throw SysError(formatSystemError(cmdLine.c_str(), + replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), utfTo<std::wstring>(output))); + const Zstring& downloadsPath = trimCpy(output); + ASSERT_SYSERROR(!downloadsPath.empty()); + return downloadsPath; + } + catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); } } diff --git a/zen/sys_info.h b/zen/sys_info.h index 9cd328bb..1b046fb6 100644 --- a/zen/sys_info.h +++ b/zen/sys_info.h @@ -28,8 +28,9 @@ ComputerModel getComputerModel(); //throw FileError std::wstring getOsDescription(); //throw FileError -Zstring getDesktopPath(); //throw FileError -Zstring getProcessPath(); //throw FileError +Zstring getRealProcessPath(); //throw FileError + +Zstring getUserDownloadsPath(); //throw FileError } diff --git a/zen/sys_version.cpp b/zen/sys_version.cpp index d07bbc33..f7e4ffc8 100644 --- a/zen/sys_version.cpp +++ b/zen/sys_version.cpp @@ -7,7 +7,7 @@ #include "sys_version.h" #include <iostream> #include "file_io.h" - #include "shell_execute.h" + #include "process_exec.h" using namespace zen; @@ -25,15 +25,17 @@ OsVersionDetail zen::getOsVersionDetail() //throw SysError { if (const auto [exitCode, output] = consoleExecute("lsb_release --id -s", std::nullopt); //throw SysError exitCode != 0) - throw SysError(formatSystemError("lsb_release --id", replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output)); + throw SysError(formatSystemError("lsb_release --id", + replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), utfTo<std::wstring>(output))); else - osName = trimCpy(output); + osName = utfTo<std::wstring>(trimCpy(output)); if (const auto [exitCode, output] = consoleExecute("lsb_release --release -s", std::nullopt); //throw SysError exitCode != 0) - throw SysError(formatSystemError("lsb_release --release", replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), output)); + throw SysError(formatSystemError("lsb_release --release", + replaceCpy(_("Exit code %x"), L"%x", numberTo<std::wstring>(exitCode)), utfTo<std::wstring>(output))); else - osVersion = trimCpy(output); + osVersion = utfTo<std::wstring>(trimCpy(output)); } //lsb_release not available on some systems: https://freefilesync.org/forum/viewtopic.php?t=7191 catch (SysError&) // => fall back to /etc/os-release: https://www.freedesktop.org/software/systemd/man/os-release.html diff --git a/zen/zstring.h b/zen/zstring.h index 234a398d..de90c324 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -140,12 +140,18 @@ const wchar_t EM_DASH = L'\u2014'; const wchar_t EN_DASH = L'\u2013'; const wchar_t* const SPACED_DASH = L" \u2013 "; //using 'EN DASH' const wchar_t LTR_MARK = L'\u200E'; //UTF-8: E2 80 8E -const wchar_t RTL_MARK = L'\u200F'; //UTF-8: E2 80 8F const wchar_t* const ELLIPSIS = L"\u2026"; //"..." const wchar_t MULT_SIGN = L'\u00D7'; //fancy "x" //const wchar_t NOBREAK_SPACE = L'\u00A0'; const wchar_t ZERO_WIDTH_SPACE = L'\u200B'; +const wchar_t RTL_MARK = L'\u200F'; //UTF-8: E2 80 8F https://www.w3.org/International/questions/qa-bidi-unicode-controls +const wchar_t BIDI_DIR_ISOLATE_RTL = L'\u2067'; //UTF-8: E2 81 A7 => not working on Win 10 +const wchar_t BIDI_POP_DIR_ISOLATE = L'\u2069'; //UTF-8: E2 81 A9 => not working on Win 10 +const wchar_t BIDI_DIR_EMBEDDING_RTL = L'\u202B'; //UTF-8: E2 80 AB => not working on Win 10 +const wchar_t BIDI_POP_DIR_FORMATTING = L'\u202C'; //UTF-8: E2 80 AC => not working on Win 10 + + //--------------------------------------------------------------------------- |