summaryrefslogtreecommitdiff
path: root/zen/file_access.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2015-10-02 14:55:19 +0200
committerDaniel Wilhelm <daniel@wili.li>2015-10-02 14:55:19 +0200
commit46fc289a8776ba253e97d01d6948fb1031ea1973 (patch)
treeb16a99c60f21b04c001f29862bf2ee16ae3a0e00 /zen/file_access.cpp
parent6.15 (diff)
downloadFreeFileSync-46fc289a8776ba253e97d01d6948fb1031ea1973.tar.gz
FreeFileSync-46fc289a8776ba253e97d01d6948fb1031ea1973.tar.bz2
FreeFileSync-46fc289a8776ba253e97d01d6948fb1031ea1973.zip
7.0
Diffstat (limited to 'zen/file_access.cpp')
-rw-r--r--zen/file_access.cpp375
1 files changed, 128 insertions, 247 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index ffbdc813..b1b781ee 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -12,8 +12,8 @@
#include "file_traverser.h"
#include "scope_guard.h"
#include "symlink_target.h"
-#include "file_io.h"
#include "file_id_def.h"
+#include "serialize.h"
#ifdef ZEN_WIN
#include <Aclapi.h>
@@ -133,67 +133,38 @@ bool zen::somethingExists(const Zstring& objname)
namespace
{
#ifdef ZEN_WIN
-//fast ::GetVolumePathName() clone: let's hope it's not too simple (doesn't honor mount points)
-Zstring getVolumeNameFast(const Zstring& filepath)
+bool isFatDrive(const Zstring& filePath) //throw()
{
- //this call is expensive: ~1.5 ms!
- // if (!::GetVolumePathName(filepath.c_str(), //__in LPCTSTR lpszFileName,
- // fsName, //__out LPTSTR lpszVolumePathName,
- // BUFFER_SIZE)) //__in DWORD cchBufferLength
- // ...
- // Zstring volumePath = appendSeparator(fsName);
-
- const Zstring nameFmt = appendSeparator(removeLongPathPrefix(filepath)); //throw()
+ const DWORD bufferSize = MAX_PATH + 1;
+ std::vector<wchar_t> buffer(bufferSize);
- if (startsWith(nameFmt, Zstr("\\\\"))) //UNC path: "\\ComputerName\SharedFolder\"
- {
- size_t nameSize = nameFmt.size();
- const size_t posFirstSlash = nameFmt.find(Zstr("\\"), 2);
- if (posFirstSlash != Zstring::npos)
- {
- nameSize = posFirstSlash + 1;
- const size_t posSecondSlash = nameFmt.find(Zstr("\\"), posFirstSlash + 1);
- if (posSecondSlash != Zstring::npos)
- nameSize = posSecondSlash + 1;
- }
- return Zstring(nameFmt.c_str(), nameSize); //include trailing backslash!
- }
- else //local path: "C:\Folder\"
+ //this call is expensive: ~1.5 ms!
+ if (!::GetVolumePathName(filePath.c_str(), //__in LPCTSTR lpszFileName,
+ &buffer[0], //__out LPTSTR lpszVolumePathName,
+ bufferSize)) //__in DWORD cchBufferLength
{
- const size_t pos = nameFmt.find(Zstr(":\\"));
- if (pos == 1) //expect single letter volume
- return Zstring(nameFmt.c_str(), 3);
- }
-
- return Zstring();
-}
-
-
-bool isFatDrive(const Zstring& filepath) //throw()
-{
- const Zstring volumePath = getVolumeNameFast(filepath);
- if (volumePath.empty())
+ assert(false);
return false;
+ }
- const DWORD bufferSize = MAX_PATH + 1;
- wchar_t fsName[bufferSize] = {};
+ const Zstring volumePath = appendSeparator(&buffer[0]);
//suprisingly fast: ca. 0.03 ms per call!
- if (!::GetVolumeInformation(appendSeparator(volumePath).c_str(), //__in_opt LPCTSTR lpRootPathName,
+ if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName,
nullptr, //__out LPTSTR lpVolumeNameBuffer,
0, //__in DWORD nVolumeNameSize,
nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
nullptr, //__out_opt LPDWORD lpMaximumComponentLength,
nullptr, //__out_opt LPDWORD lpFileSystemFlags,
- fsName, //__out LPTSTR lpFileSystemNameBuffer,
+ &buffer[0], //__out LPTSTR lpFileSystemNameBuffer,
bufferSize)) //__in DWORD nFileSystemNameSize
{
- assert(false); //shouldn't happen
+ assert(false);
return false;
}
- //DST hack seems to be working equally well for FAT and FAT32 (in particular creation time has 10^-2 s precision as advertised)
- return fsName == Zstring(L"FAT") ||
- fsName == Zstring(L"FAT32");
+
+ return &buffer[0] == Zstring(L"FAT") ||
+ &buffer[0] == Zstring(L"FAT32");
}
@@ -267,7 +238,7 @@ std::uint64_t zen::getFilesize(const Zstring& filepath) //throw FileError
}
-std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError
+std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0 if not available
{
#ifdef ZEN_WIN
ULARGE_INTEGER bytesFree = {};
@@ -275,14 +246,15 @@ std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError
&bytesFree, //__out_opt PULARGE_INTEGER lpFreeBytesAvailable,
nullptr, //__out_opt PULARGE_INTEGER lpTotalNumberOfBytes,
nullptr)) //__out_opt PULARGE_INTEGER lpTotalNumberOfFreeBytes
- throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(path)), L"GetDiskFreeSpaceEx", getLastError());
+ throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"GetDiskFreeSpaceEx", getLastError());
+ //return 0 if info is not available: "The GetDiskFreeSpaceEx function returns zero for lpFreeBytesAvailable for all CD requests"
return get64BitUInt(bytesFree.LowPart, bytesFree.HighPart);
#elif defined ZEN_LINUX || defined ZEN_MAC
struct ::statfs info = {};
if (::statfs(path.c_str(), &info) != 0)
- throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(path)), L"statfs", getLastError());
+ throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"statfs", getLastError());
return static_cast<std::uint64_t>(info.f_bsize) * info.f_bavail;
#endif
@@ -293,8 +265,8 @@ bool zen::removeFile(const Zstring& filepath) //throw FileError
{
#ifdef ZEN_WIN
const wchar_t functionName[] = L"DeleteFile";
- const Zstring& filepathFmt = applyLongPathPrefix(filepath);
- if (!::DeleteFile(filepathFmt.c_str()))
+
+ if (!::DeleteFile(applyLongPathPrefix(filepath).c_str()))
#elif defined ZEN_LINUX || defined ZEN_MAC
const wchar_t functionName[] = L"unlink";
if (::unlink(filepath.c_str()) != 0)
@@ -304,9 +276,9 @@ bool zen::removeFile(const Zstring& filepath) //throw FileError
#ifdef ZEN_WIN
if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only
{
- ::SetFileAttributes(filepathFmt.c_str(), FILE_ATTRIBUTE_NORMAL); //(try to) normalize file attributes
+ ::SetFileAttributes(applyLongPathPrefix(filepath).c_str(), FILE_ATTRIBUTE_NORMAL); //(try to) normalize file attributes
- if (::DeleteFile(filepathFmt.c_str())) //now try again...
+ if (::DeleteFile(applyLongPathPrefix(filepath).c_str())) //now try again...
return true;
lastError = ::GetLastError();
}
@@ -380,7 +352,7 @@ void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw File
}
}
}
-
+ //begin of "regular" error reporting
const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(oldName)), L"%y", L"\n" + fmtFileName(newName));
std::wstring errorDescr = formatSystemError(L"MoveFileEx", lastError);
@@ -480,8 +452,8 @@ bool have8dot3NameClash(const Zstring& filepath)
if (!shortName.empty() &&
!longName .empty() &&
- EqualFilename()(origName, shortName) &&
- !EqualFilename()(shortName, longName))
+ EqualFilePath()(origName, shortName) &&
+ !EqualFilePath()(shortName, longName))
{
//for filepath short and long file name are equal and another unrelated file happens to have the same short name
//e.g. filepath == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1"
@@ -526,21 +498,21 @@ private:
//rename file: no copying!!!
-void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+void zen::renameFile(const Zstring& itemPathOld, const Zstring& itemPathNew) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
{
try
{
- renameFile_sub(oldName, newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+ renameFile_sub(itemPathOld, itemPathNew); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
}
catch (const ErrorTargetExisting&)
{
#ifdef ZEN_WIN
//try to handle issues with already existing short 8.3 file names on Windows
- if (have8dot3NameClash(newName))
+ if (have8dot3NameClash(itemPathNew))
{
- Fix8Dot3NameClash dummy(newName); //throw FileError; move clashing filepath to the side
+ Fix8Dot3NameClash dummy(itemPathNew); //throw FileError; move clashing filepath to the side
//now try again...
- renameFile_sub(oldName, newName); //throw FileError
+ renameFile_sub(itemPathOld, itemPathNew); //throw FileError
return;
}
#endif
@@ -964,8 +936,8 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink
// - 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)
+ newTimes[0].tv_sec = modTime; //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].tv_sec = modTime; //modification time (seconds)
if (procSl == ProcSymlink::FOLLOW)
{
@@ -1016,13 +988,16 @@ bool zen::supportsPermissions(const Zstring& dirpath) //throw FileError
#ifdef ZEN_WIN
const DWORD bufferSize = MAX_PATH + 1;
std::vector<wchar_t> buffer(bufferSize);
+
if (!::GetVolumePathName(dirpath.c_str(), //__in LPCTSTR lpszFileName,
&buffer[0], //__out LPTSTR lpszVolumePathName,
bufferSize)) //__in DWORD cchBufferLength
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(dirpath)), L"GetVolumePathName", getLastError());
+ const Zstring volumePath = appendSeparator(&buffer[0]);
+
DWORD fsFlags = 0;
- if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName,
+ if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName,
nullptr, //__out LPTSTR lpVolumeNameBuffer,
0, //__in DWORD nVolumeNameSize,
nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
@@ -1309,7 +1284,7 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget
try
{
- makeDirectoryPlain(directory, Zstring(), false); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+ copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
}
catch (const ErrorTargetPathMissing&)
{
@@ -1322,10 +1297,10 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget
{
makeDirectoryRecursively(dirParent); //throw FileError, (ErrorTargetExisting)
}
- catch (const ErrorTargetExisting&) { /*parent directory created externally in the meantime? => NOT AN ERROR*/ }
+ catch (const ErrorTargetExisting&) {} //parent directory created externally in the meantime? => NOT AN ERROR; not a directory? fail in next step!
//now try again...
- makeDirectoryPlain(directory, Zstring(), false); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing)
+ copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing)
return;
}
throw;
@@ -1334,7 +1309,7 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget
}
-void zen::makeDirectory(const Zstring& directory, bool failIfExists) //throw FileError, ErrorTargetExisting
+void zen::makeNewDirectory(const Zstring& directory) //throw FileError, ErrorTargetExisting
{
//remove trailing separator (even for C:\ root directories)
const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ?
@@ -1345,40 +1320,22 @@ void zen::makeDirectory(const Zstring& directory, bool failIfExists) //throw Fil
{
makeDirectoryRecursively(dirFormatted); //FileError, ErrorTargetExisting
}
- catch (const ErrorTargetExisting&)
- {
- //avoid any file system race-condition by *not* checking directory existence again here!!!
- if (failIfExists)
- throw;
- }
- catch (const FileError&)
+ catch (const ErrorTargetExisting&) //*something* existing: folder or FILE!
{
- /*
- could there be situations where a directory/network path exists,
- but creation fails with error different than "ErrorTargetExisting"??
- - creation of C:\ fails with ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS
- */
- if (somethingExists(directory)) //a file system race-condition! don't use dirExists() => harmonize with ErrorTargetExisting!
- {
- assert(false);
- if (failIfExists)
- throw; //do NOT convert to ErrorTargetExisting: if "failIfExists", not getting a ErrorTargetExisting *atomically* is unexpected!
- }
- else
+ //avoid any file system race-condition by *not* checking existence again here!!!
throw;
}
}
-void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
- const Zstring& templateDir,
+void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
bool copyFilePermissions)
{
#ifdef ZEN_WIN
//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(directory, FILE_NAME_SEPARATOR) ?
- beforeLast(directory, FILE_NAME_SEPARATOR) :
- directory);
+ Zstring dirTmp = removeLongPathPrefix(endsWith(targetPath, FILE_NAME_SEPARATOR) ?
+ beforeLast(targetPath, FILE_NAME_SEPARATOR) :
+ targetPath);
if (dirTmp.size() == 2 &&
std::iswalpha(dirTmp[0]) && dirTmp[1] == L':')
{
@@ -1398,19 +1355,19 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
//- it may fail with "wrong parameter (error code 87)" when source is on mapped online storage
//- automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)!
//- it isn't able to copy most junctions because of missing permissions (although target path can be retrieved alternatively!)
- if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), //__in LPCTSTR lpPathName,
+ if (!::CreateDirectory(applyLongPathPrefixCreateDir(targetPath).c_str(), //__in LPCTSTR lpPathName,
nullptr)) //__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes
{
DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls!
//handle issues with already existing short 8.3 file names on Windows
if (lastError == ERROR_ALREADY_EXISTS)
- if (have8dot3NameClash(directory))
+ if (have8dot3NameClash(targetPath))
{
- Fix8Dot3NameClash dummy(directory); //throw FileError; move clashing object to the side
+ Fix8Dot3NameClash dummy(targetPath); //throw FileError; move clashing object to the side
//now try again...
- if (::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), nullptr))
+ if (::CreateDirectory(applyLongPathPrefixCreateDir(targetPath).c_str(), nullptr))
lastError = ERROR_SUCCESS;
else
lastError = ::GetLastError();
@@ -1418,7 +1375,7 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
if (lastError != ERROR_SUCCESS)
{
- const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory));
+ const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(targetPath));
const std::wstring errorDescr = formatSystemError(L"CreateDirectory", lastError);
if (lastError == ERROR_ALREADY_EXISTS)
@@ -1433,18 +1390,18 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory
struct ::stat dirInfo = {};
- if (!templateDir.empty())
- if (::stat(templateDir.c_str(), &dirInfo) == 0)
+ if (!sourcePath.empty())
+ if (::stat(sourcePath.c_str(), &dirInfo) == 0)
{
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 copyItemPermissions() only for "chown" and umask-agnostic permissions
- if (::mkdir(directory.c_str(), mode) != 0)
+ if (::mkdir(targetPath.c_str(), mode) != 0)
{
const int lastError = errno; //copy before directly or indirectly making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory));
+ const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(targetPath));
const std::wstring errorDescr = formatSystemError(L"mkdir", lastError);
if (lastError == EEXIST)
@@ -1455,11 +1412,11 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
}
#endif
- if (!templateDir.empty())
+ if (!sourcePath.empty())
{
#ifdef ZEN_WIN
//optional: try to copy file attributes (dereference symlinks and junctions)
- const HANDLE hDirSrc = ::CreateFile(zen::applyLongPathPrefix(templateDir).c_str(), //_In_ LPCTSTR lpFileName,
+ const HANDLE hDirSrc = ::CreateFile(zen::applyLongPathPrefix(sourcePath).c_str(), //_In_ LPCTSTR lpFileName,
0, //_In_ DWORD dwDesiredAccess,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode,
nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
@@ -1474,16 +1431,16 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
BY_HANDLE_FILE_INFORMATION dirInfo = {};
if (::GetFileInformationByHandle(hDirSrc, &dirInfo))
{
- ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), dirInfo.dwFileAttributes);
+ ::SetFileAttributes(applyLongPathPrefix(targetPath).c_str(), dirInfo.dwFileAttributes);
//copy "read-only and system attributes": http://blogs.msdn.com/b/oldnewthing/archive/2003/09/30/55100.aspx
const bool isEncrypted = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0;
const bool isCompressed = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
if (isEncrypted)
- ::EncryptFile(directory.c_str()); //seems no long path is required (check passed!)
+ ::EncryptFile(targetPath.c_str()); //seems no long path is required (check passed!)
- HANDLE hDirTrg = ::CreateFile(applyLongPathPrefix(directory).c_str(), //_In_ LPCTSTR lpFileName,
+ HANDLE hDirTrg = ::CreateFile(applyLongPathPrefix(targetPath).c_str(), //_In_ LPCTSTR lpFileName,
GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess,
/*read access required for FSCTL_SET_COMPRESSION*/
FILE_SHARE_READ |
@@ -1521,14 +1478,14 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT
}
#elif defined ZEN_MAC
- /*int rv =*/ ::copyfile(templateDir.c_str(), directory.c_str(), 0, COPYFILE_XATTR);
+ /*int rv =*/ ::copyfile(sourcePath.c_str(), targetPath.c_str(), 0, COPYFILE_XATTR);
#endif
- zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectory(directory); } catch (FileError&) {} }); //ensure cleanup:
+ zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectory(targetPath); } catch (FileError&) {} }); //ensure cleanup:
//enforce copying file permissions: it's advertized on GUI...
if (copyFilePermissions)
- copyItemPermissions(templateDir, directory, ProcSymlink::FOLLOW); //throw FileError
+ copyItemPermissions(sourcePath, targetPath, ProcSymlink::FOLLOW); //throw FileError
guardNewDir.dismiss(); //target has been created successfully!
}
@@ -1663,7 +1620,7 @@ bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw ()
return false; //small perf optimization: don't check "targetFile" if not needed
//------------------------------------------------------------------------------------
- const DWORD bufferSize = 10000;
+ const DWORD bufferSize = MAX_PATH + 1;
std::vector<wchar_t> buffer(bufferSize);
//full pathName need not yet exist!
@@ -1672,8 +1629,10 @@ bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw ()
bufferSize)) //__in DWORD cchBufferLength
return false;
+ const Zstring volumePath = appendSeparator(&buffer[0]);
+
DWORD fsFlagsTarget = 0;
- if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName
+ if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName
nullptr, //__out_opt LPTSTR lpVolumeNameBuffer,
0, //__in DWORD nVolumeNameSize,
nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
@@ -1715,10 +1674,9 @@ bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //thr
//precondition: canCopyAsSparse() must return "true"!
-void copyFileWindowsSparse(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting, ErrorFileLocked
+InSyncAttributes copyFileWindowsSparse(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
assert(canCopyAsSparse(sourceFile, targetFile));
@@ -1810,13 +1768,11 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"GetFileInformationByHandle", getLastError());
//return up-to-date file attributes
- if (newAttrib)
- {
- newAttrib->fileSize = get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh);
- newAttrib->modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); //no DST hack (yet)
- newAttrib->sourceFileId = extractFileId(fileInfoSource);
- newAttrib->targetFileId = extractFileId(fileInfoTarget);
- }
+ InSyncAttributes newAttrib = {};
+ newAttrib.fileSize = get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh);
+ newAttrib.modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); //no DST hack (yet)
+ newAttrib.sourceFileId = extractFileId(fileInfoSource);
+ newAttrib.targetFileId = extractFileId(fileInfoTarget);
//#################### copy NTFS compressed attribute #########################
const bool sourceIsCompressed = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
@@ -1860,7 +1816,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
}
//----------------------------------------------------------------------
- const DWORD BUFFER_SIZE = 128 * 1024; //must be greater than sizeof(WIN32_STREAM_ID)
+ const DWORD BUFFER_SIZE = std::max(128 * 1024, static_cast<int>(sizeof(WIN32_STREAM_ID))); //must be greater than sizeof(WIN32_STREAM_ID)!
std::vector<BYTE> buffer(BUFFER_SIZE);
LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite()
@@ -1886,7 +1842,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"BackupRead", getLastError()); //better use fine-granular error messages "reading/writing"!
if (bytesRead > BUFFER_SIZE)
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"buffer overflow"); //user should never see this
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"BackupRead: buffer overflow."); //user should never see this
if (bytesRead < BUFFER_SIZE)
eof = true;
@@ -1902,7 +1858,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)), L"BackupWrite", getLastError());
if (bytesWritten != bytesRead)
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)), L"incomplete write"); //user should never see this
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(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)!
@@ -1918,7 +1874,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
//::BackupRead() silently fails reading encrypted files -> double check!
if (!someBytesWritten && get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh) != 0U)
//note: there is no guaranteed ordering relation beween bytes transferred and file size! Consider ADS (>) and compressed/sparse files (<)!
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()"
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(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,
@@ -1928,6 +1884,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)), L"SetFileTime", getLastError());
guardTarget.dismiss();
+ return newAttrib;
}
@@ -2049,13 +2006,12 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
const bool supportNonEncryptedDestination = winXpOrLater(); //encrypted destination is not supported with Windows 2000
-//caveat: function scope static initialization is not thread-safe in VS 2010!
+//caveat: function scope static initialization is not thread-safe in VS 2010! -> still not sufficient if multiple threads access during static init!!!
-void copyFileWindowsDefault(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse
+InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
//try to get backup read and write privileges: who knows, maybe this helps solve some obscure "access denied" errors
try { activatePrivilege(SE_BACKUP_NAME); }
@@ -2129,7 +2085,7 @@ void copyFileWindowsDefault(const Zstring& sourceFile,
if (lastError == ERROR_INVALID_PARAMETER &&
isFatDrive(targetFile) &&
getFilesize(sourceFile) >= 4U * std::uint64_t(1024U * 1024 * 1024)) //throw FileError
- errorDescr += L"\nFAT volumes cannot store files larger than 4 gigabyte.";
+ errorDescr += L"\nFAT volumes cannot store files larger than 4 gigabytes.";
//see "Limitations of the FAT32 File System": http://support.microsoft.com/kb/314463/en-us
//note: ERROR_INVALID_PARAMETER can also occur when copying to a SharePoint server or MS SkyDrive and the target filepath is of a restricted type.
@@ -2139,47 +2095,45 @@ void copyFileWindowsDefault(const Zstring& sourceFile,
throw FileError(errorMsg, errorDescr);
}
- if (newAttrib)
- {
- newAttrib->fileSize = get64BitUInt(cbd.fileInfoSrc.nFileSizeLow, cbd.fileInfoSrc.nFileSizeHigh);
- newAttrib->modificationTime = filetimeToTimeT(cbd.fileInfoSrc.ftLastWriteTime);
- newAttrib->sourceFileId = extractFileId(cbd.fileInfoSrc);
- newAttrib->targetFileId = extractFileId(cbd.fileInfoTrg);
- }
-
//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
guardTarget.dismiss(); //target has been created successfully!
+
+ InSyncAttributes newAttrib = {};
+ newAttrib.fileSize = get64BitUInt(cbd.fileInfoSrc.nFileSizeLow, cbd.fileInfoSrc.nFileSizeHigh);
+ newAttrib.modificationTime = filetimeToTimeT(cbd.fileInfoSrc.ftLastWriteTime);
+ newAttrib.sourceFileId = extractFileId(cbd.fileInfoSrc);
+ newAttrib.targetFileId = extractFileId(cbd.fileInfoTrg);
+ return newAttrib;
}
//another layer to support copying sparse files
inline
-void copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus, InSyncAttributes* sourceAttr)
+InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
try
{
- copyFileWindowsDefault(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse
+ return copyFileWindowsDefault(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse
}
catch (ErrorShouldCopyAsSparse&) //we quickly check for this condition within callback of ::CopyFileEx()!
{
- copyFileWindowsSparse(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ return copyFileWindowsSparse(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked
}
}
//another layer of indirection solving 8.3 name clashes
inline
-void copyFileOsSpecific(const Zstring& sourceFile,
- const Zstring& targetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* sourceAttr)
+InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
try
{
- copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ return copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked
}
catch (const ErrorTargetExisting&)
{
@@ -2187,8 +2141,7 @@ void copyFileOsSpecific(const Zstring& sourceFile,
if (have8dot3NameClash(targetFile))
{
Fix8Dot3NameClash dummy(targetFile); //throw FileError; move clashing filepath to the side
- copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError; the short filepath name clash is solved, this should work now
- return;
+ return copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError; the short filepath name clash is solved, this should work now
}
throw;
}
@@ -2196,10 +2149,9 @@ void copyFileOsSpecific(const Zstring& sourceFile,
#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
+InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting
+ const Zstring& targetFile,
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
FileInput fileIn(sourceFile); //throw FileError
@@ -2221,40 +2173,31 @@ void copyFileOsSpecific(const Zstring& sourceFile,
throw FileError(errorMsg, errorDescr);
}
+ if (onUpdateCopyStatus) onUpdateCopyStatus(0); //throw X!
+ InSyncAttributes newAttrib = {};
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!!!
{
FileOutput fileOut(fdTarget, targetFile); //pass ownership
+ if (onUpdateCopyStatus) onUpdateCopyStatus(0); //throw X!
- std::vector<char> buffer(128 * 1024);
- do
- {
- const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError
-
- fileOut.write(&buffer[0], bytesRead); //throw FileError
-
- if (onUpdateCopyStatus)
- onUpdateCopyStatus(bytesRead); //throw X!
- }
- while (!fileIn.eof());
+ copyStream(fileIn, fileOut, std::min(fileIn .optimalBlockSize(),
+ fileOut.optimalBlockSize()), onUpdateCopyStatus); //throw FileError, X
struct ::stat targetInfo = {};
if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError());
- if (newAttrib) //return file statistics
- {
- newAttrib->fileSize = sourceInfo.st_size;
+ newAttrib.fileSize = sourceInfo.st_size;
#ifdef ZEN_MAC
- newAttrib->modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setFileTimeRaw() for consistency
+ newAttrib.modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setFileTimeRaw() for consistency
#else
- newAttrib->modificationTime = sourceInfo.st_mtime;
+ newAttrib.modificationTime = sourceInfo.st_mtime;
#endif
- newAttrib->sourceFileId = extractFileId(sourceInfo);
- newAttrib->targetFileId = extractFileId(targetInfo);
- }
+ newAttrib.sourceFileId = extractFileId(sourceInfo);
+ newAttrib.targetFileId = extractFileId(targetInfo);
#ifdef ZEN_MAC
//using ::copyfile with COPYFILE_DATA seems to trigger bugs unlike our stream-based copying!
@@ -2265,6 +2208,8 @@ void copyFileOsSpecific(const Zstring& sourceFile,
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
+
+fileOut.close(); //throw FileError -> optional, but good place to catch errors when closing stream!
} //close output file handle before setting file time
//we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
@@ -2272,7 +2217,7 @@ void copyFileOsSpecific(const Zstring& sourceFile,
//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
+#ifdef ZEN_MAC
setFileTimeRaw(targetFile, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::FOLLOW); //throw FileError
//sourceInfo.st_birthtime; -> only seconds-precision
//sourceInfo.st_mtime; ->
@@ -2281,6 +2226,7 @@ void copyFileOsSpecific(const Zstring& sourceFile,
#endif
guardTarget.dismiss(); //target has been created successfully!
+ return newAttrib;
}
#endif
@@ -2288,25 +2234,21 @@ void copyFileOsSpecific(const Zstring& sourceFile,
------------------
|File Copy Layers|
------------------
- copyFile (setup transactional behavior)
+ copyNewFile
|
- copyFileWithPermissions
- |
- copyFileOsSpecific (solve 8.3 issue)
+ copyFileOsSpecific (solve 8.3 issue on Windows)
|
copyFileWindowsSelectRoutine
/ \
copyFileWindowsDefault(::CopyFileEx) copyFileWindowsSparse(::BackupRead/::BackupWrite)
*/
+}
-inline
-void copyFileWithPermissions(const Zstring& sourceFile,
- const Zstring& targetFile,
- bool copyFilePermissions,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* sourceAttr)
+
+InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus)
{
- copyFileOsSpecific(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked
if (copyFilePermissions)
{
@@ -2317,67 +2259,6 @@ void copyFileWithPermissions(const Zstring& sourceFile,
guardTargetFile.dismiss(); //target has been created successfully!
}
-}
-}
-
-
-void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorFileLocked
- const Zstring& targetFile,
- bool copyFilePermissions,
- bool transactionalCopy,
- const std::function<void()>& onDeleteTargetFile,
- const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus,
- InSyncAttributes* sourceAttr)
-{
- if (transactionalCopy)
- {
- Zstring tmpTarget = targetFile + TEMP_FILE_ENDING;
-
- for (int i = 0;; ++i)
- try
- {
- copyFileWithPermissions(sourceFile, tmpTarget, copyFilePermissions, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
- break;
- }
- catch (const ErrorTargetExisting&) //optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses
- {
- if (i == 10) throw; //avoid endless recursion in pathological cases, e.g. https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/36adac33
- tmpTarget = targetFile + Zchar('_') + numberTo<Zstring>(i) + TEMP_FILE_ENDING;
- }
-
- //transactional behavior: ensure cleanup; not needed before copyFileWithPermissions() which is already transactional
- zen::ScopeGuard guardTempFile = zen::makeGuard([&] { try { removeFile(tmpTarget); } catch (FileError&) {} });
-
- //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite
- if (onDeleteTargetFile)
- onDeleteTargetFile(); //throw X
-
- //perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick!
- renameFile(tmpTarget, targetFile); //throw FileError
-
- /*
- CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" does
- NOT PRESERVE the creation time of the .ffs_tmp file, but SILENTLY "reuses" whatever creation time the old "file.txt" had!
- This "feature" is called "File System Tunneling":
- http://blogs.msdn.com/b/oldnewthing/archive/2005/07/15/439261.aspx
- http://support.microsoft.com/kb/172190/en-us
- */
-
- guardTempFile.dismiss();
- }
- else
- {
- /*
- Note: non-transactional file copy solves at least four problems:
- -> skydrive - doesn't allow for .ffs_tmp extension and returns ERROR_INVALID_PARAMETER
- -> network renaming issues
- -> allow for true delete before copy to handle low disk space problems
- -> higher performance on non-buffered drives (e.g. usb sticks)
- */
- if (onDeleteTargetFile)
- onDeleteTargetFile();
-
- copyFileWithPermissions(sourceFile, targetFile, copyFilePermissions, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked
- }
+ return attr;
}
bgstack15