diff --git a/src/filesys.cpp b/src/filesys.cpp index 4fc51a5da954..5ea71b8fdc02 100644 --- a/src/filesys.cpp +++ b/src/filesys.cpp @@ -33,6 +33,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #endif +#ifdef __linux__ +#include +#include +#ifndef FICLONE +#define FICLONE _IOW(0x94, 9, int) +#endif +#endif namespace fs { @@ -216,6 +223,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 /********* @@ -414,6 +446,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 /**************************** @@ -488,60 +615,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<