Skip to content

Commit

Permalink
Optimize fs::CopyFileContents on Linux and Windows
Browse files Browse the repository at this point in the history
  • Loading branch information
sfan5 committed Feb 4, 2024
1 parent 714c936 commit 4259ac9
Showing 1 changed file with 127 additions and 54 deletions.
181 changes: 127 additions & 54 deletions src/filesys.cpp
Expand Up @@ -33,6 +33,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <IFileArchive.h>
#include <IFileSystem.h>
#endif
#ifdef __linux__
#include <fcntl.h>
#include <sys/ioctl.h>
#ifndef FICLONE
#define FICLONE _IOW(0x94, 9, int)
#endif
#endif

namespace fs
{
Expand Down Expand Up @@ -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

/*********
Expand Down Expand Up @@ -414,6 +446,101 @@ std::string CreateTempFile()
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

/****************************
Expand Down Expand Up @@ -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<<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)
{
if(PathExists(source)){
Expand Down

0 comments on commit 4259ac9

Please sign in to comment.