summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
authorB Stack <bgstack15@gmail.com>2021-02-02 11:44:31 -0500
committerB Stack <bgstack15@gmail.com>2021-02-02 11:44:31 -0500
commitd299ddd2f27a437f0fc0cb49abdfd6dd8e3d94f8 (patch)
tree4d7c950512836f473a6a8cbb521c61e800db6584 /zen
parentMerge branch '11.5' into 'master' (diff)
downloadFreeFileSync-d299ddd2f27a437f0fc0cb49abdfd6dd8e3d94f8.tar.gz
FreeFileSync-d299ddd2f27a437f0fc0cb49abdfd6dd8e3d94f8.tar.bz2
FreeFileSync-d299ddd2f27a437f0fc0cb49abdfd6dd8e3d94f8.zip
add upstream 11.6
Diffstat (limited to 'zen')
-rw-r--r--zen/basic_math.h74
-rw-r--r--zen/dir_watcher.cpp2
-rw-r--r--zen/file_access.cpp28
-rw-r--r--zen/file_io.cpp4
-rw-r--r--zen/file_io.h2
-rw-r--r--zen/format_unit.cpp24
-rw-r--r--zen/json.h9
-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.cpp293
-rw-r--r--zen/resolve_path.h34
-rw-r--r--zen/shutdown.cpp16
-rw-r--r--zen/socket.h12
-rw-r--r--zen/string_tools.h6
-rw-r--r--zen/symlink_target.h2
-rw-r--r--zen/sys_error.h2
-rw-r--r--zen/sys_info.cpp47
-rw-r--r--zen/sys_info.h5
-rw-r--r--zen/sys_version.cpp12
-rw-r--r--zen/zstring.h8
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);
diff --git a/zen/json.h b/zen/json.h
index 82e5e271..a3740664 100644
--- a/zen/json.h
+++ b/zen/json.h
@@ -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
+
+
//---------------------------------------------------------------------------
bgstack15