aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/filesystem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zencore/filesystem.cpp')
-rw-r--r--src/zencore/filesystem.cpp406
1 files changed, 282 insertions, 124 deletions
diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp
index a63594be9..281cb8e2e 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
@@ -1092,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;
}
@@ -1185,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
@@ -1199,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);
@@ -1215,16 +1227,20 @@ TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path&
if (TargetFile == INVALID_HANDLE_VALUE)
{
TargetFile.Detach();
- return false;
+ return MakeErrorCodeFromLastError();
+ }
+ if (!TryCloneFile((void*)FromFile.m_Handle, (void*)TargetFile.m_Handle))
+ {
+ return MakeErrorCodeFromLastError();
}
- return TryCloneFile((void*)FromFile.m_Handle, (void*)TargetFile.m_Handle);
+ 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
@@ -1234,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());
@@ -1254,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);
@@ -1330,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
@@ -1439,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;
}
}
@@ -1669,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
@@ -1695,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))
{
@@ -1721,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)
@@ -1751,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
@@ -1884,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
@@ -1898,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);
@@ -1910,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)
@@ -1922,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)
@@ -1933,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;
}
@@ -1947,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
@@ -2123,7 +2133,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<uint32_t>(Stat.st_mode), gsl::narrow<uint64_t>(Stat.st_mtime));
+ Visitor.VisitFile(RootDir, FileName, Stat.st_size, gsl::narrow<uint32_t>(Stat.st_mode), StatMtime100Ns(Stat));
}
else
{
@@ -2507,7 +2517,7 @@ GetModificationTickFromHandle(void* NativeHandle, std::error_code& Ec)
struct stat Stat;
if (0 == fstat(Fd, &Stat))
{
- return gsl::narrow<uint64_t>(Stat.st_mtime);
+ return StatMtime100Ns(Stat);
}
#endif
Ec = MakeErrorCodeFromLastError();
@@ -2546,11 +2556,11 @@ GetModificationTickFromPath(const std::filesystem::path& Filename)
{
ThrowLastError(fmt::format("Failed to get mode of file {}", Filename));
}
- return gsl::narrow<uint64_t>(Stat.st_mtime);
+ return StatMtime100Ns(Stat);
#endif
}
-bool
+std::error_code
TryGetFileProperties(const std::filesystem::path& Path,
uint64_t& OutSize,
uint64_t& OutModificationTick,
@@ -2568,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 = gsl::narrow<uint64_t>(Stat.st_mtime);
+ OutModificationTick = StatMtime100Ns(Stat);
OutSize = size_t(Stat.st_size);
OutNativeModeOrAttributes = (uint32_t)Stat.st_mode;
- return true;
+ return {};
#endif
}
@@ -2709,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;
@@ -2724,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;
}
}
}
@@ -2735,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
@@ -2963,6 +2973,83 @@ 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;
+}
+
+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<std::string>(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)
{
@@ -3092,7 +3179,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
}
@@ -3286,12 +3404,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;
@@ -3419,7 +3537,7 @@ 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 {};
@@ -3591,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());
}
@@ -3824,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));
@@ -3839,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());
}
}
@@ -3853,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));
@@ -3870,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));
}
@@ -3892,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));
@@ -3908,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));
@@ -4108,6 +4227,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