diff options
Diffstat (limited to 'shared/fileHandling.cpp')
-rw-r--r-- | shared/fileHandling.cpp | 1584 |
1 files changed, 0 insertions, 1584 deletions
diff --git a/shared/fileHandling.cpp b/shared/fileHandling.cpp deleted file mode 100644 index 2a8af46a..00000000 --- a/shared/fileHandling.cpp +++ /dev/null @@ -1,1584 +0,0 @@ -// ************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2010 ZenJu (zhnmju123 AT gmx.de) * -// ************************************************************************** -// -#include "fileHandling.h" -#include <wx/intl.h> -#include "systemFunctions.h" -#include "globalFunctions.h" -#include "systemConstants.h" -#include "fileTraverser.h" -#include <boost/bind.hpp> -#include <algorithm> -#include <wx/datetime.h> -#include "stringConv.h" -#include <wx/utils.h> -#include <boost/scoped_array.hpp> -#include <boost/shared_ptr.hpp> -#include <stdexcept> -#include "loki/TypeManip.h" -#include "loki/ScopeGuard.h" - -#ifdef FFS_WIN -#include "dllLoader.h" -#include <wx/msw/wrapwin.h> //includes "windows.h" -#include "longPathPrefix.h" - -#elif defined FFS_LINUX -#include <sys/stat.h> -#include "fileIO.h" -#include <time.h> -#include <utime.h> -#include <cerrno> -#include <sys/time.h> -#endif - -using FreeFileSync::FileError; - - -namespace -{ -#ifdef FFS_WIN -Zstring resolveRelativePath(const Zstring& relativeName, DWORD proposedBufferSize = 1000) -{ - boost::scoped_array<DefaultChar> fullPath(new DefaultChar[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; - } - - //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 = wxString() + SEPARATOR + rest; - expandMacros(rest); - text = prefix + SEPARATOR + potentialMacro + rest; - } - } - } -} -} - - -Zstring FreeFileSync::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 do directory formatting with wxFileName, as it doesn't respect //?/ - prefix! - - wxString dirnameTmp = zToWx(dirname); - dirnameTmp.Trim(true); //remove whitespace characters from right - dirnameTmp.Trim(false); //remove whitespace characters from left - - if (dirnameTmp.empty()) //an empty string will later be returned as "\"; this is not desired - return Zstring(); - - //replace macros - expandMacros(dirnameTmp); - - /* - 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" - */ - dirnameTmp = zToWx(resolveRelativePath(wxToZ(dirnameTmp))); - - if (!dirnameTmp.EndsWith(zToWx(globalFunctions::FILE_NAME_SEPARATOR))) - dirnameTmp += zToWx(globalFunctions::FILE_NAME_SEPARATOR); - - return wxToZ(dirnameTmp); -} - - -bool FreeFileSync::fileExists(const Zstring& filename) -{ - //symbolic links (broken or not) are also treated as existing files! -#ifdef FFS_WIN - // we must use GetFileAttributes() instead of the ANSI C functions because - // it can cope with network (UNC) paths unlike them - const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); - return (ret != INVALID_FILE_ATTRIBUTES) && !(ret & FILE_ATTRIBUTE_DIRECTORY); //returns true for (file-)symlinks also - -#elif defined FFS_LINUX - struct stat fileInfo; - return (::lstat(filename.c_str(), &fileInfo) == 0 && - (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode))); //in Linux a symbolic link is neither file nor directory -#endif -} - - -bool FreeFileSync::dirExists(const Zstring& dirname) -{ - //symbolic links (broken or not) are also treated as existing directories! -#ifdef FFS_WIN - // we must use GetFileAttributes() instead of the ANSI C functions because - // it can cope with network (UNC) paths unlike them - const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(dirname).c_str()); - - return (ret != INVALID_FILE_ATTRIBUTES) && (ret & FILE_ATTRIBUTE_DIRECTORY); //returns true for (dir-)symlinks also - -#elif defined FFS_LINUX - struct stat dirInfo; - return (::lstat(dirname.c_str(), &dirInfo) == 0 && - (S_ISLNK(dirInfo.st_mode) || S_ISDIR(dirInfo.st_mode))); //in Linux a symbolic link is neither file nor directory -#endif -} - - -bool FreeFileSync::symlinkExists(const Zstring& objname) -{ -#ifdef FFS_WIN - const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(objname).c_str()); - return (ret != INVALID_FILE_ATTRIBUTES) && (ret & FILE_ATTRIBUTE_REPARSE_POINT); - -#elif defined FFS_LINUX - struct stat fileInfo; - return (::lstat(objname.c_str(), &fileInfo) == 0 && - S_ISLNK(fileInfo.st_mode)); //symbolic link -#endif -} - - -bool FreeFileSync::somethingExists(const Zstring& objname) //throw() check whether any object with this name exists -{ -#ifdef FFS_WIN - return ::GetFileAttributes(applyLongPathPrefix(objname).c_str()) != INVALID_FILE_ATTRIBUTES; - -#elif defined FFS_LINUX - struct stat fileInfo; - return ::lstat(objname.c_str(), &fileInfo) == 0; -#endif -} - - -#ifdef FFS_WIN -namespace -{ -wxULongLong getFileSizeSymlink(const Zstring& linkName) //throw (FileError) -{ - //open handle to target of symbolic link - const HANDLE hFile = ::CreateFile(FreeFileSync::applyLongPathPrefix(linkName).c_str(), - 0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - NULL); - if (hFile != INVALID_HANDLE_VALUE) - { - boost::shared_ptr<void> dummy(hFile, ::CloseHandle); - - BY_HANDLE_FILE_INFORMATION fileInfoByHandle; - if (::GetFileInformationByHandle(hFile, &fileInfoByHandle)) - return wxULongLong(fileInfoByHandle.nFileSizeHigh, fileInfoByHandle.nFileSizeLow); - } - - const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + FreeFileSync::zToWx(linkName) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); -} -} -#endif - - -wxULongLong FreeFileSync::getFilesize(const Zstring& filename) //throw (FileError) -{ -#ifdef FFS_WIN - WIN32_FIND_DATA fileMetaData; - const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(filename).c_str(), &fileMetaData); - if (searchHandle == INVALID_HANDLE_VALUE) - { - const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(filename) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - ::FindClose(searchHandle); - - const bool isSymbolicLink = (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; - if (isSymbolicLink) - return getFileSizeSymlink(filename); //throw (FileError) - - return wxULongLong(fileMetaData.nFileSizeHigh, fileMetaData.nFileSizeLow); - -#elif defined FFS_LINUX - struct stat fileInfo; - if (::stat(filename.c_str(), &fileInfo) != 0) //follow symbolic links - { - const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(filename) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - return fileInfo.st_size; -#endif -} - - -namespace -{ -#ifdef FFS_WIN -DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! -{ - const size_t bufferSize = std::max(pathName.size(), static_cast<size_t>(10000)); - boost::scoped_array<wchar_t> buffer(new wchar_t[bufferSize]); - - //pathName need not exist! - if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName, - buffer.get(), //__out LPTSTR lpszVolumePathName, - static_cast<DWORD>(bufferSize))) //__in DWORD cchBufferLength - return 0; - - Zstring volumePath = buffer.get(); - if (!volumePath.EndsWith(globalFunctions::FILE_NAME_SEPARATOR)) - volumePath += globalFunctions::FILE_NAME_SEPARATOR; - - DWORD volumeSerial = 0; - if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, - NULL, //__out LPTSTR lpVolumeNameBuffer, - 0, //__in DWORD nVolumeNameSize, - &volumeSerial, //__out_opt LPDWORD lpVolumeSerialNumber, - NULL, //__out_opt LPDWORD lpMaximumComponentLength, - NULL, //__out_opt LPDWORD lpFileSystemFlags, - NULL, //__out LPTSTR lpFileSystemNameBuffer, - 0)) //__in DWORD nFileSystemNameSize - return 0; - - return volumeSerial; -} -#elif defined FFS_LINUX - -dev_t retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! -{ - Zstring volumePathName = pathName; - - //remove trailing slash - if (volumePathName.size() > 1 && volumePathName.EndsWith(globalFunctions::FILE_NAME_SEPARATOR)) //exception: allow '/' - volumePathName = volumePathName.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - - struct stat fileInfo; - while (::lstat(volumePathName.c_str(), &fileInfo) != 0) - { - volumePathName = volumePathName.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); //returns empty string if ch not found - if (volumePathName.empty()) - return 0; //this includes path "/" also! - } - - return fileInfo.st_dev; -} -#endif -} - - -FreeFileSync::ResponseSameVol FreeFileSync::onSameVolume(const Zstring& folderLeft, const Zstring& folderRight) //throw() -{ -#ifdef FFS_WIN - typedef DWORD VolSerial; -#elif defined FFS_LINUX - typedef dev_t VolSerial; -#endif - const VolSerial serialLeft = retrieveVolumeSerial(folderLeft); //returns 0 on error! - const VolSerial serialRight = retrieveVolumeSerial(folderRight); //returns 0 on error! - if (serialLeft == 0 || serialRight == 0) - return VOLUME_CANT_SAY; - - return serialLeft == serialRight ? VOLUME_SAME : VOLUME_DIFFERENT; -} - - -void FreeFileSync::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 - if (!::DeleteFile(filenameFmt.c_str())) - { - //optimization: change file attributes 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; - } - } - - wxString errorMessage = wxString(_("Error deleting file:")) + wxT("\n\"") + zToWx(filename) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::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") + FreeFileSync::getLastErrorFormatted()); - } -#endif -} - - -namespace -{ -struct ErrorDifferentVolume : public FreeFileSync::FileError -{ - ErrorDifferentVolume(const wxString& message) : FileError(message) {} -}; - -/* Usage overview: - - renameFile() --> renameFileInternal() - | /|\ - \|/ | - fix8Dot3NameClash() -*/ -//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) -{ - using namespace FreeFileSync; //for zToWx() - -#ifdef FFS_WIN - const Zstring oldNameFmt = applyLongPathPrefix(oldName); - const Zstring newNameFmt = applyLongPathPrefix(newName); - - if (!::MoveFileEx(oldNameFmt.c_str(), //__in LPCTSTR lpExistingFileName, - newNameFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, - 0)) //__in DWORD dwFlags - { - if (::GetLastError() == ERROR_ACCESS_DENIED) //MoveFileEx may fail to rename a read-only file on a SAMBA-share -> (try to) handle this - { - 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 - { - //try again... - if (::MoveFileEx(oldNameFmt.c_str(), //__in LPCTSTR lpExistingFileName, - newNameFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, - 0)) //__in DWORD dwFlags - { - //(try to) restore file attributes - ::SetFileAttributes(newNameFmt.c_str(), //don't handle error - oldNameAttrib); - return; - } - else - { - const DWORD errorCode = ::GetLastError(); - //cleanup: (try to) restore file attributes: assume oldName is still existing - ::SetFileAttributes(oldNameFmt.c_str(), - oldNameAttrib); - - ::SetLastError(errorCode); //set error code from ::MoveFileEx() - } - } - } - } - - const wxString errorMessage = wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(oldName) + wxT("\" ->\n\"") + zToWx(newName) + wxT("\"") + - wxT("\n\n") + FreeFileSync::getLastErrorFormatted(); - if (::GetLastError() == ERROR_NOT_SAME_DEVICE) - throw ErrorDifferentVolume(errorMessage); - else - throw FileError(errorMessage); - } - -#elif defined FFS_LINUX - //rename temporary file - if (::rename(oldName.c_str(), newName.c_str()) != 0) - { - const wxString errorMessage = wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(oldName) + wxT("\" ->\n\"") + zToWx(newName) + wxT("\"") + - wxT("\n\n") + FreeFileSync::getLastErrorFormatted(); - if (errno == EXDEV) - throw ErrorDifferentVolume(errorMessage); - else - throw FileError(errorMessage); - } -#endif -} - - -void renameFileInternalNoThrow(const Zstring& oldName, const Zstring& newName) //throw () -{ - try - { - ::renameFileInternal(oldName, newName); - } - catch (...) {} -} - - -#ifdef FFS_WIN -/*small wrapper around -::GetShortPathName() -::GetLongPathName() */ -template <typename Function> -Zstring getFilenameFmt(const Zstring& filename, Function fun) //throw(); returns empty string on error -{ - const Zstring filenameFmt = FreeFileSync::applyLongPathPrefix(filename); - - const DWORD bufferSize = fun(filenameFmt.c_str(), NULL, 0); - if (bufferSize == 0) - return Zstring(); - - boost::scoped_array<wchar_t> buffer(new wchar_t[bufferSize]); - - const DWORD rv = fun(filenameFmt.c_str(), //__in LPCTSTR lpszShortPath, - buffer.get(), //__out LPTSTR lpszLongPath, - bufferSize); //__in DWORD cchBuffer - if (rv == 0 || rv >= bufferSize) - return Zstring(); - - return buffer.get(); -} - - -Zstring createTemp8Dot3Name(const Zstring& fileName) //find a unique 8.3 short name -{ - const Zstring pathPrefix = fileName.Find(globalFunctions::FILE_NAME_SEPARATOR) != Zstring::npos ? - (fileName.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR) + globalFunctions::FILE_NAME_SEPARATOR) : Zstring(); - - Zstring extension = fileName.AfterLast(globalFunctions::FILE_NAME_SEPARATOR).AfterLast(DefaultChar('.')); //extension needn't contain reasonable data - if (extension.empty()) - extension = DefaultStr("FFS"); - extension.Truncate(3); - - for (int index = 0; index < 100000000; ++index) //filename must be representable by <= 8 characters - { - const Zstring output = pathPrefix + numberToZstring(index) + DefaultChar('.') + extension; - if (!FreeFileSync::somethingExists(output)) //ensure uniqueness - return output; - } - - throw std::runtime_error(std::string("100000000 files, one for each number, exist in this directory? You're kidding...\n") + std::string(wxString(pathPrefix.c_str()).ToUTF8())); -} - - -//try to handle issues with already existing short 8.3 file names on Windows 7 -bool fix8Dot3NameClash(const Zstring& oldName, const Zstring& newName) //throw (FileError); return "true" if rename operation succeeded -{ - using namespace FreeFileSync; - - if (newName.Find(globalFunctions::FILE_NAME_SEPARATOR) == Zstring::npos) - return false; - - if (FreeFileSync::somethingExists(newName)) //name OR directory! - { - const Zstring fileNameOrig = newName.AfterLast(globalFunctions::FILE_NAME_SEPARATOR); //returns the whole string if ch not found - const Zstring fileNameShort = getFilenameFmt(newName, ::GetShortPathName).AfterLast(globalFunctions::FILE_NAME_SEPARATOR); //throw() returns empty string on error - const Zstring fileNameLong = getFilenameFmt(newName, ::GetLongPathName).AfterLast(globalFunctions::FILE_NAME_SEPARATOR); //throw() returns empty string on error - - if ( !fileNameShort.empty() && - !fileNameLong.empty() && - fileNameOrig.cmpFileName(fileNameShort) == 0 && - fileNameShort.cmpFileName(fileNameLong) != 0) - { - //we detected an event where newName is in shortname format (although it is intended to be a long name) and - //writing target file failed because another unrelated file happens to have the same short name - - const Zstring newNameFullPathLong = newName.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR) + globalFunctions::FILE_NAME_SEPARATOR + - fileNameLong; - - //find another name in short format: this ensures the actual short name WILL be renamed as well! - const Zstring parkedTarget = createTemp8Dot3Name(newName); - - //move already existing short name out of the way for now - renameFileInternal(newNameFullPathLong, parkedTarget); //throw (FileError, ErrorDifferentVolume); - //DON'T call FreeFileSync::renameFile() to avoid reentrance! - - //schedule cleanup; the file system should assign this unrelated file a new (unique) short name - Loki::ScopeGuard guard = Loki::MakeGuard(renameFileInternalNoThrow, parkedTarget, newNameFullPathLong);//equivalent to Boost.ScopeExit in this case - (void)guard; //silence warning "unused variable" - - renameFileInternal(oldName, newName); //the short filename name clash is solved, this should work now - return true; - } - } - return false; //issue not fixed -} -#endif -} - - -//rename file: no copying!!! -void FreeFileSync::renameFile(const Zstring& oldName, const Zstring& newName) //throw (FileError, ErrorDifferentVolume); -{ - try - { - renameFileInternal(oldName, newName); //throw (FileError, ErrorDifferentVolume) - } - catch (const FileError&) - { -#ifdef FFS_WIN - if (fix8Dot3NameClash(oldName, newName)) //throw (FileError); try to handle issues with already existing short 8.3 file names on Windows 7 - return; -#endif - throw; - } -} - - -using FreeFileSync::MoveFileCallback; - -class CopyCallbackImpl : public FreeFileSync::CopyFileCallback //callback functionality -{ -public: - CopyCallbackImpl(MoveFileCallback* callback) : moveCallback(callback) {} - - virtual Response updateCopyStatus(const wxULongLong& totalBytesTransferred) - { - switch (moveCallback->requestUiRefresh()) - { - case MoveFileCallback::CONTINUE: - return CopyFileCallback::CONTINUE; - - case MoveFileCallback::CANCEL: - return CopyFileCallback::CANCEL; - } - return CopyFileCallback::CONTINUE; //dummy return value - } - -private: - MoveFileCallback* moveCallback; -}; - - -void FreeFileSync::moveFile(const Zstring& sourceFile, const Zstring& targetFile, MoveFileCallback* callback) //throw (FileError); -{ - if (somethingExists(targetFile)) //test file existence: e.g. Linux might silently overwrite existing symlinks - { - const wxString errorMessage = wxString(_("Error moving file:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\" ->\n\"") + zToWx(targetFile) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + _("Target file already existing!")); - } - - //moving of symbolic links should work correctly: - - //first try to move the file directly without copying - try - { - 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&) {} - - - //file is on a different volume: let's copy it - std::auto_ptr<CopyCallbackImpl> copyCallback(callback != NULL ? new CopyCallbackImpl(callback) : NULL); - - copyFile(sourceFile, - targetFile, - true, //copy symbolic links -#ifdef FFS_WIN - NULL, //supply handler for making shadow copies -#endif - copyCallback.get()); //throw (FileError); - - //attention: if copy-operation was cancelled an exception is thrown => sourcefile is not deleted, as we wish! - - removeFile(sourceFile); -} - - -class TraverseOneLevel : public FreeFileSync::TraverseCallback -{ -public: - typedef std::vector<std::pair<Zstring, Zstring> > NamePair; - - TraverseOneLevel(NamePair& filesShort, NamePair& dirsShort) : - m_files(filesShort), - m_dirs(dirsShort) {} - - virtual void onFile(const DefaultChar* shortName, const Zstring& fullName, const FileInfo& details) - { - m_files.push_back(std::make_pair(Zstring(shortName), fullName)); - } - virtual void onSymlink(const DefaultChar* shortName, const Zstring& fullName, const SymlinkInfo& details) - { - if (details.dirLink) - m_dirs.push_back(std::make_pair(Zstring(shortName), fullName)); - else - m_files.push_back(std::make_pair(Zstring(shortName), fullName)); - } - - virtual ReturnValDir onDir(const DefaultChar* shortName, const Zstring& fullName) - { - m_dirs.push_back(std::make_pair(Zstring(shortName), fullName)); - return Loki::Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs; moveDirectory works recursively! - } - virtual void onError(const wxString& errorText) - { - throw FileError(errorText); - } - -private: - NamePair& m_files; - NamePair& m_dirs; -}; - - -void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExistingDirs, MoveFileCallback* callback) //throw (FileError); -{ - using namespace FreeFileSync; - - //handle symbolic links - if (symlinkExists(sourceDir)) - { - createDirectory(targetDir, sourceDir, true); //copy symbolic link - removeDirectory(sourceDir); //if target is already another symlink or directory, sourceDir-symlink is silently deleted - return; - } - - if (somethingExists(targetDir)) - { - if (!ignoreExistingDirs) //directory or symlink exists (or even a file... this error will be caught later) - { - const wxString errorMessage = wxString(_("Error moving directory:")) + wxT("\n\"") + zToWx(sourceDir) + wxT("\" ->\n\"") + zToWx(targetDir) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + _("Target directory already existing!")); - } - } - else - { - //first try to move the directory directly without copying - try - { - renameFile(sourceDir, targetDir); //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 directory) - catch (const ErrorDifferentVolume&) {} - - //create target - createDirectory(targetDir, sourceDir, false); //throw (FileError); - } - - //call back once per folder - if (callback) - switch (callback->requestUiRefresh()) - { - case MoveFileCallback::CONTINUE: - break; - case MoveFileCallback::CANCEL: - //an 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!")); - } - - //move files/folders recursively - TraverseOneLevel::NamePair fileList; //list of names: 1. short 2.long - TraverseOneLevel::NamePair dirList; // - - //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(globalFunctions::FILE_NAME_SEPARATOR) ? //ends with path separator - targetDir : - targetDir + globalFunctions::FILE_NAME_SEPARATOR; - - //move files - for (TraverseOneLevel::NamePair::const_iterator i = fileList.begin(); i != fileList.end(); ++i) - FreeFileSync::moveFile(i->second, targetDirFormatted + i->first, callback); - - //move directories - for (TraverseOneLevel::NamePair::const_iterator i = dirList.begin(); i != dirList.end(); ++i) - ::moveDirectoryImpl(i->second, targetDirFormatted + i->first, true, callback); - - //attention: if move-operation was cancelled an exception is thrown => sourceDir is not deleted, as we wish! - - //delete source - removeDirectory(sourceDir); //throw (FileError); -} - - -void FreeFileSync::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExistingDirs, MoveFileCallback* callback) //throw (FileError); -{ -#ifdef FFS_WIN - const Zstring& sourceDirFormatted = sourceDir; - const Zstring& targetDirFormatted = targetDir; - -#elif defined FFS_LINUX - const Zstring sourceDirFormatted = //remove trailing slash - sourceDir.size() > 1 && sourceDir.EndsWith(globalFunctions::FILE_NAME_SEPARATOR) ? //exception: allow '/' - sourceDir.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR) : - sourceDir; - const Zstring targetDirFormatted = //remove trailing slash - targetDir.size() > 1 && targetDir.EndsWith(globalFunctions::FILE_NAME_SEPARATOR) ? //exception: allow '/' - targetDir.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR) : - targetDir; -#endif - - ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, ignoreExistingDirs, callback); -} - - -class FilesDirsOnlyTraverser : public FreeFileSync::TraverseCallback -{ -public: - FilesDirsOnlyTraverser(std::vector<Zstring>& files, std::vector<Zstring>& dirs) : - m_files(files), - m_dirs(dirs) {} - - virtual void onFile(const DefaultChar* shortName, const Zstring& fullName, const FileInfo& details) - { - m_files.push_back(fullName); - } - virtual void onSymlink(const DefaultChar* shortName, const Zstring& fullName, const SymlinkInfo& details) - { - if (details.dirLink) - m_dirs.push_back(fullName); - else - m_files.push_back(fullName); - } - virtual ReturnValDir onDir(const DefaultChar* shortName, const Zstring& fullName) - { - m_dirs.push_back(fullName); - return Loki::Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs; removeDirectory works recursively! - } - virtual void onError(const wxString& errorText) - { - throw FileError(errorText); - } - -private: - std::vector<Zstring>& m_files; - std::vector<Zstring>& m_dirs; -}; - - -void FreeFileSync::removeDirectory(const Zstring& directory) -{ - //no error situation if directory is not existing! manual deletion relies on it! - if (!somethingExists(directory)) - return; //neither directory nor any other object (e.g. broken symlink) with that name existing - -#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") + FreeFileSync::getLastErrorFormatted()); - } -#endif - - - //attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!! - if (symlinkExists(directory)) //remove symlink directly - { -#ifdef FFS_WIN - if (!::RemoveDirectory(directoryFmt.c_str())) -#elif defined FFS_LINUX - if (::unlink(directory.c_str()) != 0) -#endif - { - wxString errorMessage = wxString(_("Error deleting directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - return; - } - - std::vector<Zstring> fileList; - std::vector<Zstring> dirList; - - //get all files and directories from current directory (WITHOUT subdirectories!) - FilesDirsOnlyTraverser traverser(fileList, dirList); - FreeFileSync::traverseFolder(directory, false, &traverser); //don't follow symlinks - - //delete files - std::for_each(fileList.begin(), fileList.end(), removeFile); - - //delete directories recursively - std::for_each(dirList.begin(), dirList.end(), removeDirectory); //call recursively to correctly handle symbolic links - - //parent directory is deleted last -#ifdef FFS_WIN - if (!::RemoveDirectory(directoryFmt.c_str())) //remove directory, support for \\?\-prefix -#else - if (::rmdir(directory.c_str()) != 0) -#endif - { - wxString errorMessage = wxString(_("Error deleting directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } -} - - -//optionally: copy directory last change date, DO NOTHING if something fails -bool FreeFileSync::copyFileTimes(const Zstring& sourceDir, const Zstring& targetDir, bool deRefSymlinks) //throw() -{ -#ifdef FFS_WIN - FILETIME creationTime = {0}; - FILETIME lastAccessTime = {0}; - FILETIME lastWriteTime = {0}; - - { - WIN32_FILE_ATTRIBUTE_DATA sourceAttr; - if (!::GetFileAttributesEx(applyLongPathPrefix(sourceDir).c_str(), //__in LPCTSTR lpFileName, - GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, - &sourceAttr)) //__out LPVOID lpFileInformation - return false; - - if ((sourceAttr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && deRefSymlinks) //we have a symlink AND need to dereference... - { - HANDLE hDirRead = ::CreateFile(applyLongPathPrefix(sourceDir).c_str(), - FILE_READ_ATTRIBUTES, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory; no FILE_FLAG_OPEN_REPARSE_POINT => deref symlinks - NULL); - if (hDirRead == INVALID_HANDLE_VALUE) - return false; - - boost::shared_ptr<void> dummy(hDirRead, ::CloseHandle); - - if (!::GetFileTime(hDirRead, //__in HANDLE hFile, - &creationTime, //__out_opt LPFILETIME lpCreationTime, - &lastAccessTime, //__out_opt LPFILETIME lpLastAccessTime, - &lastWriteTime)) //__out_opt LPFILETIME lpLastWriteTime - return false; - } - else - { - creationTime = sourceAttr.ftCreationTime; - lastAccessTime = sourceAttr.ftLastAccessTime; - lastWriteTime = sourceAttr.ftLastWriteTime; - } - } - - - HANDLE hDirWrite = ::CreateFile(applyLongPathPrefix(targetDir).c_str(), - FILE_WRITE_ATTRIBUTES, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory - (deRefSymlinks ? 0 : FILE_FLAG_OPEN_REPARSE_POINT), //process symlinks - NULL); - if (hDirWrite == INVALID_HANDLE_VALUE) - return false; - - boost::shared_ptr<void> dummy(hDirWrite, ::CloseHandle); - - if (!::SetFileTime(hDirWrite, - &creationTime, - &lastAccessTime, - &lastWriteTime)) //return value not evalutated! - return false; - -#elif defined FFS_LINUX - if (deRefSymlinks) - { - struct stat dirInfo; - if (::stat(sourceDir.c_str(), &dirInfo) != 0) //read file attributes from source directory - return false; - - struct utimbuf newTimes; - newTimes.actime = dirInfo.st_atime; - newTimes.modtime = dirInfo.st_mtime; - - //(try to) set new "last write time" - if (::utime(targetDir.c_str(), &newTimes) != 0) //return value not evalutated! - return false; - } - else - { - struct stat dirInfo; - if (::lstat(sourceDir.c_str(), &dirInfo) != 0) //read file attributes from source directory - return false; - - struct timeval newTimes[2]; - newTimes[0].tv_sec = dirInfo.st_atime; /* seconds */ - newTimes[0].tv_usec = 0; /* microseconds */ - - newTimes[1].tv_sec = dirInfo.st_mtime; /* seconds */ - newTimes[1].tv_usec = 0; /* microseconds */ - - if (::lutimes(targetDir.c_str(), newTimes) != 0) //return value not evalutated! - return false; - } -#endif - return true; -} - - -namespace -{ -#ifdef FFS_WIN -Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target path of symbolic link to a directory -{ - //open handle to target of symbolic link - const HANDLE hDir = ::CreateFile(FreeFileSync::applyLongPathPrefix(dirLinkName).c_str(), - 0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory - NULL); - if (hDir == INVALID_HANDLE_VALUE) - return Zstring(); - - boost::shared_ptr<void> dummy(hDir, ::CloseHandle); - - const size_t BUFFER_SIZE = 10000; - TCHAR targetPath[BUFFER_SIZE]; - - - //dynamically load windows API function - typedef DWORD (WINAPI *GetFinalPathNameByHandleWFunc)( - HANDLE hFile, - LPTSTR lpszFilePath, - DWORD cchFilePath, - DWORD dwFlags); - static const GetFinalPathNameByHandleWFunc getFinalPathNameByHandle = - Utility::loadDllFunction<GetFinalPathNameByHandleWFunc>(L"kernel32.dll", "GetFinalPathNameByHandleW"); - - if (getFinalPathNameByHandle == NULL) - throw FileError(wxString(_("Error loading library function:")) + wxT("\n\"") + wxT("GetFinalPathNameByHandleW") + wxT("\"")); - - const DWORD rv = (*getFinalPathNameByHandle)( - hDir, //__in HANDLE hFile, - targetPath, //__out LPTSTR lpszFilePath, - BUFFER_SIZE,//__in DWORD cchFilePath, - 0); //__in DWORD dwFlags - if (rv >= BUFFER_SIZE || rv == 0) - return Zstring(); - - return targetPath; -} - -//#include <aclapi.h> -////optionally: copy additional metadata, DO NOTHING if something fails -//void copyAdditionalMetadata(const Zstring& sourceDir, const Zstring& targetDir) -//{ -// //copy NTFS permissions -// -// PSECURITY_DESCRIPTOR pSD; -// -// PACL pDacl; -// if (::GetNamedSecurityInfo( -// const_cast<DefaultChar*>(sourceDir.c_str()), -// SE_FILE_OBJECT, //file or directory -// DACL_SECURITY_INFORMATION, -// NULL, -// NULL, -// &pDacl, -// NULL, -// &pSD -// ) == ERROR_SUCCESS) -// { -// //(try to) set new security information -// if (::SetNamedSecurityInfo( -// const_cast<DefaultChar*>(targetDir.c_str()), -// SE_FILE_OBJECT, //file or directory -// DACL_SECURITY_INFORMATION, -// NULL, -// NULL, -// pDacl, -// NULL) != ERROR_SUCCESS) //return value not evalutated! -// { -// const wxString errorMessage = wxString(wxT("Error 2:")) + wxT("\n\"") + targetDir.c_str() + wxT("\""); -// throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); -// } -// -//#warning BUG! -// LocalFree(pSD); -// } -// else -// { -// const wxString errorMessage = wxString(wxT("Error 1:")) + wxT("\n\"") + sourceDir.c_str() + wxT("\""); -// throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); -// } -// -//} - -#elif defined FFS_LINUX -void copySymlinkInternal(const Zstring& sourceLink, const Zstring& targetLink) //throw (FileError) -{ - using namespace FreeFileSync; - - //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")) + FreeFileSync::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") + FreeFileSync::getLastErrorFormatted()); - } - - //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist - Loki::ScopeGuard guardTargetLink = Loki::MakeGuard(::unlink, targetLink); - - if (!copyFileTimes(sourceLink, targetLink, false)) - { - wxString errorMessage = wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(targetLink) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - guardTargetLink.Dismiss(); -} -#endif -} - -void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, const bool copyDirectorySymLinks, const int level) -{ - using namespace FreeFileSync; - - if (FreeFileSync::dirExists(directory)) - return; - - if (level == 100) //catch endless recursion - return; - - //try to create parent folders first - const Zstring dirParent = directory.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - if (!dirParent.empty() && !FreeFileSync::dirExists(dirParent)) - { - //call function recursively - const Zstring templateParent = templateDir.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR); - createDirectoryRecursively(dirParent, templateParent, false, level + 1); //don't create symbolic links in recursion! - } - - //now creation should be possible -#ifdef FFS_WIN - const DWORD templateAttr = templateDir.empty() ? - INVALID_FILE_ATTRIBUTES : - ::GetFileAttributes(applyLongPathPrefix(templateDir).c_str()); //returns successful for broken symlinks - if (templateAttr == INVALID_FILE_ATTRIBUTES) //fallback - { - if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string - NULL) && level == 0) - { - const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - } - else - { - const bool isSymlink = (templateAttr & FILE_ATTRIBUTE_REPARSE_POINT) != 0; //syntax required to shut MSVC up - - //symbolic link handling - if (!copyDirectorySymLinks && isSymlink) //create directory based on target of symbolic link - { - //get target directory of symbolic link - const Zstring linkPath = resolveDirectorySymlink(templateDir); - if (linkPath.empty()) - { - if (level == 0) - throw FileError(wxString(_("Error resolving symbolic link:")) + wxT("\n\"") + templateDir.c_str() + wxT("\"")); - } - else - { - 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) && level == 0) - { - const wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - //(try to) copy additional metadata like last modification time: no measurable performance drawback - //copyAdditionalMetadata(linkPath, directory); - } - } - else //in all other cases - { - if (!::CreateDirectoryEx( // this function automatically copies symbolic links if encountered - applyLongPathPrefix(templateDir).c_str(), // pointer to path string of template directory - applyLongPathPrefixCreateDir(directory).c_str(), // pointer to a directory path string - NULL) && level == 0) - { - const wxString errorMessage = isSymlink ? - //give a more meaningful errormessage if copying a symbolic link failed, e.g. "C:\Users\ZenJu\Application Data" - (wxString(_("Error copying symbolic link:")) + wxT("\n\"") + templateDir.c_str() + wxT("\" ->\n\"") + directory.c_str() + wxT("\"")) : - - (wxString(_("Error creating directory:")) + wxT("\n\"") + directory.c_str() + wxT("\"")); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - - if (copyDirectorySymLinks && isSymlink) //we need to copy the Symbolic Link's change date manually - { - struct TryCleanUp - { - static void tryDeleteLink(const Zstring& linkname) //throw () - { - try - { - removeDirectory(linkname); - } - catch (...) {} - } - }; - //allow only consistent objects to be created -> don't place before ::CreateDirectoryEx, targetLink may already exist - Loki::ScopeGuard guardTargetLink = Loki::MakeGuard(&TryCleanUp::tryDeleteLink, directory); - - if (!copyFileTimes(templateDir, directory, false)) - { - wxString errorMessage = wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - guardTargetLink.Dismiss(); - } - - //(try to) copy additional metadata like last modification time: no measurable performance drawback - //copyAdditionalMetadata(templateDir, directory); - } - } -#elif defined FFS_LINUX - //symbolic link handling - if ( copyDirectorySymLinks && - !templateDir.empty() && - symlinkExists(templateDir)) - //there is no directory-type symlink in Linux! => just copy as file - return copySymlinkInternal(templateDir, directory); //throw (FileError) - - //default directory creation - if (::mkdir(directory.c_str(), 0755) != 0 && level == 0) - { - wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - -//copy directory permissions: not sure if this is a good idea: if a directory is read-only copying/sync'ing of files will fail... - /* - if (templateDirExists) - { - struct stat fileInfo; - if (stat(templateDir.c_str(), &fileInfo) != 0) //read permissions from template directory - throw FileError(Zstring(_("Error reading file attributes:")) + wxT("\n\"") + templateDir + wxT("\"")); - - // reset the umask as we want to create the directory with exactly the same permissions as the template - wxCHANGE_UMASK(0); - - if (mkdir(directory.c_str(), fileInfo.st_mode) != 0 && level == 0) - throw FileError(Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"")); - } - else - { - if (mkdir(directory.c_str(), 0777) != 0 && level == 0) - throw FileError(Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"")); - } - */ -#endif -} - - -void FreeFileSync::createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyDirectorySymLinks) -{ - //remove trailing separator - const Zstring dirFormatted = directory.EndsWith(globalFunctions::FILE_NAME_SEPARATOR) ? - directory.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR) : - directory; - - const Zstring templateFormatted = templateDir.EndsWith(globalFunctions::FILE_NAME_SEPARATOR) ? - templateDir.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR) : - templateDir; - - createDirectoryRecursively(dirFormatted, templateFormatted, copyDirectorySymLinks, 0); -} - - -namespace -{ -Zstring createTempName(const Zstring& filename) -{ - Zstring output = filename + FreeFileSync::TEMP_FILE_ENDING; - - //ensure uniqueness - for (int i = 1; FreeFileSync::somethingExists(output); ++i) - output = filename + DefaultChar('_') + numberToZstring(i) + FreeFileSync::TEMP_FILE_ENDING; - - 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, - LARGE_INTEGER streamSize, - LARGE_INTEGER streamBytesTransferred, - DWORD dwStreamNumber, - DWORD dwCallbackReason, - HANDLE hSourceFile, - HANDLE hDestinationFile, - LPVOID lpData) -{ - using FreeFileSync::CopyFileCallback; - - //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 (lpData != NULL) - { - //some odd check for some possible(?) error condition - if (totalBytesTransferred.HighPart < 0) //let's see if someone answers the call... - ::MessageBox(NULL, wxT("You've just discovered a bug in WIN32 API function \"CopyFileEx\"! \n\n\ - Please write a mail to the author of FreeFileSync at zhnmju123@gmx.de and simply state that\n\ - \"totalBytesTransferred.HighPart can be below zero\"!\n\n\ - This will then be handled in future versions of FreeFileSync.\n\nThanks -ZenJu"), - NULL, 0); - - - CopyFileCallback* callback = static_cast<CopyFileCallback*>(lpData); - - switch (callback->updateCopyStatus(wxULongLong(totalBytesTransferred.HighPart, totalBytesTransferred.LowPart))) - { - case CopyFileCallback::CONTINUE: - break; - case CopyFileCallback::CANCEL: - return PROGRESS_CANCEL; - } - } - } - - return PROGRESS_CONTINUE; -} - - -bool supportForSymbolicLinks() -{ - OSVERSIONINFO osvi; - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - - //symbolic links are supported starting with Vista - if (GetVersionEx(&osvi)) - return osvi.dwMajorVersion > 5; //XP has majorVersion == 5, minorVersion == 1, Vista majorVersion == 6 - //overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx - return false; -} - - -#ifndef COPY_FILE_ALLOW_DECRYPTED_DESTINATION -#define COPY_FILE_ALLOW_DECRYPTED_DESTINATION 0x00000008 -#endif - - -bool supportForNonEncryptedDestination() -{ - OSVERSIONINFO osvi; - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - - //encrypted destination is not supported with Windows 2000 - if (GetVersionEx(&osvi)) - return osvi.dwMajorVersion > 5 || - (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion > 0); //2000 has majorVersion == 5, minorVersion == 0 - //overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx - return false; -} -} - - -void FreeFileSync::copyFile(const Zstring& sourceFile, - const Zstring& targetFile, - bool copyFileSymLinks, - FreeFileSync::ShadowCopy* shadowCopyHandler, - FreeFileSync::CopyFileCallback* callback) -{ - //FreeFileSync::fileExists(targetFile) -> avoid this call, performance; - //if target exists (very unlikely, because sync-algorithm deletes it) renaming below will fail! - - - 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; - - //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 - - struct TryCleanUp //ensure cleanup if working with temporary failed! - { - static void tryDeleteFile(const Zstring& filename) //throw () - { - try - { - removeFile(filename); - } - catch (...) {} - } - }; - Loki::ScopeGuard guardTempFile = Loki::MakeGuard(&TryCleanUp::tryDeleteFile, temporary); - - - if (!::CopyFileEx( //same performance as CopyFile() - applyLongPathPrefix(sourceFile).c_str(), - applyLongPathPrefix(temporary).c_str(), - callback != NULL ? copyCallbackInternal : NULL, - callback, - NULL, - copyFlags)) - { - const DWORD lastError = ::GetLastError(); - - //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" - const Zstring shadowFilename(shadowCopyHandler->makeShadowCopy(sourceFile)); - return copyFile(shadowFilename, //transferred bytes is automatically reset when new file is copied - targetFile, - copyFileSymLinks, - NULL, - callback); - } - //assemble error message... - const wxString errorMessage = wxString(_("Error copying file:")) + wxT("\n\"") + sourceFile.c_str() + wxT("\" ->\n\"") + targetFile.c_str() + wxT("\"") + wxT("\n\n"); - throw FileError(errorMessage + FreeFileSync::getLastErrorFormatted(lastError)); - } - - //rename temporary file: do not add anything else here (note specific error handing) - FreeFileSync::renameFile(temporary, targetFile); - - guardTempFile.Dismiss(); //no need to delete temp file anymore - - //copy creation date (last modification date is redundantly written, too, even for symlinks) - copyFileTimes(sourceFile, targetFile, !copyFileSymLinks); //throw() -} - - -#elif defined FFS_LINUX -void FreeFileSync::copyFile(const Zstring& sourceFile, - const Zstring& targetFile, - bool copyFileSymLinks, - CopyFileCallback* callback) -{ - using FreeFileSync::CopyFileCallback; - - //symbolic link handling - if ( copyFileSymLinks && - symlinkExists(sourceFile)) - return copySymlinkInternal(sourceFile, targetFile); //throw (FileError) - - //begin of regular file copy - struct stat fileInfo; - if (::stat(sourceFile.c_str(), &fileInfo) != 0) //read file attributes from source file (resolving symlinks) - { - const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceFile) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - //open sourceFile for reading - FileInput fileIn(sourceFile); //throw FileError() - - //create targetFile and open it for writing - const Zstring temporary = createTempName(targetFile); //use temporary file until a correct date has been set - - //ensure cleanup (e.g. network drop): call BEFORE creating fileOut object! - Loki::ScopeGuard guardTempFile = Loki::MakeGuard(::unlink, temporary); - - FileOutput fileOut(temporary); //throw FileError() - - const size_t BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size - static const boost::scoped_array<char> 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 CopyFileCallback::CONTINUE: - break; - - case CopyFileCallback::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()); - - //close output stream before changing attributes - fileOut.close(); - - //adapt file modification time: - if (!copyFileTimes(sourceFile, temporary, true)) //throw() - { - wxString errorMessage = wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - //rename temporary file - FreeFileSync::renameFile(temporary, targetFile); - guardTempFile.Dismiss(); - - //ensure cleanup: - Loki::ScopeGuard guardTargetFile = Loki::MakeGuard(::unlink, targetFile.c_str()); //don't use Utility::CleanUp here - - //set file access rights - if (::chmod(targetFile.c_str(), fileInfo.st_mode) != 0) - { - const wxString errorMessage = wxString(_("Error writing file attributes:")) + wxT("\n\"") + zToWx(targetFile) + wxT("\""); - throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()); - } - - guardTargetFile.Dismiss(); //target has been created successfully! -} -#endif - - - -/* -#ifdef FFS_WIN -inline -Zstring getDriveName(const Zstring& directoryName) //GetVolume() doesn't work under Linux! -{ - const Zstring volumeName = wxFileName(directoryName.c_str()).GetVolume().c_str(); - if (volumeName.empty()) - return Zstring(); - - return volumeName + wxFileName::GetVolumeSeparator().c_str() + globalFunctions::FILE_NAME_SEPARATOR; -} - - -bool FreeFileSync::isFatDrive(const Zstring& directoryName) -{ - const Zstring driveName = getDriveName(directoryName); - if (driveName.empty()) - return false; - - wxChar fileSystem[32]; - if (!GetVolumeInformation(driveName.c_str(), NULL, 0, NULL, NULL, NULL, fileSystem, 32)) - return false; - - return Zstring(fileSystem).StartsWith(wxT("FAT")); -} -#endif //FFS_WIN -*/ |