From b64f4bdb3aa0687f80bb3f10dd80cfd8d0fd5771 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Sat, 20 Jan 2024 17:16:16 +0100 Subject: [PATCH] Optimize fs::CopyFileContents on Linux and Windows --- src/filesys.cpp | 181 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 127 insertions(+), 54 deletions(-) diff --git a/src/filesys.cpp b/src/filesys.cpp index d91199594..2239bac66 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -31,6 +31,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #ifndef SERVER #include "irr_ptr.h" #endif +#ifdef __linux__ +#include +#include +#ifndef FICLONE +#define FICLONE _IOW(0x94, 9, int) +#endif +#endif namespace fs { @@ -214,6 +221,31 @@ std::string CreateTempFile() return path; } +bool CopyFileContents(const std::string &source, const std::string &target) +{ + BOOL ok = CopyFileEx(source.c_str(), target.c_str(), nullptr, nullptr, + nullptr, COPY_FILE_ALLOW_DECRYPTED_DESTINATION); + if (!ok) { + errorstream << "copying " << source << " to " << target + << " failed: " << GetLastError() << std::endl; + return false; + } + + // docs: "File attributes for the existing file are copied to the new file." + // This is not our intention so get rid of unwanted attributes: + DWORD attr = GetFileAttributes(target.c_str()); + if (attr == INVALID_FILE_ATTRIBUTES) { + errorstream << target << ": file disappeared after copy" << std::endl; + return false; + } + attr &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN); + SetFileAttributes(target.c_str(), attr); + + tracestream << "copied " << source << " to " << target + << " using CopyFileEx" << std::endl; + return true; +} + #else /********* @@ -412,6 +444,101 @@ std::string CreateTempFile() return path; } +namespace { + struct FileDeleter { + void operator()(FILE *stream) { + fclose(stream); + } + }; + + typedef std::unique_ptr FileUniquePtr; +} + +bool CopyFileContents(const std::string &source, const std::string &target) +{ + FileUniquePtr sourcefile, targetfile; + +#ifdef __linux__ + // Try to clone using Copy-on-Write (CoW). This is instant but supported + // only by some filesystems. + + int srcfd, tgtfd; + srcfd = open(source.c_str(), O_RDONLY); + if (srcfd == -1) { + errorstream << source << ": can't open for reading: " + << strerror(errno) << std::endl; + return false; + } + tgtfd = open(target.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (tgtfd == -1) { + errorstream << target << ": can't open for writing: " + << strerror(errno) << std::endl; + close(srcfd); + return false; + } + + if (ioctl(tgtfd, FICLONE, srcfd) == 0) { + tracestream << "copied " << source << " to " << target + << " using FICLONE" << std::endl; + close(srcfd); + close(tgtfd); + return true; + } + + // fallback to normal copy, but no need to reopen the files + sourcefile.reset(fdopen(srcfd, "rb")); + targetfile.reset(fdopen(tgtfd, "wb")); + goto fallback; + +#endif + + sourcefile.reset(fopen(source.c_str(), "rb")); + targetfile.reset(fopen(target.c_str(), "wb")); + +fallback: + + if (!sourcefile) { + errorstream << source << ": can't open for reading: " + << strerror(errno) << std::endl; + return false; + } + if (!targetfile) { + errorstream << target << ": can't open for writing: " + << strerror(errno) << std::endl; + return false; + } + + size_t total = 0; + bool done = false; + char readbuffer[BUFSIZ]; + while (!done) { + size_t readbytes = fread(readbuffer, 1, + sizeof(readbuffer), sourcefile.get()); + total += readbytes; + if (ferror(sourcefile.get())) { + errorstream << source << ": IO error: " + << strerror(errno) << std::endl; + return false; + } + if (readbytes > 0) + fwrite(readbuffer, 1, readbytes, targetfile.get()); + if (feof(sourcefile.get())) { + // flush destination file to catch write errors (e.g. disk full) + fflush(targetfile.get()); + done = true; + } + if (ferror(targetfile.get())) { + errorstream << target << ": IO error: " + << strerror(errno) << std::endl; + return false; + } + } + tracestream << "copied " << total << " bytes from " + << source << " to " << target << std::endl; + + return true; +} + #endif /**************************** @@ -486,60 +613,6 @@ bool CreateAllDirs(const std::string &path) return true; } -bool CopyFileContents(const std::string &source, const std::string &target) -{ - FILE *sourcefile = fopen(source.c_str(), "rb"); - if(sourcefile == NULL){ - errorstream< 0){ - fwrite(readbuffer, 1, readbytes, targetfile); - } - if(feof(sourcefile) || ferror(sourcefile)){ - // flush destination file to catch write errors - // (e.g. disk full) - fflush(targetfile); - done = true; - } - if(ferror(targetfile)){ - errorstream<