diff options
Diffstat (limited to 'zen/file_access.cpp')
-rw-r--r-- | zen/file_access.cpp | 326 |
1 files changed, 178 insertions, 148 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 2e9d93f8..bad1b60d 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -730,7 +730,7 @@ void setFileTimeByHandle(HANDLE hFile, //throw FileError if (creationTime) basicInfo.CreationTime = toLargeInteger(*creationTime); - //set file time + attributes + //set file time + attributes if (!::SetFileInformationByHandle(hFile, //__in HANDLE hFile, FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, &basicInfo, //__in LPVOID lpFileInformation, @@ -906,21 +906,21 @@ void setWriteTimeNative(const Zstring& itemPath, ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); #if 0 //waiting for user feedback... -#ifdef ZEN_WIN_VISTA_AND_LATER - //bugs, bugs, bugs.... on "SharePoint" SetFileAttributes() seems to affect file modification time: http://www.freefilesync.org/forum/viewtopic.php?t=3699 - //on Vista we can avoid reopening the file (and the SharePoint bug) -ZEN_ON_SCOPE_EXIT( - if (attribsToRestore != INVALID_FILE_ATTRIBUTES) - { - FILE_BASIC_INFO basicInfo = {}; - basicInfo.FileAttributes = attribsToRestore; - ::SetFileInformationByHandle(hFile, //__in HANDLE hFile, - FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, - &basicInfo, //__in LPVOID lpFileInformation, - sizeof(basicInfo)); //__in DWORD dwBufferSize - attribsToRestore = INVALID_FILE_ATTRIBUTES; - } - ); +#ifdef ZEN_WIN_VISTA_AND_LATER + //bugs, bugs, bugs.... on "SharePoint" SetFileAttributes() seems to affect file modification time: http://www.freefilesync.org/forum/viewtopic.php?t=3699 + //on Vista we can avoid reopening the file (and the SharePoint bug) + ZEN_ON_SCOPE_EXIT( + if (attribsToRestore != INVALID_FILE_ATTRIBUTES) + { + FILE_BASIC_INFO basicInfo = {}; + basicInfo.FileAttributes = attribsToRestore; + ::SetFileInformationByHandle(hFile, //__in HANDLE hFile, + FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + &basicInfo, //__in LPVOID lpFileInformation, + sizeof(basicInfo)); //__in DWORD dwBufferSize + attribsToRestore = INVALID_FILE_ATTRIBUTES; + } + ); #endif #endif @@ -1407,26 +1407,26 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P } -void makeDirectoryRecursivelyImpl(const Zstring& directory) //FileError +void makeDirectoryRecursivelyImpl(const Zstring& dirPath) //FileError { - assert(!endsWith(directory, FILE_NAME_SEPARATOR)); //even "C:\" should be "C:" as input! + assert(!endsWith(dirPath, FILE_NAME_SEPARATOR)); //even "C:\" should be "C:" as input! try { - copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing + copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing } catch (const ErrorTargetExisting&) {} //*something* existing: folder or FILE! catch (const ErrorTargetPathMissing&) { //we need to create parent directories first - const Zstring dirParent = beforeLast(directory, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); - if (!dirParent.empty()) + const Zstring parentPath = beforeLast(dirPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + if (!parentPath.empty()) { //recurse... - makeDirectoryRecursivelyImpl(dirParent); //throw FileError + makeDirectoryRecursivelyImpl(parentPath); //throw FileError //now try again... - copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing) + copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing) return; } throw; @@ -1586,7 +1586,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, nullptr); //_Inout_opt_ LPOVERLAPPED lpOverlapped } - //(try to) set creation and modification time + //(try to) set creation (and modification) time /*bool rv = */::SetFileTime(hDirTrg, //_In_ HANDLE hFile, &dirInfo.ftCreationTime, //_Out_opt_ LPFILETIME lpCreationTime, nullptr, //_Out_opt_ LPFILETIME lpLastAccessTime, @@ -1598,6 +1598,16 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, #elif defined ZEN_MAC if (hasNativeSupportForExtendedAtrributes(targetPath)) //throw FileError ::copyfile(sourcePath.c_str(), targetPath.c_str(), nullptr, COPYFILE_XATTR); //ignore errors, see related comments in copyFileOsSpecific() + + //(try to) set creation (and modification) time + if (dirInfo.st_birthtimespec.tv_sec != 0) + try + { + setWriteTimeNative(targetPath, dirInfo.st_mtimespec, &dirInfo.st_birthtimespec, ProcSymlink::FOLLOW); //throw FileError + //dirInfo.st_birthtime; -> only seconds-precision + //dirInfo.st_mtime; -> + } + catch (FileError&) {} #endif ZEN_ON_SCOPE_FAIL(try { removeDirectorySimple(targetPath); } @@ -1691,7 +1701,7 @@ namespace { #ifdef ZEN_WIN /* - CopyFileEx() BackupRead() FileRead() + CopyFileEx() BackupRead() ReadFile() -------------------------------------------- Attributes YES NO NO create time NO NO NO @@ -1702,7 +1712,7 @@ Sparse NO YES NO Nonstandard FS YES UNKNOWN -> error writing ADS to Samba, issues reading from NAS, error copying files having "blocked" state... ect. PERF - 6% faster -Mark stream as compressed: FSCTL_SET_COMPRESSION - compatible with both BackupRead() and FileRead() +Mark stream as compressed: FSCTL_SET_COMPRESSION - compatible with both BackupRead() and ReadFile() Current support for combinations of NTFS extended attributes: @@ -1710,9 +1720,9 @@ Current support for combinations of NTFS extended attributes: source attr | tf normal | tf compressed | tf encrypted | handled by ============|================================================================== --- | --- -C- E-- copyFileWindowsDefault - --S | --S -CS E-S copyFileWindowsBackupStream + --S | --S -CS E-S copyFileWindowsStream -C- | -C- -C- E-- copyFileWindowsDefault - -CS | -CS -CS E-S copyFileWindowsBackupStream + -CS | -CS -CS E-S copyFileWindowsStream E-- | E-- E-- E-- copyFileWindowsDefault E-S | E-- (NOK) E-- (NOK) E-- (NOK) copyFileWindowsDefault -> may fail with ERROR_DISK_FULL for large sparse files!! @@ -1810,18 +1820,26 @@ bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //thr return false; ZEN_ON_SCOPE_EXIT(::CloseHandle(hSource)); - BY_HANDLE_FILE_INFORMATION fileInfoSource = {}; - if (!::GetFileInformationByHandle(hSource, &fileInfoSource)) + BY_HANDLE_FILE_INFORMATION sourceInfo = {}; + if (!::GetFileInformationByHandle(hSource, &sourceInfo)) return false; - return canCopyAsSparse(fileInfoSource.dwFileAttributes, targetFile); //throw () + return canCopyAsSparse(sourceInfo.dwFileAttributes, targetFile); //throw () } //============================================================================================= -InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked - const Zstring& targetFile, - const std::function<void(std::int64_t bytesDelta)>& notifyProgress) +enum class StreamCopyType +{ + READ_FILE, + BACKUP_READ, +}; + + +InSyncAttributes copyFileWindowsStream(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked + const Zstring& targetFile, + StreamCopyType scType, + 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: //http://www.freefilesync.org/forum/viewtopic.php?t=1714 @@ -1830,7 +1848,6 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw try { activatePrivilege(PrivilegeName::RESTORE); } catch (const FileError&) {} - //open sourceFile for reading HANDLE hFileSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(), //_In_ LPCTSTR lpFileName, GENERIC_READ, //_In_ DWORD dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, @@ -1862,16 +1879,18 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw throw FileError(errorMsg, errorDescr); } - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileSource)); + FileInput fileIn(hFileSource, sourceFile); //pass ownership + + if (notifyProgress) notifyProgress(0); //throw X! //---------------------------------------------------------------------- - BY_HANDLE_FILE_INFORMATION fileInfoSource = {}; - if (!::GetFileInformationByHandle(hFileSource, &fileInfoSource)) + BY_HANDLE_FILE_INFORMATION sourceInfo = {}; + if (!::GetFileInformationByHandle(fileIn.getHandle(), &sourceInfo)) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"GetFileInformationByHandle"); //encrypted files cannot be read with BackupRead which would fail silently! - const bool sourceIsEncrypted = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; - if (sourceIsEncrypted) + const bool sourceIsEncrypted = (sourceInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; + if (sourceIsEncrypted && scType == StreamCopyType::BACKUP_READ) throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: Source file is encrypted."); //---------------------------------------------------------------------- @@ -1883,7 +1902,6 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; // //FILE_ATTRIBUTE_ENCRYPTED -> no! - //create targetFile and open it for writing HANDLE hFileTarget = ::CreateFile(applyLongPathPrefix(targetFile).c_str(), //_In_ LPCTSTR lpFileName, GENERIC_READ | GENERIC_WRITE | DELETE, //_In_ DWORD dwDesiredAccess, //GENERIC_READ required for FSCTL_SET_COMPRESSION, DELETE for ::SetFileInformationByHandle(),FileDispositionInfo @@ -1892,7 +1910,7 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, CREATE_NEW, //_In_ DWORD dwCreationDisposition, //FILE_FLAG_OVERLAPPED must not be used! FILE_FLAG_NO_BUFFERING should not be used! - (fileInfoSource.dwFileAttributes & validAttribs) | + (sourceInfo.dwFileAttributes & validAttribs) | FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes, FILE_FLAG_BACKUP_SEMANTICS, //-> also required by FSCTL_SET_SPARSE nullptr); //_In_opt_ HANDLE hTemplateFile @@ -1911,14 +1929,14 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw throw FileError(errorMsg, errorDescr); } #ifdef ZEN_WIN_VISTA_AND_LATER - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileTarget)); + FileOutput fileOut(hFileTarget, targetFile); //pass ownership //no need for ::DeleteFile(), we already have an open handle! Maybe this also prevents needless buffer-flushing in ::CloseHandle()??? Anyway, same behavior like ::CopyFileEx() ZEN_ON_SCOPE_FAIL ( FILE_DISPOSITION_INFO di = {}; di.DeleteFile = true; - if (!::SetFileInformationByHandle(hFileTarget, //_In_ HANDLE hFile, + if (!::SetFileInformationByHandle(fileOut.getHandle(), //_In_ HANDLE hFile, FileDispositionInfo, //_In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass, &di, //_In_ LPVOID lpFileInformation, sizeof(di))) //_In_ DWORD dwBufferSize @@ -1928,29 +1946,30 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); } catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileTarget)); + FileOutput fileOut(hFileTarget, targetFile); //pass ownership #endif + if (notifyProgress) notifyProgress(0); //throw X! //---------------------------------------------------------------------- - BY_HANDLE_FILE_INFORMATION fileInfoTarget = {}; - if (!::GetFileInformationByHandle(hFileTarget, &fileInfoTarget)) + BY_HANDLE_FILE_INFORMATION targetInfo = {}; + if (!::GetFileInformationByHandle(fileOut.getHandle(), &targetInfo)) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"GetFileInformationByHandle"); //return up-to-date file attributes InSyncAttributes newAttrib; - newAttrib.fileSize = get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh); - newAttrib.modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); - newAttrib.sourceFileId = extractFileId(fileInfoSource); - newAttrib.targetFileId = extractFileId(fileInfoTarget); + newAttrib.fileSize = get64BitUInt(sourceInfo.nFileSizeLow, sourceInfo.nFileSizeHigh); + newAttrib.modificationTime = filetimeToTimeT(sourceInfo.ftLastWriteTime); + newAttrib.sourceFileId = extractFileId(sourceInfo); + newAttrib.targetFileId = extractFileId(targetInfo); //#################### 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! + const bool sourceIsCompressed = (sourceInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool targetIsCompressed = (targetInfo.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, //_In_ HANDLE hDevice, + if (!::DeviceIoControl(fileOut.getHandle(), //_In_ HANDLE hDevice, FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode, &cmpState, //_In_opt_ LPVOID lpInBuffer, sizeof(cmpState), //_In_ DWORD nInBufferSize, @@ -1969,91 +1988,97 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw //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. - + if (scType == StreamCopyType::BACKUP_READ) #ifdef ZEN_WIN_VISTA_AND_LATER - if (canCopyAsSparse(fileInfoSource.dwFileAttributes, hFileTarget)) //throw () + if (canCopyAsSparse(sourceInfo.dwFileAttributes, fileOut.getHandle())) //throw () #else - if (canCopyAsSparse(fileInfoSource.dwFileAttributes, targetFile)) //throw () + if (canCopyAsSparse(sourceInfo.dwFileAttributes, targetFile)) //throw () #endif - { - DWORD bytesReturned = 0; - if (!::DeviceIoControl(hFileTarget, //_In_ HANDLE hDevice, - FSCTL_SET_SPARSE, //_In_ DWORD dwIoControlCode, - nullptr, //_In_opt_ LPVOID lpInBuffer, - 0, //_In_ DWORD nInBufferSize, - nullptr, //_Out_opt_ LPVOID lpOutBuffer, - 0, //_In_ DWORD nOutBufferSize, - &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned, - nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(targetFile)), L"DeviceIoControl, FSCTL_SET_SPARSE"); - } + { + DWORD bytesReturned = 0; + if (!::DeviceIoControl(fileOut.getHandle(), //_In_ HANDLE hDevice, + FSCTL_SET_SPARSE, //_In_ DWORD dwIoControlCode, + nullptr, //_In_opt_ LPVOID lpInBuffer, + 0, //_In_ DWORD nInBufferSize, + nullptr, //_Out_opt_ LPVOID lpOutBuffer, + 0, //_In_ DWORD nOutBufferSize, + &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned, + nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(targetFile)), L"DeviceIoControl, FSCTL_SET_SPARSE"); + } //---------------------------------------------------------------------- - 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); + if (scType == StreamCopyType::READ_FILE) + unbufferedStreamCopy(fileIn, fileOut, notifyProgress); //throw FileError, X + else + { + 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() - LPVOID contextWrite = nullptr; // + LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite() + LPVOID contextWrite = nullptr; // - ZEN_ON_SCOPE_EXIT( - if (contextRead ) ::BackupRead (0, nullptr, 0, nullptr, true, false, &contextRead); //MSDN: "lpContext must be passed [...] all other parameters are ignored." - if (contextWrite) ::BackupWrite(0, nullptr, 0, nullptr, true, false, &contextWrite); ); // + ZEN_ON_SCOPE_EXIT( + if (contextRead ) ::BackupRead (0, nullptr, 0, nullptr, true, false, &contextRead); //MSDN: "lpContext must be passed [...] all other parameters are ignored." + if (contextWrite) ::BackupWrite(0, nullptr, 0, nullptr, true, false, &contextWrite); ); // - //stream-copy sourceFile to targetFile - bool eof = false; - bool someBytesRead = false; //try to detect failure reading encrypted files - do - { - DWORD bytesRead = 0; - if (!::BackupRead(hFileSource, //__in HANDLE hFile, - &buffer[0], //__out LPBYTE lpBuffer, - BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, - &bytesRead, //__out LPDWORD lpNumberOfBytesRead, - false, //__in BOOL bAbort, - false, //__in BOOL bProcessSecurity, - &contextRead)) //__out LPVOID *lpContext - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead"); //better use fine-granular error messages "reading/writing"! - - if (bytesRead > BUFFER_SIZE) - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: buffer overflow."); //user should never see this - - if (bytesRead < BUFFER_SIZE) - eof = true; - - DWORD bytesWritten = 0; - if (!::BackupWrite(hFileTarget, //__in HANDLE hFile, - &buffer[0], //__in LPBYTE lpBuffer, - bytesRead, //__in DWORD nNumberOfBytesToWrite, - &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten, - false, //__in BOOL bAbort, - false, //__in BOOL bProcessSecurity, - &contextWrite)) //__out LPVOID *lpContext - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite"); - - if (bytesWritten != bytesRead) - 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 (notifyProgress) notifyProgress(bytesRead); //throw X! - - if (bytesRead > 0) - someBytesRead = true; - } - while (!eof); + //stream-copy sourceFile to targetFile + bool eof = false; + bool someBytesRead = false; //try to detect failure reading encrypted files + do + { + DWORD bytesRead = 0; + if (!::BackupRead(fileIn.getHandle(), //__in HANDLE hFile, + &buffer[0], //__out LPBYTE lpBuffer, + BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out LPDWORD lpNumberOfBytesRead, + false, //__in BOOL bAbort, + false, //__in BOOL bProcessSecurity, + &contextRead)) //__out LPVOID *lpContext + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead"); //better use fine-granular error messages "reading/writing"! + + if (bytesRead > BUFFER_SIZE) + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: buffer overflow."); //user should never see this + + if (bytesRead < BUFFER_SIZE) + eof = true; + + DWORD bytesWritten = 0; + if (!::BackupWrite(fileOut.getHandle(), //__in HANDLE hFile, + &buffer[0], //__in LPBYTE lpBuffer, + bytesRead, //__in DWORD nNumberOfBytesToWrite, + &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten, + false, //__in BOOL bAbort, + false, //__in BOOL bProcessSecurity, + &contextWrite)) //__out LPVOID *lpContext + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite"); + + if (bytesWritten != bytesRead) + 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 (notifyProgress) notifyProgress(bytesRead); //throw X! + + if (bytesRead > 0) + someBytesRead = true; + } + while (!eof); - //::BackupRead() silently fails reading encrypted files -> double check! - if (!someBytesRead && 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", fmtPath(sourceFile)), L"BackupRead: unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()" + //::BackupRead() silently fails reading encrypted files -> double check! + if (!someBytesRead && get64BitUInt(sourceInfo.nFileSizeLow, sourceInfo.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", 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 - setFileTimeByHandle(hFileTarget, &fileInfoSource.ftCreationTime,fileInfoSource.ftLastWriteTime, targetFile); //throw FileError + //time needs to be set at the end: WriteFile/BackupWrite() change modification time + setFileTimeByHandle(fileOut.getHandle(), &sourceInfo.ftCreationTime,sourceInfo.ftLastWriteTime, targetFile); //throw FileError return newAttrib; } -DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyAsBackupStream); +DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyViaBackupRead); +DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyViaReadFile); struct CallbackData @@ -2127,7 +2152,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, #else if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw () #endif - throw ErrorFallbackToCopyAsBackupStream(L"sparse, callback"); //use a different copy routine! + throw ErrorFallbackToCopyViaBackupRead(L"sparse, callback"); //use a different copy routine! //#################### copy file creation time ################################ ::SetFileTime(hDestinationFile, &cbd.fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling! @@ -2170,8 +2195,8 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, } -InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream - const Zstring& targetFile, +InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, + const Zstring& targetFile, // ErrorFallbackToCopyViaReadFile, ErrorFallbackToCopyViaBackupRead const std::function<void(std::int64_t bytesDelta)>& notifyProgress) { //try to get backup read and write privileges: may help solve some "access denied" errors @@ -2205,27 +2230,28 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE nullptr, //__in_opt LPBOOL pbCancel, copyFlags) != FALSE; //__in DWORD dwCopyFlags if (cbd.exception) - std::rethrow_exception(cbd.exception); //throw ?, process errors in callback first! + std::rethrow_exception(cbd.exception); //throw X, process errors in callback first! if (!success) { const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user aborted operation IS an error condition! + //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user-aborted operation IS an error condition! //trying to copy huge sparse files may directly fail with ERROR_DISK_FULL before entering the callback function if (canCopyAsSparse(sourceFile, targetFile)) //noexcept - throw ErrorFallbackToCopyAsBackupStream(L"sparse, copy failure"); + throw ErrorFallbackToCopyViaBackupRead(L"sparse, copy failure"); if (ec == ERROR_ACCESS_DENIED && backupPrivilegesActive) - //chances are good this will work with copyFileWindowsBackupStream: http://www.freefilesync.org/forum/viewtopic.php?t=1714 - throw ErrorFallbackToCopyAsBackupStream(L"access denied"); + //chances are good this will work with copyFileWindowsStream: http://www.freefilesync.org/forum/viewtopic.php?t=1714 + throw ErrorFallbackToCopyViaReadFile(L"access denied"); - //copying ADS may incorrectly fail with ERROR_FILE_NOT_FOUND: http://www.freefilesync.org/forum/viewtopic.php?t=446 + //- copying ADS may incorrectly fail with ERROR_FILE_NOT_FOUND: http://www.freefilesync.org/forum/viewtopic.php?t=446 + //- even BackupWrite may fail => use ReadFile: http://www.freefilesync.org/forum/viewtopic.php?t=2321 if (ec == ERROR_FILE_NOT_FOUND && cbd.fileInfoSrc.nNumberOfLinks > 0 && cbd.fileInfoTrg.nNumberOfLinks > 0) - throw ErrorFallbackToCopyAsBackupStream(L"bogus file not found"); + throw ErrorFallbackToCopyViaReadFile(L"bogus file not found"); //assemble error message... const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtPath(sourceFile)), L"%y", L"\n" + fmtPath(targetFile)); @@ -2288,11 +2314,15 @@ InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Z { try { - return copyFileWindowsDefault(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream + return copyFileWindowsDefault(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyViaReadFile, ErrorFallbackToCopyViaBackupRead + } + catch (ErrorFallbackToCopyViaReadFile&) + { + return copyFileWindowsStream(sourceFile, targetFile, StreamCopyType::READ_FILE, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked } - catch (ErrorFallbackToCopyAsBackupStream&) + catch (ErrorFallbackToCopyViaBackupRead&) { - return copyFileWindowsBackupStream(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked + return copyFileWindowsStream(sourceFile, targetFile, StreamCopyType::BACKUP_READ, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked } } @@ -2325,7 +2355,7 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& notifyProgress) { - FileInput fileIn(sourceFile); //throw FileError + FileInput fileIn(sourceFile); //throw FileError, (ErrorFileLocked -> Windows-only) if (notifyProgress) notifyProgress(0); //throw X! struct ::stat sourceInfo = {}; @@ -2409,16 +2439,16 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError #endif /* - ------------------ - |File Copy Layers| - ------------------ - copyNewFile - | - copyFileOsSpecific (solve 8.3 issue on Windows) - | - copyFileWindowsSelectRoutine - / \ -copyFileWindowsDefault(::CopyFileEx) copyFileWindowsBackupStream(::BackupRead/::BackupWrite) + ------------------ + |File Copy Layers| + ------------------ + copyNewFile + | + copyFileOsSpecific (solve 8.3 issue on Windows) + | + copyFileWindowsSelectRoutine + / \ +copyFileWindowsDefault(::CopyFileEx) copyFileWindowsStream(::BackupRead/::BackupWrite) */ } |