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.cpp641
1 files changed, 328 insertions, 313 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index 009d60b2..ea13d381 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -12,7 +12,7 @@
#include "scope_guard.h"
#include "symlink_target.h"
#include "file_id_def.h"
-#include "serialize.h"
+#include "file_io.h"
#ifdef ZEN_WIN
#include <Aclapi.h>
@@ -232,7 +232,7 @@ std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, retu
nullptr, //__out_opt PULARGE_INTEGER lpTotalNumberOfBytes,
nullptr)) //__out_opt PULARGE_INTEGER lpTotalNumberOfFreeBytes
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), L"GetDiskFreeSpaceEx");
- //succeeds even if path is not yet existing!
+ //succeeds even if path is not yet existing!
//return 0 if info is not available: "The GetDiskFreeSpaceEx function returns zero for lpFreeBytesAvailable for all CD requests"
return get64BitUInt(bytesFree.LowPart, bytesFree.HighPart);
@@ -613,80 +613,191 @@ namespace
{
#ifdef ZEN_WIN
-void setFileTimeRaw(const Zstring& filePath,
- const FILETIME* creationTime, //optional
- const FILETIME& lastWriteTime,
- ProcSymlink procSl) //throw FileError
+void setFileTimeByHandle(HANDLE hFile, //throw FileError
+ const FILETIME* creationTime, //optional
+ const FILETIME& lastWriteTime,
+ const Zstring& filePath) //for error message only
{
+ if (!::SetFileTime(hFile, //__in HANDLE hFile,
+ creationTime, //__in_opt const FILETIME *lpCreationTime,
+ nullptr, //__in_opt const FILETIME *lpLastAccessTime,
+ &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime
{
- //extra scope for debug check below
-
- //privilege SE_BACKUP_NAME doesn't seem to be required here for symbolic links
- //note: setting privileges requires admin rights!
+ DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
- //opening newly created target file may fail due to some AV-software scanning it: no error, we will wait!
- //http://support.microsoft.com/?scid=kb%3Ben-us%3B316609&x=17&y=20
- //-> enable as soon it turns out it is required!
+ auto toLargeInteger = [](const FILETIME& ft) -> LARGE_INTEGER
+ {
+ LARGE_INTEGER tmp = {};
+ tmp.LowPart = ft.dwLowDateTime;
+ tmp.HighPart = ft.dwHighDateTime;
+ return tmp;
+ };
- /*const int retryInterval = 50;
- const int maxRetries = 2000 / retryInterval;
- for (int i = 0; i < maxRetries; ++i)
+#ifdef ZEN_WIN_VISTA_AND_LATER
+ //function may fail if file is read-only: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
+ if (ec == ERROR_ACCESS_DENIED)
{
- */
+ auto setFileInfo = [&](FILE_BASIC_INFO basicInfo) //throw FileError; no const& since SetFileInformationByHandle() requires non-const parameter!
+ {
+ if (!::SetFileInformationByHandle(hFile, //__in HANDLE hFile,
+ FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
+ &basicInfo, //__in LPVOID lpFileInformation,
+ sizeof(basicInfo))) //__in DWORD dwBufferSize
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(filePath)), L"SetFileInformationByHandle");
+ };
+ //---------------------------------------------------------------------------
+
+ BY_HANDLE_FILE_INFORMATION fileInfo = {};
+ if (::GetFileInformationByHandle(hFile, &fileInfo))
+ if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+ {
+ FILE_BASIC_INFO basicInfo = {}; //undocumented: file times of "0" stand for "don't change"
+ basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; //[!] the bug in the ticket above requires we set this together with file times!!!
+ basicInfo.LastWriteTime = toLargeInteger(lastWriteTime); //
+ if (creationTime)
+ basicInfo.CreationTime = toLargeInteger(*creationTime);
- /*
- if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION)
- ::Sleep(retryInterval); //wait then retry
- else //success or unknown failure
- break;
+ //set file time + attributes
+ setFileInfo(basicInfo); //throw FileError
+
+ try //... to restore original file attributes
+ {
+ FILE_BASIC_INFO basicInfo2 = {};
+ basicInfo2.FileAttributes = fileInfo.dwFileAttributes;
+ setFileInfo(basicInfo2); //throw FileError
+ }
+ catch (FileError&) {}
+
+ ec = ERROR_SUCCESS;
+ }
}
- */
- //temporarily reset read-only flag if required
- DWORD attribs = INVALID_FILE_ATTRIBUTES;
- ZEN_ON_SCOPE_EXIT(
- if (attribs != INVALID_FILE_ATTRIBUTES)
- ::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), attribs);
- );
-
- auto removeReadonly = [&]() -> bool //throw FileError; may need to remove the readonly-attribute (e.g. on FAT usb drives)
+#endif
+
+ std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath));
+
+ //add more meaningful message: FAT accepts only a subset of the NTFS date range
+ if (ec == ERROR_INVALID_PARAMETER && isFatDrive(filePath))
{
- if (attribs == INVALID_FILE_ATTRIBUTES)
+ //let's not fail due to an invalid creation time on FAT: http://www.freefilesync.org/forum/viewtopic.php?t=2278
+ if (creationTime) //retry (single-level recursion at most!)
+ return setFileTimeByHandle(hFile, nullptr, lastWriteTime, filePath); //throw FileError
+
+ //if the ERROR_INVALID_PARAMETER is due to an invalid date, enhance message:
+ const LARGE_INTEGER writeTimeInt = toLargeInteger(lastWriteTime);
+ if (writeTimeInt.QuadPart < 0x01a8e79fe1d58000 || //1980-01-01 https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs#Year_2100
+ writeTimeInt.QuadPart >= 0x022f716377640000) //2100-01-01
{
- const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str());
- if (tmpAttr == INVALID_FILE_ATTRIBUTES)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileAttributes");
-
- if (tmpAttr & FILE_ATTRIBUTE_READONLY)
+ errorMsg += L"\nA FAT volume can only store dates from 1980 to 2099:\n" "\twrite time (UTC):";
+ SYSTEMTIME st = {};
+ if (::FileTimeToSystemTime(&lastWriteTime, //__in const FILETIME *lpFileTime,
+ &st)) //__out LPSYSTEMTIME lpSystemTime
{
- if (!::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), FILE_ATTRIBUTE_NORMAL))
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(filePath)), L"SetFileAttributes");
+ //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!!
+ int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
+ if (bufferSize > 0)
+ {
+ std::vector<wchar_t> buffer(bufferSize);
+ if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale,
+ 0, //_In_ DWORD dwFlags,
+ &st, //_In_opt_ const SYSTEMTIME *lpDate,
+ nullptr, //_In_opt_ LPCTSTR lpFormat,
+ &buffer[0], //_Out_opt_ LPTSTR lpDateStr,
+ bufferSize) > 0) //_In_ int cchDate
+ errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
+ }
- attribs = tmpAttr; //reapplied on scope exit
- return true;
+ bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
+ if (bufferSize > 0)
+ {
+ std::vector<wchar_t> buffer(bufferSize);
+ if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0)
+ errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
+ }
}
+ errorMsg += L" (" + numberTo<std::wstring>(writeTimeInt.QuadPart) + L")"; //just in case the above date formatting fails
}
- return false;
- };
+ }
+
+ if (ec != ERROR_SUCCESS)
+ throw FileError(errorMsg, formatSystemError(L"SetFileTime", ec));
+ }
+}
+
+
+void setWriteTimeNative(const Zstring& filePath,
+ const FILETIME& lastWriteTime,
+ const FILETIME* creationTime, //optional
+ ProcSymlink procSl) //throw FileError
+{
+ //privilege SE_BACKUP_NAME doesn't seem to be required here for symbolic links
+ //note: setting privileges requires admin rights!
+
+ //opening newly created target file may fail due to some AV-software scanning it: no error, we will wait!
+ //http://support.microsoft.com/?scid=kb%3Ben-us%3B316609&x=17&y=20
+ //-> enable as soon it turns out it is required!
+
+ /*const int retryInterval = 50;
+ const int maxRetries = 2000 / retryInterval;
+ for (int i = 0; i < maxRetries; ++i)
+ {
+ */
- auto openFile = [&](bool conservativeApproach)
+ /*
+ if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION)
+ ::Sleep(retryInterval); //wait then retry
+ else //success or unknown failure
+ break;
+ }
+ */
+ //temporarily reset read-only flag if required
+ DWORD attribs = INVALID_FILE_ATTRIBUTES;
+ ZEN_ON_SCOPE_EXIT(
+ if (attribs != INVALID_FILE_ATTRIBUTES)
+ ::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), attribs);
+ );
+
+ auto removeReadonly = [&]() -> bool //throw FileError; may need to remove the readonly-attribute (e.g. on FAT usb drives)
+ {
+ if (attribs == INVALID_FILE_ATTRIBUTES)
{
- return ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName,
- (conservativeApproach ?
- //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES, even worse, they may fail silently!
- //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430
- //Citrix shares seem to have this issue, too, but at least fail with "access denied" => try generic access first:
- GENERIC_READ | GENERIC_WRITE :
- //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even *after* read-only was removed directly before the call!
- //http://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
- //since former gives an error notification we may very well try FILE_WRITE_ATTRIBUTES second.
- FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES), //_In_ DWORD dwDesiredAccess,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
- nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
- (procSl == ProcSymlink::DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0) |
- FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes,
- nullptr); //_In_opt_ HANDLE hTemplateFile
- };
+ const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str());
+ if (tmpAttr == INVALID_FILE_ATTRIBUTES)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileAttributes");
+
+ if (tmpAttr & FILE_ATTRIBUTE_READONLY)
+ {
+ if (!::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), FILE_ATTRIBUTE_NORMAL))
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(filePath)), L"SetFileAttributes");
+
+ attribs = tmpAttr; //reapplied on scope exit
+ return true;
+ }
+ }
+ return false;
+ };
+
+ auto openFile = [&](bool conservativeApproach)
+ {
+ return ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName,
+ (conservativeApproach ?
+ //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES, even worse, they may fail silently!
+ //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430
+ //Citrix shares seem to have this issue, too, but at least fail with "access denied" => try generic access first:
+ GENERIC_READ | GENERIC_WRITE :
+ //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even *after* read-only was removed directly before the call!
+ //http://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
+ //since former gives an error notification we may very well try FILE_WRITE_ATTRIBUTES second.
+ FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES), //_In_ DWORD dwDesiredAccess,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
+ nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ OPEN_EXISTING, //_In_ DWORD dwCreationDisposition,
+ (procSl == ProcSymlink::DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0) |
+ FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes,
+ nullptr); //_In_opt_ HANDLE hTemplateFile
+ };
+
+ {
+ //extra scope for debug check below
HANDLE hFile = INVALID_HANDLE_VALUE;
for (int i = 0; i < 2; ++i) //we will get this handle, no matter what! :)
@@ -716,118 +827,7 @@ void setFileTimeRaw(const Zstring& filePath,
}
ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
-
- if (!::SetFileTime(hFile, //__in HANDLE hFile,
- creationTime, //__in_opt const FILETIME *lpCreationTime,
- nullptr, //__in_opt const FILETIME *lpLastAccessTime,
- &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime
- {
- DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
-
- //function may fail if file is read-only: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
- if (ec == ERROR_ACCESS_DENIED)
- {
- //dynamically load windows API function: available with Windows Vista and later
- typedef BOOL (WINAPI* SetFileInformationByHandleFunc)(HANDLE hFile, FILE_INFO_BY_HANDLE_CLASS FileInformationClass, LPVOID lpFileInformation, DWORD dwBufferSize);
- const SysDllFun<SetFileInformationByHandleFunc> setFileInformationByHandle(L"kernel32.dll", "SetFileInformationByHandle");
-
- if (setFileInformationByHandle) //if not: let the original error propagate!
- {
- auto setFileInfo = [&](FILE_BASIC_INFO basicInfo) //throw FileError; no const& since SetFileInformationByHandle() requires non-const parameter!
- {
- if (!setFileInformationByHandle(hFile, //__in HANDLE hFile,
- FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
- &basicInfo, //__in LPVOID lpFileInformation,
- sizeof(basicInfo))) //__in DWORD dwBufferSize
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(filePath)), L"SetFileInformationByHandle");
- };
-
- auto toLargeInteger = [](const FILETIME& ft) -> LARGE_INTEGER
- {
- LARGE_INTEGER tmp = {};
- tmp.LowPart = ft.dwLowDateTime;
- tmp.HighPart = ft.dwHighDateTime;
- return tmp;
- };
- //---------------------------------------------------------------------------
-
- BY_HANDLE_FILE_INFORMATION fileInfo = {};
- if (::GetFileInformationByHandle(hFile, &fileInfo))
- if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
- {
- FILE_BASIC_INFO basicInfo = {}; //undocumented: file times of "0" stand for "don't change"
- basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; //[!] the bug in the ticket above requires we set this together with file times!!!
- basicInfo.LastWriteTime = toLargeInteger(lastWriteTime); //
- if (creationTime)
- basicInfo.CreationTime = toLargeInteger(*creationTime);
-
- //set file time + attributes
- setFileInfo(basicInfo); //throw FileError
-
- try //... to restore original file attributes
- {
- FILE_BASIC_INFO basicInfo2 = {};
- basicInfo2.FileAttributes = fileInfo.dwFileAttributes;
- setFileInfo(basicInfo2); //throw FileError
- }
- catch (FileError&) {}
-
- ec = ERROR_SUCCESS;
- }
- }
- }
-
- std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath));
-
- //add more meaningful message: FAT accepts only a subset of the NTFS date range
- if (ec == ERROR_INVALID_PARAMETER &&
- isFatDrive(filePath))
- {
- //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!!
- auto fmtDate = [](const FILETIME& ft)
- {
- SYSTEMTIME st = {};
- if (!::FileTimeToSystemTime(&ft, //__in const FILETIME *lpFileTime,
- &st)) //__out LPSYSTEMTIME lpSystemTime
- return std::wstring();
-
- std::wstring dateTime;
- {
- const int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
- if (bufferSize > 0)
- {
- std::vector<wchar_t> buffer(bufferSize);
- if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale,
- 0, //_In_ DWORD dwFlags,
- &st, //_In_opt_ const SYSTEMTIME *lpDate,
- nullptr, //_In_opt_ LPCTSTR lpFormat,
- &buffer[0], //_Out_opt_ LPTSTR lpDateStr,
- bufferSize) > 0) //_In_ int cchDate
- dateTime = &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
- }
- }
-
- const int bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
- if (bufferSize > 0)
- {
- std::vector<wchar_t> buffer(bufferSize);
- if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0)
- {
- dateTime += L" ";
- dateTime += &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
- }
- }
- return dateTime;
- };
-
- errorMsg += std::wstring(L"\nA FAT volume can only store dates between 1980 and 2107:\n") +
- L"\twrite (UTC): \t" + fmtDate(lastWriteTime) +
- (creationTime ? L"\n\tcreate (UTC): \t" + fmtDate(*creationTime) : L"");
- }
-
- if (ec != ERROR_SUCCESS)
- throw FileError(errorMsg, formatSystemError(L"SetFileTime", ec));
- }
+ setFileTimeByHandle(hFile, creationTime, lastWriteTime, filePath); //throw FileError
}
#ifndef NDEBUG //verify written data
FILETIME creationTimeDbg = {};
@@ -856,9 +856,26 @@ void setFileTimeRaw(const Zstring& filePath,
#elif defined ZEN_LINUX
-DEFINE_NEW_FILE_ERROR(ErrorLinuxFallbackToUtimes);
+void setWriteTimeFallback(const Zstring& filePath, std::int64_t modTime, ProcSymlink procSl) //throw FileError
+{
+ struct ::timeval writeTime[2] = {};
+ writeTime[0].tv_sec = ::time(nullptr); //access time (seconds)
+ writeTime[1].tv_sec = modTime; //modification time (seconds)
+
+ if (procSl == ProcSymlink::FOLLOW)
+ {
+ if (::utimes(filePath.c_str(), writeTime) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"utimes");
+ }
+ else
+ {
+ if (::lutimes(filePath.c_str(), writeTime) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"lutimes");
+ }
+}
-void setFileTimeRaw(const Zstring& filePath, const struct ::timespec& modTime, ProcSymlink procSl) //throw FileError, ErrorLinuxFallbackToUtimes
+
+void setWriteTimeNative(const Zstring& filePath, std::int64_t modTime, ProcSymlink procSl) //throw FileError
{
/*
[2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit???
@@ -867,15 +884,15 @@ void setFileTimeRaw(const Zstring& filePath, const struct ::timespec& modTime, P
[2015-03-09]
- cannot reproduce issues with NTFS and utimensat() on Ubuntu
- utimensat() is supposed to obsolete utime/utimes and is also used by "cp" and "touch"
- - solves utimes() EINVAL bug for certain CIFS/NTFS drives: https://sourceforge.net/p/freefilesync/discussion/help/thread/1ace042d/
+ - solves utimes() EINVAL bug for certain CIFS/NTFS drives: http://www.freefilesync.org/forum/viewtopic.php?t=387
=> don't use utimensat() directly, but open file descriptor manually, else EINVAL, again!
=> let's give utimensat another chance:
*/
struct ::timespec newTimes[2] = {};
newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs!!
- //https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/218564cf/
- newTimes[1] = modTime; //modification time
+ //http://www.freefilesync.org/forum/viewtopic.php?t=1701
+ newTimes[1].tv_sec = modTime; //modification time
//=> using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"!
if (procSl == ProcSymlink::FOLLOW)
@@ -884,7 +901,7 @@ void setFileTimeRaw(const Zstring& filePath, const struct ::timespec& modTime, P
if (fdFile == -1)
{
if (errno == EACCES) //bullshit, access denied even with 0777 permissions! => utimes should work!
- throw ErrorLinuxFallbackToUtimes(L"");
+ return setWriteTimeFallback(filePath, modTime, procSl); //throw FileError
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"open");
}
@@ -910,10 +927,10 @@ struct AttrBufFileTimes
} __attribute__((aligned(4), packed));
-void setFileTimeRaw(const Zstring& filePath,
- const struct ::timespec* createTime, //optional
- const struct ::timespec& writeTime,
- ProcSymlink procSl) //throw FileError
+void setWriteTimeNative(const Zstring& filePath,
+ const struct ::timespec& writeTime,
+ const struct ::timespec* createTime, //optional
+ ProcSymlink procSl) //throw FileError
{
//OS X: utime() is obsoleted by utimes()! utimensat() not yet implemented
//use ::setattrlist() instead of ::utimes() => 1. set file creation times 2. nanosecond precision
@@ -923,19 +940,19 @@ void setFileTimeRaw(const Zstring& filePath,
attribs.bitmapcount = ATTR_BIT_MAP_COUNT;
attribs.commonattr = (createTime ? ATTR_CMN_CRTIME : 0) | ATTR_CMN_MODTIME;
- AttrBufFileTimes newTimes;
+ AttrBufFileTimes attrBuf;
if (createTime)
{
- newTimes.createTime.tv_sec = createTime->tv_sec;
- newTimes.createTime.tv_nsec = createTime->tv_nsec;
+ attrBuf.createTime.tv_sec = createTime->tv_sec;
+ attrBuf.createTime.tv_nsec = createTime->tv_nsec;
}
- newTimes.writeTime.tv_sec = writeTime.tv_sec;
- newTimes.writeTime.tv_nsec = writeTime.tv_nsec;
+ attrBuf.writeTime.tv_sec = writeTime.tv_sec;
+ attrBuf.writeTime.tv_nsec = writeTime.tv_nsec;
const int rv = ::setattrlist(filePath.c_str(), //const char* path,
&attribs, //struct ::attrlist* attrList,
- createTime ? &newTimes.createTime : &newTimes.writeTime, //void* attrBuf,
- (createTime ? sizeof(newTimes.createTime) : 0) + sizeof(newTimes.writeTime), //size_t attrBufSize,
+ createTime ? &attrBuf.createTime : &attrBuf.writeTime, //void* attrBuf,
+ (createTime ? sizeof(attrBuf.createTime) : 0) + sizeof(attrBuf.writeTime), //size_t attrBufSize,
procSl == ProcSymlink::DIRECT ? FSOPT_NOFOLLOW : 0); //unsigned long options
if (rv != 0)
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"setattrlist");
@@ -952,22 +969,52 @@ void getFileTimeRaw(int fd, //throw FileError
attribs.bitmapcount = ATTR_BIT_MAP_COUNT;
attribs.commonattr = ATTR_CMN_CRTIME | ATTR_CMN_MODTIME;
- AttrBufFileTimes fileTimes;
+ AttrBufFileTimes attrBuf;
- const int rv = ::fgetattrlist(fd, //int fd,
- &attribs, //struct ::attrlist* attrList,
- &fileTimes, //void* attrBuf,
- sizeof(fileTimes), //size_t attrBufSize,
- 0); //unsigned long options
+ const int rv = ::fgetattrlist(fd, //int fd,
+ &attribs, //struct ::attrlist* attrList,
+ &attrBuf, //void* attrBuf,
+ sizeof(attrBuf), //size_t attrBufSize,
+ 0); //unsigned long options
if (rv != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"getattrlist");
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"fgetattrlist");
- createTime.tv_sec = fileTimes.createTime.tv_sec;
- createTime.tv_nsec = fileTimes.createTime.tv_nsec;
- writeTime.tv_sec = fileTimes.writeTime.tv_sec;
- writeTime.tv_nsec = fileTimes.writeTime.tv_nsec;
+ createTime.tv_sec = attrBuf.createTime.tv_sec;
+ createTime.tv_nsec = attrBuf.createTime.tv_nsec;
+ writeTime.tv_sec = attrBuf.writeTime.tv_sec;
+ writeTime.tv_nsec = attrBuf.writeTime.tv_nsec;
}
*/
+
+//PERF: ~10µs per call for "int fd" variant (irrespective if file is local or on mounted USB; test case: 3000 files)
+bool hasNativeSupportForExtendedAtrributes(const Zstring& filePath, //throw FileError
+ int fd = -1) //speed up!?
+{
+ struct ::statfs volInfo = {};
+ if ((fd != -1 ? ::fstatfs(fd, &volInfo) : ::statfs(filePath.c_str(), &volInfo)) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), fd != -1 ? L"fstatfs" : L"statfs");
+
+ struct ::attrlist attribs = {};
+ attribs.bitmapcount = ATTR_BIT_MAP_COUNT;
+ attribs.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES;
+
+ struct
+ {
+ std::uint32_t length = 0;
+ vol_capabilities_attr_t volCaps{};
+ } __attribute__((aligned(4), packed)) attrBuf;
+
+ const int rv = ::getattrlist(volInfo.f_mntonname, //const char* path,
+ &attribs, //struct ::attrlist* attrList,
+ &attrBuf, //void* attrBuf,
+ sizeof(attrBuf), //size_t attrBufSize,
+ 0); //unsigned long options
+ if (rv != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(volInfo.f_mntonname)), L"getattrlist");
+
+ return (attrBuf.volCaps.valid [VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) != 0 &&
+ (attrBuf.volCaps.capabilities[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) != 0;
+}
#endif
}
@@ -975,37 +1022,15 @@ void getFileTimeRaw(int fd, //throw FileError
void zen::setFileTime(const Zstring& filePath, std::int64_t modTime, ProcSymlink procSl) //throw FileError
{
#ifdef ZEN_WIN
- setFileTimeRaw(filePath, nullptr, timetToFileTime(modTime), procSl); //throw FileError
+ setWriteTimeNative(filePath, timetToFileTime(modTime), nullptr, procSl); //throw FileError
#elif defined ZEN_LINUX
- try
- {
- struct ::timespec writeTime = {};
- writeTime.tv_sec = modTime;
- setFileTimeRaw(filePath, writeTime, procSl); //throw FileError, ErrorLinuxFallbackToUtimes
- }
- catch (ErrorLinuxFallbackToUtimes&)
- {
- struct ::timeval writeTime[2] = {};
- writeTime[0].tv_sec = ::time(nullptr); //access time (seconds)
- writeTime[1].tv_sec = modTime; //modification time (seconds)
-
- if (procSl == ProcSymlink::FOLLOW)
- {
- if (::utimes(filePath.c_str(), writeTime) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"utimes");
- }
- else
- {
- if (::lutimes(filePath.c_str(), writeTime) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"lutimes");
- }
- }
+ setWriteTimeNative(filePath, modTime, procSl); //throw FileError
#elif defined ZEN_MAC
struct ::timespec writeTime = {};
writeTime.tv_sec = modTime;
- setFileTimeRaw(filePath, nullptr, writeTime, procSl); //throw FileError
+ setWriteTimeNative(filePath, writeTime, nullptr, procSl); //throw FileError
#endif
}
@@ -1351,20 +1376,16 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
auto getErrorMsg = [](const Zstring& path) { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(path)); };
//special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS!
- Zstring dirTmp = removeLongPathPrefix(endsWith(targetPath, FILE_NAME_SEPARATOR) ?
- beforeLast(targetPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) :
- targetPath);
- if (dirTmp.size() == 2 &&
- isAlpha(dirTmp[0]) && dirTmp[1] == L':')
+ const Zstring pathPf = appendSeparator(targetPath); //we do not support "C:" to represent a relative path!
+ if (pathPf.size() == 3 &&
+ isAlpha(pathPf[0]) && pathPf[1] == L':')
{
- dirTmp += FILE_NAME_SEPARATOR; //we do not support "C:" to represent a relative path!
-
- const DWORD ec = somethingExists(dirTmp) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; //don't use dirExists() => harmonize with ErrorTargetExisting!
+ const DWORD ec = somethingExists(pathPf) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; //don't use dirExists() => harmonize with ErrorTargetExisting!
const std::wstring errorDescr = formatSystemError(L"CreateDirectory", ec);
if (ec == ERROR_ALREADY_EXISTS)
- throw ErrorTargetExisting(getErrorMsg(dirTmp), errorDescr);
- throw FileError(getErrorMsg(dirTmp), errorDescr); //[!] this is NOT a ErrorTargetPathMissing case!
+ throw ErrorTargetExisting(getErrorMsg(pathPf), errorDescr);
+ throw FileError(getErrorMsg(pathPf), errorDescr); //[!] this is NOT a ErrorTargetPathMissing case!
}
//deliberately don't support creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/
@@ -1498,7 +1519,8 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
}
#elif defined ZEN_MAC
- /*int rv =*/ ::copyfile(sourcePath.c_str(), targetPath.c_str(), nullptr, COPYFILE_XATTR);
+ if (hasNativeSupportForExtendedAtrributes(targetPath)) //throw FileError
+ ::copyfile(sourcePath.c_str(), targetPath.c_str(), nullptr, COPYFILE_XATTR); //ignore errors, see related comments in copyFileOsSpecific()
#endif
ZEN_ON_SCOPE_FAIL(try { removeDirectorySimple(targetPath); }
@@ -1564,24 +1586,24 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
&sourceAttr)) //__out LPVOID lpFileInformation
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"GetFileAttributesEx");
- setFileTimeRaw(targetLink, &sourceAttr.ftCreationTime, sourceAttr.ftLastWriteTime, ProcSymlink::DIRECT); //throw FileError
+ setWriteTimeNative(targetLink, sourceAttr.ftLastWriteTime, &sourceAttr.ftCreationTime, ProcSymlink::DIRECT); //throw FileError
#elif defined ZEN_LINUX
struct ::stat sourceInfo = {};
if (::lstat(sourceLink.c_str(), &sourceInfo) != 0)
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"lstat");
- setFileTime(targetLink, sourceInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError
+ setWriteTimeNative(targetLink, sourceInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError
#elif defined ZEN_MAC
struct ::stat sourceInfo = {};
if (::lstat(sourceLink.c_str(), &sourceInfo) != 0)
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"lstat");
- if (::copyfile(sourceLink.c_str(), targetLink.c_str(), nullptr, COPYFILE_XATTR | COPYFILE_NOFOLLOW) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), L"copyfile");
+ if (hasNativeSupportForExtendedAtrributes(targetLink)) //throw FileError
+ ::copyfile(sourceLink.c_str(), targetLink.c_str(), nullptr, COPYFILE_XATTR | COPYFILE_NOFOLLOW); //ignore errors, see related comments in copyFileOsSpecific()
- setFileTimeRaw(targetLink, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::DIRECT); //throw FileError
+ setWriteTimeNative(targetLink, sourceInfo.st_mtimespec, &sourceInfo.st_birthtimespec, ProcSymlink::DIRECT); //throw FileError
#endif
if (copyFilePermissions)
@@ -1720,10 +1742,10 @@ bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //thr
InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked
const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
+ const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
{
//try to get backup read and write privileges: help solve most "access denied" errors with FILE_FLAG_BACKUP_SEMANTICS:
- //https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/1998ebf2/
+ //http://www.freefilesync.org/forum/viewtopic.php?t=1714
try { activatePrivilege(SE_BACKUP_NAME); }
catch (const FileError&) {}
try { activatePrivilege(SE_RESTORE_NAME); }
@@ -1933,7 +1955,7 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw
throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite: incomplete write."); //user should never see this
//total bytes transferred may be larger than file size! context information + ADS or smaller (sparse, compressed)!
- if (onUpdateCopyStatus) onUpdateCopyStatus(bytesRead); //throw X!
+ if (notifyProgress) notifyProgress(bytesRead); //throw X!
if (bytesRead > 0)
someBytesRead = true;
@@ -1946,11 +1968,7 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw
throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()"
//time needs to be set at the end: BackupWrite() changes modification time
- if (!::SetFileTime(hFileTarget,
- &fileInfoSource.ftCreationTime,
- nullptr,
- &fileInfoSource.ftLastWriteTime))
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(targetFile)), L"SetFileTime");
+ setFileTimeByHandle(hFileTarget, &fileInfoSource.ftCreationTime,fileInfoSource.ftLastWriteTime, targetFile); //throw FileError
return newAttrib;
}
@@ -1961,16 +1979,16 @@ DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyAsBackupStream);
struct CallbackData
{
- CallbackData(const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
+ CallbackData(const std::function<void(std::int64_t bytesDelta)>& notifyProgress,
const Zstring& sourceFile,
const Zstring& targetFile) :
sourceFile_(sourceFile),
targetFile_(targetFile),
- onUpdateCopyStatus_(onUpdateCopyStatus) {}
+ notifyProgress_(notifyProgress) {}
const Zstring& sourceFile_;
const Zstring& targetFile_;
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus_; //optional
+ const std::function<void(std::int64_t bytesDelta)>& notifyProgress_; //optional
std::exception_ptr exception; //out
BY_HANDLE_FILE_INFORMATION fileInfoSrc{}; //out: modified by CopyFileEx() at beginning
@@ -2059,9 +2077,9 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
}
}
- if (cbd.onUpdateCopyStatus_ && totalBytesTransferred.QuadPart >= 0) //should always be true, but let's still check
+ if (cbd.notifyProgress_ && totalBytesTransferred.QuadPart >= 0) //should always be true, but let's still check
{
- cbd.onUpdateCopyStatus_(totalBytesTransferred.QuadPart - cbd.bytesReported); //throw X!
+ cbd.notifyProgress_(totalBytesTransferred.QuadPart - cbd.bytesReported); //throw X!
cbd.bytesReported = totalBytesTransferred.QuadPart;
}
}
@@ -2076,7 +2094,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream
const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
+ const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
{
//try to get backup read and write privileges: may help solve some "access denied" errors
bool backupPrivilegesActive = true;
@@ -2095,12 +2113,12 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE
//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, 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/
+ // - this flag may cause file corruption! http://www.freefilesync.org/forum/viewtopic.php?t=1857
// - 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);
+ CallbackData cbd(notifyProgress, sourceFile, targetFile);
const bool success = ::CopyFileEx(applyLongPathPrefix(sourceFile).c_str(), //__in LPCTSTR lpExistingFileName,
applyLongPathPrefix(targetFile).c_str(), //__in LPCTSTR lpNewFileName,
@@ -2122,10 +2140,10 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE
throw ErrorFallbackToCopyAsBackupStream(L"sparse, copy failure");
if (ec == ERROR_ACCESS_DENIED && backupPrivilegesActive)
- //chances are good this will work with copyFileWindowsBackupStream: https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/1998ebf2/
+ //chances are good this will work with copyFileWindowsBackupStream: http://www.freefilesync.org/forum/viewtopic.php?t=1714
throw ErrorFallbackToCopyAsBackupStream(L"access denied");
- //copying ADS may incorrectly fail with ERROR_FILE_NOT_FOUND: https://sourceforge.net/p/freefilesync/discussion/help/thread/a18a2c02/
+ //copying ADS may incorrectly fail with ERROR_FILE_NOT_FOUND: http://www.freefilesync.org/forum/viewtopic.php?t=446
if (ec == ERROR_FILE_NOT_FOUND &&
cbd.fileInfoSrc.nNumberOfLinks > 0 &&
cbd.fileInfoTrg.nNumberOfLinks > 0)
@@ -2175,7 +2193,7 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE
//caveat: - ::CopyFileEx() silently *ignores* failure to set modification time!!! => we always need to set it again but with proper error checking!
// - perf: recent measurements show no slow down at all for buffered USB sticks!
- setFileTimeRaw(targetFile, &cbd.fileInfoSrc.ftCreationTime, cbd.fileInfoSrc.ftLastWriteTime, ProcSymlink::FOLLOW); //throw FileError
+ setWriteTimeNative(targetFile, cbd.fileInfoSrc.ftLastWriteTime, &cbd.fileInfoSrc.ftCreationTime, ProcSymlink::FOLLOW); //throw FileError
InSyncAttributes newAttrib;
newAttrib.fileSize = get64BitUInt(cbd.fileInfoSrc.nFileSizeLow, cbd.fileInfoSrc.nFileSizeHigh);
@@ -2188,15 +2206,15 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE
//another layer to support copying sparse files and handle some access denied errors
inline
-InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
+InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
{
try
{
- return copyFileWindowsDefault(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream
+ return copyFileWindowsDefault(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream
}
catch (ErrorFallbackToCopyAsBackupStream&)
{
- return copyFileWindowsBackupStream(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ return copyFileWindowsBackupStream(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked
}
}
@@ -2205,11 +2223,11 @@ InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Z
inline
InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile,
const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
+ const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
{
try
{
- return copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ return copyFileWindowsSelectRoutine(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked
}
catch (const ErrorTargetExisting&)
{
@@ -2217,7 +2235,7 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile,
if (have8dot3NameClash(targetFile))
{
Fix8Dot3NameClash dummy(targetFile); //throw FileError; move clashing file path to the side
- return copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError; the short file path name clash is solved, this should work now
+ return copyFileWindowsSelectRoutine(sourceFile, targetFile, notifyProgress); //throw FileError; the short file path name clash is solved, this should work now
}
throw;
}
@@ -2227,19 +2245,20 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile,
#elif defined ZEN_LINUX || defined ZEN_MAC
InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting
const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
+ const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
{
FileInput fileIn(sourceFile); //throw FileError
+ if (notifyProgress) notifyProgress(0); //throw X!
struct ::stat sourceInfo = {};
if (::fstat(fileIn.getHandle(), &sourceInfo) != 0)
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"fstat");
-
+
const mode_t mode = sourceInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
- //it seems we don't need S_IWUSR, not even for the setFileTime() below! (tested with source file having different user/group!)
-
- const int fdTarget = ::open(targetFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, mode);
+ //it seems we don't need S_IWUSR, not even for the setFileTime() below! (tested with source file having different user/group!)
+
//=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
+ const int fdTarget = ::open(targetFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, mode);
if (fdTarget == -1)
{
const int ec = errno; //copy before making other system calls!
@@ -2251,66 +2270,62 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError
throw FileError(errorMsg, errorDescr);
}
- if (onUpdateCopyStatus) onUpdateCopyStatus(0); //throw X!
-
- InSyncAttributes newAttrib;
ZEN_ON_SCOPE_FAIL( try { removeFile(targetFile); }
catch (FileError&) {} );
//transactional behavior: place guard after ::open() and before lifetime of FileOutput:
//=> don't delete file that existed previously!!!
- {
- FileOutput fileOut(fdTarget, targetFile); //pass ownership
- if (onUpdateCopyStatus) onUpdateCopyStatus(0); //throw X!
-
- copyStream(fileIn, fileOut, std::min(fileIn .optimalBlockSize(),
- fileOut.optimalBlockSize()), onUpdateCopyStatus); //throw FileError, X
-
- struct ::stat targetInfo = {};
- if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"fstat");
+ FileOutput fileOut(fdTarget, targetFile); //pass ownership
+ if (notifyProgress) notifyProgress(0); //throw X!
- newAttrib.fileSize = sourceInfo.st_size;
-#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);
+ unbufferedStreamCopy(fileIn, fileOut, notifyProgress); //throw FileError, X
#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
+ //using ::copyfile with COPYFILE_DATA seems to trigger bugs unlike our stream-based copying!
+ //=> use ::copyfile for extended attributes only: http://www.freefilesync.org/forum/viewtopic.php?t=401
+ //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
+
+ //avoid creation of ._ files if target doesn't support extended attributes: http://www.freefilesync.org/forum/viewtopic.php?t=2226
+ if (hasNativeSupportForExtendedAtrributes(targetFile, fileOut.getHandle())) //throw FileError
if (::fcopyfile(fileIn.getHandle(), fileOut.getHandle(), nullptr, COPYFILE_XATTR) != 0)
- {
- /*
- extended attributes are only optional here => ignore error
- E2BIG - reference email: "FFS V7.8 on Mac with 10.11.2 ElCapitan"
- EINVAL - reference email: "Error Code 22: Invalid argument (copyfile)"
- */
- //THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtPath(sourceFile)), L"%y", L"\n" + fmtPath(targetFile)), L"fcopyfile");
- }
+ ; //THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtPath(sourceFile)), L"%y", L"\n" + fmtPath(targetFile)), L"fcopyfile");
+ /*
+ both problems still occur even with the "hasNativeSupportForExtendedAtrributes" check above:
+ E2BIG - reference email: "Re: FFS V7.8 on Mac with 10.11.2 ElCapitan"
+ EINVAL - reference email: "Error Code 22: Invalid argument (copyfile)"
+ */
#endif
+ struct ::stat targetInfo = {};
+ if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"fstat");
- fileOut.close(); //throw FileError -> optional, but good place to catch errors when closing stream!
- } //close output file handle before setting file time
+ //close output file handle before setting file time; also good place to catch errors when closing stream!
+ fileOut.close(); //throw FileError
+ if (notifyProgress) notifyProgress(0); //throw X!
//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.
//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/
+ //OS X: http://www.freefilesync.org/forum/viewtopic.php?t=356
#ifdef ZEN_MAC
- setFileTimeRaw(targetFile, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::FOLLOW); //throw FileError
+ setWriteTimeNative(targetFile, sourceInfo.st_mtimespec, &sourceInfo.st_birthtimespec, ProcSymlink::FOLLOW); //throw FileError
//sourceInfo.st_birthtime; -> only seconds-precision
//sourceInfo.st_mtime; ->
#else
- setFileTime(targetFile, sourceInfo.st_mtime, ProcSymlink::FOLLOW); //throw FileError
+ setWriteTimeNative(targetFile, sourceInfo.st_mtime, ProcSymlink::FOLLOW); //throw FileError
#endif
+ InSyncAttributes newAttrib;
+ newAttrib.fileSize = sourceInfo.st_size;
+#ifdef ZEN_MAC
+ newAttrib.modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setWriteTimeNative() for consistency
+#else
+ newAttrib.modificationTime = sourceInfo.st_mtime;
+#endif
+ newAttrib.sourceFileId = extractFileId(sourceInfo);
+ newAttrib.targetFileId = extractFileId(targetInfo);
return newAttrib;
}
#endif
@@ -2331,9 +2346,9 @@ copyFileWindowsDefault(::CopyFileEx) copyFileWindowsBackupStream(::BackupRead/:
InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
+ const std::function<void(std::int64_t bytesDelta)>& notifyProgress)
{
- const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked
//at this point we know we created a new file, so it's fine to delete it for cleanup!
ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); }
bgstack15