diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/basic_math.h | 2 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 24 | ||||
-rw-r--r-- | zen/dir_watcher.h | 25 | ||||
-rw-r--r-- | zen/file_access.cpp | 66 | ||||
-rw-r--r-- | zen/file_io.cpp | 18 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 8 | ||||
-rw-r--r-- | zen/guid.h | 6 | ||||
-rw-r--r-- | zen/http.cpp | 18 | ||||
-rw-r--r-- | zen/http.h | 2 | ||||
-rw-r--r-- | zen/open_ssl.cpp | 193 | ||||
-rw-r--r-- | zen/perf.h | 2 | ||||
-rw-r--r-- | zen/process_priority.cpp | 4 | ||||
-rw-r--r-- | zen/recycler.cpp | 13 | ||||
-rw-r--r-- | zen/shell_execute.cpp | 250 | ||||
-rw-r--r-- | zen/shell_execute.h | 99 | ||||
-rw-r--r-- | zen/shutdown.cpp | 28 | ||||
-rw-r--r-- | zen/socket.h | 20 | ||||
-rw-r--r-- | zen/string_base.h | 2 | ||||
-rw-r--r-- | zen/string_tools.h | 15 | ||||
-rw-r--r-- | zen/symlink_target.h | 6 | ||||
-rw-r--r-- | zen/sys_error.cpp | 23 | ||||
-rw-r--r-- | zen/sys_error.h | 4 | ||||
-rw-r--r-- | zen/system.cpp | 21 | ||||
-rw-r--r-- | zen/time.h | 2 | ||||
-rw-r--r-- | zen/zlib_wrap.cpp | 8 | ||||
-rw-r--r-- | zen/zlib_wrap.h | 2 | ||||
-rw-r--r-- | zen/zstring.cpp | 2 | ||||
-rw-r--r-- | zen/zstring.h | 2 |
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) { @@ -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); } @@ -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 @@ -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()); } @@ -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);} }; |