diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:08:06 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:08:06 +0200 |
commit | fbe76102e941b9f1edaf236788e42678f05fdf9a (patch) | |
tree | f5f538316019fa89be8dc478103490c3a826f3ac /shared/file_handling.cpp | |
parent | 3.8 (diff) | |
download | FreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.tar.gz FreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.tar.bz2 FreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.zip |
3.9
Diffstat (limited to 'shared/file_handling.cpp')
-rw-r--r-- | shared/file_handling.cpp | 1855 |
1 files changed, 1855 insertions, 0 deletions
diff --git a/shared/file_handling.cpp b/shared/file_handling.cpp new file mode 100644 index 00000000..3f56abe4 --- /dev/null +++ b/shared/file_handling.cpp @@ -0,0 +1,1855 @@ +// ************************************************************************** +// * 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 "file_handling.h" +#include <wx/intl.h> +#include "system_func.h" +#include "global_func.h" +#include "system_constants.h" +#include "file_traverser.h" +#include <boost/bind.hpp> +#include <algorithm> +#include <wx/datetime.h> +#include "string_conv.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" +#include <map> + +#ifdef FFS_WIN +#include "dll_loader.h" +#include <wx/msw/wrapwin.h> //includes "windows.h" +#include "long_path_prefix.h" +#include <Aclapi.h> + +#elif defined FFS_LINUX +#include <sys/stat.h> +#include "file_io.h" +#include <time.h> +#include <utime.h> +#include <cerrno> +#include <sys/time.h> +#endif + +using ffs3::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; + } + + 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 = wxString() + 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 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(common::FILE_NAME_SEPARATOR))) + dirnameTmp += zToWx(common::FILE_NAME_SEPARATOR); + + return wxToZ(dirnameTmp); +} + + +bool ffs3::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 ffs3::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 ffs3::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 ffs3::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(ffs3::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\"") + ffs3::zToWx(linkName) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); +} +} +#endif + + +wxULongLong ffs3::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") + ffs3::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") + ffs3::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(common::FILE_NAME_SEPARATOR)) + volumePath += common::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(common::FILE_NAME_SEPARATOR)) //exception: allow '/' + volumePathName = volumePathName.BeforeLast(common::FILE_NAME_SEPARATOR); + + struct stat fileInfo; + while (::lstat(volumePathName.c_str(), &fileInfo) != 0) + { + volumePathName = volumePathName.BeforeLast(common::FILE_NAME_SEPARATOR); //returns empty string if ch not found + if (volumePathName.empty()) + return 0; //this includes path "/" also! + } + + return fileInfo.st_dev; +} +#endif +} + + +ffs3::ResponseSameVol ffs3::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 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 + 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") + 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()); + } +#endif +} + + +namespace +{ +struct ErrorDifferentVolume : public ffs3::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 ffs3; //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") + ffs3::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") + ffs3::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 = ffs3::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(common::FILE_NAME_SEPARATOR) != Zstring::npos ? + (fileName.BeforeLast(common::FILE_NAME_SEPARATOR) + common::FILE_NAME_SEPARATOR) : Zstring(); + + Zstring extension = fileName.AfterLast(common::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 (!ffs3::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 ffs3; + + if (newName.Find(common::FILE_NAME_SEPARATOR) == Zstring::npos) + return false; + + if (ffs3::somethingExists(newName)) //name OR directory! + { + 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 + + if ( !fileNameShort.empty() && + !fileNameLong.empty() && + cmpFileName(fileNameOrig, fileNameShort) == 0 && + cmpFileName(fileNameShort, 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(common::FILE_NAME_SEPARATOR) + common::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 ffs3::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 ffs3::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 ffs3::MoveFileCallback; + +class CopyCallbackImpl : public ffs3::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 ffs3::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 + false, //dont copy filesystem permissions +#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 ffs3::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 ffs3; + + //handle symbolic links + if (symlinkExists(sourceDir)) + { + createDirectory(targetDir, sourceDir, true, false); //copy symbolic link, don't copy permissions + 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, false); //throw (FileError); don't copy permissions + } + + //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(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 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 ffs3::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(common::FILE_NAME_SEPARATOR) ? //exception: allow '/' + sourceDir.BeforeLast(common::FILE_NAME_SEPARATOR) : + sourceDir; + const Zstring targetDirFormatted = //remove trailing slash + targetDir.size() > 1 && targetDir.EndsWith(common::FILE_NAME_SEPARATOR) ? //exception: allow '/' + targetDir.BeforeLast(common::FILE_NAME_SEPARATOR) : + targetDir; +#endif + + ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, ignoreExistingDirs, callback); +} + + +class FilesDirsOnlyTraverser : public ffs3::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 ffs3::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") + ffs3::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") + ffs3::getLastErrorFormatted()); + } + return; + } + + std::vector<Zstring> fileList; + std::vector<Zstring> dirList; + + //get all files and directories from current directory (WITHOUT subdirectories!) + FilesDirsOnlyTraverser traverser(fileList, dirList); + ffs3::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") + ffs3::getLastErrorFormatted()); + } +} + + +//optionally: copy directory last change date, DO NOTHING if something fails +void ffs3::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, bool deRefSymlinks) //throw (FileError) +{ +#ifdef FFS_WIN + FILETIME creationTime = {0}; + FILETIME lastAccessTime = {0}; + FILETIME lastWriteTime = {0}; + + { + WIN32_FILE_ATTRIBUTE_DATA sourceAttr; + if (!::GetFileAttributesEx(applyLongPathPrefix(sourceObj).c_str(), //__in LPCTSTR lpFileName, + GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, + &sourceAttr)) //__out LPVOID lpFileInformation + { + const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceObj) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + + if ((sourceAttr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && deRefSymlinks) //we have a symlink AND need to dereference... + { + HANDLE hSource = ::CreateFile(applyLongPathPrefix(sourceObj).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 (hSource == INVALID_HANDLE_VALUE) + { + const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceObj) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + + boost::shared_ptr<void> dummy(hSource, ::CloseHandle); + + if (!::GetFileTime(hSource, //__in HANDLE hFile, + &creationTime, //__out_opt LPFILETIME lpCreationTime, + &lastAccessTime, //__out_opt LPFILETIME lpLastAccessTime, + &lastWriteTime)) //__out_opt LPFILETIME lpLastWriteTime + { + const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(sourceObj) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + } + else + { + creationTime = sourceAttr.ftCreationTime; + lastAccessTime = sourceAttr.ftLastAccessTime; + lastWriteTime = sourceAttr.ftLastWriteTime; + } + } + + HANDLE hTarget = ::CreateFile(applyLongPathPrefix(targetObj).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 (hTarget == INVALID_HANDLE_VALUE) + { + wxString errorMessage = wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(targetObj) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + + boost::shared_ptr<void> dummy(hTarget, ::CloseHandle); + + if (!::SetFileTime(hTarget, + &creationTime, + &lastAccessTime, + &lastWriteTime)) //return value not evalutated! + { + wxString errorMessage = wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(targetObj) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + +#elif defined FFS_LINUX + if (deRefSymlinks) + { + 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; + newTimes.actime = objInfo.st_atime; + newTimes.modtime = objInfo.st_mtime; + + //(try to) set new "last write time" + if (::utime(targetObj.c_str(), &newTimes) != 0) //return value not evalutated! + { + wxString errorMessage = wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(targetObj) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + } + else + { + 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]; + newTimes[0].tv_sec = objInfo.st_atime; /* seconds */ + newTimes[0].tv_usec = 0; /* microseconds */ + + newTimes[1].tv_sec = objInfo.st_mtime; /* seconds */ + newTimes[1].tv_usec = 0; /* microseconds */ + + if (::lutimes(targetObj.c_str(), newTimes) != 0) //return value not evalutated! + { + wxString errorMessage = wxString(_("Error changing modification time:")) + wxT("\n\"") + zToWx(targetObj) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + } +#endif +} + + +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(ffs3::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 = + util::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; +} + + +#elif defined FFS_LINUX +void copySymlinkInternal(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw (FileError) +{ + using namespace ffs3; + + //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()); + } + + //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist + Loki::ScopeGuard guardTargetLink = Loki::MakeGuard(::unlink, targetLink); + + copyFileTimes(sourceLink, targetLink, false); //throw (FileError) + + if (copyFilePermissions) + copyObjectPermissions(sourceLink, targetLink, false); //throw FileError() + + guardTargetLink.Dismiss(); +} +#endif +} + + +namespace ffs3 +{ +#ifdef FFS_WIN +class Privileges +{ +public: + static Privileges& getInstance() + { + static Privileges instance; + return instance; + } + + void ensureActive(LPCTSTR privilege) //throw FileError() + { + if (activePrivileges.find(privilege) != activePrivileges.end()) + return; //privilege already active + + if (privilegeIsActive(privilege)) //privilege was already active before starting this tool + activePrivileges.insert(std::make_pair(privilege, false)); + else + { + setPrivilege(privilege, true); + activePrivileges.insert(std::make_pair(privilege, true)); + } + } + +private: + Privileges() {} + Privileges(Privileges&); + void operator=(Privileges&); + + ~Privileges() //clean up: deactivate all privileges that have been activated by this application + { + for (PrivBuffType::const_iterator i = activePrivileges.begin(); i != activePrivileges.end(); ++i) + try + { + if (i->second) + Privileges::setPrivilege(i->first, false); + } + catch(...) {} + } + + static bool privilegeIsActive(LPCTSTR privilege); //throw FileError() + static void setPrivilege(LPCTSTR privilege, bool enable); //throw FileError() + + typedef std::map<Zstring, bool> PrivBuffType; //bool: enabled by this application + + PrivBuffType activePrivileges; +}; + + +bool Privileges::privilegeIsActive(LPCTSTR privilege) //throw FileError() +{ + HANDLE hToken = NULL; + if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, + TOKEN_QUERY, //__in DWORD DesiredAccess, + &hToken)) //__out PHANDLE TokenHandle + { + const wxString errorMessage = wxString(_("Error setting privilege:")) + wxT(" \"") + privilege + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted()); + } + boost::shared_ptr<void> dummy(hToken, ::CloseHandle); + + LUID luid = {0}; + if (!::LookupPrivilegeValue( + NULL, //__in_opt LPCTSTR lpSystemName, + privilege, //__in LPCTSTR lpName, + &luid )) //__out PLUID lpLuid + { + const wxString errorMessage = wxString(_("Error setting privilege:")) + wxT(" \"") + privilege + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted()); + } + + PRIVILEGE_SET priv = {0}; + priv.PrivilegeCount = 1; + priv.Control = PRIVILEGE_SET_ALL_NECESSARY; + priv.Privilege[0].Luid = luid; + priv.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED; + + BOOL alreadyGranted = FALSE; + if (!::PrivilegeCheck( + hToken, //__in HANDLE ClientToken, + &priv, //__inout PPRIVILEGE_SET RequiredPrivileges, + &alreadyGranted)) //__out LPBOOL pfResult + { + const wxString errorMessage = wxString(_("Error setting privilege:")) + wxT(" \"") + privilege + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted()); + } + + return alreadyGranted == TRUE; +} + + +void Privileges::setPrivilege(LPCTSTR privilege, bool enable) //throw FileError() +{ + HANDLE hToken = NULL; + if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, + TOKEN_ADJUST_PRIVILEGES, //__in DWORD DesiredAccess, + &hToken)) //__out PHANDLE TokenHandle + { + const wxString errorMessage = wxString(_("Error setting privilege:")) + wxT(" \"") + privilege + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted()); + } + boost::shared_ptr<void> dummy(hToken, ::CloseHandle); + + LUID luid = {0}; + if (!::LookupPrivilegeValue( + NULL, //__in_opt LPCTSTR lpSystemName, + privilege, //__in LPCTSTR lpName, + &luid )) //__out PLUID lpLuid + { + const wxString errorMessage = wxString(_("Error setting privilege:")) + wxT(" \"") + privilege + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted()); + } + + TOKEN_PRIVILEGES tp = {0}; + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; + + if (!::AdjustTokenPrivileges( + hToken, //__in HANDLE TokenHandle, + false, //__in BOOL DisableAllPrivileges, + &tp, //__in_opt PTOKEN_PRIVILEGES NewState, + 0, //__in DWORD BufferLength, + NULL, //__out_opt PTOKEN_PRIVILEGES PreviousState, + NULL)) //__out_opt PDWORD ReturnLength + { + const wxString errorMessage = wxString(_("Error setting privilege:")) + wxT(" \"") + privilege + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted()); + } + + if (::GetLastError() == ERROR_NOT_ALL_ASSIGNED) //check although previous function returned with success! + { + const wxString errorMessage = wxString(_("Error setting privilege:")) + wxT(" \"") + privilege + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted()); + } +} +#endif +} + + +//copy permissions for files, directories or symbolic links +void ffs3::copyObjectPermissions(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw FileError(); probably requires admin rights +{ +#ifdef FFS_WIN + //enable privilege: required to read/write SACL information + Privileges::getInstance().ensureActive(SE_SECURITY_NAME); //polling allowed... + + //enable privilege: required to copy owner information + Privileges::getInstance().ensureActive(SE_RESTORE_NAME); + + //the following privilege may be required according to http://msdn.microsoft.com/en-us/library/aa364399(VS.85).aspx (although not needed nor active in my tests) + Privileges::getInstance().ensureActive(SE_BACKUP_NAME); + + PSECURITY_DESCRIPTOR buffer = NULL; + PSID owner = NULL; + PSID group = NULL; + PACL dacl = NULL; + PACL sacl = NULL; + + //http://msdn.microsoft.com/en-us/library/aa364399(v=VS.85).aspx + const HANDLE hSource = ::CreateFile(ffs3::applyLongPathPrefix(source).c_str(), + READ_CONTROL | ACCESS_SYSTEM_SECURITY, //ACCESS_SYSTEM_SECURITY required for SACL access + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | (derefSymlinks ? 0 : FILE_FLAG_OPEN_REPARSE_POINT), //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory + NULL); + if (hSource == INVALID_HANDLE_VALUE) + throw FileError(wxString(_("Error opening file:")) + wxT("\n\"") + zToWx(source) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted()); + boost::shared_ptr<void> dummy(hSource, ::CloseHandle); + +// DWORD rc = ::GetNamedSecurityInfo(const_cast<WCHAR*>(applyLongPathPrefix(source).c_str()), -> does NOT dereference symlinks! + DWORD rc = ::GetSecurityInfo( + hSource, //__in LPTSTR pObjectName, + SE_FILE_OBJECT, //__in SE_OBJECT_TYPE ObjectType, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInfo, + &owner, //__out_opt PSID *ppsidOwner, + &group, //__out_opt PSID *ppsidGroup, + &dacl, //__out_opt PACL *ppDacl, + &sacl, //__out_opt PACL *ppSacl, + &buffer); //__out_opt PSECURITY_DESCRIPTOR *ppSecurityDescriptor + if (rc != ERROR_SUCCESS) + { + const wxString errorMessage = wxString(_("Error copying file permissions:")) + wxT("\n\"") + zToWx(source) + wxT("\" ->\n\"") + zToWx(target) + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted(rc)); + } + boost::shared_ptr<void> dummy2(buffer, ::LocalFree); + + + const Zstring targetFmt = ffs3::applyLongPathPrefix(target); + + //read-only file attribute may cause trouble: temporarily reset it + const DWORD targetAttr = ::GetFileAttributes(targetFmt.c_str()); + Loki::ScopeGuard resetAttributes = Loki::MakeGuard(::SetFileAttributes, targetFmt, targetAttr); + if ( targetAttr != INVALID_FILE_ATTRIBUTES && + (targetAttr & FILE_ATTRIBUTE_READONLY)) + ::SetFileAttributes(targetFmt.c_str(), targetAttr & (~FILE_ATTRIBUTE_READONLY)); //try to... + else + resetAttributes.Dismiss(); + + + HANDLE hTarget = ::CreateFile( targetFmt.c_str(), // lpFileName + FILE_GENERIC_WRITE | WRITE_OWNER | WRITE_DAC | ACCESS_SYSTEM_SECURITY, // dwDesiredAccess: all four seem to be required!!! + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // dwShareMode + NULL, // lpSecurityAttributes + OPEN_EXISTING, // dwCreationDisposition + FILE_FLAG_BACKUP_SEMANTICS | (derefSymlinks ? 0 : FILE_FLAG_OPEN_REPARSE_POINT), // dwFlagsAndAttributes + NULL); // hTemplateFile + if (hTarget == INVALID_HANDLE_VALUE) + throw FileError(wxString(_("Error opening file:")) + wxT("\n\"") + zToWx(target) + wxT("\"") + + wxT("\n\n") + ffs3::getLastErrorFormatted()); + boost::shared_ptr<void> dummy3(hTarget, ::CloseHandle); + +// rc = ::SetNamedSecurityInfo(const_cast<WCHAR*>(applyLongPathPrefix(target).c_str()), //__in LPTSTR pObjectName, -> does NOT dereference symlinks! + rc = ::SetSecurityInfo( + hTarget, //__in LPTSTR pObjectName, + SE_FILE_OBJECT, //__in SE_OBJECT_TYPE ObjectType, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInfo, + owner, //__in_opt PSID psidOwner, + group, //__in_opt PSID psidGroup, + dacl, //__in_opt PACL pDacl, + sacl); //__in_opt PACL pSacl + + if (rc != ERROR_SUCCESS) + { + const wxString errorMessage = wxString(_("Error copying file permissions:")) + wxT("\n\"") + zToWx(source) + wxT("\" ->\n\"") + zToWx(target) + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted(rc)); + } + +#elif defined FFS_LINUX + if (derefSymlinks) + { + struct stat fileInfo; + 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) + { + const wxString errorMessage = wxString(_("Error copying file permissions:")) + wxT("\n\"") + zToWx(source) + wxT("\" ->\n\"") + zToWx(target) + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted()); + } + } + else + { + struct stat fileInfo; + 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() + { + const wxString errorMessage = wxString(_("Error copying file permissions:")) + wxT("\n\"") + zToWx(source) + wxT("\" ->\n\"") + zToWx(target) + wxT("\"") + wxT("\n\n"); + throw FileError(errorMessage + ffs3::getLastErrorFormatted()); + } + } +#endif +} + + +void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyDirectorySymLinks, bool copyFilePermissions, int level) +{ + using namespace ffs3; + + if (ffs3::dirExists(directory)) + return; + + if (level == 100) //catch endless recursion + return; + + //try to create parent folders first + const Zstring dirParent = directory.BeforeLast(common::FILE_NAME_SEPARATOR); + if (!dirParent.empty() && !ffs3::dirExists(dirParent)) + { + //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! + } + + struct TryCleanUp + { + static void tryDeleteDir(const Zstring& linkname) //throw () + { + try + { + removeDirectory(linkname); + } + catch (...) {} + } + }; + + //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)) + { + 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()); + } + } + 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) return; + throw FileError(wxString(_("Error resolving symbolic link:")) + wxT("\n\"") + templateDir.c_str() + wxT("\"")); + } + + 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()); + } + } + 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)) + { + if (level != 0) return; + 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") + ffs3::getLastErrorFormatted()); + } + } + + //ensure cleanup: + Loki::ScopeGuard guardNewDir = Loki::MakeGuard(&TryCleanUp::tryDeleteDir, directory); + + if (copyDirectorySymLinks && isSymlink) //we need to copy the Symbolic Link's change date manually + copyFileTimes(templateDir, directory, false); //throw (FileError) + + if (copyFilePermissions) + copyObjectPermissions(templateDir, directory, !copyDirectorySymLinks); //throw FileError() + + guardNewDir.Dismiss(); //target has been created successfully! + } +#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, copyFilePermissions); //throw (FileError) + + //default directory creation + if (::mkdir(directory.c_str(), 0755) != 0) + { + if (level != 0) return; + wxString errorMessage = wxString(_("Error creating directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""); + throw FileError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted()); + } + + //ensure cleanup: + Loki::ScopeGuard guardNewDir = Loki::MakeGuard(&TryCleanUp::tryDeleteDir, directory); + + if (!templateDir.empty() && copyFilePermissions) + copyObjectPermissions(templateDir, directory, true); //throw FileError() + + guardNewDir.Dismiss(); //target has been created successfully! +#endif +} + + +void ffs3::createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyDirectorySymLinks, bool copyFilePermissions) +{ + //remove trailing separator + const Zstring dirFormatted = directory.EndsWith(common::FILE_NAME_SEPARATOR) ? + directory.BeforeLast(common::FILE_NAME_SEPARATOR) : + directory; + + const Zstring templateFormatted = templateDir.EndsWith(common::FILE_NAME_SEPARATOR) ? + templateDir.BeforeLast(common::FILE_NAME_SEPARATOR) : + templateDir; + + createDirectoryRecursively(dirFormatted, templateFormatted, copyDirectorySymLinks, copyFilePermissions, 0); +} + + +void ffs3::createDirectory(const Zstring& directory) +{ + ffs3::createDirectory(directory, Zstring(), false, false); +} + + +namespace +{ +Zstring createTempName(const Zstring& filename) +{ + Zstring output = filename + ffs3::TEMP_FILE_ENDING; + +#ifndef _MSC_VER +#warning TEMP_FILE_ENDING -> harmonize with other "endings" remove trailing dot +#endif + + //ensure uniqueness + for (int i = 1; ffs3::somethingExists(output); ++i) + output = filename + DefaultChar('_') + numberToZstring(i) + ffs3::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 ffs3::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); + try + { + switch (callback->updateCopyStatus(wxULongLong(totalBytesTransferred.HighPart, totalBytesTransferred.LowPart))) + { + case CopyFileCallback::CONTINUE: + break; + case CopyFileCallback::CANCEL: + return PROGRESS_CANCEL; + } + } + catch (...) + { + ::MessageBox(NULL, wxT("Exception in callback ffs3::copyFile! Please contact the author of FFS."), NULL, 0); + } + } + } + + 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 ffs3::copyFile(const Zstring& sourceFile, + const Zstring& targetFile, + bool copyFileSymLinks, + bool copyFilePermissions, + shadow::ShadowCopy* shadowCopyHandler, + ffs3::CopyFileCallback* callback) +{ + //ffs3::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" + + 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); + } + + return copyFile(shadowFilename, //transferred bytes is automatically reset when new file is copied + targetFile, + copyFileSymLinks, + copyFilePermissions, + 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 + ffs3::getLastErrorFormatted(lastError)); + } + + //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(&TryCleanUp::tryDeleteFile, targetFile); + + if (copyFilePermissions) + copyObjectPermissions(sourceFile, targetFile, !copyFileSymLinks); //throw FileError() + + //copy creation date (last modification date is REDUNDANTLY written, too, even for symlinks) + copyFileTimes(sourceFile, targetFile, !copyFileSymLinks); //throw (FileError) + + guardTargetFile.Dismiss(); +} + + +#elif defined FFS_LINUX +void ffs3::copyFile(const Zstring& sourceFile, + const Zstring& targetFile, + bool copyFileSymLinks, + bool copyFilePermissions, + CopyFileCallback* callback) +{ + using ffs3::CopyFileCallback; + + //symbolic link handling + if ( copyFileSymLinks && + symlinkExists(sourceFile)) + { + return copySymlinkInternal(sourceFile, targetFile, copyFilePermissions); //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") + ffs3::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(); + + //rename temporary file + ffs3::renameFile(temporary, targetFile); + guardTempFile.Dismiss(); + + //ensure cleanup: + Loki::ScopeGuard guardTargetFile = Loki::MakeGuard(::unlink, targetFile.c_str()); + + //adapt file modification time: + copyFileTimes(sourceFile, targetFile, true); //throw (FileError) + + //set file permissions + if (copyFilePermissions) + copyObjectPermissions(sourceFile, targetFile, true); //throw FileError() + + 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() + common::FILE_NAME_SEPARATOR; +} + + +bool ffs3::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 +*/ |