summaryrefslogtreecommitdiff
path: root/zen/file_handling.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:20:07 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:20:07 +0200
commit88a8b528e20013c0aa3cc6bcd9659b0b5ddd9170 (patch)
treec6c5babb49b90293380106b81ae5c446959ac70f /zen/file_handling.cpp
parent5.3 (diff)
downloadFreeFileSync-88a8b528e20013c0aa3cc6bcd9659b0b5ddd9170.tar.gz
FreeFileSync-88a8b528e20013c0aa3cc6bcd9659b0b5ddd9170.tar.bz2
FreeFileSync-88a8b528e20013c0aa3cc6bcd9659b0b5ddd9170.zip
5.4
Diffstat (limited to 'zen/file_handling.cpp')
-rw-r--r--zen/file_handling.cpp348
1 files changed, 202 insertions, 146 deletions
diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp
index 864fd61f..f004e09c 100644
--- a/zen/file_handling.cpp
+++ b/zen/file_handling.cpp
@@ -14,7 +14,6 @@
#include "file_io.h"
#include "assert_static.h"
#include <boost/thread/tss.hpp>
-//#include <boost/thread/once.hpp>
#include "file_id_def.h"
#ifdef FFS_WIN
@@ -73,15 +72,15 @@ bool zen::dirExists(const Zstring& dirname)
}
-bool zen::symlinkExists(const Zstring& objname)
+bool zen::symlinkExists(const Zstring& linkname)
{
#ifdef FFS_WIN
- const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(objname).c_str());
+ const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(linkname).c_str());
return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
#elif defined FFS_LINUX
struct stat fileInfo = {};
- return ::lstat(objname.c_str(), &fileInfo) == 0 &&
+ return ::lstat(linkname.c_str(), &fileInfo) == 0 &&
S_ISLNK(fileInfo.st_mode); //symbolic link
#endif
}
@@ -143,7 +142,7 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS,
+ FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory
nullptr);
if (hFile == INVALID_HANDLE_VALUE)
throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted());
@@ -453,7 +452,7 @@ Zstring getFilenameFmt(const Zstring& filename, Function fun) //throw(); returns
const DWORD rv = fun(filenameFmt.c_str(), //__in LPCTSTR lpszShortPath,
&buffer[0], //__out LPTSTR lpszLongPath,
bufferSize); //__in DWORD cchBuffer
- if (rv == 0 || rv >= buffer.size())
+ if (rv == 0 || rv >= bufferSize)
return Zstring();
return &buffer[0];
@@ -478,7 +477,7 @@ Zstring findUnused8Dot3Name(const Zstring& filename) //find a unique 8.3 short n
return output;
}
- throw std::runtime_error(std::string("100000000 files, one for each number, exist in this directory? You're kidding...\n") + utf8CvrtTo<std::string>(pathPrefix));
+ throw std::runtime_error(std::string("100000000 files, one for each number, exist in this directory? You're kidding...\n") + utfCvrtTo<std::string>(pathPrefix));
}
@@ -575,14 +574,10 @@ public:
virtual void deleteTargetFile(const Zstring& targetFile) { assert(!fileExists(targetFile)); }
- virtual void updateCopyStatus(UInt64 totalBytesTransferred)
+ virtual void updateCopyStatus(Int64 bytesDelta)
{
if (moveCallback)
- {
- const Int64 delta = to<Int64>(totalBytesTransferred) - bytesReported;
- moveCallback->updateStatus(delta);
- bytesReported += delta;
- }
+ moveCallback->updateStatus(bytesDelta);
}
private:
@@ -592,7 +587,6 @@ private:
const Zstring sourceFile_;
const Zstring targetFile_;
CallbackMoveFile* moveCallback; //optional
- Int64 bytesReported;
};
@@ -649,12 +643,13 @@ public:
files_.push_back(std::make_pair(shortName, fullName));
}
- virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details)
+ virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details)
{
if (details.dirLink)
dirs_.push_back(std::make_pair(shortName, fullName));
else
files_.push_back(std::make_pair(shortName, fullName));
+ return LINK_SKIP;
}
virtual std::shared_ptr<TraverseCallback> onDir(const Zchar* shortName, const Zstring& fullName)
@@ -727,7 +722,7 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, Callb
//traverse source directory one level
TraverseOneLevel traverseCallback(fileList, dirList);
- traverseFolder(sourceDir, false, traverseCallback); //traverse one level, don't follow symlinks
+ traverseFolder(sourceDir, traverseCallback); //traverse one level
const Zstring targetDirPf = appendSeparator(targetDir);
@@ -780,12 +775,13 @@ public:
{
m_files.push_back(fullName);
}
- virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details)
+ virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details)
{
if (details.dirLink)
m_dirs.push_back(fullName);
else
m_files.push_back(fullName);
+ return LINK_SKIP;
}
virtual std::shared_ptr<TraverseCallback> onDir(const Zchar* shortName, const Zstring& fullName)
{
@@ -836,7 +832,7 @@ void zen::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback)
{
//get all files and directories from current directory (WITHOUT subdirectories!)
FilesDirsOnlyTraverser traverser(fileList, dirList);
- traverseFolder(directory, false, traverser); //don't follow symlinks
+ traverseFolder(directory, traverser); //don't follow symlinks
}
//delete directories recursively
@@ -896,7 +892,9 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
FileUpdateHandle targetHandle(filename, [=]
{
return ::CreateFile(applyLongPathPrefix(filename).c_str(),
- GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp
+ FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
+ //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even after read-only was removed right before:
+ //https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING,
@@ -917,11 +915,68 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
auto isNullTime = [](const FILETIME & ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; };
- if (!::SetFileTime(targetHandle.get(),
- isNullTime(creationTime) ? nullptr : &creationTime,
- nullptr,
- &lastWriteTime))
- throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted());
+ if (!::SetFileTime(targetHandle.get(), //__in HANDLE hFile,
+ !isNullTime(creationTime) ? &creationTime : nullptr, //__in_opt const FILETIME *lpCreationTime,
+ nullptr, //__in_opt const FILETIME *lpLastAccessTime,
+ &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime
+ {
+ auto lastErr = ::GetLastError();
+
+ //function may fail if file is read-only: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
+ if (lastErr == 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(targetHandle.get(), //__in HANDLE hFile,
+ FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
+ &basicInfo, //__in LPVOID lpFileInformation,
+ sizeof(basicInfo))) //__in DWORD dwBufferSize
+ throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted());
+ };
+
+ 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(targetHandle.get(), &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 (!isNullTime(creationTime))
+ basicInfo.CreationTime = toLargeInteger(creationTime);
+
+ setFileInfo(basicInfo); //throw FileError
+
+ try //... to restore original file attributes
+ {
+ FILE_BASIC_INFO basicInfo2 = {};
+ basicInfo2.FileAttributes = fileInfo.dwFileAttributes;
+ setFileInfo(basicInfo2); //throw FileError
+ }
+ catch (FileError&) {}
+
+ lastErr = ERROR_SUCCESS;
+ }
+ }
+
+ if (lastErr != ERROR_SUCCESS)
+ throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastErr));
+ }
+ }
#ifndef NDEBUG //dst hack: verify data written
if (dst::isFatDrive(filename) && !dirExists(filename)) //throw()
@@ -1322,7 +1377,6 @@ void createDirectory_straight(const Zstring& directory, const Zstring& templateD
ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir));
USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
-
DWORD bytesReturned = 0;
::DeviceIoControl(hDir, //handle to file or directory
FSCTL_SET_COMPRESSION, //dwIoControlCode
@@ -1364,8 +1418,11 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat
return;
}
#elif defined FFS_LINUX
- if (dirExists(directory))
- return;
+ if (somethingExists(directory))
+ {
+ if (dirExists(directory))
+ return;
+ }
#endif
else //if "not somethingExists" we need to create the parent directory
{
@@ -1485,7 +1542,7 @@ source attr | tf normal | tf compressed | tf encrypted | handled by
============|==================================================================
--- | --- -C- E-- copyFileWindowsDefault
--S | --S -CS E-S copyFileWindowsSparse
- -C- | --- (NOK) -C- E-- copyFileWindowsDefault
+ -C- | -C- -C- E-- copyFileWindowsDefault
-CS | -CS -CS E-S copyFileWindowsSparse
E-- | E-- E-- E-- copyFileWindowsDefault
E-S | E-- (NOK) E-- (NOK) E-- (NOK) copyFileWindowsDefault -> may fail with ERROR_DISK_FULL!!
@@ -1502,14 +1559,10 @@ Note: - if target parent folder is compressed or encrypted, both attributes are
//due to issues on non-NTFS volumes, we should use the copy-as-sparse routine only if required and supported!
-bool canCopyAsSparse(HANDLE hSource, const Zstring& targetFile) //throw ()
+bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw ()
{
- BY_HANDLE_FILE_INFORMATION fileInfoSource = {};
- if (!::GetFileInformationByHandle(hSource, &fileInfoSource))
- return false;
-
- const bool sourceIsEncrypted = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0;
- const bool sourceIsSparse = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0;
+ const bool sourceIsEncrypted = (fileAttrSource & FILE_ATTRIBUTE_ENCRYPTED) != 0;
+ const bool sourceIsSparse = (fileAttrSource & FILE_ATTRIBUTE_SPARSE_FILE) != 0;
if (sourceIsEncrypted || !sourceIsSparse) //BackupRead() silently fails reading encrypted files!
return false; //small perf optimization: don't check "targetFile" if not needed
@@ -1544,18 +1597,23 @@ bool canCopyAsSparse(HANDLE hSource, const Zstring& targetFile) //throw ()
bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //throw ()
{
- HANDLE hFileSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(),
- GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read files that are open in other applications
- nullptr,
- OPEN_EXISTING,
- 0,
- nullptr);
- if (hFileSource == INVALID_HANDLE_VALUE)
+ //follow symlinks!
+ HANDLE hSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read files that are open in other applications
+ nullptr,
+ OPEN_EXISTING,
+ 0,
+ nullptr);
+ if (hSource == INVALID_HANDLE_VALUE)
+ return false;
+ ZEN_ON_SCOPE_EXIT(::CloseHandle(hSource));
+
+ BY_HANDLE_FILE_INFORMATION fileInfoSource = {};
+ if (!::GetFileInformationByHandle(hSource, &fileInfoSource))
return false;
- ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileSource));
- return canCopyAsSparse(hFileSource, targetFile); //throw ()
+ return canCopyAsSparse(fileInfoSource.dwFileAttributes, targetFile); //throw ()
}
@@ -1567,7 +1625,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
{
assert(canCopyAsSparse(sourceFile, targetFile));
- //comment suggests "FILE_FLAG_BACKUP_SEMANTICS + SE_BACKUP_NAME" may be needed: http://msdn.microsoft.com/en-us/library/windows/desktop/aa362509(v=vs.85).aspx
+ //try to get backup read and write privileges: who knows, maybe this helps solve some obscure "access denied" errors
try { activatePrivilege(SE_BACKUP_NAME); }
catch (const FileError&) {}
try { activatePrivilege(SE_RESTORE_NAME); }
@@ -1576,7 +1634,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
//open sourceFile for reading
HANDLE hFileSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(),
GENERIC_READ,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read files that are open in other applications
+ FILE_SHARE_READ | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING, //FILE_FLAG_OVERLAPPED must not be used!
FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_BACKUP_SEMANTICS, //FILE_FLAG_NO_BUFFERING should not be used!
@@ -1605,7 +1663,8 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + getLastErrorFormatted());
//----------------------------------------------------------------------
- const DWORD validAttribs = FILE_ATTRIBUTE_READONLY |
+ const DWORD validAttribs = FILE_ATTRIBUTE_NORMAL | //"This attribute is valid only if used alone."
+ FILE_ATTRIBUTE_READONLY |
FILE_ATTRIBUTE_HIDDEN |
FILE_ATTRIBUTE_SYSTEM |
FILE_ATTRIBUTE_ARCHIVE | //those two are not set properly (not worse than ::CopyFileEx())
@@ -1615,7 +1674,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
//create targetFile and open it for writing
HANDLE hFileTarget = ::CreateFile(applyLongPathPrefix(targetFile).c_str(),
GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION
- FILE_SHARE_READ | FILE_SHARE_DELETE,
+ FILE_SHARE_DELETE, //FILE_SHARE_DELETE is required to rename file while handle is open!
nullptr,
CREATE_NEW,
FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_BACKUP_SEMANTICS | (fileInfoSource.dwFileAttributes & validAttribs),
@@ -1652,73 +1711,44 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
newAttrib->targetFileId = extractFileID(fileInfoTarget);
}
- //----------------------------------------------------------------------
- DWORD fsFlagsTarget = 0;
- {
- const DWORD bufferSize = 10000;
- std::vector<wchar_t> buffer(bufferSize);
-
- //full pathName need not yet exist!
- if (!::GetVolumePathName(targetFile.c_str(), //__in LPCTSTR lpszFileName,
- &buffer[0], //__out LPTSTR lpszVolumePathName,
- bufferSize)) //__in DWORD cchBufferLength
- throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted());
-
- //GetVolumeInformationByHandleW would be a better solution, but it is supported beginning with Vista only!
- if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName
- nullptr, //__out_opt LPTSTR lpVolumeNameBuffer,
- 0, //__in DWORD nVolumeNameSize,
- nullptr, //__out_opt LPDWORD lpVolumeSerialNumber,
- nullptr, //__out_opt LPDWORD lpMaximumComponentLength,
- &fsFlagsTarget, //__out_opt LPDWORD lpFileSystemFlags,
- nullptr, //__out LPTSTR lpFileSystemNameBuffer,
- 0)) //__in DWORD nFileSystemNameSize
- throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted());
- }
-
- //----------------------------------------------------------------------
- const bool sourceIsCompressed = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
- const bool sourceIsSparse = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0;
-
- const bool targetSupportsCompressed = (fsFlagsTarget & FILE_FILE_COMPRESSION ) != 0;
- const bool targetSupportsSparse = (fsFlagsTarget & FILE_SUPPORTS_SPARSE_FILES) != 0;
-
- //----------------------------------------------------------------------
- if (sourceIsCompressed && targetSupportsCompressed)
+ //#################### copy NTFS compressed attribute #########################
+ const bool sourceIsCompressed = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
+ const bool targetIsCompressed = (fileInfoTarget.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CreateFile if target parent folder is compressed!
+ if (sourceIsCompressed && !targetIsCompressed)
{
USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
DWORD bytesReturned = 0;
- if (!DeviceIoControl(hFileTarget, //handle to file or directory
- FSCTL_SET_COMPRESSION, //dwIoControlCode
- &cmpState, //input buffer
- sizeof(cmpState), //size of input buffer
- nullptr, //lpOutBuffer
- 0, //OutBufferSize
- &bytesReturned, //number of bytes returned
- nullptr)) //OVERLAPPED structure
- {
- //-> if target folder is encrypted this call will legitimately fail with ERROR_INVALID_FUNCTION
- //throw FileError
- }
- }
+ if (!::DeviceIoControl(hFileTarget, //handle to file or directory
+ FSCTL_SET_COMPRESSION, //dwIoControlCode
+ &cmpState, //input buffer
+ sizeof(cmpState), //size of input buffer
+ nullptr, //lpOutBuffer
+ 0, //OutBufferSize
+ &bytesReturned, //number of bytes returned
+ nullptr)) //OVERLAPPED structure
+ {} //may legitimately fail with ERROR_INVALID_FUNCTION if:
+ // - target folder is encrypted
+ // - target volume does not support compressed attribute -> unlikely in this context
+ }
+ //#############################################################################
//although it seems the sparse attribute is set automatically by BackupWrite, we are required to do this manually: http://support.microsoft.com/kb/271398/en-us
//Quote: It is the responsibility of the backup utility to apply file attributes to a file after it is restored by using BackupWrite.
//The application should retrieve the attributes by using GetFileAttributes prior to creating a backup with BackupRead.
//If a file originally had the sparse attribute (FILE_ATTRIBUTE_SPARSE_FILE), the backup utility must explicitly set the
- //attribute on the restored file. The attribute can be set by using the DeviceIoControl function with the FSCTL_SET_SPARSE flag.
+ //attribute on the restored file.
- if (sourceIsSparse && targetSupportsSparse)
+ //if (sourceIsSparse && targetSupportsSparse) -> no need to check, this is our precondition!
{
DWORD bytesReturned = 0;
- if (!DeviceIoControl(hFileTarget, //handle to file
- FSCTL_SET_SPARSE, //dwIoControlCode
- nullptr, //input buffer
- 0, //size of input buffer
- nullptr, //lpOutBuffer
- 0, //OutBufferSize
- &bytesReturned, //number of bytes returned
- nullptr)) //OVERLAPPED structure
+ if (!::DeviceIoControl(hFileTarget, //handle to file
+ FSCTL_SET_SPARSE, //dwIoControlCode
+ nullptr, //input buffer
+ 0, //size of input buffer
+ nullptr, //lpOutBuffer
+ 0, //OutBufferSize
+ &bytesReturned, //number of bytes returned
+ nullptr)) //OVERLAPPED structure
throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(targetFile)) +
L"\n\n" + zen::getLastErrorFormatted() + L" (NTFS sparse)");
}
@@ -1737,10 +1767,9 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
if (contextRead ) ::BackupRead (0, nullptr, 0, nullptr, true, false, &contextRead); //lpContext must be passed [...] all other parameters are ignored.
if (contextWrite) ::BackupWrite(0, nullptr, 0, nullptr, true, false, &contextWrite); );
-
//stream-copy sourceFile to targetFile
- UInt64 totalBytesTransferred; //may be larger than file size! context information + ADS!
bool eof = false;
+ bool silentFailure = true; //try to detect failure reading encrypted files
do
{
DWORD bytesRead = 0;
@@ -1772,18 +1801,21 @@ void copyFileWindowsSparse(const Zstring& sourceFile,
if (bytesWritten != bytesRead)
throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + L"(incomplete write)");
- totalBytesTransferred += bytesRead;
+ //total bytes transferred may be larger than file size! context information + ADS or smaller (sparse, compressed)!
//invoke callback method to update progress indicators
if (callback)
- callback->updateCopyStatus(totalBytesTransferred); //throw X!
+ callback->updateCopyStatus(Int64(bytesRead)); //throw X!
+
+ if (bytesRead > 0)
+ silentFailure = false;
}
while (!eof);
//DST hack not required, since both source and target volumes cannot be FAT!
//::BackupRead() silently fails reading encrypted files -> double check!
- if (totalBytesTransferred == 0U && UInt64(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh) != 0U)
+ if (silentFailure && UInt64(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"\n\n" + L"(unknown error)");
@@ -1835,33 +1867,32 @@ DEFINE_NEW_FILE_ERROR(ErrorShouldCopyAsSparse);
class ErrorHandling
{
public:
- ErrorHandling() : shouldCopyAsSparse(false) {}
-
- void reportUserException(CallbackCopyFile& userCallback, const UInt64& bytesTransferred)
- {
- exceptionInUserCallback.reset(new std::pair<CallbackCopyFile*, UInt64>(&userCallback, bytesTransferred));
- }
+ ErrorHandling() : shouldCopyAsSparse(false), exceptionInUserCallback(nullptr) {}
+ //call context: copyCallbackInternal()
void reportErrorShouldCopyAsSparse() { shouldCopyAsSparse = true; }
+ void reportUserException(CallbackCopyFile& userCallback) { exceptionInUserCallback = &userCallback; }
+
void reportError(const std::wstring& message) { errorMsg = message; }
+ //call context: copyFileWindowsDefault()
void evaluateErrors() //throw X
{
if (shouldCopyAsSparse)
throw ErrorShouldCopyAsSparse(L"sparse dummy value");
if (exceptionInUserCallback)
- exceptionInUserCallback->first->updateCopyStatus(exceptionInUserCallback->second); //rethrow (hopefully!)
+ exceptionInUserCallback->updateCopyStatus(0); //rethrow (hopefully!)
if (!errorMsg.empty())
throw FileError(errorMsg);
}
private:
- bool shouldCopyAsSparse;
- std::wstring errorMsg; //these two are exclusive!
- std::unique_ptr<std::pair<CallbackCopyFile*, UInt64>> exceptionInUserCallback; //
+ bool shouldCopyAsSparse; //
+ std::wstring errorMsg; //these are exclusive!
+ CallbackCopyFile* exceptionInUserCallback; //
};
@@ -1879,7 +1910,9 @@ struct CallbackData
CallbackCopyFile* const userCallback; //optional!
ErrorHandling errorHandler;
- FileAttrib newAttrib; //modified by CopyFileEx at start
+ FileAttrib newAttrib; //modified by CopyFileEx() at beginning
+
+ Int64 bytesReported; //used internally to calculate bytes transferred delta
};
@@ -1903,6 +1936,8 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
file time handling:
::CopyFileEx() will copy file modification time (only) over from source file AFTER the last invokation of this callback
=> it is possible to adapt file creation time of target in here, but NOT file modification time!
+ CAVEAT: if ::CopyFileEx() fails to set modification time, it silently ignores this error and returns success!!!
+ see procmon log in: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
alternate data stream handling:
CopyFileEx() processes multiple streams one after another, stream 1 is the file data stream and always available!
@@ -1915,12 +1950,6 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
if (dwCallbackReason == CALLBACK_STREAM_SWITCH && //called up-front for every file (even if 0-sized)
dwStreamNumber == 1) //consider ADS!
{
- if (canCopyAsSparse(hSourceFile, cbd.targetFile_)) //throw ()
- {
- cbd.errorHandler.reportErrorShouldCopyAsSparse(); //use another copy routine!
- return PROGRESS_CANCEL;
- }
-
//#################### return source file attributes ################################
BY_HANDLE_FILE_INFORMATION fileInfoSrc = {};
if (!::GetFileInformationByHandle(hSourceFile, &fileInfoSrc))
@@ -1941,14 +1970,40 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
cbd.newAttrib.sourceFileId = extractFileID(fileInfoSrc);
cbd.newAttrib.targetFileId = extractFileID(fileInfoTrg);
+ //#################### switch to sparse file copy if req. #######################
+ if (canCopyAsSparse(fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw ()
+ {
+ cbd.errorHandler.reportErrorShouldCopyAsSparse(); //use a different copy routine!
+ return PROGRESS_CANCEL;
+ }
+
//#################### copy file creation time ################################
::SetFileTime(hDestinationFile, &fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling!
- //##############################################################################
+
+ //#################### copy NTFS compressed attribute #########################
+ const bool sourceIsCompressed = (fileInfoSrc.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
+ const bool targetIsCompressed = (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, //handle to file or directory
+ FSCTL_SET_COMPRESSION, //dwIoControlCode
+ &cmpState, //input buffer
+ sizeof(cmpState), //size of input buffer
+ nullptr, //lpOutBuffer
+ 0, //OutBufferSize
+ &bytesReturned, //number of bytes returned
+ nullptr)) //OVERLAPPED structure
+ {} //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.userCallback)
{
//some odd check for some possible(?) error condition
@@ -1960,13 +2015,14 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
nullptr, 0);
try
{
- cbd.userCallback->updateCopyStatus(UInt64(totalBytesTransferred.QuadPart)); //throw X!
+ cbd.userCallback->updateCopyStatus(totalBytesTransferred.QuadPart - cbd.bytesReported); //throw X!
+ cbd.bytesReported = totalBytesTransferred.QuadPart;
}
catch (...)
{
//#warning migrate to std::exception_ptr when available
- cbd.errorHandler.reportUserException(*cbd.userCallback, UInt64(totalBytesTransferred.QuadPart));
+ cbd.errorHandler.reportUserException(*cbd.userCallback);
return PROGRESS_CANCEL;
}
}
@@ -1975,7 +2031,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize,
const bool supportNonEncryptedDestination = winXpOrLater(); //encrypted destination is not supported with Windows 2000
-const bool supportUnbufferedCopy = vistaOrLater();
+//const bool supportUnbufferedCopy = vistaOrLater();
//caveat: function scope static initialization is not thread-safe in VS 2010!
@@ -1984,20 +2040,23 @@ void copyFileWindowsDefault(const Zstring& sourceFile,
CallbackCopyFile* callback,
FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse
{
+ //try to get backup read and write privileges: who knows, maybe this helps solve some obscure "access denied" errors
+ try { activatePrivilege(SE_BACKUP_NAME); }
+ catch (const FileError&) {}
+ try { activatePrivilege(SE_RESTORE_NAME); }
+ catch (const FileError&) {}
+
zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (...) {} });
//transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we? ;)
DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS;
-#ifndef COPY_FILE_ALLOW_DECRYPTED_DESTINATION
- const DWORD COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008;
-#endif
-
if (supportNonEncryptedDestination)
copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; //allow copying from encrypted to non-encrytped location
- if (supportUnbufferedCopy) //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)
+ //if (supportUnbufferedCopy) //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/tracker/?func=detail&atid=1093080&aid=3529683&group_id=234430
CallbackData cbd(callback, sourceFile, targetFile);
@@ -2007,7 +2066,7 @@ void copyFileWindowsDefault(const Zstring& sourceFile,
copyCallbackInternal, //__in_opt LPPROGRESS_ROUTINE lpProgressRoutine,
&cbd, //__in_opt LPVOID lpData,
nullptr, //__in_opt LPBOOL pbCancel,
- copyFlags) == TRUE; //__in DWORD dwCopyFlags
+ copyFlags) != FALSE; //__in DWORD dwCopyFlags
cbd.errorHandler.evaluateErrors(); //throw ?, process errors in callback first!
if (!success)
@@ -2141,18 +2200,15 @@ void copyFileLinux(const Zstring& sourceFile,
}();
//copy contents of sourceFile to targetFile
- UInt64 totalBytesTransferred;
do
{
const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError
fileOut.write(&buffer[0], bytesRead); //throw FileError
- totalBytesTransferred += bytesRead;
-
//invoke callback method to update progress indicators
if (callback)
- callback->updateCopyStatus(totalBytesTransferred); //throw X!
+ callback->updateCopyStatus(Int64(bytesRead)); //throw X!
}
while (!fileIn.eof());
}
@@ -2194,11 +2250,11 @@ void copyFileLinux(const Zstring& sourceFile,
#endif
-Zstring createTempName(const Zstring& filename)
+Zstring findUnusedTempName(const Zstring& filename)
{
Zstring output = filename + zen::TEMP_FILE_ENDING;
- //ensure uniqueness
+ //ensure uniqueness (+ minor race condition)
for (int i = 1; somethingExists(output); ++i)
output = filename + Zchar('_') + numberTo<Zstring>(i) + zen::TEMP_FILE_ENDING;
@@ -2255,7 +2311,7 @@ void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPath
{
//determine non-used temp file name "first":
//using optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses
- temporary = createTempName(targetFile);
+ temporary = findUnusedTempName(targetFile);
//retry
copyFileSelectOs(sourceFile, temporary, callback, sourceAttr); //throw FileError
bgstack15