diff options
Diffstat (limited to 'src/zencore/filesystem.cpp')
| -rw-r--r-- | src/zencore/filesystem.cpp | 406 |
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 |