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.cpp974
1 files changed, 555 insertions, 419 deletions
diff --git a/shared/file_handling.cpp b/shared/file_handling.cpp
index 8fa8568a..c9e85c15 100644
--- a/shared/file_handling.cpp
+++ b/shared/file_handling.cpp
@@ -17,6 +17,7 @@
#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"
@@ -98,63 +99,99 @@ bool zen::somethingExists(const Zstring& objname) //throw() check whether
}
-#ifdef FFS_WIN
namespace
{
-zen::UInt64 getFileSizeSymlink(const Zstring& linkName) //throw (FileError)
-{
- //open handle to target of symbolic link
- const HANDLE hFile = ::CreateFile(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)
- throw FileError(_("Error reading file attributes:") + "\n\"" + linkName + "\"" + "\n\n" + getLastErrorFormatted());
-
- Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hFile);
- (void)dummy; //silence warning "unused variable"
-
- BY_HANDLE_FILE_INFORMATION fileInfo = {};
- if (!::GetFileInformationByHandle(hFile, &fileInfo))
- throw FileError(_("Error reading file attributes:") + "\n\"" + linkName + "\"" + "\n\n" + getLastErrorFormatted());
-
- return UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh);
-}
-}
-#endif
-
-
-UInt64 zen::getFilesize(const Zstring& filename) //throw (FileError)
+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);
+ {
+ 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)
- return getFileSizeSymlink(filename); //throw (FileError)
+ 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 ###########################################
- return UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh);
+ 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 = {};
- if (::stat(filename.c_str(), &fileInfo) != 0) //follow symbolic links
+
+ 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());
- return UInt64(fileInfo.st_size);
+ 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!
{
@@ -218,7 +255,7 @@ zen::ResponseSame zen::onSameVolume(const Zstring& folderLeft, const Zstring& fo
}
-bool zen::removeFile(const Zstring& filename) //throw (FileError);
+bool zen::removeFile(const Zstring& filename) //throw FileError;
{
#ifdef FFS_WIN
//remove file, support for \\?\-prefix
@@ -261,16 +298,15 @@ namespace
{
DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume);
-/* Usage overview:
+/* Usage overview: (avoid circular pattern!)
- renameFile() --> renameFileInternal()
+ renameFile() --> renameFile_sub()
| /|\
\|/ |
- fix8Dot3NameClash()
+ 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, ErrorTargetExisting)
+void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
{
#ifdef FFS_WIN
const Zstring oldNameFmt = applyLongPathPrefix(oldName);
@@ -361,12 +397,12 @@ Zstring getFilenameFmt(const Zstring& filename, Function fun) //throw(); returns
}
-Zstring createTemp8Dot3Name(const Zstring& fileName) //find a unique 8.3 short name
+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();
+ 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
+ Zstring extension = filename.AfterLast(FILE_NAME_SEPARATOR).AfterLast(Zchar('.')); //extension needn't contain reasonable data
if (extension.empty())
extension = Zstr("FFS");
extension.Truncate(3);
@@ -382,63 +418,82 @@ Zstring createTemp8Dot3Name(const Zstring& fileName) //find a unique 8.3 short n
}
-//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
+bool have8dot3NameClash(const Zstring& filename)
{
- using namespace zen;
-
- if (newName.find(FILE_NAME_SEPARATOR) == Zstring::npos)
+ if (filename.find(FILE_NAME_SEPARATOR) == Zstring::npos)
return false;
- if (somethingExists(newName)) //name OR directory!
+ if (somethingExists(filename)) //name OR directory!
{
- const Zstring fileNameOrig = newName.AfterLast(FILE_NAME_SEPARATOR); //returns the whole string if ch not found
- const Zstring fileNameShort = getFilenameFmt(newName, ::GetShortPathName).AfterLast(FILE_NAME_SEPARATOR); //throw() returns empty string on error
- const Zstring fileNameLong = getFilenameFmt(newName, ::GetLongPathName) .AfterLast(FILE_NAME_SEPARATOR); //throw() returns empty string on error
-
- if (!fileNameShort.empty() &&
- !fileNameLong.empty() &&
- EqualFilename() (fileNameOrig, fileNameShort) &&
- !EqualFilename()(fileNameShort, fileNameLong))
+ 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))
{
- //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
+ //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;
+}
- const Zstring unrelatedPathLong = newName.BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + fileNameLong;
+class Fix8Dot3NameClash
+{
+public:
+ Fix8Dot3NameClash(const Zstring& filename)
+ {
+ const Zstring longName = getFilenameFmt(filename, ::GetLongPathName).AfterLast(FILE_NAME_SEPARATOR); //throw() returns empty string on error
- //find another name in short format: this ensures the actual short name WILL be renamed as well!
- const Zstring parkedTarget = createTemp8Dot3Name(newName);
+ unrelatedFile = filename.BeforeLast(FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + longName;
- //move already existing short name out of the way for now
- renameFileInternal(unrelatedPathLong, parkedTarget); //throw (FileError: ErrorDifferentVolume);
- //DON'T call renameFile() to avoid reentrance!
+ //find another name in short format: this ensures the actual short name WILL be renamed as well!
+ unrelatedFileParked = findUnused8Dot3Name(filename);
- //schedule cleanup; the file system should assign this unrelated file a new (unique) short name
- Loki::ScopeGuard guard = Loki::MakeGuard(renameFileInternal, parkedTarget, unrelatedPathLong);//equivalent to Boost.ScopeExit in this case
- (void)guard; //silence warning "unused variable"
+ //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!
+ }
- renameFileInternal(oldName, newName); //the short filename name clash is solved, this should work now
- return true;
+ ~Fix8Dot3NameClash()
+ {
+ //the file system should assign this unrelated file a new (unique) short name
+ try
+ {
+ renameFile_sub(unrelatedFileParked, unrelatedFile); //throw FileError, ErrorDifferentVolume
}
+ catch (...) {}
}
- return false; //issue not fixed
-}
+private:
+ Zstring unrelatedFile;
+ Zstring unrelatedFileParked;
+};
#endif
}
//rename file: no copying!!!
-void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting);
+void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
{
try
{
- renameFileInternal(oldName, newName); //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting)
+ renameFile_sub(oldName, newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
}
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
+ //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;
}
@@ -458,12 +513,15 @@ public:
}
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);
+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)
@@ -480,24 +538,24 @@ void zen::moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ig
//try to move the file directly without copying
try
{
- renameFile(sourceFile, targetFile); //throw (FileError: ErrorDifferentVolume);
+ renameFile(sourceFile, targetFile); //throw FileError, ErrorDifferentVolume
return; //great, we get away cheaply!
}
//if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file)
catch (const ErrorDifferentVolume&) {}
//file is on a different volume: let's copy it
- std::auto_ptr<CopyCallbackImpl> copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, *callback) : NULL);
+ std::unique_ptr<CopyCallbackImpl> copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, *callback) : NULL);
if (symlinkExists(sourceFile))
- copySymlink(sourceFile, targetFile, SYMLINK_TYPE_FILE, false); //throw (FileError) dont copy filesystem permissions
+ copySymlink(sourceFile, targetFile, false); //throw FileError; don't copy filesystem permissions
else
- copyFile(sourceFile, targetFile, false, true, copyCallback.get()); //throw (FileError);
+ 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)
+ 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
}
@@ -535,6 +593,9 @@ public:
virtual HandleError onError(const std::wstring& errorText) { throw FileError(errorText); }
private:
+ TraverseOneLevel(const TraverseOneLevel&);
+ TraverseOneLevel& operator=(const TraverseOneLevel&);
+
NameList& files_;
NameList& dirs_;
};
@@ -547,22 +608,21 @@ struct RemoveCallbackImpl : public CallbackRemoveDir
sourceDir_(sourceDir),
moveCallback_(moveCallback) {}
- virtual void notifyDeletion(const Zstring& currentObject)
- {
- moveCallback_.requestUiRefresh(sourceDir_);
- }
+ 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);
+void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError;
{
- using namespace zen;
-
//call back once per folder
if (callback)
callback->requestUiRefresh(sourceDir);
@@ -580,7 +640,7 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool
//first try to move the directory directly without copying
try
{
- renameFile(sourceDir, targetDir); //throw (FileError: ErrorDifferentVolume, ErrorTargetExisting);
+ 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)
@@ -588,9 +648,9 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool
//create target
if (isSymlink)
- copySymlink(sourceDir, targetDir, SYMLINK_TYPE_DIR, false); //throw (FileError) -> don't copy permissions
+ copySymlink(sourceDir, targetDir, false); //throw FileError -> don't copy permissions
else
- createDirectory(targetDir, sourceDir, false); //throw (FileError)
+ createDirectory(targetDir, sourceDir, false); //throw FileError
}
if (!isSymlink) //handle symbolic links
@@ -609,7 +669,7 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool
//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);
+ 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)
@@ -619,12 +679,12 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool
}
//delete source
- std::auto_ptr<RemoveCallbackImpl> removeCallback(callback != NULL ? new RemoveCallbackImpl(sourceDir, *callback) : NULL);
- removeDirectory(sourceDir, removeCallback.get()); //throw (FileError);
+ 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);
+void zen::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError;
{
#ifdef FFS_WIN
const Zstring& sourceDirFormatted = sourceDir;
@@ -671,6 +731,9 @@ public:
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;
};
@@ -699,7 +762,8 @@ void zen::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback)
#endif
throw FileError(_("Error deleting directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted());
- if (callback) callback->notifyDeletion(directory); //once per symlink
+ if (callback)
+ callback->notifyDirDeletion(directory); //once per symlink
return;
}
@@ -714,7 +778,8 @@ void zen::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback)
for (std::vector<Zstring>::const_iterator i = fileList.begin(); i != fileList.end(); ++i)
{
const bool workDone = removeFile(*i);
- if (callback && workDone) callback->notifyDeletion(*i); //call once per file
+ if (callback && workDone)
+ callback->notifyFileDeletion(*i); //call once per file
}
//delete directories recursively
@@ -730,76 +795,25 @@ void zen::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback)
{
throw FileError(_("Error deleting directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted());
}
- if (callback) callback->notifyDeletion(directory); //and once per folder
+ if (callback)
+ callback->notifyDirDeletion(directory); //and once per folder
}
-void zen::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, bool deRefSymlinks) //throw (FileError)
+void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, ProcSymlink procSl) //throw FileError
{
#ifdef FFS_WIN
- FILETIME creationTime = {};
- FILETIME lastWriteTime = {};
+ FILETIME creationTime = {};
+ FILETIME lastWriteTime = tofiletime(modificationTime);
+ //####################################### DST hack ###########################################
+ if (dst::isFatDrive(filename)) //throw()
{
- 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
- throw FileError(_("Error reading file attributes:") + "\n\"" + sourceObj + "\"" + "\n\n" + getLastErrorFormatted());
-
- const bool isReparsePoint = (sourceAttr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
- const bool isDirectory = (sourceAttr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
-
- if (isReparsePoint && 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)
- throw FileError(_("Error reading file attributes:") + "\n\"" + sourceObj + "\"" + "\n\n" + getLastErrorFormatted());
-
- Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hSource);
- (void)dummy; //silence warning "unused variable"
-
- if (!::GetFileTime(hSource, //__in HANDLE hFile,
- &creationTime, //__out_opt LPFILETIME lpCreationTime,
- NULL, //__out_opt LPFILETIME lpLastAccessTime,
- &lastWriteTime)) //__out_opt LPFILETIME lpLastWriteTime
- throw FileError(_("Error reading file attributes:") + "\n\"" + sourceObj + "\"" + "\n\n" + getLastErrorFormatted());
- }
- else
- {
- creationTime = sourceAttr.ftCreationTime;
- lastWriteTime = sourceAttr.ftLastWriteTime;
- }
-
- //####################################### DST hack ###########################################
- if (!isDirectory) //dst hack not (yet) required for directories (symlinks implicitly checked by isFatDrive())
- {
- if (dst::isFatDrive(sourceObj)) //throw()
- {
- const dst::RawTime rawTime(creationTime, lastWriteTime);
- if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error)
- {
- lastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error)
- ::GetSystemTimeAsFileTime(&creationTime); //real creation time information is not available...
- }
- }
-
- if (dst::isFatDrive(targetObj)) //throw()
- {
- const dst::RawTime encodedTime = dst::fatEncodeUtcTime(lastWriteTime); //throw (std::runtime_error)
- creationTime = encodedTime.createTimeRaw;
- lastWriteTime = encodedTime.writeTimeRaw;
- }
- }
- //####################################### DST hack ###########################################
+ 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!
@@ -815,20 +829,20 @@ void zen::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, bool
*/
//may need to remove the readonly-attribute (e.g. FAT usb drives)
- FileUpdateHandle targetHandle(targetObj, [=]()
+ FileUpdateHandle targetHandle(filename, [ = ]()
{
- return ::CreateFile(applyLongPathPrefix(targetObj).c_str(),
- FILE_GENERIC_WRITE, //ATTENTION: although FILE_WRITE_ATTRIBUTES should(!) be sufficient, this may leads to access denied on NAS shares, unless we specify FILE_GENERIC_WRITE
+ 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,
- NULL,
+ 0,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory
- (deRefSymlinks ? 0 : FILE_FLAG_OPEN_REPARSE_POINT), //process symlinks
+ (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\"" + targetObj + "\"" + "\n\n" + getLastErrorFormatted());
+ throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
/*
if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION)
@@ -838,17 +852,19 @@ void zen::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, bool
}
*/
+ auto isNullTime = [](const FILETIME & ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; };
+
if (!::SetFileTime(targetHandle.get(),
- &creationTime,
+ isNullTime(creationTime) ? NULL : &creationTime,
NULL,
&lastWriteTime))
- throw FileError(_("Error changing modification time:") + "\n\"" + targetObj + "\"" + "\n\n" + getLastErrorFormatted());
+ throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
#ifndef NDEBUG //dst hack: verify data written
- if (dst::isFatDrive(targetObj) && !dirExists(targetObj)) //throw()
+ if (dst::isFatDrive(filename) && !dirExists(filename)) //throw()
{
WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {};
- assert(::GetFileAttributesEx(applyLongPathPrefix(targetObj).c_str(), //__in LPCTSTR lpFileName,
+ assert(::GetFileAttributesEx(applyLongPathPrefix(filename).c_str(), //__in LPCTSTR lpFileName,
GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
&debugeAttr)); //__out LPVOID lpFileInformation
@@ -858,35 +874,27 @@ void zen::copyFileTimes(const Zstring& sourceObj, const Zstring& targetObj, bool
#endif
#elif defined FFS_LINUX
- if (deRefSymlinks)
+ if (procSl == SYMLINK_FOLLOW)
{
- struct stat objInfo = {};
- if (::stat(sourceObj.c_str(), &objInfo) != 0) //read file attributes from source directory
- throw FileError(_("Error reading file attributes:") + "\n\"" + sourceObj + "\"" + "\n\n" + getLastErrorFormatted());
-
struct utimbuf newTimes = {};
- newTimes.actime = objInfo.st_atime;
- newTimes.modtime = objInfo.st_mtime;
+ newTimes.actime = ::time(NULL);
+ newTimes.modtime = to<time_t>(modificationTime);
- //(try to) set new "last write time"
- if (::utime(targetObj.c_str(), &newTimes) != 0) //return value not evaluated!
- throw FileError(_("Error changing modification time:") + "\n\"" + targetObj + "\"" + "\n\n" + getLastErrorFormatted());
+ // 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 stat objInfo = {};
- if (::lstat(sourceObj.c_str(), &objInfo) != 0) //read file attributes from source directory
- throw FileError(_("Error reading file attributes:") + "\n\"" + sourceObj + "\"" + "\n\n" + getLastErrorFormatted());
-
struct timeval newTimes[2] = {};
- newTimes[0].tv_sec = objInfo.st_atime; /* seconds */
- newTimes[0].tv_usec = 0; /* microseconds */
+ newTimes[0].tv_sec = ::time(NULL); /* seconds */
+ newTimes[0].tv_usec = 0; /* microseconds */
- newTimes[1].tv_sec = objInfo.st_mtime; /* seconds */
- newTimes[1].tv_usec = 0; /* microseconds */
+ newTimes[1].tv_sec = to<time_t>(modificationTime);
+ newTimes[1].tv_usec = 0;
- if (::lutimes(targetObj.c_str(), newTimes) != 0) //return value not evaluated!
- throw FileError(_("Error changing modification time:") + "\n\"" + targetObj + "\"" + "\n\n" + getLastErrorFormatted());
+ if (::lutimes(filename.c_str(), newTimes) != 0)
+ throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted());
}
#endif
}
@@ -901,56 +909,50 @@ Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target pa
const HANDLE hDir = ::CreateFile(applyLongPathPrefix(dirLinkName).c_str(),
0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- NULL,
+ 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));
- Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hDir);
- (void)dummy; //silence warning "unused variable"
-
- const size_t BUFFER_SIZE = 10000;
- TCHAR targetPath[BUFFER_SIZE];
+ 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 GetFinalPathNameByHandleWFunc getFinalPathNameByHandle =
- util::getDllFun<GetFinalPathNameByHandleWFunc>(L"kernel32.dll", "GetFinalPathNameByHandleW");
-
- if (getFinalPathNameByHandle == NULL)
+ 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 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)
+ 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 (rv == 0)
+ if (charsWritten == 0)
errorMessage += L"\n\n" + getLastErrorFormatted();
throw FileError(errorMessage);
}
- return targetPath;
+ return Zstring(&targetPath[0], charsWritten);
}
#endif
#ifdef HAVE_SELINUX
//copy SELinux security context
-void copySecurityContext(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw (FileError)
+void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError
{
security_context_t contextSource = NULL;
- const int rv = derefSymlinks ?
- ::getfilecon (source.c_str(), &contextSource) :
+ const int rv = procSl == SYMLINK_FOLLOW ?
+ ::getfilecon(source.c_str(), &contextSource) :
::lgetfilecon(source.c_str(), &contextSource);
if (rv < 0)
{
@@ -960,13 +962,12 @@ void copySecurityContext(const Zstring& source, const Zstring& target, bool dere
throw FileError(_("Error reading security context:") + "\n\"" + source + "\"" + "\n\n" + getLastErrorFormatted());
}
- Loki::ScopeGuard dummy1 = Loki::MakeGuard(::freecon, contextSource);
- (void)dummy1; //silence warning "unused variable"
+ LOKI_ON_BLOCK_EXIT2(::freecon(contextSource));
{
security_context_t contextTarget = NULL;
- const int rv2 = derefSymlinks ?
- ::getfilecon (target.c_str(), &contextTarget) :
+ const int rv2 = procSl == SYMLINK_FOLLOW ?
+ ::getfilecon(target.c_str(), &contextTarget) :
::lgetfilecon(target.c_str(), &contextTarget);
if (rv2 < 0)
{
@@ -976,16 +977,15 @@ void copySecurityContext(const Zstring& source, const Zstring& target, bool dere
}
else
{
- Loki::ScopeGuard dummy2 = Loki::MakeGuard(::freecon, contextTarget);
- (void)dummy2; //silence warning "unused variable"
+ LOKI_ON_BLOCK_EXIT2(::freecon(contextTarget));
if (::strcmp(contextSource, contextTarget) == 0) //nothing to do
return;
}
}
- const int rv3 = derefSymlinks ?
- ::setfilecon (target.c_str(), contextSource) :
+ 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());
@@ -994,7 +994,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, bool dere
//copy permissions for files, directories or symbolic links: requires admin rights
-void copyObjectPermissions(const Zstring& source, const Zstring& target, bool derefSymlinks) //throw (FileError);
+void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError;
{
#ifdef FFS_WIN
//setting privileges requires admin rights!
@@ -1024,15 +1024,13 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, bool de
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,
- NULL,
+ 0,
OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS | (derefSymlinks ? 0 : FILE_FLAG_OPEN_REPARSE_POINT), //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory
+ 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::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hSource);
- (void)dummy; //silence warning "unused variable"
+ 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,
@@ -1045,20 +1043,17 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, bool de
&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::ScopeGuard dummy4 = Loki::MakeGuard(::LocalFree, buffer);
- (void)dummy4; //silence warning "unused variable"
-
+ LOKI_ON_BLOCK_EXIT2(::LocalFree(buffer));
//may need to remove the readonly-attribute (e.g. FAT usb drives)
- FileUpdateHandle targetHandle(target, [=]()
+ FileUpdateHandle targetHandle(target, [ = ]()
{
- return ::CreateFile(applyLongPathPrefix(target).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
+ 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 | (derefSymlinks ? 0 : FILE_FLAG_OPEN_REPARSE_POINT), // dwFlagsAndAttributes
+ FILE_FLAG_BACKUP_SEMANTICS | (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), // dwFlagsAndAttributes
NULL); // hTemplateFile
});
@@ -1066,14 +1061,13 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, bool de
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
+ 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)");
@@ -1081,21 +1075,20 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, bool de
#elif defined FFS_LINUX
#ifdef HAVE_SELINUX //copy SELinux security context
- copySecurityContext(source, target, derefSymlinks); //throw (FileError)
+ copySecurityContext(source, target, procSl); //throw FileError
#endif
- if (derefSymlinks)
+ struct stat fileInfo = {};
+ if (procSl == SYMLINK_FOLLOW)
{
- struct stat fileInfo = {};
- if (::stat (source.c_str(), &fileInfo) != 0 ||
+ if (::stat(source.c_str(), &fileInfo) != 0 ||
::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights!
::chmod(target.c_str(), fileInfo.st_mode) != 0)
throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (R)");
}
else
{
- struct stat fileInfo = {};
- if (::lstat (source.c_str(), &fileInfo) != 0 ||
+ if (::lstat(source.c_str(), &fileInfo) != 0 ||
::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights!
(!symlinkExists(target) && ::chmod(target.c_str(), fileInfo.st_mode) != 0)) //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (W)");
@@ -1104,35 +1097,8 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, bool de
}
-namespace
-{
-//provide uniform binary interface:
-void removeDirSimple(const Zstring& directory) { removeDirectory(directory); } //throw (FileError)
-void removeFileSimple(const Zstring& filename) { removeFile(filename); } //throw (FileError)
-}
-
-
-void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level)
+void createDirectory_straight(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level)
{
- using namespace zen;
-
- if (dirExists(directory))
- return;
-
- if (level == 100) //catch endless recursion
- return;
-
- //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);
- createDirectoryRecursively(dirParent, templateParent, copyFilePermissions, level + 1);
- }
-
- //now creation should be possible
-
//default directory creation
#ifdef FFS_WIN
//don't use ::CreateDirectoryEx:
@@ -1159,11 +1125,11 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat
try
{
//get target directory of symbolic link
- sourcePath = resolveDirectorySymlink(templateDir); //throw (FileError)
+ 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
+ else //no symbolic link
sourcePath = templateDir;
//try to copy file attributes
@@ -1185,14 +1151,13 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat
HANDLE hDir = ::CreateFile(applyLongPathPrefix(directory).c_str(),
GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- NULL,
+ 0,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (hDir != INVALID_HANDLE_VALUE)
{
- Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hDir);
- (void)dummy; //silence warning "unused variable"
+ LOKI_ON_BLOCK_EXIT2(::CloseHandle(hDir));
USHORT cmpState = COMPRESSION_FORMAT_DEFAULT;
@@ -1210,23 +1175,51 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat
}
}
#endif
-
- //try to copy file times: NOT mission critical for a directory, since modification time is changed on each added, deleted file, however creation time will stay
- try
- {
- copyFileTimes(templateDir, directory, true); //throw (FileError)
- }
- catch (FileError&) {}
-
- Loki::ScopeGuard guardNewDir = Loki::MakeGuard(&removeDirSimple, directory); //ensure cleanup:
+ Loki::ScopeGuard guardNewDir = Loki::MakeGuard([&]() { removeDirectory(directory); }); //ensure cleanup:
//enforce copying file permissions: it's advertized on GUI...
if (copyFilePermissions)
- copyObjectPermissions(templateDir, directory, true); //throw (FileError)
+ 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
+}
}
@@ -1251,38 +1244,51 @@ void zen::createDirectory(const Zstring& directory)
}
-void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, zen::SymlinkType type, bool copyFilePermissions) //throw (FileError)
+void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw FileError
{
- const Zstring linkPath = getSymlinkRawTargetString(sourceLink); //accept broken symlinks; 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 CreateSymbolicLinkFunc createSymbolicLink = util::getDllFun<CreateSymbolicLinkFunc>(L"kernel32.dll", "CreateSymbolicLinkW");
- if (createSymbolicLink == NULL)
- throw FileError(_("Error loading library function:") + "\n\"" + "CreateSymbolicLinkW" + "\"");
- if (!createSymbolicLink( //seems no long path prefix is required...
- targetLink.c_str(), //__in LPTSTR lpSymlinkFileName,
- linkPath.c_str(), //__in LPTSTR lpTargetFileName,
- (type == SYMLINK_TYPE_DIR ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0))) //__in DWORD dwFlags
- throw FileError(_("Error copying symbolic link:") + "\n\"" + sourceLink + "\" ->\n\"" + targetLink + "\"" + "\n\n" + getLastErrorFormatted());
+ 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)
- throw FileError(_("Error copying symbolic link:") + "\n\"" + sourceLink + "\" ->\n\"" + targetLink + "\"" + "\n\n" + getLastErrorFormatted());
#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 = type == SYMLINK_TYPE_DIR ?
- Loki::MakeGuard(&removeDirSimple, targetLink) :
- Loki::MakeGuard(&removeFileSimple, targetLink);
+ 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!)
- copyFileTimes(sourceLink, targetLink, false); //throw (FileError)
+ {
+ const Int64 modTime = getFileTime(sourceLink, SYMLINK_DIRECT); //throw FileError
+ setFileTime(targetLink, modTime, SYMLINK_DIRECT); //throw FileError
+ }
if (copyFilePermissions)
- copyObjectPermissions(sourceLink, targetLink, false); //throw (FileError)
+ copyObjectPermissions(sourceLink, targetLink, SYMLINK_DIRECT); //throw FileError
guardNewDir.Dismiss(); //target has been created successfully!
}
@@ -1302,16 +1308,57 @@ Zstring createTempName(const Zstring& filename)
}
#ifdef FFS_WIN
-struct CallbackData
+class CallbackData
{
- CallbackData(CallbackCopyFile& cb) :
- callback(cb),
- callNr(0),
- exceptionCaught(false) {}
-
- CallbackCopyFile& callback;
- size_t callNr;
- bool exceptionCaught;
+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;
};
@@ -1327,36 +1374,81 @@ DWORD CALLBACK copyCallbackInternal(
HANDLE hDestinationFile,
LPVOID lpData)
{
- if (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)
{
- CallbackData& cbd = *static_cast<CallbackData*>(lpData);
+ 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;
+ }
- //small performance optimization: it seems this callback function is called for every 64 kB (depending on cluster size).
- if (++cbd.callNr % 4 == 0) //executing callback every 256 kB should suffice
+ if (!::SetFileTime(hDestinationFile,
+ &creationTime,
+ NULL,
+ NULL))
{
- //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\
+ 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.callback.updateCopyStatus(UInt64(totalBytesTransferred.QuadPart));
- }
- catch (...)
- {
-#ifndef _MSC_VER
-#warning migrate to std::exception_ptr when available
-#endif
- cbd.exceptionCaught = true;
- cbd.bytesTransferredOnException = UInt64(totalBytesTransferred.QuadPart);
- return PROGRESS_CANCEL;
- }
+ 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;
}
@@ -1379,36 +1471,43 @@ bool supportForNonEncryptedDestination()
}
-void rawCopyWinApi(const Zstring& sourceFile,
- const Zstring& targetFile,
- CallbackCopyFile* callback) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked)
+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!
+ 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 once = BOOST_ONCE_INIT; //caveat: function scope static initialization is not thread-safe in VS 2010!
- boost::call_once(once, []() { nonEncSupported = supportForNonEncryptedDestination(); });
-
+ {
+ 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;
- std::unique_ptr<CallbackData> cbd(callback ? new CallbackData(*callback) : NULL);
-
- if (!::CopyFileEx( //same performance like CopyFile()
- applyLongPathPrefix(sourceFile).c_str(),
- applyLongPathPrefix(targetFile).c_str(),
- callback ? copyCallbackInternal : NULL,
- cbd.get(),
- NULL,
- copyFlags))
+ static bool osIsvistaOrLater = false;
{
- if (cbd.get() && cbd->exceptionCaught)
- callback->updateCopyStatus(cbd->bytesTransferredOnException); //rethrow (hopefully!)
+ 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!
@@ -1439,38 +1538,56 @@ void rawCopyWinApi(const Zstring& sourceFile,
//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)
+ 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(...) {}
+ catch (...) {}
throw FileError(errorMessage);
}
- try //adapt file modification time
- {
- //this is optional, since ::CopyFileEx already copies modification time (but not creation time)
- //chances are good this also avoids a number of unnecessary access-denied errors on NAS shares!
- //(one is: missing permission to change file attributes, remove read-only in particular)
- copyFileTimes(sourceFile, targetFile, true); //throw FileError
- }
- catch (FileError&)
+ if (sourceAttr)
+ *sourceAttr = cbd.getSrcAttr();
+
{
- //CAVEAT: in case one of the drives is FAT, we still(!) need copyFileTimes to apply the DST hack!
- if (dst::isFatDrive(sourceFile) || dst::isFatDrive(targetFile)) //throw()
- throw;
- assert(false); //maybe this catches some test-case left-overs?
+ 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)
+// CallbackCopyFile* callback) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
//{
// /*
// BackupRead() FileRead() CopyFileEx()
@@ -1491,7 +1608,7 @@ void rawCopyWinApi(const Zstring& sourceFile,
// 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
-// NULL,
+// 0,
// OPEN_EXISTING,
// FILE_FLAG_SEQUENTIAL_SCAN,
// NULL);
@@ -1507,8 +1624,7 @@ void rawCopyWinApi(const Zstring& sourceFile,
//
// throw FileError(errorMessage);
// }
-// Loki::ScopeGuard dummy1 = Loki::MakeGuard(::CloseHandle, hFileIn);
-// (void)dummy1; //silence warning "unused variable"
+// LOKI_ON_BLOCK_EXIT2(::CloseHandle, hFileIn);
//
//
// BY_HANDLE_FILE_INFORMATION infoFileIn = {};
@@ -1545,7 +1661,7 @@ void rawCopyWinApi(const Zstring& sourceFile,
// 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,
-// NULL,
+// 0,
// CREATE_NEW,
// (infoFileIn.dwFileAttributes & validAttribs) | FILE_FLAG_SEQUENTIAL_SCAN,
// NULL);
@@ -1565,8 +1681,7 @@ void rawCopyWinApi(const Zstring& sourceFile,
// }
// Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: guard just after opening target and before managing hFileOut
//
-// Loki::ScopeGuard dummy = Loki::MakeGuard(::CloseHandle, hFileOut);
-// (void)dummy; //silence warning "unused variable"
+// LOKI_ON_BLOCK_EXIT2(::CloseHandle, hFileOut);
//
//
//#ifndef _MSC_VER
@@ -1773,7 +1888,7 @@ void rawCopyWinApi(const Zstring& sourceFile,
// HANDLE hSparse = ::CreateFile(L"C:\\sparse.file",
// GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION
// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
-// NULL,
+// 0,
// CREATE_NEW,
// FILE_FLAG_SEQUENTIAL_SCAN,
// NULL);
@@ -1800,19 +1915,20 @@ void rawCopyWinApi(const Zstring& sourceFile,
//}
#endif
-
+#ifdef FFS_LINUX
void rawCopyStream(const Zstring& sourceFile,
const Zstring& targetFile,
- CallbackCopyFile* callback) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting)
+ 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)
+ FileInput fileIn(sourceFile); //throw FileError
//create targetFile and open it for writing
- FileOutput fileOut(targetFile, FileOutput::ACC_CREATE_NEW); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting)
+ FileOutput fileOut(targetFile, FileOutput::ACC_CREATE_NEW); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
static boost::thread_specific_ptr<std::vector<char>> cpyBuf;
if (!cpyBuf.get())
@@ -1823,35 +1939,55 @@ void rawCopyStream(const Zstring& sourceFile,
UInt64 totalBytesTransferred;
do
{
- const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw (FileError)
+ const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError
- fileOut.write(&buffer[0], bytesRead); //throw (FileError)
+ fileOut.write(&buffer[0], bytesRead); //throw FileError
totalBytesTransferred += bytesRead;
//invoke callback method to update progress indicators
- if (callback != NULL)
+ if (callback)
callback->updateCopyStatus(totalBytesTransferred);
}
while (!fileIn.eof());
}
- catch(ErrorTargetExisting&)
+ catch (ErrorTargetExisting&)
{
guardTarget.Dismiss(); //don't delete file that existed previously!
throw;
}
//adapt file modification time:
- copyFileTimes(sourceFile, targetFile, true); //throw (FileError)
+ {
+ 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) //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked)
+ CallbackCopyFile* callback,
+ FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
{
#ifdef FFS_WIN
/*
@@ -1867,21 +2003,22 @@ void copyFileImpl(const Zstring& sourceFile,
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); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked)
- //rawCopyWinOptimized(sourceFile, targetFile, callback); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked) ->about 8% faster
+ 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); //throw (FileError: ErrorTargetPathMissing, ErrorTargetExisting)
+ rawCopyStream(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting
#endif
}
}
-void zen::copyFile(const Zstring& sourceFile, //throw (FileError: ErrorTargetPathMissing, ErrorFileLocked);
+void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPathMissing, ErrorFileLocked
const Zstring& targetFile,
bool copyFilePermissions,
bool transactionalCopy,
- CallbackCopyFile* callback)
+ CallbackCopyFile* callback,
+ FileAttrib* sourceAttr)
{
if (transactionalCopy)
{
@@ -1891,7 +2028,7 @@ void zen::copyFile(const Zstring& sourceFile, //throw (FileError: ErrorTargetPat
//raw file copy
try
{
- copyFileImpl(sourceFile, temporary, callback); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
+ copyFileImpl(sourceFile, temporary, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
}
catch (ErrorTargetExisting&)
{
@@ -1900,40 +2037,39 @@ void zen::copyFile(const Zstring& sourceFile, //throw (FileError: ErrorTargetPat
temporary = createTempName(targetFile);
//retry
- copyFileImpl(sourceFile, temporary, callback); //throw FileError
+ 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);
+ 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)
+ renameFile(temporary, targetFile); //throw FileError
guardTempFile.Dismiss();
-
- //perf: interestingly it is much faster to apply file times BEFORE renaming temporary!
}
else
{
//have target file deleted
if (callback) callback->deleteTargetFile(targetFile);
- copyFileImpl(sourceFile, targetFile, callback); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked
+ 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)
-*/
+ /*
+ 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, true); //throw (FileError)
+ copyObjectPermissions(sourceFile, targetFile, SYMLINK_FOLLOW); //throw FileError
guardTargetFile.Dismiss(); //target has been created successfully!
}
}
bgstack15