aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/filesystem.cpp
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2025-10-11 20:30:01 +0200
committerGitHub Enterprise <[email protected]>2025-10-11 20:30:01 +0200
commitac6d1838d7b9d1a49f064bc67c027e0dda5348bb (patch)
treef31f2da84564c735dbccc507628f034e6a29468f /src/zencore/filesystem.cpp
parentadd ability to limit concurrency (#565) (diff)
downloadzen-ac6d1838d7b9d1a49f064bc67c027e0dda5348bb.tar.xz
zen-ac6d1838d7b9d1a49f064bc67c027e0dda5348bb.zip
block reference couting copy support windows (#564)
- Improvement: On Windows file systems that allow block reference counting we use it where possible to speed up copy of data during `zen builds download` operations - Enabled by default, disable with `--allow-file-clone=false`
Diffstat (limited to 'src/zencore/filesystem.cpp')
-rw-r--r--src/zencore/filesystem.cpp276
1 files changed, 244 insertions, 32 deletions
diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp
index d18f21dbe..5ac460fd9 100644
--- a/src/zencore/filesystem.cpp
+++ b/src/zencore/filesystem.cpp
@@ -621,24 +621,217 @@ SupportsBlockRefCounting(std::filesystem::path Path)
#endif // ZEN_PLATFORM_WINDOWS
}
-static bool
-CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath)
+#if ZEN_PLATFORM_WINDOWS
+class WindowsCloneQueryInterface : public CloneQueryInterface
+{
+public:
+ WindowsCloneQueryInterface(uint64_t AlignmentSize, DWORD TargetVolumeSerialNumber)
+ : m_AlignmentSize(AlignmentSize)
+ , m_TargetVolumeSerialNumber(TargetVolumeSerialNumber)
+ {
+ }
+ virtual bool CanClone(void* SourceNativeHandle) override
+ {
+ HANDLE SourceFile = (HANDLE)SourceNativeHandle;
+ DWORD SourceVolumeSerialNumber = 0;
+ if (GetVolumeInformationByHandleW(SourceFile, nullptr, MAX_PATH, &SourceVolumeSerialNumber, nullptr, nullptr, nullptr, 0) == FALSE)
+ {
+ return false;
+ }
+ if (SourceVolumeSerialNumber != m_TargetVolumeSerialNumber)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ virtual uint64_t GetClonableRange(uint64_t SourceOffset,
+ uint64_t TargetOffset,
+ uint64_t Size,
+ uint64_t& OutPreBytes,
+ uint64_t& OutPostBytes) override
+ {
+ if (Size < m_AlignmentSize)
+ {
+ return 0;
+ }
+
+ uint64_t PreBytes = (m_AlignmentSize - (SourceOffset % m_AlignmentSize)) % m_AlignmentSize;
+ uint64_t PostBytes = (SourceOffset + Size) % m_AlignmentSize;
+ ZEN_ASSERT(Size >= PreBytes + PostBytes);
+ if (Size - (PreBytes + PostBytes) < m_AlignmentSize)
+ {
+ return 0;
+ }
+ ZEN_ASSERT((PreBytes < Size && PostBytes < Size && Size >= PreBytes + PostBytes + m_AlignmentSize));
+
+ const uint64_t DestCloneOffset = TargetOffset + PreBytes;
+ if (DestCloneOffset % m_AlignmentSize != 0)
+ {
+ return 0;
+ }
+
+ OutPreBytes = PreBytes;
+ OutPostBytes = PostBytes;
+ uint64_t CloneSize = Size - (PreBytes + PostBytes);
+ ZEN_ASSERT(CloneSize % m_AlignmentSize == 0);
+ return CloneSize;
+ }
+
+ virtual bool TryClone(void* SourceNativeHandle,
+ void* TargetNativeHandle,
+ uint64_t AlignedSourceOffset,
+ uint64_t AlignedTargetOffset,
+ uint64_t AlignedSize,
+ uint64_t TargetFinalSize)
+ {
+ ZEN_ASSERT_SLOW(CanClone(SourceNativeHandle));
+ ZEN_ASSERT((AlignedSourceOffset % m_AlignmentSize) == 0);
+ ZEN_ASSERT((AlignedTargetOffset % m_AlignmentSize) == 0);
+ ZEN_ASSERT(AlignedSize > 0);
+ ZEN_ASSERT((AlignedSize % m_AlignmentSize) == 0);
+
+ HANDLE SourceFile = (HANDLE)SourceNativeHandle;
+ ZEN_ASSERT(SourceFile != INVALID_HANDLE_VALUE);
+ HANDLE TargetFile = (HANDLE)TargetNativeHandle;
+ ZEN_ASSERT(TargetFile != INVALID_HANDLE_VALUE);
+
+ FILE_BASIC_INFO SourceFileInfo{};
+ if (GetFileInformationByHandleEx(SourceFile, FileBasicInfo, &SourceFileInfo, sizeof(SourceFileInfo)) == FALSE)
+ {
+ std::error_code DummyEc;
+ ZEN_DEBUG("Failed to get file information for file {}", PathFromHandle(SourceFile, DummyEc));
+ return false;
+ }
+ FILE_BASIC_INFO DestFileInfo{};
+ if (GetFileInformationByHandleEx(TargetFile, FileBasicInfo, &DestFileInfo, sizeof(DestFileInfo)) == FALSE)
+ {
+ std::error_code DummyEc;
+ ZEN_DEBUG("Failed to get file information for file {}", PathFromHandle(TargetFile, DummyEc));
+ return false;
+ }
+
+ if ((SourceFileInfo.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != (DestFileInfo.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE))
+ {
+ const bool SourceIsSparse = (SourceFileInfo.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0;
+ FILE_SET_SPARSE_BUFFER Sparse{};
+ Sparse.SetSparse = SourceIsSparse ? TRUE : FALSE;
+ DWORD BytesReturned{};
+ if (!DeviceIoControl(TargetFile, FSCTL_SET_SPARSE, &Sparse, sizeof(Sparse), nullptr, 0, &BytesReturned, nullptr))
+ {
+ std::error_code DummyEc;
+ ZEN_DEBUG("Failed setting sparse status for file {} to {}", PathFromHandle(TargetFile, DummyEc), SourceIsSparse);
+ return false;
+ }
+ }
+
+ {
+ const uint64_t TargetFileSize = FileSizeFromHandle(TargetFile);
+ if (TargetFileSize != TargetFinalSize)
+ {
+ FILE_END_OF_FILE_INFO EOFInfo{};
+ EOFInfo.EndOfFile.QuadPart = TargetFinalSize;
+ if (!SetFileInformationByHandle(TargetFile, FileEndOfFileInfo, &EOFInfo, sizeof(EOFInfo)))
+ {
+ std::error_code DummyEc;
+ ZEN_DEBUG("Failed setting final size {} for file {}", TargetFinalSize, PathFromHandle(TargetFile, DummyEc));
+ return false;
+ }
+ }
+ }
+ {
+ DUPLICATE_EXTENTS_DATA Extent{};
+ Extent.FileHandle = SourceFile;
+ Extent.SourceFileOffset.QuadPart = AlignedSourceOffset;
+ Extent.TargetFileOffset.QuadPart = AlignedTargetOffset;
+ Extent.ByteCount.QuadPart = AlignedSize;
+ DWORD BytesReturned{};
+ if (DeviceIoControl(TargetFile, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &Extent, sizeof(Extent), NULL, 0, &BytesReturned, NULL) !=
+ TRUE)
+ {
+ std::error_code DummyEc;
+ ZEN_DEBUG("Failed cloning {} bytes from file {} at {} to file {} at {}",
+ Extent.ByteCount.QuadPart,
+ PathFromHandle(SourceFile, DummyEc),
+ Extent.SourceFileOffset.QuadPart,
+ PathFromHandle(TargetFile, DummyEc),
+ Extent.TargetFileOffset.QuadPart);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+private:
+ uint64_t m_AlignmentSize;
+ DWORD m_TargetVolumeSerialNumber;
+};
+
+#endif // ZEN_PLATFORM_WINDOWS
+
+std::unique_ptr<CloneQueryInterface>
+GetCloneQueryInterface(const std::filesystem::path& TargetDirectory)
{
#if ZEN_PLATFORM_WINDOWS
- windows::Handle FromFile(CreateFileW(FromPath.c_str(),
- GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- nullptr,
- OPEN_EXISTING,
- 0,
- nullptr));
- if (FromFile == INVALID_HANDLE_VALUE)
+ std::filesystem::path TargetFilePath = TargetDirectory / ".cloneinfo";
+ windows::Handle TargetFile(CreateFileW(TargetFilePath.c_str(),
+ GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr,
+ OPEN_ALWAYS,
+ 0,
+ nullptr));
+ if (TargetFile == INVALID_HANDLE_VALUE)
{
- FromFile.Detach();
- return false;
+ TargetFile.Detach();
+ ThrowLastError(fmt::format("Failed opening file '{}' for write", TargetFilePath));
+ }
+
+ DWORD DestVolumeSerialNumber = 0;
+ DWORD DestVolumeFlags = 0;
+ if (GetVolumeInformationByHandleW(TargetFile, nullptr, 0, &DestVolumeSerialNumber, nullptr, &DestVolumeFlags, nullptr, 0) == FALSE)
+ {
+ ZEN_DEBUG("Failed to get volume information for path {}", TargetFilePath);
+ return {};
}
+ TargetFile.Close();
+ std::error_code Dummy;
+ RemoveFile(TargetFilePath, Dummy);
+ if ((DestVolumeFlags & FILE_SUPPORTS_BLOCK_REFCOUNTING) == 0)
+ {
+ return {};
+ }
+
+ std::filesystem::path::string_type NativePath = TargetFilePath;
+ if (NativePath.find(TEXT("\\\\?\\")) == 0)
+ {
+ NativePath = NativePath.substr(4);
+ }
+ if (std::filesystem::path::string_type::size_type DelimiterPos = NativePath.find(TEXT(":\\"));
+ DelimiterPos != std::filesystem::path::string_type::npos)
+ {
+ NativePath = NativePath.substr(0, DelimiterPos + 2);
+ DWORD SectorsPerCluster = 0;
+ DWORD BytesPerSector = 0;
+ if (GetDiskFreeSpace(NativePath.c_str(), &SectorsPerCluster, &BytesPerSector, nullptr, nullptr) == TRUE)
+ {
+ return std::make_unique<WindowsCloneQueryInterface>(SectorsPerCluster * BytesPerSector, DestVolumeSerialNumber);
+ }
+ }
+#else // ZEN_PLATFORM_WINDOWS
+ ZEN_UNUSED(TargetDirectory);
+#endif // ZEN_PLATFORM_WINDOWS
+ return {};
+}
- ULONG FileSystemFlags;
+#if ZEN_PLATFORM_WINDOWS
+static bool
+TryCloneFile(void* SourceNativeHandle, void* TargetNativeHandle)
+{
+ HANDLE FromFile = (HANDLE)SourceNativeHandle;
+ HANDLE TargetFile = (HANDLE)TargetNativeHandle;
+ ULONG FileSystemFlags;
if (!GetVolumeInformationByHandleW(FromFile, nullptr, 0, nullptr, nullptr, /* lpFileSystemFlags */ &FileSystemFlags, nullptr, 0))
{
return false;
@@ -675,29 +868,11 @@ CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath)
return false;
}
- SetFileAttributesW(ToPath.c_str(), FILE_ATTRIBUTE_NORMAL);
-
- windows::Handle TargetFile(CreateFileW(ToPath.c_str(),
- GENERIC_READ | GENERIC_WRITE | DELETE,
- /* no sharing */ FILE_SHARE_READ,
- nullptr,
- OPEN_ALWAYS,
- 0,
- /* hTemplateFile */ FromFile));
-
- if (TargetFile == INVALID_HANDLE_VALUE)
- {
- TargetFile.Detach();
- return false;
- }
-
// Delete target file when handle is closed (we only reset this if the copy succeeds)
FILE_DISPOSITION_INFO FileDisposition = {TRUE};
if (!SetFileInformationByHandle(TargetFile, FileDispositionInfo, &FileDisposition, sizeof FileDisposition))
{
const DWORD ErrorCode = ::GetLastError();
- TargetFile.Close();
- DeleteFileW(ToPath.c_str());
SetLastError(ErrorCode);
return false;
}
@@ -787,6 +962,43 @@ CloneFile(std::filesystem::path FromPath, std::filesystem::path ToPath)
const bool AllOk = (TRUE == SetFileInformationByHandle(TargetFile, FileDispositionInfo, &FileDisposition, sizeof FileDisposition));
return AllOk;
+}
+#endif // ZEN_PLATFORM_WINDOWS
+
+bool
+TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToPath)
+{
+#if ZEN_PLATFORM_WINDOWS
+ windows::Handle FromFile(CreateFileW(FromPath.c_str(),
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr,
+ OPEN_EXISTING,
+ 0,
+ nullptr));
+ if (FromFile == INVALID_HANDLE_VALUE)
+ {
+ FromFile.Detach();
+ return false;
+ }
+
+ SetFileAttributesW(ToPath.c_str(), FILE_ATTRIBUTE_NORMAL);
+
+ windows::Handle TargetFile(CreateFileW(ToPath.c_str(),
+ GENERIC_READ | GENERIC_WRITE | DELETE,
+ /* no sharing */ FILE_SHARE_READ,
+ nullptr,
+ OPEN_ALWAYS,
+ 0,
+ /* hTemplateFile */ FromFile));
+
+ if (TargetFile == INVALID_HANDLE_VALUE)
+ {
+ TargetFile.Detach();
+ return false;
+ }
+ return TryCloneFile((void*)FromFile.m_Handle, (void*)TargetFile.m_Handle);
+
#elif ZEN_PLATFORM_LINUX
# if 0
struct ScopedFd
@@ -850,7 +1062,7 @@ CopyFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToP
if (Options.EnableClone)
{
- Success = CloneFile(FromPath.native(), ToPath.native());
+ Success = TryCloneFile(FromPath.native(), ToPath.native());
if (Success)
{