summaryrefslogtreecommitdiff
path: root/shared/file_handling.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'shared/file_handling.cpp')
-rw-r--r--shared/file_handling.cpp2075
1 files changed, 0 insertions, 2075 deletions
diff --git a/shared/file_handling.cpp b/shared/file_handling.cpp
deleted file mode 100644
index c9e85c15..00000000
--- a/shared/file_handling.cpp
+++ /dev/null
@@ -1,2075 +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-2011 ZenJu (zhnmju123 AT gmx.de) *
-// **************************************************************************
-
-#include "file_handling.h"
-#include <map>
-#include <algorithm>
-#include <stdexcept>
-#include "last_error.h"
-#include "file_traverser.h"
-#include "loki/ScopeGuard.h"
-#include "symlink_target.h"
-#include "file_io.h"
-#include "i18n.h"
-#include "assert_static.h"
-#include <boost/thread/tss.hpp>
-#include <boost/thread/once.hpp>
-#include "file_id_internal.h"
-
-#ifdef FFS_WIN
-#include "privilege.h"
-#include "dll_loader.h"
-#include <wx/msw/wrapwin.h> //includes "windows.h"
-#include "long_path_prefix.h"
-#include <Aclapi.h>
-#include "dst_hack.h"
-#include "file_update_handle.h"
-
-#elif defined FFS_LINUX
-#include <sys/stat.h>
-#include <time.h>
-#include <utime.h>
-#include <cerrno>
-#include <sys/time.h>
-
-#ifdef HAVE_SELINUX
-#include <selinux/selinux.h>
-#endif
-#endif
-
-using namespace zen;
-
-
-bool zen::fileExists(const Zstring& filename)
-{
- //symbolic links (broken or not) are also treated as existing files!
-#ifdef FFS_WIN
- 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 zen::dirExists(const Zstring& dirname)
-{
- //symbolic links (broken or not) are also treated as existing directories!
-#ifdef FFS_WIN
- 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 zen::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 zen::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
-}
-
-
-namespace
-{
-void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl) //throw FileError
-{
-#ifdef FFS_WIN
- WIN32_FIND_DATA fileInfo = {};
- {
- const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(filename).c_str(), &fileInfo);
- if (searchHandle == INVALID_HANDLE_VALUE)
- throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
- ::FindClose(searchHandle);
- }
- // 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 bool isSymbolicLink = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
- if (!isSymbolicLink || procSl == SYMLINK_DIRECT)
- {
- //####################################### DST hack ###########################################
- const bool isDirectory = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
- if (!isDirectory && dst::isFatDrive(filename)) //throw()
- {
- const dst::RawTime rawTime(fileInfo.ftCreationTime, fileInfo.ftLastWriteTime);
- if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error)
- {
- fileInfo.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error)
- ::GetSystemTimeAsFileTime(&fileInfo.ftCreationTime); //real creation time information is not available...
- }
- }
- //####################################### DST hack ###########################################
-
- attr.fileSize = UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh);
- attr.modificationTime = toTimeT(fileInfo.ftLastWriteTime);
- }
- else
- {
- const HANDLE hFile = ::CreateFile(applyLongPathPrefix(filename).c_str(), //open handle to target of symbolic link
- 0,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- 0,
- OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS,
- NULL);
- if (hFile == INVALID_HANDLE_VALUE)
- throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
- LOKI_ON_BLOCK_EXIT2(::CloseHandle(hFile));
-
- BY_HANDLE_FILE_INFORMATION fileInfoHnd = {};
- if (!::GetFileInformationByHandle(hFile, &fileInfoHnd))
- throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
-
- attr.fileSize = UInt64(fileInfoHnd.nFileSizeLow, fileInfoHnd.nFileSizeHigh);
- attr.modificationTime = toTimeT(fileInfoHnd.ftLastWriteTime);
- }
-
-#elif defined FFS_LINUX
- struct stat fileInfo = {};
-
- const int rv = procSl == SYMLINK_FOLLOW ?
- :: stat(filename.c_str(), &fileInfo) :
- ::lstat(filename.c_str(), &fileInfo);
- if (rv != 0) //follow symbolic links
- throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
-
- attr.fileSize = UInt64(fileInfo.st_size);
- attr.modificationTime = fileInfo.st_mtime;
-#endif
-}
-}
-
-
-UInt64 zen::getFilesize(const Zstring& filename) //throw FileError
-{
- FileAttrib attr;
- getFileAttrib(filename, attr, SYMLINK_FOLLOW); //throw FileError
- return attr.fileSize;
-}
-
-
-Int64 zen::getFileTime(const Zstring& filename, ProcSymlink procSl) //throw FileError
-{
- FileAttrib attr;
- getFileAttrib(filename, attr, procSl); //throw FileError
- return attr.modificationTime;
-}
-
-
-namespace
-{
-
-
-#ifdef FFS_WIN
-DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error!
-{
- std::vector<wchar_t> buffer(10000);
-
- //full pathName need not yet exist!
- if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName,
- &buffer[0], //__out LPTSTR lpszVolumePathName,
- static_cast<DWORD>(buffer.size()))) //__in DWORD cchBufferLength
- return 0;
-
- Zstring volumePath = &buffer[0];
- if (!volumePath.EndsWith(FILE_NAME_SEPARATOR))
- volumePath += 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(FILE_NAME_SEPARATOR)) //exception: allow '/'
- volumePathName = volumePathName.BeforeLast(FILE_NAME_SEPARATOR);
-
- struct stat fileInfo = {};
- while (::lstat(volumePathName.c_str(), &fileInfo) != 0) //go up in folder hierarchy until existing folder is found
- {
- volumePathName = volumePathName.BeforeLast(FILE_NAME_SEPARATOR); //returns empty string if ch not found
- if (volumePathName.empty())
- return 0; //this includes path "/" also!
- }
-
- return fileInfo.st_dev;
-}
-#endif
-}
-
-
-zen::ResponseSame zen::onSameVolume(const Zstring& folderLeft, const Zstring& folderRight) //throw()
-{
- const auto serialLeft = retrieveVolumeSerial(folderLeft); //returns 0 on error!
- const auto serialRight = retrieveVolumeSerial(folderRight); //returns 0 on error!
- if (serialLeft == 0 || serialRight == 0)
- return IS_SAME_CANT_SAY;
-
- return serialLeft == serialRight ? IS_SAME_YES : IS_SAME_NO;
-}
-
-
-bool zen::removeFile(const Zstring& filename) //throw FileError;
-{
-#ifdef FFS_WIN
- //remove file, support for \\?\-prefix
- const Zstring filenameFmt = applyLongPathPrefix(filename);
- if (!::DeleteFile(filenameFmt.c_str()))
-#elif defined FFS_LINUX
- if (::unlink(filename.c_str()) != 0)
-#endif
- {
-#ifdef FFS_WIN
- //perf: apply ONLY when necessary!
- if (::GetLastError() == ERROR_ACCESS_DENIED) //function fails if file is read-only
- {
- //(try to) normalize file attributes
- ::SetFileAttributes(filenameFmt.c_str(), FILE_ATTRIBUTE_NORMAL);
-
- //now try again...
- if (::DeleteFile(filenameFmt.c_str()))
- return true;
- }
- //eval error code before next call
- DWORD lastError = ::GetLastError();
-#elif defined FFS_LINUX
- int lastError = errno;
-#endif
-
- //no error situation if file is not existing! manual deletion relies on it!
- //perf: check is placed in error handling block
- //warning: this call changes error code!!
- if (!somethingExists(filename))
- return false; //neither file nor any other object (e.g. broken symlink) with that name existing
-
- throw FileError(_("Error deleting file:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted(lastError));
- }
- return true;
-}
-
-
-namespace
-{
-DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume);
-
-/* Usage overview: (avoid circular pattern!)
-
- renameFile() --> renameFile_sub()
- | /|\
- \|/ |
- Fix8Dot3NameClash()
-*/
-//wrapper for file system rename function:
-void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
-{
-#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
- {
- DWORD lastError = ::GetLastError();
- if (lastError == ERROR_ACCESS_DENIED) //MoveFileEx may fail to rename a read-only file on a SAMBA-share -> (try to) handle this
- {
- const DWORD oldAttr = ::GetFileAttributes(oldNameFmt.c_str());
- if (oldAttr != INVALID_FILE_ATTRIBUTES && (oldAttr & FILE_ATTRIBUTE_READONLY))
- {
- if (::SetFileAttributes(oldNameFmt.c_str(), 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(), oldAttr); //don't handle error
- return;
- }
- else
- {
- lastError = ::GetLastError(); //use error code from second call to ::MoveFileEx()
-
- //cleanup: (try to) restore file attributes: assume oldName is still existing
- ::SetFileAttributes(oldNameFmt.c_str(), oldAttr);
- }
- }
- }
- }
-
- std::wstring errorMessage = _("Error moving file:") + "\n\"" + oldName + "\" ->\n\"" + newName + "\"" + "\n\n" + getLastErrorFormatted(lastError);
-
- if (lastError == ERROR_NOT_SAME_DEVICE)
- throw ErrorDifferentVolume(errorMessage);
- else if (lastError == ERROR_FILE_EXISTS)
- throw ErrorTargetExisting(errorMessage);
- else
- throw FileError(errorMessage);
- }
-
-#elif defined FFS_LINUX
- if (::rename(oldName.c_str(), newName.c_str()) != 0)
- {
- const int lastError = errno;
-
- std::wstring errorMessage = _("Error moving file:") + "\n\"" + oldName + "\" ->\n\"" + newName + "\"" + "\n\n" + getLastErrorFormatted(lastError);
-
- if (lastError == EXDEV)
- throw ErrorDifferentVolume(errorMessage);
- else if (lastError == EEXIST)
- throw ErrorTargetExisting(errorMessage);
- else
- throw FileError(errorMessage);
- }
-#endif
-}
-
-
-#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 = applyLongPathPrefix(filename);
-
- const DWORD bufferSize = fun(filenameFmt.c_str(), NULL, 0);
- if (bufferSize == 0)
- return Zstring();
-
- std::vector<wchar_t> buffer(bufferSize);
-
- const DWORD rv = fun(filenameFmt.c_str(), //__in LPCTSTR lpszShortPath,
- &buffer[0], //__out LPTSTR lpszLongPath,
- static_cast<DWORD>(buffer.size())); //__in DWORD cchBuffer
- if (rv == 0 || rv >= buffer.size())
- return Zstring();
-
- return &buffer[0];
-}
-
-
-Zstring findUnused8Dot3Name(const Zstring& filename) //find a unique 8.3 short name
-{
- const Zstring pathPrefix = filename.find(FILE_NAME_SEPARATOR) != Zstring::npos ?
- (filename.BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR) : Zstring();
-
- Zstring extension = filename.AfterLast(FILE_NAME_SEPARATOR).AfterLast(Zchar('.')); //extension needn't contain reasonable data
- if (extension.empty())
- extension = Zstr("FFS");
- extension.Truncate(3);
-
- for (int index = 0; index < 100000000; ++index) //filename must be representable by <= 8 characters
- {
- const Zstring output = pathPrefix + Zstring::fromNumber(index) + Zchar('.') + extension;
- if (!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") + utf8CvrtTo<std::string>(pathPrefix));
-}
-
-
-bool have8dot3NameClash(const Zstring& filename)
-{
- if (filename.find(FILE_NAME_SEPARATOR) == Zstring::npos)
- return false;
-
- if (somethingExists(filename)) //name OR directory!
- {
- const Zstring origName = filename.AfterLast(FILE_NAME_SEPARATOR); //returns the whole string if ch not found
- const Zstring shortName = getFilenameFmt(filename, ::GetShortPathName).AfterLast(FILE_NAME_SEPARATOR); //throw() returns empty string on error
- const Zstring longName = getFilenameFmt(filename, ::GetLongPathName) .AfterLast(FILE_NAME_SEPARATOR); //
-
- if (!shortName.empty() &&
- !longName.empty() &&
- EqualFilename()(origName, shortName) &&
- !EqualFilename()(shortName, longName))
- {
- //for filename short and long file name are equal and another unrelated file happens to have the same short name
- //e.g. filename == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1"
- return true;
- }
- }
- return false;
-}
-
-class Fix8Dot3NameClash
-{
-public:
- Fix8Dot3NameClash(const Zstring& filename)
- {
- const Zstring longName = getFilenameFmt(filename, ::GetLongPathName).AfterLast(FILE_NAME_SEPARATOR); //throw() returns empty string on error
-
- unrelatedFile = filename.BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + longName;
-
- //find another name in short format: this ensures the actual short name WILL be renamed as well!
- unrelatedFileParked = findUnused8Dot3Name(filename);
-
- //move already existing short name out of the way for now
- renameFile_sub(unrelatedFile, unrelatedFileParked); //throw FileError, ErrorDifferentVolume
- //DON'T call renameFile() to avoid reentrance!
- }
-
- ~Fix8Dot3NameClash()
- {
- //the file system should assign this unrelated file a new (unique) short name
- try
- {
- renameFile_sub(unrelatedFileParked, unrelatedFile); //throw FileError, ErrorDifferentVolume
- }
- catch (...) {}
- }
-private:
- Zstring unrelatedFile;
- Zstring unrelatedFileParked;
-};
-#endif
-}
-
-
-//rename file: no copying!!!
-void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
-{
- try
- {
- renameFile_sub(oldName, newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
- }
- catch (const FileError&)
- {
-#ifdef FFS_WIN
- //try to handle issues with already existing short 8.3 file names on Windows
- if (have8dot3NameClash(newName))
- {
- Fix8Dot3NameClash dummy(newName); //move clashing filename to the side
- //now try again...
- renameFile_sub(oldName, newName); //throw FileError
- return;
- }
-#endif
- throw;
- }
-}
-
-
-class CopyCallbackImpl : public zen::CallbackCopyFile //callback functionality
-{
-public:
- CopyCallbackImpl(const Zstring& sourceFile, CallbackMoveFile& callback) : sourceFile_(sourceFile), moveCallback(callback) {}
-
- virtual void deleteTargetFile(const Zstring& targetFile) { assert(!fileExists(targetFile)); }
-
- virtual void updateCopyStatus(UInt64 totalBytesTransferred)
- {
- moveCallback.requestUiRefresh(sourceFile_);
- }
-
-private:
- CopyCallbackImpl(const CopyCallbackImpl&);
- CopyCallbackImpl& operator=(const CopyCallbackImpl&);
-
- const Zstring sourceFile_;
- CallbackMoveFile& moveCallback;
-};
-
-
-void zen::moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError;
-{
- //call back once per file (moveFile() is called by moveDirectory())
- if (callback)
- callback->requestUiRefresh(sourceFile);
-
- const bool targetExisting = fileExists(targetFile);
-
- if (targetExisting && !ignoreExisting) //test file existence: e.g. Linux might silently overwrite existing symlinks
- throw FileError(_("Error moving file:") + "\n\"" + sourceFile + "\" ->\n\"" + targetFile + "\"" +
- "\n\n" + _("Target file already existing!"));
-
- if (!targetExisting)
- {
- //try to move the file directly without copying
- try
- {
- renameFile(sourceFile, targetFile); //throw FileError, ErrorDifferentVolume
- return; //great, we get away cheaply!
- }
- //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file)
- catch (const ErrorDifferentVolume&) {}
-
- //file is on a different volume: let's copy it
- std::unique_ptr<CopyCallbackImpl> copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, *callback) : NULL);
-
- if (symlinkExists(sourceFile))
- copySymlink(sourceFile, targetFile, false); //throw FileError; don't copy filesystem permissions
- else
- copyFile(sourceFile, targetFile, false, true, copyCallback.get()); //throw FileError;
-
- //attention: if copy-operation was cancelled an exception is thrown => sourcefile is not deleted, as we wish!
- }
-
- removeFile(sourceFile); //throw FileError
- //note: copying file is NOT undone in case of exception: currently this function is called in context of user-defined deletion dir, where this behavior is fine
-}
-
-namespace
-{
-class TraverseOneLevel : public zen::TraverseCallback
-{
-public:
- typedef std::pair<Zstring, Zstring> NamePair;
- typedef std::vector<NamePair> NameList;
-
- TraverseOneLevel(NameList& files, NameList& dirs) :
- files_(files),
- dirs_(dirs) {}
-
- virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details)
- {
- files_.push_back(NamePair(shortName, fullName));
- }
-
- virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details)
- {
- if (details.dirLink)
- dirs_.push_back(NamePair(shortName, fullName));
- else
- files_.push_back(NamePair(shortName, fullName));
- }
-
- virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName)
- {
- dirs_.push_back(NamePair(shortName, fullName));
- return Loki::Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs; moveDirectory works recursively!
- }
-
- virtual HandleError onError(const std::wstring& errorText) { throw FileError(errorText); }
-
-private:
- TraverseOneLevel(const TraverseOneLevel&);
- TraverseOneLevel& operator=(const TraverseOneLevel&);
-
- NameList& files_;
- NameList& dirs_;
-};
-
-
-struct RemoveCallbackImpl : public CallbackRemoveDir
-{
- RemoveCallbackImpl(const Zstring& sourceDir,
- CallbackMoveFile& moveCallback) :
- sourceDir_(sourceDir),
- moveCallback_(moveCallback) {}
-
- virtual void notifyFileDeletion(const Zstring& filename) { moveCallback_.requestUiRefresh(sourceDir_); }
- virtual void notifyDirDeletion(const Zstring& dirname) { moveCallback_.requestUiRefresh(sourceDir_); }
-
-private:
- RemoveCallbackImpl(const RemoveCallbackImpl&);
- RemoveCallbackImpl& operator=(const RemoveCallbackImpl&);
-
- const Zstring sourceDir_;
- CallbackMoveFile& moveCallback_;
-};
-}
-
-
-void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError;
-{
- //call back once per folder
- if (callback)
- callback->requestUiRefresh(sourceDir);
-
- const bool targetExisting = dirExists(targetDir);
-
- if (targetExisting && !ignoreExisting) //directory or symlink exists (or even a file... this error will be caught later)
- throw FileError(_("Error moving directory:") + "\n\"" + sourceDir + "\" ->\n\"" + targetDir + "\"" +
- "\n\n" + _("Target directory already existing!"));
-
- const bool isSymlink = symlinkExists(sourceDir);
-
- if (!targetExisting)
- {
- //first try to move the directory directly without copying
- try
- {
- renameFile(sourceDir, targetDir); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
- return; //great, we get away cheaply!
- }
- //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the directory)
- catch (const ErrorDifferentVolume&) {}
-
- //create target
- if (isSymlink)
- copySymlink(sourceDir, targetDir, false); //throw FileError -> don't copy permissions
- else
- createDirectory(targetDir, sourceDir, false); //throw FileError
- }
-
- if (!isSymlink) //handle symbolic links
- {
- //move files/folders recursively
- TraverseOneLevel::NameList fileList; //list of names: 1. short 2.long
- TraverseOneLevel::NameList dirList; //
-
- //traverse source directory one level
- TraverseOneLevel traverseCallback(fileList, dirList);
- traverseFolder(sourceDir, false, traverseCallback); //traverse one level, don't follow symlinks
-
- const Zstring targetDirFormatted = targetDir.EndsWith(FILE_NAME_SEPARATOR) ? //ends with path separator
- targetDir :
- targetDir + FILE_NAME_SEPARATOR;
-
- //move files
- for (TraverseOneLevel::NameList::const_iterator i = fileList.begin(); i != fileList.end(); ++i)
- moveFile(i->second, targetDirFormatted + i->first, ignoreExisting, callback); //throw FileError, ErrorTargetExisting
-
- //move directories
- for (TraverseOneLevel::NameList::const_iterator i = dirList.begin(); i != dirList.end(); ++i)
- ::moveDirectoryImpl(i->second, targetDirFormatted + i->first, ignoreExisting, callback);
-
- //attention: if move-operation was cancelled an exception is thrown => sourceDir is not deleted, as we wish!
- }
-
- //delete source
- std::unique_ptr<RemoveCallbackImpl> removeCallback(callback != NULL ? new RemoveCallbackImpl(sourceDir, *callback) : NULL);
- removeDirectory(sourceDir, removeCallback.get()); //throw FileError;
-}
-
-
-void zen::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* 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(FILE_NAME_SEPARATOR) ? //exception: allow '/'
- sourceDir.BeforeLast(FILE_NAME_SEPARATOR) :
- sourceDir;
- const Zstring targetDirFormatted = //remove trailing slash
- targetDir.size() > 1 && targetDir.EndsWith(FILE_NAME_SEPARATOR) ? //exception: allow '/'
- targetDir.BeforeLast(FILE_NAME_SEPARATOR) :
- targetDir;
-#endif
-
- ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, ignoreExisting, callback);
-}
-
-
-class FilesDirsOnlyTraverser : public zen::TraverseCallback
-{
-public:
- FilesDirsOnlyTraverser(std::vector<Zstring>& files, std::vector<Zstring>& dirs) :
- m_files(files),
- m_dirs(dirs) {}
-
- virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details)
- {
- m_files.push_back(fullName);
- }
- virtual void onSymlink(const Zchar* 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 Zchar* 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 HandleError onError(const std::wstring& errorText) { throw FileError(errorText); }
-
-private:
- FilesDirsOnlyTraverser(const FilesDirsOnlyTraverser&);
- FilesDirsOnlyTraverser& operator=(const FilesDirsOnlyTraverser&);
-
- std::vector<Zstring>& m_files;
- std::vector<Zstring>& m_dirs;
-};
-
-
-void zen::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback)
-{
- //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
-
- //(try to) normalize file attributes: actually NEEDED for symbolic links also!
- ::SetFileAttributes(directoryFmt.c_str(), FILE_ATTRIBUTE_NORMAL);
-#endif
-
- //attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!!
- if (symlinkExists(directory)) //remove symlink directly
- {
-#ifdef FFS_WIN
- if (!::RemoveDirectory(directoryFmt.c_str()))
-#elif defined FFS_LINUX
- if (::unlink(directory.c_str()) != 0)
-#endif
- throw FileError(_("Error deleting directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted());
-
- if (callback)
- callback->notifyDirDeletion(directory); //once per symlink
- return;
- }
-
- std::vector<Zstring> fileList;
- std::vector<Zstring> dirList;
-
- //get all files and directories from current directory (WITHOUT subdirectories!)
- FilesDirsOnlyTraverser traverser(fileList, dirList);
- traverseFolder(directory, false, traverser); //don't follow symlinks
-
- //delete files
- for (std::vector<Zstring>::const_iterator i = fileList.begin(); i != fileList.end(); ++i)
- {
- const bool workDone = removeFile(*i);
- if (callback && workDone)
- callback->notifyFileDeletion(*i); //call once per file
- }
-
- //delete directories recursively
- for (std::vector<Zstring>::const_iterator i = dirList.begin(); i != dirList.end(); ++i)
- removeDirectory(*i, callback); //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
- {
- throw FileError(_("Error deleting directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted());
- }
- if (callback)
- callback->notifyDirDeletion(directory); //and once per folder
-}
-
-
-void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, ProcSymlink procSl) //throw FileError
-{
-#ifdef FFS_WIN
- FILETIME creationTime = {};
- FILETIME lastWriteTime = tofiletime(modificationTime);
-
- //####################################### DST hack ###########################################
- if (dst::isFatDrive(filename)) //throw()
- {
- const dst::RawTime encodedTime = dst::fatEncodeUtcTime(lastWriteTime); //throw (std::runtime_error)
- creationTime = encodedTime.createTimeRaw;
- lastWriteTime = encodedTime.writeTimeRaw;
- }
- //####################################### DST hack ###########################################
-
- //privilege SE_BACKUP_NAME doesn't seem to be required here for symbolic links
- //note: setting privileges requires admin rights!
-
- //opening newly created target file may fail due to some AV-software scanning it: no error, we will wait!
- //http://support.microsoft.com/?scid=kb%3Ben-us%3B316609&x=17&y=20
- //-> enable as soon it turns out it is required!
-
- /*const int retryInterval = 50;
- const int maxRetries = 2000 / retryInterval;
- for (int i = 0; i < maxRetries; ++i)
- {
- */
-
- //may need to remove the readonly-attribute (e.g. FAT usb drives)
- FileUpdateHandle targetHandle(filename, [ = ]()
- {
- return ::CreateFile(applyLongPathPrefix(filename).c_str(),
- GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- 0,
- OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory
- (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks
- NULL);
- });
-
- if (targetHandle.get() == INVALID_HANDLE_VALUE)
- throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
-
- /*
- if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION)
- ::Sleep(retryInterval); //wait then retry
- else //success or unknown failure
- break;
- }
- */
-
- auto isNullTime = [](const FILETIME & ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; };
-
- if (!::SetFileTime(targetHandle.get(),
- isNullTime(creationTime) ? NULL : &creationTime,
- NULL,
- &lastWriteTime))
- throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
-
-#ifndef NDEBUG //dst hack: verify data written
- if (dst::isFatDrive(filename) && !dirExists(filename)) //throw()
- {
- WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {};
- assert(::GetFileAttributesEx(applyLongPathPrefix(filename).c_str(), //__in LPCTSTR lpFileName,
- GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
- &debugeAttr)); //__out LPVOID lpFileInformation
-
- assert(::CompareFileTime(&debugeAttr.ftCreationTime, &creationTime) == 0);
- assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &lastWriteTime) == 0);
- }
-#endif
-
-#elif defined FFS_LINUX
- if (procSl == SYMLINK_FOLLOW)
- {
- struct utimbuf newTimes = {};
- newTimes.actime = ::time(NULL);
- newTimes.modtime = to<time_t>(modificationTime);
-
- // set new "last write time"
- if (::utime(filename.c_str(), &newTimes) != 0)
- throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
- }
- else
- {
- struct timeval newTimes[2] = {};
- newTimes[0].tv_sec = ::time(NULL); /* seconds */
- newTimes[0].tv_usec = 0; /* microseconds */
-
- newTimes[1].tv_sec = to<time_t>(modificationTime);
- newTimes[1].tv_usec = 0;
-
- if (::lutimes(filename.c_str(), newTimes) != 0)
- throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
- }
-#endif
-}
-
-
-namespace
-{
-#ifdef FFS_WIN
-Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target path of symbolic link to a directory; throw (FileError)
-{
- //open handle to target of symbolic link
- const HANDLE hDir = ::CreateFile(applyLongPathPrefix(dirLinkName).c_str(),
- 0,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- 0,
- OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory
- NULL);
- if (hDir == INVALID_HANDLE_VALUE)
- throw FileError(_("Error resolving symbolic link:") + "\n\"" + dirLinkName + "\"" + "\n\n" + getLastErrorFormatted());
- LOKI_ON_BLOCK_EXIT2(::CloseHandle(hDir));
-
- const DWORD BUFFER_SIZE = 10000;
- std::vector<wchar_t> targetPath(BUFFER_SIZE);
-
- //dynamically load windows API function
- typedef DWORD (WINAPI *GetFinalPathNameByHandleWFunc)(HANDLE hFile,
- LPTSTR lpszFilePath,
- DWORD cchFilePath,
- DWORD dwFlags);
- const util::DllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW");
- if (!getFinalPathNameByHandle)
- throw FileError(_("Error loading library function:") + "\n\"" + "GetFinalPathNameByHandleW" + "\"");
-
- const DWORD charsWritten = getFinalPathNameByHandle(hDir, //__in HANDLE hFile,
- &targetPath[0], //__out LPTSTR lpszFilePath,
- BUFFER_SIZE, //__in DWORD cchFilePath,
- FILE_NAME_NORMALIZED); //__in DWORD dwFlags
- if (charsWritten >= BUFFER_SIZE || charsWritten == 0)
- {
- std::wstring errorMessage = _("Error resolving symbolic link:") + "\n\"" + dirLinkName + "\"";
- if (charsWritten == 0)
- errorMessage += L"\n\n" + getLastErrorFormatted();
- throw FileError(errorMessage);
- }
-
- return Zstring(&targetPath[0], charsWritten);
-}
-#endif
-
-
-#ifdef HAVE_SELINUX
-//copy SELinux security context
-void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError
-{
- security_context_t contextSource = NULL;
- const int rv = procSl == SYMLINK_FOLLOW ?
- ::getfilecon(source.c_str(), &contextSource) :
- ::lgetfilecon(source.c_str(), &contextSource);
- if (rv < 0)
- {
- if (errno == ENODATA || //no security context (allegedly) is not an error condition on SELinux
- errno == EOPNOTSUPP) //extended attributes are not supported by the filesystem
- return;
-
- throw FileError(_("Error reading security context:") + "\n\"" + source + "\"" + "\n\n" + getLastErrorFormatted());
- }
- LOKI_ON_BLOCK_EXIT2(::freecon(contextSource));
-
- {
- security_context_t contextTarget = NULL;
- const int rv2 = procSl == SYMLINK_FOLLOW ?
- ::getfilecon(target.c_str(), &contextTarget) :
- ::lgetfilecon(target.c_str(), &contextTarget);
- if (rv2 < 0)
- {
- if (errno == EOPNOTSUPP)
- return;
- //else: still try to set security context
- }
- else
- {
- LOKI_ON_BLOCK_EXIT2(::freecon(contextTarget));
-
- if (::strcmp(contextSource, contextTarget) == 0) //nothing to do
- return;
- }
- }
-
- const int rv3 = procSl == SYMLINK_FOLLOW ?
- ::setfilecon(target.c_str(), contextSource) :
- ::lsetfilecon(target.c_str(), contextSource);
- if (rv3 < 0)
- throw FileError(_("Error writing security context:") + "\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted());
-}
-#endif //HAVE_SELINUX
-
-
-//copy permissions for files, directories or symbolic links: requires admin rights
-void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError;
-{
-#ifdef FFS_WIN
- //setting privileges requires admin rights!
- try
- {
- //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);
- }
- catch (const FileError& e)
- {
- throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + e.msg());
- }
-
- 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(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,
- 0,
- OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS | (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory
- NULL);
- if (hSource == INVALID_HANDLE_VALUE)
- throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (OR)");
- LOKI_ON_BLOCK_EXIT2(::CloseHandle(hSource));
-
- // 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)
- throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted(rc) + " (R)");
- LOKI_ON_BLOCK_EXIT2(::LocalFree(buffer));
-
- //may need to remove the readonly-attribute (e.g. FAT usb drives)
- FileUpdateHandle targetHandle(target, [ = ]()
- {
- return ::CreateFile(applyLongPathPrefix(target).c_str(), // lpFileName
- 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
- 0, // lpSecurityAttributes
- OPEN_EXISTING, // dwCreationDisposition
- FILE_FLAG_BACKUP_SEMANTICS | (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), // dwFlagsAndAttributes
- NULL); // hTemplateFile
- });
-
- if (targetHandle.get() == INVALID_HANDLE_VALUE)
- throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (OW)");
-
- // rc = ::SetNamedSecurityInfo(const_cast<WCHAR*>(applyLongPathPrefix(target).c_str()), //__in LPTSTR pObjectName, -> does NOT dereference symlinks!
- rc = ::SetSecurityInfo(targetHandle.get(), //__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)
- throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted(rc) + " (W)");
-
-#elif defined FFS_LINUX
-
-#ifdef HAVE_SELINUX //copy SELinux security context
- copySecurityContext(source, target, procSl); //throw FileError
-#endif
-
- struct stat fileInfo = {};
- if (procSl == SYMLINK_FOLLOW)
- {
- 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)
- throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (R)");
- }
- else
- {
- 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()
- throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (W)");
- }
-#endif
-}
-
-
-void createDirectory_straight(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level)
-{
- //default directory creation
-#ifdef FFS_WIN
- //don't use ::CreateDirectoryEx:
- //- it may fail with "wrong parameter (error code 87)" when source is on mapped online storage
- //- automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)!
- //- it isn't able to copy most junctions because of missing permissions (although target path can be retrieved alternatively!)
- if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), NULL))
-#elif defined FFS_LINUX
- if (::mkdir(directory.c_str(), 0755) != 0)
-#endif
- {
- if (level != 0) return;
- throw FileError(_("Error creating directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted());
- }
-
- if (!templateDir.empty())
- {
-#ifdef FFS_WIN
- //try to copy file attributes
- Zstring sourcePath;
-
- if (symlinkExists(templateDir)) //dereference symlink!
- {
- try
- {
- //get target directory of symbolic link
- sourcePath = resolveDirectorySymlink(templateDir); //throw FileError
- }
- catch (FileError&) {} //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error...
- }
- else //no symbolic link
- sourcePath = templateDir;
-
- //try to copy file attributes
- if (!sourcePath.empty())
- {
- const DWORD sourceAttr = ::GetFileAttributes(applyLongPathPrefix(sourcePath).c_str());
- if (sourceAttr != INVALID_FILE_ATTRIBUTES)
- {
- const bool isCompressed = (sourceAttr & FILE_ATTRIBUTE_COMPRESSED) != 0;
- const bool isEncrypted = (sourceAttr & FILE_ATTRIBUTE_ENCRYPTED) != 0;
-
- ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), sourceAttr);
-
- if (isEncrypted)
- ::EncryptFile(directory.c_str()); //seems no long path is required (check passed!)
-
- if (isCompressed)
- {
- HANDLE hDir = ::CreateFile(applyLongPathPrefix(directory).c_str(),
- GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- 0,
- OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS,
- NULL);
- if (hDir != INVALID_HANDLE_VALUE)
- {
- LOKI_ON_BLOCK_EXIT2(::CloseHandle(hDir));
-
- USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
-
- DWORD bytesReturned = 0;
- ::DeviceIoControl(hDir, //handle to file or directory
- FSCTL_SET_COMPRESSION, //dwIoControlCode
- &cmpState, //input buffer
- sizeof(cmpState), //size of input buffer
- NULL, //lpOutBuffer
- 0, //OutBufferSize
- &bytesReturned, //number of bytes returned
- NULL); //OVERLAPPED structure
- }
- }
- }
- }
-#endif
- Loki::ScopeGuard guardNewDir = Loki::MakeGuard([&]() { removeDirectory(directory); }); //ensure cleanup:
-
- //enforce copying file permissions: it's advertized on GUI...
- if (copyFilePermissions)
- copyObjectPermissions(templateDir, directory, SYMLINK_FOLLOW); //throw FileError
-
- guardNewDir.Dismiss(); //target has been created successfully!
- }
-}
-
-
-void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level)
-{
- if (level == 100) //catch endless recursion
- return;
-
-#ifdef FFS_WIN
- std::unique_ptr<Fix8Dot3NameClash> fnc;
- if (somethingExists(directory))
- {
- //handle issues with already existing short 8.3 file names on Windows
- if (have8dot3NameClash(directory))
- fnc.reset(new Fix8Dot3NameClash(directory)); //move clashing object to the side
- else if (dirExists(directory))
- return;
- }
-#elif defined FFS_LINUX
- if (dirExists(directory))
- return;
-#endif
- else //if "somethingExists" we needn't create the parent directory
- {
- //try to create parent folders first
- const Zstring dirParent = directory.BeforeLast(FILE_NAME_SEPARATOR);
- if (!dirParent.empty() && !dirExists(dirParent))
- {
- //call function recursively
- const Zstring templateParent = templateDir.BeforeLast(FILE_NAME_SEPARATOR); //returns empty string if ch not found
- createDirectoryRecursively(dirParent, templateParent, copyFilePermissions, level + 1);
- }
- }
-
- //now creation should be possible
- createDirectory_straight(directory, templateDir, copyFilePermissions, level); //throw FileError
-}
-}
-
-
-void zen::createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions)
-{
- //remove trailing separator
- const Zstring dirFormatted = directory.EndsWith(FILE_NAME_SEPARATOR) ?
- directory.BeforeLast(FILE_NAME_SEPARATOR) :
- directory;
-
- const Zstring templateFormatted = templateDir.EndsWith(FILE_NAME_SEPARATOR) ?
- templateDir.BeforeLast(FILE_NAME_SEPARATOR) :
- templateDir;
-
- createDirectoryRecursively(dirFormatted, templateFormatted, copyFilePermissions, 0);
-}
-
-
-void zen::createDirectory(const Zstring& directory)
-{
- zen::createDirectory(directory, Zstring(), false);
-}
-
-
-void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw FileError
-{
- const Zstring linkPath = getSymlinkRawTargetString(sourceLink); //accept broken symlinks; throw FileError
-
-#ifdef FFS_WIN
- const bool isDirLink = [&]() -> bool
- {
- const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(sourceLink).c_str());
- return ret != INVALID_FILE_ATTRIBUTES && (ret& FILE_ATTRIBUTE_DIRECTORY);
- }();
-
- //dynamically load windows API function
- typedef BOOLEAN (WINAPI *CreateSymbolicLinkFunc)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags);
-
- const util::DllFun<CreateSymbolicLinkFunc> createSymbolicLink(L"kernel32.dll", "CreateSymbolicLinkW");
- if (!createSymbolicLink)
- throw FileError(_("Error loading library function:") + "\n\"" + "CreateSymbolicLinkW" + "\"");
-
- if (!createSymbolicLink(targetLink.c_str(), //__in LPTSTR lpSymlinkFileName, - seems no long path prefix is required...
- linkPath.c_str(), //__in LPTSTR lpTargetFileName,
- (isDirLink ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0))) //__in DWORD dwFlags
-#elif defined FFS_LINUX
- if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0)
-#endif
- throw FileError(_("Error copying symbolic link:") + "\n\"" + sourceLink + "\" ->\n\"" + targetLink + "\"" + "\n\n" + getLastErrorFormatted());
-
- //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist
- Loki::ScopeGuard guardNewDir = Loki::MakeGuard([&]()
- {
-#ifdef FFS_WIN
- if (isDirLink)
- removeDirectory(targetLink);
- else
-#endif
- removeFile(targetLink);
- });
-
- //file times: essential for a symlink: enforce this! (don't just try!)
- {
- const Int64 modTime = getFileTime(sourceLink, SYMLINK_DIRECT); //throw FileError
- setFileTime(targetLink, modTime, SYMLINK_DIRECT); //throw FileError
- }
-
- if (copyFilePermissions)
- copyObjectPermissions(sourceLink, targetLink, SYMLINK_DIRECT); //throw FileError
-
- guardNewDir.Dismiss(); //target has been created successfully!
-}
-
-
-namespace
-{
-Zstring createTempName(const Zstring& filename)
-{
- Zstring output = filename + zen::TEMP_FILE_ENDING;
-
- //ensure uniqueness
- for (int i = 1; somethingExists(output); ++i)
- output = filename + Zchar('_') + Zstring::fromNumber(i) + zen::TEMP_FILE_ENDING;
-
- return output;
-}
-
-#ifdef FFS_WIN
-class CallbackData
-{
-public:
- CallbackData(CallbackCopyFile* cb, //may be NULL
- const Zstring& sourceFile,
- const Zstring& targetFile,
- bool osIsvistaOrLater) :
- userCallback(cb),
- sourceFile_(sourceFile),
- targetFile_(targetFile),
- osIsvistaOrLater_(osIsvistaOrLater),
- exceptionInUserCallback(false) {}
-
- CallbackCopyFile* userCallback; //optional!
- const Zstring& sourceFile_;
- const Zstring& targetFile_;
- const bool osIsvistaOrLater_;
-
- //there is some mixed responsibility in this class, pure read-only data and abstraction for error reporting
- //however we need to keep it at one place as ::CopyFileEx() requires!
-
- void reportUserException(const UInt64& bytesTransferred)
- {
- exceptionInUserCallback = true;
- bytesTransferredOnException = bytesTransferred;
- }
-
- void reportError(const std::wstring& message) { errorMsg = message; }
-
- void evaluateErrors() //throw
- {
- if (exceptionInUserCallback)
- {
- assert(userCallback);
- if (userCallback)
- userCallback->updateCopyStatus(bytesTransferredOnException); //rethrow (hopefully!)
- }
- if (!errorMsg.empty())
- throw FileError(errorMsg);
- }
-
- void setSrcAttr(const FileAttrib& attr) { sourceAttr = attr; }
- FileAttrib getSrcAttr() const { assert(sourceAttr.modificationTime != 0); return sourceAttr; }
-
-private:
- CallbackData(const CallbackData&);
- CallbackData& operator=(const CallbackData&);
-
- FileAttrib sourceAttr;
- std::wstring errorMsg; //
- bool exceptionInUserCallback; //these two are exclusive!
- UInt64 bytesTransferredOnException;
-};
-
-
-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)
-{
- //this callback is invoked for block sizes managed by Windows, these may vary from e.g. 64 kB up to 1MB. It seems this is dependent from file size amongst others.
-
- //symlink handling:
- //if source is a symlink and COPY_FILE_COPY_SYMLINK is specified, this callback is NOT invoked!
- //if source is a symlink and COPY_FILE_COPY_SYMLINK is NOT specified, this callback is called and hSourceFile is a handle to the *target* of the link!
-
- //file time handling:
- //::CopyFileEx() will copy file modification time (only) over from source file AFTER the last inocation of this callback
- //=> it is possible to adapt file creation time of target in here, but NOT file modification time!
-
- CallbackData& cbd = *static_cast<CallbackData*>(lpData);
-
- //#################### return source file attributes ################################
- if (dwCallbackReason == CALLBACK_STREAM_SWITCH) //called up front for every file (even if 0-sized)
- {
- BY_HANDLE_FILE_INFORMATION fileInfo = {};
- if (!::GetFileInformationByHandle(hSourceFile, &fileInfo))
- {
- cbd.reportError(_("Error reading file attributes:") + "\n\"" + cbd.sourceFile_ + "\"" + "\n\n" + getLastErrorFormatted());
- return PROGRESS_CANCEL;
- }
-
- const FileAttrib attr = { UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh),
- toTimeT(fileInfo.ftLastWriteTime)
- };
- //extractFileID(fileInfo));
-
- cbd.setSrcAttr(attr);
-
- //#################### copy file creation time ################################
- FILETIME creationTime = {};
-
- if (!::GetFileTime(hSourceFile, //__in HANDLE hFile,
- &creationTime, //__out_opt LPFILETIME lpCreationTime,
- NULL, //__out_opt LPFILETIME lpLastAccessTime,
- NULL)) //__out_opt LPFILETIME lpLastWriteTime
- {
- cbd.reportError(_("Error reading file attributes:") + "\n\"" + cbd.sourceFile_ + "\"" + "\n\n" + getLastErrorFormatted());
- return PROGRESS_CANCEL;
- }
-
- if (!::SetFileTime(hDestinationFile,
- &creationTime,
- NULL,
- NULL))
- {
- cbd.reportError(_("Error changing modification time:") + "\n\"" + cbd.targetFile_ + "\"" + "\n\n" + getLastErrorFormatted());
- return PROGRESS_CANCEL;
- }
- //##############################################################################
- }
-
- //if (totalFileSize.QuadPart == totalBytesTransferred.QuadPart) {} //called after copy operation is finished - note: for 0-sized files this callback is invoked just ONCE!
-
- if (cbd.userCallback)
- {
- //some odd check for some possible(?) error condition
- if (totalBytesTransferred.QuadPart < 0) //let's see if someone answers the call...
- ::MessageBox(NULL, L"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);
- try
- {
- cbd.userCallback->updateCopyStatus(UInt64(totalBytesTransferred.QuadPart));
- }
- catch (...)
- {
-//#warning migrate to std::exception_ptr when available
-
- cbd.reportUserException(UInt64(totalBytesTransferred.QuadPart));
- return PROGRESS_CANCEL;
- }
- }
- return PROGRESS_CONTINUE;
-}
-
-
-#ifndef COPY_FILE_ALLOW_DECRYPTED_DESTINATION
-#define COPY_FILE_ALLOW_DECRYPTED_DESTINATION 0x00000008
-#endif
-
-bool supportForNonEncryptedDestination()
-{
- OSVERSIONINFO osvi = {};
- 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 rawCopyWinApi_sub(const Zstring& sourceFile,
- const Zstring& targetFile,
- CallbackCopyFile* callback,
- FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
-{
- Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we ;)
-
- DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS;
-
- //allow copying from encrypted to non-encrytped location
- static bool nonEncSupported = false;
- {
- static boost::once_flag initNonEncOnce = BOOST_ONCE_INIT; //caveat: function scope static initialization is not thread-safe in VS 2010!
- boost::call_once(initNonEncOnce, []() { nonEncSupported = supportForNonEncryptedDestination(); });
- }
- if (nonEncSupported)
- copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION;
-
- static bool osIsvistaOrLater = false;
- {
- static boost::once_flag initVistaLaterOnce = BOOST_ONCE_INIT; //caveat: function scope static initialization is not thread-safe in VS 2010!
- boost::call_once(initVistaLaterOnce, []() { osIsvistaOrLater = dst::vistaOrLater(); });
- }
-
- CallbackData cbd(callback, sourceFile, targetFile, osIsvistaOrLater);
-
- const bool success = ::CopyFileEx( //same performance like CopyFile()
- applyLongPathPrefix(sourceFile).c_str(),
- applyLongPathPrefix(targetFile).c_str(),
- copyCallbackInternal,
- &cbd,
- NULL,
- copyFlags) == TRUE; //silence x64 perf warning
-
- cbd.evaluateErrors(); //throw ?, process errors in callback first!
- if (!success)
- {
- const DWORD lastError = ::GetLastError();
-
- //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user aborted operation IS an error condition!
-
- //assemble error message...
- std::wstring errorMessage = _("Error copying file:") + "\n\"" + sourceFile + "\" ->\n\"" + targetFile + "\"" +
- "\n\n" + getLastErrorFormatted(lastError);
-
- //if file is locked (try to) use Windows Volume Shadow Copy Service
- if (lastError == ERROR_SHARING_VIOLATION ||
- lastError == ERROR_LOCK_VIOLATION)
- throw ErrorFileLocked(errorMessage);
-
- if (lastError == ERROR_FILE_EXISTS) //if target is existing this functions is expected to throw ErrorTargetExisting!!!
- {
- guardTarget.Dismiss(); //don't delete file that existed previously!
- throw ErrorTargetExisting(errorMessage);
- }
-
- if (lastError == ERROR_PATH_NOT_FOUND)
- {
- guardTarget.Dismiss(); //not relevant
- throw ErrorTargetPathMissing(errorMessage);
- }
-
- try //add more meaningful message
- {
- //trying to copy > 4GB file to FAT/FAT32 volume gives obscure ERROR_INVALID_PARAMETER (FAT can indeed handle files up to 4 Gig, tested!)
- if (lastError == ERROR_INVALID_PARAMETER &&
- dst::isFatDrive(targetFile) &&
- getFilesize(sourceFile) >= 4U * UInt64(1024U * 1024 * 1024)) //throw FileError
- errorMessage += L"\nFAT volume cannot store files larger than 4 gigabyte!";
-
- //note: ERROR_INVALID_PARAMETER can also occur when copying to a SharePoint server or MS SkyDrive and the target filename is of a restricted type.
- }
- catch (...) {}
-
- throw FileError(errorMessage);
- }
-
- if (sourceAttr)
- *sourceAttr = cbd.getSrcAttr();
-
- {
- const Int64 modTime = getFileTime(sourceFile, SYMLINK_FOLLOW); //throw FileError
- setFileTime(targetFile, modTime, SYMLINK_FOLLOW); //throw FileError
- //note: this sequence leads to a loss of precision of up to 1 sec!
- //perf-loss on USB sticks with many small files of about 30%! damn!
- }
-
- guardTarget.Dismiss(); //target has been created successfully!
-}
-
-
-inline
-void rawCopyWinApi(const Zstring& sourceFile,
- const Zstring& targetFile,
- CallbackCopyFile* callback,
- FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
-{
- try
- {
- rawCopyWinApi_sub(sourceFile, targetFile, callback, sourceAttr); // throw ...
- }
- catch (ErrorTargetExisting&)
- {
- //try to handle issues with already existing short 8.3 file names on Windows
- if (have8dot3NameClash(targetFile))
- {
- Fix8Dot3NameClash dummy(targetFile); //move clashing filename to the side
- rawCopyWinApi_sub(sourceFile, targetFile, callback, sourceAttr); //throw FileError; the short filename name clash is solved, this should work now
- return;
- }
- throw;
- }
-}
-
-//void rawCopyWinOptimized(const Zstring& sourceFile,
-// const Zstring& targetFile,
-// CallbackCopyFile* callback) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
-//{
-// /*
-// BackupRead() FileRead() CopyFileEx()
-// --------------------------------------------
-// Attributes NO NO YES
-// create time NO NO NO
-// ADS YES NO YES
-// Encrypted NO(silent fail) NO YES
-// Compressed NO NO NO
-// Sparse YES NO NO
-// PERF 6% faster -
-//
-// Mark stream as compressed: FSCTL_SET_COMPRESSION
-// compatible with: BackupRead() FileRead()
-// */
-//
-// //open sourceFile for reading
-// HANDLE hFileIn = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(),
-// GENERIC_READ,
-// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read files that are open in other applications
-// 0,
-// OPEN_EXISTING,
-// FILE_FLAG_SEQUENTIAL_SCAN,
-// NULL);
-// if (hFileIn == INVALID_HANDLE_VALUE)
-// {
-// const DWORD lastError = ::GetLastError();
-// const std::wstring& errorMessage = _("Error opening file:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted(lastError);
-//
-// //if file is locked (try to) use Windows Volume Shadow Copy Service
-// if (lastError == ERROR_SHARING_VIOLATION ||
-// lastError == ERROR_LOCK_VIOLATION)
-// throw ErrorFileLocked(errorMessage);
-//
-// throw FileError(errorMessage);
-// }
-// LOKI_ON_BLOCK_EXIT2(::CloseHandle, hFileIn);
-//
-//
-// BY_HANDLE_FILE_INFORMATION infoFileIn = {};
-// if (!::GetFileInformationByHandle(hFileIn, &infoFileIn))
-// throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted());
-//
-// //####################################### DST hack ###########################################
-// if (dst::isFatDrive(sourceFile)) //throw()
-// {
-// const dst::RawTime rawTime(infoFileIn.ftCreationTime, infoFileIn.ftLastWriteTime);
-// if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error)
-// {
-// infoFileIn.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error)
-// ::GetSystemTimeAsFileTime(&infoFileIn.ftCreationTime); //real creation time information is not available...
-// }
-// }
-//
-// if (dst::isFatDrive(targetFile)) //throw()
-// {
-// const dst::RawTime encodedTime = dst::fatEncodeUtcTime(infoFileIn.ftLastWriteTime); //throw (std::runtime_error)
-// infoFileIn.ftCreationTime = encodedTime.createTimeRaw;
-// infoFileIn.ftLastWriteTime = encodedTime.writeTimeRaw;
-// }
-// //####################################### DST hack ###########################################
-//
-// const DWORD validAttribs = FILE_ATTRIBUTE_READONLY |
-// FILE_ATTRIBUTE_HIDDEN |
-// FILE_ATTRIBUTE_SYSTEM |
-// FILE_ATTRIBUTE_ARCHIVE | //those two are not set properly (not worse than ::CopyFileEx())
-// FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | //
-// FILE_ATTRIBUTE_ENCRYPTED;
-//
-// //create targetFile and open it for writing
-// HANDLE hFileOut = ::CreateFile(applyLongPathPrefix(targetFile).c_str(),
-// GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION
-// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
-// 0,
-// CREATE_NEW,
-// (infoFileIn.dwFileAttributes & validAttribs) | FILE_FLAG_SEQUENTIAL_SCAN,
-// NULL);
-// if (hFileOut == INVALID_HANDLE_VALUE)
-// {
-// const DWORD lastError = ::GetLastError();
-// const std::wstring& errorMessage = _("Error writing file:") + "\n\"" + targetFile + "\"" +
-// "\n\n" + getLastErrorFormatted(lastError);
-//
-// if (lastError == ERROR_FILE_EXISTS)
-// throw ErrorTargetExisting(errorMessage);
-//
-// if (lastError == ERROR_PATH_NOT_FOUND)
-// throw ErrorTargetPathMissing(errorMessage);
-//
-// throw FileError(errorMessage);
-// }
-// Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: guard just after opening target and before managing hFileOut
-//
-// LOKI_ON_BLOCK_EXIT2(::CloseHandle, hFileOut);
-//
-//
-//#ifndef _MSC_VER
-//#warning teste perf von GetVolumeInformationByHandleW
-//#endif
-// DWORD fsFlags = 0;
-// if (!GetVolumeInformationByHandleW(hFileOut, //__in HANDLE hFile,
-// NULL, //__out_opt LPTSTR lpVolumeNameBuffer,
-// 0, //__in DWORD nVolumeNameSize,
-// NULL, //__out_opt LPDWORD lpVolumeSerialNumber,
-// NULL, //__out_opt LPDWORD lpMaximumComponentLength,
-// &fsFlags, //__out_opt LPDWORD lpFileSystemFlags,
-// NULL, //__out LPTSTR lpFileSystemNameBuffer,
-// 0)) //__in DWORD nFileSystemNameSize
-// throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted());
-//
-// const bool sourceIsEncrypted = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0;
-// const bool sourceIsCompressed = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0;
-// const bool sourceIsSparse = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0;
-//
-// bool targetSupportSparse = (fsFlags & FILE_SUPPORTS_SPARSE_FILES) != 0;
-// bool targetSupportCompressed = (fsFlags & FILE_FILE_COMPRESSION ) != 0;
-// bool targetSupportStreams = (fsFlags & FILE_NAMED_STREAMS ) != 0;
-//
-//
-// const bool useBackupFun = !sourceIsEncrypted; //http://msdn.microsoft.com/en-us/library/aa362509(v=VS.85).aspx
-//
-// if (sourceIsCompressed && targetSupportCompressed)
-// {
-// USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
-//
-// DWORD bytesReturned = 0;
-// if (!DeviceIoControl(hFileOut, //handle to file or directory
-// FSCTL_SET_COMPRESSION, //dwIoControlCode
-// &cmpState, //input buffer
-// sizeof(cmpState), //size of input buffer
-// NULL, //lpOutBuffer
-// 0, //OutBufferSize
-// &bytesReturned, //number of bytes returned
-// NULL)) //OVERLAPPED structure
-// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" +
-// "\n\n" + getLastErrorFormatted() +
-// "\nFailed to write NTFS compressed attribute!");
-// }
-//
-// //although it seems the sparse attribute is set automatically by BackupWrite, we are required to do this manually: http://support.microsoft.com/kb/271398/en-us
-// if (sourceIsSparse && targetSupportSparse)
-// {
-// if (useBackupFun)
-// {
-// DWORD bytesReturned = 0;
-// if (!DeviceIoControl(hFileOut, //handle to file
-// FSCTL_SET_SPARSE, //dwIoControlCode
-// NULL, //input buffer
-// 0, //size of input buffer
-// NULL, //lpOutBuffer
-// 0, //OutBufferSize
-// &bytesReturned, //number of bytes returned
-// NULL)) //OVERLAPPED structure
-// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" +
-// "\n\n" + getLastErrorFormatted() +
-// "\nFailed to write NTFS sparse attribute!");
-// }
-// }
-//
-//
-// const DWORD BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size
-// static boost::thread_specific_ptr<std::vector<char>> cpyBuf;
-// if (!cpyBuf.get())
-// cpyBuf.reset(new std::vector<char>(BUFFER_SIZE)); //512 kb seems to be a reasonable buffer size
-// std::vector<char>& buffer = *cpyBuf;
-//
-// struct ManageCtxt //manage context for BackupRead()/BackupWrite()
-// {
-// ManageCtxt() : read(NULL), write(NULL) {}
-// ~ManageCtxt()
-// {
-// if (read != NULL)
-// ::BackupRead (0, NULL, 0, NULL, true, false, &read);
-// if (write != NULL)
-// ::BackupWrite(0, NULL, 0, NULL, true, false, &write);
-// }
-//
-// LPVOID read;
-// LPVOID write;
-// } context;
-//
-// //copy contents of sourceFile to targetFile
-// UInt64 totalBytesTransferred;
-//
-// bool eof = false;
-// do
-// {
-// DWORD bytesRead = 0;
-//
-// if (useBackupFun)
-// {
-// if (!::BackupRead(hFileIn, //__in HANDLE hFile,
-// &buffer[0], //__out LPBYTE lpBuffer,
-// BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead,
-// &bytesRead, //__out LPDWORD lpNumberOfBytesRead,
-// false, //__in BOOL bAbort,
-// false, //__in BOOL bProcessSecurity,
-// &context.read)) //__out LPVOID *lpContext
-// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" +
-// "\n\n" + getLastErrorFormatted());
-// }
-// else if (!::ReadFile(hFileIn, //__in HANDLE hFile,
-// &buffer[0], //__out LPVOID lpBuffer,
-// BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead,
-// &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead,
-// NULL)) //__inout_opt LPOVERLAPPED lpOverlapped
-// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" +
-// "\n\n" + getLastErrorFormatted());
-//
-// if (bytesRead > BUFFER_SIZE)
-// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" +
-// "\n\n" + "buffer overflow");
-//
-// if (bytesRead < BUFFER_SIZE)
-// eof = true;
-//
-// DWORD bytesWritten = 0;
-//
-// if (useBackupFun)
-// {
-// if (!::BackupWrite(hFileOut, //__in HANDLE hFile,
-// &buffer[0], //__in LPBYTE lpBuffer,
-// bytesRead, //__in DWORD nNumberOfBytesToWrite,
-// &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten,
-// false, //__in BOOL bAbort,
-// false, //__in BOOL bProcessSecurity,
-// &context.write)) //__out LPVOID *lpContext
-// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" +
-// "\n\n" + getLastErrorFormatted() + " (w)"); //w -> distinguish from fopen error message!
-// }
-// else if (!::WriteFile(hFileOut, //__in HANDLE hFile,
-// &buffer[0], //__out LPVOID lpBuffer,
-// bytesRead, //__in DWORD nNumberOfBytesToWrite,
-// &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten,
-// NULL)) //__inout_opt LPOVERLAPPED lpOverlapped
-// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" +
-// "\n\n" + getLastErrorFormatted() + " (w)"); //w -> distinguish from fopen error message!
-//
-// if (bytesWritten != bytesRead)
-// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" + "\n\n" + "incomplete write");
-//
-// totalBytesTransferred += bytesRead;
-//
-//#ifndef _MSC_VER
-//#warning totalBytesTransferred kann größer als filesize sein!!
-//#endif
-//
-// //invoke callback method to update progress indicators
-// if (callback != NULL)
-// switch (callback->updateCopyStatus(totalBytesTransferred))
-// {
-// case CallbackCopyFile::CONTINUE:
-// break;
-//
-// case CallbackCopyFile::CANCEL: //a user aborted operation IS an error condition!
-// throw FileError(_("Error copying file:") + "\n\"" + sourceFile + "\" ->\n\"" +
-// targetFile + "\"\n\n" + _("Operation aborted!"));
-// }
-// }
-// while (!eof);
-//
-//
-// if (totalBytesTransferred == 0) //BackupRead silently fails reading encrypted files -> double check!
-// {
-// LARGE_INTEGER inputSize = {};
-// if (!::GetFileSizeEx(hFileIn, &inputSize))
-// throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted());
-//
-// if (inputSize.QuadPart != 0)
-// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" + "\n\n" + "unknown error");
-// }
-//
-// //time needs to be set at the end: BackupWrite() changes file time
-// if (!::SetFileTime(hFileOut,
-// &infoFileIn.ftCreationTime,
-// NULL,
-// &infoFileIn.ftLastWriteTime))
-// throw FileError(_("Error changing modification time:") + "\n\"" + targetFile + "\"" + "\n\n" + getLastErrorFormatted());
-//
-//
-//#ifndef NDEBUG //dst hack: verify data written
-// if (dst::isFatDrive(targetFile)) //throw()
-// {
-// WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {};
-// assert(::GetFileAttributesEx(applyLongPathPrefix(targetFile).c_str(), //__in LPCTSTR lpFileName,
-// GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
-// &debugeAttr)); //__out LPVOID lpFileInformation
-//
-// assert(::CompareFileTime(&debugeAttr.ftCreationTime, &infoFileIn.ftCreationTime) == 0);
-// assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &infoFileIn.ftLastWriteTime) == 0);
-// }
-//#endif
-//
-// guardTarget.Dismiss();
-//
-// /*
-// //create test sparse file
-// HANDLE hSparse = ::CreateFile(L"C:\\sparse.file",
-// GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION
-// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
-// 0,
-// CREATE_NEW,
-// FILE_FLAG_SEQUENTIAL_SCAN,
-// NULL);
-// DWORD br = 0;
-// if (!::DeviceIoControl(hSparse, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &br,NULL))
-// throw 1;
-//
-// LARGE_INTEGER liDistanceToMove = {};
-// liDistanceToMove.QuadPart = 1024 * 1024 * 1024; //create 5 TB sparse file
-// liDistanceToMove.QuadPart *= 5 * 1024; //
-// if (!::SetFilePointerEx(hSparse, liDistanceToMove, NULL, FILE_BEGIN))
-// throw 1;
-//
-// if (!SetEndOfFile(hSparse))
-// throw 1;
-//
-// FILE_ZERO_DATA_INFORMATION zeroInfo = {};
-// zeroInfo.BeyondFinalZero.QuadPart = liDistanceToMove.QuadPart;
-// if (!::DeviceIoControl(hSparse, FSCTL_SET_ZERO_DATA, &zeroInfo, sizeof(zeroInfo), NULL, 0, &br, NULL))
-// throw 1;
-//
-// ::CloseHandle(hSparse);
-// */
-//}
-#endif
-
-#ifdef FFS_LINUX
-void rawCopyStream(const Zstring& sourceFile,
- const Zstring& targetFile,
- CallbackCopyFile* callback,
- FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
-{
- Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: place guard before lifetime of FileOutput
- try
- {
- //open sourceFile for reading
- FileInput fileIn(sourceFile); //throw FileError
-
- //create targetFile and open it for writing
- FileOutput fileOut(targetFile, FileOutput::ACC_CREATE_NEW); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
-
- static boost::thread_specific_ptr<std::vector<char>> cpyBuf;
- if (!cpyBuf.get())
- cpyBuf.reset(new std::vector<char>(512 * 1024)); //512 kb seems to be a reasonable buffer size
- std::vector<char>& buffer = *cpyBuf;
-
- //copy contents of sourceFile to targetFile
- UInt64 totalBytesTransferred;
- do
- {
- const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError
-
- fileOut.write(&buffer[0], bytesRead); //throw FileError
-
- totalBytesTransferred += bytesRead;
-
- //invoke callback method to update progress indicators
- if (callback)
- callback->updateCopyStatus(totalBytesTransferred);
- }
- while (!fileIn.eof());
- }
- catch (ErrorTargetExisting&)
- {
- guardTarget.Dismiss(); //don't delete file that existed previously!
- throw;
- }
-
- //adapt file modification time:
- {
- struct stat srcInfo = {};
- if (::stat(sourceFile.c_str(), &srcInfo) != 0) //read file attributes from source directory
- throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted());
-
- if (sourceAttr)
- {
- sourceAttr->fileSize = UInt64(srcInfo.st_size);
- sourceAttr->modificationTime = srcInfo.st_mtime;
- }
-
- struct utimbuf newTimes = {};
- newTimes.actime = srcInfo.st_atime;
- newTimes.modtime = srcInfo.st_mtime;
-
- //set new "last write time"
- if (::utime(targetFile.c_str(), &newTimes) != 0)
- throw FileError(_("Error changing modification time:") + "\n\"" + targetFile + "\"" + "\n\n" + getLastErrorFormatted());
- }
-
- guardTarget.Dismiss(); //target has been created successfully!
-}
-#endif
-
-
-inline
-void copyFileImpl(const Zstring& sourceFile,
- const Zstring& targetFile,
- CallbackCopyFile* callback,
- FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
-{
-#ifdef FFS_WIN
- /*
- rawCopyWinApi() rawCopyWinOptimized()
- -------------------------------------
- Attributes YES YES
- Filetimes YES YES
- ADS YES YES
- Encrypted YES YES
- Compressed NO YES
- Sparse NO YES
- PERF - 6% faster
- SAMBA, ect. YES UNKNOWN! -> issues writing ADS to Samba, issues reading from NAS, error copying files having "blocked" state... ect. damn!
- */
-
- rawCopyWinApi(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
- //rawCopyWinOptimized(sourceFile, targetFile, callback); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked ->about 8% faster
-
-#elif defined FFS_LINUX
- rawCopyStream(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
-#endif
-}
-}
-
-
-void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPathMissing, ErrorFileLocked
- const Zstring& targetFile,
- bool copyFilePermissions,
- bool transactionalCopy,
- CallbackCopyFile* callback,
- FileAttrib* sourceAttr)
-{
- if (transactionalCopy)
- {
- Zstring temporary = targetFile + zen::TEMP_FILE_ENDING; //use temporary file until a correct date has been set
- Loki::ScopeGuard guardTempFile = Loki::MakeGuard([&]() { removeFile(temporary); }); //transactional behavior: ensure cleanup (e.g. network drop) -> ref to temporary[!]
-
- //raw file copy
- try
- {
- copyFileImpl(sourceFile, temporary, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
- }
- catch (ErrorTargetExisting&)
- {
- //determine non-used temp file name "first":
- //using optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses
- temporary = createTempName(targetFile);
-
- //retry
- copyFileImpl(sourceFile, temporary, callback, sourceAttr); //throw FileError
- }
-
- //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite
- if (callback)
- callback->deleteTargetFile(targetFile);
-
- //rename temporary file:
- //perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick!
- renameFile(temporary, targetFile); //throw FileError
-
- guardTempFile.Dismiss();
- }
- else
- {
- //have target file deleted
- if (callback) callback->deleteTargetFile(targetFile);
-
- copyFileImpl(sourceFile, targetFile, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
- }
- /*
- Note: non-transactional file copy solves at least four problems:
- -> skydrive - doesn't allow for .ffs_tmp extension and returns ERROR_INVALID_PARAMETER
- -> network renaming issues
- -> allow for true delete before copy to handle low disk space problems
- -> higher performance on non-buffered drives (e.g. usb sticks)
- */
-
- //set file permissions
- if (copyFilePermissions)
- {
- Loki::ScopeGuard guardTargetFile = Loki::MakeGuard(&removeFile, targetFile);
- copyObjectPermissions(sourceFile, targetFile, SYMLINK_FOLLOW); //throw FileError
- guardTargetFile.Dismiss(); //target has been created successfully!
- }
-}
bgstack15