summaryrefslogtreecommitdiff
path: root/zen/file_handling.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:20:29 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:20:29 +0200
commitb8f13e45be884dc12884ebe8f3dcd9eecb23a106 (patch)
tree22a6d8b96815d626061ff3e2d432c13078fca5c4 /zen/file_handling.cpp
parent5.4 (diff)
downloadFreeFileSync-b8f13e45be884dc12884ebe8f3dcd9eecb23a106.tar.gz
FreeFileSync-b8f13e45be884dc12884ebe8f3dcd9eecb23a106.tar.bz2
FreeFileSync-b8f13e45be884dc12884ebe8f3dcd9eecb23a106.zip
5.5
Diffstat (limited to 'zen/file_handling.cpp')
-rw-r--r--zen/file_handling.cpp238
1 files changed, 167 insertions, 71 deletions
diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp
index f004e09c..ebfe4d19 100644
--- a/zen/file_handling.cpp
+++ b/zen/file_handling.cpp
@@ -23,7 +23,6 @@
#include "long_path_prefix.h"
#include <Aclapi.h>
#include "dst_hack.h"
-#include "file_update_handle.h"
#include "win_ver.h"
#include "IFileOperation/file_op.h"
@@ -703,18 +702,25 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, Callb
}
//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&) {}
- catch (const ErrorTargetExisting&) {}
+ catch (const ErrorTargetExisting& ) {}
//create target
if (symlinkExists(sourceDir))
{
- if (!dirExists(targetDir))
+ if (!symlinkExists(targetDir))
copySymlink(sourceDir, targetDir, false); //throw FileError -> don't copy permissions
}
else
{
- if (!dirExists(targetDir)) //check even if ErrorTargetExisting: me may have clashed with a file of the same name!!!
- createDirectory(targetDir, sourceDir, false); //throw FileError
+ try
+ {
+ makeNewDirectory(targetDir, sourceDir, false); //FileError, ErrorTargetExisting
+ }
+ catch (const ErrorTargetExisting&)
+ {
+ if (!dirExists(targetDir))
+ throw; //clashed with a file or symlink of the same name!!!
+ }
//move files/folders recursively
TraverseOneLevel::NameList fileList; //list of names: 1. short 2.long
@@ -888,23 +894,6 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
{
*/
- //may need to remove the readonly-attribute (e.g. FAT usb drives)
- FileUpdateHandle targetHandle(filename, [=]
- {
- return ::CreateFile(applyLongPathPrefix(filename).c_str(),
- FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,
- //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even after read-only was removed right before:
- //https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- nullptr,
- OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory
- (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks
- nullptr);
- });
-
- if (targetHandle.get() == INVALID_HANDLE_VALUE)
- throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted());
/*
if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION)
::Sleep(retryInterval); //wait then retry
@@ -913,12 +902,87 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
}
*/
- auto isNullTime = [](const FILETIME & ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; };
+ //temporarily reset read-only flag if required
+ DWORD attribs = INVALID_FILE_ATTRIBUTES;
+ ZEN_ON_SCOPE_EXIT(
+ if (attribs != INVALID_FILE_ATTRIBUTES)
+ ::SetFileAttributes(applyLongPathPrefix(filename).c_str(), attribs);
+ );
+
+ auto removeReadonly = [&]() -> bool //may need to remove the readonly-attribute (e.g. on FAT usb drives)
+ {
+ if (attribs == INVALID_FILE_ATTRIBUTES)
+ {
+ const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(filename).c_str());
+ if (tmpAttr == INVALID_FILE_ATTRIBUTES)
+ throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted());
+
+ if (tmpAttr & FILE_ATTRIBUTE_READONLY)
+ {
+ if (!::SetFileAttributes(applyLongPathPrefix(filename).c_str(), FILE_ATTRIBUTE_NORMAL))
+ throw FileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted());
+
+ attribs = tmpAttr; //reapplied on scope exit
+ return true;
+ }
+ }
+ return false;
+ };
+
+ auto openFile = [&](bool conservativeApproach)
+ {
+ return ::CreateFile(applyLongPathPrefix(filename).c_str(),
+ (conservativeApproach ?
+ //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES, even worse, they may fail silently!
+ //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430
+ //Citrix shares seem to have this issue, too, but at least fail with "access denied" => try generic access first:
+ GENERIC_READ | GENERIC_WRITE :
+ //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even *after* read-only was removed directly before the call!
+ //http://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430
+ //since former gives an error notification we may very well try FILE_WRITE_ATTRIBUTES second.
+ FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES),
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory
+ (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks
+ nullptr);
+ };
+
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+ for (int i = 0; i < 2; ++i) //we will get this handle, no matter what! :)
+ {
+ //1. be conservative
+ hFile = openFile(true);
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ if (::GetLastError() == ERROR_ACCESS_DENIED) //fails if file is read-only (or for "other" reasons)
+ if (removeReadonly())
+ continue;
- if (!::SetFileTime(targetHandle.get(), //__in HANDLE hFile,
+ //2. be a *little* fancy
+ hFile = openFile(false);
+ if (hFile == INVALID_HANDLE_VALUE)
+ {
+ if (::GetLastError() == ERROR_ACCESS_DENIED)
+ if (removeReadonly())
+ continue;
+
+ //3. after these herculean stunts we give up...
+ throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted());
+ }
+ }
+ break;
+ }
+ ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile));
+
+
+ auto isNullTime = [](const FILETIME& ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; };
+
+ if (!::SetFileTime(hFile, //__in HANDLE hFile,
!isNullTime(creationTime) ? &creationTime : nullptr, //__in_opt const FILETIME *lpCreationTime,
- nullptr, //__in_opt const FILETIME *lpLastAccessTime,
- &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime
+ nullptr, //__in_opt const FILETIME *lpLastAccessTime,
+ &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime
{
auto lastErr = ::GetLastError();
@@ -933,7 +997,7 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
{
auto setFileInfo = [&](FILE_BASIC_INFO basicInfo) //throw FileError; no const& since SetFileInformationByHandle() requires non-const parameter!
{
- if (!setFileInformationByHandle(targetHandle.get(), //__in HANDLE hFile,
+ if (!setFileInformationByHandle(hFile, //__in HANDLE hFile,
FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass,
&basicInfo, //__in LPVOID lpFileInformation,
sizeof(basicInfo))) //__in DWORD dwBufferSize
@@ -950,7 +1014,7 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
//---------------------------------------------------------------------------
BY_HANDLE_FILE_INFORMATION fileInfo = {};
- if (::GetFileInformationByHandle(targetHandle.get(), &fileInfo))
+ if (::GetFileInformationByHandle(hFile, &fileInfo))
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
{
FILE_BASIC_INFO basicInfo = {}; //undocumented: file times of "0" stand for "don't change"
@@ -959,6 +1023,7 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
if (!isNullTime(creationTime))
basicInfo.CreationTime = toLargeInteger(creationTime);
+ //set file time + attributes
setFileInfo(basicInfo); //throw FileError
try //... to restore original file attributes
@@ -972,10 +1037,10 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
lastErr = ERROR_SUCCESS;
}
}
-
- if (lastErr != ERROR_SUCCESS)
- throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastErr));
}
+
+ if (lastErr != ERROR_SUCCESS)
+ throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastErr));
}
#ifndef NDEBUG //dst hack: verify data written
@@ -1005,8 +1070,8 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr
else
{
struct timeval newTimes[2] = {};
- newTimes[0].tv_sec = ::time(nullptr); /* seconds */
- newTimes[0].tv_usec = 0; /* microseconds */
+ newTimes[0].tv_sec = ::time(nullptr); //seconds
+ newTimes[0].tv_usec = 0; //microseconds
newTimes[1].tv_sec = to<time_t>(modificationTime);
newTimes[1].tv_usec = 0;
@@ -1140,7 +1205,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli
//copy permissions for files, directories or symbolic links: requires admin rights
-void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError;
+void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError
{
#ifdef FFS_WIN
//setting privileges requires admin rights!
@@ -1314,22 +1379,41 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym
}
-void createDirectory_straight(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level)
+void createDirectoryStraight(const Zstring& directory, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+ const Zstring& templateDir,
+ bool copyFilePermissions)
{
- //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(), nullptr))
+ if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), //__in LPCTSTR lpPathName,
+ nullptr)) //__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes
+ {
+ const std::wstring msg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted();
+ const ErrorCode lastError = getLastError();
+
+ if (lastError == ERROR_ALREADY_EXISTS)
+ throw ErrorTargetExisting(msg);
+ else if (lastError == ERROR_PATH_NOT_FOUND)
+ throw ErrorTargetPathMissing(msg);
+ throw FileError(msg);
+ }
+
#elif defined FFS_LINUX
- if (::mkdir(directory.c_str(), 0755) != 0)
-#endif
+ if (::mkdir(directory.c_str(), 0755) != 0) //mode: drwxr-xr-x
{
- if (level != 0) return;
- throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted());
+ const std::wstring msg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted();
+ const ErrorCode lastError = getLastError();
+
+ if (lastError == EEXIST)
+ throw ErrorTargetExisting(msg);
+ else if (lastError == ENOENT)
+ throw ErrorTargetPathMissing(msg);
+ throw FileError(msg);
}
+#endif
if (!templateDir.empty())
{
@@ -1355,11 +1439,12 @@ void createDirectory_straight(const Zstring& directory, const Zstring& templateD
const DWORD sourceAttr = ::GetFileAttributes(applyLongPathPrefix(sourcePath).c_str());
if (sourceAttr != INVALID_FILE_ATTRIBUTES)
{
+ ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), sourceAttr);
+ //copy "read-only and system attributes": http://blogs.msdn.com/b/oldnewthing/archive/2003/09/30/55100.aspx
+
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!)
@@ -1391,6 +1476,7 @@ void createDirectory_straight(const Zstring& directory, const Zstring& templateD
}
}
#endif
+
zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectory(directory); } catch (...) {} }); //ensure cleanup:
//enforce copying file permissions: it's advertized on GUI...
@@ -1402,47 +1488,48 @@ void createDirectory_straight(const Zstring& directory, const Zstring& templateD
}
-void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level)
+void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions) //FileError, ErrorTargetExisting
{
- if (level == 100) //catch endless recursion
- return;
-
-#ifdef FFS_WIN
- std::unique_ptr<Fix8Dot3NameClash> fnc;
- if (somethingExists(directory))
+ try
{
- //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;
+ createDirectoryStraight(directory, templateDir, copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
}
-#elif defined FFS_LINUX
- if (somethingExists(directory))
+ catch (const ErrorTargetExisting&)
{
- if (dirExists(directory))
+#ifdef FFS_WIN
+ //handle issues with already existing short 8.3 file names on Windows
+ if (have8dot3NameClash(directory))
+ {
+ Fix8Dot3NameClash dummy(directory); //move clashing object to the side
+
+ //now try again...
+ createDirectoryStraight(directory, templateDir, copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
return;
- }
+ }
#endif
- else //if "not somethingExists" we need to create the parent directory
+ throw;
+ }
+ catch (const ErrorTargetPathMissing&)
{
- //try to create parent folders first
+ //we need to create parent directories first
const Zstring dirParent = beforeLast(directory, FILE_NAME_SEPARATOR);
- if (!dirParent.empty() && !dirExists(dirParent))
+ if (!dirParent.empty())
{
//call function recursively
const Zstring templateParent = beforeLast(templateDir, FILE_NAME_SEPARATOR); //returns empty string if ch not found
- createDirectoryRecursively(dirParent, templateParent, copyFilePermissions, level + 1);
+ createDirectoryRecursively(dirParent, templateParent, copyFilePermissions); //throw
+
+ //now try again...
+ createDirectoryStraight(directory, templateDir, copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+ return;
}
+ throw;
}
-
- //now creation should be possible
- createDirectory_straight(directory, templateDir, copyFilePermissions, level); //throw FileError
}
}
-void zen::createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions)
+void zen::makeNewDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions) //FileError, ErrorTargetExisting
{
//remove trailing separator
const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ?
@@ -1453,13 +1540,22 @@ void zen::createDirectory(const Zstring& directory, const Zstring& templateDir,
beforeLast(templateDir, FILE_NAME_SEPARATOR) :
templateDir;
- createDirectoryRecursively(dirFormatted, templateFormatted, copyFilePermissions, 0);
+ createDirectoryRecursively(dirFormatted, templateFormatted, copyFilePermissions); //FileError, ErrorTargetExisting
}
-void zen::createDirectory(const Zstring& directory)
+void zen::makeDirectory(const Zstring& directory)
{
- zen::createDirectory(directory, Zstring(), false);
+ try
+ {
+ makeNewDirectory(directory, Zstring(), false); //FileError, ErrorTargetExisting
+ }
+ catch (const ErrorTargetExisting&)
+ {
+ if (dirExists(directory))
+ return;
+ throw; //clash with file (dir symlink is okay)
+ }
}
@@ -2254,7 +2350,7 @@ Zstring findUnusedTempName(const Zstring& filename)
{
Zstring output = filename + zen::TEMP_FILE_ENDING;
- //ensure uniqueness (+ minor race condition)
+ //ensure uniqueness (+ minor file system race condition!)
for (int i = 1; somethingExists(output); ++i)
output = filename + Zchar('_') + numberTo<Zstring>(i) + zen::TEMP_FILE_ENDING;
bgstack15