From 9cc790869ed3905c78c7eeeb0bb44f800b3f2af4 Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 17:11:09 +0200 Subject: 3.15 --- shared/file_handling.cpp | 1338 ++++++++++++++++++++++++++-------------------- 1 file changed, 755 insertions(+), 583 deletions(-) (limited to 'shared/file_handling.cpp') diff --git a/shared/file_handling.cpp b/shared/file_handling.cpp index 14a4b84c..97c4d077 100644 --- a/shared/file_handling.cpp +++ b/shared/file_handling.cpp @@ -5,22 +5,21 @@ // ************************************************************************** // #include "file_handling.h" -#include +#include +#include +#include +#include +#include #include "system_func.h" #include "global_func.h" #include "system_constants.h" #include "file_traverser.h" -#include -#include -#include #include "string_conv.h" -#include -#include -#include #include "loki/TypeManip.h" #include "loki/ScopeGuard.h" -#include #include "symlink_target.h" +#include "file_io.h" +#include "i18n.h" #ifdef FFS_WIN #include "privilege.h" @@ -32,7 +31,6 @@ #elif defined FFS_LINUX #include -#include "file_io.h" #include #include #include @@ -44,162 +42,7 @@ #endif using ffs3::FileError; - - -namespace -{ -#ifdef FFS_WIN -Zstring resolveRelativePath(const Zstring& relativeName, DWORD proposedBufferSize = 1000) -{ - boost::scoped_array fullPath(new Zchar[proposedBufferSize]); - const DWORD rv = ::GetFullPathName( - relativeName.c_str(), //__in LPCTSTR lpFileName, - proposedBufferSize, //__in DWORD nBufferLength, - fullPath.get(), //__out LPTSTR lpBuffer, - NULL); //__out LPTSTR *lpFilePart - if (rv == 0 || rv == proposedBufferSize) - //ERROR! Don't do anything - return relativeName; - if (rv > proposedBufferSize) - return resolveRelativePath(relativeName, rv); - - return fullPath.get(); -} - -#elif defined FFS_LINUX -Zstring resolveRelativePath(const Zstring& relativeName) //additional: resolves symbolic links!!! -{ - char absolutePath[PATH_MAX + 1]; - if (::realpath(relativeName.c_str(), absolutePath) == NULL) - //ERROR! Don't do anything - return relativeName; - - return Zstring(absolutePath); -} -#endif - - -bool replaceMacro(wxString& macro) //macro without %-characters, return true if replaced successfully -{ - if (macro.IsEmpty()) - return false; - - //there are equally named environment variables %TIME%, %DATE% existing, so replace these first! - if (macro.CmpNoCase(wxT("time")) == 0) - { - macro = wxDateTime::Now().FormatISOTime(); - macro.Replace(wxT(":"), wxT("")); - return true; - } - - if (macro.CmpNoCase(wxT("date")) == 0) - { - macro = wxDateTime::Now().FormatISODate(); - return true; - } - - if (macro.CmpNoCase(wxT("month")) == 0) - { - macro = wxDateTime::Now().Format(wxT("%B")); - return true; - } - - if (macro.CmpNoCase(wxT("week")) == 0) - { - macro = wxDateTime::Now().Format(wxT("%U")); - return true; - } - - if (macro.CmpNoCase(wxT("year")) == 0) - { - macro = wxDateTime::Now().Format(wxT("%Y")); - return true; - } - - //try to apply environment variables - wxString envValue; - if (wxGetEnv(macro, &envValue)) - { - macro = envValue; - - //some postprocessing: - macro.Trim(true); //remove leading, trailing blanks - macro.Trim(false); // - - //remove leading, trailing double-quotes - if (macro.StartsWith(wxT("\"")) && - macro.EndsWith(wxT("\"")) && - macro.length() >= 2) - macro = wxString(macro.c_str() + 1, macro.length() - 2); - return true; - } - - return false; -} - - -void expandMacros(wxString& text) -{ - const wxChar SEPARATOR = '%'; - - if (text.Find(SEPARATOR) != wxNOT_FOUND) - { - wxString prefix = text.BeforeFirst(SEPARATOR); - wxString postfix = text.AfterFirst(SEPARATOR); - if (postfix.Find(SEPARATOR) != wxNOT_FOUND) - { - wxString potentialMacro = postfix.BeforeFirst(SEPARATOR); - wxString rest = postfix.AfterFirst(SEPARATOR); //text == prefix + SEPARATOR + potentialMacro + SEPARATOR + rest - - if (replaceMacro(potentialMacro)) - { - expandMacros(rest); - text = prefix + potentialMacro + rest; - } - else - { - rest = SEPARATOR + rest; - expandMacros(rest); - text = prefix + SEPARATOR + potentialMacro + rest; - } - } - } -} -} - - -Zstring ffs3::getFormattedDirectoryName(const Zstring& dirname) -{ - //Formatting is needed since functions expect the directory to end with '\' to be able to split the relative names. - //note: don't combine directory formatting with wxFileName, as it doesn't respect //?/ - prefix! - - wxString dirnameTmp = zToWx(dirname); - expandMacros(dirnameTmp); - - Zstring output = wxToZ(dirnameTmp); - - output.Trim(); - - if (output.empty()) //an empty string will later be returned as "\"; this is not desired - return Zstring(); - - /* - resolve relative names; required by: - WINDOWS: - - \\?\-prefix which needs absolute names - - Volume Shadow Copy: volume name needs to be part of each filename - - file icon buffer (at least for extensions that are actually read from disk, e.g. "exe") - - ::SHFileOperation(): Using relative path names is not thread safe - WINDOWS/LINUX: - - detection of dependent directories, e.g. "\" and "C:\test" - */ - output = resolveRelativePath(output); - - if (!output.EndsWith(common::FILE_NAME_SEPARATOR)) - output += common::FILE_NAME_SEPARATOR; - - return output; -} +using namespace ffs3; bool ffs3::fileExists(const Zstring& filename) @@ -275,18 +118,22 @@ wxULongLong getFileSizeSymlink(const Zstring& linkName) //throw (FileError) OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (hFile != INVALID_HANDLE_VALUE) + if (hFile == INVALID_HANDLE_VALUE) { - Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hFile); - (void)dummy; //silence warning "unused variable" + const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + ffs3::zToWx(linkName) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hFile); + (void)dummy; //silence warning "unused variable" - BY_HANDLE_FILE_INFORMATION fileInfoByHandle; - if (::GetFileInformationByHandle(hFile, &fileInfoByHandle)) - return wxULongLong(fileInfoByHandle.nFileSizeHigh, fileInfoByHandle.nFileSizeLow); + BY_HANDLE_FILE_INFORMATION fileInfoByHandle = {}; + if (!::GetFileInformationByHandle(hFile, &fileInfoByHandle)) + { + const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + ffs3::zToWx(linkName) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + ffs3::zToWx(linkName) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + return wxULongLong(fileInfoByHandle.nFileSizeHigh, fileInfoByHandle.nFileSizeLow); } } #endif @@ -331,7 +178,7 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! const size_t bufferSize = std::max(pathName.size(), static_cast(10000)); boost::scoped_array buffer(new wchar_t[bufferSize]); - //pathName need not exist! + //full pathName need not yet exist! if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName, buffer.get(), //__out LPTSTR lpszVolumePathName, static_cast(bufferSize))) //__in DWORD cchBufferLength @@ -364,8 +211,8 @@ dev_t retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! if (volumePathName.size() > 1 && volumePathName.EndsWith(common::FILE_NAME_SEPARATOR)) //exception: allow '/' volumePathName = volumePathName.BeforeLast(common::FILE_NAME_SEPARATOR); - struct stat fileInfo; - while (::lstat(volumePathName.c_str(), &fileInfo) != 0) + struct stat fileInfo = {}; + while (::lstat(volumePathName.c_str(), &fileInfo) != 0) //go up in folder hierarchy until existing folder is found { volumePathName = volumePathName.BeforeLast(common::FILE_NAME_SEPARATOR); //returns empty string if ch not found if (volumePathName.empty()) @@ -396,48 +243,43 @@ ffs3::ResponseSameVol ffs3::onSameVolume(const Zstring& folderLeft, const Zstrin void ffs3::removeFile(const Zstring& filename) //throw (FileError); { - //no error situation if file is not existing! manual deletion relies on it! - if (!somethingExists(filename)) - return; //neither file nor any other object (e.g. broken symlink) with that name existing - #ifdef FFS_WIN - const Zstring filenameFmt = applyLongPathPrefix(filename); - //remove file, support for \\?\-prefix + const Zstring filenameFmt = applyLongPathPrefix(filename); if (!::DeleteFile(filenameFmt.c_str())) +#elif defined FFS_LINUX + if (::unlink(filename.c_str()) != 0) +#endif { - //optimization: change file attributes ONLY when necessary! +#ifdef FFS_WIN + //perf: apply ONLY when necessary! if (::GetLastError() == ERROR_ACCESS_DENIED) //function fails if file is read-only { - //initialize file attributes - if (::SetFileAttributes(filenameFmt.c_str(), //address of filename - FILE_ATTRIBUTE_NORMAL)) //attributes to set - { - //now try again... - if (::DeleteFile(filenameFmt.c_str())) - return; - } + //(try to) normalize file attributes + ::SetFileAttributes(filenameFmt.c_str(), FILE_ATTRIBUTE_NORMAL); + + //now try again... + if (::DeleteFile(filenameFmt.c_str())) + return; } +#endif + //eval error code before next call + const wxString errorMessage = wxString(_("Error deleting file:")) + wxT("\n\"") + zToWx(filename) + wxT("\"") + wxT("\n\n") + ffs3::getLastErrorFormatted(); - wxString errorMessage = wxString(_("Error deleting file:")) + wxT("\n\"") + zToWx(filename) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } -#elif defined FFS_LINUX - if (::unlink(filename.c_str()) != 0) - { - wxString errorMessage = wxString(_("Error deleting file:")) + wxT("\n\"") + zToWx(filename) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + //no error situation if file is not existing! manual deletion relies on it! + //perf: place check in error handling block + //warning: this call changes error code!! + if (!somethingExists(filename)) + return; //neither file nor any other object (e.g. broken symlink) with that name existing + + throw FileError(errorMessage); } -#endif } namespace { -struct ErrorDifferentVolume : public ffs3::FileError -{ - ErrorDifferentVolume(const wxString& message) : FileError(message) {} -}; +DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume); /* Usage overview: @@ -448,7 +290,7 @@ struct ErrorDifferentVolume : public ffs3::FileError */ //wrapper for file system rename function: //throw (FileError); ErrorDifferentVolume if it is due to moving file to another volume -void renameFileInternal(const Zstring& oldName, const Zstring& newName) //throw (FileError, ErrorDifferentVolume) +void renameFileInternal(const Zstring& oldName, const Zstring& newName) //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting) { using namespace ffs3; //for zToWx() @@ -465,8 +307,7 @@ void renameFileInternal(const Zstring& oldName, const Zstring& newName) //throw const DWORD oldNameAttrib = ::GetFileAttributes(oldNameFmt.c_str()); if (oldNameAttrib != INVALID_FILE_ATTRIBUTES) { - if (::SetFileAttributes(oldNameFmt.c_str(), //address of filename - FILE_ATTRIBUTE_NORMAL)) //remove readonly-attribute + if (::SetFileAttributes(oldNameFmt.c_str(), FILE_ATTRIBUTE_NORMAL)) //remove readonly-attribute { //try again... if (::MoveFileEx(oldNameFmt.c_str(), //__in LPCTSTR lpExistingFileName, @@ -474,8 +315,7 @@ void renameFileInternal(const Zstring& oldName, const Zstring& newName) //throw 0)) //__in DWORD dwFlags { //(try to) restore file attributes - ::SetFileAttributes(newNameFmt.c_str(), //don't handle error - oldNameAttrib); + ::SetFileAttributes(newNameFmt.c_str(), oldNameAttrib); //don't handle error return; } else @@ -491,22 +331,27 @@ void renameFileInternal(const Zstring& oldName, const Zstring& newName) //throw } } + const DWORD lastError = ::GetLastError(); const wxString errorMessage = wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(oldName) + wxT("\" ->\n\"") + zToWx(newName) + wxT("\"") + - wxT("\n\n") + ffs3::getLastErrorFormatted(); - if (::GetLastError() == ERROR_NOT_SAME_DEVICE) + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + if (lastError == ERROR_NOT_SAME_DEVICE) throw ErrorDifferentVolume(errorMessage); + else if (lastError == ERROR_FILE_EXISTS) + throw ErrorTargetExisting(errorMessage); else throw FileError(errorMessage); } #elif defined FFS_LINUX - //rename temporary file if (::rename(oldName.c_str(), newName.c_str()) != 0) { + const int lastError = errno; const wxString errorMessage = wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(oldName) + wxT("\" ->\n\"") + zToWx(newName) + wxT("\"") + - wxT("\n\n") + ffs3::getLastErrorFormatted(); - if (errno == EXDEV) + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + if (lastError == EXDEV) throw ErrorDifferentVolume(errorMessage); + else if (lastError == EEXIST) + throw ErrorTargetExisting(errorMessage); else throw FileError(errorMessage); } @@ -572,11 +417,11 @@ bool fix8Dot3NameClash(const Zstring& oldName, const Zstring& newName) //throw { const Zstring fileNameOrig = newName.AfterLast(common::FILE_NAME_SEPARATOR); //returns the whole string if ch not found const Zstring fileNameShort = getFilenameFmt(newName, ::GetShortPathName).AfterLast(common::FILE_NAME_SEPARATOR); //throw() returns empty string on error - const Zstring fileNameLong = getFilenameFmt(newName, ::GetLongPathName).AfterLast(common::FILE_NAME_SEPARATOR); //throw() returns empty string on error + const Zstring fileNameLong = getFilenameFmt(newName, ::GetLongPathName) .AfterLast(common::FILE_NAME_SEPARATOR); //throw() returns empty string on error if (!fileNameShort.empty() && !fileNameLong.empty() && - EqualFilename()(fileNameOrig, fileNameShort) && + EqualFilename() (fileNameOrig, fileNameShort) && !EqualFilename()(fileNameShort, fileNameLong)) { //we detected an event where newName is in shortname format (although it is intended to be a long name) and @@ -589,7 +434,7 @@ bool fix8Dot3NameClash(const Zstring& oldName, const Zstring& newName) //throw const Zstring parkedTarget = createTemp8Dot3Name(newName); //move already existing short name out of the way for now - renameFileInternal(unrelatedPathLong, parkedTarget); //throw (FileError, ErrorDifferentVolume); + renameFileInternal(unrelatedPathLong, parkedTarget); //throw (FileError: ErrorDifferentVolume); //DON'T call ffs3::renameFile() to avoid reentrance! //schedule cleanup; the file system should assign this unrelated file a new (unique) short name @@ -607,11 +452,11 @@ bool fix8Dot3NameClash(const Zstring& oldName, const Zstring& newName) //throw //rename file: no copying!!! -void ffs3::renameFile(const Zstring& oldName, const Zstring& newName) //throw (FileError, ErrorDifferentVolume); +void ffs3::renameFile(const Zstring& oldName, const Zstring& newName) //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting); { try { - renameFileInternal(oldName, newName); //throw (FileError, ErrorDifferentVolume) + renameFileInternal(oldName, newName); //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting) } catch (const FileError&) { @@ -624,80 +469,77 @@ void ffs3::renameFile(const Zstring& oldName, const Zstring& newName) //throw (F } -using ffs3::MoveFileCallback; +using ffs3::CallbackMoveFile; -class CopyCallbackImpl : public ffs3::CopyFileCallback //callback functionality +class CopyCallbackImpl : public ffs3::CallbackCopyFile //callback functionality { public: - CopyCallbackImpl(const Zstring& sourceFile, MoveFileCallback& callback) : sourceFile_(sourceFile), moveCallback(callback) {} + CopyCallbackImpl(const Zstring& sourceFile, CallbackMoveFile& callback) : sourceFile_(sourceFile), moveCallback(callback) {} + + virtual void deleteTargetFile(const Zstring& targetFile) { assert(!fileExists(targetFile)); } virtual Response updateCopyStatus(const wxULongLong& totalBytesTransferred) { switch (moveCallback.requestUiRefresh(sourceFile_)) { - case MoveFileCallback::CONTINUE: - return CopyFileCallback::CONTINUE; + case CallbackMoveFile::CONTINUE: + return CallbackCopyFile::CONTINUE; - case MoveFileCallback::CANCEL: - return CopyFileCallback::CANCEL; + case CallbackMoveFile::CANCEL: + return CallbackCopyFile::CANCEL; } - return CopyFileCallback::CONTINUE; //dummy return value + return CallbackCopyFile::CONTINUE; //dummy return value } private: const Zstring sourceFile_; - MoveFileCallback& moveCallback; + CallbackMoveFile& moveCallback; }; -void ffs3::moveFile(const Zstring& sourceFile, const Zstring& targetFile, MoveFileCallback* callback) //throw (FileError); +void ffs3::moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback) //throw (FileError); { //call back once per file (moveFile() is called by moveDirectory()) if (callback) switch (callback->requestUiRefresh(sourceFile)) { - case MoveFileCallback::CONTINUE: + case CallbackMoveFile::CONTINUE: break; - case MoveFileCallback::CANCEL: //a user aborted operation IS an error condition! + case CallbackMoveFile::CANCEL: //a user aborted operation IS an error condition! throw FileError(wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + zToWx(targetFile) + wxT("\"") + wxT("\n\n") + _("Operation aborted!")); } - //support case-sensitive renaming - if (EqualFilename()(sourceFile, targetFile)) //difference in case only - return renameFile(sourceFile, targetFile); //throw (FileError, ErrorDifferentVolume); + const bool targetExisting = fileExists(targetFile); - if (somethingExists(targetFile)) //test file existence: e.g. Linux might silently overwrite existing symlinks + if (targetExisting && !ignoreExisting) //test file existence: e.g. Linux might silently overwrite existing symlinks throw FileError(wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + zToWx(targetFile) + wxT("\"") + wxT("\n\n") + _("Target file already existing!")); - //moving of symbolic links should work correctly: - - //first try to move the file directly without copying - try + if (!targetExisting) { - renameFile(sourceFile, targetFile); //throw (FileError, ErrorDifferentVolume); - return; - } - //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file) - catch (const ErrorDifferentVolume&) {} - + //try to move the file directly without copying + try + { + renameFile(sourceFile, targetFile); //throw (FileError: ErrorDifferentVolume); + return; //great, we get away cheaply! + } + //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file) + catch (const ErrorDifferentVolume&) {} - //file is on a different volume: let's copy it - std::auto_ptr copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, *callback) : NULL); + //file is on a different volume: let's copy it + std::auto_ptr copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, *callback) : NULL); - copyFile(sourceFile, - targetFile, - true, //copy symbolic links - false, //dont copy filesystem permissions -#ifdef FFS_WIN - NULL, //supply handler for making shadow copies -#endif - copyCallback.get()); //throw (FileError); + if (symlinkExists(sourceFile)) + copySymlink(sourceFile, targetFile, SYMLINK_TYPE_FILE, false); //throw (FileError) dont copy filesystem permissions + else + copyFile(sourceFile, targetFile, false, copyCallback.get()); //throw (FileError); - //attention: if copy-operation was cancelled an exception is thrown => sourcefile is not deleted, as we wish! + //attention: if copy-operation was cancelled an exception is thrown => sourcefile is not deleted, as we wish! + } - removeFile(sourceFile); + removeFile(sourceFile); //throw (FileError) + //note: copying file is NOT undone in case of exception: currently this function is called in context of user-defined deletion dir, where this behavior is fine } namespace @@ -705,45 +547,48 @@ namespace class TraverseOneLevel : public ffs3::TraverseCallback { public: - typedef std::vector > NamePair; + typedef std::pair NamePair; + typedef std::vector NameList; - TraverseOneLevel(NamePair& filesShort, NamePair& dirsShort) : - m_files(filesShort), - m_dirs(dirsShort) {} + TraverseOneLevel(NameList& files, NameList& dirs) : + files_(files), + dirs_(dirs) {} virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) { - m_files.push_back(std::make_pair(Zstring(shortName), fullName)); + files_.push_back(NamePair(shortName, fullName)); } + virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) { if (details.dirLink) - m_dirs.push_back(std::make_pair(Zstring(shortName), fullName)); + dirs_.push_back(NamePair(shortName, fullName)); else - m_files.push_back(std::make_pair(Zstring(shortName), fullName)); + files_.push_back(NamePair(shortName, fullName)); } virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName) { - m_dirs.push_back(std::make_pair(Zstring(shortName), fullName)); + dirs_.push_back(NamePair(shortName, fullName)); return Loki::Int2Type(); //DON'T traverse into subdirs; moveDirectory works recursively! } + virtual void onError(const wxString& errorText) { throw FileError(errorText); } private: - NamePair& m_files; - NamePair& m_dirs; + NameList& files_; + NameList& dirs_; }; -struct RemoveCallbackImpl : public ffs3::RemoveDirCallback +struct RemoveCallbackImpl : public ffs3::CallbackRemoveDir { RemoveCallbackImpl(const Zstring& sourceDir, const Zstring& targetDir, - MoveFileCallback& moveCallback) : + CallbackMoveFile& moveCallback) : sourceDir_(sourceDir), targetDir_(targetDir), moveCallback_(moveCallback) {} @@ -752,9 +597,9 @@ struct RemoveCallbackImpl : public ffs3::RemoveDirCallback { switch (moveCallback_.requestUiRefresh(sourceDir_)) { - case MoveFileCallback::CONTINUE: + case CallbackMoveFile::CONTINUE: break; - case MoveFileCallback::CANCEL: //a user aborted operation IS an error condition! + case CallbackMoveFile::CANCEL: //a user aborted operation IS an error condition! throw ffs3::FileError(wxString(_("Error moving directory:")) + wxT("\n\"") + ffs3::zToWx(sourceDir_) + wxT("\" ->\n\"") + ffs3::zToWx(targetDir_) + wxT("\"") + wxT("\n\n") + _("Operation aborted!")); } @@ -763,12 +608,12 @@ struct RemoveCallbackImpl : public ffs3::RemoveDirCallback private: const Zstring sourceDir_; const Zstring targetDir_; - MoveFileCallback& moveCallback_; + CallbackMoveFile& moveCallback_; }; } -void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExistingDirs, MoveFileCallback* callback) //throw (FileError); +void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw (FileError); { using namespace ffs3; @@ -776,63 +621,63 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool if (callback) switch (callback->requestUiRefresh(sourceDir)) { - case MoveFileCallback::CONTINUE: + case CallbackMoveFile::CONTINUE: break; - case MoveFileCallback::CANCEL: //a user aborted operation IS an error condition! + case CallbackMoveFile::CANCEL: //a user aborted operation IS an error condition! throw FileError(wxString(_("Error moving directory:")) + wxT("\n\"") + zToWx(sourceDir) + wxT("\" ->\n\"") + zToWx(targetDir) + wxT("\"") + wxT("\n\n") + _("Operation aborted!")); } - //handle symbolic links - if (symlinkExists(sourceDir)) - { - createDirectory(targetDir, sourceDir, true, false); //copy symbolic link, don't copy permissions - removeDirectory(sourceDir, NULL); //if target is already another symlink or directory, sourceDir-symlink is silently deleted - return; - } + const bool targetExisting = dirExists(targetDir); - if (somethingExists(targetDir)) - { - if (!ignoreExistingDirs) //directory or symlink exists (or even a file... this error will be caught later) - throw FileError(wxString(_("Error moving directory:")) + wxT("\n\"") + zToWx(sourceDir) + wxT("\" ->\n\"") + zToWx(targetDir) + wxT("\"") + - wxT("\n\n") + _("Target directory already existing!")); - } - else + if (targetExisting && !ignoreExisting) //directory or symlink exists (or even a file... this error will be caught later) + throw FileError(wxString(_("Error moving directory:")) + wxT("\n\"") + zToWx(sourceDir) + wxT("\" ->\n\"") + zToWx(targetDir) + wxT("\"") + + wxT("\n\n") + _("Target directory already existing!")); + + const bool isSymlink = symlinkExists(sourceDir); + + if (!targetExisting) { //first try to move the directory directly without copying try { - renameFile(sourceDir, targetDir); //throw (FileError, ErrorDifferentVolume); - return; + renameFile(sourceDir, targetDir); //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting); + return; //great, we get away cheaply! } //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the directory) catch (const ErrorDifferentVolume&) {} //create target - createDirectory(targetDir, sourceDir, false, false); //throw (FileError); don't copy permissions + if (isSymlink) + copySymlink(sourceDir, targetDir, SYMLINK_TYPE_DIR, false); //throw (FileError) -> don't copy permissions + else + createDirectory(targetDir, sourceDir, false); //throw (FileError) } - //move files/folders recursively - TraverseOneLevel::NamePair fileList; //list of names: 1. short 2.long - TraverseOneLevel::NamePair dirList; // + if (!isSymlink) //handle symbolic links + { + //move files/folders recursively + TraverseOneLevel::NameList fileList; //list of names: 1. short 2.long + TraverseOneLevel::NameList dirList; // - //traverse source directory one level - TraverseOneLevel traverseCallback(fileList, dirList); - traverseFolder(sourceDir, false, traverseCallback); //traverse one level, don't follow symlinks + //traverse source directory one level + TraverseOneLevel traverseCallback(fileList, dirList); + traverseFolder(sourceDir, false, traverseCallback); //traverse one level, don't follow symlinks - const Zstring targetDirFormatted = targetDir.EndsWith(common::FILE_NAME_SEPARATOR) ? //ends with path separator - targetDir : - targetDir + common::FILE_NAME_SEPARATOR; + const Zstring targetDirFormatted = targetDir.EndsWith(common::FILE_NAME_SEPARATOR) ? //ends with path separator + targetDir : + targetDir + common::FILE_NAME_SEPARATOR; - //move files - for (TraverseOneLevel::NamePair::const_iterator i = fileList.begin(); i != fileList.end(); ++i) - ffs3::moveFile(i->second, targetDirFormatted + i->first, callback); + //move files + for (TraverseOneLevel::NameList::const_iterator i = fileList.begin(); i != fileList.end(); ++i) + ffs3::moveFile(i->second, targetDirFormatted + i->first, ignoreExisting, callback); //throw (FileError: ErrorTargetExisting); - //move directories - for (TraverseOneLevel::NamePair::const_iterator i = dirList.begin(); i != dirList.end(); ++i) - ::moveDirectoryImpl(i->second, targetDirFormatted + i->first, true, callback); + //move directories + for (TraverseOneLevel::NameList::const_iterator i = dirList.begin(); i != dirList.end(); ++i) + ::moveDirectoryImpl(i->second, targetDirFormatted + i->first, ignoreExisting, callback); - //attention: if move-operation was cancelled an exception is thrown => sourceDir is not deleted, as we wish! + //attention: if move-operation was cancelled an exception is thrown => sourceDir is not deleted, as we wish! + } //delete source std::auto_ptr removeCallback(callback != NULL ? new RemoveCallbackImpl(sourceDir, targetDir, *callback) : NULL); @@ -840,7 +685,7 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool } -void ffs3::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExistingDirs, MoveFileCallback* callback) //throw (FileError); +void ffs3::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw (FileError); { #ifdef FFS_WIN const Zstring& sourceDirFormatted = sourceDir; @@ -857,7 +702,7 @@ void ffs3::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, boo targetDir; #endif - ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, ignoreExistingDirs, callback); + ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, ignoreExisting, callback); } @@ -895,7 +740,7 @@ private: }; -void ffs3::removeDirectory(const Zstring& directory, RemoveDirCallback* callback) +void ffs3::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback) { //no error situation if directory is not existing! manual deletion relies on it! if (!somethingExists(directory)) @@ -904,17 +749,10 @@ void ffs3::removeDirectory(const Zstring& directory, RemoveDirCallback* callback #ifdef FFS_WIN const Zstring directoryFmt = applyLongPathPrefix(directory); //support for \\?\-prefix - //initialize file attributes - if (!::SetFileAttributes( // initialize file attributes: actually NEEDED for symbolic links also! - directoryFmt.c_str(), // address of directory name - FILE_ATTRIBUTE_NORMAL)) // attributes to set - { - wxString errorMessage = wxString(_("Error deleting directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } + //(try to) normalize file attributes: actually NEEDED for symbolic links also! + ::SetFileAttributes(directoryFmt.c_str(), FILE_ATTRIBUTE_NORMAL); #endif - //attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!! if (symlinkExists(directory)) //remove symlink directly { @@ -970,7 +808,6 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo { #ifdef FFS_WIN FILETIME creationTime = {}; - FILETIME lastAccessTime = {}; FILETIME lastWriteTime = {}; { @@ -1006,7 +843,7 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo if (!::GetFileTime(hSource, //__in HANDLE hFile, &creationTime, //__out_opt LPFILETIME lpCreationTime, - &lastAccessTime, //__out_opt LPFILETIME lpLastAccessTime, + NULL, //__out_opt LPFILETIME lpLastAccessTime, &lastWriteTime)) //__out_opt LPFILETIME lpLastWriteTime { const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceObj) + wxT("\""); @@ -1016,7 +853,6 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo else { creationTime = sourceAttr.ftCreationTime; - lastAccessTime = sourceAttr.ftLastAccessTime; lastWriteTime = sourceAttr.ftLastWriteTime; } @@ -1065,7 +901,7 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo if (!::SetFileTime(hTarget, &creationTime, - &lastAccessTime, + NULL, &lastWriteTime)) { wxString errorMessage = wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(targetObj) + wxT("\""); @@ -1088,14 +924,14 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo #elif defined FFS_LINUX if (deRefSymlinks) { - struct stat objInfo; + struct stat objInfo = {}; if (::stat(sourceObj.c_str(), &objInfo) != 0) //read file attributes from source directory { const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceObj) + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - struct utimbuf newTimes; + struct utimbuf newTimes = {}; newTimes.actime = objInfo.st_atime; newTimes.modtime = objInfo.st_mtime; @@ -1108,14 +944,14 @@ void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, boo } else { - struct stat objInfo; + struct stat objInfo = {}; if (::lstat(sourceObj.c_str(), &objInfo) != 0) //read file attributes from source directory { const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceObj) + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - struct timeval newTimes[2]; + struct timeval newTimes[2] = {}; newTimes[0].tv_sec = objInfo.st_atime; /* seconds */ newTimes[0].tv_usec = 0; /* microseconds */ @@ -1207,75 +1043,10 @@ Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target pa } #endif -enum SymlinkType -{ - SYMLINK_TYPE_FILE, - SYMLINK_TYPE_DIR -}; -void copySymlinkInternal(const Zstring& sourceLink, const Zstring& targetLink, SymlinkType type, bool copyFilePermissions) //throw (FileError) -{ - using namespace ffs3; - -#ifdef FFS_WIN - const Zstring linkPath = getSymlinkRawTargetString(sourceLink); //accept broken symlinks; throw (FileError) - - //dynamically load windows API function - typedef BOOLEAN (WINAPI *CreateSymbolicLinkFunc)( - LPCTSTR lpSymlinkFileName, - LPCTSTR lpTargetFileName, - DWORD dwFlags); - static const CreateSymbolicLinkFunc createSymbolicLink = util::getDllFun(L"kernel32.dll", "CreateSymbolicLinkW"); - if (createSymbolicLink == NULL) - throw FileError(wxString(_("Error loading library function:")) + wxT("\n\"") + wxT("CreateSymbolicLinkW") + wxT("\"")); - - if (!createSymbolicLink( //seems no long path prefix is required... - targetLink.c_str(), //__in LPTSTR lpSymlinkFileName, - linkPath.c_str(), //__in LPTSTR lpTargetFileName, - (type == SYMLINK_TYPE_DIR ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0))) //__in DWORD dwFlags - { - const wxString errorMessage = wxString(_("Error copying symbolic link:")) + wxT("\n\"") + zToWx(sourceLink) + wxT("\" ->\n\"") + zToWx(targetLink) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } - -#elif defined FFS_LINUX - //copy symbolic link - const int BUFFER_SIZE = 10000; - char buffer[BUFFER_SIZE]; - const int bytesWritten = ::readlink(sourceLink.c_str(), buffer, BUFFER_SIZE); - if (bytesWritten < 0 || bytesWritten == BUFFER_SIZE) - { - wxString errorMessage = wxString(_("Error resolving symbolic link:")) + wxT("\n\"") + zToWx(sourceLink) + wxT("\""); - if (bytesWritten < 0) errorMessage += wxString(wxT("\n\n")) + ffs3::getLastErrorFormatted(); - throw FileError(errorMessage); - } - //set null-terminating char - buffer[bytesWritten] = 0; - - if (::symlink(buffer, targetLink.c_str()) != 0) - { - const wxString errorMessage = wxString(_("Error copying symbolic link:")) + wxT("\n\"") + zToWx(sourceLink) + wxT("\" ->\n\"") + zToWx(targetLink) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } -#endif - - //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist - Loki::ScopeGuard guardNewDir = type == SYMLINK_TYPE_DIR ? - Loki::MakeGuard(&TryCleanUp::tryDeleteDir, targetLink) : - Loki::MakeGuard(&TryCleanUp::tryDeleteFile, targetLink); - - copyFileTimes(sourceLink, targetLink, false); //throw (FileError) - - if (copyFilePermissions) - copyObjectPermissions(sourceLink, targetLink, false); //throw FileError() - - guardNewDir.Dismiss(); //target has been created successfully! -} -} - #ifdef HAVE_SELINUX //copy SELinux security context -void copySecurityContext(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw FileError() +void copySecurityContext(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw (FileError) { using ffs3::zToWx; @@ -1329,7 +1100,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, bool dere //copy permissions for files, directories or symbolic links: requires admin rights -void ffs3::copyObjectPermissions(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw FileError(); +void copyObjectPermissions(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw (FileError); { #ifdef FFS_WIN //setting privileges requires admin rights! @@ -1431,13 +1202,13 @@ void ffs3::copyObjectPermissions(const Zstring& source, const Zstring& target, b #elif defined FFS_LINUX #ifdef HAVE_SELINUX //copy SELinux security context - copySecurityContext(source, target, derefSymlinks); //throw FileError() + copySecurityContext(source, target, derefSymlinks); //throw (FileError) #endif if (derefSymlinks) { struct stat fileInfo; - if (::stat(source.c_str(), &fileInfo) != 0 || + if (::stat (source.c_str(), &fileInfo) != 0 || ::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights! ::chmod(target.c_str(), fileInfo.st_mode) != 0) { @@ -1448,7 +1219,7 @@ void ffs3::copyObjectPermissions(const Zstring& source, const Zstring& target, b else { struct stat fileInfo; - if (::lstat(source.c_str(), &fileInfo) != 0 || + if (::lstat (source.c_str(), &fileInfo) != 0 || ::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights! (!symlinkExists(target) && ::chmod(target.c_str(), fileInfo.st_mode) != 0)) //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod() { @@ -1460,7 +1231,7 @@ void ffs3::copyObjectPermissions(const Zstring& source, const Zstring& target, b } -void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyDirectorySymLinks, bool copyFilePermissions, int level) +void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level) { using namespace ffs3; @@ -1476,108 +1247,110 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat { //call function recursively const Zstring templateParent = templateDir.BeforeLast(common::FILE_NAME_SEPARATOR); - createDirectoryRecursively(dirParent, templateParent, false, copyFilePermissions, level + 1); //don't create symbolic links in recursion! + createDirectoryRecursively(dirParent, templateParent, copyFilePermissions, level + 1); } //now creation should be possible - if (templateDir.empty() || !somethingExists(templateDir)) - { - //default directory creation + //default directory creation #ifdef FFS_WIN - if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), NULL))// pointer to a directory path string + //don't use ::CreateDirectoryEx: + //- 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(), NULL)) #elif defined FFS_LINUX - if (::mkdir(directory.c_str(), 0755) != 0) + if (::mkdir(directory.c_str(), 0755) != 0) #endif - { - if (level != 0) return; - wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } + { + if (level != 0) return; + wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - else + + if (!templateDir.empty()) { #ifdef FFS_WIN - //symbolic link handling - if (symlinkExists(templateDir)) + //try to copy file attributes + Zstring sourcePath; + + if (symlinkExists(templateDir)) //dereference symlink! { - if (copyDirectorySymLinks) - return copySymlinkInternal(templateDir, directory, SYMLINK_TYPE_DIR, copyFilePermissions); //throw (FileError) - else //create directory based on target of symbolic link + try { //get target directory of symbolic link - Zstring linkPath; - try - { - linkPath = resolveDirectorySymlink(templateDir); //throw (FileError) - } - catch (FileError&) - { - //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error... - if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string - NULL)) - { - if (level != 0) return; - const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } - return; - } - - if (!::CreateDirectoryEx( // this function automatically copies symbolic links if encountered - applyLongPathPrefix(linkPath).c_str(), // pointer to path string of template directory - applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string - NULL)) - { - if (level != 0) return; - const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); - } + sourcePath = resolveDirectorySymlink(templateDir); //throw (FileError) } + catch (FileError&) {} //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error... } else //no symbolic link + sourcePath = templateDir; + + //try to copy file attributes + if (!sourcePath.empty()) { - //automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)! - //also it isn't able to copy most junctions because of missing permissions (although target path can be retrieved!) - if (!::CreateDirectoryEx( - applyLongPathPrefix(templateDir).c_str(), // pointer to path string of template directory - applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string - NULL)) + const DWORD sourceAttr = ::GetFileAttributes(applyLongPathPrefix(sourcePath).c_str()); + if (sourceAttr != INVALID_FILE_ATTRIBUTES) { - if (level != 0) return; - const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + const bool isCompressed = (sourceAttr & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool isEncrypted = (sourceAttr & FILE_ATTRIBUTE_ENCRYPTED) != 0; + + ::SetFileAttributes(applyLongPathPrefix(directory), sourceAttr); + + if (isEncrypted) + ::EncryptFile(directory.c_str()); //seems no long path is required (check passed!) + + if (isCompressed) + { + HANDLE hDir = ::CreateFile(applyLongPathPrefix(directory).c_str(), + GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hDir != INVALID_HANDLE_VALUE) + { + Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hDir); + (void)dummy; //silence warning "unused variable" + + USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; + + DWORD bytesReturned = 0; + ::DeviceIoControl(hDir, //handle to file or directory + FSCTL_SET_COMPRESSION, //dwIoControlCode + &cmpState, //input buffer + sizeof(cmpState), //size of input buffer + NULL, //lpOutBuffer + 0, //OutBufferSize + &bytesReturned, //number of bytes returned + NULL); //OVERLAPPED structure + } + } } } +#endif -#elif defined FFS_LINUX - //symbolic link handling - if (copyDirectorySymLinks && - symlinkExists(templateDir)) - //there is no directory-type symlink in Linux! => just copy as file - return copySymlinkInternal(templateDir, directory, SYMLINK_TYPE_DIR, copyFilePermissions); //throw (FileError) - - //default directory creation - if (::mkdir(directory.c_str(), 0755) != 0) + //try to copy file times: NOT mission critical for a directory + try { - if (level != 0) return; - wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + copyFileTimes(templateDir, directory, true); //throw (FileError) } -#endif - Loki::ScopeGuard guardNewDir = Loki::MakeGuard(&TryCleanUp::tryDeleteDir, directory); //ensure cleanup: + catch (FileError&) {} - copyFileTimes(templateDir, directory, !copyDirectorySymLinks); //throw (FileError) + Loki::ScopeGuard guardNewDir = Loki::MakeGuard(&TryCleanUp::tryDeleteDir, directory); //ensure cleanup: + //enforce copying file permissions: it's advertized on GUI... if (copyFilePermissions) - copyObjectPermissions(templateDir, directory, !copyDirectorySymLinks); //throw FileError() + copyObjectPermissions(templateDir, directory, true); //throw (FileError) guardNewDir.Dismiss(); //target has been created successfully! } } +} -void ffs3::createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyDirectorySymLinks, bool copyFilePermissions) +void ffs3::createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions) { //remove trailing separator const Zstring dirFormatted = directory.EndsWith(common::FILE_NAME_SEPARATOR) ? @@ -1588,13 +1361,56 @@ void ffs3::createDirectory(const Zstring& directory, const Zstring& templateDir, templateDir.BeforeLast(common::FILE_NAME_SEPARATOR) : templateDir; - createDirectoryRecursively(dirFormatted, templateFormatted, copyDirectorySymLinks, copyFilePermissions, 0); + createDirectoryRecursively(dirFormatted, templateFormatted, copyFilePermissions, 0); } void ffs3::createDirectory(const Zstring& directory) { - ffs3::createDirectory(directory, Zstring(), false, false); + ffs3::createDirectory(directory, Zstring(), false); +} + + +void ffs3::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, ffs3::SymlinkType type, bool copyFilePermissions) //throw (FileError) +{ + const Zstring linkPath = getSymlinkRawTargetString(sourceLink); //accept broken symlinks; throw (FileError) + +#ifdef FFS_WIN + //dynamically load windows API function + typedef BOOLEAN (WINAPI *CreateSymbolicLinkFunc)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags); + static const CreateSymbolicLinkFunc createSymbolicLink = util::getDllFun(L"kernel32.dll", "CreateSymbolicLinkW"); + if (createSymbolicLink == NULL) + throw FileError(wxString(_("Error loading library function:")) + wxT("\n\"") + wxT("CreateSymbolicLinkW") + wxT("\"")); + + if (!createSymbolicLink( //seems no long path prefix is required... + targetLink.c_str(), //__in LPTSTR lpSymlinkFileName, + linkPath.c_str(), //__in LPTSTR lpTargetFileName, + (type == SYMLINK_TYPE_DIR ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0))) //__in DWORD dwFlags + { + const wxString errorMessage = wxString(_("Error copying symbolic link:")) + wxT("\n\"") + zToWx(sourceLink) + wxT("\" ->\n\"") + zToWx(targetLink) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + +#elif defined FFS_LINUX + if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0) + { + const wxString errorMessage = wxString(_("Error copying symbolic link:")) + wxT("\n\"") + zToWx(sourceLink) + wxT("\" ->\n\"") + zToWx(targetLink) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } +#endif + + //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist + Loki::ScopeGuard guardNewDir = type == SYMLINK_TYPE_DIR ? + Loki::MakeGuard(&TryCleanUp::tryDeleteDir, targetLink) : + Loki::MakeGuard(&TryCleanUp::tryDeleteFile, targetLink); + + //file times: essential for a symlink: enforce this! (don't just try!) + copyFileTimes(sourceLink, targetLink, false); //throw (FileError) + + if (copyFilePermissions) + copyObjectPermissions(sourceLink, targetLink, false); //throw (FileError) + + guardNewDir.Dismiss(); //target has been created successfully! } @@ -1610,15 +1426,8 @@ Zstring createTempName(const Zstring& filename) return output; } -} #ifdef FFS_WIN -namespace -{ -#ifndef COPY_FILE_COPY_SYMLINK -#define COPY_FILE_COPY_SYMLINK 0x00000800 -#endif - DWORD CALLBACK copyCallbackInternal( LARGE_INTEGER totalFileSize, LARGE_INTEGER totalBytesTransferred, @@ -1630,11 +1439,11 @@ DWORD CALLBACK copyCallbackInternal( HANDLE hDestinationFile, LPVOID lpData) { - using ffs3::CopyFileCallback; + using ffs3::CallbackCopyFile; //small performance optimization: it seems this callback function is called for every 64 kB (depending on cluster size). static size_t callNr = 0; - if (++callNr % 4 == 0) //executing callback for each 256 kB should suffice + if (++callNr % 4 == 0) //executing callback every 256 kB should suffice { if (lpData != NULL) { @@ -1646,14 +1455,14 @@ DWORD CALLBACK copyCallbackInternal( This will then be handled in future versions of FreeFileSync.\n\nThanks -ZenJu"), NULL, 0); - CopyFileCallback* callback = static_cast(lpData); + CallbackCopyFile* callback = static_cast(lpData); try { switch (callback->updateCopyStatus(wxULongLong(totalBytesTransferred.HighPart, totalBytesTransferred.LowPart))) { - case CopyFileCallback::CONTINUE: + case CallbackCopyFile::CONTINUE: break; - case CopyFileCallback::CANCEL: + case CallbackCopyFile::CANCEL: return PROGRESS_CANCEL; } } @@ -1668,6 +1477,10 @@ DWORD CALLBACK copyCallbackInternal( } +#ifndef COPY_FILE_COPY_SYMLINK +#define COPY_FILE_COPY_SYMLINK 0x00000800 +#endif + bool supportForSymbolicLinks() { OSVERSIONINFO osvi = {}; @@ -1685,11 +1498,9 @@ bool supportForSymbolicLinks() #define COPY_FILE_ALLOW_DECRYPTED_DESTINATION 0x00000008 #endif - bool supportForNonEncryptedDestination() { - OSVERSIONINFO osvi; - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + OSVERSIONINFO osvi = {}; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); //encrypted destination is not supported with Windows 2000 @@ -1699,39 +1510,28 @@ bool supportForNonEncryptedDestination() //overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx return false; } -} -void ffs3::copyFile(const Zstring& sourceFile, - const Zstring& targetFile, - bool copyFileSymLinks, - bool copyFilePermissions, - shadow::ShadowCopy* shadowCopyHandler, - ffs3::CopyFileCallback* callback) +void rawCopyWinApi(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) { - //ffs3::fileExists(targetFile) -> avoid this call, performance; - //if target exists (very unlikely, because sync-algorithm deletes it) renaming below will fail! + Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: guard just before starting copy! DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS; - //copy symbolic links instead of the files pointed at - static const bool symlinksSupported = supportForSymbolicLinks(); //only set "true" if supported by OS: else copying in Windows XP fails - if (copyFileSymLinks && symlinksSupported) - copyFlags |= COPY_FILE_COPY_SYMLINK; + // static const bool symlinksSupported = supportForSymbolicLinks(); //only set "true" if supported by OS: else copying in Windows XP fails + // if (copyFileSymLinks && symlinksSupported) + // copyFlags |= COPY_FILE_COPY_SYMLINK; //allow copying from encrypted to non-encrytped location static const bool nonEncSupported = supportForNonEncryptedDestination(); if (nonEncSupported) copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; - const Zstring temporary = createTempName(targetFile); //use temporary file until a correct date has been set - - Loki::ScopeGuard guardTempFile = Loki::MakeGuard(&ffs3::removeFile, temporary); - - - if (!::CopyFileEx( //same performance as CopyFile() + if (!::CopyFileEx( //same performance like CopyFile() applyLongPathPrefix(sourceFile).c_str(), - applyLongPathPrefix(temporary).c_str(), + applyLongPathPrefix(targetFile).c_str(), callback != NULL ? copyCallbackInternal : NULL, callback, NULL, @@ -1741,44 +1541,30 @@ void ffs3::copyFile(const Zstring& sourceFile, //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user aborted operation IS an error condition! - //if file is locked (try to) use Windows Volume Shadow Copy Service - if (shadowCopyHandler != NULL && - (lastError == ERROR_SHARING_VIOLATION || - lastError == ERROR_LOCK_VIOLATION)) - { - //shadowFilename already contains prefix: E.g. "\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\Program Files\FFS\sample.dat" + //assemble error message... + wxString errorMessage = wxString(_("Error copying file:")) + wxT("\n\"") + sourceFile.c_str() + wxT("\" ->\n\"") + targetFile.c_str() + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); - Zstring shadowFilename; - try - { - shadowFilename = shadowCopyHandler->makeShadowCopy(sourceFile); //throw (FileError) - } - catch (const FileError& e) - { - wxString errorMsg = _("Error copying locked file %x!"); - errorMsg.Replace(wxT("%x"), wxString(wxT("\"")) + zToWx(sourceFile) + wxT("\"")); - errorMsg += wxT("\n\n") + e.msg(); - throw FileError(errorMsg); - } + //if file is locked (try to) use Windows Volume Shadow Copy Service + if (lastError == ERROR_SHARING_VIOLATION || + lastError == ERROR_LOCK_VIOLATION) + throw ErrorFileLocked(errorMessage); - return copyFile(shadowFilename, //transferred bytes is automatically reset when new file is copied - targetFile, - copyFileSymLinks, - copyFilePermissions, - NULL, - callback); + if (lastError == ERROR_FILE_EXISTS) //if target is existing this functions is expected to throw ErrorTargetExisting!!! + { + guardTarget.Dismiss(); //don't delete file that existed previously! + throw ErrorTargetExisting(errorMessage); } - //assemble error message... - wxString errorMessage = wxString(_("Error copying file:")) + wxT("\n\"") + sourceFile.c_str() + wxT("\" ->\n\"") + targetFile.c_str() + wxT("\"") + - wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(errorMessage); try //add more meaningful message { //trying to copy > 4GB file to FAT/FAT32 volume gives obscure ERROR_INVALID_PARAMETER (FAT can indeed handle files up to 4 Gig, tested!) if (lastError == ERROR_INVALID_PARAMETER && dst::isFatDrive(targetFile) && - getFilesize(sourceFile) >= (wxULongLong(1024 * 1024 * 1024)*=4)) //throw (FileError) + getFilesize(sourceFile) >= wxULongLong(1024 * 1024 * 1024) * 4) //throw (FileError) errorMessage += wxT("\nFAT volume cannot store file larger than 4 gigabyte!"); } catch(...) {} @@ -1786,69 +1572,283 @@ void ffs3::copyFile(const Zstring& sourceFile, throw FileError(errorMessage); } - //rename temporary file: do not add anything else here (note specific error handing) - ffs3::renameFile(temporary, targetFile); - - guardTempFile.Dismiss(); //no need to delete temp file anymore - - Loki::ScopeGuard guardTargetFile = Loki::MakeGuard(&ffs3::removeFile, targetFile); - - //copy creation date (last modification date is REDUNDANTLY written, too, even for symlinks) - copyFileTimes(sourceFile, targetFile, !copyFileSymLinks); //throw (FileError) - - if (copyFilePermissions) - copyObjectPermissions(sourceFile, targetFile, !copyFileSymLinks); //throw FileError() + //adapt file modification time: + copyFileTimes(sourceFile, targetFile, true); //throw (FileError) - guardTargetFile.Dismiss(); + guardTarget.Dismiss(); //target has been created successfully! } -#elif defined FFS_LINUX -void ffs3::copyFile(const Zstring& sourceFile, - const Zstring& targetFile, - bool copyFileSymLinks, - bool copyFilePermissions, - CopyFileCallback* callback) +void rawCopyWinOptimized(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) { - using ffs3::CopyFileCallback; + /* + BackupRead() FileRead() CopyFileEx() + -------------------------------------------- + Attributes NO NO YES + create time NO NO NO + ADS YES NO YES + Encrypted NO(silent fail) NO YES + Compressed NO NO NO + Sparse YES NO NO + PERF 6% faster - + + Mark stream as compressed: FSCTL_SET_COMPRESSION + compatible with: BackupRead() FileRead() + */ - //symbolic link handling - if (copyFileSymLinks && - symlinkExists(sourceFile)) + //open sourceFile for reading + HANDLE hFileIn = ::CreateFile(ffs3::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 + NULL, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + if (hFileIn == INVALID_HANDLE_VALUE) { - return copySymlinkInternal(sourceFile, targetFile, SYMLINK_TYPE_FILE, copyFilePermissions); //throw (FileError) + const DWORD lastError = ::GetLastError(); + const wxString& errorMessage = wxString(_("Error opening file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + + //if file is locked (try to) use Windows Volume Shadow Copy Service + if (lastError == ERROR_SHARING_VIOLATION || + lastError == ERROR_LOCK_VIOLATION) + throw ErrorFileLocked(errorMessage); + + throw FileError(errorMessage); } + Loki::ScopeGuard dummy1 = Loki::MakeGuard(::CloseHandle, hFileIn); + (void)dummy1; //silence warning "unused variable" - //begin of regular file copy - struct stat fileInfo; - if (::stat(sourceFile.c_str(), &fileInfo) != 0) //read file attributes from source file (resolving symlinks) + + BY_HANDLE_FILE_INFORMATION infoFileIn = {}; + if (!::GetFileInformationByHandle(hFileIn, &infoFileIn)) { const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\""); throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); } - //open sourceFile for reading - FileInput fileIn(sourceFile); //throw FileError() + const bool sourceIsCompressed = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool sourceIsSparse = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0; + const bool sourceIsEncrypted = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; + + bool targetSupportSparse = false; + bool targetSupportCompressed = false; + bool targetSupportEncryption = false; + + if (sourceIsCompressed || sourceIsSparse || sourceIsEncrypted) + { + DWORD fsFlags = 0; + + { + const size_t bufferSize = std::max(targetFile.size(), static_cast(10000)); + boost::scoped_array buffer(new wchar_t[bufferSize]); + + //suprisingly slow: ca. 1.5 ms per call! + if (!::GetVolumePathName(targetFile.c_str(), //__in LPCTSTR lpszFileName, -> seems to be no need for path prefix (check passed) + buffer.get(), //__out LPTSTR lpszVolumePathName, + static_cast(bufferSize))) //__in DWORD cchBufferLength + throw FileError(wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + + wxT("\nFailed to determine volume name!")); + + Zstring volumePath = buffer.get(); + if (!volumePath.EndsWith(common::FILE_NAME_SEPARATOR)) + volumePath += common::FILE_NAME_SEPARATOR; + + //suprisingly fast: ca. 0.03 ms per call! + if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, + NULL, //__out LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + NULL, //__out_opt LPDWORD lpVolumeSerialNumber, + NULL, //__out_opt LPDWORD lpMaximumComponentLength, + &fsFlags, //__out_opt LPDWORD lpFileSystemFlags, + NULL, //__out LPTSTR lpFileSystemNameBuffer, + 0)) //__in DWORD nFileSystemNameSize + throw FileError(wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(volumePath) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + + wxT("\nFailed to determine volume information!")); + } + + targetSupportSparse = (fsFlags & FILE_SUPPORTS_SPARSE_FILES) != 0; + targetSupportCompressed = (fsFlags & FILE_FILE_COMPRESSION ) != 0; + targetSupportEncryption = (fsFlags & FILE_SUPPORTS_ENCRYPTION ) != 0; + } + + //####################################### DST hack ########################################### + if (dst::isFatDrive(sourceFile)) //throw() + { + const dst::RawTime rawTime(infoFileIn.ftCreationTime, infoFileIn.ftLastWriteTime); + if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) + { + infoFileIn.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error) + ::GetSystemTimeAsFileTime(&infoFileIn.ftCreationTime); //real creation time information is not available... + } + } + + if (dst::isFatDrive(targetFile)) //throw() + { + const dst::RawTime encodedTime = dst::fatEncodeUtcTime(infoFileIn.ftLastWriteTime); //throw (std::runtime_error) + infoFileIn.ftCreationTime = encodedTime.createTimeRaw; + infoFileIn.ftLastWriteTime = encodedTime.writeTimeRaw; + } + //####################################### DST hack ########################################### + + const DWORD validAttribs = FILE_ATTRIBUTE_READONLY | + FILE_ATTRIBUTE_HIDDEN | + FILE_ATTRIBUTE_SYSTEM | + FILE_ATTRIBUTE_ARCHIVE | //those two are not set properly (not worse than ::CopyFileEx()) + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | // + (targetSupportEncryption ? FILE_ATTRIBUTE_ENCRYPTED : 0); //create targetFile and open it for writing - const Zstring temporary = createTempName(targetFile); //use temporary file until a correct date has been set + HANDLE hFileOut = ::CreateFile(ffs3::applyLongPathPrefix(targetFile).c_str(), + GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + CREATE_NEW, + (infoFileIn.dwFileAttributes & validAttribs) | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + if (hFileOut == INVALID_HANDLE_VALUE) + { + const DWORD lastError = ::GetLastError(); + const wxString& errorMessage = wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError); + + if (lastError == ERROR_FILE_EXISTS) + throw ErrorTargetExisting(errorMessage); + + if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } + Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: guard just after opening target and before managing hFileOut + + Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hFileOut); + (void)dummy; //silence warning "unused variable" + + + const bool useBackupFun = !sourceIsEncrypted; //http://msdn.microsoft.com/en-us/library/aa362509(v=VS.85).aspx + + if (sourceIsCompressed && targetSupportCompressed) + { + USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; + + DWORD bytesReturned = 0; + if (!DeviceIoControl(hFileOut, //handle to file or directory + FSCTL_SET_COMPRESSION, //dwIoControlCode + &cmpState, //input buffer + sizeof(cmpState), //size of input buffer + NULL, //lpOutBuffer + 0, //OutBufferSize + &bytesReturned, //number of bytes returned + NULL)) //OVERLAPPED structure + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + + wxT("\nFailed to write NTFS compressed attribute!")); + } + + //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 + if (sourceIsSparse && targetSupportSparse) + { + if (useBackupFun) + { + DWORD bytesReturned = 0; + if (!DeviceIoControl(hFileOut, //handle to file + FSCTL_SET_SPARSE, //dwIoControlCode + NULL, //input buffer + 0, //size of input buffer + NULL, //lpOutBuffer + 0, //OutBufferSize + &bytesReturned, //number of bytes returned + NULL)) //OVERLAPPED structure + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + + wxT("\nFailed to write NTFS sparse attribute!")); + } + } - //ensure cleanup (e.g. network drop): call BEFORE creating fileOut object! - Loki::ScopeGuard guardTempFile = Loki::MakeGuard(&ffs3::removeFile, temporary); + const DWORD BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size + static const boost::scoped_array memory(new BYTE[BUFFER_SIZE]); - FileOutput fileOut(temporary); //throw FileError() + struct ManageCtxt //manage context for BackupRead()/BackupWrite() + { + ManageCtxt() : read(NULL), write(NULL) {} + ~ManageCtxt() + { + if (read != NULL) + ::BackupRead (0, NULL, 0, NULL, true, false, &read); + if (write != NULL) + ::BackupWrite(0, NULL, 0, NULL, true, false, &write); + } - const size_t BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size - static const boost::scoped_array memory(new char[BUFFER_SIZE]); + LPVOID read; + LPVOID write; + } context; //copy contents of sourceFile to targetFile wxULongLong totalBytesTransferred; + + bool eof = false; do { - const size_t bytesRead = fileIn.read(memory.get(), BUFFER_SIZE); //throw FileError() + DWORD bytesRead = 0; - fileOut.write(memory.get(), bytesRead); //throw FileError() + if (useBackupFun) + { + if (!::BackupRead(hFileIn, //__in HANDLE hFile, + memory.get(), //__out LPBYTE lpBuffer, + BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out LPDWORD lpNumberOfBytesRead, + false, //__in BOOL bAbort, + false, //__in BOOL bProcessSecurity, + &context.read)) //__out LPVOID *lpContext + throw FileError(wxString(_("Error reading file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + else if (!::ReadFile(hFileIn, //__in HANDLE hFile, + memory.get(), //__out LPVOID lpBuffer, + BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped + throw FileError(wxString(_("Error reading file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted()); + + if (bytesRead > BUFFER_SIZE) + throw FileError(wxString(_("Error reading file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + + wxT("\n\n") + wxT("buffer overflow")); + + if (bytesRead < BUFFER_SIZE) + eof = true; + + DWORD bytesWritten = 0; + + if (useBackupFun) + { + if (!::BackupWrite(hFileOut, //__in HANDLE hFile, + memory.get(), //__in LPBYTE lpBuffer, + bytesRead, //__in DWORD nNumberOfBytesToWrite, + &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten, + false, //__in BOOL bAbort, + false, //__in BOOL bProcessSecurity, + &context.write)) //__out LPVOID *lpContext + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + wxT(" (w)")); //w -> distinguish from fopen error message! + } + else if (!::WriteFile(hFileOut, //__in HANDLE hFile, + memory.get(), //__out LPVOID lpBuffer, + bytesRead, //__in DWORD nNumberOfBytesToWrite, + &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted() + wxT(" (w)")); //w -> distinguish from fopen error message! + + if (bytesWritten != bytesRead) + throw FileError(wxString(_("Error writing file:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + wxT("incomplete write")); totalBytesTransferred += bytesRead; @@ -1856,33 +1856,205 @@ void ffs3::copyFile(const Zstring& sourceFile, if (callback != NULL) switch (callback->updateCopyStatus(totalBytesTransferred)) { - case CopyFileCallback::CONTINUE: + case CallbackCopyFile::CONTINUE: break; - case CopyFileCallback::CANCEL: //a user aborted operation IS an error condition! + case CallbackCopyFile::CANCEL: //a user aborted operation IS an error condition! throw FileError(wxString(_("Error copying file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + zToWx(targetFile) + wxT("\"\n\n") + _("Operation aborted!")); } } - while (!fileIn.eof()); + while (!eof); - //close output stream before changing attributes - fileOut.close(); - //rename temporary file - ffs3::renameFile(temporary, targetFile); - guardTempFile.Dismiss(); + if (totalBytesTransferred == 0) //BackupRead silently fails reading encrypted files -> double check! + { + LARGE_INTEGER inputSize = {}; + if (!::GetFileSizeEx(hFileIn, &inputSize)) + throw FileError(wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted()); + + if (inputSize.QuadPart != 0) + throw FileError(wxString(_("Error reading file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\"") + + wxT("\n\n") + wxT("unknown error")); + } + + //time needs to be set at the end: BackupWrite() changes file time + if (!::SetFileTime(hFileOut, + &infoFileIn.ftCreationTime, + NULL, + &infoFileIn.ftLastWriteTime)) + throw FileError(wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted()); + + +#ifndef NDEBUG //dst hack: verify data written + if (dst::isFatDrive(targetFile)) //throw() + { + WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {}; + assert(::GetFileAttributesEx(applyLongPathPrefix(targetFile).c_str(), //__in LPCTSTR lpFileName, + GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, + &debugeAttr)); //__out LPVOID lpFileInformation + + assert(::CompareFileTime(&debugeAttr.ftCreationTime, &infoFileIn.ftCreationTime) == 0); + assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &infoFileIn.ftLastWriteTime) == 0); + } +#endif + + guardTarget.Dismiss(); + + /* + //create test sparse file + size_t sparseSize = 50 * 1024 * 1024; + HANDLE hSparse = ::CreateFile(L"C:\\sparse.file", + GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + CREATE_NEW, + FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + DWORD br = 0; + if (!::DeviceIoControl(hSparse, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &br,NULL)) + throw 1; + + LARGE_INTEGER liDistanceToMove = {}; + liDistanceToMove.QuadPart = sparseSize; + if (!::SetFilePointerEx(hSparse, liDistanceToMove, NULL, FILE_BEGIN)) + throw 1; + + if (!SetEndOfFile(hSparse)) + throw 1; - //ensure cleanup: - Loki::ScopeGuard guardTargetFile = Loki::MakeGuard(&ffs3::removeFile, targetFile); + FILE_ZERO_DATA_INFORMATION zeroInfo = {}; + zeroInfo.BeyondFinalZero.QuadPart = sparseSize; + if (!::DeviceIoControl(hSparse, FSCTL_SET_ZERO_DATA, &zeroInfo, sizeof(zeroInfo), NULL, 0, &br, NULL)) + throw 1; + + ::CloseHandle(hSparse); + */ +} +#endif + + +void rawCopyStream(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting) +{ + Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: place guard before lifetime of FileOutput + try + { + //open sourceFile for reading + FileInput fileIn(sourceFile); //throw (FileError) + + //create targetFile and open it for writing + FileOutput fileOut(targetFile, FileOutput::ACC_CREATE_NEW); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting) + + const size_t BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size + static const boost::scoped_array memory(new char[BUFFER_SIZE]); + + //copy contents of sourceFile to targetFile + wxULongLong totalBytesTransferred; + do + { + const size_t bytesRead = fileIn.read(memory.get(), BUFFER_SIZE); //throw (FileError) + + fileOut.write(memory.get(), bytesRead); //throw (FileError) + + totalBytesTransferred += bytesRead; + + //invoke callback method to update progress indicators + if (callback != NULL) + switch (callback->updateCopyStatus(totalBytesTransferred)) + { + case CallbackCopyFile::CONTINUE: + break; + + case CallbackCopyFile::CANCEL: //a user aborted operation IS an error condition! + throw FileError(wxString(_("Error copying file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + + zToWx(targetFile) + wxT("\"\n\n") + _("Operation aborted!")); + } + } + while (!fileIn.eof()); + } + catch(ErrorTargetExisting&) + { + guardTarget.Dismiss(); //don't delete file that existed previously! + throw; + } //adapt file modification time: copyFileTimes(sourceFile, targetFile, true); //throw (FileError) + guardTarget.Dismiss(); //target has been created successfully! +} + + +inline +void copyFileImpl(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) +{ +#ifdef FFS_WIN + /* + rawCopyWinApi() rawCopyWinOptimized() + ------------------------------------- + Attributes YES YES + Filetimes YES YES + ADS YES YES + Encrypted YES YES + Compressed NO YES + Sparse NO YES + PERF - 6% faster + */ + + //rawCopyWinApi(sourceFile, targetFile, callback); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) + rawCopyWinOptimized(sourceFile, targetFile, callback); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) ->about 8% faster + +#elif defined FFS_LINUX + rawCopyStream(sourceFile, targetFile, callback); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting) +#endif +} +} + + +void ffs3::copyFile(const Zstring& sourceFile, //throw (FileError: ErrorTargetPathMissing, ErrorFileLocked); + const Zstring& targetFile, + bool copyFilePermissions, + CallbackCopyFile* callback) +{ + Zstring temporary = targetFile + ffs3::TEMP_FILE_ENDING; //use temporary file until a correct date has been set + Loki::ScopeGuard guardTempFile = Loki::MakeGuard(&removeFile, boost::cref(temporary)); //transactional behavior: ensure cleanup (e.g. network drop) -> cref [!] + + //raw file copy + try + { + copyFileImpl(sourceFile, temporary, callback); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) + } + catch (ErrorTargetExisting&) + { + //determine non-used temp file name "first": + //using optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses + temporary = createTempName(targetFile); + + //retry + copyFileImpl(sourceFile, temporary, callback); //throw (FileError) + } + + //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite + if (callback) callback->deleteTargetFile(targetFile); + + //rename temporary file: + //perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick! + renameFile(temporary, targetFile); //throw (FileError) + + guardTempFile.Dismiss(); + Loki::ScopeGuard guardTargetFile = Loki::MakeGuard(&removeFile, targetFile); + + //perf: interestingly it is much faster to apply file times BEFORE renaming temporary! + //set file permissions if (copyFilePermissions) - copyObjectPermissions(sourceFile, targetFile, true); //throw FileError() + copyObjectPermissions(sourceFile, targetFile, true); //throw (FileError) guardTargetFile.Dismiss(); //target has been created successfully! } -#endif -- cgit