diff options
| author | Dan Engelbrecht <[email protected]> | 2025-10-11 20:30:01 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2025-10-11 20:30:01 +0200 |
| commit | ac6d1838d7b9d1a49f064bc67c027e0dda5348bb (patch) | |
| tree | f31f2da84564c735dbccc507628f034e6a29468f /src/zencore/filesystem.cpp | |
| parent | add ability to limit concurrency (#565) (diff) | |
| download | zen-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.cpp | 276 |
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) { |