aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/filesystem.cpp
diff options
context:
space:
mode:
authorzousar <[email protected]>2025-06-24 16:26:29 -0600
committerzousar <[email protected]>2025-06-24 16:26:29 -0600
commitbb298631ba35a323827dda0b8cd6158e276b5f61 (patch)
tree7ba8db91c44ce83f2c518f80f80ab14910eefa6f /src/zencore/filesystem.cpp
parentChange to PutResult structure (diff)
parent5.6.14 (diff)
downloadzen-bb298631ba35a323827dda0b8cd6158e276b5f61.tar.xz
zen-bb298631ba35a323827dda0b8cd6158e276b5f61.zip
Merge branch 'main' into zs/put-overwrite-policy
Diffstat (limited to 'src/zencore/filesystem.cpp')
-rw-r--r--src/zencore/filesystem.cpp1435
1 files changed, 1341 insertions, 94 deletions
diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp
index b8c35212f..46337ffc8 100644
--- a/src/zencore/filesystem.cpp
+++ b/src/zencore/filesystem.cpp
@@ -33,6 +33,7 @@ ZEN_THIRD_PARTY_INCLUDES_END
# include <dirent.h>
# include <fcntl.h>
# include <sys/resource.h>
+# include <sys/mman.h>
# include <sys/stat.h>
# include <pwd.h>
# include <unistd.h>
@@ -43,6 +44,7 @@ ZEN_THIRD_PARTY_INCLUDES_END
# include <fcntl.h>
# include <libproc.h>
# include <sys/resource.h>
+# include <sys/mman.h>
# include <sys/stat.h>
# include <sys/syslimits.h>
# include <pwd.h>
@@ -86,16 +88,9 @@ DeleteReparsePoint(const wchar_t* Path, DWORD dwReparseTag)
}
bool
-CreateDirectories(const wchar_t* Dir)
+CreateDirectories(const wchar_t* Path)
{
- // This may be suboptimal, in that it appears to try and create directories
- // from the root on up instead of from some directory which is known to
- // be present
- //
- // We should implement a smarter version at some point since this can be
- // pretty expensive in aggregate
-
- return std::filesystem::create_directories(Dir);
+ return CreateDirectories(std::filesystem::path(Path));
}
// Erase all files and directories in a given directory, leaving an empty directory
@@ -212,75 +207,377 @@ DeleteDirectoriesInternal(const wchar_t* DirPath)
bool
CleanDirectory(const wchar_t* DirPath, bool KeepDotFiles)
{
- if (std::filesystem::exists(DirPath))
+ if (IsDir(DirPath))
{
return WipeDirectory(DirPath, KeepDotFiles);
}
return CreateDirectories(DirPath);
}
+
+#endif // ZEN_PLATFORM_WINDOWS
+
+#if ZEN_PLATFORM_WINDOWS
+const uint32_t FileAttributesSystemReadOnlyFlag = FILE_ATTRIBUTE_READONLY;
+#else
+const uint32_t FileAttributesSystemReadOnlyFlag = 0x00000001;
#endif // ZEN_PLATFORM_WINDOWS
+const uint32_t FileModeWriteEnableFlags = 0222;
+
bool
-CreateDirectories(const std::filesystem::path& Dir)
+IsFileAttributeReadOnly(uint32_t FileAttributes)
+{
+#if ZEN_PLATFORM_WINDOWS
+ return (FileAttributes & FileAttributesSystemReadOnlyFlag) != 0;
+#else
+ return (FileAttributes & 0x00000001) != 0;
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
+bool
+IsFileModeReadOnly(uint32_t FileMode)
+{
+ return (FileMode & FileModeWriteEnableFlags) == 0;
+}
+
+uint32_t
+MakeFileAttributeReadOnly(uint32_t FileAttributes, bool ReadOnly)
+{
+ return ReadOnly ? (FileAttributes | FileAttributesSystemReadOnlyFlag) : (FileAttributes & ~FileAttributesSystemReadOnlyFlag);
+}
+
+uint32_t
+MakeFileModeReadOnly(uint32_t FileMode, bool ReadOnly)
+{
+ return ReadOnly ? (FileMode & ~FileModeWriteEnableFlags) : (FileMode | FileModeWriteEnableFlags);
+}
+
+#if ZEN_PLATFORM_WINDOWS
+
+static DWORD
+WinGetFileAttributes(const std::filesystem::path& Path, std::error_code& Ec)
{
- if (Dir.string().ends_with(":"))
+ DWORD Attributes = ::GetFileAttributes(Path.native().c_str());
+ if (Attributes == INVALID_FILE_ATTRIBUTES)
{
+ DWORD LastError = GetLastError();
+ switch (LastError)
+ {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ case ERROR_BAD_NETPATH:
+ case ERROR_INVALID_DRIVE:
+ break;
+ case ERROR_ACCESS_DENIED:
+ {
+ WIN32_FIND_DATA FindData;
+ HANDLE FindHandle = ::FindFirstFile(Path.native().c_str(), &FindData);
+ if (FindHandle == INVALID_HANDLE_VALUE)
+ {
+ DWORD LastFindError = GetLastError();
+ if (LastFindError != ERROR_FILE_NOT_FOUND)
+ {
+ Ec = MakeErrorCode(LastError);
+ }
+ }
+ else
+ {
+ FindClose(FindHandle);
+ Attributes = FindData.dwFileAttributes;
+ }
+ }
+ break;
+ default:
+ Ec = MakeErrorCode(LastError);
+ break;
+ }
+ }
+ return Attributes;
+}
+
+#endif // ZEN_PLATFORM_WINDOWS
+
+bool
+RemoveDirNative(const std::filesystem::path& Path, std::error_code& Ec)
+{
+#if ZEN_PLATFORM_WINDOWS
+ BOOL Success = ::RemoveDirectory(Path.native().c_str());
+ if (!Success)
+ {
+ DWORD LastError = GetLastError();
+ switch (LastError)
+ {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ break;
+ default:
+ Ec = MakeErrorCode(LastError);
+ break;
+ }
return false;
}
- while (!std::filesystem::is_directory(Dir))
+ return true;
+#else
+ return std::filesystem::remove(Path, Ec);
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
+bool
+RemoveFileNative(const std::filesystem::path& Path, bool ForceRemoveReadOnlyFiles, std::error_code& Ec)
+{
+#if ZEN_PLATFORM_WINDOWS
+ const std::filesystem::path::value_type* NativePath = Path.native().c_str();
+ BOOL Success = ::DeleteFile(NativePath);
+ if (!Success)
{
- if (Dir.has_parent_path())
+ if (ForceRemoveReadOnlyFiles)
{
- CreateDirectories(Dir.parent_path());
+ DWORD FileAttributes = WinGetFileAttributes(NativePath, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+
+ if ((FileAttributes != INVALID_FILE_ATTRIBUTES) && IsFileAttributeReadOnly(FileAttributes) != 0)
+ {
+ ::SetFileAttributes(NativePath, MakeFileAttributeReadOnly(FileAttributes, false));
+ Success = ::DeleteFile(NativePath);
+ }
}
- std::error_code ErrorCode;
- std::filesystem::create_directory(Dir, ErrorCode);
- if (ErrorCode)
+ if (!Success)
{
- throw std::system_error(ErrorCode, fmt::format("Failed to create directories for '{}'", Dir.string()));
+ DWORD LastError = GetLastError();
+ switch (LastError)
+ {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ break;
+ default:
+ Ec = MakeErrorCode(LastError);
+ break;
+ }
+ return false;
+ }
+ }
+ return true;
+#else
+ if (!ForceRemoveReadOnlyFiles)
+ {
+ struct stat Stat;
+ int err = stat(Path.native().c_str(), &Stat);
+ if (err != 0)
+ {
+ int32_t err = errno;
+ if (err == ENOENT)
+ {
+ Ec.clear();
+ return false;
+ }
+ }
+ const uint32_t Mode = (uint32_t)Stat.st_mode;
+ if (IsFileModeReadOnly(Mode))
+ {
+ Ec = MakeErrorCode(EACCES);
+ return false;
+ }
+ }
+ return std::filesystem::remove(Path, Ec);
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
+static void
+WipeDirectoryContentInternal(const std::filesystem::path& Path, bool ForceRemoveReadOnlyFiles, std::error_code& Ec)
+{
+ DirectoryContent LocalDirectoryContent;
+ GetDirectoryContent(Path, DirectoryContentFlags::IncludeDirs | DirectoryContentFlags::IncludeFiles, LocalDirectoryContent);
+ for (const std::filesystem::path& LocalFilePath : LocalDirectoryContent.Files)
+ {
+ RemoveFileNative(LocalFilePath, ForceRemoveReadOnlyFiles, Ec);
+ for (size_t Retries = 0; Ec && Retries < 3; Retries++)
+ {
+ Sleep(100 + int(Retries * 50));
+ Ec.clear();
+ if (IsFile(LocalFilePath))
+ {
+ RemoveFileNative(LocalFilePath, ForceRemoveReadOnlyFiles, Ec);
+ }
+ }
+ if (Ec)
+ {
+ return;
+ }
+ }
+
+ for (std::filesystem::path& LocalDirPath : LocalDirectoryContent.Directories)
+ {
+ WipeDirectoryContentInternal(LocalDirPath, ForceRemoveReadOnlyFiles, Ec);
+ if (Ec)
+ {
+ return;
+ }
+
+ RemoveDirNative(LocalDirPath, Ec);
+ for (size_t Retries = 0; Ec && Retries < 3; Retries++)
+ {
+ Sleep(100 + int(Retries * 50));
+ Ec.clear();
+ if (IsDir(LocalDirPath))
+ {
+ RemoveDirNative(LocalDirPath, Ec);
+ }
+ }
+ if (Ec)
+ {
+ return;
}
- return true;
}
- return false;
}
bool
-DeleteDirectories(const std::filesystem::path& Dir)
+CreateDirectory(const std::filesystem::path& Path, std::error_code& Ec)
{
- std::error_code ErrorCode;
- return std::filesystem::remove_all(Dir, ErrorCode);
+#if ZEN_PLATFORM_WINDOWS
+ BOOL Success = ::CreateDirectory(Path.native().c_str(), nullptr);
+ if (!Success)
+ {
+ DWORD LastError = GetLastError();
+ switch (LastError)
+ {
+ case ERROR_FILE_EXISTS:
+ case ERROR_ALREADY_EXISTS:
+ break;
+ default:
+ Ec = MakeErrorCode(LastError);
+ break;
+ }
+ return false;
+ }
+ return Success;
+#else
+ return std::filesystem::create_directory(Path, Ec);
+#endif // ZEN_PLATFORM_WINDOWS
}
bool
-CleanDirectory(const std::filesystem::path& Dir)
+CreateDirectories(const std::filesystem::path& Path)
{
- if (std::filesystem::exists(Dir))
+ std::error_code Ec;
+ bool Success = CreateDirectories(Path, Ec);
+ if (Ec)
{
- bool Success = true;
+ throw std::system_error(Ec, fmt::format("Failed to create directories for '{}'", Path.string()));
+ }
+ return Success;
+}
- for (const auto& Item : std::filesystem::directory_iterator(Dir))
- {
- std::error_code ErrorCode;
- const uintmax_t RemovedCount = std::filesystem::remove_all(Item, ErrorCode);
+bool
+CreateDirectories(const std::filesystem::path& Path, std::error_code& Ec)
+{
+ if (Path.string().ends_with(":"))
+ {
+ return false;
+ }
+ bool Exists = IsDir(Path, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+ if (Exists)
+ {
+ return false;
+ }
- Success = Success && !ErrorCode && RemovedCount;
+ if (Path.has_parent_path())
+ {
+ bool Result = CreateDirectories(Path.parent_path(), Ec);
+ if (Ec)
+ {
+ return Result;
}
+ }
+ return CreateDirectory(Path, Ec);
+}
- return Success;
+bool
+CleanDirectory(const std::filesystem::path& Path, bool ForceRemoveReadOnlyFiles)
+{
+ std::error_code Ec;
+ bool Result = CleanDirectory(Path, ForceRemoveReadOnlyFiles, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to clean directory for '{}'", Path.string()));
}
+ return Result;
+}
- return CreateDirectories(Dir);
+bool
+CleanDirectory(const std::filesystem::path& Path, bool ForceRemoveReadOnlyFiles, std::error_code& Ec)
+{
+ bool Exists = IsDir(Path, Ec);
+ if (Ec)
+ {
+ return Exists;
+ }
+ if (Exists)
+ {
+ WipeDirectoryContentInternal(Path, ForceRemoveReadOnlyFiles, Ec);
+ return false;
+ }
+ return CreateDirectory(Path, Ec);
+}
+
+bool
+DeleteDirectories(const std::filesystem::path& Path)
+{
+ std::error_code Ec;
+ bool Result = DeleteDirectories(Path, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to delete directories for '{}'", Path.string()));
+ }
+ return Result;
}
bool
-CleanDirectoryExceptDotFiles(const std::filesystem::path& Dir)
+DeleteDirectories(const std::filesystem::path& Path, std::error_code& Ec)
+{
+ bool Exists = IsDir(Path, Ec);
+ if (Ec)
+ {
+ return Exists;
+ }
+
+ if (Exists)
+ {
+ WipeDirectoryContentInternal(Path, false, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+ bool Result = RemoveDirNative(Path, Ec);
+ for (size_t Retries = 0; Ec && Retries < 3; Retries++)
+ {
+ Sleep(100 + int(Retries * 50));
+ Ec.clear();
+ if (IsDir(Path))
+ {
+ Result = RemoveDirNative(Path, Ec);
+ }
+ }
+ return Result;
+ }
+ return false;
+}
+
+bool
+CleanDirectoryExceptDotFiles(const std::filesystem::path& Path)
{
#if ZEN_PLATFORM_WINDOWS
const bool KeepDotFiles = true;
- return CleanDirectory(Dir.c_str(), KeepDotFiles);
+ return CleanDirectory(Path.c_str(), KeepDotFiles);
#else
- ZEN_UNUSED(Dir);
+ ZEN_UNUSED(Path);
ZEN_NOT_IMPLEMENTED();
#endif
@@ -531,7 +828,10 @@ CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath)
}
void
-CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options, std::error_code& OutErrorCode)
+CopyFile(const std::filesystem::path& FromPath,
+ const std::filesystem::path& ToPath,
+ const CopyFileOptions& Options,
+ std::error_code& OutErrorCode)
{
OutErrorCode.clear();
@@ -544,7 +844,7 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
}
bool
-CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options)
+CopyFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToPath, const CopyFileOptions& Options)
{
bool Success = false;
@@ -587,7 +887,7 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
ScopedFd $From = {FromFd};
// To file
- int ToFd = open(ToPath.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0666);
+ int ToFd = open(ToPath.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0666);
if (ToFd < 0)
{
ThrowLastError(fmt::format("failed to create file {}", ToPath));
@@ -595,9 +895,14 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
fchmod(ToFd, 0666);
ScopedFd $To = {ToFd};
+ struct stat Stat;
+ fstat(FromFd, &Stat);
+
+ size_t FileSizeBytes = Stat.st_size;
+
// Copy impl
- static const size_t BufferSize = 64 << 10;
- void* Buffer = malloc(BufferSize);
+ const size_t BufferSize = Min(FileSizeBytes, 64u << 10);
+ void* Buffer = malloc(BufferSize);
while (true)
{
int BytesRead = read(FromFd, Buffer, BufferSize);
@@ -607,7 +912,7 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
break;
}
- if (write(ToFd, Buffer, BytesRead) != BufferSize)
+ if (write(ToFd, Buffer, BytesRead) != BytesRead)
{
Success = false;
break;
@@ -618,7 +923,7 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
if (!Success)
{
- ThrowLastError("file copy failed"sv);
+ ThrowLastError(fmt::format("file copy from {} to {} failed", FromPath, ToPath));
}
return true;
@@ -629,7 +934,7 @@ CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
{
// Validate arguments
- if (FromPath.empty() || !std::filesystem::is_directory(FromPath))
+ if (FromPath.empty() || !IsDir(FromPath))
throw std::runtime_error("invalid CopyTree source directory specified");
if (ToPath.empty())
@@ -638,16 +943,13 @@ CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
if (Options.MustClone && !SupportsBlockRefCounting(FromPath))
throw std::runtime_error(fmt::format("cloning not possible from '{}'", FromPath));
- if (std::filesystem::exists(ToPath))
+ if (IsFile(ToPath))
{
- if (!std::filesystem::is_directory(ToPath))
- {
- throw std::runtime_error(fmt::format("specified CopyTree target '{}' is not a directory", ToPath));
- }
+ throw std::runtime_error(fmt::format("specified CopyTree target '{}' is not a directory", ToPath));
}
- else
+ if (!IsDir(ToPath))
{
- std::filesystem::create_directories(ToPath);
+ CreateDirectories(ToPath);
}
if (Options.MustClone && !SupportsBlockRefCounting(ToPath))
@@ -683,7 +985,7 @@ CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
{
}
- virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t FileSize, uint32_t) override
+ virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t FileSize, uint32_t, uint64_t) override
{
std::error_code Ec;
const std::filesystem::path Relative = std::filesystem::relative(Parent, BasePath, Ec);
@@ -752,6 +1054,100 @@ CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
}
void
+WriteFile(void* NativeHandle, const void* Data, uint64_t Size, uint64_t FileOffset, uint64_t ChunkSize, std::error_code& Ec)
+{
+ ZEN_ASSERT(NativeHandle != nullptr);
+
+ Ec.clear();
+
+ while (Size)
+ {
+ const uint64_t NumberOfBytesToWrite = Min(Size, ChunkSize);
+
+#if ZEN_PLATFORM_WINDOWS
+ OVERLAPPED Ovl{};
+
+ Ovl.Offset = DWORD(FileOffset & 0xffff'ffffu);
+ Ovl.OffsetHigh = DWORD(FileOffset >> 32);
+
+ DWORD dwNumberOfBytesWritten = 0;
+
+ BOOL Success = ::WriteFile(NativeHandle, Data, DWORD(NumberOfBytesToWrite), &dwNumberOfBytesWritten, &Ovl);
+#else
+ static_assert(sizeof(off_t) >= sizeof(uint64_t), "sizeof(off_t) does not support large files");
+ int Fd = int(uintptr_t(NativeHandle));
+ int BytesWritten = pwrite(Fd, Data, NumberOfBytesToWrite, FileOffset);
+ bool Success = (BytesWritten > 0);
+#endif
+
+ if (!Success)
+ {
+ Ec = MakeErrorCodeFromLastError();
+ return;
+ }
+
+ Size -= NumberOfBytesToWrite;
+ FileOffset += NumberOfBytesToWrite;
+ Data = reinterpret_cast<const uint8_t*>(Data) + NumberOfBytesToWrite;
+ }
+}
+
+void
+ReadFile(void* NativeHandle, void* Data, uint64_t Size, uint64_t FileOffset, uint64_t ChunkSize, std::error_code& Ec)
+{
+ while (Size)
+ {
+ const uint64_t NumberOfBytesToRead = Min(Size, ChunkSize);
+ size_t BytesRead = 0;
+
+#if ZEN_PLATFORM_WINDOWS
+ OVERLAPPED Ovl{};
+
+ Ovl.Offset = DWORD(FileOffset & 0xffff'ffffu);
+ Ovl.OffsetHigh = DWORD(FileOffset >> 32);
+
+ DWORD dwNumberOfBytesRead = 0;
+ BOOL Success = ::ReadFile(NativeHandle, Data, DWORD(NumberOfBytesToRead), &dwNumberOfBytesRead, &Ovl);
+ if (Success)
+ {
+ BytesRead = size_t(dwNumberOfBytesRead);
+ }
+ else if ((BytesRead != NumberOfBytesToRead))
+ {
+ Ec = MakeErrorCode(ERROR_HANDLE_EOF);
+ return;
+ }
+ else
+ {
+ Ec = MakeErrorCodeFromLastError();
+ return;
+ }
+#else
+ static_assert(sizeof(off_t) >= sizeof(uint64_t), "sizeof(off_t) does not support large files");
+ int Fd = int(uintptr_t(NativeHandle));
+ ssize_t ReadResult = pread(Fd, Data, NumberOfBytesToRead, FileOffset);
+ if (ReadResult != -1)
+ {
+ BytesRead = size_t(ReadResult);
+ }
+ else if ((BytesRead != NumberOfBytesToRead))
+ {
+ Ec = MakeErrorCode(EIO);
+ return;
+ }
+ else
+ {
+ Ec = MakeErrorCodeFromLastError();
+ return;
+ }
+#endif
+ Size -= NumberOfBytesToRead;
+ FileOffset += NumberOfBytesToRead;
+ Data = reinterpret_cast<uint8_t*>(Data) + NumberOfBytesToRead;
+ }
+}
+
+void
WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t BufferCount)
{
#if ZEN_PLATFORM_WINDOWS
@@ -803,7 +1199,7 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer
{
Outfile.Close();
std::error_code DummyEc;
- std::filesystem::remove(Path, DummyEc);
+ RemoveFile(Path, DummyEc);
ThrowSystemException(hRes, fmt::format("File write failed for '{}'", Path).c_str());
}
#else
@@ -811,7 +1207,7 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer
{
close(Fd);
std::error_code DummyEc;
- std::filesystem::remove(Path, DummyEc);
+ RemoveFile(Path, DummyEc);
ThrowLastError(fmt::format("File write failed for '{}'", Path));
}
#endif // ZEN_PLATFORM_WINDOWS
@@ -1164,7 +1560,7 @@ void
FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, TreeVisitor& Visitor)
{
#if ZEN_PLATFORM_WINDOWS
- uint64_t FileInfoBuffer[8 * 1024];
+ std::vector<uint64_t> FileInfoBuffer(8 * 1024);
FILE_INFO_BY_HANDLE_CLASS FibClass = FileIdBothDirectoryRestartInfo;
bool Continue = true;
@@ -1175,7 +1571,7 @@ FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, Tr
if (FAILED(hRes))
{
- if (hRes == ERROR_FILE_NOT_FOUND || hRes == ERROR_PATH_NOT_FOUND)
+ if (HRESULT_CODE(hRes) == ERROR_FILE_NOT_FOUND || HRESULT_CODE(hRes) == ERROR_PATH_NOT_FOUND)
{
// Directory no longer exist, treat it as empty
return;
@@ -1185,8 +1581,9 @@ FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, Tr
while (Continue)
{
- BOOL Success = GetFileInformationByHandleEx(RootDirHandle, FibClass, FileInfoBuffer, sizeof FileInfoBuffer);
- FibClass = FileIdBothDirectoryInfo; // Set up for next iteration
+ BOOL Success =
+ GetFileInformationByHandleEx(RootDirHandle, FibClass, FileInfoBuffer.data(), (DWORD)(FileInfoBuffer.size() * sizeof(uint64_t)));
+ FibClass = FileIdBothDirectoryInfo; // Set up for next iteration
uint64_t EntryOffset = 0;
@@ -1205,7 +1602,7 @@ FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, Tr
for (;;)
{
const FILE_ID_BOTH_DIR_INFO* DirInfo =
- reinterpret_cast<const FILE_ID_BOTH_DIR_INFO*>(reinterpret_cast<const uint8_t*>(FileInfoBuffer) + EntryOffset);
+ reinterpret_cast<const FILE_ID_BOTH_DIR_INFO*>(reinterpret_cast<const uint8_t*>(FileInfoBuffer.data()) + EntryOffset);
std::wstring_view FileName(DirInfo->FileName, DirInfo->FileNameLength / sizeof(wchar_t));
@@ -1236,7 +1633,11 @@ FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, Tr
}
else
{
- Visitor.VisitFile(RootDir, FileName, DirInfo->EndOfFile.QuadPart, gsl::narrow<uint32_t>(DirInfo->FileAttributes));
+ Visitor.VisitFile(RootDir,
+ FileName,
+ DirInfo->EndOfFile.QuadPart,
+ gsl::narrow<uint32_t>(DirInfo->FileAttributes),
+ (uint64_t)DirInfo->LastWriteTime.QuadPart);
}
const uint64_t NextOffset = DirInfo->NextEntryOffset;
@@ -1285,7 +1686,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));
+ Visitor.VisitFile(RootDir, FileName, Stat.st_size, gsl::narrow<uint32_t>(Stat.st_mode), gsl::narrow<uint64_t>(Stat.st_mtime));
}
else
{
@@ -1326,6 +1727,156 @@ CanonicalPath(std::filesystem::path InPath, std::error_code& Ec)
#endif
}
+bool
+IsFile(const std::filesystem::path& Path)
+{
+ std::error_code Ec;
+ bool Result = IsFile(Path, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to test if path '{}' is a file", Path.string()));
+ }
+ return Result;
+}
+
+bool
+IsFile(const std::filesystem::path& Path, std::error_code& Ec)
+{
+#if ZEN_PLATFORM_WINDOWS
+ DWORD Attributes = WinGetFileAttributes(Path, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+ if (Attributes == INVALID_FILE_ATTRIBUTES)
+ {
+ return false;
+ }
+ return (Attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
+#else
+ struct stat Stat;
+ int err = stat(Path.native().c_str(), &Stat);
+ if (err != 0)
+ {
+ int32_t err = errno;
+ if (err == ENOENT)
+ {
+ Ec.clear();
+ return false;
+ }
+ }
+ if (S_ISREG(Stat.st_mode))
+ {
+ return true;
+ }
+ return false;
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
+bool
+IsDir(const std::filesystem::path& Path)
+{
+ std::error_code Ec;
+ bool Result = IsDir(Path, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to test if path '{}' is a directory", Path.string()));
+ }
+ return Result;
+}
+
+bool
+IsDir(const std::filesystem::path& Path, std::error_code& Ec)
+{
+#if ZEN_PLATFORM_WINDOWS
+ DWORD Attributes = WinGetFileAttributes(Path, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+ if (Attributes == INVALID_FILE_ATTRIBUTES)
+ {
+ return false;
+ }
+ return (Attributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY;
+#else
+ struct stat Stat;
+ int err = stat(Path.native().c_str(), &Stat);
+ if (err != 0)
+ {
+ int32_t err = errno;
+ if (err == ENOENT)
+ {
+ Ec.clear();
+ return false;
+ }
+ }
+ if (S_ISDIR(Stat.st_mode))
+ {
+ return true;
+ }
+ return false;
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
+bool
+RemoveFile(const std::filesystem::path& Path)
+{
+ std::error_code Ec;
+ bool Success = RemoveFile(Path, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to remove file '{}'", Path.string()));
+ }
+ return Success;
+}
+
+bool
+RemoveFile(const std::filesystem::path& Path, std::error_code& Ec)
+{
+#if ZEN_PLATFORM_WINDOWS
+ return RemoveFileNative(Path, false, Ec);
+#else
+ bool IsDirectory = std::filesystem::is_directory(Path, Ec);
+ if (IsDirectory)
+ {
+ Ec = MakeErrorCode(EPERM);
+ return false;
+ }
+ Ec.clear();
+ return RemoveFileNative(Path, false, Ec);
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
+bool
+RemoveDir(const std::filesystem::path& Path)
+{
+ std::error_code Ec;
+ bool Success = RemoveDir(Path, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to remove directory '{}'", Path.string()));
+ }
+ return Success;
+}
+
+bool
+RemoveDir(const std::filesystem::path& Path, std::error_code& Ec)
+{
+#if ZEN_PLATFORM_WINDOWS
+ return RemoveDirNative(Path, Ec);
+#else
+ bool IsFile = std::filesystem::is_regular_file(Path, Ec);
+ if (IsFile)
+ {
+ Ec = MakeErrorCode(EPERM);
+ return false;
+ }
+ Ec.clear();
+ return RemoveDirNative(Path, Ec);
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
std::filesystem::path
PathFromHandle(void* NativeHandle, std::error_code& Ec)
{
@@ -1423,23 +1974,84 @@ PathFromHandle(void* NativeHandle, std::error_code& Ec)
}
uint64_t
-FileSizeFromHandle(void* NativeHandle)
+FileSizeFromPath(const std::filesystem::path& Path)
{
- uint64_t FileSize = ~0ull;
+ std::error_code Ec;
+ uint64_t Size = FileSizeFromPath(Path, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to get file size for path '{}'", Path.string()));
+ }
+ return Size;
+}
+
+uint64_t
+FileSizeFromPath(const std::filesystem::path& Path, std::error_code& Ec)
+{
+#if ZEN_PLATFORM_WINDOWS
+ void* Handle = ::CreateFile(Path.native().c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr,
+ OPEN_EXISTING,
+ 0,
+ nullptr);
+ if (Handle == INVALID_HANDLE_VALUE)
+ {
+ DWORD LastError = GetLastError();
+ Ec = MakeErrorCode(LastError);
+ return 0;
+ }
+ auto _ = MakeGuard([Handle]() { CloseHandle(Handle); });
+ LARGE_INTEGER FileSize;
+ BOOL Success = GetFileSizeEx(Handle, &FileSize);
+ if (!Success)
+ {
+ Ec = MakeErrorCodeFromLastError();
+ return 0;
+ }
+ return FileSize.QuadPart;
+#else
+ return std::filesystem::file_size(Path, Ec);
+#endif // ZEN_PLATFORM_WINDOWS
+}
+uint64_t
+FileSizeFromHandle(void* NativeHandle, std::error_code& Ec)
+{
#if ZEN_PLATFORM_WINDOWS
BY_HANDLE_FILE_INFORMATION Bhfh = {};
if (GetFileInformationByHandle(NativeHandle, &Bhfh))
{
- FileSize = uint64_t(Bhfh.nFileSizeHigh) << 32 | Bhfh.nFileSizeLow;
+ return uint64_t(Bhfh.nFileSizeHigh) << 32 | Bhfh.nFileSizeLow;
+ }
+ else
+ {
+ Ec = MakeErrorCodeFromLastError();
+ return 0;
}
#else
- int Fd = int(intptr_t(NativeHandle));
+ int Fd = int(intptr_t(NativeHandle));
+ static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files");
struct stat Stat;
- fstat(Fd, &Stat);
- FileSize = size_t(Stat.st_size);
+ if (fstat(Fd, &Stat) == -1)
+ {
+ Ec = MakeErrorCodeFromLastError();
+ return 0;
+ }
+ return uint64_t(Stat.st_size);
#endif
+}
+uint64_t
+FileSizeFromHandle(void* NativeHandle)
+{
+ std::error_code Ec;
+ uint64_t FileSize = FileSizeFromHandle(NativeHandle, Ec);
+ if (Ec)
+ {
+ return ~0ull;
+ }
return FileSize;
}
@@ -1465,6 +2077,138 @@ GetModificationTickFromHandle(void* NativeHandle, std::error_code& Ec)
return 0;
}
+uint64_t
+GetModificationTickFromPath(const std::filesystem::path& Filename)
+{
+ // PathFromHandle
+ void* Handle;
+#if ZEN_PLATFORM_WINDOWS
+ Handle = CreateFileW(Filename.c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr,
+ OPEN_EXISTING,
+ 0,
+ nullptr);
+ if (Handle == INVALID_HANDLE_VALUE)
+ {
+ ThrowLastError(fmt::format("Failed to open file {} to check modification tick.", Filename));
+ }
+ auto _ = MakeGuard([Handle]() { CloseHandle(Handle); });
+ std::error_code Ec;
+ uint64_t ModificatonTick = GetModificationTickFromHandle(Handle, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to get modification tick for path '{}'", Filename.string()));
+ }
+ return ModificatonTick;
+#else
+ struct stat Stat;
+ int err = stat(Filename.native().c_str(), &Stat);
+ if (err)
+ {
+ ThrowLastError(fmt::format("Failed to get mode of file {}", Filename));
+ }
+ return gsl::narrow<uint64_t>(Stat.st_mtime);
+#endif
+}
+
+bool
+TryGetFileProperties(const std::filesystem::path& Path,
+ uint64_t& OutSize,
+ uint64_t& OutModificationTick,
+ uint32_t& OutNativeModeOrAttributes)
+{
+#if ZEN_PLATFORM_WINDOWS
+ const std::filesystem::path::value_type* NativePath = Path.native().c_str();
+ {
+ void* Handle = CreateFileW(NativePath,
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr,
+ OPEN_EXISTING,
+ 0,
+ nullptr);
+ if (Handle == INVALID_HANDLE_VALUE)
+ {
+ return false;
+ }
+ auto _ = MakeGuard([Handle]() { CloseHandle(Handle); });
+
+ BY_HANDLE_FILE_INFORMATION Bhfh = {};
+ if (!GetFileInformationByHandle(Handle, &Bhfh))
+ {
+ return false;
+ }
+ OutSize = uint64_t(Bhfh.nFileSizeHigh) << 32 | Bhfh.nFileSizeLow;
+ OutModificationTick = ((uint64_t(Bhfh.ftLastWriteTime.dwHighDateTime) << 32) | Bhfh.ftLastWriteTime.dwLowDateTime);
+ OutNativeModeOrAttributes = Bhfh.dwFileAttributes;
+ return true;
+ }
+#else
+ struct stat Stat;
+ int err = stat(Path.native().c_str(), &Stat);
+ if (err)
+ {
+ return false;
+ }
+ OutModificationTick = gsl::narrow<uint64_t>(Stat.st_mtime);
+ OutSize = size_t(Stat.st_size);
+ OutNativeModeOrAttributes = (uint32_t)Stat.st_mode;
+ return true;
+#endif
+}
+
+void
+RenameFile(const std::filesystem::path& SourcePath, const std::filesystem::path& TargetPath)
+{
+ std::error_code Ec;
+ RenameFile(SourcePath, TargetPath, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to rename path from '{}' to '{}'", SourcePath.string(), TargetPath.string()));
+ }
+}
+
+void
+RenameFile(const std::filesystem::path& SourcePath, const std::filesystem::path& TargetPath, std::error_code& Ec)
+{
+#if ZEN_PLATFORM_WINDOWS
+ BOOL Success = ::MoveFileEx(SourcePath.native().c_str(), TargetPath.native().c_str(), MOVEFILE_REPLACE_EXISTING);
+ if (!Success)
+ {
+ Ec = MakeErrorCodeFromLastError();
+ }
+#else
+ return std::filesystem::rename(SourcePath, TargetPath, Ec);
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
+void
+RenameDirectory(const std::filesystem::path& SourcePath, const std::filesystem::path& TargetPath)
+{
+ std::error_code Ec;
+ RenameDirectory(SourcePath, TargetPath, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to rename directory from '{}' to '{}'", SourcePath.string(), TargetPath.string()));
+ }
+}
+
+void
+RenameDirectory(const std::filesystem::path& SourcePath, const std::filesystem::path& TargetPath, std::error_code& Ec)
+{
+#if ZEN_PLATFORM_WINDOWS
+ BOOL Success = ::MoveFile(SourcePath.native().c_str(), TargetPath.native().c_str());
+ if (!Success)
+ {
+ Ec = MakeErrorCodeFromLastError();
+ }
+#else
+ return std::filesystem::rename(SourcePath, TargetPath, Ec);
+#endif // ZEN_PLATFORM_WINDOWS
+}
+
std::filesystem::path
GetRunningExecutablePath()
{
@@ -1528,6 +2272,43 @@ MaximizeOpenFileCount()
#endif
}
+bool
+PrepareFileForScatteredWrite(void* FileHandle, uint64_t FinalSize)
+{
+ bool Result = true;
+#if ZEN_PLATFORM_WINDOWS
+
+ BY_HANDLE_FILE_INFORMATION Information;
+ if (GetFileInformationByHandle(FileHandle, &Information))
+ {
+ if ((Information.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) == 0)
+ {
+ DWORD _ = 0;
+ BOOL Ok = DeviceIoControl(FileHandle, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &_, nullptr);
+ if (!Ok)
+ {
+ std::error_code DummyEc;
+ ZEN_DEBUG("Unable to set sparse mode for file '{}'", PathFromHandle(FileHandle, DummyEc));
+ Result = false;
+ }
+ }
+ }
+
+ FILE_ALLOCATION_INFO AllocationInfo = {};
+ AllocationInfo.AllocationSize.QuadPart = LONGLONG(FinalSize);
+ if (!SetFileInformationByHandle(FileHandle, FileAllocationInfo, &AllocationInfo, DWORD(sizeof(AllocationInfo))))
+ {
+ 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
+ ZEN_UNUSED(FileHandle, FinalSize);
+#endif // ZEN_PLATFORM_WINDOWS
+ return Result;
+}
+
void
GetDirectoryContent(const std::filesystem::path& RootDir, DirectoryContentFlags Flags, DirectoryContent& OutContent)
{
@@ -1544,7 +2325,8 @@ GetDirectoryContent(const std::filesystem::path& RootDir, DirectoryContentFlags
virtual void VisitFile(const std::filesystem::path& Parent,
const path_view& File,
uint64_t FileSize,
- uint32_t NativeModeOrAttributes) override
+ uint32_t NativeModeOrAttributes,
+ uint64_t NativeModificationTick) override
{
if (EnumHasAnyFlags(Flags, DirectoryContentFlags::IncludeFiles))
{
@@ -1557,6 +2339,10 @@ GetDirectoryContent(const std::filesystem::path& RootDir, DirectoryContentFlags
{
Content.FileAttributes.push_back(NativeModeOrAttributes);
}
+ if (EnumHasAnyFlags(Flags, DirectoryContentFlags::IncludeModificationTick))
+ {
+ Content.FileModificationTicks.push_back(NativeModificationTick);
+ }
}
}
@@ -1612,7 +2398,8 @@ GetDirectoryContent(const std::filesystem::path& RootDir,
virtual void VisitFile(const std::filesystem::path&,
const path_view& File,
uint64_t FileSize,
- uint32_t NativeModeOrAttributes) override
+ uint32_t NativeModeOrAttributes,
+ uint64_t NativeModificationTick) override
{
if (EnumHasAnyFlags(Flags, DirectoryContentFlags::IncludeFiles))
{
@@ -1625,6 +2412,10 @@ GetDirectoryContent(const std::filesystem::path& RootDir,
{
Content.FileAttributes.push_back(NativeModeOrAttributes);
}
+ if (EnumHasAnyFlags(Flags, DirectoryContentFlags::IncludeModificationTick))
+ {
+ Content.FileModificationTicks.push_back(NativeModificationTick);
+ }
}
}
@@ -1654,12 +2445,17 @@ GetDirectoryContent(const std::filesystem::path& RootDir,
RelativeRoot = RelativeRoot / DirectoryName]() {
ZEN_ASSERT(Visitor);
auto _ = MakeGuard([&]() { PendingWorkCount->CountDown(); });
+ try
{
MultithreadedVisitor SubVisitor(*WorkerPool, *PendingWorkCount, RelativeRoot, Flags, Visitor);
FileSystemTraversal Traversal;
Traversal.TraverseFileSystem(Path, SubVisitor);
Visitor->AsyncVisitDirectory(SubVisitor.RelativeRoot, std::move(SubVisitor.Content));
}
+ catch (const std::exception& Ex)
+ {
+ ZEN_ERROR("Failed scheduling work to scan subfolder '{}'. Reason: '{}'", Path / RelativeRoot, Ex.what());
+ }
});
}
catch (const std::exception Ex)
@@ -1741,7 +2537,7 @@ RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles)
};
auto IsEmpty = [](const std::filesystem::path& Path, std::error_code& Ec) -> bool {
- bool Exists = std::filesystem::exists(Path, Ec);
+ bool Exists = IsFile(Path, Ec);
if (Ec)
{
return false;
@@ -1750,7 +2546,7 @@ RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles)
{
return true;
}
- uintmax_t Size = std::filesystem::file_size(Path, Ec);
+ uintmax_t Size = FileSizeFromPath(Path, Ec);
if (Ec)
{
return false;
@@ -1769,17 +2565,17 @@ RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles)
for (auto i = MaxFiles; i > 0; i--)
{
std::filesystem::path src = GetFileName(i - 1);
- if (!std::filesystem::exists(src))
+ if (!IsFile(src))
{
continue;
}
std::error_code DummyEc;
std::filesystem::path target = GetFileName(i);
- if (std::filesystem::exists(target, DummyEc))
+ if (IsFile(target, DummyEc))
{
- std::filesystem::remove(target, DummyEc);
+ RemoveFile(target, DummyEc);
}
- std::filesystem::rename(src, target, DummyEc);
+ RenameFile(src, target, DummyEc);
}
}
@@ -1816,16 +2612,16 @@ RotateDirectories(const std::filesystem::path& DirectoryName, std::size_t MaxDir
{
const std::filesystem::path SourcePath = GetPathForIndex(i - 1);
- if (std::filesystem::exists(SourcePath))
+ if (IsDir(SourcePath))
{
std::filesystem::path TargetPath = GetPathForIndex(i);
std::error_code DummyEc;
- if (std::filesystem::exists(TargetPath, DummyEc))
+ if (IsDir(TargetPath, DummyEc))
{
- std::filesystem::remove_all(TargetPath, DummyEc);
+ DeleteDirectories(TargetPath, DummyEc);
}
- std::filesystem::rename(SourcePath, TargetPath, DummyEc);
+ RenameDirectory(SourcePath, TargetPath, DummyEc);
}
}
@@ -1881,6 +2677,367 @@ PickDefaultSystemRootDirectory()
#endif // ZEN_PLATFORM_WINDOWS
}
+#if ZEN_PLATFORM_WINDOWS
+
+uint32_t
+GetFileAttributes(const std::filesystem::path& Filename, std::error_code& Ec)
+{
+ return WinGetFileAttributes(Filename, Ec);
+}
+
+uint32_t
+GetFileAttributes(const std::filesystem::path& Filename)
+{
+ std::error_code Ec;
+ uint32_t Result = zen::GetFileAttributes(Filename, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("failed to get attributes of file '{}'", Filename.string()));
+ }
+ return Result;
+}
+
+void
+SetFileAttributes(const std::filesystem::path& Filename, uint32_t Attributes, std::error_code& Ec)
+{
+ if (::SetFileAttributes(Filename.native().c_str(), Attributes) == 0)
+ {
+ Ec = MakeErrorCodeFromLastError();
+ }
+}
+
+void
+SetFileAttributes(const std::filesystem::path& Filename, uint32_t Attributes)
+{
+ std::error_code Ec;
+ zen::SetFileAttributes(Filename, Attributes, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("failed to set attributes of file {}", Filename.string()));
+ }
+}
+
+#endif // ZEN_PLATFORM_WINDOWS
+
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+
+uint32_t
+GetFileMode(const std::filesystem::path& Filename)
+{
+ std::error_code Ec;
+ uint32_t Result = GetFileMode(Filename, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to get mode of file {}", Filename));
+ }
+ return Result;
+}
+
+uint32_t
+GetFileMode(const std::filesystem::path& Filename, std::error_code& Ec)
+{
+ struct stat Stat;
+ int err = stat(Filename.native().c_str(), &Stat);
+ if (err)
+ {
+ Ec = MakeErrorCodeFromLastError();
+ return 0;
+ }
+ return (uint32_t)Stat.st_mode;
+}
+
+void
+SetFileMode(const std::filesystem::path& Filename, uint32_t Mode)
+{
+ std::error_code Ec;
+ SetFileMode(Filename, Mode, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("Failed to set mode of file {}", Filename));
+ }
+}
+
+void
+SetFileMode(const std::filesystem::path& Filename, uint32_t Mode, std::error_code& Ec)
+{
+ int err = chmod(Filename.native().c_str(), (mode_t)Mode);
+ if (err)
+ {
+ Ec = MakeErrorCodeFromLastError();
+ }
+}
+
+#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+
+bool
+SetFileReadOnly(const std::filesystem::path& Filename, bool ReadOnly, std::error_code& Ec)
+{
+#if ZEN_PLATFORM_WINDOWS
+ uint32_t CurrentAttributes = GetFileAttributes(Filename, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+ if (CurrentAttributes == INVALID_FILE_ATTRIBUTES)
+ {
+ Ec = MakeErrorCode(ERROR_FILE_NOT_FOUND);
+ return false;
+ }
+ uint32_t NewAttributes = MakeFileAttributeReadOnly(CurrentAttributes, ReadOnly);
+ if (CurrentAttributes != NewAttributes)
+ {
+ SetFileAttributes(Filename, NewAttributes, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+ return true;
+ }
+#endif // ZEN_PLATFORM_WINDOWS
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ uint32_t CurrentMode = GetFileMode(Filename, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+ uint32_t NewMode = MakeFileModeReadOnly(CurrentMode, ReadOnly);
+ if (CurrentMode != NewMode)
+ {
+ SetFileMode(Filename, NewMode, Ec);
+ if (Ec)
+ {
+ return false;
+ }
+ return true;
+ }
+#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ return false;
+}
+
+bool
+SetFileReadOnly(const std::filesystem::path& Filename, bool ReadOnly)
+{
+ std::error_code Ec;
+ bool Result = SetFileReadOnly(Filename, ReadOnly, Ec);
+ if (Ec)
+ {
+ throw std::system_error(Ec, fmt::format("failed to set read only mode of file '{}'", Filename.string()));
+ }
+ return Result;
+}
+
+class SharedMemoryImpl : public SharedMemory
+{
+public:
+ struct Data
+ {
+ void* Handle = nullptr;
+ void* DataPtr = nullptr;
+ size_t Size = 0;
+ std::string Name;
+ };
+
+ static Data Open(std::string_view Name, size_t Size, bool SystemGlobal)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ std::wstring InstanceMapName = Utf8ToWide(fmt::format("{}\\{}", SystemGlobal ? "Global" : "Local", Name));
+
+ HANDLE hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, InstanceMapName.c_str());
+ if (hMap == NULL)
+ {
+ return {};
+ }
+ void* pBuf = MapViewOfFile(hMap, // handle to map object
+ FILE_MAP_ALL_ACCESS, // read/write permission
+ 0, // offset high
+ 0, // offset low
+ DWORD(Size)); // size
+
+ if (pBuf == NULL)
+ {
+ CloseHandle(hMap);
+ }
+ return Data{.Handle = hMap, .DataPtr = pBuf, .Size = Size, .Name = std::string(Name)};
+#endif // ZEN_PLATFORM_WINDOWS
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ ZEN_UNUSED(SystemGlobal);
+ std::string InstanceMapName = fmt::format("/{}", Name);
+
+ int Fd = shm_open(InstanceMapName.c_str(), O_RDWR, 0666);
+ if (Fd < 0)
+ {
+ return {};
+ }
+ void* hMap = (void*)intptr_t(Fd);
+
+ struct stat Stat;
+ fstat(Fd, &Stat);
+
+ if (size_t(Stat.st_size) < Size)
+ {
+ close(Fd);
+ return {};
+ }
+
+ void* pBuf = mmap(nullptr, Size, PROT_READ | PROT_WRITE, MAP_SHARED, Fd, 0);
+ if (pBuf == MAP_FAILED)
+ {
+ close(Fd);
+ return {};
+ }
+ return Data{.Handle = hMap, .DataPtr = pBuf, .Size = Size, .Name = std::string(Name)};
+#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ }
+
+ static Data Create(std::string_view Name, size_t Size, bool SystemGlobal)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ std::wstring InstanceMapName = Utf8ToWide(fmt::format("{}\\{}", SystemGlobal ? "Global" : "Local", Name));
+
+ SECURITY_ATTRIBUTES m_Attributes{};
+ SECURITY_DESCRIPTOR m_Sd{};
+
+ m_Attributes.nLength = sizeof m_Attributes;
+ m_Attributes.bInheritHandle = false; // Disable inheritance
+
+ const BOOL Success = InitializeSecurityDescriptor(&m_Sd, SECURITY_DESCRIPTOR_REVISION);
+
+ if (Success)
+ {
+ if (!SetSecurityDescriptorDacl(&m_Sd, TRUE, (PACL)NULL, FALSE))
+ {
+ ThrowLastError("SetSecurityDescriptorDacl failed");
+ }
+
+ m_Attributes.lpSecurityDescriptor = &m_Sd;
+ }
+
+ HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE, // use paging file
+ &m_Attributes, // allow anyone to access
+ PAGE_READWRITE, // read/write access
+ 0, // maximum object size (high-order DWORD)
+ DWORD(Size), // maximum object size (low-order DWORD)
+ InstanceMapName.c_str());
+ if (hMap == NULL)
+ {
+ return {};
+ }
+ void* pBuf = MapViewOfFile(hMap, // handle to map object
+ FILE_MAP_ALL_ACCESS, // read/write permission
+ 0, // offset high
+ 0, // offset low
+ DWORD(Size)); // size
+
+ if (pBuf == NULL)
+ {
+ CloseHandle(hMap);
+ return {};
+ }
+ return Data{.Handle = hMap, .DataPtr = pBuf, .Size = Size, .Name = std::string(Name)};
+#endif // ZEN_PLATFORM_WINDOWS
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ ZEN_UNUSED(SystemGlobal);
+ std::string InstanceMapName = fmt::format("/{}", Name);
+
+ int Fd = shm_open(InstanceMapName.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666);
+ if (Fd < 0)
+ {
+ return {};
+ }
+ fchmod(Fd, 0666);
+ void* hMap = (void*)intptr_t(Fd);
+
+ int Result = ftruncate(Fd, Size);
+ ZEN_UNUSED(Result);
+
+ void* pBuf = mmap(nullptr, Size, PROT_READ | PROT_WRITE, MAP_SHARED, Fd, 0);
+ if (pBuf == MAP_FAILED)
+ {
+ close(Fd);
+ return {};
+ }
+ return Data{.Handle = hMap, .DataPtr = pBuf, .Size = Size, .Name = std::string(Name)};
+#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ }
+
+ static void Close(Data&& MemMap, bool Delete)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ ZEN_UNUSED(Delete);
+ if (MemMap.DataPtr != nullptr)
+ {
+ UnmapViewOfFile(MemMap.DataPtr);
+ MemMap.DataPtr = nullptr;
+ }
+ if (MemMap.Handle != nullptr)
+ {
+ CloseHandle(MemMap.Handle);
+ MemMap.Handle = nullptr;
+ }
+#endif // ZEN_PLATFORM_WINDOWS
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ if (MemMap.DataPtr != nullptr)
+ {
+ munmap(MemMap.DataPtr, MemMap.Size);
+ MemMap.DataPtr = nullptr;
+ }
+
+ if (MemMap.Handle != nullptr)
+ {
+ int Fd = int(intptr_t(MemMap.Handle));
+ close(Fd);
+ MemMap.Handle = nullptr;
+ }
+ if (Delete)
+ {
+ std::string InstanceMapName = fmt::format("/{}", MemMap.Name);
+ shm_unlink(InstanceMapName.c_str());
+ }
+#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ }
+
+ SharedMemoryImpl(Data&& MemMap, bool IsOwned) : m_MemMap(std::move(MemMap)), m_IsOwned(IsOwned) {}
+ virtual ~SharedMemoryImpl()
+ {
+ try
+ {
+ Close(std::move(m_MemMap), /*Delete*/ m_IsOwned);
+ }
+ catch (const std::exception& Ex)
+ {
+ ZEN_ERROR("SharedMemoryImpl::~SharedMemoryImpl threw exception: {}", Ex.what());
+ }
+ }
+
+ virtual void* GetData() override { return m_MemMap.DataPtr; }
+
+private:
+ Data m_MemMap;
+ const bool m_IsOwned = false;
+};
+
+std::unique_ptr<SharedMemory>
+OpenSharedMemory(std::string_view Name, size_t Size, bool SystemGlobal)
+{
+ SharedMemoryImpl::Data MemMap = SharedMemoryImpl::Open(Name, Size, SystemGlobal);
+ if (MemMap.DataPtr)
+ {
+ return std::make_unique<SharedMemoryImpl>(std::move(MemMap), /*IsOwned*/ false);
+ }
+ return {};
+}
+
+std::unique_ptr<SharedMemory>
+CreateSharedMemory(std::string_view Name, size_t Size, bool SystemGlobal)
+{
+ SharedMemoryImpl::Data MemMap = SharedMemoryImpl::Create(Name, Size, SystemGlobal);
+ if (MemMap.DataPtr)
+ {
+ return std::make_unique<SharedMemoryImpl>(std::move(MemMap), /*IsOwned*/ true);
+ }
+ return {};
+}
+
//////////////////////////////////////////////////////////////////////////
//
// Testing related code follows...
@@ -1901,7 +3058,7 @@ TEST_CASE("filesystem")
path BinPath = GetRunningExecutablePath();
const bool ExpectedExe = PathToUtf8(BinPath.stem().native()).ends_with("-test"sv) || BinPath.stem() == "zenserver";
CHECK(ExpectedExe);
- CHECK(is_regular_file(BinPath));
+ CHECK(IsFile(BinPath));
// PathFromHandle
void* Handle;
@@ -1928,7 +3085,7 @@ TEST_CASE("filesystem")
// Traversal
struct : public FileSystemTraversal::TreeVisitor
{
- virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t, uint32_t) override
+ virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t, uint32_t, uint64_t) override
{
bFoundExpected |= std::filesystem::equivalent(Parent / File, Expected);
}
@@ -1954,6 +3111,80 @@ TEST_CASE("filesystem")
CHECK_EQ(BinScan.size(), BinRead.Data[0].GetSize());
}
+TEST_CASE("Filesystem.Basics")
+{
+ std::filesystem::path TestBaseDir = GetRunningExecutablePath().parent_path() / ".test";
+ CleanDirectory(TestBaseDir, true);
+ DeleteDirectories(TestBaseDir);
+ CHECK(!IsDir(TestBaseDir));
+ CHECK(CleanDirectory(TestBaseDir, false));
+ CHECK(IsDir(TestBaseDir));
+ CHECK(!CleanDirectory(TestBaseDir, false));
+ CHECK(!IsDir(TestBaseDir / "no_such_thing"));
+ CHECK(!IsDir("hgjda/cev_/q12"));
+ CHECK(!IsFile(TestBaseDir));
+ CHECK(!IsFile(TestBaseDir / "no_such_thing"));
+ CHECK(!IsFile("hgjda/cev_/q12"));
+ CHECK_THROWS(FileSizeFromPath(TestBaseDir) == 0);
+ CHECK_THROWS(FileSizeFromPath(TestBaseDir / "no_such_file"));
+ CHECK(!CreateDirectories(TestBaseDir));
+ CHECK(CreateDirectories(TestBaseDir / "nested" / "a" / "bit" / "deep"));
+ CHECK(!CreateDirectories(TestBaseDir / "nested" / "a" / "bit" / "deep"));
+ CHECK(IsDir(TestBaseDir / "nested" / "a" / "bit" / "deep"));
+ CHECK(IsDir(TestBaseDir / "nested" / "a" / "bit"));
+ CHECK(!IsDir(TestBaseDir / "nested" / "a" / "bit" / "deep" / "no"));
+ CHECK_THROWS(WriteFile(TestBaseDir / "nested" / "a", IoBuffer(20)));
+ CHECK_NOTHROW(WriteFile(TestBaseDir / "nested" / "a" / "yo", IoBuffer(20)));
+ CHECK(IsFile(TestBaseDir / "nested" / "a" / "yo"));
+ CHECK(FileSizeFromPath(TestBaseDir / "nested" / "a" / "yo") == 20);
+ CHECK(!IsFile(TestBaseDir / "nested" / "a"));
+ CHECK(DeleteDirectories(TestBaseDir / "nested" / "a" / "bit"));
+ CHECK(IsFile(TestBaseDir / "nested" / "a" / "yo"));
+ CHECK(!IsDir(TestBaseDir / "nested" / "a" / "bit"));
+ CHECK(!DeleteDirectories(TestBaseDir / "nested" / "a" / "bit"));
+ CHECK(IsDir(TestBaseDir / "nested" / "a"));
+ CHECK(DeleteDirectories(TestBaseDir / "nested"));
+ CHECK(!IsFile(TestBaseDir / "nested" / "a" / "yo"));
+ CHECK(CreateDirectories(TestBaseDir / "nested" / "deeper"));
+ CHECK_NOTHROW(WriteFile(TestBaseDir / "nested" / "deeper" / "yo", IoBuffer(20)));
+ CHECK_NOTHROW(RenameDirectory(TestBaseDir / "nested" / "deeper", TestBaseDir / "new_place"));
+ CHECK(IsFile(TestBaseDir / "new_place" / "yo"));
+ CHECK(FileSizeFromPath(TestBaseDir / "new_place" / "yo") == 20);
+ CHECK(IsDir(TestBaseDir / "new_place"));
+ CHECK(!IsFile(TestBaseDir / "new_place"));
+ CHECK_THROWS(RenameDirectory(TestBaseDir / "nested" / "deeper", TestBaseDir / "new_place"));
+ CHECK(!RemoveDir(TestBaseDir / "nested" / "deeper"));
+ CHECK(RemoveFile(TestBaseDir / "new_place" / "yo"));
+ CHECK(!IsFile(TestBaseDir / "new_place" / "yo"));
+ CHECK_THROWS(FileSizeFromPath(TestBaseDir / "new_place" / "yo"));
+ CHECK(!RemoveFile(TestBaseDir / "new_place" / "yo"));
+ CHECK_THROWS(RemoveFile(TestBaseDir / "nested"));
+ CHECK_THROWS(RemoveDir(TestBaseDir));
+ CHECK_NOTHROW(WriteFile(TestBaseDir / "yo", IoBuffer(20)));
+ CHECK_NOTHROW(RenameFile(TestBaseDir / "yo", TestBaseDir / "new_place" / "yo"));
+ CHECK(!IsFile(TestBaseDir / "yo"));
+ CHECK(IsFile(TestBaseDir / "new_place" / "yo"));
+ CHECK(FileSizeFromPath(TestBaseDir / "new_place" / "yo") == 20);
+ CHECK_THROWS(RemoveDir(TestBaseDir / "new_place" / "yo"));
+ CHECK(DeleteDirectories(TestBaseDir));
+ CHECK(!IsFile(TestBaseDir / "new_place" / "yo"));
+ CHECK(!IsDir(TestBaseDir));
+ CHECK(!IsDir(TestBaseDir / "nested"));
+ CHECK(CreateDirectories(TestBaseDir / "nested"));
+ CHECK_NOTHROW(WriteFile(TestBaseDir / "nested" / "readonly", IoBuffer(20)));
+ CHECK(SetFileReadOnly(TestBaseDir / "nested" / "readonly", true));
+ CHECK_THROWS(RemoveFile(TestBaseDir / "nested" / "readonly"));
+ CHECK_THROWS(CleanDirectory(TestBaseDir, false));
+ CHECK(SetFileReadOnly(TestBaseDir / "nested" / "readonly", false));
+ CHECK(RemoveFile(TestBaseDir / "nested" / "readonly"));
+ CHECK(!CleanDirectory(TestBaseDir, false));
+ CHECK_NOTHROW(WriteFile(TestBaseDir / "nested" / "readonly", IoBuffer(20)));
+ CHECK(SetFileReadOnly(TestBaseDir / "nested" / "readonly", true));
+ CHECK(!CleanDirectory(TestBaseDir / "nested", true));
+ CHECK(!CleanDirectory(TestBaseDir, false));
+ CHECK(RemoveDir(TestBaseDir));
+}
+
TEST_CASE("WriteFile")
{
std::filesystem::path TempFile = GetRunningExecutablePath().parent_path();
@@ -1988,7 +3219,7 @@ TEST_CASE("WriteFile")
CHECK_EQ(memcmp(MagicTest.Data, MagicsReadback.Data[0].Data(), MagicTest.Size), 0);
}
- std::filesystem::remove(TempFile);
+ RemoveFile(TempFile);
}
TEST_CASE("DiskSpaceInfo")
@@ -2045,7 +3276,7 @@ TEST_CASE("PathBuilder")
TEST_CASE("RotateDirectories")
{
std::filesystem::path TestBaseDir = GetRunningExecutablePath().parent_path() / ".test";
- CleanDirectory(TestBaseDir);
+ CleanDirectory(TestBaseDir, false);
std::filesystem::path RotateDir = TestBaseDir / "rotate_dir" / "dir_to_rotate";
IoBuffer DummyFileData = IoBufferBuilder::MakeCloneFromMemory("blubb", 5);
@@ -2059,16 +3290,16 @@ TEST_CASE("RotateDirectories")
const int RotateMax = 10;
NewDir();
- CHECK(std::filesystem::exists(RotateDir));
+ CHECK(IsDir(RotateDir));
RotateDirectories(RotateDir, RotateMax);
- CHECK(!std::filesystem::exists(RotateDir));
- CHECK(std::filesystem::exists(DirWithSuffix(1)));
+ CHECK(!IsDir(RotateDir));
+ CHECK(IsDir(DirWithSuffix(1)));
NewDir();
- CHECK(std::filesystem::exists(RotateDir));
+ CHECK(IsDir(RotateDir));
RotateDirectories(RotateDir, RotateMax);
- CHECK(!std::filesystem::exists(RotateDir));
- CHECK(std::filesystem::exists(DirWithSuffix(1)));
- CHECK(std::filesystem::exists(DirWithSuffix(2)));
+ CHECK(!IsDir(RotateDir));
+ CHECK(IsDir(DirWithSuffix(1)));
+ CHECK(IsDir(DirWithSuffix(2)));
for (int i = 0; i < RotateMax; ++i)
{
@@ -2078,19 +3309,35 @@ TEST_CASE("RotateDirectories")
CHECK_EQ(IsError, false);
}
- CHECK(!std::filesystem::exists(RotateDir));
+ CHECK(!IsDir(RotateDir));
for (int i = 0; i < RotateMax; ++i)
{
- CHECK(std::filesystem::exists(DirWithSuffix(i + 1)));
+ CHECK(IsDir(DirWithSuffix(i + 1)));
}
for (int i = RotateMax; i < RotateMax + 5; ++i)
{
- CHECK(!std::filesystem::exists(DirWithSuffix(RotateMax + i + 1)));
+ CHECK(!IsDir(DirWithSuffix(RotateMax + i + 1)));
}
}
+TEST_CASE("SharedMemory")
+{
+ CHECK(!OpenSharedMemory("SharedMemoryTest0", 482, false));
+ CHECK(!OpenSharedMemory("SharedMemoryTest0", 482, true));
+
+ {
+ auto Mem0 = CreateSharedMemory("SharedMemoryTest0", 482, false);
+ CHECK(Mem0);
+ strcpy((char*)Mem0->GetData(), "this is the string we are looking for");
+ auto Mem1 = OpenSharedMemory("SharedMemoryTest0", 482, false);
+ CHECK_EQ(std::string((char*)Mem0->GetData()), std::string((char*)Mem1->GetData()));
+ }
+
+ CHECK(!OpenSharedMemory("SharedMemoryTest0", 482, false));
+}
+
#endif
} // namespace zen