diff options
Diffstat (limited to 'zencore/filesystem.cpp')
| -rw-r--r-- | zencore/filesystem.cpp | 276 |
1 files changed, 255 insertions, 21 deletions
diff --git a/zencore/filesystem.cpp b/zencore/filesystem.cpp index 45e177aaa..8ddcbac52 100644 --- a/zencore/filesystem.cpp +++ b/zencore/filesystem.cpp @@ -7,20 +7,33 @@ #include <zencore/iobuffer.h> #include <zencore/logging.h> #include <zencore/string.h> -#include <zencore/windows.h> +#if ZEN_PLATFORM_WINDOWS +# include <zencore/windows.h> +#endif + +#if ZEN_PLATFORM_WINDOWS +# include <atlbase.h> +# include <atlfile.h> +# include <winioctl.h> +# include <winnt.h> +#else +# include <dirent.h> +# include <fcntl.h> +# include <sys/stat.h> +# include <unistd.h> +#endif -#include <atlbase.h> -#include <atlfile.h> -#include <winioctl.h> -#include <winnt.h> #include <filesystem> +#include <doctest/doctest.h> #include <gsl/gsl-lite.hpp> namespace zen { using namespace std::literals; +#if ZEN_PLATFORM_WINDOWS + static bool DeleteReparsePoint(const wchar_t* Path, DWORD dwReparseTag) { @@ -53,12 +66,6 @@ CreateDirectories(const wchar_t* Dir) return std::filesystem::create_directories(Dir); } -bool -CreateDirectories(const std::filesystem::path& Dir) -{ - return std::filesystem::create_directories(Dir); -} - // Erase all files and directories in a given directory, leaving an empty directory // behind @@ -157,16 +164,46 @@ CleanDirectory(const wchar_t* DirPath) } } +#endif // ZEN_PLATFORM_WINDOWS + +bool +CreateDirectories(const std::filesystem::path& Dir) +{ + return std::filesystem::create_directories(Dir); +} + bool DeleteDirectories(const std::filesystem::path& Dir) { +#if ZEN_PLATFORM_WINDOWS return DeleteDirectories(Dir.c_str()); +#else + std::error_code ErrorCode; + return std::filesystem::remove_all(Dir, ErrorCode); +#endif } bool CleanDirectory(const std::filesystem::path& Dir) { +#if ZEN_PLATFORM_WINDOWS return CleanDirectory(Dir.c_str()); +#else + if (std::filesystem::exists(Dir)) + { + bool Success = true; + + std::error_code ErrorCode; + for (const auto& Item : std::filesystem::directory_iterator(Dir)) + { + Success &= std::filesystem::remove_all(Item, ErrorCode); + } + + return Success; + } + + return CreateDirectories(Dir); +#endif } ////////////////////////////////////////////////////////////////////////// @@ -174,6 +211,7 @@ CleanDirectory(const std::filesystem::path& Dir) bool SupportsBlockRefCounting(std::filesystem::path Path) { +#if ZEN_PLATFORM_WINDOWS ATL::CHandle Handle(CreateFileW(Path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, @@ -200,11 +238,15 @@ SupportsBlockRefCounting(std::filesystem::path Path) } return true; +#else + return false; +#endif // ZEN_PLATFORM_WINDOWS } bool CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath) { +#if ZEN_PLATFORM_WINDOWS ATL::CHandle FromFile(CreateFileW(FromPath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr)); if (FromFile == INVALID_HANDLE_VALUE) { @@ -359,11 +401,16 @@ CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath) const bool AllOk = (TRUE == SetFileInformationByHandle(TargetFile, FileDispositionInfo, &FileDisposition, sizeof FileDisposition)); return AllOk; +#else + ZEN_ERROR("CloneFile() is not implemented on this platform"); + return false; +#endif // ZEN_PLATFORM_WINDOWS } bool CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options) { +#if ZEN_PLATFORM_WINDOWS bool Success = false; if (Options.EnableClone) @@ -395,6 +442,10 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop } return Success; +#else + ZEN_ERROR("CopyFile() is not implemented on this platform"); + return false; +#endif // ZEN_PLATFORM_WINDOWS } void @@ -402,6 +453,7 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer { using namespace fmt::literals; +#if ZEN_PLATFORM_WINDOWS CAtlFile Outfile; HRESULT hRes = Outfile.Create(Path.c_str(), GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS); if (hRes == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) @@ -416,6 +468,20 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer ThrowSystemException(hRes, "File open failed for '{}'"_format(Path).c_str()); } +#else + int Fd = open(Path.c_str(), O_WRONLY); + if (Fd < 0) + { + zen::CreateDirectories(Path.parent_path()); + Fd = open(Path.c_str(), O_WRONLY); + } + + if (Fd < 0) + { + ThrowLastError("File open failed for '{}'"_format(Path)); + } +#endif + // TODO: this should be block-enlightened for (size_t i = 0; i < BufferCount; ++i) @@ -427,17 +493,27 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer { const uint64_t ChunkSize = Min<uint64_t>(WriteSize, uint64_t(2) * 1024 * 1024 * 1024); +#if ZEN_PLATFORM_WINDOWS hRes = Outfile.Write(DataPtr, gsl::narrow_cast<uint32_t>(WriteSize)); - if (FAILED(hRes)) { ThrowSystemException(hRes, "File write failed for '{}'"_format(Path).c_str()); } +#else + if (write(Fd, DataPtr, WriteSize) != WriteSize) + { + ThrowLastError("File write failed for '{}'"_format(Path)); + } +#endif // ZEN_PLATFORM_WINDOWS WriteSize -= ChunkSize; DataPtr = reinterpret_cast<const uint8_t*>(DataPtr) + ChunkSize; } } + +#if !ZEN_PLATFORM_WINDOWS + close(Fd); +#endif } void @@ -451,6 +527,10 @@ WriteFile(std::filesystem::path Path, IoBuffer Data) FileContents ReadFile(std::filesystem::path Path) { + uint64_t FileSizeBytes; + void* Handle; + +#if ZEN_PLATFORM_WINDOWS ATL::CHandle FromFile(CreateFileW(Path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr)); if (FromFile == INVALID_HANDLE_VALUE) { @@ -464,18 +544,32 @@ ReadFile(std::filesystem::path Path) return FileContents{.ErrorCode = std::error_code(::GetLastError(), std::system_category())}; } - const uint64_t FileSizeBytes = FileSize.EndOfFile.QuadPart; + FileSizeBytes = FileSize.EndOfFile.QuadPart; + Handle = FromFile.Detach(); +#else + int Fd = open(Path.c_str(), O_RDONLY); + if (Fd < 0) + { + return FileContents{.ErrorCode = std::error_code(zen::GetLastError(), std::system_category())}; + } - FileContents Contents; + static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files"); + struct stat Stat; + fstat(Fd, &Stat); - Contents.Data.emplace_back(IoBuffer(IoBuffer::File, FromFile.Detach(), 0, FileSizeBytes)); + FileSizeBytes = Stat.st_size; + Handle = (void*)uintptr_t(Fd); +#endif + FileContents Contents; + Contents.Data.emplace_back(IoBuffer(IoBuffer::File, Handle, 0, FileSizeBytes)); return Contents; } bool ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<void(const void* Data, size_t Size)>&& ProcessFunc) { +#if ZEN_PLATFORM_WINDOWS ATL::CHandle FromFile(CreateFileW(Path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr)); if (FromFile == INVALID_HANDLE_VALUE) { @@ -502,26 +596,33 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi } return true; +#else + ZEN_ERROR("ScanFile() is not implemented on this platform"); + return false; +#endif // ZEN_PLATFORM_WINDOWS } std::string ToUtf8(const std::filesystem::path& Path) { +#if ZEN_PLATFORM_WINDOWS return WideToUtf8(Path.native().c_str()); +#else + return Path.string(); +#endif } void FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, TreeVisitor& Visitor) { +#if ZEN_PLATFORM_WINDOWS uint64_t FileInfoBuffer[8 * 1024]; FILE_INFO_BY_HANDLE_CLASS FibClass = FileIdBothDirectoryRestartInfo; bool Continue = true; - std::wstring RootDirPath = RootDir.native(); - CAtlFile RootDirHandle; - HRESULT hRes = RootDirHandle.Create(RootDirPath.c_str(), + HRESULT hRes = RootDirHandle.Create(RootDir.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, @@ -581,12 +682,10 @@ FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, Tr } else if (DirInfo->FileAttributes & FILE_ATTRIBUTE_DEVICE) { - ZEN_WARN("encountered device node during file system traversal: {} found in {}", WideToUtf8(FileName), RootDir); + ZEN_WARN("encountered device node during file system traversal: {} found in {}", WideToUtf8(FileName), WideToUtf8(RootDir.c_str())); } else { - std::filesystem::path FullPath = RootDir / FileName; - Visitor.VisitFile(RootDir, FileName, DirInfo->EndOfFile.QuadPart); } @@ -600,11 +699,57 @@ FileSystemTraversal::TraverseFileSystem(const std::filesystem::path& RootDir, Tr EntryOffset += NextOffset; } } +#else + using namespace fmt::literals; + + /* Could also implement this using Linux's getdents() syscall */ + + DIR* Dir = opendir(RootDir.c_str()); + if (Dir == nullptr) + { + ThrowLastError("Failed to open directory for traversal: {}"_format(RootDir.c_str())); + } + + for (struct dirent* Entry; Entry = readdir(Dir);) + { + const char* FileName = Entry->d_name; + + struct stat Stat; + std::filesystem::path FullPath = RootDir / FileName; + stat(FullPath.c_str(), &Stat); + + if (S_ISDIR(Stat.st_mode)) + { + if (strcmp(FileName, ".") == 0 || strcmp(FileName, "..") == 0) + { + /* nop */ + } + else if (Visitor.VisitDirectory(RootDir, FileName)) + { + TraverseFileSystem(FullPath, Visitor); + } + } + else if (S_ISREG(Stat.st_mode)) + { + Visitor.VisitFile(RootDir, FileName, Stat.st_size); + } + else + { + ZEN_WARN("encountered non-regular file during file system traversal ({}): {} found in {}", + Stat.st_mode, FileName, RootDir.c_str()); + } + } + + closedir(Dir); +#endif // ZEN_PLATFORM_WINDOWS } + + std::filesystem::path PathFromHandle(void* NativeHandle) { +#if ZEN_PLATFORM_WINDOWS if (NativeHandle == nullptr || NativeHandle == INVALID_HANDLE_VALUE) { return std::filesystem::path(); @@ -620,15 +765,104 @@ PathFromHandle(void* NativeHandle) ZEN_UNUSED(FinalLength); return FullPath; +#elif ZEN_PLATFORM_LINUX + char Buffer[256]; + sprintf(Buffer, "/proc/%d/fd/%d", getpid(), int(uintptr_t(NativeHandle))); + ssize_t BytesRead = readlink(Buffer, Buffer, sizeof(Buffer) - 1); + if (BytesRead <= 0) + return std::filesystem::path(); + + Buffer[BytesRead] = '\0'; + return Buffer; +#else +# error Unimplemented platform +#endif // ZEN_PLATFORM_WINDOWS } std::filesystem::path GetRunningExecutablePath() { +#if ZEN_PLATFORM_WINDOWS TCHAR ExePath[MAX_PATH]; DWORD PathLength = GetModuleFileName(NULL, ExePath, ZEN_ARRAY_COUNT(ExePath)); return {std::wstring_view(ExePath, PathLength)}; +#elif ZEN_PLATFORM_LINUX + char Buffer[256]; + sprintf(Buffer, "/proc/%d/exe", getpid()); + ssize_t BytesRead = readlink(Buffer, Buffer, sizeof(Buffer) - 1); + if (BytesRead < 0) + return {}; + + Buffer[BytesRead] = '\0'; + return Buffer; +#else +# error Unimplemented platform +#endif // ZEN_PLATFORM_WINDOWS +} + + + +////////////////////////////////////////////////////////////////////////// +// +// Testing related code follows... +// + +void +filesystem_forcelink() +{ +} + +TEST_CASE("filesystem") +{ + using namespace std::filesystem; + + // GetExePath + path BinPath = GetRunningExecutablePath(); + CHECK(BinPath.stem() == "zencore-test"); + CHECK(is_regular_file(BinPath)); + + // PathFromHandle + void* Handle; +#if ZEN_PLATFORM_WINDOWS + Handle = CreateFileW(BinPath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, 0, nullptr); + CHECK(Handle != INVALID_HANDLE_VALUE); +#else + int Fd = open(BinPath.c_str(), O_RDONLY); + CHECK(Fd >= 0); + Handle = (void*)uintptr_t(Fd); +#endif + + auto FromHandle = PathFromHandle((void*)uintptr_t(Handle)); + CHECK(equivalent(FromHandle, BinPath)); + +#if ZEN_PLATFORM_WINDOWS + CloseHandle(Handle); +#else + close(int(uintptr_t(Handle))); +#endif + + // Traversal + struct : public FileSystemTraversal::TreeVisitor + { + virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t) override + { + bFoundExpected |= std::filesystem::equivalent(Parent / File, Expected); + } + + virtual bool VisitDirectory(const std::filesystem::path&, const path_view&) override + { + return true; + } + + bool bFoundExpected = false; + std::filesystem::path Expected; + } Visitor; + Visitor.Expected = BinPath; + + FileSystemTraversal().TraverseFileSystem(BinPath.parent_path().parent_path(), Visitor); + CHECK(Visitor.bFoundExpected); } } // namespace zen |