Optimize fs::CopyFileContents on Linux and Windows

This commit is contained in:
sfan5 2024-01-20 17:16:16 +01:00
parent 714c9361ea
commit 4259ac96ea

@ -33,6 +33,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <IFileArchive.h> #include <IFileArchive.h>
#include <IFileSystem.h> #include <IFileSystem.h>
#endif #endif
#ifdef __linux__
#include <fcntl.h>
#include <sys/ioctl.h>
#ifndef FICLONE
#define FICLONE _IOW(0x94, 9, int)
#endif
#endif
namespace fs namespace fs
{ {
@ -216,6 +223,31 @@ std::string CreateTempFile()
return path; 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 #else
/********* /*********
@ -414,6 +446,101 @@ std::string CreateTempFile()
return path; return path;
} }
namespace {
struct FileDeleter {
void operator()(FILE *stream) {
fclose(stream);
}
};
typedef std::unique_ptr<FILE, FileDeleter> 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 #endif
/**************************** /****************************
@ -488,60 +615,6 @@ bool CreateAllDirs(const std::string &path)
return true; return true;
} }
bool CopyFileContents(const std::string &source, const std::string &target)
{
FILE *sourcefile = fopen(source.c_str(), "rb");
if(sourcefile == NULL){
errorstream<<source<<": can't open for reading: "
<<strerror(errno)<<std::endl;
return false;
}
FILE *targetfile = fopen(target.c_str(), "wb");
if(targetfile == NULL){
errorstream<<target<<": can't open for writing: "
<<strerror(errno)<<std::endl;
fclose(sourcefile);
return false;
}
size_t total = 0;
bool retval = true;
bool done = false;
char readbuffer[BUFSIZ];
while(!done){
size_t readbytes = fread(readbuffer, 1,
sizeof(readbuffer), sourcefile);
total += readbytes;
if(ferror(sourcefile)){
errorstream<<source<<": IO error: "
<<strerror(errno)<<std::endl;
retval = false;
done = true;
}
if(readbytes > 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<<target<<": IO error: "
<<strerror(errno)<<std::endl;
retval = false;
done = true;
}
}
infostream<<"copied "<<total<<" bytes from "
<<source<<" to "<<target<<std::endl;
fclose(sourcefile);
fclose(targetfile);
return retval;
}
bool CopyDir(const std::string &source, const std::string &target) bool CopyDir(const std::string &source, const std::string &target)
{ {
if(PathExists(source)){ if(PathExists(source)){