aboutsummaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/zen/cmds/builds_cmd.cpp242
-rw-r--r--src/zen/cmds/builds_cmd.h1
-rw-r--r--src/zencore/filesystem.cpp276
-rw-r--r--src/zencore/include/zencore/filesystem.h23
4 files changed, 475 insertions, 67 deletions
diff --git a/src/zen/cmds/builds_cmd.cpp b/src/zen/cmds/builds_cmd.cpp
index 331b200c6..e40d4366b 100644
--- a/src/zen/cmds/builds_cmd.cpp
+++ b/src/zen/cmds/builds_cmd.cpp
@@ -371,9 +371,10 @@ namespace {
const std::vector<std::string_view> DefaultExcludeFolders({UnsyncFolderName, ZenFolderName, UGSFolderName, LegacyZenTempFolderName});
const std::vector<std::string_view> DefaultExcludeExtensions({});
- static bool IsVerbose = false;
- static bool IsQuiet = false;
- static ProgressBar::Mode ProgressMode = ProgressBar::Mode::Pretty;
+ static bool IsVerbose = false;
+ static bool IsQuiet = false;
+ static ProgressBar::Mode ProgressMode = ProgressBar::Mode::Pretty;
+ static bool AllowFileClone = true;
uint32_t GetUpdateDelayMS(ProgressBar::Mode InMode)
{
@@ -566,22 +567,34 @@ namespace {
const std::filesystem::path& TargetFilePath,
uint64_t RawSize,
std::atomic<uint64_t>& WriteCount,
- std::atomic<uint64_t>& WriteByteCount)
+ std::atomic<uint64_t>& WriteByteCount,
+ std::atomic<uint64_t>& CloneCount,
+ std::atomic<uint64_t>& CloneByteCount)
{
- BasicFile TargetFile(TargetFilePath, BasicFile::Mode::kTruncate);
- if (UseSparseFiles)
+ if (AllowFileClone && TryCloneFile(SourceFilePath, TargetFilePath))
{
- PrepareFileForScatteredWrite(TargetFile.Handle(), RawSize);
+ WriteCount += 1;
+ WriteByteCount += RawSize;
+ CloneCount += 1;
+ CloneByteCount += RawSize;
}
- uint64_t Offset = 0;
- if (!ScanFile(SourceFilePath, 512u * 1024u, [&](const void* Data, size_t Size) {
- TargetFile.Write(Data, Size, Offset);
- Offset += Size;
- WriteCount++;
- WriteByteCount += Size;
- }))
+ else
{
- throw std::runtime_error(fmt::format("Failed to copy scavenged file '{}' to '{}'", SourceFilePath, TargetFilePath));
+ BasicFile TargetFile(TargetFilePath, BasicFile::Mode::kTruncate);
+ if (UseSparseFiles)
+ {
+ PrepareFileForScatteredWrite(TargetFile.Handle(), RawSize);
+ }
+ uint64_t Offset = 0;
+ if (!ScanFile(SourceFilePath, 512u * 1024u, [&](const void* Data, size_t Size) {
+ TargetFile.Write(Data, Size, Offset);
+ Offset += Size;
+ WriteCount++;
+ WriteByteCount += Size;
+ }))
+ {
+ throw std::runtime_error(fmt::format("Failed to copy scavenged file '{}' to '{}'", SourceFilePath, TargetFilePath));
+ }
}
}
@@ -962,6 +975,8 @@ namespace {
std::atomic<uint64_t> ReadByteCount = 0;
std::atomic<uint64_t> WriteCount = 0;
std::atomic<uint64_t> WriteByteCount = 0;
+ std::atomic<uint64_t> CloneCount = 0;
+ std::atomic<uint64_t> CloneByteCount = 0;
std::atomic<uint64_t> CurrentOpenFileCount = 0;
};
@@ -2005,6 +2020,9 @@ namespace {
return Result;
}
+ public:
+ void* Handle() { return m_Source.Handle(); }
+
private:
BasicFile m_Source;
const uint64_t m_SourceSize;
@@ -4652,15 +4670,17 @@ namespace {
"\n OpenWriteCount: {}"
"\n ReadCount: {}"
"\n ReadByteCount: {}"
- "\n WriteCount: {}"
- "\n WriteByteCount: {}"
+ "\n WriteCount: {} ({} cloned)"
+ "\n WriteByteCount: {} ({} cloned)"
"\n CurrentOpenFileCount: {}",
DiskStats.OpenReadCount.load(),
DiskStats.OpenWriteCount.load(),
DiskStats.ReadCount.load(),
NiceBytes(DiskStats.ReadByteCount.load()),
DiskStats.WriteCount.load(),
+ DiskStats.CloneCount.load(),
NiceBytes(DiskStats.WriteByteCount.load()),
+ NiceBytes(DiskStats.CloneByteCount.load()),
DiskStats.CurrentOpenFileCount.load());
ZEN_CONSOLE_VERBOSE(
@@ -7106,8 +7126,14 @@ namespace {
const std::filesystem::path TempFilePath =
GetTempChunkedSequenceFileName(CacheFolderPath, RemoteSequenceRawHash);
- const uint64_t RawSize = ScavengedContent.RawSizes[ScavengeOp.ScavengedContentIndex];
- CopyFile(ScavengedFilePath, TempFilePath, RawSize, DiskStats.WriteCount, DiskStats.WriteByteCount);
+ const uint64_t RawSize = ScavengedContent.RawSizes[ScavengeOp.ScavengedPathIndex];
+ CopyFile(ScavengedFilePath,
+ TempFilePath,
+ RawSize,
+ DiskStats.WriteCount,
+ DiskStats.WriteByteCount,
+ DiskStats.CloneCount,
+ DiskStats.CloneByteCount);
const std::filesystem::path CacheFilePath =
GetFinalChunkedSequenceFileName(CacheFolderPath, RemoteSequenceRawHash);
@@ -7467,6 +7493,12 @@ namespace {
});
}
+ std::unique_ptr<CloneQueryInterface> CloneQuery;
+ if (AllowFileClone)
+ {
+ CloneQuery = GetCloneQueryInterface(CacheFolderPath);
+ }
+
for (size_t CopyDataIndex = 0; CopyDataIndex < CacheCopyDatas.size(); CopyDataIndex++)
{
ZEN_ASSERT(!Options.PrimeCacheOnly);
@@ -7482,6 +7514,7 @@ namespace {
&RemoteContent,
&RemoteLookup,
&CacheFolderPath,
+ &CloneQuery,
&LocalLookup,
&SequenceIndexChunksLeftToWriteCounters,
&WriteCache,
@@ -7577,7 +7610,10 @@ namespace {
tsl::robin_set<uint32_t> ChunkIndexesWritten;
- BufferedOpenFile SourceFile(SourceFilePath, DiskStats);
+ BufferedOpenFile SourceFile(SourceFilePath, DiskStats);
+
+ bool CanCloneSource = CloneQuery && CloneQuery->CanClone(SourceFile.Handle());
+
BufferedWriteFileCache::Local LocalWriter(WriteCache);
for (size_t WriteOpIndex = 0; WriteOpIndex < WriteOps.size();)
@@ -7593,6 +7629,7 @@ namespace {
RemoteContent.ChunkedContent.ChunkCounts[RemoteSequenceIndex]);
ZEN_ASSERT(SequenceIndexChunksLeftToWriteCounters[RemoteSequenceIndex].load() > 0);
const uint32_t RemotePathIndex = RemoteLookup.SequenceIndexFirstPathIndex[RemoteSequenceIndex];
+ const uint64_t TargetSize = RemoteContent.RawSizes[RemotePathIndex];
const uint64_t ChunkSize = RemoteContent.ChunkedContent.ChunkRawSizes[Op.ChunkIndex];
uint64_t ReadLength = ChunkSize;
@@ -7625,18 +7662,102 @@ namespace {
WriteCount++;
}
- CompositeBuffer ChunkSource = SourceFile.GetRange(Op.CacheFileOffset, ReadLength);
+ {
+ bool DidClone = false;
+
+ if (CanCloneSource)
+ {
+ uint64_t PreBytes = 0;
+ uint64_t PostBytes = 0;
+ uint64_t ClonableBytes = CloneQuery->GetClonableRange(Op.CacheFileOffset,
+ Op.Target->Offset,
+ ReadLength,
+ PreBytes,
+ PostBytes);
+ if (ClonableBytes > 0)
+ {
+ // We need to open the file...
+ BufferedWriteFileCache::Local::Writer* Writer = LocalWriter.GetWriter(RemoteSequenceIndex);
+ if (!Writer)
+ {
+ Writer =
+ LocalWriter.PutWriter(RemoteSequenceIndex,
+ std::make_unique<BufferedWriteFileCache::Local::Writer>());
+
+ Writer->File = std::make_unique<BasicFile>();
+
+ const std::filesystem::path FileName = GetTempChunkedSequenceFileName(
+ CacheFolderPath,
+ RemoteContent.ChunkedContent.SequenceRawHashes[RemoteSequenceIndex]);
+ Writer->File->Open(FileName, BasicFile::Mode::kWrite);
+ if (UseSparseFiles)
+ {
+ PrepareFileForScatteredWrite(Writer->File->Handle(), TargetSize);
+ }
+ }
+ DidClone = CloneQuery->TryClone(SourceFile.Handle(),
+ Writer->File->Handle(),
+ Op.CacheFileOffset + PreBytes,
+ Op.Target->Offset + PreBytes,
+ ClonableBytes,
+ TargetSize);
+ if (DidClone)
+ {
+ DiskStats.WriteCount++;
+ DiskStats.WriteByteCount += ClonableBytes;
+
+ DiskStats.CloneCount++;
+ DiskStats.CloneByteCount += ClonableBytes;
- const uint64_t FileOffset = Op.Target->Offset;
+ if (PreBytes > 0)
+ {
+ CompositeBuffer ChunkSource = SourceFile.GetRange(Op.CacheFileOffset, PreBytes);
+ const uint64_t FileOffset = Op.Target->Offset;
+
+ WriteSequenceChunk(CacheFolderPath,
+ RemoteContent,
+ LocalWriter,
+ ChunkSource,
+ RemoteSequenceIndex,
+ FileOffset,
+ RemotePathIndex,
+ DiskStats);
+ }
+ if (PostBytes > 0)
+ {
+ CompositeBuffer ChunkSource =
+ SourceFile.GetRange(Op.CacheFileOffset + ReadLength - PostBytes, PostBytes);
+ const uint64_t FileOffset = Op.Target->Offset + ReadLength - PostBytes;
+
+ WriteSequenceChunk(CacheFolderPath,
+ RemoteContent,
+ LocalWriter,
+ ChunkSource,
+ RemoteSequenceIndex,
+ FileOffset,
+ RemotePathIndex,
+ DiskStats);
+ }
+ }
+ }
+ }
- WriteSequenceChunk(CacheFolderPath,
- RemoteContent,
- LocalWriter,
- ChunkSource,
- RemoteSequenceIndex,
- FileOffset,
- RemotePathIndex,
- DiskStats);
+ if (!DidClone)
+ {
+ CompositeBuffer ChunkSource = SourceFile.GetRange(Op.CacheFileOffset, ReadLength);
+
+ const uint64_t FileOffset = Op.Target->Offset;
+
+ WriteSequenceChunk(CacheFolderPath,
+ RemoteContent,
+ LocalWriter,
+ ChunkSource,
+ RemoteSequenceIndex,
+ FileOffset,
+ RemotePathIndex,
+ DiskStats);
+ }
+ }
CacheLocalFileBytesRead += ReadLength; // TODO: This should be the sum of unique chunk sizes?
@@ -8169,11 +8290,17 @@ namespace {
(DownloadStats.RequestsCompleteCount == TotalRequestCount)
? ""
: fmt::format(" {}bits/s", NiceNum(FilteredDownloadedBytesPerSecond.GetCurrent() * 8));
+ std::string CloneDetails;
+ if (DiskStats.CloneCount.load() > 0)
+ {
+ CloneDetails = fmt::format(" ({} cloned)", NiceBytes(DiskStats.CloneByteCount.load()));
+ }
std::string WriteDetails = Options.PrimeCacheOnly ? ""
- : fmt::format(" {}/{} ({}B/s) written.",
+ : fmt::format(" {}/{} ({}B/s) written{}.",
NiceBytes(DiskStats.WriteByteCount.load()),
NiceBytes(BytesToWrite),
- NiceNum(FilteredWrittenBytesPerSecond.GetCurrent()));
+ NiceNum(FilteredWrittenBytesPerSecond.GetCurrent()),
+ CloneDetails);
std::string Details = fmt::format("{}/{} ({}{}) downloaded.{}",
DownloadStats.RequestsCompleteCount.load(),
TotalRequestCount,
@@ -8191,6 +8318,8 @@ namespace {
});
}
+ CloneQuery.reset();
+
FilteredWrittenBytesPerSecond.Stop();
FilteredDownloadedBytesPerSecond.Stop();
@@ -8230,12 +8359,18 @@ namespace {
+DownloadStats.DownloadedPartialBlockByteCount.load();
if (!IsQuiet)
{
- ZEN_CONSOLE("Downloaded {} ({}bits/s) in {}. Wrote {} ({}B/s) in {}. Completed in {}",
+ std::string CloneDetails;
+ if (DiskStats.CloneCount.load() > 0)
+ {
+ CloneDetails = fmt::format(" ({} cloned)", NiceBytes(DiskStats.CloneByteCount.load()));
+ }
+ ZEN_CONSOLE("Downloaded {} ({}bits/s) in {}. Wrote {} ({}B/s){} in {}. Completed in {}",
NiceBytes(DownloadedBytes),
NiceNum(GetBytesPerSecond(FilteredDownloadedBytesPerSecond.GetElapsedTimeUS(), DownloadedBytes * 8)),
NiceTimeSpanMs(FilteredDownloadedBytesPerSecond.GetElapsedTimeUS() / 1000),
NiceBytes(DiskStats.WriteByteCount.load()),
NiceNum(GetBytesPerSecond(FilteredWrittenBytesPerSecond.GetElapsedTimeUS(), DiskStats.WriteByteCount.load())),
+ CloneDetails,
NiceTimeSpanMs(FilteredWrittenBytesPerSecond.GetElapsedTimeUS() / 1000),
NiceTimeSpanMs(WriteTimer.GetElapsedTimeMs()));
}
@@ -8603,7 +8738,15 @@ namespace {
const uint64_t RawSize = LocalContent.RawSizes[LocalPathIndex];
std::atomic<uint64_t> WriteCount;
std::atomic<uint64_t> WriteByteCount;
- CopyFile(SourceFilePath, FirstTargetFilePath, RawSize, WriteCount, WriteByteCount);
+ std::atomic<uint64_t> CloneCount;
+ std::atomic<uint64_t> CloneByteCount;
+ CopyFile(SourceFilePath,
+ FirstTargetFilePath,
+ RawSize,
+ WriteCount,
+ WriteByteCount,
+ CloneCount,
+ CloneByteCount);
RebuildFolderStateStats.FinalizeTreeFilesCopiedCount++;
}
else
@@ -8663,7 +8806,15 @@ namespace {
const uint64_t RawSize = RemoteContent.RawSizes[RemotePathIndex];
std::atomic<uint64_t> WriteCount;
std::atomic<uint64_t> WriteByteCount;
- CopyFile(FirstTargetFilePath, TargetFilePath, RawSize, WriteCount, WriteByteCount);
+ std::atomic<uint64_t> CloneCount;
+ std::atomic<uint64_t> CloneByteCount;
+ CopyFile(FirstTargetFilePath,
+ TargetFilePath,
+ RawSize,
+ WriteCount,
+ WriteByteCount,
+ CloneCount,
+ CloneByteCount);
RebuildFolderStateStats.FinalizeTreeFilesCopiedCount++;
}
@@ -10546,6 +10697,13 @@ BuildsCommand::BuildsCommand()
m_DownloadOptions.parse_positional({"local-path", "build-id", "build-part-name"});
m_DownloadOptions.positional_help("local-path build-id build-part-name");
+ m_DownloadOptions.add_option("",
+ "",
+ "allow-file-clone",
+ "Enable use of block reference counting when copying files",
+ cxxopts::value(m_AllowFileClone),
+ "<allowclone>");
+
// ls
AddSystemOptions(m_LsOptions);
AddCloudOptions(m_LsOptions);
@@ -10608,6 +10766,12 @@ BuildsCommand::BuildsCommand()
"Enable scavenging of data from previouse download locations",
cxxopts::value(m_EnableScavenging),
"<scavenge>");
+ m_TestOptions.add_option("",
+ "",
+ "allow-file-clone",
+ "Enable use of block reference counting when copying files",
+ cxxopts::value(m_AllowFileClone),
+ "<allowclone>");
m_TestOptions.parse_positional({"local-path"});
m_TestOptions.positional_help("local-path");
@@ -10679,6 +10843,12 @@ BuildsCommand::BuildsCommand()
"Enable scavenging of data from previouse download locations",
cxxopts::value(m_EnableScavenging),
"<scavenge>");
+ m_MultiTestDownloadOptions.add_option("",
+ "",
+ "allow-file-clone",
+ "Enable use of block reference counting when copying files",
+ cxxopts::value(m_AllowFileClone),
+ "<allowclone>");
m_MultiTestDownloadOptions.parse_positional({"local-path"});
m_MultiTestDownloadOptions.positional_help("local-path");
}
@@ -10814,6 +10984,8 @@ BuildsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
};
ParseOutputOptions();
+ AllowFileClone = m_AllowFileClone;
+
std::unique_ptr<AuthMgr> Auth;
HttpClientSettings ClientSettings{.LogCategory = "httpbuildsclient",
.AssumeHttp2 = m_AssumeHttp2,
diff --git a/src/zen/cmds/builds_cmd.h b/src/zen/cmds/builds_cmd.h
index ddec1f791..e909a6155 100644
--- a/src/zen/cmds/builds_cmd.h
+++ b/src/zen/cmds/builds_cmd.h
@@ -35,6 +35,7 @@ private:
bool m_BoostWorkerThreads = false;
bool m_UseSparseFiles = true;
bool m_Quiet = false;
+ bool m_AllowFileClone = true;
std::filesystem::path m_ZenFolderPath;
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)
{
diff --git a/src/zencore/include/zencore/filesystem.h b/src/zencore/include/zencore/filesystem.h
index 36d4d1b68..92b249cec 100644
--- a/src/zencore/include/zencore/filesystem.h
+++ b/src/zencore/include/zencore/filesystem.h
@@ -179,6 +179,29 @@ ZENCORE_API void WriteFile(void* NativeHandle,
std::error_code& Ec);
ZENCORE_API void ReadFile(void* NativeHandle, void* Data, uint64_t Size, uint64_t FileOffset, uint64_t ChunkSize, std::error_code& Ec);
+class CloneQueryInterface
+{
+public:
+ virtual ~CloneQueryInterface() {}
+
+ virtual bool CanClone(void* SourceNativeHandle) = 0;
+ virtual uint64_t GetClonableRange(uint64_t SourceOffset,
+ uint64_t TargetOffset,
+ uint64_t Size,
+ uint64_t& OutPreBytes,
+ uint64_t& OutPostBytes) = 0;
+ virtual bool TryClone(void* SourceNativeHandle,
+ void* TargetNativeHandle,
+ uint64_t AlignedSourceOffset,
+ uint64_t AlignedTargetOffset,
+ uint64_t AlignedSize,
+ uint64_t TargetFinalSize) = 0;
+};
+
+ZENCORE_API std::unique_ptr<CloneQueryInterface> GetCloneQueryInterface(const std::filesystem::path& TargetDirectory);
+
+ZENCORE_API bool TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToPath);
+
struct CopyFileOptions
{
bool EnableClone = true;