aboutsummaryrefslogtreecommitdiff
path: root/zencore/filesystem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zencore/filesystem.cpp')
-rw-r--r--zencore/filesystem.cpp276
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