summaryrefslogtreecommitdiff
path: root/zen/file_access.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zen/file_access.cpp')
-rw-r--r--zen/file_access.cpp541
1 files changed, 209 insertions, 332 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index ca07e76a..ffbdc813 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -25,7 +25,7 @@
#elif defined ZEN_LINUX
#include <sys/vfs.h> //statfs
- #include <fcntl.h> //AT_SYMLINK_NOFOLLOW, UTIME_OMIT
+ #include <sys/time.h> //lutimes
#ifdef HAVE_SELINUX
#include <selinux/selinux.h>
#endif
@@ -36,8 +36,8 @@
#endif
#if defined ZEN_LINUX || defined ZEN_MAC
+ #include <fcntl.h> //open, close, AT_SYMLINK_NOFOLLOW, UTIME_OMIT
#include <sys/stat.h>
- #include <sys/time.h> //lutimes
#endif
using namespace zen;
@@ -250,7 +250,7 @@ std::uint64_t zen::getFilesize(const Zstring& filepath) //throw FileError
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"CreateFile", getLastError());
ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
- //why not use ::GetFileSizeEx() instead???
+ //why not use ::GetFileSizeEx() instead???
BY_HANDLE_FILE_INFORMATION fileInfoHnd = {};
if (!::GetFileInformationByHandle(hFile, &fileInfoHnd))
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"GetFileInformationByHandle", getLastError());
@@ -462,7 +462,8 @@ Zstring findUnused8Dot3Name(const Zstring& filepath) //find a unique 8.3 short n
if (!somethingExists(output)) //ensure uniqueness
return output;
}
- throw std::runtime_error(std::string("100000000 files, one for each number, exist in this directory? You're kidding...") + utfCvrtTo<std::string>(pathPrefix));
+ throw std::runtime_error(std::string("100000000 files, one for each number, exist in this directory? You're kidding...") + utfCvrtTo<std::string>(pathPrefix) +
+ "\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
}
@@ -581,18 +582,18 @@ void removeDirectoryImpl(const Zstring& directory, //throw FileError
{
std::vector<Zstring> fileList;
std::vector<Zstring> dirList;
- //get all files and directories from current directory (WITHOUT subdirectories!)
-traverseFolder(directory,
- [&](const FileInfo& fi){ fileList.push_back(fi.fullPath); },
- [&](const DirInfo& di){ dirList .push_back(di.fullPath); },
- [&](const SymlinkInfo& si)
-{
- if (dirExists(si.fullPath)) //dir symlink
- dirList.push_back(si.fullPath);
- else //file symlink, broken symlink
- fileList.push_back(si.fullPath);
-},
-[&](const std::wstring& errorMsg){ throw FileError(errorMsg); });
+ //get all files and directories from current directory (WITHOUT subdirectories!)
+ traverseFolder(directory,
+ [&](const FileInfo& fi) { fileList.push_back(fi.fullPath); },
+ [&](const DirInfo& di) { dirList .push_back(di.fullPath); },
+ [&](const SymlinkInfo& si)
+ {
+ if (dirExists(si.fullPath)) //dir symlink
+ dirList.push_back(si.fullPath);
+ else //file symlink, broken symlink
+ fileList.push_back(si.fullPath);
+ },
+ [&](const std::wstring& errorMsg) { throw FileError(errorMsg); });
//delete directories recursively
for (const Zstring& dirpath : dirList)
@@ -955,26 +956,52 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink
setFileTimeRaw(filepath, nullptr, timetToFileTime(modTime), procSl); //throw FileError
#elif defined ZEN_LINUX
- //sigh, we can't use utimensat on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit???
-
- // struct ::timespec newTimes[2] = {};
- // newTimes[0].tv_nsec = UTIME_OMIT; //omit access time
- // newTimes[1].tv_sec = to<time_t>(modTime); //modification time (seconds)
- //
- // if (::utimensat(AT_FDCWD, filepath.c_str(), newTimes, procSl == SYMLINK_DIRECT ? AT_SYMLINK_NOFOLLOW : 0) != 0)
- // throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimensat", getLastError());
-
+ //[2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit???
//=> fallback to "retarded-idiot version"! -- DarkByte
+ //
+ //[2015-03-09]
+ // - cannot reproduce issues with NTFS and utimensat() on Ubuntu
+ // - utimensat() is supposed to obsolete utime/utimes and is also used by "touch"
+ //=> let's give utimensat another chance:
+ struct ::timespec newTimes[2] = {};
+ newTimes[0].tv_nsec = UTIME_OMIT; //omit access time
+ newTimes[1].tv_sec = modTime; //modification time (seconds)
- struct ::timeval newTimes[2] = {};
- newTimes[0].tv_sec = ::time(nullptr); //access time (seconds)
- newTimes[1].tv_sec = modTime; //modification time (seconds)
+ if (procSl == ProcSymlink::FOLLOW)
+ {
+ //don't use utimensat() directly, but open file descriptor manually:
+ //=> solves EINVAL bug for certain CIFS/NTFS drives: https://sourceforge.net/p/freefilesync/discussion/help/thread/1ace042d/
+ //=> using utimensat(AT_SYMLINK_NOFOLLOW) for symlinks and open()/futimens() for regular files is consistent with "cp" and "touch"!
+ const int fdFile = ::open(filepath.c_str(), O_WRONLY, 0); //"if O_CREAT is not specified, then mode is ignored"
+ if (fdFile == -1)
+ throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"open", getLastError());
+ ZEN_ON_SCOPE_EXIT(::close(fdFile));
+
+ if (::futimens(fdFile, newTimes) != 0)
+ throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"futimens", getLastError());
+ }
+ else
+ {
+ if (::utimensat(AT_FDCWD, filepath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0)
+ throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimensat", getLastError());
+ }
- const int rv = procSl == ProcSymlink::FOLLOW ?
- :: utimes(filepath.c_str(), newTimes) :
- ::lutimes(filepath.c_str(), newTimes);
- if (rv != 0)
- throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimes", getLastError());
+ /*
+ struct ::timeval newTimes[2] = {};
+ newTimes[0].tv_sec = ::time(nullptr); //access time (seconds)
+ newTimes[1].tv_sec = modTime; //modification time (seconds)
+
+ if (procSl == ProcSymlink::FOLLOW)
+ {
+ if (::utimes(filepath.c_str(), newTimes) != 0)
+ throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimes", getLastError());
+ }
+ else
+ {
+ if (::lutimes(filepath.c_str(), newTimes) != 0)
+ throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"lutimes", getLastError());
+ }
+ */
#elif defined ZEN_MAC
struct ::timespec writeTime = {};
@@ -1063,15 +1090,15 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli
//copy permissions for files, directories or symbolic links: requires admin rights
-void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError
+void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, ProcSymlink procSl) //throw FileError
{
#ifdef ZEN_WIN
//in contrast to ::SetSecurityInfo(), ::SetFileSecurity() seems to honor the "inherit DACL/SACL" flags
//CAVEAT: if a file system does not support ACLs, GetFileSecurity() will return successfully with a *valid* security descriptor containing *no* ACL entries!
//NOTE: ::GetFileSecurity()/::SetFileSecurity() do NOT follow Symlinks! getResolvedFilePath() requires Vista or later!
- const Zstring sourceResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(source) ? getResolvedFilePath(source) : source; //throw FileError
- const Zstring targetResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(target) ? getResolvedFilePath(target) : target; //
+ const Zstring sourceResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(sourcePath) ? getResolvedFilePath(sourcePath) : sourcePath; //throw FileError
+ const Zstring targetResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(targetPath) ? getResolvedFilePath(targetPath) : targetPath; //
//setting privileges requires admin rights!
try
@@ -1217,32 +1244,32 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym
#elif defined ZEN_LINUX
#ifdef HAVE_SELINUX //copy SELinux security context
- copySecurityContext(source, target, procSl); //throw FileError
+ copySecurityContext(sourcePath, targetPath, procSl); //throw FileError
#endif
struct ::stat fileInfo = {};
if (procSl == ProcSymlink::FOLLOW)
{
- if (::stat(source.c_str(), &fileInfo) != 0)
- throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)), L"stat", getLastError());
+ if (::stat(sourcePath.c_str(), &fileInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"stat", getLastError());
- if (::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"chown", getLastError());
+ if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
+ throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chown", getLastError());
- if (::chmod(target.c_str(), fileInfo.st_mode) != 0)
- throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"chmod", getLastError());
+ if (::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
+ throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chmod", getLastError());
}
else
{
- if (::lstat(source.c_str(), &fileInfo) != 0)
- throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)), L"lstat", getLastError());
+ if (::lstat(sourcePath.c_str(), &fileInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"lstat", getLastError());
- if (::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"lchown", getLastError());
+ if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
+ throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"lchown", getLastError());
- if (!symlinkExists(target) && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
- ::chmod(target.c_str(), fileInfo.st_mode) != 0)
- throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"chmod", getLastError());
+ if (!symlinkExists(targetPath) && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
+ ::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
+ throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chmod", getLastError());
}
#elif defined ZEN_MAC
@@ -1250,27 +1277,27 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym
if (procSl == ProcSymlink::DIRECT)
flags |= COPYFILE_NOFOLLOW;
- if (::copyfile(source.c_str(), target.c_str(), 0, flags) != 0)
- throwFileError(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtFileName(source)), L"%y", L"\n" + fmtFileName(target)), L"copyfile", getLastError());
+ if (::copyfile(sourcePath.c_str(), targetPath.c_str(), 0, flags) != 0)
+ throwFileError(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtFileName(sourcePath)), L"%y", L"\n" + fmtFileName(targetPath)), L"copyfile", getLastError());
//owner is *not* copied with ::copyfile():
struct ::stat fileInfo = {};
if (procSl == ProcSymlink::FOLLOW)
{
- if (::stat(source.c_str(), &fileInfo) != 0)
- throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)), L"stat", getLastError());
+ if (::stat(sourcePath.c_str(), &fileInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"stat", getLastError());
- if (::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"chown", getLastError());
+ if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
+ throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chown", getLastError());
}
else
{
- if (::lstat(source.c_str(), &fileInfo) != 0)
- throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)), L"lstat", getLastError());
+ if (::lstat(sourcePath.c_str(), &fileInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"lstat", getLastError());
- if (::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"lchown", getLastError());
+ if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
+ throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"lchown", getLastError());
}
#endif
}
@@ -1412,7 +1439,7 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
mode = dirInfo.st_mode; //analog to "cp" which copies "mode" (considering umask) by default
mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items
}
- //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions
+ //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
if (::mkdir(directory.c_str(), mode) != 0)
{
@@ -1501,7 +1528,7 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
//enforce copying file permissions: it's advertized on GUI...
if (copyFilePermissions)
- copyObjectPermissions(templateDir, directory, ProcSymlink::FOLLOW); //throw FileError
+ copyItemPermissions(templateDir, directory, ProcSymlink::FOLLOW); //throw FileError
guardNewDir.dismiss(); //target has been created successfully!
}
@@ -1561,23 +1588,26 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
setFileTimeRaw(targetLink, &sourceAttr.ftCreationTime, sourceAttr.ftLastWriteTime, ProcSymlink::DIRECT); //throw FileError
-#elif defined ZEN_LINUX || defined ZEN_MAC
+#elif defined ZEN_LINUX
struct ::stat sourceInfo = {};
if (::lstat(sourceLink.c_str(), &sourceInfo) != 0)
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceLink)), L"lstat", getLastError());
-#ifdef ZEN_LINUX
- setFileTime(targetLink, sourceInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError
+ setFileTime(targetLink, sourceInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError
+
#elif defined ZEN_MAC
- setFileTimeRaw(targetLink, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::DIRECT); //throw FileError
+ struct ::stat sourceInfo = {};
+ if (::lstat(sourceLink.c_str(), &sourceInfo) != 0)
+ throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceLink)), L"lstat", getLastError());
if (::copyfile(sourceLink.c_str(), targetLink.c_str(), 0, COPYFILE_XATTR | COPYFILE_NOFOLLOW) != 0)
throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)), L"copyfile", getLastError());
-#endif
+
+ setFileTimeRaw(targetLink, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::DIRECT); //throw FileError
#endif
if (copyFilePermissions)
- copyObjectPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError
+ copyItemPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError
guardNewLink.dismiss(); //target has been created successfully!
}
@@ -1903,37 +1933,6 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
DEFINE_NEW_FILE_ERROR(ErrorShouldCopyAsSparse);
-class ErrorHandling
-{
-public:
- ErrorHandling() : shouldCopyAsSparse(false) {}
-
- //call context: copyCallbackInternal()
- void reportErrorShouldCopyAsSparse() { shouldCopyAsSparse = true; }
-
- void reportUserException(const std::exception_ptr& e) { exception = e; }
-
- void reportError(const std::wstring& msg, const std::wstring& description) { errorMsg = std::make_pair(msg, description); }
-
- //call context: copyFileWindowsDefault()
- void evaluateErrors() //throw X
- {
- if (shouldCopyAsSparse)
- throw ErrorShouldCopyAsSparse(L"sparse dummy value");
-
- if (exception)
- std::rethrow_exception(exception);
-
- if (!errorMsg.first.empty())
- throw FileError(errorMsg.first, errorMsg.second);
- }
-
-private:
- bool shouldCopyAsSparse; //
- std::pair<std::wstring, std::wstring> errorMsg; //these are exclusive!
- std::exception_ptr exception;
-};
-
struct CallbackData
{
@@ -1949,10 +1948,10 @@ struct CallbackData
const Zstring& sourceFile_;
const Zstring& targetFile_;
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus_;
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus_; //optional
- ErrorHandling errorHandler;
- BY_HANDLE_FILE_INFORMATION fileInfoSrc; //modified by CopyFileEx() at beginning
+ std::exception_ptr exception; //out
+ BY_HANDLE_FILE_INFORMATION fileInfoSrc; //out: modified by CopyFileEx() at beginning
BY_HANDLE_FILE_INFORMATION fileInfoTrg; //
std::int64_t bytesReported; //used internally to calculate bytes transferred delta
@@ -1971,6 +1970,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
{
/*
this callback is invoked for block sizes managed by Windows, these may vary from e.g. 64 kB up to 1MB. It seems this depends on file size amongst others.
+ Note: for 0-sized files this callback is invoked just ONCE!
symlink handling:
if source is a symlink and COPY_FILE_COPY_SYMLINK is specified, this callback is NOT invoked!
@@ -1990,71 +1990,60 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
CallbackData& cbd = *static_cast<CallbackData*>(lpData);
- if (dwCallbackReason == CALLBACK_STREAM_SWITCH && //called up-front for every file (even if 0-sized)
- dwStreamNumber == 1) //consider ADS!
+ try
{
- //#################### return source file attributes ################################
- if (!::GetFileInformationByHandle(hSourceFile, &cbd.fileInfoSrc))
+ if (dwCallbackReason == CALLBACK_STREAM_SWITCH && //called up-front for every file (even if 0-sized)
+ dwStreamNumber == 1) //consider ADS!
{
- const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls!
- cbd.errorHandler.reportError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.sourceFile_)), formatSystemError(L"GetFileInformationByHandle", lastError));
- return PROGRESS_CANCEL;
- }
+ //#################### return source file attributes ################################
+ if (!::GetFileInformationByHandle(hSourceFile, &cbd.fileInfoSrc))
+ throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.sourceFile_)), L"GetFileInformationByHandle", ::GetLastError());
- if (!::GetFileInformationByHandle(hDestinationFile, &cbd.fileInfoTrg))
- {
- const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls!
- cbd.errorHandler.reportError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.targetFile_)), formatSystemError(L"GetFileInformationByHandle", lastError));
- return PROGRESS_CANCEL;
- }
+ if (!::GetFileInformationByHandle(hDestinationFile, &cbd.fileInfoTrg))
+ throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.targetFile_)), L"GetFileInformationByHandle", ::GetLastError());
- //#################### switch to sparse file copy if req. #######################
- if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw ()
- {
- cbd.errorHandler.reportErrorShouldCopyAsSparse(); //use a different copy routine!
- return PROGRESS_CANCEL;
- }
+ //#################### switch to sparse file copy if req. #######################
+ if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw ()
+ throw ErrorShouldCopyAsSparse(L"sparse dummy value"); //use a different copy routine!
- //#################### copy file creation time ################################
- ::SetFileTime(hDestinationFile, &cbd.fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling!
- //=> not really needed here, creation time is set anyway at the end of copyFileWindowsDefault()!
+ //#################### copy file creation time ################################
+ ::SetFileTime(hDestinationFile, &cbd.fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling!
+ //=> not really needed here, creation time is set anyway at the end of copyFileWindowsDefault()!
- //#################### copy NTFS compressed attribute #########################
- const bool sourceIsCompressed = (cbd.fileInfoSrc.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
- const bool targetIsCompressed = (cbd.fileInfoTrg.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CopyFileEx if target parent folder is compressed!
- if (sourceIsCompressed && !targetIsCompressed)
- {
- USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
- DWORD bytesReturned = 0;
- if (!::DeviceIoControl(hDestinationFile, //_In_ HANDLE hDevice,
- FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode,
- &cmpState, //_In_opt_ LPVOID lpInBuffer,
- sizeof(cmpState), //_In_ DWORD nInBufferSize,
- nullptr, //_Out_opt_ LPVOID lpOutBuffer,
- 0, //_In_ DWORD nOutBufferSize,
- &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned
- nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped
- {} //may legitimately fail with ERROR_INVALID_FUNCTION if
-
- // - if target folder is encrypted
- // - target volume does not support compressed attribute
- //#############################################################################
+ //#################### copy NTFS compressed attribute #########################
+ const bool sourceIsCompressed = (cbd.fileInfoSrc.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
+ const bool targetIsCompressed = (cbd.fileInfoTrg.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CopyFileEx if target parent folder is compressed!
+ if (sourceIsCompressed && !targetIsCompressed)
+ {
+ USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
+ DWORD bytesReturned = 0;
+ if (!::DeviceIoControl(hDestinationFile, //_In_ HANDLE hDevice,
+ FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode,
+ &cmpState, //_In_opt_ LPVOID lpInBuffer,
+ sizeof(cmpState), //_In_ DWORD nInBufferSize,
+ nullptr, //_Out_opt_ LPVOID lpOutBuffer,
+ 0, //_In_ DWORD nOutBufferSize,
+ &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned
+ nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped
+ {} //may legitimately fail with ERROR_INVALID_FUNCTION if
+
+ // - if target folder is encrypted
+ // - target volume does not support compressed attribute
+ //#############################################################################
+ }
}
- }
- //called after copy operation is finished - note: for 0-sized files this callback is invoked just ONCE!
- //if (totalFileSize.QuadPart == totalBytesTransferred.QuadPart && dwStreamNumber == 1) {}
- if (cbd.onUpdateCopyStatus_ && totalBytesTransferred.QuadPart >= 0) //should always be true, but let's still check
- try
+ if (cbd.onUpdateCopyStatus_ && totalBytesTransferred.QuadPart >= 0) //should always be true, but let's still check
{
cbd.onUpdateCopyStatus_(totalBytesTransferred.QuadPart - cbd.bytesReported); //throw X!
cbd.bytesReported = totalBytesTransferred.QuadPart;
}
- catch (...)
- {
- cbd.errorHandler.reportUserException(std::current_exception());
- return PROGRESS_CANCEL;
- }
+ }
+ catch (...)
+ {
+ cbd.exception = std::current_exception();
+ return PROGRESS_CANCEL;
+ }
return PROGRESS_CONTINUE;
}
@@ -2083,10 +2072,11 @@ void copyFileWindowsDefault(const Zstring& sourceFile,
copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; //allow copying from encrypted to non-encrypted location
//if (vistaOrLater()) //see http://blogs.technet.com/b/askperf/archive/2007/05/08/slow-large-file-copy-issues.aspx
- // copyFlags |= COPY_FILE_NO_BUFFERING; //no perf difference at worst, huge improvement for large files (20% in test NTFS -> NTFS)
- //It's a shame this flag causes file corruption! https://sourceforge.net/projects/freefilesync/forums/forum/847542/topic/5177950
- //documentation on CopyFile2() even states: "It is not recommended to pause copies that are using this flag." How dangerous is this thing, why offer it at all???
- //perf advantage: ~15% faster
+ // copyFlags |= COPY_FILE_NO_BUFFERING; //no perf difference at worst, improvement for large files (20% in test NTFS -> NTFS)
+ // - this flag may cause file corruption! https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/65f48357/
+ // - documentation on CopyFile2() even states: "It is not recommended to pause copies that are using this flag."
+ //=> it's not worth it! instead of skipping buffering at kernel-level (=> also NO prefetching!!!), skip it at user-level: memory mapped files!
+ // however, perf-measurements for memory mapped files show: it's also not worth it!
CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile);
@@ -2096,8 +2086,9 @@ void copyFileWindowsDefault(const Zstring& sourceFile,
&cbd, //__in_opt LPVOID lpData,
nullptr, //__in_opt LPBOOL pbCancel,
copyFlags) != FALSE; //__in DWORD dwCopyFlags
+ if (cbd.exception)
+ std::rethrow_exception(cbd.exception); //throw ?, process errors in callback first!
- cbd.errorHandler.evaluateErrors(); //throw ?, process errors in callback first!
if (!success)
{
const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls!
@@ -2204,26 +2195,40 @@ void copyFileOsSpecific(const Zstring& sourceFile,
}
-#elif defined ZEN_LINUX
+#elif defined ZEN_LINUX || defined ZEN_MAC
void copyFileOsSpecific(const Zstring& sourceFile,
const Zstring& targetFile,
const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting
{
- FileInputUnbuffered fileIn(sourceFile); //throw FileError
+ FileInput fileIn(sourceFile); //throw FileError
struct ::stat sourceInfo = {};
- if (::fstat(fileIn.getDescriptor(), &sourceInfo) != 0)
+ if (::fstat(fileIn.getHandle(), &sourceInfo) != 0)
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError());
- zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: place guard before lifetime of FileOutput
- try
+ const int fdTarget = ::open(targetFile.c_str(), O_WRONLY | O_CREAT | O_EXCL,
+ sourceInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); //analog to "cp" which copies "mode" (considering umask) by default
+ //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
+ if (fdTarget == -1)
+ {
+ const int ec = errno; //copy before making other system calls!
+ const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile));
+ const std::wstring errorDescr = formatSystemError(L"open", ec);
+
+ if (ec == EEXIST)
+ throw ErrorTargetExisting(errorMsg, errorDescr);
+
+ throw FileError(errorMsg, errorDescr);
+ }
+
+ zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} });
+ //transactional behavior: place guard after ::open() and before lifetime of FileOutput:
+ //=> don't delete file that existed previously!!!
{
- FileOutputUnbuffered fileOut(targetFile, //throw FileError, ErrorTargetExisting
- sourceInfo.st_mode); //analog to "cp" which copies "mode" (considering umask) by default
- //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions
+ FileOutput fileOut(fdTarget, targetFile); //pass ownership
- std::vector<char> buffer(128 * 1024); //see comment in FileInputUnbuffered::read
+ std::vector<char> buffer(128 * 1024);
do
{
const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError
@@ -2235,186 +2240,58 @@ void copyFileOsSpecific(const Zstring& sourceFile,
}
while (!fileIn.eof());
- //adapt target file modification time:
- {
- //read and return file statistics
- struct ::stat targetInfo = {};
- if (::fstat(fileOut.getDescriptor(), &targetInfo) != 0)
- throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError());
-
- if (newAttrib)
- {
- newAttrib->fileSize = sourceInfo.st_size;
- newAttrib->modificationTime = sourceInfo.st_mtime;
- newAttrib->sourceFileId = extractFileId(sourceInfo);
- newAttrib->targetFileId = extractFileId(targetInfo);
- }
- }
- }
- catch (const ErrorTargetExisting&)
- {
- guardTarget.dismiss(); //don't delete file that existed previously!
- throw;
- }
-
- //we cannot set the target file times while the file descriptor is still open after a write operation:
- //this triggers bugs on samba shares where the modification time is set to current time instead.
- //http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
- //http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
- //on the other hand we thereby have to reopen https://sourceforge.net/p/freefilesync/bugs/230/
- setFileTime(targetFile, sourceInfo.st_mtime, ProcSymlink::FOLLOW); //throw FileError
-
- guardTarget.dismiss(); //target has been created successfully!
-}
-
-
-#elif defined ZEN_MAC
-struct CallbackData
-{
- CallbackData(const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- const Zstring& sourceFile,
- const Zstring& targetFile) :
- onUpdateCopyStatus_(onUpdateCopyStatus),
- sourceFile_(sourceFile),
- targetFile_(targetFile),
- bytesReported() {}
-
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus_; //in
- const Zstring& sourceFile_;
- const Zstring& targetFile_;
-
- std::pair<std::wstring, std::wstring> errorMsg; //out; these are exclusive!
- std::exception_ptr exception; //
-
- std::int64_t bytesReported; //private to callback
-};
-
-
-int copyFileCallback(int what, int stage, copyfile_state_t state, const char* src, const char* dst, void* ctx)
-{
- CallbackData& cbd = *static_cast<CallbackData*>(ctx);
-
- off_t bytesCopied = 0;
- if (::copyfile_state_get(state, COPYFILE_STATE_COPIED, &bytesCopied) != 0)
- {
- cbd.errorMsg = std::make_pair(replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(cbd.sourceFile_)), L"%y", L"\n" + fmtFileName(cbd.targetFile_)),
- formatSystemError(L"copyfile_state_get, COPYFILE_STATE_COPIED", getLastError()));
- return COPYFILE_QUIT;
- }
-
- if (cbd.onUpdateCopyStatus_)
- try
- {
- cbd.onUpdateCopyStatus_(bytesCopied - cbd.bytesReported); //throw X!
- cbd.bytesReported = bytesCopied;
- }
- catch (...)
- {
- cbd.exception = std::current_exception();
- return COPYFILE_QUIT;
- }
- return COPYFILE_CONTINUE;
-}
-
-
-void copyFileOsSpecific(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting
-{
- struct ::stat sourceInfo = {};
-
- //http://blog.plasticsfuture.org/2006/03/05/the-state-of-backup-and-cloning-tools-under-mac-os-x/
- {
- auto getCopyErrorMessage = [&] { return replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); };
-
- copyfile_state_t copyState = ::copyfile_state_alloc();
- ZEN_ON_SCOPE_EXIT(::copyfile_state_free(copyState));
-
- CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile);
-
- if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CTX, &cbd) != 0)
- throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CTX", getLastError());
-
- if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CB, reinterpret_cast<const void*>(&copyFileCallback)) != 0)
- throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CB", getLastError());
-
- zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: docs seem to indicate that copyfile does not clean up
-
- //http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html
- if (::copyfile(sourceFile.c_str(), targetFile.c_str(),
- copyState,
- COPYFILE_XATTR | COPYFILE_DATA | COPYFILE_EXCL) != 0)
- //- even though we don't use COPYFILE_STAT, "mode" (considering umask) is still copied! => harmonized with Linux file copy!
- //- COPYFILE_STAT does not copy file creation time
- {
- //evaluate first! errno is not set for COPYFILE_QUIT!
- if (cbd.exception)
- std::rethrow_exception(cbd.exception);
-
- if (!cbd.errorMsg.first.empty())
- throw FileError(cbd.errorMsg.first, cbd.errorMsg.second);
-
- const int lastError = errno;
- std::wstring errorDescr = formatSystemError(L"copyfile", lastError);
-
- if (lastError == EEXIST)
- {
- guardTarget.dismiss(); //don't delete file that existed previously!
- throw ErrorTargetExisting(getCopyErrorMessage(), errorDescr);
- }
-
- throw FileError(getCopyErrorMessage(), errorDescr);
- }
-
- int fdSource = 0;
- if (::copyfile_state_get(copyState, COPYFILE_STATE_SRC_FD, &fdSource) != 0)
- throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_SRC_FD", getLastError());
-
- int fdTarget = 0;
- if (::copyfile_state_get(copyState, COPYFILE_STATE_DST_FD, &fdTarget) != 0)
- throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_DST_FD", getLastError());
-
- if (::fstat(fdSource, &sourceInfo) != 0)
- throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError());
-
struct ::stat targetInfo = {};
- if (::fstat(fdTarget, &targetInfo) != 0)
+ if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError());
- if (newAttrib)
+ if (newAttrib) //return file statistics
{
newAttrib->fileSize = sourceInfo.st_size;
- newAttrib->modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable as setFileTimeRaw() for consistency
- //newAttrib->modificationTime = sourceInfo.st_mtime; //
+#ifdef ZEN_MAC
+ newAttrib->modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setFileTimeRaw() for consistency
+#else
+ newAttrib->modificationTime = sourceInfo.st_mtime;
+#endif
newAttrib->sourceFileId = extractFileId(sourceInfo);
newAttrib->targetFileId = extractFileId(targetInfo);
}
- guardTarget.dismiss();
- } //make sure target file handle is closed before setting modification time!
-
+#ifdef ZEN_MAC
+ //using ::copyfile with COPYFILE_DATA seems to trigger bugs unlike our stream-based copying!
+ //=> use ::copyfile for extended attributes only: https://sourceforge.net/p/freefilesync/discussion/help/thread/91384c8a/
+ //http://blog.plasticsfuture.org/2006/03/05/the-state-of-backup-and-cloning-tools-under-mac-os-x/
+ //docs: http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html
+ //source: http://www.opensource.apple.com/source/copyfile/copyfile-103.92.1/copyfile.c
+ if (::fcopyfile(fileIn.getHandle(), fileOut.getHandle(), 0, COPYFILE_XATTR) != 0)
+ throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)), L"copyfile", getLastError());
+#endif
+ } //close output file handle before setting file time
- zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} });
- //same issue like on Linux: we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
+ //we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
//this triggers bugs on samba shares where the modification time is set to current time instead.
- //https://sourceforge.net/p/freefilesync/discussion/help/thread/881357c0/
- //http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
+ //Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
+ // http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
+ //OS X: https://sourceforge.net/p/freefilesync/discussion/help/thread/881357c0/
+#ifdef ZEN_MAC
setFileTimeRaw(targetFile, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::FOLLOW); //throw FileError
- //sourceInfo.st_birthtime; -> only seconds-preicions
- //sourceInfo.st_mtime; ->
- guardTarget.dismiss();
+ //sourceInfo.st_birthtime; -> only seconds-precision
+ //sourceInfo.st_mtime; ->
+#else
+ setFileTime(targetFile, sourceInfo.st_mtime, ProcSymlink::FOLLOW); //throw FileError
+#endif
+
+ guardTarget.dismiss(); //target has been created successfully!
}
#endif
/*
- ------------------
- |File Copy Layers|
- ------------------
+ ------------------
+ |File Copy Layers|
+ ------------------
copyFile (setup transactional behavior)
- |
+ |
copyFileWithPermissions
- |
+ |
copyFileOsSpecific (solve 8.3 issue)
|
copyFileWindowsSelectRoutine
@@ -2436,7 +2313,7 @@ void copyFileWithPermissions(const Zstring& sourceFile,
//at this point we know we created a new file, so it's fine to delete it for cleanup!
zen::ScopeGuard guardTargetFile = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {}});
- copyObjectPermissions(sourceFile, targetFile, ProcSymlink::FOLLOW); //throw FileError
+ copyItemPermissions(sourceFile, targetFile, ProcSymlink::FOLLOW); //throw FileError
guardTargetFile.dismiss(); //target has been created successfully!
}
bgstack15