From 4d8fae7636ad45900f22253621b9f7d51d0b646e Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Tue, 7 Apr 2026 16:53:55 +0200 Subject: incremental dehydrate (#921) - Feature: Incremental CAS-based hydration/dehydration replacing the previous full-copy approach - Feature: S3 hydration backend with multipart upload/download support - Feature: Configurable thread pools for hub instance provisioning and hydration `--hub-instance-provision-threads` defaults to `max(cpu_count / 4, 2)`. Set to 0 for synchronous operation. `--hub-hydration-threads` defaults to `max(cpu_count / 4, 2)`. Set to 0 for synchronous operation. - Improvement: Hub triggers GC on instance before deprovisioning to compact storage before dehydration - Improvement: GC status now reports pending triggers as running - Improvement: S3 client debug logging gated behind verbose mode to reduce log noise at default verbosity - Improvement: Hub dashboard Resources tile now shows total memory - Improvement: `filesystemutils` moved from `zenremotestore` to `zenutil` for broader reuse - Improvement: Hub uses separate provision and hydration worker pools to avoid deadlocks - Improvement: Hibernate/wake/deprovision on non-existent or already-in-target-state modules are idempotent - Improvement: `ScopedTemporaryDirectory` with empty path now creates a temporary directory instead of asserting --- src/zencore/filesystem.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'src/zencore/filesystem.cpp') diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp index a63594be9..cae6ba5d4 100644 --- a/src/zencore/filesystem.cpp +++ b/src/zencore/filesystem.cpp @@ -114,6 +114,20 @@ struct ScopedFd explicit operator bool() const { return Fd >= 0; } }; +# if ZEN_PLATFORM_LINUX +inline uint64_t +StatMtime100Ns(const struct stat& S) +{ + return uint64_t(S.st_mtim.tv_sec) * 10'000'000ULL + uint64_t(S.st_mtim.tv_nsec) / 100; +} +# elif ZEN_PLATFORM_MAC +inline uint64_t +StatMtime100Ns(const struct stat& S) +{ + return uint64_t(S.st_mtimespec.tv_sec) * 10'000'000ULL + uint64_t(S.st_mtimespec.tv_nsec) / 100; +} +# endif + #endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC #if ZEN_PLATFORM_WINDOWS @@ -2123,7 +2137,7 @@ FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, Tr } else if (S_ISREG(Stat.st_mode)) { - Visitor.VisitFile(RootDir, FileName, Stat.st_size, gsl::narrow(Stat.st_mode), gsl::narrow(Stat.st_mtime)); + Visitor.VisitFile(RootDir, FileName, Stat.st_size, gsl::narrow(Stat.st_mode), StatMtime100Ns(Stat)); } else { @@ -2507,7 +2521,7 @@ GetModificationTickFromHandle(void* NativeHandle, std::error_code& Ec) struct stat Stat; if (0 == fstat(Fd, &Stat)) { - return gsl::narrow(Stat.st_mtime); + return StatMtime100Ns(Stat); } #endif Ec = MakeErrorCodeFromLastError(); @@ -2546,7 +2560,7 @@ GetModificationTickFromPath(const std::filesystem::path& Filename) { ThrowLastError(fmt::format("Failed to get mode of file {}", Filename)); } - return gsl::narrow(Stat.st_mtime); + return StatMtime100Ns(Stat); #endif } @@ -2589,7 +2603,7 @@ TryGetFileProperties(const std::filesystem::path& Path, { return false; } - OutModificationTick = gsl::narrow(Stat.st_mtime); + OutModificationTick = StatMtime100Ns(Stat); OutSize = size_t(Stat.st_size); OutNativeModeOrAttributes = (uint32_t)Stat.st_mode; return true; -- cgit v1.2.3 From c49e5b15e0f86080d7d33e4e31aecfb701f8f96f Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 13 Apr 2026 14:05:03 +0200 Subject: Some minor polish from tourist branch (#949) - Replace per-type fmt::formatter specializations (StringBuilderBase, NiceBase) with a single generic formatter using a HasStringViewConversion concept - Add ThousandsNum for comma-separated integer formatting (e.g. "1,234,567") - Thread naming now accepts a sort hint for trace ordering - Fix main thread trace registration to use actual thread ID and sort first - Add ExpandEnvironmentVariables() for expanding %VAR% references in strings, with tests - Add ParseHexBytes() overload with expected byte count validation - Add Flag_BelowNormalPriority to CreateProcOptions (BELOW_NORMAL_PRIORITY_CLASS on Windows, setpriority on POSIX) - Add PrettyScroll progress bar mode that pins the status line to the bottom of the terminal using scroll regions, with signal handler cleanup for Ctrl+C/SIGTERM --- src/zencore/filesystem.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'src/zencore/filesystem.cpp') diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp index cae6ba5d4..518146648 100644 --- a/src/zencore/filesystem.cpp +++ b/src/zencore/filesystem.cpp @@ -2977,6 +2977,35 @@ GetEnvVariable(std::string_view VariableName) return ""; } +std::string +ExpandEnvironmentVariables(std::string_view Input) +{ + std::string Result; + Result.reserve(Input.size()); + + for (size_t i = 0; i < Input.size(); ++i) + { + if (Input[i] == '%') + { + size_t End = Input.find('%', i + 1); + if (End != std::string_view::npos && End > i + 1) + { + std::string_view VarName = Input.substr(i + 1, End - i - 1); + std::string Value = GetEnvVariable(VarName); + if (!Value.empty()) + { + Result += Value; + i = End; + continue; + } + } + } + Result += Input[i]; + } + + return Result; +} + std::error_code RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles) { @@ -4122,6 +4151,45 @@ TEST_CASE("filesystem.MakeSafeAbsolutePath") # endif // ZEN_PLATFORM_WINDOWS } +TEST_CASE("ExpandEnvironmentVariables") +{ + // No variables — pass-through + CHECK_EQ(ExpandEnvironmentVariables("plain/path"), "plain/path"); + CHECK_EQ(ExpandEnvironmentVariables(""), ""); + + // Single percent sign is not a variable reference + CHECK_EQ(ExpandEnvironmentVariables("50%"), "50%"); + + // Empty variable name (%%) is not expanded + CHECK_EQ(ExpandEnvironmentVariables("%%"), "%%"); + + // Known variable +# if ZEN_PLATFORM_WINDOWS + // PATH is always set on Windows + std::string PathValue = GetEnvVariable("PATH"); + CHECK(!PathValue.empty()); + CHECK_EQ(ExpandEnvironmentVariables("%PATH%"), PathValue); + CHECK_EQ(ExpandEnvironmentVariables("prefix/%PATH%/suffix"), "prefix/" + PathValue + "/suffix"); +# else + std::string HomeValue = GetEnvVariable("HOME"); + CHECK(!HomeValue.empty()); + CHECK_EQ(ExpandEnvironmentVariables("%HOME%"), HomeValue); + CHECK_EQ(ExpandEnvironmentVariables("prefix/%HOME%/suffix"), "prefix/" + HomeValue + "/suffix"); +# endif + + // Unknown variable is left unexpanded + CHECK_EQ(ExpandEnvironmentVariables("%ZEN_UNLIKELY_SET_VAR_12345%"), "%ZEN_UNLIKELY_SET_VAR_12345%"); + + // Multiple variables +# if ZEN_PLATFORM_WINDOWS + std::string OSValue = GetEnvVariable("OS"); + if (!OSValue.empty()) + { + CHECK_EQ(ExpandEnvironmentVariables("%PATH%/%OS%"), PathValue + "/" + OSValue); + } +# endif +} + TEST_SUITE_END(); #endif -- cgit v1.2.3 From 3d59b5d7036c35fe484d052ff32dbdc9d0a75cf7 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Mon, 13 Apr 2026 19:17:09 +0200 Subject: fix utf characters in source code (#953) --- src/zencore/filesystem.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/zencore/filesystem.cpp') diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp index 518146648..70d0f32b3 100644 --- a/src/zencore/filesystem.cpp +++ b/src/zencore/filesystem.cpp @@ -3329,12 +3329,12 @@ MakeSafeAbsolutePathInPlace(std::filesystem::path& Path) { if (PathString.starts_with(UncPrefix)) { - // UNC path: \\server\share → \\?\UNC\server\share + // UNC path: \\server\share -> \\?\UNC\server\share PathString.replace(0, UncPrefix.size(), LongPathUncPrefix); } else { - // Local path: C:\foo → \\?\C:\foo + // Local path: C:\foo -> \\?\C:\foo PathString.insert(0, LongPathPrefix); } Path = PathString; @@ -4153,7 +4153,7 @@ TEST_CASE("filesystem.MakeSafeAbsolutePath") TEST_CASE("ExpandEnvironmentVariables") { - // No variables — pass-through + // No variables - pass-through CHECK_EQ(ExpandEnvironmentVariables("plain/path"), "plain/path"); CHECK_EQ(ExpandEnvironmentVariables(""), ""); -- cgit v1.2.3 From e7d3065cf47c9d8430be409a0c53422aea2e3532 Mon Sep 17 00:00:00 2001 From: zousar Date: Mon, 13 Apr 2026 14:08:09 -0600 Subject: Stop using O_CLOEXEC in shm_open --- src/zencore/filesystem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/zencore/filesystem.cpp') diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp index 70d0f32b3..debe51cc9 100644 --- a/src/zencore/filesystem.cpp +++ b/src/zencore/filesystem.cpp @@ -3462,11 +3462,12 @@ public: ZEN_UNUSED(SystemGlobal); std::string InstanceMapName = fmt::format("/{}", Name); - ScopedFd FdGuard(shm_open(InstanceMapName.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666)); + ScopedFd FdGuard(shm_open(InstanceMapName.c_str(), O_RDWR | O_CREAT, 0666)); if (!FdGuard) { return {}; } + fcntl(FdGuard.Fd, F_SETFD, FD_CLOEXEC); fchmod(FdGuard.Fd, 0666); int Result = ftruncate(FdGuard.Fd, Size); -- cgit v1.2.3 From ecd4acb89406cfa573d1819532dcaec2c44113f5 Mon Sep 17 00:00:00 2001 From: zousar Date: Mon, 13 Apr 2026 14:42:13 -0600 Subject: Removing CLOEXEC use on shared memory descriptors According to documentation, shm_open already sets O_CLOEXEC. --- src/zencore/filesystem.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'src/zencore/filesystem.cpp') diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp index debe51cc9..5160bfdc6 100644 --- a/src/zencore/filesystem.cpp +++ b/src/zencore/filesystem.cpp @@ -3467,7 +3467,6 @@ public: { return {}; } - fcntl(FdGuard.Fd, F_SETFD, FD_CLOEXEC); fchmod(FdGuard.Fd, 0666); int Result = ftruncate(FdGuard.Fd, Size); -- cgit v1.2.3 From 28d373a3ca31d8c7d726b78e0ac9ddae1f60d95b Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 20 Apr 2026 10:10:55 +0200 Subject: zencore: promote ScopedEnvVar to a shared filesystem helper (#979) - Moves the RAII `ScopedEnvVar` helper out of `hydration.cpp`'s anonymous test namespace and into `zencore/filesystem.{h,cpp}` next to `GetEnvVariable` so it can be reused by other subsystems. - Makes the class non-copyable/non-movable and moves its members to `private`. --- src/zencore/filesystem.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) (limited to 'src/zencore/filesystem.cpp') diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp index 5160bfdc6..08e921293 100644 --- a/src/zencore/filesystem.cpp +++ b/src/zencore/filesystem.cpp @@ -3006,6 +3006,54 @@ ExpandEnvironmentVariables(std::string_view Input) return Result; } +ScopedEnvVar::ScopedEnvVar(std::string_view Name, std::string_view Value) : m_Name(Name) +{ +#if ZEN_PLATFORM_WINDOWS + // Use the raw API so we can distinguish "not set" (ERROR_ENVVAR_NOT_FOUND) + // from "set to empty string" (returns 0 with no error). + char Buf[1]; + DWORD Len = GetEnvironmentVariableA(m_Name.c_str(), Buf, sizeof(Buf)); + if (Len == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) + { + m_OldValue = std::nullopt; + } + else + { + // Len == 0 with no error: variable exists but is empty. + // Len > sizeof(Buf): value is non-empty; Len is the required buffer size + // (including null terminator) - allocate and re-read. + std::string Old(Len == 0 ? 0 : Len - 1, '\0'); + if (Len > sizeof(Buf)) + { + GetEnvironmentVariableA(m_Name.c_str(), Old.data(), Len); + } + m_OldValue = std::move(Old); + } + SetEnvironmentVariableA(m_Name.c_str(), std::string(Value).c_str()); +#else + // getenv returns nullptr when not set, "" when set to empty string. + const char* Existing = getenv(m_Name.c_str()); + m_OldValue = Existing ? std::optional(Existing) : std::nullopt; + setenv(m_Name.c_str(), std::string(Value).c_str(), 1); +#endif +} + +ScopedEnvVar::~ScopedEnvVar() +{ +#if ZEN_PLATFORM_WINDOWS + SetEnvironmentVariableA(m_Name.c_str(), m_OldValue.has_value() ? m_OldValue->c_str() : nullptr); +#else + if (m_OldValue.has_value()) + { + setenv(m_Name.c_str(), m_OldValue->c_str(), 1); + } + else + { + unsetenv(m_Name.c_str()); + } +#endif +} + std::error_code RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles) { -- cgit v1.2.3 From 4d828df648b9e5093f3509cbeeb5887de8920ede Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 20 Apr 2026 11:02:38 +0200 Subject: zencore: implement SearchPathForExecutable on POSIX (#981) - The Linux/macOS branch of `SearchPathForExecutable` was previously a no-op that returned the input unchanged. Callers passing a bare executable name (e.g. `llvm-symbolizer`) got the same bare name back even when the binary lived elsewhere on `PATH`. - Now walks `$PATH` like `execvp` does: skip the search if the input contains a `/`, try each colon-separated entry (empty entry == cwd), and return the first candidate that is both a regular file and executable by the current user. Falls back to returning the input unchanged if nothing matches, preserving the previous behavior for the no-match case. - Windows branch is unchanged (still uses `SearchPathW`). --- src/zencore/filesystem.cpp | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) (limited to 'src/zencore/filesystem.cpp') diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp index 08e921293..03e04c77c 100644 --- a/src/zencore/filesystem.cpp +++ b/src/zencore/filesystem.cpp @@ -3183,7 +3183,38 @@ SearchPathForExecutable(std::string_view ExecutableName) return PathBuffer.get(); #else - return ExecutableName; + // If the name already contains a path separator, don't search PATH + // (matches the shell / execvp semantics). + if (ExecutableName.find('/') != std::string_view::npos) + { + return std::filesystem::path(ExecutableName); + } + + const char* PathEnv = ::getenv("PATH"); + if (PathEnv == nullptr || *PathEnv == '\0') + { + return std::filesystem::path(ExecutableName); + } + + std::string_view PathView(PathEnv); + while (!PathView.empty()) + { + size_t Sep = PathView.find(':'); + std::string_view Dir = (Sep == std::string_view::npos) ? PathView : PathView.substr(0, Sep); + PathView = (Sep == std::string_view::npos) ? std::string_view{} : PathView.substr(Sep + 1); + + // An empty entry in PATH is interpreted as the current directory. + std::filesystem::path Candidate = Dir.empty() ? std::filesystem::path(".") : std::filesystem::path(Dir); + Candidate /= ExecutableName; + + std::error_code Ec; + if (std::filesystem::is_regular_file(Candidate, Ec) && ::access(Candidate.c_str(), X_OK) == 0) + { + return Candidate; + } + } + + return std::filesystem::path(ExecutableName); #endif } -- cgit v1.2.3 From 82e222bf23dee04e6fb825037fbb4d86a9571ce0 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Tue, 21 Apr 2026 17:22:18 +0200 Subject: filesystem.h surface error codes (#998) - Improvement: File copy, scan, clone, and move operations now report the underlying OS error in failure messages --- src/zencore/filesystem.cpp | 229 ++++++++++++++++++++++----------------------- 1 file changed, 113 insertions(+), 116 deletions(-) (limited to 'src/zencore/filesystem.cpp') diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp index 03e04c77c..281cb8e2e 100644 --- a/src/zencore/filesystem.cpp +++ b/src/zencore/filesystem.cpp @@ -1106,8 +1106,6 @@ TryCloneFile(void* SourceNativeHandle, void* TargetNativeHandle) FILE_DISPOSITION_INFO FileDisposition = {TRUE}; if (!SetFileInformationByHandle(TargetFile, FileDispositionInfo, &FileDisposition, sizeof FileDisposition)) { - const DWORD ErrorCode = ::GetLastError(); - SetLastError(ErrorCode); return false; } @@ -1199,7 +1197,7 @@ TryCloneFile(void* SourceNativeHandle, void* TargetNativeHandle) } #endif // ZEN_PLATFORM_WINDOWS -bool +std::error_code TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToPath) { #if ZEN_PLATFORM_WINDOWS @@ -1213,7 +1211,7 @@ TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path& if (FromFile == INVALID_HANDLE_VALUE) { FromFile.Detach(); - return false; + return MakeErrorCodeFromLastError(); } SetFileAttributesW(ToPath.c_str(), FILE_ATTRIBUTE_NORMAL); @@ -1229,16 +1227,20 @@ TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path& if (TargetFile == INVALID_HANDLE_VALUE) { TargetFile.Detach(); - return false; + return MakeErrorCodeFromLastError(); } - return TryCloneFile((void*)FromFile.m_Handle, (void*)TargetFile.m_Handle); + if (!TryCloneFile((void*)FromFile.m_Handle, (void*)TargetFile.m_Handle)) + { + return MakeErrorCodeFromLastError(); + } + return {}; #elif ZEN_PLATFORM_LINUX // The 'from' file ScopedFd FromFd(open(FromPath.c_str(), O_RDONLY | O_CLOEXEC)); if (!FromFd) { - return false; + return MakeErrorCodeFromLastError(); } // Remove any existing target so we can create a fresh clone @@ -1248,19 +1250,20 @@ TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path& ScopedFd ToFd(open(ToPath.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0666)); if (!ToFd) { - return false; + return MakeErrorCodeFromLastError(); } if (ioctl(ToFd.Fd, FICLONE, FromFd.Fd) != 0) { // Clone not supported by this filesystem or files are on different volumes. // Remove the empty target file we created. - ToFd = ScopedFd(); + std::error_code Ec = MakeErrorCodeFromLastError(); + ToFd = ScopedFd(); unlink(ToPath.c_str()); - return false; + return Ec; } - return true; + return {}; #elif ZEN_PLATFORM_MAC // Remove any existing target - clonefile() requires the destination not exist unlink(ToPath.c_str()); @@ -1268,70 +1271,63 @@ TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path& if (clonefile(FromPath.c_str(), ToPath.c_str(), CLONE_NOFOLLOW) != 0) { // Clone not supported (non-APFS) or files are on different volumes - return false; + return MakeErrorCodeFromLastError(); } - return true; + return {}; #endif // ZEN_PLATFORM_WINDOWS } -void -CopyFile(const std::filesystem::path& FromPath, - const std::filesystem::path& ToPath, - const CopyFileOptions& Options, - std::error_code& OutErrorCode) -{ - OutErrorCode.clear(); - - bool Success = CopyFile(FromPath, ToPath, Options); - - if (!Success) - { - OutErrorCode = MakeErrorCodeFromLastError(); - } -} - -bool +std::error_code CopyFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToPath, const CopyFileOptions& Options) { - bool Success = false; - if (Options.EnableClone) { - Success = TryCloneFile(FromPath.native(), ToPath.native()); - if (Success) + std::error_code CloneEc = TryCloneFile(FromPath.native(), ToPath.native()); + if (CloneEc) { - return true; + if (Options.MustClone) + { + ZEN_ERROR("CloneFile() failed for {} -> {}: {}", FromPath, ToPath, CloneEc.message()); + return CloneEc; + } + } + else + { + return {}; } } - - if (Options.MustClone) + else if (Options.MustClone) { - ZEN_ERROR("CloneFile() failed for {} -> {}", FromPath, ToPath); - return false; + ZEN_ERROR("CloneFile() required but not enabled for {} -> {}", FromPath, ToPath); + return std::make_error_code(std::errc::invalid_argument); } #if ZEN_PLATFORM_WINDOWS BOOL CancelFlag = FALSE; - Success = !!::CopyFileExW(FromPath.c_str(), - ToPath.c_str(), - /* lpProgressRoutine */ nullptr, - /* lpData */ nullptr, - &CancelFlag, - /* dwCopyFlags */ 0); + BOOL Success = ::CopyFileExW(FromPath.c_str(), + ToPath.c_str(), + /* lpProgressRoutine */ nullptr, + /* lpData */ nullptr, + &CancelFlag, + /* dwCopyFlags */ 0); + if (!Success) + { + return MakeErrorCodeFromLastError(); + } #else // From file ScopedFd FromFd(open(FromPath.c_str(), O_RDONLY | O_CLOEXEC)); if (!FromFd) { - ThrowLastError(fmt::format("failed to open file {}", FromPath)); + return MakeErrorCodeFromLastError(); } // To file ScopedFd ToFd(open(ToPath.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0666)); if (!ToFd) { - ThrowLastError(fmt::format("failed to create file {}", ToPath)); + return MakeErrorCodeFromLastError(); } fchmod(ToFd.Fd, 0666); @@ -1344,32 +1340,36 @@ CopyFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToP ZEN_UNUSED($Ignore); // What's the appropriate error handling here? // Copy impl - const size_t BufferSize = Min(FileSizeBytes, 64u << 10); - void* Buffer = malloc(BufferSize); + const size_t BufferSize = Min(FileSizeBytes, 64u << 10); + void* Buffer = malloc(BufferSize); + std::error_code Result; while (true) { int BytesRead = read(FromFd.Fd, Buffer, BufferSize); - if (BytesRead <= 0) + if (BytesRead < 0) + { + Result = MakeErrorCodeFromLastError(); + break; + } + if (BytesRead == 0) { - Success = (BytesRead == 0); break; } if (write(ToFd.Fd, Buffer, BytesRead) != BytesRead) { - Success = false; + Result = MakeErrorCodeFromLastError(); break; } } free(Buffer); -#endif // ZEN_PLATFORM_WINDOWS - - if (!Success) + if (Result) { - ThrowLastError(fmt::format("file copy from {} to {} failed", FromPath, ToPath)); + return Result; } +#endif // ZEN_PLATFORM_WINDOWS - return true; + return {}; } void @@ -1453,24 +1453,13 @@ CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop ToPath = TargetPath / File; } - try - { - if (zen::CopyFile(FromPath, ToPath, CopyOptions)) - { - ++FileCount; - ByteCount += FileSize; - } - else - { - throw std::runtime_error("CopyFile failed in an unexpected way"); - } - } - catch (const std::exception& Ex) + if (std::error_code CopyEc = zen::CopyFile(FromPath, ToPath, CopyOptions); CopyEc) { ++FailedFileCount; - - throw std::runtime_error(fmt::format("failed to copy '{}' to '{}': '{}'", FromPath, ToPath, Ex.what())); + throw std::system_error(CopyEc, fmt::format("failed to copy '{}' to '{}'", FromPath, ToPath)); } + ++FileCount; + ByteCount += FileSize; } } @@ -1683,17 +1672,17 @@ WriteFile(std::filesystem::path Path, CompositeBuffer InData) WriteFile(Path, DataPtrs.data(), DataPtrs.size()); } -bool +std::error_code MoveToFile(std::filesystem::path Path, IoBuffer Data) { if (!Data.IsWholeFile()) { - return false; + return std::make_error_code(std::errc::invalid_argument); } IoBufferFileReference FileRef; if (!Data.GetFileReference(/* out */ FileRef)) { - return false; + return std::make_error_code(std::errc::invalid_argument); } #if ZEN_PLATFORM_WINDOWS @@ -1709,15 +1698,16 @@ MoveToFile(std::filesystem::path Path, IoBuffer Data) RenameInfo->FileName[FileName.size()] = 0; // Try to move file into place - BOOL Success = SetFileInformationByHandle(ChunkFileHandle, FileRenameInfo, RenameInfo, BufferSize); + BOOL Success = SetFileInformationByHandle(ChunkFileHandle, FileRenameInfo, RenameInfo, BufferSize); + DWORD LastError = Success ? ERROR_SUCCESS : GetLastError(); if (!Success) { - DWORD LastError = GetLastError(); if (LastError == ERROR_PATH_NOT_FOUND) { zen::CreateDirectories(Path.parent_path()); - Success = SetFileInformationByHandle(ChunkFileHandle, FileRenameInfo, RenameInfo, BufferSize); + Success = SetFileInformationByHandle(ChunkFileHandle, FileRenameInfo, RenameInfo, BufferSize); + LastError = Success ? ERROR_SUCCESS : GetLastError(); } if (!Success && (LastError == ERROR_ACCESS_DENIED)) { @@ -1735,23 +1725,29 @@ MoveToFile(std::filesystem::path Path, IoBuffer Data) if (LastError == ERROR_PATH_NOT_FOUND) { zen::CreateDirectories(Path.parent_path()); - Success = ::MoveFile(NativeSourcePath, NativeTargetPath); + Success = ::MoveFile(NativeSourcePath, NativeTargetPath); + LastError = Success ? ERROR_SUCCESS : GetLastError(); } } } + else + { + Memory::Free(RenameInfo); + return Ec; + } } } Memory::Free(RenameInfo); if (!Success) { - return false; + return MakeErrorCode(LastError); } #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC std::error_code Ec; std::filesystem::path SourcePath = PathFromHandle(FileRef.FileHandle, Ec); if (Ec) { - return false; + return Ec; } int Ret = rename(SourcePath.c_str(), Path.c_str()); if (Ret < 0) @@ -1765,11 +1761,11 @@ MoveToFile(std::filesystem::path Path, IoBuffer Data) } if (Ret < 0) { - return false; + return MakeErrorCodeFromLastError(); } #endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC Data.SetDeleteOnClose(false); - return true; + return {}; } IoBuffer @@ -1898,7 +1894,7 @@ ScanFile(void* NativeHandle, } } -bool +std::error_code ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function&& ProcessFunc) { #if ZEN_PLATFORM_WINDOWS @@ -1912,7 +1908,7 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function ReadBuffer(ChunkSize); @@ -1924,7 +1920,7 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function BinScan; - ScanFile(BinPath, 16 << 10, [&](const void* Data, size_t Size) { - const auto* Ptr = (uint8_t*)Data; - BinScan.insert(BinScan.end(), Ptr, Ptr + Size); - }); + std::error_code ScanEc = ScanFile(BinPath, 16 << 10, [&](const void* Data, size_t Size) { + const auto* Ptr = (uint8_t*)Data; + BinScan.insert(BinScan.end(), Ptr, Ptr + Size); + }); + CHECK(!ScanEc); CHECK_EQ(BinRead.Data.size(), 1); CHECK_EQ(BinScan.size(), BinRead.Data[0].GetSize()); } @@ -3946,9 +3943,9 @@ TEST_CASE("TryCloneFile") WriteFile(SrcPath, IoBuffer(IoBuffer::Wrap, Content, sizeof(Content))); CHECK(IsFile(SrcPath)); - bool Cloned = TryCloneFile(SrcPath, DstPath); + std::error_code CloneEc = TryCloneFile(SrcPath, DstPath); - if (Cloned) + if (!CloneEc) { CHECK(IsFile(DstPath)); CHECK_EQ(FileSizeFromPath(DstPath), sizeof(Content)); @@ -3961,7 +3958,7 @@ TEST_CASE("TryCloneFile") else { // Clone not supported on this filesystem - that's okay, just verify it didn't leave debris - ZEN_INFO("TryCloneFile not supported on this filesystem, skipping content check"); + ZEN_INFO("TryCloneFile not supported on this filesystem ({}), skipping content check", CloneEc.message()); } } @@ -3975,9 +3972,9 @@ TEST_CASE("TryCloneFile") WriteFile(DstPath, IoBuffer(IoBuffer::Wrap, OldContent, sizeof(OldContent))); WriteFile(SrcPath, IoBuffer(IoBuffer::Wrap, NewContent, sizeof(NewContent))); - bool Cloned = TryCloneFile(SrcPath, DstPath); + std::error_code CloneEc = TryCloneFile(SrcPath, DstPath); - if (Cloned) + if (!CloneEc) { CHECK_EQ(FileSizeFromPath(DstPath), sizeof(NewContent)); @@ -3992,7 +3989,7 @@ TEST_CASE("TryCloneFile") std::filesystem::path SrcPath = TestBaseDir / "no_such_file.bin"; std::filesystem::path DstPath = TestBaseDir / "dst_nosrc.bin"; - CHECK_FALSE(TryCloneFile(SrcPath, DstPath)); + CHECK(TryCloneFile(SrcPath, DstPath)); CHECK_FALSE(IsFile(DstPath)); } @@ -4014,8 +4011,8 @@ TEST_CASE("CopyFile.Clone") CopyFileOptions Options; Options.EnableClone = true; - bool Success = CopyFile(SrcPath, DstPath, Options); - CHECK(Success); + std::error_code Ec = CopyFile(SrcPath, DstPath, Options); + CHECK(!Ec); CHECK(IsFile(DstPath)); CHECK_EQ(FileSizeFromPath(DstPath), sizeof(Content)); @@ -4030,8 +4027,8 @@ TEST_CASE("CopyFile.Clone") CopyFileOptions Options; Options.EnableClone = false; - bool Success = CopyFile(SrcPath, DstPath, Options); - CHECK(Success); + std::error_code Ec = CopyFile(SrcPath, DstPath, Options); + CHECK(!Ec); CHECK(IsFile(DstPath)); CHECK_EQ(FileSizeFromPath(DstPath), sizeof(Content)); -- cgit v1.2.3