aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2026-04-21 17:22:18 +0200
committerGitHub Enterprise <[email protected]>2026-04-21 17:22:18 +0200
commit82e222bf23dee04e6fb825037fbb4d86a9571ce0 (patch)
tree007b805500a5e23167ae8acc977efc3a6298d826
parentimproved s3 hydration (#997) (diff)
downloadarchived-zen-82e222bf23dee04e6fb825037fbb4d86a9571ce0.tar.xz
archived-zen-82e222bf23dee04e6fb825037fbb4d86a9571ce0.zip
filesystem.h surface error codes (#998)
- Improvement: File copy, scan, clone, and move operations now report the underlying OS error in failure messages
-rw-r--r--CHANGELOG.md17
-rw-r--r--src/zen/cmds/admin_cmd.cpp13
-rw-r--r--src/zen/cmds/bench_cmd.cpp2
-rw-r--r--src/zen/cmds/cache_cmd.cpp2
-rw-r--r--src/zen/cmds/dedup_cmd.cpp13
-rw-r--r--src/zen/cmds/projectstore_cmd.cpp2
-rw-r--r--src/zen/cmds/service_cmd.cpp4
-rw-r--r--src/zencore/filesystem.cpp229
-rw-r--r--src/zencore/include/zencore/filesystem.h42
-rw-r--r--src/zenserver/hub/hydration.cpp12
-rw-r--r--src/zenstore/filecas.cpp4
-rw-r--r--src/zenutil/filesystemutils.cpp21
12 files changed, 190 insertions, 171 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f4727290d..b54cd4f5f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,14 +14,6 @@
- `zen history --print` prints the reconstructed command line of the selected row instead of launching it
- `--enable-execution-history` global option on both binaries (default `true`) to opt out per invocation
- The history file is attached to Sentry crash reports (alongside the existing zenserver log)
-- Bugfix: `zen builds download --download-spec-path <file>` now resolves the manifest path relative to `--local-path` when relative; previously the computed absolute path was discarded and the file was looked up relative to the current working directory
-- Bugfix: `zen project oplog-export --builds-metadata-path <file>` now fails with a descriptive error when the metadata JSON is malformed; previously a JSON parse failure was silently ignored and a truncated compact-binary payload was shipped to the server
-- Bugfix: `zen project oplog-mirror` now rejects filenames from the remote oplog that could escape the mirror root (absolute paths, drive letters, UNC / device path prefixes, or `..` components); previously a malicious or compromised remote oplog could cause writes to arbitrary filesystem locations
-- Bugfix: `zen project oplog-mirror` now handles Ctrl-C cleanly by signalling the worker pool to abort; previously SIGINT killed the process mid-transfer
-- Bugfix: Malformed `--build-id` / `--build-part-id` values are now rejected with an `OptionParseException`; previously the `Oid` parser aborted the process on non-hex or wrong-length input
-- Bugfix: `zen cache get` now requires `--output-path` unless `--as-text` is set; previously an empty path silently wrote a file named after the value key / attachment hash into the process's current working directory
-- Bugfix: `zen project oplog-export` and `oplog-import` "[builds] ..." description messages now print the actual builds URL; previously they showed the (often empty) cloud URL due to a copy-paste error
-- Bugfix: `zen` auth storage now uses a machine-specific random AES key/IV persisted to `<system-root>/auth/machinekey.dat` when `--encryption-aes-key`/`--encryption-aes-iv` are not supplied; previously a hardcoded constant visible in the source was used, letting anyone with source access decrypt persisted OIDC refresh tokens. Supplying only one of the two flags is now rejected rather than silently substituting the hardcoded default for the missing half. Existing auth state files will fail to decrypt once after upgrade and re-auth automatically.
- Improvement: `zen` auth machine key is now wrapped with OS-protected storage so the on-disk blob cannot be unwrapped off-machine or from a backup without also stealing the user's OS master key: Windows DPAPI at per-user scope, macOS Keychain Services (GenericPassword, `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly`), and Linux libsecret (opt-in via `--zenlibsecret=yes`, default off because headless servers typically have no Secret Service daemon). On platforms/configurations without wrapping available, the file falls back to raw bytes with `0600` permissions.
- Improvement: `tourist` trace-analysis library and `raw_pdb` vendored under `thirdparty/`; tourist made cross-platform (Windows/Linux/macOS)
- Improvement: Trace viewer HTML/JS frontend bundled via `zen` frontend zip build rule; `ZipFs` moved from `zenserver` into `zenhttp` for reuse
@@ -40,6 +32,7 @@
- Improvement: `zen builds download`, `zen builds ls`, and `zen builds prime-cache` now default to the part named `default` when no `--build-part-id`/`--build-part-name` is given (previously all parts were selected). Pass `--build-part-name=*` to select all parts.
- Improvement: Hub Consul service registration and deregistration are now dispatched on a dedicated background thread so instance state transitions no longer stall when the Consul agent is slow or unreachable
- Improvement: `ZenCacheStore` namespace lookups use transparent string hashing so hot-path `Get`/`Put` calls no longer allocate a `std::string` per request
+- Improvement: File copy, scan, clone, and move operations now report the underlying OS error in failure messages
- Improvement: Hub shares a single S3 client and IMDS credential provider across all modules, reducing IMDS load and surviving transient IMDS blips during bulk provisioning
- Improvement: Hub validates hydration config at startup; bad `--hub-hydration-target-spec` or `--hub-hydration-target-config` now fails `zen hub` at boot instead of per-module at first hydrate
- Improvement: S3 hydration multipart chunk size configurable via `settings.chunk-size` (default 32 MiB)
@@ -52,6 +45,14 @@
- Bugfix: Structured cache PUT errors with a detail body no longer write the HTTP response twice
- Bugfix: Structured cache endpoints now return 405 Method Not Allowed for unsupported verbs instead of hanging the client
- Bugfix: `exec$/replay-recording` now clamps `thread_count` to at most `max(HW concurrency * 4, 64)` so a bogus value cannot spawn an unbounded thread pool
+- Bugfix: `zen builds download --download-spec-path <file>` now resolves the manifest path relative to `--local-path` when relative; previously the computed absolute path was discarded and the file was looked up relative to the current working directory
+- Bugfix: `zen project oplog-export --builds-metadata-path <file>` now fails with a descriptive error when the metadata JSON is malformed; previously a JSON parse failure was silently ignored and a truncated compact-binary payload was shipped to the server
+- Bugfix: `zen project oplog-mirror` now rejects filenames from the remote oplog that could escape the mirror root (absolute paths, drive letters, UNC / device path prefixes, or `..` components); previously a malicious or compromised remote oplog could cause writes to arbitrary filesystem locations
+- Bugfix: `zen project oplog-mirror` now handles Ctrl-C cleanly by signalling the worker pool to abort; previously SIGINT killed the process mid-transfer
+- Bugfix: Malformed `--build-id` / `--build-part-id` values are now rejected with an `OptionParseException`; previously the `Oid` parser aborted the process on non-hex or wrong-length input
+- Bugfix: `zen cache get` now requires `--output-path` unless `--as-text` is set; previously an empty path silently wrote a file named after the value key / attachment hash into the process's current working directory
+- Bugfix: `zen project oplog-export` and `oplog-import` "[builds] ..." description messages now print the actual builds URL; previously they showed the (often empty) cloud URL due to a copy-paste error
+- Bugfix: `zen` auth storage now uses a machine-specific random AES key/IV persisted to `<system-root>/auth/machinekey.dat` when `--encryption-aes-key`/`--encryption-aes-iv` are not supplied; previously a hardcoded constant visible in the source was used, letting anyone with source access decrypt persisted OIDC refresh tokens. Supplying only one of the two flags is now rejected rather than silently substituting the hardcoded default for the missing half. Existing auth state files will fail to decrypt once after upgrade and re-auth automatically.
## 5.8.5
- Improvement: Session service and endpoint moved from storage server to base server class; now available in hub, compute, and proxy modes
diff --git a/src/zen/cmds/admin_cmd.cpp b/src/zen/cmds/admin_cmd.cpp
index 12ffd49aa..2580517fa 100644
--- a/src/zen/cmds/admin_cmd.cpp
+++ b/src/zen/cmds/admin_cmd.cpp
@@ -489,9 +489,10 @@ LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
throw std::runtime_error(fmt::format("Failed to retrieve {} log path", SourceName));
}
- if (!CopyFile(SourcePath, TargetPath, {}))
+ if (std::error_code Ec = CopyFile(SourcePath, TargetPath, {}); Ec)
{
- throw std::runtime_error(
+ throw std::system_error(
+ Ec,
fmt::format("Failed to copy {} log file {} to output file '{}'", SourceName, SourcePath, TargetPath));
}
};
@@ -585,7 +586,10 @@ Copy(const std::filesystem::path& Source, const std::filesystem::path& Target)
CreateDirectories(Target.parent_path());
CopyFileOptions Options;
- CopyFile(Source, Target, Options);
+ if (std::error_code Ec = CopyFile(Source, Target, Options); Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to copy '{}' to '{}'", Source, Target));
+ }
}
static bool
@@ -599,7 +603,8 @@ TryCopy(const std::filesystem::path& Source, const std::filesystem::path& Target
CreateDirectories(Target.parent_path());
CopyFileOptions Options;
- return CopyFile(Source, Target, Options);
+ std::error_code Ec = CopyFile(Source, Target, Options);
+ return !Ec;
}
void
diff --git a/src/zen/cmds/bench_cmd.cpp b/src/zen/cmds/bench_cmd.cpp
index b1639105a..c935179e2 100644
--- a/src/zen/cmds/bench_cmd.cpp
+++ b/src/zen/cmds/bench_cmd.cpp
@@ -1661,7 +1661,7 @@ BenchDiskSubCmd::RunClone(const std::filesystem::path& Dir)
try
{
std::filesystem::path DstPath = Dir / fmt::format("bench_clone_{}.tmp", FileIndex);
- if (TryCloneFile(SrcPath, DstPath))
+ if (std::error_code CloneEc = TryCloneFile(SrcPath, DstPath); !CloneEc)
{
CloneCount.fetch_add(1, std::memory_order_relaxed);
}
diff --git a/src/zen/cmds/cache_cmd.cpp b/src/zen/cmds/cache_cmd.cpp
index c03284462..f93a5318c 100644
--- a/src/zen/cmds/cache_cmd.cpp
+++ b/src/zen/cmds/cache_cmd.cpp
@@ -759,7 +759,7 @@ CacheGetSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
}
else
{
- if (MoveToFile(m_OutputPath, ChunkData))
+ if (std::error_code MoveEc = MoveToFile(m_OutputPath, ChunkData); MoveEc)
{
// The file was renamed into place; clearing DeleteOnClose prevents
// the move'd-out file at m_OutputPath from being deleted when the
diff --git a/src/zen/cmds/dedup_cmd.cpp b/src/zen/cmds/dedup_cmd.cpp
index 9ef50a97d..18ad56aec 100644
--- a/src/zen/cmds/dedup_cmd.cpp
+++ b/src/zen/cmds/dedup_cmd.cpp
@@ -240,7 +240,12 @@ DedupCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
zen::BLAKE3Stream b3s;
- zen::ScanFile(Entry.path(), 64 * 1024, [&](const void* Data, size_t Size) { b3s.Append(Data, Size); });
+ if (std::error_code ScanEc =
+ zen::ScanFile(Entry.path(), 64 * 1024, [&](const void* Data, size_t Size) { b3s.Append(Data, Size); });
+ ScanEc)
+ {
+ throw std::system_error(ScanEc, fmt::format("Failed to scan file '{}'", Entry.path()));
+ }
Hash = b3s.GetHash();
}
@@ -279,7 +284,11 @@ DedupCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
Options.EnableClone = true;
Options.MustClone = true;
- zen::CopyFile(Dupe->path(), Entry.path(), Options);
+ if (std::error_code Ec = zen::CopyFile(Dupe->path(), Entry.path(), Options); Ec)
+ {
+ ZEN_ERROR("Failed to clone '{}' to '{}': {}", Dupe->path(), Entry.path(), Ec.message());
+ continue;
+ }
DupeBytes += Entry.file_size();
}
diff --git a/src/zen/cmds/projectstore_cmd.cpp b/src/zen/cmds/projectstore_cmd.cpp
index d9ce1cfa7..c6a3434f8 100644
--- a/src/zen/cmds/projectstore_cmd.cpp
+++ b/src/zen/cmds/projectstore_cmd.cpp
@@ -2191,7 +2191,7 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg
IoBuffer ChunkData =
m_Decompress ? TryDecompress(ChunkResponse.ResponsePayload) : ChunkResponse.ResponsePayload;
- if (!MoveToFile(TargetPath, ChunkData))
+ if (std::error_code MoveEc = MoveToFile(TargetPath, ChunkData); MoveEc)
{
WriteFile(TargetPath, ChunkData);
}
diff --git a/src/zen/cmds/service_cmd.cpp b/src/zen/cmds/service_cmd.cpp
index 5e284cbdf..c43c4e614 100644
--- a/src/zen/cmds/service_cmd.cpp
+++ b/src/zen/cmds/service_cmd.cpp
@@ -500,9 +500,9 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
{
std::filesystem::path Destination = m_InstallPath / File.filename();
- if (!CopyFile(File, Destination, {.EnableClone = false}))
+ if (std::error_code CopyEc = CopyFile(File, Destination, {.EnableClone = false}); CopyEc)
{
- throw std::runtime_error(fmt::format("Failed to copy '{}' to '{}'", File, Destination));
+ throw std::system_error(CopyEc, fmt::format("Failed to copy '{}' to '{}'", File, Destination));
}
ZEN_INFO("Copied '{}' to '{}'", File, Destination);
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<void(const void* Data, size_t Size)>&& ProcessFunc)
{
#if ZEN_PLATFORM_WINDOWS
@@ -1912,7 +1908,7 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi
if (FromFile == INVALID_HANDLE_VALUE)
{
FromFile.Detach();
- return false;
+ return MakeErrorCodeFromLastError();
}
std::vector<uint8_t> ReadBuffer(ChunkSize);
@@ -1924,7 +1920,7 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi
if (!Success)
{
- throw std::system_error(std::error_code(::GetLastError(), std::system_category()), "file scan failed");
+ return MakeErrorCodeFromLastError();
}
if (dwBytesRead == 0)
@@ -1936,10 +1932,10 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi
ScopedFd InFd(open(Path.c_str(), O_RDONLY | O_CLOEXEC));
if (!InFd)
{
- return false;
+ return MakeErrorCodeFromLastError();
}
- bool Success = true;
+ std::error_code Result;
void* Buffer = malloc(ChunkSize);
while (true)
@@ -1947,7 +1943,7 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi
int BytesRead = read(InFd.Fd, Buffer, ChunkSize);
if (BytesRead < 0)
{
- Success = false;
+ Result = MakeErrorCodeFromLastError();
break;
}
@@ -1961,13 +1957,13 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi
free(Buffer);
- if (!Success)
+ if (Result)
{
- ThrowLastError("file scan failed");
+ return Result;
}
#endif // ZEN_PLATFORM_WINDOWS
- return true;
+ return {};
}
void
@@ -2564,7 +2560,7 @@ GetModificationTickFromPath(const std::filesystem::path& Filename)
#endif
}
-bool
+std::error_code
TryGetFileProperties(const std::filesystem::path& Path,
uint64_t& OutSize,
uint64_t& OutModificationTick,
@@ -2582,31 +2578,31 @@ TryGetFileProperties(const std::filesystem::path& Path,
nullptr);
if (Handle == INVALID_HANDLE_VALUE)
{
- return false;
+ return MakeErrorCodeFromLastError();
}
auto _ = MakeGuard([Handle]() { CloseHandle(Handle); });
BY_HANDLE_FILE_INFORMATION Bhfh = {};
if (!GetFileInformationByHandle(Handle, &Bhfh))
{
- return false;
+ return MakeErrorCodeFromLastError();
}
OutSize = uint64_t(Bhfh.nFileSizeHigh) << 32 | Bhfh.nFileSizeLow;
OutModificationTick = ((uint64_t(Bhfh.ftLastWriteTime.dwHighDateTime) << 32) | Bhfh.ftLastWriteTime.dwLowDateTime);
OutNativeModeOrAttributes = Bhfh.dwFileAttributes;
- return true;
+ return {};
}
#else
struct stat Stat;
int err = stat(Path.native().c_str(), &Stat);
if (err)
{
- return false;
+ return MakeErrorCodeFromLastError();
}
OutModificationTick = StatMtime100Ns(Stat);
OutSize = size_t(Stat.st_size);
OutNativeModeOrAttributes = (uint32_t)Stat.st_mode;
- return true;
+ return {};
#endif
}
@@ -2723,10 +2719,10 @@ MaximizeOpenFileCount()
#endif
}
-bool
+std::error_code
PrepareFileForScatteredWrite(void* FileHandle, uint64_t FinalSize)
{
- bool Result = true;
+ std::error_code Result;
#if ZEN_PLATFORM_WINDOWS
BY_HANDLE_FILE_INFORMATION Information;
@@ -2738,9 +2734,9 @@ PrepareFileForScatteredWrite(void* FileHandle, uint64_t FinalSize)
BOOL Ok = DeviceIoControl(FileHandle, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &_, nullptr);
if (!Ok)
{
+ Result = MakeErrorCodeFromLastError();
std::error_code DummyEc;
ZEN_DEBUG("Unable to set sparse mode for file '{}'", PathFromHandle(FileHandle, DummyEc));
- Result = false;
}
}
}
@@ -2749,9 +2745,9 @@ PrepareFileForScatteredWrite(void* FileHandle, uint64_t FinalSize)
AllocationInfo.AllocationSize.QuadPart = LONGLONG(FinalSize);
if (!SetFileInformationByHandle(FileHandle, FileAllocationInfo, &AllocationInfo, DWORD(sizeof(AllocationInfo))))
{
+ Result = MakeErrorCodeFromLastError();
std::error_code DummyEc;
ZEN_DEBUG("Unable to set file allocation size to {} for file '{}'", FinalSize, PathFromHandle(FileHandle, DummyEc));
- Result = false;
}
#else // ZEN_PLATFORM_WINDOWS
@@ -3713,10 +3709,11 @@ TEST_CASE("filesystem")
// Scan/read file
FileContents BinRead = ReadFile(BinPath);
std::vector<uint8_t> 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));
diff --git a/src/zencore/include/zencore/filesystem.h b/src/zencore/include/zencore/filesystem.h
index 6bccb551c..bce902500 100644
--- a/src/zencore/include/zencore/filesystem.h
+++ b/src/zencore/include/zencore/filesystem.h
@@ -121,10 +121,10 @@ uint64_t GetModificationTickFromPath(const std::filesystem::path& Filename);
*/
uint64_t GetModificationTickFromHandle(void* NativeHandle, std::error_code& Ec);
-bool TryGetFileProperties(const std::filesystem::path& Path,
- uint64_t& OutSize,
- uint64_t& OutModificationTick,
- uint32_t& OutNativeModeOrAttributes);
+std::error_code TryGetFileProperties(const std::filesystem::path& Path,
+ uint64_t& OutSize,
+ uint64_t& OutModificationTick,
+ uint32_t& OutNativeModeOrAttributes);
/** Move/rename a file, if the files are not on the same drive the function will fail (throws)
*/
@@ -148,7 +148,7 @@ std::filesystem::path GetRunningExecutablePath();
*/
void MaximizeOpenFileCount();
-bool PrepareFileForScatteredWrite(void* FileHandle, uint64_t FinalSize);
+std::error_code PrepareFileForScatteredWrite(void* FileHandle, uint64_t FinalSize);
struct FileContents
{
@@ -175,16 +175,16 @@ FileContents ReadStdIn();
*/
FileContents ReadFile(const std::filesystem::path& Path);
-bool ScanFile(std::filesystem::path Path, uint64_t ChunkSize, std::function<void(const void* Data, size_t Size)>&& ProcessFunc);
-void WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t BufferCount);
-void WriteFile(std::filesystem::path Path, IoBuffer Data);
-void WriteFile(std::filesystem::path Path, CompositeBuffer Data);
-bool MoveToFile(std::filesystem::path Path, IoBuffer Data);
-void ScanFile(void* NativeHandle,
- uint64_t Offset,
- uint64_t Size,
- uint64_t ChunkSize,
- std::function<void(const void* Data, size_t Size)>&& ProcessFunc);
+std::error_code ScanFile(std::filesystem::path Path, uint64_t ChunkSize, std::function<void(const void* Data, size_t Size)>&& ProcessFunc);
+void WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t BufferCount);
+void WriteFile(std::filesystem::path Path, IoBuffer Data);
+void WriteFile(std::filesystem::path Path, CompositeBuffer Data);
+std::error_code MoveToFile(std::filesystem::path Path, IoBuffer Data);
+void ScanFile(void* NativeHandle,
+ uint64_t Offset,
+ uint64_t Size,
+ uint64_t ChunkSize,
+ std::function<void(const void* Data, size_t Size)>&& ProcessFunc);
void WriteFile(void* NativeHandle, const void* Data, uint64_t Size, uint64_t FileOffset, uint64_t ChunkSize, std::error_code& Ec);
void ReadFile(void* NativeHandle, void* Data, uint64_t Size, uint64_t FileOffset, uint64_t ChunkSize, std::error_code& Ec);
@@ -217,7 +217,7 @@ public:
std::unique_ptr<CloneQueryInterface> GetCloneQueryInterface(const std::filesystem::path& TargetDirectory);
-bool TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToPath);
+std::error_code TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToPath);
struct CopyFileOptions
{
@@ -225,13 +225,9 @@ struct CopyFileOptions
bool MustClone = false;
};
-bool CopyFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToPath, const CopyFileOptions& Options);
-void CopyFile(const std::filesystem::path& FromPath,
- const std::filesystem::path& ToPath,
- const CopyFileOptions& Options,
- std::error_code& OutError);
-void CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options);
-bool SupportsBlockRefCounting(std::filesystem::path Path);
+std::error_code CopyFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToPath, const CopyFileOptions& Options);
+void CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options);
+bool SupportsBlockRefCounting(std::filesystem::path Path);
void PathToUtf8(const std::filesystem::path& Path, StringBuilderBase& Out);
std::string PathToUtf8(const std::filesystem::path& Path);
diff --git a/src/zenserver/hub/hydration.cpp b/src/zenserver/hub/hydration.cpp
index 4b2294b49..259171b49 100644
--- a/src/zenserver/hub/hydration.cpp
+++ b/src/zenserver/hub/hydration.cpp
@@ -239,7 +239,11 @@ namespace hydration_impl {
[this, Hash = IoHash(Hash), SourcePath = std::filesystem::path(SourcePath)](std::atomic<bool>& AbortFlag) {
if (!AbortFlag.load())
{
- CopyFile(SourcePath, m_CASPath / fmt::format("{}", Hash), CopyFileOptions{.EnableClone = true});
+ std::filesystem::path DestPath = m_CASPath / fmt::format("{}", Hash);
+ if (std::error_code Ec = CopyFile(SourcePath, DestPath, CopyFileOptions{.EnableClone = true}); Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to copy '{}' to '{}'", SourcePath, DestPath));
+ }
}
});
}
@@ -256,7 +260,11 @@ namespace hydration_impl {
[this, Hash = IoHash(Hash), DestinationPath = std::filesystem::path(DestinationPath)](std::atomic<bool>& AbortFlag) {
if (!AbortFlag.load())
{
- CopyFile(m_CASPath / fmt::format("{}", Hash), DestinationPath, CopyFileOptions{.EnableClone = true});
+ std::filesystem::path SourcePath = m_CASPath / fmt::format("{}", Hash);
+ if (std::error_code Ec = CopyFile(SourcePath, DestinationPath, CopyFileOptions{.EnableClone = true}); Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to copy '{}' to '{}'", SourcePath, DestinationPath));
+ }
}
});
}
diff --git a/src/zenstore/filecas.cpp b/src/zenstore/filecas.cpp
index 3a7a72ee3..b254d06ab 100644
--- a/src/zenstore/filecas.cpp
+++ b/src/zenstore/filecas.cpp
@@ -315,7 +315,7 @@ FileCasStrategy::InsertChunk(IoBuffer Chunk, const IoHash& ChunkHash, CasStore::
// File-based chunks have special case handling whereby we move the file into
// place in the file store directory, thus avoiding unnecessary copying
- if (MoveToFile(ChunkPath, Chunk))
+ if (std::error_code MoveEc = MoveToFile(ChunkPath, Chunk); !MoveEc)
{
bool IsNew = UpdateIndex(ChunkHash, Chunk.Size());
return CasStore::InsertResult{.New = IsNew};
@@ -337,7 +337,7 @@ FileCasStrategy::InsertChunk(IoBuffer Chunk, const IoHash& ChunkHash, CasStore::
if (Mode == CasStore::InsertMode::kMayBeMovedInPlace)
{
- if (MoveToFile(ChunkPath, Chunk))
+ if (std::error_code MoveEc = MoveToFile(ChunkPath, Chunk); !MoveEc)
{
bool IsNew = UpdateIndex(ChunkHash, Chunk.Size());
return CasStore::InsertResult{.New = IsNew};
diff --git a/src/zenutil/filesystemutils.cpp b/src/zenutil/filesystemutils.cpp
index ccc42a838..f8f7bfb18 100644
--- a/src/zenutil/filesystemutils.cpp
+++ b/src/zenutil/filesystemutils.cpp
@@ -211,7 +211,9 @@ FastCopyFile(bool AllowFileClone,
std::atomic<uint64_t>& CloneByteCount)
{
ZEN_TRACE_CPU("CopyFile");
- if (AllowFileClone && TryCloneFile(SourceFilePath, TargetFilePath))
+ std::error_code CloneEc =
+ AllowFileClone ? TryCloneFile(SourceFilePath, TargetFilePath) : std::make_error_code(std::errc::operation_not_supported);
+ if (!CloneEc)
{
WriteCount += 1;
WriteByteCount += RawSize;
@@ -225,15 +227,16 @@ FastCopyFile(bool AllowFileClone,
{
PrepareFileForScatteredWrite(TargetFile.Handle(), RawSize);
}
- uint64_t Offset = 0;
- if (!ScanFile(SourceFilePath, 512u * 1024u, [&](const void* Data, size_t Size) {
- TargetFile.Write(Data, Size, Offset);
- Offset += Size;
- WriteCount++;
- WriteByteCount += Size;
- }))
+ uint64_t Offset = 0;
+ std::error_code ScanEc = ScanFile(SourceFilePath, 512u * 1024u, [&](const void* Data, size_t Size) {
+ TargetFile.Write(Data, Size, Offset);
+ Offset += Size;
+ WriteCount++;
+ WriteByteCount += Size;
+ });
+ if (ScanEc)
{
- throw std::runtime_error(fmt::format("Failed to copy file '{}' to '{}'", SourceFilePath, TargetFilePath));
+ throw std::system_error(ScanEc, fmt::format("Failed to copy file '{}' to '{}'", SourceFilePath, TargetFilePath));
}
}
}