summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
authorB Stack <bgstack15@gmail.com>2020-04-18 12:59:51 -0400
committerB Stack <bgstack15@gmail.com>2020-04-18 12:59:51 -0400
commitfc8cd27e4c0c8a48ebc151f73639a573e9e5c7f0 (patch)
tree8cfcea5441be72ad92095a3887ded84d38f9ba11 /zen
parentMerge branch '10.22' into 'master' (diff)
downloadFreeFileSync-fc8cd27e4c0c8a48ebc151f73639a573e9e5c7f0.tar.gz
FreeFileSync-fc8cd27e4c0c8a48ebc151f73639a573e9e5c7f0.tar.bz2
FreeFileSync-fc8cd27e4c0c8a48ebc151f73639a573e9e5c7f0.zip
add upstream 10.23
Diffstat (limited to 'zen')
-rw-r--r--zen/basic_math.h2
-rw-r--r--zen/dir_watcher.cpp24
-rw-r--r--zen/dir_watcher.h25
-rw-r--r--zen/file_access.cpp66
-rw-r--r--zen/file_io.cpp18
-rw-r--r--zen/file_traverser.cpp8
-rw-r--r--zen/guid.h6
-rw-r--r--zen/http.cpp18
-rw-r--r--zen/http.h2
-rw-r--r--zen/open_ssl.cpp193
-rw-r--r--zen/perf.h2
-rw-r--r--zen/process_priority.cpp4
-rw-r--r--zen/recycler.cpp13
-rw-r--r--zen/shell_execute.cpp250
-rw-r--r--zen/shell_execute.h99
-rw-r--r--zen/shutdown.cpp28
-rw-r--r--zen/socket.h20
-rw-r--r--zen/string_base.h2
-rw-r--r--zen/string_tools.h15
-rw-r--r--zen/symlink_target.h6
-rw-r--r--zen/sys_error.cpp23
-rw-r--r--zen/sys_error.h4
-rw-r--r--zen/system.cpp21
-rw-r--r--zen/time.h2
-rw-r--r--zen/zlib_wrap.cpp8
-rw-r--r--zen/zlib_wrap.h2
-rw-r--r--zen/zstring.cpp2
-rw-r--r--zen/zstring.h2
28 files changed, 537 insertions, 328 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h
index 26dda9a6..0a226555 100644
--- a/zen/basic_math.h
+++ b/zen/basic_math.h
@@ -271,7 +271,7 @@ template <class InputIterator> inline
double stdDeviation(InputIterator first, InputIterator last, double* arithMean)
{
//implementation minimizing rounding errors, see: https://en.wikipedia.org/wiki/Standard_deviation
- //combined with technique avoiding overflow, see: http://www.netlib.org/blas/dnrm2.f -> only 10% performance degradation
+ //combined with technique avoiding overflow, see: https://www.netlib.org/blas/dnrm2.f -> only 10% performance degradation
size_t n = 0;
double mean = 0;
diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp
index 307b48e5..d02e229e 100644
--- a/zen/dir_watcher.cpp
+++ b/zen/dir_watcher.cpp
@@ -52,7 +52,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
//init
pimpl_->notifDescr = ::inotify_init();
if (pimpl_->notifDescr == -1)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), L"inotify_init");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "inotify_init");
ZEN_ON_SCOPE_FAIL( ::close(pimpl_->notifDescr); );
@@ -61,10 +61,10 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
{
int flags = ::fcntl(pimpl_->notifDescr, F_GETFL);
if (flags != -1)
- initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1;
+ initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) == 0;
}
if (!initSuccess)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), L"fcntl");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "fcntl");
//add watches
for (const Zstring& subDirPath : fullFolderList)
@@ -85,10 +85,10 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError
const ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
if (ec == ENOSPC) //fix misleading system message "No space left on device"
throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)),
- formatSystemError(L"inotify_add_watch", L"ENOSPC",
+ formatSystemError("inotify_add_watch", L"ENOSPC",
L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource."));
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)), formatSystemError(L"inotify_add_watch", ec));
+ throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)), formatSystemError("inotify_add_watch", ec));
}
pimpl_->watchedPaths.emplace(wd, subDirPath);
@@ -102,7 +102,7 @@ DirWatcher::~DirWatcher()
}
-std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>& requestUiUpdate, std::chrono::milliseconds cbInterval) //throw FileError
+std::vector<DirWatcher::Change> DirWatcher::fetchChanges(const std::function<void()>& requestUiUpdate, std::chrono::milliseconds cbInterval) //throw FileError
{
std::vector<std::byte> buffer(512 * (sizeof(struct ::inotify_event) + NAME_MAX + 1));
@@ -117,12 +117,12 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()
if (bytesRead < 0)
{
if (errno == EAGAIN) //this error is ignored in all inotify wrappers I found
- return std::vector<Entry>();
+ return std::vector<Change>();
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), L"read");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath_)), "read");
}
- std::vector<Entry> output;
+ std::vector<Change> output;
ssize_t bytePos = 0;
while (bytePos < bytesRead)
@@ -140,15 +140,15 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()
if ((evt.mask & IN_CREATE) ||
(evt.mask & IN_MOVED_TO))
- output.push_back({ ACTION_CREATE, itemPath });
+ output.push_back({ ChangeType::create, itemPath });
else if ((evt.mask & IN_MODIFY) ||
(evt.mask & IN_CLOSE_WRITE))
- output.push_back({ ACTION_UPDATE, itemPath });
+ output.push_back({ ChangeType::update, itemPath });
else if ((evt.mask & IN_DELETE ) ||
(evt.mask & IN_DELETE_SELF) ||
(evt.mask & IN_MOVE_SELF ) ||
(evt.mask & IN_MOVED_FROM))
- output.push_back({ ACTION_DELETE, itemPath });
+ output.push_back({ ChangeType::remove, itemPath });
}
}
bytePos += sizeof(struct ::inotify_event) + evt.len;
diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h
index 4d514e89..3103039d 100644
--- a/zen/dir_watcher.h
+++ b/zen/dir_watcher.h
@@ -16,23 +16,23 @@
namespace zen
{
-//Windows: ReadDirectoryChangesW https://msdn.microsoft.com/en-us/library/aa365465
+//Windows: ReadDirectoryChangesW https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
//Linux: inotify https://linux.die.net/man/7/inotify
-//OS X: kqueue https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/kqueue.2.html
+//macOS: kqueue https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/kqueue.2.html
//watch directory including subdirectories
/*
!Note handling of directories!:
Windows: removal of top watched directory is NOT notified when watching the dir handle, e.g. brute force usb stick removal,
(watchting for GUID_DEVINTERFACE_WPD OTOH works fine!)
- however manual unmount IS notified (e.g. usb stick removal, then re-insert), but watching is stopped!
+ however manual unmount IS notified (e.g. USB stick removal, then re-insert), but watching is stopped!
Renaming of top watched directory handled incorrectly: Not notified(!) + additional changes in subfolders
now do report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!)
Linux: newly added subdirectories are reported but not automatically added for watching! -> reset Dirwatcher!
- removal of top watched directory is NOT notified!
+ removal of base directory is NOT notified!
- OS X: everything works as expected; renaming of top level folder is also detected
+ macOS: everything works as expected; renaming of base directory is also detected
Overcome all issues portably: check existence of top watched directory externally + reinstall watch after changes in directory structure (added directories) are detected
*/
@@ -42,21 +42,22 @@ public:
DirWatcher(const Zstring& dirPath); //throw FileError
~DirWatcher();
- enum ActionType
+ enum class ChangeType
{
- ACTION_CREATE, //informal!
- ACTION_UPDATE, //use for debugging/logging only!
- ACTION_DELETE, //
+ create, //informal!
+ update, //use for debugging/logging only!
+ remove, //
+ baseFolderUnavailable, //1. not existing or 2. can't access
};
- struct Entry
+ struct Change
{
- ActionType action = ACTION_CREATE;
+ ChangeType type = ChangeType::create;
Zstring itemPath;
};
//extract accumulated changes since last call
- std::vector<Entry> getChanges(const std::function<void()>& requestUiUpdate, std::chrono::milliseconds cbInterval); //throw FileError
+ std::vector<Change> fetchChanges(const std::function<void()>& requestUiUpdate, std::chrono::milliseconds cbInterval); //throw FileError
private:
DirWatcher (const DirWatcher&) = delete;
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index 4f6704d2..cb5a45ed 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -98,7 +98,7 @@ ItemType zen::getItemType(const Zstring& itemPath) //throw FileError
{
struct ::stat itemInfo = {};
if (::lstat(itemPath.c_str(), &itemInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "lstat");
if (S_ISLNK(itemInfo.st_mode))
return ItemType::SYMLINK;
@@ -174,7 +174,7 @@ uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0
{
struct ::statfs info = {};
if (::statfs(path.c_str(), &info) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), L"statfs");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), "statfs");
return static_cast<uint64_t>(info.f_bsize) * info.f_bavail;
}
@@ -184,7 +184,7 @@ VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError
{
struct ::stat fileInfo = {};
if (::stat(itemPath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"stat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "stat");
return fileInfo.st_dev;
}
@@ -194,7 +194,7 @@ 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)), L"stat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), "stat");
return fileInfo.st_size;
}
@@ -211,7 +211,7 @@ Zstring zen::getTempFolderPath() //throw FileError
void zen::removeFilePlain(const Zstring& filePath) //throw FileError
{
- const wchar_t functionName[] = L"unlink";
+ const char* functionName = "unlink";
if (::unlink(filePath.c_str()) != 0)
{
ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
@@ -231,7 +231,7 @@ void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError
void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError
{
- const wchar_t functionName[] = L"rmdir";
+ const char* functionName = "rmdir";
if (::rmdir(dirPath.c_str()) != 0)
{
ErrorCode ec = getLastError(); //copy before making other system calls!
@@ -241,7 +241,7 @@ void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError
if (symlinkExists)
{
if (::unlink(dirPath.c_str()) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), L"unlink");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), "unlink");
return;
}
throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(functionName, ec));
@@ -312,7 +312,7 @@ void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool r
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(L"rename", ec);
+ const std::wstring errorDescr = formatSystemError("rename", ec);
if (ec == EXDEV)
throw ErrorMoveUnsupported(errorMsg, errorDescr);
@@ -329,7 +329,7 @@ void moveAndRenameFileSub(const Zstring& pathFrom, const Zstring& pathTo, bool r
{
struct ::stat infoSrc = {};
if (::lstat(pathFrom.c_str(), &infoSrc) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(pathFrom)), L"stat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(pathFrom)), "stat");
struct ::stat infoTrg = {};
if (::lstat(pathTo.c_str(), &infoTrg) == 0)
@@ -394,16 +394,16 @@ void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTim
//in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: https://freefilesync.org/forum/viewtopic.php?t=387
const int fdFile = ::open(itemPath.c_str(), O_WRONLY | O_APPEND | O_CLOEXEC); //2017-07-04: O_WRONLY | O_APPEND seems to avoid EOPNOTSUPP on gvfs SFTP!
if (fdFile == -1)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"open");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), "open");
ZEN_ON_SCOPE_EXIT(::close(fdFile));
if (::futimens(fdFile, newTimes) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"futimens");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), "futimens");
}
else
{
if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"utimensat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), "utimensat");
}
}
@@ -442,7 +442,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli
errno == EOPNOTSUPP) //extended attributes are not supported by the filesystem
return;
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtPath(source)), L"getfilecon");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtPath(source)), "getfilecon");
}
ZEN_ON_SCOPE_EXIT(::freecon(contextSource));
@@ -470,7 +470,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli
::setfilecon(target.c_str(), contextSource) :
::lsetfilecon(target.c_str(), contextSource);
if (rv3 < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtPath(target)), L"setfilecon");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtPath(target)), "setfilecon");
}
#endif
}
@@ -488,26 +488,26 @@ void zen::copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPa
if (procSl == ProcSymlink::FOLLOW)
{
if (::stat(sourcePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"stat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), "stat");
if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chown");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), "chown");
if (::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), "chmod");
}
else
{
if (::lstat(sourcePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"lstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), "lstat");
if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), "lchown");
const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
if (!isSymlinkTarget && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), "chmod");
}
}
@@ -515,19 +515,25 @@ void zen::copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPa
void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTargetExisting
{
+ auto getErrorMsg = [&] { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)); };
+
+ //deliberately don't support creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/
+ if (endsWith(dirPath, Zstr(' ')) ||
+ endsWith(dirPath, Zstr('.')))
+ throw FileError(getErrorMsg(), replaceCpy<std::wstring>(L"Invalid trailing character \"%x\".", L"%x", utfTo<std::wstring>(dirPath.end()[-1])));
+
const mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777, default for newly created directories
if (::mkdir(dirPath.c_str(), mode) != 0)
{
const int lastError = errno; //copy before directly or indirectly making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath));
- const std::wstring errorDescr = formatSystemError(L"mkdir", lastError);
+ const std::wstring errorDescr = formatSystemError("mkdir", lastError);
if (lastError == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
+ throw ErrorTargetExisting(getErrorMsg(), errorDescr);
//else if (lastError == ENOENT)
// throw ErrorTargetPathMissing(errorMsg, errorDescr);
- throw FileError(errorMsg, errorDescr);
+ throw FileError(getErrorMsg(), errorDescr);
}
}
@@ -575,7 +581,7 @@ void zen::copySymlink(const Zstring& sourcePath, const Zstring& targetPath, bool
const Zstring linkPath = getSymlinkTargetRaw(sourcePath); //throw FileError; accept broken symlinks
if (::symlink(linkPath.c_str(), targetPath.c_str()) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L'\n' + fmtPath(sourcePath)), L"%y", L'\n' + fmtPath(targetPath)), L"symlink");
+ THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L'\n' + fmtPath(sourcePath)), L"%y", L'\n' + fmtPath(targetPath)), "symlink");
//allow only consistent objects to be created -> don't place before ::symlink(); targetPath may already exist!
ZEN_ON_SCOPE_FAIL(try { removeSymlinkPlain(targetPath); /*throw FileError*/ }
@@ -584,7 +590,7 @@ void zen::copySymlink(const Zstring& sourcePath, const Zstring& targetPath, bool
//file times: essential for syncing a symlink: enforce this! (don't just try!)
struct ::stat sourceInfo = {};
if (::lstat(sourcePath.c_str(), &sourceInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourcePath)), L"lstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourcePath)), "lstat");
setWriteTimeNative(targetPath, sourceInfo.st_mtim, ProcSymlink::DIRECT); //throw FileError
@@ -605,7 +611,7 @@ FileCopyResult copyFileOsSpecific(const Zstring& sourceFile, //throw FileError,
struct ::stat sourceInfo = {};
if (::fstat(fileIn.getHandle(), &sourceInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"fstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), "fstat");
const mode_t mode = sourceInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
//it seems we don't need S_IWUSR, not even for the setFileTime() below! (tested with source file having different user/group!)
@@ -616,7 +622,7 @@ FileCopyResult copyFileOsSpecific(const Zstring& sourceFile, //throw FileError,
{
const int ec = errno; //copy before making other system calls!
const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile));
- const std::wstring errorDescr = formatSystemError(L"open", ec);
+ const std::wstring errorDescr = formatSystemError("open", ec);
if (ec == EEXIST)
throw ErrorTargetExisting(errorMsg, errorDescr);
@@ -639,7 +645,7 @@ FileCopyResult copyFileOsSpecific(const Zstring& sourceFile, //throw FileError,
struct ::stat targetInfo = {};
if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"fstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), "fstat");
//close output file handle before setting file time; also good place to catch errors when closing stream!
fileOut.finalize(); //throw FileError, (X) essentially a close() since buffers were already flushed
@@ -649,7 +655,7 @@ FileCopyResult copyFileOsSpecific(const Zstring& sourceFile, //throw FileError,
{
//we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
//this triggers bugs on samba shares where the modification time is set to current time instead.
- //Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
+ //Linux: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
// http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
//OS X: https://freefilesync.org/forum/viewtopic.php?t=356
setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::FOLLOW); //throw FileError
diff --git a/zen/file_io.cpp b/zen/file_io.cpp
index b78259e0..942f367f 100644
--- a/zen/file_io.cpp
+++ b/zen/file_io.cpp
@@ -37,7 +37,7 @@ void FileBase::close() //throw FileError
//no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional!
if (::close(fileHandle_) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"close");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "close");
}
//----------------------------------------------------------------------------------------------------
@@ -73,10 +73,10 @@ FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileErro
}
//else: let ::open() fail for errors like "not existing"
- //don't use O_DIRECT: http://yarchive.net/comp/linux/o_direct.html
+ //don't use O_DIRECT: https://yarchive.net/comp/linux/o_direct.html
const FileBase::FileHandle fileHandle = ::open(filePath.c_str(), O_RDONLY | O_CLOEXEC);
if (fileHandle == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), L"open");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), "open");
return fileHandle; //pass ownership
}
}
@@ -92,7 +92,7 @@ FileInput::FileInput(const Zstring& filePath, const IOCallback& notifyUnbuffered
{
//optimize read-ahead on input file:
if (::posix_fadvise(getHandle(), 0, 0, POSIX_FADV_SEQUENTIAL) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), L"posix_fadvise");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), "posix_fadvise");
}
@@ -113,9 +113,9 @@ size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError, E
//read() on macOS: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/read.2.html
if (bytesRead < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"read");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), "read");
if (static_cast<size_t>(bytesRead) > bytesToRead) //better safe than sorry
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"ReadFile: buffer overflow."); //user should never see this
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), formatSystemError("ReadFile", L"", L"Buffer overflow."));
//if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead"
@@ -180,7 +180,7 @@ FileBase::FileHandle openHandleForWrite(const Zstring& filePath, FileOutput::Acc
{
const int ec = errno; //copy before making other system calls!
const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath));
- const std::wstring errorDescr = formatSystemError(L"open", ec);
+ const std::wstring errorDescr = formatSystemError("open", ec);
if (ec == EEXIST)
throw ErrorTargetExisting(errorMsg, errorDescr);
@@ -231,10 +231,10 @@ size_t FileOutput::tryWrite(const void* buffer, size_t bytesToWrite) //throw Fil
if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers
errno = ENOSPC;
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "write");
}
if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write: buffer overflow."); //user should never see this
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), formatSystemError("write", L"", L"Buffer overflow."));
//if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"!
return bytesWritten;
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index 2d2a0cce..48185516 100644
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -26,7 +26,7 @@ void zen::traverseFolder(const Zstring& dirPath,
{
DIR* folder = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/"
if (!folder)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"opendir");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), "opendir");
ZEN_ON_SCOPE_EXIT(::closedir(folder)); //never close nullptr handles! -> crash
for (;;)
@@ -38,7 +38,7 @@ void zen::traverseFolder(const Zstring& dirPath,
if (errno == 0) //errno left unchanged => no more items
return;
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), "readdir");
//don't retry but restart dir traversal on error! https://devblogs.microsoft.com/oldnewthing/20140612-00/?p=753/
}
@@ -51,7 +51,7 @@ void zen::traverseFolder(const Zstring& dirPath,
const Zstring& itemName = itemNameRaw;
if (itemName.empty()) //checks result of normalizeUtfForPosix, too!
- throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"readdir: Data corruption; item with empty name.");
+ throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), formatSystemError("readdir", L"", L"Data corruption; item with empty name."));
const Zstring& itemPath = appendSeparator(dirPath) + itemName;
@@ -59,7 +59,7 @@ void zen::traverseFolder(const Zstring& dirPath,
try
{
if (::lstat(itemPath.c_str(), &statData) != 0) //lstat() does not resolve symlinks
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), "lstat");
}
catch (const FileError& e)
{
diff --git a/zen/guid.h b/zen/guid.h
index 88059be8..f4af4880 100644
--- a/zen/guid.h
+++ b/zen/guid.h
@@ -27,7 +27,7 @@ std::string generateGUID() //creates a 16-byte GUID
#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25) //getentropy() requires glibc 2.25 (ldd --version) PS: CentOS 7 is on 2.17
if (::getentropy(&guid[0], guid.size()) != 0) //"The maximum permitted value for the length argument is 256"
throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" +
- utfTo<std::string>(formatSystemError(L"getentropy", errno)));
+ utfTo<std::string>(formatSystemError("getentropy", errno)));
#else
class RandomGeneratorPosix
{
@@ -36,7 +36,7 @@ std::string generateGUID() //creates a 16-byte GUID
{
if (fd_ == -1)
throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" +
- utfTo<std::string>(formatSystemError(L"open", errno)));
+ utfTo<std::string>(formatSystemError("open", errno)));
}
~RandomGeneratorPosix() { ::close(fd_); }
@@ -48,7 +48,7 @@ std::string generateGUID() //creates a 16-byte GUID
const ssize_t bytesRead = ::read(fd_, static_cast<char*>(buf) + offset, size - offset);
if (bytesRead < 1) //0 means EOF => error in this context (should check for buffer overflow, too?)
throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to generate GUID." + "\n\n" +
- utfTo<std::string>(formatSystemError(L"read", bytesRead < 0 ? errno : EIO)));
+ utfTo<std::string>(formatSystemError("read", bytesRead < 0 ? errno : EIO)));
offset += bytesRead;
assert(offset <= size);
}
diff --git a/zen/http.cpp b/zen/http.cpp
index c6a390de..848b2cb3 100644
--- a/zen/http.cpp
+++ b/zen/http.cpp
@@ -59,7 +59,7 @@ public:
else //HTTP default port: 80, see %WINDIR%\system32\drivers\etc\services
socket_ = std::make_unique<Socket>(server, Zstr("http")); //throw SysError
- //we don't support "chunked and gzip transfer encoding" => HTTP 1.0
+ //we don't support "chunked and gzip transfer encoding" => HTTP 1.0 => no "Content-Length" support!
headers["Host" ] = utfTo<std::string>(server); //only required for HTTP/1.1 but a few servers expect it even for HTTP/1.0
headers["User-Agent"] = utfTo<std::string>(userAgent);
headers["Accept" ] = "*/*"; //won't hurt?
@@ -194,7 +194,8 @@ private:
contentRemaining_ -= bytesReceived;
if (bytesReceived == 0 && contentRemaining_ > 0)
- throw SysError(replaceCpy<std::wstring>(L"HttpInputStream::tryRead: incomplete server response; %x more bytes expected.", L"%x", numberTo<std::wstring>(contentRemaining_)));
+ throw SysError(formatSystemError("HttpInputStream::tryRead", L"", L"Incomplete server response: " +
+ numberTo<std::wstring>(contentRemaining_) + L" more bytes expected."));
return bytesReceived; //"zero indicates end of file"
}
@@ -259,8 +260,8 @@ std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const Zstring& url,
}
else
{
- if (httpStatus != 200) //HTTP_STATUS_OK(200)
- throw SysError(formatHttpStatus(httpStatus)); //e.g. HTTP_STATUS_NOT_FOUND(404)
+ if (httpStatus != 200) //HTTP_STATUS_OK
+ throw SysError(formatHttpError(httpStatus)); //e.g. "HTTP status 404: Not found."
return response;
}
@@ -380,9 +381,9 @@ bool zen::internetIsAlive() //noexcept
}
-std::wstring zen::formatHttpStatus(int sc)
+std::wstring zen::formatHttpError(int sc)
{
- const wchar_t* statusText = [&] //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+ const wchar_t* statusDescr = [&] //https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
{
switch (sc)
{
@@ -455,10 +456,7 @@ std::wstring zen::formatHttpStatus(int sc)
}
}();
- if (strLength(statusText) == 0)
- return trimCpy(replaceCpy<std::wstring>(L"HTTP status %x.", L"%x", numberTo<std::wstring>(sc)));
- else
- return trimCpy(replaceCpy<std::wstring>(L"HTTP status %x: ", L"%x", numberTo<std::wstring>(sc)) + statusText);
+ return formatSystemError("", L"HTTP status " + numberTo<std::wstring>(sc), statusDescr);
}
diff --git a/zen/http.h b/zen/http.h
index 09395f8f..6cb107bf 100644
--- a/zen/http.h
+++ b/zen/http.h
@@ -54,7 +54,7 @@ HttpInputStream sendHttpPost(const Zstring& url,
const IOCallback& notifyUnbufferedIO /*throw X*/); //throw SysError, X
bool internetIsAlive(); //noexcept
-std::wstring formatHttpStatus(int httpStatus);
+std::wstring formatHttpError(int httpStatus);
bool isValidEmail(const std::string& email);
std::string htmlSpecialChars(const std::string_view& str);
diff --git a/zen/open_ssl.cpp b/zen/open_ssl.cpp
index 0f1da3fc..1feeb1a9 100644
--- a/zen/open_ssl.cpp
+++ b/zen/open_ssl.cpp
@@ -18,7 +18,7 @@ using namespace zen;
#error FFS, we are royally screwed!
#endif
-static_assert(OPENSSL_VERSION_NUMBER >= 0x1010105fL, "OpenSSL version too old");
+static_assert(OPENSSL_VERSION_NUMBER >= 0x10100000L, "OpenSSL version too old");
void zen::openSslInit()
@@ -57,16 +57,16 @@ thread_local OpenSslThreadCleanUp tearDownOpenSslThreadData;
openssl dgst -sha256 -verify public.pem -signature file.sig file.txt */
-std::wstring formatOpenSSLError(const std::wstring& functionName, unsigned long ec)
+std::wstring formatOpenSSLError(const char* functionName, unsigned long ec)
{
char errorBuf[256] = {}; //== buffer size used by ERR_error_string(); err.c: it seems the message uses at most ~200 bytes
::ERR_error_string_n(ec, errorBuf, sizeof(errorBuf)); //includes null-termination
- return formatSystemError(functionName, replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(ec)), utfTo<std::wstring>(errorBuf));
+ return formatSystemError(functionName, replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(ec)), utfTo<std::wstring>(errorBuf));
}
-std::wstring formatLastOpenSSLError(const std::wstring& functionName)
+std::wstring formatLastOpenSSLError(const char* functionName)
{
const auto ec = ::ERR_peek_last_error();
::ERR_clear_error(); //clean up for next OpenSSL operation on this thread
@@ -80,19 +80,19 @@ std::shared_ptr<EVP_PKEY> generateRsaKeyPair(int bits) //throw SysError
EVP_PKEY_CTX* keyCtx = ::EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, //int id,
nullptr); //ENGINE* e
if (!keyCtx)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_CTX_new_id"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_CTX_new_id"));
ZEN_ON_SCOPE_EXIT(::EVP_PKEY_CTX_free(keyCtx));
if (::EVP_PKEY_keygen_init(keyCtx) != 1)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_keygen_init"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_keygen_init"));
//"RSA keys set the key length during key generation rather than parameter generation"
if (::EVP_PKEY_CTX_set_rsa_keygen_bits(keyCtx, bits) <= 0) //"[...] return a positive value for success" => effectively returns "1"
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_CTX_set_rsa_keygen_bits"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_CTX_set_rsa_keygen_bits"));
EVP_PKEY* keyPair = nullptr;
if (::EVP_PKEY_keygen(keyCtx, &keyPair) != 1)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_keygen"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_keygen"));
return std::shared_ptr<EVP_PKEY>(keyPair, ::EVP_PKEY_free);
}
@@ -101,11 +101,11 @@ std::shared_ptr<EVP_PKEY> generateRsaKeyPair(int bits) //throw SysError
using BioToEvpFunc = EVP_PKEY* (*)(BIO* bp, EVP_PKEY** x, pem_password_cb* cb, void* u);
-std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToEvpFunc bioToEvp, const wchar_t* functionName) //throw SysError
+std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToEvpFunc bioToEvp, const char* functionName) //throw SysError
{
BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size()));
if (!bio)
- throw SysError(formatLastOpenSSLError(L"BIO_new_mem_buf"));
+ throw SysError(formatLastOpenSSLError("BIO_new_mem_buf"));
ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio));
if (EVP_PKEY* evp = bioToEvp(bio, //BIO* bp,
@@ -119,11 +119,11 @@ std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToEvpF
using BioToRsaFunc = RSA* (*)(BIO* bp, RSA** x, pem_password_cb* cb, void* u);
-std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToRsaFunc bioToRsa, const wchar_t* functionName) //throw SysError
+std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToRsaFunc bioToRsa, const char* functionName) //throw SysError
{
BIO* bio = ::BIO_new_mem_buf(keyStream.c_str(), static_cast<int>(keyStream.size()));
if (!bio)
- throw SysError(formatLastOpenSSLError(L"BIO_new_mem_buf"));
+ throw SysError(formatLastOpenSSLError("BIO_new_mem_buf"));
ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio));
RSA* rsa = bioToRsa(bio, //BIO* bp,
@@ -136,11 +136,11 @@ std::shared_ptr<EVP_PKEY> streamToEvpKey(const std::string& keyStream, BioToRsaF
EVP_PKEY* evp = ::EVP_PKEY_new();
if (!evp)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_new"));
std::shared_ptr<EVP_PKEY> sharedKey(evp, ::EVP_PKEY_free);
if (::EVP_PKEY_set1_RSA(evp, rsa) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_set1_RSA"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_RSA"));
return sharedKey;
}
@@ -153,13 +153,13 @@ std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamTyp
{
case RsaStreamType::pkix:
return publicKey ?
- streamToEvpKey(keyStream, ::PEM_read_bio_PUBKEY, L"PEM_read_bio_PUBKEY") : //throw SysError
- streamToEvpKey(keyStream, ::PEM_read_bio_PrivateKey, L"PEM_read_bio_PrivateKey"); //
+ streamToEvpKey(keyStream, ::PEM_read_bio_PUBKEY, "PEM_read_bio_PUBKEY") : //throw SysError
+ streamToEvpKey(keyStream, ::PEM_read_bio_PrivateKey, "PEM_read_bio_PrivateKey"); //
case RsaStreamType::pkcs1:
return publicKey ?
- streamToEvpKey(keyStream, ::PEM_read_bio_RSAPublicKey, L"PEM_read_bio_RSAPublicKey") : //throw SysError
- streamToEvpKey(keyStream, ::PEM_read_bio_RSAPrivateKey, L"PEM_read_bio_RSAPrivateKey"); //
+ streamToEvpKey(keyStream, ::PEM_read_bio_RSAPublicKey, "PEM_read_bio_RSAPublicKey") : //throw SysError
+ streamToEvpKey(keyStream, ::PEM_read_bio_RSAPrivateKey, "PEM_read_bio_RSAPrivateKey"); //
case RsaStreamType::raw:
break;
@@ -171,7 +171,7 @@ std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamTyp
&tmp, /*changes tmp pointer itself!*/ //const unsigned char** pp,
static_cast<long>(keyStream.size())); //long length
if (!evp)
- throw SysError(formatLastOpenSSLError(publicKey ? L"d2i_PublicKey" : L"d2i_PrivateKey"));
+ throw SysError(formatLastOpenSSLError(publicKey ? "d2i_PublicKey" : "d2i_PrivateKey"));
return std::shared_ptr<EVP_PKEY>(evp, ::EVP_PKEY_free);
}
@@ -179,11 +179,11 @@ std::shared_ptr<EVP_PKEY> streamToKey(const std::string& keyStream, RsaStreamTyp
using EvpToBioFunc = int (*)(BIO* bio, EVP_PKEY* evp);
-std::string evpKeyToStream(EVP_PKEY* evp, EvpToBioFunc evpToBio, const wchar_t* functionName) //throw SysError
+std::string evpKeyToStream(EVP_PKEY* evp, EvpToBioFunc evpToBio, const char* functionName) //throw SysError
{
BIO* bio = ::BIO_new(BIO_s_mem());
if (!bio)
- throw SysError(formatLastOpenSSLError(L"BIO_new"));
+ throw SysError(formatLastOpenSSLError("BIO_new"));
ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio));
if (evpToBio(bio, evp) != 1)
@@ -191,44 +191,44 @@ std::string evpKeyToStream(EVP_PKEY* evp, EvpToBioFunc evpToBio, const wchar_t*
//---------------------------------------------
const int keyLen = BIO_pending(bio);
if (keyLen < 0)
- throw SysError(formatLastOpenSSLError(L"BIO_pending"));
+ throw SysError(formatLastOpenSSLError("BIO_pending"));
if (keyLen == 0)
- throw SysError(L"BIO_pending failed."); //no more error details
+ throw SysError(formatSystemError("BIO_pending", L"", L"Unexpected failure.")); //no more error details
std::string keyStream(keyLen, '\0');
if (::BIO_read(bio, &keyStream[0], keyLen) != keyLen)
- throw SysError(formatLastOpenSSLError(L"BIO_read"));
+ throw SysError(formatLastOpenSSLError("BIO_read"));
return keyStream;
}
using RsaToBioFunc = int (*)(BIO* bp, RSA* x);
-std::string evpKeyToStream(EVP_PKEY* evp, RsaToBioFunc rsaToBio, const wchar_t* functionName) //throw SysError
+std::string evpKeyToStream(EVP_PKEY* evp, RsaToBioFunc rsaToBio, const char* functionName) //throw SysError
{
BIO* bio = ::BIO_new(BIO_s_mem());
if (!bio)
- throw SysError(formatLastOpenSSLError(L"BIO_new"));
+ throw SysError(formatLastOpenSSLError("BIO_new"));
ZEN_ON_SCOPE_EXIT(::BIO_free_all(bio));
RSA* rsa = ::EVP_PKEY_get0_RSA(evp); //unowned reference!
if (!rsa)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_get0_RSA"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_get0_RSA"));
if (rsaToBio(bio, rsa) != 1)
throw SysError(formatLastOpenSSLError(functionName));
//---------------------------------------------
const int keyLen = BIO_pending(bio);
if (keyLen < 0)
- throw SysError(formatLastOpenSSLError(L"BIO_pending"));
+ throw SysError(formatLastOpenSSLError("BIO_pending"));
if (keyLen == 0)
- throw SysError(L"BIO_pending failed."); //no more error details
+ throw SysError(formatSystemError("BIO_pending", L"", L"Unexpected failure.")); //no more error details
std::string keyStream(keyLen, '\0');
if (::BIO_read(bio, &keyStream[0], keyLen) != keyLen)
- throw SysError(formatLastOpenSSLError(L"BIO_read"));
+ throw SysError(formatLastOpenSSLError("BIO_read"));
return keyStream;
}
@@ -266,13 +266,13 @@ std::string keyToStream(EVP_PKEY* evp, RsaStreamType streamType, bool publicKey)
{
case RsaStreamType::pkix:
return publicKey ?
- evpKeyToStream(evp, ::PEM_write_bio_PUBKEY, L"PEM_write_bio_PUBKEY") : //throw SysError
- evpKeyToStream(evp, ::PEM_write_bio_PrivateKey2, L"PEM_write_bio_PrivateKey"); //
+ evpKeyToStream(evp, ::PEM_write_bio_PUBKEY, "PEM_write_bio_PUBKEY") : //throw SysError
+ evpKeyToStream(evp, ::PEM_write_bio_PrivateKey2, "PEM_write_bio_PrivateKey"); //
case RsaStreamType::pkcs1:
return publicKey ?
- evpKeyToStream(evp, ::PEM_write_bio_RSAPublicKey2, L"PEM_write_bio_RSAPublicKey") : //throw SysError
- evpKeyToStream(evp, ::PEM_write_bio_RSAPrivateKey2, L"PEM_write_bio_RSAPrivateKey"); //
+ evpKeyToStream(evp, ::PEM_write_bio_RSAPublicKey2, "PEM_write_bio_RSAPublicKey") : //throw SysError
+ evpKeyToStream(evp, ::PEM_write_bio_RSAPrivateKey2, "PEM_write_bio_RSAPrivateKey"); //
case RsaStreamType::raw:
break;
@@ -281,7 +281,7 @@ std::string keyToStream(EVP_PKEY* evp, RsaStreamType streamType, bool publicKey)
unsigned char* buf = nullptr;
const int bufSize = (publicKey ? ::i2d_PublicKey : ::i2d_PrivateKey)(evp, &buf);
if (bufSize <= 0)
- throw SysError(formatLastOpenSSLError(publicKey ? L"i2d_PublicKey" : L"i2d_PrivateKey"));
+ throw SysError(formatLastOpenSSLError(publicKey ? "i2d_PublicKey" : "i2d_PrivateKey"));
ZEN_ON_SCOPE_EXIT(::OPENSSL_free(buf)); //memory is only allocated for bufSize > 0
return { reinterpret_cast<const char*>(buf), static_cast<size_t>(bufSize) };
@@ -294,7 +294,7 @@ std::string createSignature(const std::string& message, EVP_PKEY* privateKey) //
//https://www.openssl.org/docs/manmaster/man3/EVP_DigestSign.html
EVP_MD_CTX* mdctx = ::EVP_MD_CTX_create();
if (!mdctx)
- throw SysError(L"EVP_MD_CTX_create failed."); //no more error details
+ throw SysError(formatSystemError("EVP_MD_CTX_create", L"", L"Unexpected failure.")); //no more error details
ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_destroy(mdctx));
if (::EVP_DigestSignInit(mdctx, //EVP_MD_CTX* ctx,
@@ -302,18 +302,18 @@ std::string createSignature(const std::string& message, EVP_PKEY* privateKey) //
EVP_sha256(), //const EVP_MD* type,
nullptr, //ENGINE* e,
privateKey) != 1) //EVP_PKEY* pkey
- throw SysError(formatLastOpenSSLError(L"EVP_DigestSignInit"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestSignInit"));
if (::EVP_DigestSignUpdate(mdctx, //EVP_MD_CTX* ctx,
message.c_str(), //const void* d,
message.size()) != 1) //size_t cnt
- throw SysError(formatLastOpenSSLError(L"EVP_DigestSignUpdate"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestSignUpdate"));
size_t sigLenMax = 0; //"first call to EVP_DigestSignFinal returns the maximum buffer size required"
if (::EVP_DigestSignFinal(mdctx, //EVP_MD_CTX* ctx,
nullptr, //unsigned char* sigret,
&sigLenMax) != 1) //size_t* siglen
- throw SysError(formatLastOpenSSLError(L"EVP_DigestSignFinal"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestSignFinal"));
std::string signature(sigLenMax, '\0');
size_t sigLen = sigLenMax;
@@ -321,7 +321,7 @@ std::string createSignature(const std::string& message, EVP_PKEY* privateKey) //
if (::EVP_DigestSignFinal(mdctx, //EVP_MD_CTX* ctx,
reinterpret_cast<unsigned char*>(&signature[0]), //unsigned char* sigret,
&sigLen) != 1) //size_t* siglen
- throw SysError(formatLastOpenSSLError(L"EVP_DigestSignFinal"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestSignFinal"));
signature.resize(sigLen);
return signature;
@@ -333,7 +333,7 @@ void verifySignature(const std::string& message, const std::string& signature, E
//https://www.openssl.org/docs/manmaster/man3/EVP_DigestVerify.html
EVP_MD_CTX* mdctx = ::EVP_MD_CTX_create();
if (!mdctx)
- throw SysError(L"EVP_MD_CTX_create failed."); //no more error details
+ throw SysError(formatSystemError("EVP_MD_CTX_create", L"", L"Unexpected failure.")); //no more error details
ZEN_ON_SCOPE_EXIT(::EVP_MD_CTX_destroy(mdctx));
if (::EVP_DigestVerifyInit(mdctx, //EVP_MD_CTX* ctx,
@@ -341,17 +341,17 @@ void verifySignature(const std::string& message, const std::string& signature, E
EVP_sha256(), //const EVP_MD* type,
nullptr, //ENGINE* e,
publicKey) != 1) //EVP_PKEY* pkey
- throw SysError(formatLastOpenSSLError(L"EVP_DigestVerifyInit"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestVerifyInit"));
if (::EVP_DigestVerifyUpdate(mdctx, //EVP_MD_CTX* ctx,
message.c_str(), //const void* d,
message.size()) != 1) //size_t cnt
- throw SysError(formatLastOpenSSLError(L"EVP_DigestVerifyUpdate"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestVerifyUpdate"));
if (::EVP_DigestVerifyFinal(mdctx, //EVP_MD_CTX* ctx,
reinterpret_cast<const unsigned char*>(signature.c_str()), //const unsigned char* sig,
signature.size()) != 1) //size_t siglen
- throw SysError(formatLastOpenSSLError(L"EVP_DigestVerifyFinal"));
+ throw SysError(formatLastOpenSSLError("EVP_DigestVerifyFinal"));
}
}
@@ -494,31 +494,31 @@ public:
ctx_ = ::SSL_CTX_new(::TLS_client_method());
if (!ctx_)
- throw SysError(formatLastOpenSSLError(L"SSL_CTX_new"));
+ throw SysError(formatLastOpenSSLError("SSL_CTX_new"));
ssl_ = ::SSL_new(ctx_);
if (!ssl_)
- throw SysError(formatLastOpenSSLError(L"SSL_new"));
+ throw SysError(formatLastOpenSSLError("SSL_new"));
BIO* bio = ::BIO_new_socket(socket, BIO_NOCLOSE);
if (!bio)
- throw SysError(formatLastOpenSSLError(L"BIO_new_socket"));
+ throw SysError(formatLastOpenSSLError("BIO_new_socket"));
::SSL_set0_rbio(ssl_, bio); //pass ownership
if (::BIO_up_ref(bio) != 1)
- throw SysError(formatLastOpenSSLError(L"BIO_up_ref"));
+ throw SysError(formatLastOpenSSLError("BIO_up_ref"));
::SSL_set0_wbio(ssl_, bio); //pass ownership
assert(::SSL_get_mode(ssl_) == SSL_MODE_AUTO_RETRY); //verify OpenSSL default
::SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE);
if (::SSL_set_tlsext_host_name(ssl_, server.c_str()) != 1) //enable SNI (Server Name Indication)
- throw SysError(formatLastOpenSSLError(L"SSL_set_tlsext_host_name"));
+ throw SysError(formatLastOpenSSLError("SSL_set_tlsext_host_name"));
if (caCertFilePath)
{
if (!::SSL_CTX_load_verify_locations(ctx_, utfTo<std::string>(*caCertFilePath).c_str(), nullptr))
- throw SysError(formatLastOpenSSLError(L"SSL_CTX_load_verify_locations"));
+ throw SysError(formatLastOpenSSLError("SSL_CTX_load_verify_locations"));
//alternative: SSL_CTX_set_default_verify_paths(): use OpenSSL default paths considering SSL_CERT_FILE environment variable
//1. enable check for valid certificate: see SSL_get_verify_result()
@@ -526,18 +526,18 @@ public:
//2. enable check that the certificate matches our host: see SSL_get_verify_result()
if (::SSL_set1_host(ssl_, server.c_str()) != 1) //no ownership transfer
- throw SysError(L"SSL_set1_host failed."); //no more error details
+ throw SysError(formatSystemError("SSL_set1_host", L"", L"Unexpected failure.")); //no more error details
}
const int rv = ::SSL_connect(ssl_); //implicitly calls SSL_set_connect_state()
if (rv != 1)
- throw SysError(formatLastOpenSSLError(L"SSL_connect") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv)));
+ throw SysError(formatLastOpenSSLError("SSL_connect") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv)));
if (caCertFilePath)
{
const long verifyResult = ::SSL_get_verify_result(ssl_);
if (verifyResult != X509_V_OK)
- throw SysError(formatSystemError(L"SSL_get_verify_result", formatX509ErrorCode(verifyResult), L""));
+ throw SysError(formatSystemError("SSL_get_verify_result", formatX509ErrorCode(verifyResult), L""));
}
}
@@ -570,17 +570,20 @@ public:
return 0; //EOF + close_notify alert
warn_static("find a better solution for SSL_read_ex + EOF")
- //"sslError == SSL_ERROR_SYSCALL && ::ERR_peek_last_error() == 0" => obsolete as of OpenSSL 1.1.1e
- //https://github.com/openssl/openssl/issues/10880#issuecomment-575746226
+#if OPENSSL_VERSION_NUMBER == 0x1010105fL //OpenSSL 1.1.1e
const auto ec = ::ERR_peek_last_error();
if (sslError == SSL_ERROR_SSL && ERR_GET_REASON(ec) == SSL_R_UNEXPECTED_EOF_WHILE_READING) //EOF: only expected for HTTP/1.0
return 0;
-
- throw SysError(formatLastOpenSSLError(L"SSL_read_ex") + L' ' + formatSslErrorCode(sslError));
+#else //obsolete handling, at least in OpenSSL 1.1.1e (but valid again with OpenSSL 1.1.1f!)
+ //https://github.com/openssl/openssl/issues/10880#issuecomment-575746226
+ if ((sslError == SSL_ERROR_SYSCALL && ::ERR_peek_last_error() == 0)) //EOF: only expected for HTTP/1.0
+ return 0;
+#endif
+ throw SysError(formatLastOpenSSLError("SSL_read_ex") + L' ' + formatSslErrorCode(sslError));
}
assert(bytesReceived > 0); //SSL_read_ex() considers EOF an error!
if (bytesReceived > bytesToRead) //better safe than sorry
- throw SysError(L"SSL_read_ex: buffer overflow.");
+ throw SysError(formatSystemError("SSL_read_ex", L"", L"Buffer overflow."));
return bytesReceived; //"zero indicates end of file"
}
@@ -593,12 +596,12 @@ public:
size_t bytesWritten = 0;
const int rv = ::SSL_write_ex(ssl_, buffer, bytesToWrite, &bytesWritten);
if (rv != 1)
- throw SysError(formatLastOpenSSLError(L"SSL_write_ex") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv)));
+ throw SysError(formatLastOpenSSLError("SSL_write_ex") + L' ' + formatSslErrorCode(::SSL_get_error(ssl_, rv)));
if (bytesWritten > bytesToWrite)
- throw SysError(L"SSL_write_ex: buffer overflow.");
+ throw SysError(formatSystemError("SSL_write_ex", L"", L"Buffer overflow."));
if (bytesWritten == 0)
- throw SysError(L"SSL_write_ex: zero bytes processed");
+ throw SysError(formatSystemError("SSL_write_ex", L"", L"Zero bytes processed."));
return bytesWritten;
}
@@ -728,7 +731,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
EVP_CIPHER_CTX* cipCtx = ::EVP_CIPHER_CTX_new();
if (!cipCtx)
- throw SysError(L"EVP_CIPHER_CTX_new failed."); //no more error details
+ throw SysError(formatSystemError("EVP_CIPHER_CTX_new", L"", L"Unexpected failure.")); //no more error details
ZEN_ON_SCOPE_EXIT(::EVP_CIPHER_CTX_free(cipCtx));
if (::EVP_DecryptInit_ex(cipCtx, //EVP_CIPHER_CTX* ctx,
@@ -736,10 +739,10 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
nullptr, //ENGINE* impl,
key, //const unsigned char* key, => implied length of 256 bit!
nullptr) != 1) //const unsigned char* iv
- throw SysError(formatLastOpenSSLError(L"EVP_DecryptInit_ex"));
+ throw SysError(formatLastOpenSSLError("EVP_DecryptInit_ex"));
if (::EVP_CIPHER_CTX_set_padding(cipCtx, 0 /*padding*/) != 1)
- throw SysError(L"EVP_CIPHER_CTX_set_padding failed."); //no more error details
+ throw SysError(formatSystemError("EVP_CIPHER_CTX_set_padding", L"", L"Unexpected failure.")); //no more error details
privateBlob.resize(privateBlobEnc.size() + ::EVP_CIPHER_block_size(EVP_aes_256_cbc()));
//"EVP_DecryptUpdate() should have room for (inl + cipher_block_size) bytes"
@@ -750,13 +753,13 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
&decLen1, //int* outl,
reinterpret_cast<const unsigned char*>(privateBlobEnc.c_str()), //const unsigned char* in,
static_cast<int>(privateBlobEnc.size())) != 1) //int inl
- throw SysError(formatLastOpenSSLError(L"EVP_DecryptUpdate"));
+ throw SysError(formatLastOpenSSLError("EVP_DecryptUpdate"));
int decLen2 = 0;
if (::EVP_DecryptFinal_ex(cipCtx, //EVP_CIPHER_CTX* ctx,
reinterpret_cast<unsigned char*>(&privateBlob[decLen1]), //unsigned char* outm,
&decLen2) != 1) //int* outl
- throw SysError(formatLastOpenSSLError(L"EVP_DecryptFinal_ex"));
+ throw SysError(formatLastOpenSSLError("EVP_DecryptFinal_ex"));
privateBlob.resize(decLen1 + decLen2);
}
@@ -790,11 +793,11 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
static_cast<int>(macData.size()), //int n,
reinterpret_cast<unsigned char*>(md), //unsigned char* md,
&mdLen)) //unsigned int* md_len
- throw SysError(L"HMAC failed."); //no more error details
+ throw SysError(formatSystemError("HMAC", L"", L"Unexpected failure.")); //no more error details
const bool hashValid = mac == std::string_view(md, mdLen);
if (!hashValid)
- throw SysError(keyEncrypted ? L"MAC validation failed: wrong passphrase or corrupted key" : L"MAC validation failed: corrupted key");
+ throw SysError(formatSystemError("HMAC", L"", keyEncrypted ? L"Validation failed: wrong passphrase or corrupted key" : L"Validation failed: corrupted key"));
//----------------------------------------------------------
auto extractString = [](auto& it, auto itEnd)
@@ -823,7 +826,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
{
BIGNUM* bn = ::BN_new();
if (!bn)
- throw SysError(formatLastOpenSSLError(L"BN_new"));
+ throw SysError(formatLastOpenSSLError("BN_new"));
return std::unique_ptr<BIGNUM, BnFree>(bn);
};
@@ -833,7 +836,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
BIGNUM* bn = ::BN_bin2bn(reinterpret_cast<const unsigned char*>(&bytes[0]), static_cast<int>(bytes.size()), nullptr);
if (!bn)
- throw SysError(formatLastOpenSSLError(L"BN_bin2bn"));
+ throw SysError(formatLastOpenSSLError("BN_bin2bn"));
return std::unique_ptr<BIGNUM, BnFree>(bn);
};
@@ -866,43 +869,43 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
BN_CTX* bnCtx = BN_CTX_new();
if (!bnCtx)
- throw SysError(formatLastOpenSSLError(L"BN_CTX_new"));
+ throw SysError(formatLastOpenSSLError("BN_CTX_new"));
ZEN_ON_SCOPE_EXIT(::BN_CTX_free(bnCtx));
if (::BN_sub(tmp.get(), p.get(), BN_value_one()) != 1)
- throw SysError(formatLastOpenSSLError(L"BN_sub"));
+ throw SysError(formatLastOpenSSLError("BN_sub"));
if (::BN_mod(dmp1.get(), d.get(), tmp.get(), bnCtx) != 1)
- throw SysError(formatLastOpenSSLError(L"BN_mod"));
+ throw SysError(formatLastOpenSSLError("BN_mod"));
if (::BN_sub(tmp.get(), q.get(), BN_value_one()) != 1)
- throw SysError(formatLastOpenSSLError(L"BN_sub"));
+ throw SysError(formatLastOpenSSLError("BN_sub"));
if (::BN_mod(dmq1.get(), d.get(), tmp.get(), bnCtx) != 1)
- throw SysError(formatLastOpenSSLError(L"BN_mod"));
+ throw SysError(formatLastOpenSSLError("BN_mod"));
//----------------------------------------------------------
RSA* rsa = ::RSA_new();
if (!rsa)
- throw SysError(formatLastOpenSSLError(L"RSA_new"));
+ throw SysError(formatLastOpenSSLError("RSA_new"));
ZEN_ON_SCOPE_EXIT(::RSA_free(rsa));
if (::RSA_set0_key(rsa, n.release(), e.release(), d.release()) != 1) //pass BIGNUM ownership
- throw SysError(formatLastOpenSSLError(L"RSA_set0_key"));
+ throw SysError(formatLastOpenSSLError("RSA_set0_key"));
if (::RSA_set0_factors(rsa, p.release(), q.release()) != 1)
- throw SysError(formatLastOpenSSLError(L"RSA_set0_factors"));
+ throw SysError(formatLastOpenSSLError("RSA_set0_factors"));
if (::RSA_set0_crt_params(rsa, dmp1.release(), dmq1.release(), iqmp.release()) != 1)
- throw SysError(formatLastOpenSSLError(L"RSA_set0_crt_params"));
+ throw SysError(formatLastOpenSSLError("RSA_set0_crt_params"));
EVP_PKEY* evp = ::EVP_PKEY_new();
if (!evp)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_new"));
ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp));
if (::EVP_PKEY_set1_RSA(evp, rsa) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_set1_RSA"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_RSA"));
return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError
}
@@ -918,22 +921,22 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
DSA* dsa = ::DSA_new();
if (!dsa)
- throw SysError(formatLastOpenSSLError(L"DSA_new"));
+ throw SysError(formatLastOpenSSLError("DSA_new"));
ZEN_ON_SCOPE_EXIT(::DSA_free(dsa));
if (::DSA_set0_pqg(dsa, p.release(), q.release(), g.release()) != 1) //pass BIGNUM ownership
- throw SysError(formatLastOpenSSLError(L"DSA_set0_pqg"));
+ throw SysError(formatLastOpenSSLError("DSA_set0_pqg"));
if (::DSA_set0_key(dsa, pub.release(), pri.release()) != 1)
- throw SysError(formatLastOpenSSLError(L"DSA_set0_key"));
+ throw SysError(formatLastOpenSSLError("DSA_set0_key"));
EVP_PKEY* evp = ::EVP_PKEY_new();
if (!evp)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_new"));
ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp));
if (::EVP_PKEY_set1_DSA(evp, dsa) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_set1_DSA"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_DSA"));
return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError
}
@@ -963,16 +966,16 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
EC_KEY* ecKey = ::EC_KEY_new_by_curve_name(curveNid);
if (!ecKey)
- throw SysError(formatLastOpenSSLError(L"EC_KEY_new_by_curve_name"));
+ throw SysError(formatLastOpenSSLError("EC_KEY_new_by_curve_name"));
ZEN_ON_SCOPE_EXIT(::EC_KEY_free(ecKey));
const EC_GROUP* ecGroup = ::EC_KEY_get0_group(ecKey);
if (!ecGroup)
- throw SysError(formatLastOpenSSLError(L"EC_KEY_get0_group"));
+ throw SysError(formatLastOpenSSLError("EC_KEY_get0_group"));
EC_POINT* ecPoint = ::EC_POINT_new(ecGroup);
if (!ecPoint)
- throw SysError(formatLastOpenSSLError(L"EC_POINT_new"));
+ throw SysError(formatLastOpenSSLError("EC_POINT_new"));
ZEN_ON_SCOPE_EXIT(::EC_POINT_free(ecPoint));
if (::EC_POINT_oct2point(ecGroup, //const EC_GROUP* group,
@@ -980,21 +983,21 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
reinterpret_cast<const unsigned char*>(&pointStream[0]), //const unsigned char* buf,
pointStream.size(), //size_t len,
nullptr) != 1) //BN_CTX* ctx
- throw SysError(formatLastOpenSSLError(L"EC_POINT_oct2point"));
+ throw SysError(formatLastOpenSSLError("EC_POINT_oct2point"));
if (::EC_KEY_set_public_key(ecKey, ecPoint) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EC_KEY_set_public_key"));
+ throw SysError(formatLastOpenSSLError("EC_KEY_set_public_key"));
if (::EC_KEY_set_private_key(ecKey, pri.get()) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EC_KEY_set_private_key"));
+ throw SysError(formatLastOpenSSLError("EC_KEY_set_private_key"));
EVP_PKEY* evp = ::EVP_PKEY_new();
if (!evp)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_new"));
ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evp));
if (::EVP_PKEY_set1_EC_KEY(evp, ecKey) != 1) //no ownership transfer (internally ref-counted)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_set1_EC_KEY"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_set1_EC_KEY"));
return keyToStream(evp, RsaStreamType::pkix, false /*publicKey*/); //throw SysError
}
@@ -1009,7 +1012,7 @@ std::string zen::convertPuttyKeyToPkix(const std::string& keyStream, const std::
reinterpret_cast<const unsigned char*>(&priStream[0]), //const unsigned char* priv,
priStream.size()); //size_t len
if (!evpPriv)
- throw SysError(formatLastOpenSSLError(L"EVP_PKEY_new_raw_private_key"));
+ throw SysError(formatLastOpenSSLError("EVP_PKEY_new_raw_private_key"));
ZEN_ON_SCOPE_EXIT(::EVP_PKEY_free(evpPriv));
return keyToStream(evpPriv, RsaStreamType::pkix, false /*publicKey*/); //throw SysError
diff --git a/zen/perf.h b/zen/perf.h
index 9f368016..33005e9f 100644
--- a/zen/perf.h
+++ b/zen/perf.h
@@ -32,7 +32,7 @@ namespace zen
// => wxStopWatch implementation uses QueryPerformanceCounter: https://github.com/wxWidgets/wxWidgets/blob/17d72a48ffd4d8ff42eed070ac48ee2de50ceabd/src/common/stopwatch.cpp
// => whatever the problem was, it's almost certainly not caused by QueryPerformanceCounter():
// MSDN: "How often does QPC roll over? Not less than 100 years from the most recent system boot"
-// https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408#How_often_does_QPC_roll_over_
+// https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps#How_often_does_QPC_roll_over
//
// => using the system clock is problematic: https://freefilesync.org/forum/viewtopic.php?t=5280
//
diff --git a/zen/process_priority.cpp b/zen/process_priority.cpp
index 5aa9a0ce..b7b4e029 100644
--- a/zen/process_priority.cpp
+++ b/zen/process_priority.cpp
@@ -15,7 +15,7 @@ struct PreventStandby::Impl {};
PreventStandby::PreventStandby() {}
PreventStandby::~PreventStandby() {}
-//solution for GNOME?: http://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Inhibit
+//solution for GNOME?: https://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Inhibit
struct ScheduleForBackgroundProcessing::Impl {};
ScheduleForBackgroundProcessing::ScheduleForBackgroundProcessing() {}
@@ -25,7 +25,7 @@ ScheduleForBackgroundProcessing::~ScheduleForBackgroundProcessing() {}
struct ScheduleForBackgroundProcessing
{
- required functions ioprio_get/ioprio_set are not part of glibc: https://linux.die.net/man/2/ioprio_set
- - and probably never will: http://sourceware.org/bugzilla/show_bug.cgi?id=4464
+ - and probably never will: https://sourceware.org/bugzilla/show_bug.cgi?id=4464
- /usr/include/linux/ioprio.h not available on Ubuntu, so we can't use it instead
ScheduleForBackgroundProcessing() : oldIoPrio(getIoPriority(IOPRIO_WHO_PROCESS, ::getpid()))
diff --git a/zen/recycler.cpp b/zen/recycler.cpp
index f4fd870b..4d6ea1fd 100644
--- a/zen/recycler.cpp
+++ b/zen/recycler.cpp
@@ -31,12 +31,8 @@ bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError
if (!type)
return false;
- const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath));
- if (!error)
- throw FileError(errorMsg, L"g_file_trash: unknown error."); //user should never see this
-
//implement same behavior as in Windows: if recycler is not existing, delete permanently
- if (error->code == G_IO_ERROR_NOT_SUPPORTED)
+ if (error && error->code == G_IO_ERROR_NOT_SUPPORTED)
{
if (*type == ItemType::FOLDER)
removeDirectoryPlainRecursion(itemPath); //throw FileError
@@ -45,9 +41,10 @@ bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError
return true;
}
- throw FileError(errorMsg, formatSystemError(L"g_file_trash",
- replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(error->code)),
- utfTo<std::wstring>(error->message)));
+ throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)),
+ formatSystemError("g_file_trash",
+ error ? replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(error->code)) : L"",
+ error ? utfTo<std::wstring>(error->message) : L"Unknown error."));
//g_quark_to_string(error->domain)
}
return true;
diff --git a/zen/shell_execute.cpp b/zen/shell_execute.cpp
new file mode 100644
index 00000000..63696568
--- /dev/null
+++ b/zen/shell_execute.cpp
@@ -0,0 +1,250 @@
+// *****************************************************************************
+// * 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 "shell_execute.h"
+#include <chrono>
+#include "guid.h"
+#include "file_access.h"
+#include "file_io.h"
+
+ #include <unistd.h> //fork, pipe
+ #include <sys/wait.h> //waitpid
+ #include <fcntl.h>
+
+using namespace zen;
+
+
+std::vector<Zstring> zen::parseCommandline(const Zstring& cmdLine)
+{
+ 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
+ {
+ 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;
+ }
+ }
+ 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);
+
+ return args;
+}
+
+
+
+
+std::pair<int /*exit code*/, std::wstring> zen::consoleExecute(const Zstring& cmdLine, std::optional<int> timeoutMs) //throw SysError, SysErrorTimeOut
+{
+ const Zstring tempFilePath = appendSeparator(getTempFolderPath()) + //throw FileError
+ Zstr("FFS-") + utfTo<Zstring>(formatAsHexString(generateGUID()));
+ /* can't use popen(): does NOT return the exit code on Linux (despite the documentation!), although it works correctly on macOS
+ => use pipes instead: https://linux.die.net/man/2/waitpid
+ bonus: no need for "2>&1" to redirect STDERR to STDOUT
+
+ What about premature exit via SysErrorTimeOut?
+ Linux: child process' end of the pipe *still works* even after the parent process is gone:
+ There does not seem to be any output buffer size limit + no observable strain on system memory or disk space! :)
+ macOS: child process exits if parent end of pipe is closed: fuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu..........
+
+ => solution: buffer output in temporary file
+
+ Unresolved problem: premature exit via SysErrorTimeOut (=> no waitpid()) creates zombie proceses:
+ "As long as a zombie is not removed from the system via a wait,
+ it will consume a slot in the kernel process table, and if this table fills,
+ it will not be possible to create further processes." */
+
+ const int EC_CHILD_LAUNCH_FAILED = 120; //avoid 127: used by the system, e.g. failure to execute due to missing .so file
+
+ const int fdTempFile = ::open(tempFilePath.c_str(), O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC,
+ S_IRUSR | S_IWUSR); //0600
+ if (fdTempFile == -1)
+ THROW_LAST_SYS_ERROR("open");
+ auto guardTmpFile = makeGuard<ScopeGuardRunMode::onExit>([&] { ::close(fdTempFile); });
+
+ //"deleting while handles are open" == FILE_FLAG_DELETE_ON_CLOSE
+ if (::unlink(tempFilePath.c_str()) != 0)
+ THROW_LAST_SYS_ERROR("unlink");
+
+ //--------------------------------------------------------------
+ //waitpid() is a useless pile of garbage without time out => check EOF from dummy pipe instead
+ int pipe[2] = {};
+ if (::pipe2(pipe, O_CLOEXEC) != 0)
+ THROW_LAST_SYS_ERROR("pipe2");
+
+
+ const int fdLifeSignR = pipe[0]; //for parent process
+ const int fdLifeSignW = pipe[1]; //for child process
+ ZEN_ON_SCOPE_EXIT(::close(fdLifeSignR));
+ auto guardFdLifeSignW = makeGuard<ScopeGuardRunMode::onExit>([&] { ::close(fdLifeSignW ); });
+ //--------------------------------------------------------------
+
+ //follow implemenation of ::system(): https://github.com/lattera/glibc/blob/master/sysdeps/posix/system.c
+ const pid_t pid = ::fork();
+ if (pid < 0) //pids are never negative, empiric proof: https://linux.die.net/man/2/wait
+ THROW_LAST_SYS_ERROR("fork");
+
+ if (pid == 0) //child process
+ try
+ {
+ //first task: set STDOUT redirection in case an error needs to be reported
+ if (::dup2(fdTempFile, STDOUT_FILENO) != STDOUT_FILENO) //O_CLOEXEC does NOT propagate with dup2()
+ THROW_LAST_SYS_ERROR("dup2(STDOUT)");
+
+ if (::dup2(fdTempFile, STDERR_FILENO) != STDERR_FILENO) //O_CLOEXEC does NOT propagate with dup2()
+ THROW_LAST_SYS_ERROR("dup2(STDERR)");
+
+ //avoid blocking scripts waiting for user input
+ // => appending " < /dev/null" is not good enough! e.g. hangs for: read -p "still hanging here"; echo fuuuuu...
+ const int fdDevNull = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
+ if (fdDevNull == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle
+ THROW_LAST_SYS_ERROR("open(/dev/null)");
+ ZEN_ON_SCOPE_EXIT(::close(fdDevNull));
+
+ if (::dup2(fdDevNull, STDIN_FILENO) != STDIN_FILENO) //O_CLOEXEC does NOT propagate with dup2()
+ THROW_LAST_SYS_ERROR("dup2(STDIN)");
+
+ //*leak* the fd and have it closed automatically on child process exit after execv()
+ if (::dup(fdLifeSignW) == -1) //O_CLOEXEC does NOT propagate with dup()
+ THROW_LAST_SYS_ERROR("dup(fdLifeSignW)");
+
+
+ const char* argv[] = { "sh", "-c", cmdLine.c_str(), nullptr };
+ /*int rv =*/::execv("/bin/sh", const_cast<char**>(argv)); //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
+ // the ISO C standard, it is not possible to state that idea in standard C."
+ THROW_LAST_SYS_ERROR("execv");
+ }
+ catch (const SysError& e)
+ {
+ ::puts(utfTo<std::string>(e.toString()).c_str());
+ ::fflush(stdout); //note: stderr is unbuffered by default
+ ::_exit(EC_CHILD_LAUNCH_FAILED); //[!] avoid flushing I/O buffers or doing other clean up from child process like with "exit()"!
+ }
+ //else: parent process
+
+
+ if (timeoutMs)
+ {
+ guardFdLifeSignW.dismiss();
+ ::close(fdLifeSignW); //[!] make sure we get EOF when fd is closed by child!
+
+ if (::fcntl(fdLifeSignR, F_SETFL, O_NONBLOCK) != 0)
+ THROW_LAST_SYS_ERROR("fcntl(O_NONBLOCK)");
+
+ const auto endTime = std::chrono::steady_clock::now() + std::chrono::milliseconds(*timeoutMs);
+ for (;;) //EINTR handling? => allow interrupt!?
+ {
+ //read until EAGAIN
+ char buf[16];
+ const ssize_t bytesRead = ::read(fdLifeSignR, buf, sizeof(buf));
+ if (bytesRead < 0)
+ {
+ if (errno != EAGAIN)
+ THROW_LAST_SYS_ERROR("read");
+ }
+ else if (bytesRead > 0)
+ throw SysError(formatSystemError("read", L"", L"Unexpected data."));
+ else //bytesRead == 0: EOF
+ break;
+
+ //wait for stream input
+ const auto now = std::chrono::steady_clock::now();
+ if (now > endTime)
+ throw SysErrorTimeOut(_P("Operation timed out after 1 second.", "Operation timed out after %x seconds.", *timeoutMs / 1000));
+
+ const auto waitTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - now).count();
+
+ struct ::timeval tv = {};
+ tv.tv_sec = static_cast<long>(waitTimeMs / 1000);
+ tv.tv_usec = static_cast<long>(waitTimeMs - tv.tv_sec * 1000) * 1000;
+
+ 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,
+ &tv); //struct timeval* timeout
+ rv < 0)
+ THROW_LAST_SYS_ERROR("select");
+ else if (rv == 0)
+ throw SysErrorTimeOut(_P("Operation timed out after 1 second.", "Operation timed out after %x seconds.", *timeoutMs / 1000));
+ }
+ }
+
+ //https://linux.die.net/man/2/waitpid
+ int statusCode = 0;
+ if (::waitpid(pid, //pid_t pid
+ &statusCode, //int* status
+ 0) != pid) //int options
+ THROW_LAST_SYS_ERROR("waitpid");
+
+
+ if (::lseek(fdTempFile, 0, SEEK_SET) != 0)
+ THROW_LAST_SYS_ERROR("lseek");
+
+ guardTmpFile.dismiss();
+ FileInput streamIn(fdTempFile, tempFilePath, nullptr /*notifyUnbufferedIO*/); //takes ownership!
+ const std::wstring output = utfTo<std::wstring>(bufferedLoad<std::string>(streamIn)); //throw FileError
+
+
+ if (!WIFEXITED(statusCode)) //signalled, crashed?
+ throw SysError(formatSystemError("waitpid", WIFSIGNALED(statusCode) ?
+ L"Killed by signal " + numberTo<std::wstring>(WTERMSIG(statusCode)) :
+ L"Exit status " + numberTo<std::wstring>(statusCode),
+ utfTo<std::wstring>(trimCpy(output))));
+
+ const int exitCode = WEXITSTATUS(statusCode); //precondition: "WIFEXITED() == true"
+ if (exitCode == EC_CHILD_LAUNCH_FAILED || //child process should already have provided details to STDOUT
+ exitCode == 127) //details should have been streamed to STDERR: used by /bin/sh, e.g. failure to execute due to missing .so file
+ throw SysError(utfTo<std::wstring>(trimCpy(output)));
+
+ return { exitCode, output };
+}
+
+
+void zen::openWithDefaultApp(const Zstring& itemPath) //throw FileError
+{
+ try
+ {
+ const Zstring cmdTemplate = R"(xdg-open "%x")"; //doesn't block => no need for time out!
+ const Zstring cmdLine = replaceCpy(cmdTemplate, Zstr("%x"), itemPath);
+
+ 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));
+ }
+ 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/shell_execute.h
index faea4bd9..b80cf2ba 100644
--- a/zen/shell_execute.h
+++ b/zen/shell_execute.h
@@ -9,105 +9,18 @@
#include "file_error.h"
- #include <unistd.h> //fork()
- #include <stdlib.h> //::system()
-
namespace zen
{
-//launch commandline and report errors via popup dialog
-//Windows: COM needs to be initialized before calling this function!
-enum class ExecutionType
-{
- sync,
- async
-};
-
-namespace
-{
-
-
-int shellExecute(const Zstring& command, ExecutionType type, bool hideConsole) //throw FileError
-{
- /*
- we cannot use wxExecute due to various issues:
- - screws up encoding on OS X for non-ASCII characters
- - does not provide any reasonable error information
- - uses a zero-sized dummy window as a hack to keep focus which leaves a useless empty icon in ALT-TAB list in Windows
- */
- if (type == ExecutionType::sync)
- {
- //Posix ::system() - execute a shell command
- const int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", etc...
- if (rv == -1 || WEXITSTATUS(rv) == 127)
- throw FileError(_("Incorrect command line:") + L' ' + utfTo<std::wstring>(command));
- //https://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)"
- //Bonus: For an incorrect command line /bin/sh also returns with 127!
-
- return /*int exitCode = */ WEXITSTATUS(rv);
- }
- else
- {
- //follow implemenation of ::system() except for waitpid():
- const pid_t pid = ::fork();
- if (pid < 0) //pids are never negative, empiric proof: https://linux.die.net/man/2/wait
- THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L' ' + utfTo<std::wstring>(command), L"fork");
-
- if (pid == 0) //child process
- {
- const char* argv[] = { "sh", "-c", command.c_str(), nullptr };
- /*int rv =*/::execv("/bin/sh", const_cast<char**>(argv));
- //safe to cast away const: http://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
- // the ISO C standard, it is not possible to state that idea in standard C."
-
- //"execv() only returns if an error has occurred. The return value is -1, and errno is set to indicate the error."
- ::_exit(127); //[!] avoid flushing I/O buffers or doing other clean up from child process like with "exit(127)"!
- }
- //else //parent process
- return 0;
- }
-}
-
-
-std::string getCommandOutput(const Zstring& command) //throw SysError
-{
- //https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/popen.3.html
- FILE* pipe = ::popen(command.c_str(), "r");
- if (!pipe)
- THROW_LAST_SYS_ERROR(L"popen");
- ZEN_ON_SCOPE_EXIT(::pclose(pipe));
+std::vector<Zstring> parseCommandline(const Zstring& cmdLine);
- std::string output;
- const size_t blockSize = 64 * 1024;
- do
- {
- output.resize(output.size() + blockSize);
- //caveat: SIGCHLD is NOT ignored under macOS debugger => EINTR inside fread() => call ::siginterrupt(SIGCHLD, false) during startup
- const size_t bytesRead = ::fread(&*(output.end() - blockSize), 1, blockSize, pipe);
- if (::ferror(pipe))
- THROW_LAST_SYS_ERROR(L"fread");
+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 */
- if (bytesRead > blockSize)
- throw SysError(L"fread: buffer overflow");
-
- if (bytesRead < blockSize)
- output.resize(output.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics
- }
- while (!::feof(pipe));
-
- return output;
-}
-}
-
-
-inline
-void openWithDefaultApplication(const Zstring& itemPath) //throw FileError
-{
- shellExecute("xdg-open \"" + itemPath + '"', ExecutionType::async, false /*hideConsole*/); //throw FileError
-}
+void openWithDefaultApp(const Zstring& itemPath); //throw FileError
}
#endif //SHELL_EXECUTE_H_23482134578134134
diff --git a/zen/shutdown.cpp b/zen/shutdown.cpp
index 89da55ee..21e24527 100644
--- a/zen/shutdown.cpp
+++ b/zen/shutdown.cpp
@@ -15,19 +15,31 @@ using namespace zen;
void zen::shutdownSystem() //throw FileError
{
- //https://linux.die.net/man/2/reboot => needs admin rights!
-
- //"systemctl" should work without admin rights:
- shellExecute("systemctl poweroff", ExecutionType::sync, false/*hideConsole*/); //throw FileError
-
+ try
+ {
+ //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);
+
+ }
+ catch (const SysError& e) { throw FileError(_("Unable to shut down the system."), e.toString()); }
}
void zen::suspendSystem() //throw FileError
{
- //"systemctl" should work without admin rights:
- shellExecute("systemctl suspend", ExecutionType::sync, false/*hideConsole*/); //throw FileError
-
+ try
+ {
+ //"systemctl" should work without admin rights:
+ const auto [exitCode, output] = consoleExecute("systemctl suspend", std::nullopt /*timeoutMs*/); //throw SysError, (SysErrorTimeOut)
+ //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);
+
+ }
+ 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 3bd0a2a0..f1d26450 100644
--- a/zen/socket.h
+++ b/zen/socket.h
@@ -42,19 +42,19 @@ public:
const int rcGai = ::getaddrinfo(server.c_str(), serviceName.c_str(), &hints, &servinfo);
if (rcGai != 0)
- throw SysError(formatSystemError(L"getaddrinfo", replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai))));
+ throw SysError(formatSystemError("getaddrinfo", replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai))));
if (!servinfo)
- throw SysError(L"getaddrinfo: empty server info");
+ throw SysError(formatSystemError("getaddrinfo", L"", L"Empty server info."));
const auto getConnectedSocket = [](const auto& /*::addrinfo*/ ai)
{
SocketType testSocket = ::socket(ai.ai_family, ai.ai_socktype, ai.ai_protocol);
if (testSocket == invalidSocket)
- THROW_LAST_SYS_ERROR_WSA(L"socket");
+ THROW_LAST_SYS_ERROR_WSA("socket");
ZEN_ON_SCOPE_FAIL(closeSocket(testSocket));
if (::connect(testSocket, ai.ai_addr, static_cast<int>(ai.ai_addrlen)) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"connect");
+ THROW_LAST_SYS_ERROR_WSA("connect");
return testSocket;
};
@@ -102,10 +102,10 @@ size_t tryReadSocket(SocketType socket, void* buffer, size_t bytesToRead) //thro
break;
}
if (bytesReceived < 0)
- THROW_LAST_SYS_ERROR_WSA(L"recv");
+ THROW_LAST_SYS_ERROR_WSA("recv");
if (static_cast<size_t>(bytesReceived) > bytesToRead) //better safe than sorry
- throw SysError(L"recv: buffer overflow.");
+ throw SysError(formatSystemError("recv", L"", L"Buffer overflow."));
return bytesReceived; //"zero indicates end of file"
}
@@ -127,11 +127,11 @@ size_t tryWriteSocket(SocketType socket, const void* buffer, size_t bytesToWrite
break;
}
if (bytesWritten < 0)
- THROW_LAST_SYS_ERROR_WSA(L"send");
+ THROW_LAST_SYS_ERROR_WSA("send");
if (bytesWritten > static_cast<int>(bytesToWrite))
- throw SysError(L"send: buffer overflow.");
+ throw SysError(formatSystemError("send", L"", L"Buffer overflow."));
if (bytesWritten == 0)
- throw SysError(L"send: zero bytes processed");
+ throw SysError(formatSystemError("send", L"", L"Zero bytes processed."));
return bytesWritten;
}
@@ -143,7 +143,7 @@ inline
void shutdownSocketSend(SocketType socket) //throw SysError
{
if (::shutdown(socket, SHUT_WR) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"shutdown");
+ THROW_LAST_SYS_ERROR_WSA("shutdown");
}
}
diff --git a/zen/string_base.h b/zen/string_base.h
index 42e1bdf3..5922c3ff 100644
--- a/zen/string_base.h
+++ b/zen/string_base.h
@@ -566,8 +566,8 @@ const Char& Zbase<Char, SP>::operator[](size_t pos) const
template <class Char, template <class> class SP> inline
Char& Zbase<Char, SP>::operator[](size_t pos)
{
- assert(pos < length()); //design by contract! no runtime check!
reserve(length()); //make unshared!
+ assert(pos < length()); //design by contract! no runtime check!
return rawStr_[pos];
}
diff --git a/zen/string_tools.h b/zen/string_tools.h
index 40a4ea52..cd26f5fd 100644
--- a/zen/string_tools.h
+++ b/zen/string_tools.h
@@ -83,6 +83,7 @@ template <class Num, class S> Num stringTo(const S& str);
std::pair<char, char> hexify (unsigned char c, bool upperCase = true);
char unhexify(char high, char low);
+std::string formatAsHexString(const std::string& blob); //bytes -> (human-readable) hex string
template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using std::snprintf()
@@ -848,6 +849,20 @@ char unhexify(char high, char low)
}
+inline
+std::string formatAsHexString(const std::string& blob)
+{
+ std::string output;
+ for (const char c : blob)
+ {
+ const auto [high, low] = hexify(c, false /*upperCase*/);
+ output += high;
+ output += low;
+ }
+ return output;
+}
+
+
}
#endif //STRING_TOOLS_H_213458973046
diff --git a/zen/symlink_target.h b/zen/symlink_target.h
index 2393013e..077fd4b3 100644
--- a/zen/symlink_target.h
+++ b/zen/symlink_target.h
@@ -42,9 +42,9 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro
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)), L"readlink");
+ 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!
- throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"readlink: buffer truncated.");
+ 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!
}
@@ -55,7 +55,7 @@ Zstring getResolvedSymlinkPath_impl(const Zstring& linkPath) //throw FileError
using namespace zen;
char* targetPath = ::realpath(linkPath.c_str(), nullptr);
if (!targetPath)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"realpath");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), "realpath");
ZEN_ON_SCOPE_EXIT(::free(targetPath));
return targetPath;
}
diff --git a/zen/sys_error.cpp b/zen/sys_error.cpp
index b802780b..f9747d45 100644
--- a/zen/sys_error.cpp
+++ b/zen/sys_error.cpp
@@ -162,29 +162,30 @@ std::wstring formatSystemErrorCode(ErrorCode ec)
ZEN_CHECK_CASE_FOR_CONSTANT(ERFKILL);
ZEN_CHECK_CASE_FOR_CONSTANT(EHWPOISON);
default:
- return replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(ec));
+ return replaceCpy(_("Error code %x"), L"%x", numberTo<std::wstring>(ec));
}
}
}
-std::wstring zen::formatSystemError(const std::wstring& functionName, ErrorCode ec)
+std::wstring zen::formatSystemError(const std::string& functionName, ErrorCode ec)
{
return formatSystemError(functionName, formatSystemErrorCode(ec), getSystemErrorDescription(ec));
}
-std::wstring zen::formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg)
+std::wstring zen::formatSystemError(const std::string& functionName, const std::wstring& errorCode, const std::wstring& errorMsg)
{
- std::wstring output = errorCode + L':';
+ std::wstring output = errorCode;
const std::wstring errorMsgFmt = trimCpy(errorMsg);
- if (!errorMsgFmt.empty())
- {
- output += L' ';
- output += errorMsgFmt;
- }
+ if (!errorCode.empty() && !errorMsgFmt.empty())
+ output += L": ";
+
+ output += errorMsgFmt;
+
+ if (!functionName.empty())
+ output += L" [" + utfTo<std::wstring>(functionName) + L']';
- output += L" [" + functionName + L']';
- return output;
+ return trimCpy(output);
}
diff --git a/zen/sys_error.h b/zen/sys_error.h
index 6bef45ea..2dd3c188 100644
--- a/zen/sys_error.h
+++ b/zen/sys_error.h
@@ -22,8 +22,8 @@ namespace zen
ErrorCode getLastError();
-std::wstring formatSystemError(const std::wstring& functionName, const std::wstring& errorCode, const std::wstring& errorMsg);
-std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec);
+std::wstring formatSystemError(const std::string& functionName, const std::wstring& errorCode, const std::wstring& errorMsg);
+std::wstring formatSystemError(const std::string& functionName, ErrorCode ec);
//A low-level exception class giving (non-translated) detail information only - same conceptional level like "GetLastError()"!
diff --git a/zen/system.cpp b/zen/system.cpp
index 9401b94f..d9a169c7 100644
--- a/zen/system.cpp
+++ b/zen/system.cpp
@@ -29,7 +29,7 @@ std::wstring zen::getUserName() //throw FileError
struct passwd buffer2 = {};
struct passwd* pwsEntry = nullptr;
if (::getpwuid_r(userIdNo, &buffer2, &buffer[0], buffer.size(), &pwsEntry) != 0) //getlogin() is deprecated and not working on Ubuntu at all!!!
- THROW_LAST_FILE_ERROR(_("Cannot get process information."), L"getpwuid_r");
+ THROW_LAST_FILE_ERROR(_("Cannot get process information."), "getpwuid_r");
if (!pwsEntry)
throw FileError(_("Cannot get process information."), L"no login found"); //should not happen?
@@ -96,9 +96,22 @@ std::wstring zen::getOsDescription() //throw FileError
{
try
{
- const std::string osName = trimCpy(getCommandOutput("lsb_release --id -s" )); //throw SysError
- const std::string osVersion = trimCpy(getCommandOutput("lsb_release --release -s")); //
- return utfTo<std::wstring>(osName + ' ' + osVersion); //e.g. "CentOS 7.7.1908"
+ std::wstring osName;
+ std::wstring osVersion;
+
+ 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));
+ else
+ osName = 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));
+ else
+ osVersion = trimCpy(output);
+
+ return osName + L' ' + osVersion; //e.g. "CentOS 7.7.1908"
}
catch (const SysError& e) { throw FileError(_("Cannot get process information."), e.toString()); }
diff --git a/zen/time.h b/zen/time.h
index 9718e5f6..d3aef36f 100644
--- a/zen/time.h
+++ b/zen/time.h
@@ -121,7 +121,7 @@ bool isValid(const std::tm& t)
auto inRange = [](int value, int minVal, int maxVal) { return minVal <= value && value <= maxVal; };
- //http://www.cplusplus.com/reference/clibrary/ctime/tm/
+ //https://www.cplusplus.com/reference/clibrary/ctime/tm/
return inRange(t.tm_sec, 0, 61) &&
inRange(t.tm_min, 0, 59) &&
inRange(t.tm_hour, 0, 23) &&
diff --git a/zen/zlib_wrap.cpp b/zen/zlib_wrap.cpp
index 685843c3..dba890ee 100644
--- a/zen/zlib_wrap.cpp
+++ b/zen/zlib_wrap.cpp
@@ -55,7 +55,7 @@ size_t zen::impl::zlib_compress(const void* src, size_t srcLen, void* trg, size_
// Z_MEM_ERROR: not enough memory
// Z_BUF_ERROR: not enough room in the output buffer
if (rv != Z_OK || bufferSize > trgLen)
- throw SysError(formatSystemError(L"zlib compress2", formatZlibStatusCode(rv), L""));
+ throw SysError(formatSystemError("zlib compress2", formatZlibStatusCode(rv), L""));
return bufferSize;
}
@@ -73,7 +73,7 @@ size_t zen::impl::zlib_decompress(const void* src, size_t srcLen, void* trg, siz
// Z_BUF_ERROR: not enough room in the output buffer
// Z_DATA_ERROR: input data was corrupted or incomplete
if (rv != Z_OK || bufferSize > trgLen)
- throw SysError(formatSystemError(L"zlib uncompress", formatZlibStatusCode(rv), L""));
+ throw SysError(formatSystemError("zlib uncompress", formatZlibStatusCode(rv), L""));
return bufferSize;
}
@@ -98,7 +98,7 @@ public:
memLevel, //int memLevel
Z_DEFAULT_STRATEGY); //int strategy
if (rv != Z_OK)
- throw SysError(formatSystemError(L"zlib deflateInit2", formatZlibStatusCode(rv), L""));
+ throw SysError(formatSystemError("zlib deflateInit2", formatZlibStatusCode(rv), L""));
}
~Impl()
@@ -133,7 +133,7 @@ public:
if (rv == Z_STREAM_END)
return bytesToRead - gzipStream_.avail_out;
if (rv != Z_OK)
- throw SysError(formatSystemError(L"zlib deflate", formatZlibStatusCode(rv), L""));
+ throw SysError(formatSystemError("zlib deflate", formatZlibStatusCode(rv), L""));
if (gzipStream_.avail_out == 0)
return bytesToRead;
diff --git a/zen/zlib_wrap.h b/zen/zlib_wrap.h
index 3db609da..41d7428a 100644
--- a/zen/zlib_wrap.h
+++ b/zen/zlib_wrap.h
@@ -113,7 +113,7 @@ BinContainer decompress(const BinContainer& stream) //throw SysError
&*contOut.begin(),
static_cast<size_t>(uncompressedSize)); //throw SysError
if (bytesWritten != static_cast<size_t>(uncompressedSize))
- throw SysError(L"zlib error: bytes written != uncompressed size");
+ throw SysError(formatSystemError("zlib_decompress", L"", L"bytes written != uncompressed size."));
}
return contOut;
}
diff --git a/zen/zstring.cpp b/zen/zstring.cpp
index 82082df0..8b16e02d 100644
--- a/zen/zstring.cpp
+++ b/zen/zstring.cpp
@@ -59,7 +59,7 @@ Zstring getUnicodeNormalForm(const Zstring& str)
{
gchar* outStr = ::g_utf8_normalize(str.c_str(), str.length(), G_NORMALIZE_DEFAULT_COMPOSE);
if (!outStr)
- throw SysError(L"g_utf8_normalize: conversion failed. (" + utfTo<std::wstring>(str) + L')');
+ throw SysError(formatSystemError("g_utf8_normalize(" + utfTo<std::string>(str) + ')', L"", L"Conversion failed."));
ZEN_ON_SCOPE_EXIT(::g_free(outStr));
return outStr;
diff --git a/zen/zstring.h b/zen/zstring.h
index d5d8c588..e34d14a3 100644
--- a/zen/zstring.h
+++ b/zen/zstring.h
@@ -35,7 +35,7 @@ Zstring makeUpperCopy(const Zstring& str);
Zstring getUnicodeNormalForm(const Zstring& str);
// "In fact, Unicode declares that there is an equivalence relationship between decomposed and composed sequences,
// and conformant software should not treat canonically equivalent sequences, whether composed or decomposed or something in between, as different."
-// http://www.win.tue.nl/~aeb/linux/uc/nfc_vs_nfd.html
+// https://www.win.tue.nl/~aeb/linux/uc/nfc_vs_nfd.html
struct LessUnicodeNormal { bool operator()(const Zstring& lhs, const Zstring& rhs) const { return getUnicodeNormalForm(lhs) < getUnicodeNormalForm(rhs);} };
bgstack15