summaryrefslogtreecommitdiff
path: root/zen/file_access.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zen/file_access.cpp')
-rw-r--r--zen/file_access.cpp620
1 files changed, 370 insertions, 250 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index bad1b60d..ac68330e 100644
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -44,7 +44,140 @@
using namespace zen;
-bool zen::fileExists(const Zstring& filePath)
+Opt<Zstring> zen::getParentFolderPath(const Zstring& itemPath)
+{
+#ifdef ZEN_WIN
+ assert(startsWith(itemPath, L"\\\\") || //we do NOT support relative paths!
+ (itemPath.size() >= 3 && isAlpha(itemPath[0]) && itemPath[1] == L':' && itemPath[2] == L'\\'));
+
+ //remove trailing separator (even for C:\ root directories)
+ const Zstring itemPathFmt = endsWith(itemPath, FILE_NAME_SEPARATOR) ?
+ beforeLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) :
+ itemPath;
+
+ if (startsWith(itemPathFmt, L"\\\\")) //UNC-name, e.g. \\server-name\share
+ if (std::count(itemPathFmt.begin(), itemPathFmt.end(), FILE_NAME_SEPARATOR) <= 3)
+ return NoValue();
+
+ const Zstring parentDir = beforeLast(itemPathFmt, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
+ if (parentDir.empty())
+ return NoValue();
+ if (parentDir.size() == 2 && isAlpha(parentDir[0]) && parentDir[1] == L':')
+ return appendSeparator(parentDir);
+
+#elif defined ZEN_LINUX || defined ZEN_MAC
+ assert(startsWith(itemPath, L"/")); //we do NOT support relative paths!
+
+ if (itemPath == "/")
+ return NoValue();
+
+ const Zstring parentDir = beforeLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
+ if (parentDir.empty())
+ return Zstring("/");
+#endif
+ return parentDir;
+}
+
+
+ItemType zen::getItemType(const Zstring& itemPath) //throw FileError
+{
+#ifdef ZEN_WIN
+ const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str());
+ if (attr == INVALID_FILE_ATTRIBUTES)
+ {
+ const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
+ if (ec == ERROR_PATH_NOT_FOUND || //
+ ec == ERROR_FILE_NOT_FOUND || //perf: short circuit for common "not existing" error codes
+ ec == ERROR_BAD_NETPATH || //
+ ec == ERROR_BAD_NET_NAME) //
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"GetFileAttributes");
+ }
+
+ if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0)
+ return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 ? ItemType::FOLDER : ItemType::FILE;
+
+ //handle obscure file permission problem where ::GetFileAttributes() fails with ERROR_ACCESS_DENIED or ERROR_SHARING_VIOLATION
+ //while parent directory traversal is successful: e.g. "C:\pagefile.sys"
+ WIN32_FIND_DATA itemInfo = {};
+ const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(itemPath).c_str(), &itemInfo);
+ if (searchHandle == INVALID_HANDLE_VALUE)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"FindFirstFile");
+ ::FindClose(searchHandle);
+
+ if (isSymlink(itemInfo)) //not every FILE_ATTRIBUTE_REPARSE_POINT is a symlink!!!
+ return ItemType::SYMLINK;
+ return (itemInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ? ItemType::FOLDER : ItemType::FILE;
+
+#elif defined ZEN_LINUX || defined ZEN_MAC
+ struct ::stat itemInfo = {};
+ if (::lstat(itemPath.c_str(), &itemInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
+
+ if (S_ISLNK(itemInfo.st_mode))
+ return ItemType::SYMLINK;
+ if (S_ISDIR(itemInfo.st_mode))
+ return ItemType::FOLDER;
+ return ItemType::FILE; //S_ISREG || S_ISCHR || S_ISBLK || S_ISFIFO || S_ISSOCK
+#endif
+}
+
+
+PathDetails zen::getPathDetails(const Zstring& itemPath) //throw FileError
+{
+ const Opt<Zstring> parentPath = getParentFolderPath(itemPath);
+ try
+ {
+ return { getItemType(itemPath), itemPath, {} }; //throw FileError
+ }
+ catch (FileError&)
+ {
+ if (!parentPath) //device root
+ throw;
+ //else: let's dig deeper... don't bother checking Win32 codes; e.g. not existing item may have the codes:
+ // ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE,
+ // ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH => not reliable
+ }
+ const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
+ assert(!itemName.empty());
+
+ PathDetails pd = getPathDetails(*parentPath); //throw FileError
+ if (!pd.relPath.empty())
+ {
+ pd.relPath.push_back(itemName);
+ return { pd.existingType, pd.existingPath, pd.relPath };
+ }
+
+ try
+ {
+ traverseFolder(*parentPath,
+ [&](const FileInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FILE; },
+ [&](const FolderInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FOLDER; },
+ [&](const SymlinkInfo& si) { if (equalFilePath(si.itemName, itemName)) throw ItemType::SYMLINK; },
+ [](const std::wstring& errorMsg) { throw FileError(errorMsg); });
+
+ return { pd.existingType, *parentPath, { itemName } }; //throw FileError
+ }
+ catch (const ItemType& type) { return { type, itemPath, {} }; } //yes, exceptions for control-flow are bad design... but, but...
+ //we're not CPU-bound here and finding the item after getItemType() previously failed is exceptional (even C:\pagefile.sys should be found)
+}
+
+
+Opt<ItemType> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError
+{
+ const PathDetails pd = getPathDetails(itemPath); //throw FileError
+#ifndef NDEBUG
+ Zstring reconstructedPath = pd.existingPath;
+ for (const Zstring& itemName : pd.relPath)
+ reconstructedPath += endsWith(reconstructedPath, FILE_NAME_SEPARATOR) ? itemName : FILE_NAME_SEPARATOR + itemName;
+ assert(itemPath == reconstructedPath);
+#endif
+ if (pd.relPath.empty())
+ return pd.existingType;
+ return NoValue();
+}
+
+
+bool zen::fileAvailable(const Zstring& filePath) //noexcept
{
//symbolic links (broken or not) are also treated as existing files!
#ifdef ZEN_WIN
@@ -61,13 +194,13 @@ bool zen::fileExists(const Zstring& filePath)
}
-bool zen::dirExists(const Zstring& dirPath)
+bool zen::dirAvailable(const Zstring& dirPath) //noexcept
{
//symbolic links (broken or not) are also treated as existing directories!
#ifdef ZEN_WIN
const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirPath).c_str());
if (attr != INVALID_FILE_ATTRIBUTES)
- return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for (dir-)symlinks also
+ return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for ((broken) dir-)symlinks also
#elif defined ZEN_LINUX || defined ZEN_MAC
struct ::stat dirInfo = {};
@@ -78,54 +211,39 @@ bool zen::dirExists(const Zstring& dirPath)
}
-bool zen::symlinkExists(const Zstring& linkPath)
+warn_static("remove following test functions after refactoring")
+
+
+
+bool zen::fileExists(const Zstring& filePath)
{
+ //symbolic links (broken or not) are also treated as existing files!
#ifdef ZEN_WIN
- WIN32_FIND_DATA linkInfo = {};
- const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(linkPath).c_str(), &linkInfo);
- if (searchHandle != INVALID_HANDLE_VALUE)
- {
- ::FindClose(searchHandle);
- return isSymlink(linkInfo);
- }
+ const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str());
+ if (attr != INVALID_FILE_ATTRIBUTES)
+ return (attr & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also
#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat linkInfo = {};
- if (::lstat(linkPath.c_str(), &linkInfo) == 0)
- return S_ISLNK(linkInfo.st_mode);
+ struct ::stat fileInfo = {};
+ if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks!
+ return S_ISREG(fileInfo.st_mode);
#endif
return false;
}
-bool zen::somethingExists(const Zstring& itemPath)
+bool zen::dirExists(const Zstring& dirPath)
{
+ //symbolic links (broken or not) are also treated as existing directories!
#ifdef ZEN_WIN
- const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str());
+ const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirPath).c_str());
if (attr != INVALID_FILE_ATTRIBUTES)
- return true;
- const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls!
-
- //handle obscure file permission problem where ::GetFileAttributes() fails with ERROR_ACCESS_DENIED or ERROR_SHARING_VIOLATION
- //while parent directory traversal is successful: e.g. "C:\pagefile.sys"
- if (ec != ERROR_PATH_NOT_FOUND && //perf: short circuit for common "not existing" error codes
- ec != ERROR_FILE_NOT_FOUND && //
- ec != ERROR_BAD_NETPATH && //
- ec != ERROR_BAD_NET_NAME) //
- {
- WIN32_FIND_DATA fileInfo = {};
- const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(itemPath).c_str(), &fileInfo);
- if (searchHandle != INVALID_HANDLE_VALUE)
- {
- ::FindClose(searchHandle);
- return true;
- }
- }
+ return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for ((broken) dir-)symlinks also
#elif defined ZEN_LINUX || defined ZEN_MAC
- struct ::stat fileInfo = {};
- if (::lstat(itemPath.c_str(), &fileInfo) == 0)
- return true;
+ struct ::stat dirInfo = {};
+ if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks!
+ return S_ISDIR(dirInfo.st_mode);
#endif
return false;
}
@@ -134,7 +252,15 @@ bool zen::somethingExists(const Zstring& itemPath)
namespace
{
#ifdef ZEN_WIN
-bool isFatDrive(const Zstring& filePath) //noexcept
+enum class FatType
+{
+ NONE,
+ FAT,
+ FAT32,
+ EXFAT,
+};
+
+FatType getFatType(const Zstring& filePath) //noexcept
{
const DWORD bufferSize = MAX_PATH + 1;
std::vector<wchar_t> buffer(bufferSize);
@@ -145,7 +271,7 @@ bool isFatDrive(const Zstring& filePath) //noexcept
bufferSize)) //__in DWORD cchBufferLength
{
assert(false);
- return false;
+ return FatType::NONE;
}
const Zstring volumePath = appendSeparator(&buffer[0]);
@@ -161,16 +287,21 @@ bool isFatDrive(const Zstring& filePath) //noexcept
bufferSize)) //__in DWORD nFileSystemNameSize
{
assert(false);
- return false;
+ return FatType::NONE;
}
- return &buffer[0] == Zstring(L"FAT") ||
- &buffer[0] == Zstring(L"FAT32");
+ if (&buffer[0] == Zstring(L"FAT"))
+ return FatType::FAT;
+ if (&buffer[0] == Zstring(L"FAT32"))
+ return FatType::FAT32;
+ if (&buffer[0] == Zstring(L"exFAT"))
+ return FatType::EXFAT;
+ return FatType::NONE;
}
#ifdef ZEN_WIN_VISTA_AND_LATER
-bool isFatDrive(HANDLE hFile) //noexcept
+FatType getFatType(HANDLE hFile) //noexcept
{
const DWORD bufferSize = MAX_PATH + 1; //"The length of the file system name buffer, in TCHARs. The maximum buffer size is MAX_PATH + 1"
std::vector<wchar_t> buffer(bufferSize);
@@ -185,11 +316,16 @@ bool isFatDrive(HANDLE hFile) //noexcept
bufferSize)) //_In_ DWORD nFileSystemNameSize
{
assert(false);
- return false;
+ return FatType::NONE;
}
- return &buffer[0] == Zstring(L"FAT") ||
- &buffer[0] == Zstring(L"FAT32");
+ if (&buffer[0] == Zstring(L"FAT"))
+ return FatType::FAT;
+ if (&buffer[0] == Zstring(L"FAT32"))
+ return FatType::FAT32;
+ if (&buffer[0] == Zstring(L"exFAT"))
+ return FatType::EXFAT;
+ return FatType::NONE;
}
#endif
#endif
@@ -331,7 +467,7 @@ Zstring zen::getTempFolderPath() //throw FileError
}
-bool zen::removeFile(const Zstring& filePath) //throw FileError
+void zen::removeFilePlain(const Zstring& filePath) //throw FileError
{
#ifdef ZEN_WIN
const wchar_t functionName[] = L"DeleteFile";
@@ -345,19 +481,14 @@ bool zen::removeFile(const Zstring& filePath) //throw FileError
ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
#ifdef ZEN_WIN
if (ec == ERROR_ACCESS_DENIED) //function fails if file is read-only
- {
- ::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), FILE_ATTRIBUTE_NORMAL); //(try to) normalize file attributes
-
- if (::DeleteFile(applyLongPathPrefix(filePath).c_str())) //now try again...
- return true;
- ec = ::GetLastError();
- }
+ if (::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), FILE_ATTRIBUTE_NORMAL)) //(try to) normalize file attributes
+ {
+ if (::DeleteFile(applyLongPathPrefix(filePath).c_str())) //now try again...
+ return;
+ ec = ::GetLastError();
+ }
#endif
- if (!somethingExists(filePath)) //warning: changes global error code!!
- return false; //neither file nor any other object (e.g. broken symlink) with that name existing - caveat: what if "access is denied"!?!??!?!?
-
//begin of "regular" error reporting
- const std::wstring errorMsg = replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath));
std::wstring errorDescr = formatSystemError(functionName, ec);
#ifdef ZEN_WIN_VISTA_AND_LATER
@@ -369,18 +500,29 @@ bool zen::removeFile(const Zstring& filePath) //throw FileError
errorDescr = _("The file is locked by another process:") + L"\n" + procList;
}
#endif
- throw FileError(errorMsg, errorDescr);
+ throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath)), errorDescr);
}
- return true;
}
-void zen::removeDirectorySimple(const Zstring& dirPath) //throw FileError
+void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError
{
#ifdef ZEN_WIN
- //(try to) normalize file attributes: actually NEEDED for symbolic links also!
- ::SetFileAttributes(applyLongPathPrefix(dirPath).c_str(), FILE_ATTRIBUTE_NORMAL);
+ const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(linkPath).c_str());
+ if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
+ removeDirectoryPlain(linkPath); //throw FileError
+ else
+ removeFilePlain(linkPath); //throw FileError
+#elif defined ZEN_LINUX || defined ZEN_MAC
+ removeFilePlain(linkPath); //throw FileError
+#endif
+}
+
+
+void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError
+{
+#ifdef ZEN_WIN
const wchar_t functionName[] = L"RemoveDirectory";
if (!::RemoveDirectory(applyLongPathPrefix(dirPath).c_str()))
#elif defined ZEN_LINUX || defined ZEN_MAC
@@ -388,13 +530,20 @@ void zen::removeDirectorySimple(const Zstring& dirPath) //throw FileError
if (::rmdir(dirPath.c_str()) != 0)
#endif
{
- const ErrorCode ec = getLastError(); //copy before making other system calls!
-
- if (!somethingExists(dirPath)) //warning: changes global error code!!
- return;
+ ErrorCode ec = getLastError(); //copy before making other system calls!
+#ifdef ZEN_WIN
+ if (ec == ERROR_ACCESS_DENIED) //(try to) normalize file attributes: NEEDED! even folders and symlinks can have FILE_ATTRIBUTE_READONLY set!
+ if (::SetFileAttributes(applyLongPathPrefix(dirPath).c_str(), FILE_ATTRIBUTE_NORMAL))
+ {
+ if (::RemoveDirectory(applyLongPathPrefix(dirPath).c_str())) //now try again...
+ return;
+ ec = ::GetLastError();
+ }
+#elif defined ZEN_LINUX || defined ZEN_MAC
+ bool symlinkExists = false;
+ try { symlinkExists = getItemType(dirPath) == ItemType::SYMLINK; } /*throw FileError*/ catch (FileError&) {} //previous exception is more relevant
-#if defined ZEN_LINUX || defined ZEN_MAC
- if (symlinkExists(dirPath))
+ if (symlinkExists)
{
if (::unlink(dirPath.c_str()) != 0)
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), L"unlink");
@@ -418,48 +567,41 @@ namespace
{
void removeDirectoryImpl(const Zstring& folderPath) //throw FileError
{
- assert(dirExists(folderPath)); //[!] no symlinks in this context!!!
- //attention: check if folderPath is a symlink! Do NOT traverse into it deleting contained files!!!
-
+#ifndef NDEBUG //[!] no symlinks in this context!!! Do NOT traverse into it deleting contained files!!!
+ try { assert(getItemType(folderPath) != ItemType::SYMLINK); /*throw FileError*/ }
+ catch (FileError&) {}
+#endif
std::vector<Zstring> filePaths;
- std::vector<Zstring> folderSymlinkPaths;
+ std::vector<Zstring> symlinkPaths;
std::vector<Zstring> folderPaths;
//get all files and directories from current directory (WITHOUT subdirectories!)
traverseFolder(folderPath,
- [&](const FileInfo& fi) { filePaths.push_back(fi.fullPath); },
- [&](const DirInfo& di) { folderPaths .push_back(di.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies!
- [&](const SymlinkInfo& si)
- {
-#ifdef ZEN_WIN
- if (dirExists(si.fullPath)) //dir symlink
- folderSymlinkPaths.push_back(si.fullPath);
- else //file symlink, broken symlink
-#endif
- filePaths.push_back(si.fullPath);
- },
+ [&](const FileInfo& fi) { filePaths .push_back(fi.fullPath); },
+ [&](const FolderInfo& fi) { folderPaths .push_back(fi.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies!
+ [&](const SymlinkInfo& si) { symlinkPaths.push_back(si.fullPath); },
[](const std::wstring& errorMsg) { throw FileError(errorMsg); });
for (const Zstring& filePath : filePaths)
- removeFile(filePath); //throw FileError
+ removeFilePlain(filePath); //throw FileError
- for (const Zstring& symlinkPath : folderSymlinkPaths)
- removeDirectorySimple(symlinkPath); //throw FileError
+ for (const Zstring& symlinkPath : symlinkPaths)
+ removeSymlinkPlain(symlinkPath); //throw FileError
//delete directories recursively
for (const Zstring& subFolderPath : folderPaths)
removeDirectoryImpl(subFolderPath); //throw FileError; call recursively to correctly handle symbolic links
- removeDirectorySimple(folderPath); //throw FileError
+ removeDirectoryPlain(folderPath); //throw FileError
}
}
-void zen::removeDirectoryRecursively(const Zstring& dirPath) //throw FileError
+void zen::removeDirectoryPlainRecursion(const Zstring& dirPath) //throw FileError
{
- if (symlinkExists(dirPath))
- removeDirectorySimple(dirPath); //throw FileError
- else if (somethingExists(dirPath))
+ if (getItemType(dirPath) == ItemType::SYMLINK) //throw FileError
+ removeSymlinkPlain(dirPath); //throw FileError
+ else
removeDirectoryImpl(dirPath); //throw FileError
}
@@ -524,7 +666,6 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro
errorDescr = _("The file is locked by another process:") + L"\n" + procList;
}
#endif
-
if (ec == ERROR_NOT_SAME_DEVICE)
throw ErrorDifferentVolume(errorMsg, errorDescr);
if (ec == ERROR_ALREADY_EXISTS || //-> used on Win7 x64
@@ -534,7 +675,8 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro
}
#elif defined ZEN_LINUX || defined ZEN_MAC
- //rename() will never fail with EEXIST, but always (atomically) overwrite! => equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists
+ //rename() will never fail with EEXIST, but always (atomically) overwrite!
+ //=> equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists or ::MoveFileEx + MOVEFILE_REPLACE_EXISTING
//=> Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy
//=> OS X: no solution
@@ -550,9 +692,15 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro
throw FileError(errorMsg, errorDescr);
};
- if (!equalFilePath(pathSource, pathTarget)) //OS X: changing file name case is not an "already exists" error!
- if (somethingExists(pathTarget))
+ if (!equalFilePath(pathSource, pathTarget)) //exception for OS X: changing file name case is not an "already exists" situation!
+ {
+ bool alreadyExists = true;
+ try { /*ItemType type = */getItemType(pathTarget); } /*throw FileError*/ catch (FileError&) { alreadyExists = false; }
+
+ if (alreadyExists)
throwException(EEXIST);
+ //else: nothing exists or other error (hopefully ::rename will also fail!)
+ }
if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0)
throwException(errno);
@@ -585,10 +733,11 @@ Zstring getFilenameFmt(const Zstring& filePath, Function fun) //throw(); returns
}
-Zstring findUnused8Dot3Name(const Zstring& filePath) //find a unique 8.3 short name
+Zstring findUnused8Dot3Name(const Zstring& filePath) //throw FileError
{
- const Zstring pathPrefix = contains(filePath, FILE_NAME_SEPARATOR) ?
- (beforeLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) + FILE_NAME_SEPARATOR) : Zstring();
+ Opt<Zstring> parentPath = getParentFolderPath(filePath);
+ assert(parentPath);
+ const Zstring pathPrefix = parentPath ? appendSeparator(*parentPath) : Zstring();
//extension needn't contain reasonable data
Zstring extension = getFileExtension(filePath);
@@ -599,35 +748,35 @@ Zstring findUnused8Dot3Name(const Zstring& filePath) //find a unique 8.3 short n
for (int index = 0; index < 100000000; ++index) //filePath must be representable by <= 8 characters
{
- const Zstring output = pathPrefix + numberTo<Zstring>(index) + Zchar('.') + extension;
- if (!somethingExists(output)) //ensure uniqueness
- return output;
+ const Zstring testPath = pathPrefix + numberTo<Zstring>(index) + Zchar('.') + extension;
+ if (!getItemTypeIfExists(testPath)) //throw FileError
+ return testPath;
}
throw std::runtime_error(std::string("100,000,000 files, one for each number, exist in this directory? You're kidding...") + utfCvrtTo<std::string>(pathPrefix) +
"\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
}
-bool have8dot3NameClash(const Zstring& filePath)
+bool have8dot3NameClash(const Zstring& itemPath)
{
- if (!contains(filePath, FILE_NAME_SEPARATOR))
- return false;
+ try
+ {
+ /*ItemType type =*/ getItemType(itemPath); //throw FileError
+ }
+ catch (FileError&) { return false; }
- if (somethingExists(filePath)) //name OR directory!
+ const Zstring nameOrig = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
+ const Zstring nameShort = afterLast(getFilenameFmt(itemPath, ::GetShortPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error
+ const Zstring nameLong = afterLast(getFilenameFmt(itemPath, ::GetLongPathName ), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //
+
+ if (!nameShort.empty() &&
+ !nameLong .empty() &&
+ equalFilePath(nameOrig, nameShort) &&
+ !equalFilePath(nameShort, nameLong))
{
- const Zstring origName = afterLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
- const Zstring shortName = afterLast(getFilenameFmt(filePath, ::GetShortPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error
- const Zstring longName = afterLast(getFilenameFmt(filePath, ::GetLongPathName ), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //
-
- if (!shortName.empty() &&
- !longName .empty() &&
- equalFilePath(origName, shortName) &&
- !equalFilePath(shortName, longName))
- {
- //for filePath short and long file name are equal and another unrelated file happens to have the same short name
- //e.g. filePath == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1"
- return true;
- }
+ //for itemPath short and long file name are equal and another unrelated file happens to have the same short name
+ //e.g. itemPath == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1"
+ return true;
}
return false;
}
@@ -639,16 +788,16 @@ public:
{
const Zstring longName = afterLast(getFilenameFmt(filePath, ::GetLongPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error
- unrelatedFile = beforeLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
- if (!unrelatedFile.empty())
- unrelatedFile += FILE_NAME_SEPARATOR;
- unrelatedFile += longName;
+ Opt<Zstring> parentPath = getParentFolderPath(filePath);
+ assert(parentPath);
+ unrelatedFilePath_ = parentPath ? appendSeparator(*parentPath) : Zstring();
+ unrelatedFilePath_ += longName;
//find another name in short format: this ensures the actual short name WILL be renamed as well!
- unrelatedFileParked = findUnused8Dot3Name(filePath);
+ parkedFilePath_ = findUnused8Dot3Name(filePath); //throw FileError
//move already existing short name out of the way for now
- renameFile_sub(unrelatedFile, unrelatedFileParked); //throw FileError, ErrorDifferentVolume
+ renameFile_sub(unrelatedFilePath_, parkedFilePath_); //throw FileError, ErrorDifferentVolume
//DON'T call renameFile() to avoid reentrance!
}
@@ -657,13 +806,13 @@ public:
//the file system should assign this unrelated file a new (unique) short name
try
{
- renameFile_sub(unrelatedFileParked, unrelatedFile); //throw FileError, ErrorDifferentVolume
+ renameFile_sub(parkedFilePath_, unrelatedFilePath_); //throw FileError, ErrorDifferentVolume
}
catch (FileError&) {}
}
private:
- Zstring unrelatedFile;
- Zstring unrelatedFileParked;
+ Zstring unrelatedFilePath_;
+ Zstring parkedFilePath_;
};
#endif
}
@@ -752,51 +901,52 @@ void setFileTimeByHandle(HANDLE hFile, //throw FileError
std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath));
//add more meaningful message: FAT accepts only a subset of the NTFS date range
+ if (ec == ERROR_INVALID_PARAMETER)
#ifdef ZEN_WIN_VISTA_AND_LATER
- if (ec == ERROR_INVALID_PARAMETER && isFatDrive(hFile))
+ if (getFatType(hFile) != FatType::NONE) //exFAT has the same date range like FAT/FAT32: http://www.freefilesync.org/forum/viewtopic.php?t=4051
#else
- if (ec == ERROR_INVALID_PARAMETER && isFatDrive(filePath))
+ if (getFatType(filePath) != FatType::NONE)
#endif
- {
- //let's not fail due to an invalid creation time on FAT: http://www.freefilesync.org/forum/viewtopic.php?t=2278
- if (creationTime) //retry (single-level recursion at most!)
- return setFileTimeByHandle(hFile, nullptr, lastWriteTime, filePath); //throw FileError
-
- //if the ERROR_INVALID_PARAMETER is due to an invalid date, enhance message:
- const LARGE_INTEGER writeTimeInt = toLargeInteger(lastWriteTime);
- if (writeTimeInt.QuadPart < 0x01a8e79fe1d58000 || //1980-01-01 https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs#Year_2100
- writeTimeInt.QuadPart >= 0x022f716377640000) //2100-01-01
{
- errorMsg += L"\nA FAT volume can only store dates from 1980 to 2099:\n" "\twrite time (UTC):";
- SYSTEMTIME st = {};
- if (::FileTimeToSystemTime(&lastWriteTime, //__in const FILETIME *lpFileTime,
- &st)) //__out LPSYSTEMTIME lpSystemTime
+ //let's not fail due to an invalid creation time on FAT: http://www.freefilesync.org/forum/viewtopic.php?t=2278
+ if (creationTime) //retry (single-level recursion at most!)
+ return setFileTimeByHandle(hFile, nullptr, lastWriteTime, filePath); //throw FileError
+
+ //if the ERROR_INVALID_PARAMETER is due to an invalid date, enhance message:
+ const LARGE_INTEGER writeTimeInt = toLargeInteger(lastWriteTime);
+ if (writeTimeInt.QuadPart < 0x01a8e79fe1d58000 || //1980-01-01 https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs#Year_2100
+ writeTimeInt.QuadPart >= 0x022f716377640000) //2100-01-01
{
- //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!!
- int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
- if (bufferSize > 0)
- {
- std::vector<wchar_t> buffer(bufferSize);
- if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale,
- 0, //_In_ DWORD dwFlags,
- &st, //_In_opt_ const SYSTEMTIME *lpDate,
- nullptr, //_In_opt_ LPCTSTR lpFormat,
- &buffer[0], //_Out_opt_ LPTSTR lpDateStr,
- bufferSize) > 0) //_In_ int cchDate
- errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
- }
-
- bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
- if (bufferSize > 0)
+ errorMsg += L"\nA FAT volume can only store dates from 1980 to 2099:\n" "\twrite time (UTC):";
+ SYSTEMTIME st = {};
+ if (::FileTimeToSystemTime(&lastWriteTime, //__in const FILETIME *lpFileTime,
+ &st)) //__out LPSYSTEMTIME lpSystemTime
{
- std::vector<wchar_t> buffer(bufferSize);
- if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0)
- errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
+ //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!!
+ int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
+ if (bufferSize > 0)
+ {
+ std::vector<wchar_t> buffer(bufferSize);
+ if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale,
+ 0, //_In_ DWORD dwFlags,
+ &st, //_In_opt_ const SYSTEMTIME *lpDate,
+ nullptr, //_In_opt_ LPCTSTR lpFormat,
+ &buffer[0], //_Out_opt_ LPTSTR lpDateStr,
+ bufferSize) > 0) //_In_ int cchDate
+ errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
+ }
+
+ bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0);
+ if (bufferSize > 0)
+ {
+ std::vector<wchar_t> buffer(bufferSize);
+ if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0)
+ errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination!
+ }
}
+ errorMsg += L" (" + numberTo<std::wstring>(writeTimeInt.QuadPart) + L")"; //just in case the above date formatting fails
}
- errorMsg += L" (" + numberTo<std::wstring>(writeTimeInt.QuadPart) + L")"; //just in case the above date formatting fails
}
- }
throw FileError(errorMsg, formatSystemError(L"SetFileTime", ec));
}
@@ -1200,9 +1350,12 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P
//in contrast to ::SetSecurityInfo(), ::SetFileSecurity() seems to honor the "inherit DACL/SACL" flags
//CAVEAT: if a file system does not support ACLs, GetFileSecurity() will return successfully with a *valid* security descriptor containing *no* ACL entries!
+ const bool isSymlinkSource = getItemType(sourcePath) == ItemType::SYMLINK; //throw FileError
+ const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
+
//NOTE: ::GetFileSecurity()/::SetFileSecurity() do NOT follow Symlinks! getResolvedSymlinkPath() requires Vista or later!
- const Zstring sourceResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(sourcePath) ? getResolvedSymlinkPath(sourcePath) : sourcePath; //throw FileError
- const Zstring targetResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(targetPath) ? getResolvedSymlinkPath(targetPath) : targetPath; //
+ const Zstring sourceResolved = procSl == ProcSymlink::FOLLOW && isSymlinkSource ? getResolvedSymlinkPath(sourcePath) : sourcePath; //throw FileError
+ const Zstring targetResolved = procSl == ProcSymlink::FOLLOW && isSymlinkTarget ? getResolvedSymlinkPath(targetPath) : targetPath; //
//setting privileges requires admin rights!
try
@@ -1371,7 +1524,8 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P
if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown");
- if (!symlinkExists(targetPath) && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
+ const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
+ if (!isSymlinkTarget && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
}
@@ -1405,66 +1559,46 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P
}
#endif
}
+}
-void makeDirectoryRecursivelyImpl(const Zstring& dirPath) //FileError
+void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw FileError
{
- assert(!endsWith(dirPath, FILE_NAME_SEPARATOR)); //even "C:\" should be "C:" as input!
+ if (!getParentFolderPath(dirPath)) //device root
+ return static_cast<void>(/*ItemType =*/ getItemType(dirPath)); //throw FileError
try
{
- copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+ copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting
}
- catch (const ErrorTargetExisting&) {} //*something* existing: folder or FILE!
- catch (const ErrorTargetPathMissing&)
+ catch (FileError&)
{
- //we need to create parent directories first
- const Zstring parentPath = beforeLast(dirPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
- if (!parentPath.empty())
- {
- //recurse...
- makeDirectoryRecursivelyImpl(parentPath); //throw FileError
+ Opt<PathDetails> pd;
+ try { pd = getPathDetails(dirPath); /*throw FileError*/ }
+ catch (FileError&) {} //previous exception is more relevant
- //now try again...
- copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing)
+ if (pd && pd->existingType != ItemType::FILE)
+ {
+ Zstring intermediatePath = pd->existingPath;
+ for (const Zstring& itemName : pd->relPath)
+ {
+ intermediatePath = appendSeparator(intermediatePath) + itemName;
+ copyNewDirectory(Zstring(), intermediatePath, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting)
+ }
return;
}
throw;
}
}
-}
-
-
-void zen::makeDirectoryRecursively(const Zstring& dirPath) //throw FileError
-{
- //remove trailing separator (even for C:\ root directories)
- const Zstring dirFormatted = endsWith(dirPath, FILE_NAME_SEPARATOR) ?
- beforeLast(dirPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) :
- dirPath;
- makeDirectoryRecursivelyImpl(dirFormatted); //FileError
-}
//source path is optional (may be empty)
-void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing
+void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting
bool copyFilePermissions)
{
#ifdef ZEN_WIN
auto getErrorMsg = [](const Zstring& path) { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(path)); };
- //special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS!
- const Zstring pathPf = appendSeparator(targetPath); //we do not support "C:" to represent a relative path!
- if (pathPf.size() == 3 &&
- isAlpha(pathPf[0]) && pathPf[1] == L':')
- {
- const DWORD ec = somethingExists(pathPf) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; //don't use dirExists() => harmonize with ErrorTargetExisting!
- const std::wstring errorDescr = formatSystemError(L"CreateDirectory", ec);
-
- if (ec == ERROR_ALREADY_EXISTS)
- throw ErrorTargetExisting(getErrorMsg(pathPf), errorDescr);
- throw FileError(getErrorMsg(pathPf), errorDescr); //[!] this is NOT a ErrorTargetPathMissing case!
- }
-
//deliberately don't support creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/
if (endsWith(targetPath, L' ') ||
endsWith(targetPath, L'.'))
@@ -1498,8 +1632,9 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
if (ec == ERROR_ALREADY_EXISTS)
throw ErrorTargetExisting(getErrorMsg(targetPath), errorDescr);
- else if (ec == ERROR_PATH_NOT_FOUND)
- throw ErrorTargetPathMissing(getErrorMsg(targetPath), errorDescr);
+ //else if (ec == ERROR_PATH_NOT_FOUND || //the ususal suspect
+ // ec == ERROR_FILE_NOT_FOUND) //Webdav incorrectly returns this one: http://www.freefilesync.org/forum/viewtopic.php?t=4053
+ // throw ErrorTargetPathMissing(getErrorMsg(targetPath), errorDescr);
throw FileError(getErrorMsg(targetPath), errorDescr);
}
}
@@ -1524,8 +1659,8 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
if (lastError == EEXIST)
throw ErrorTargetExisting(errorMsg, errorDescr);
- else if (lastError == ENOENT)
- throw ErrorTargetPathMissing(errorMsg, errorDescr);
+ //else if (lastError == ENOENT)
+ // throw ErrorTargetPathMissing(errorMsg, errorDescr);
throw FileError(errorMsg, errorDescr);
}
#endif
@@ -1610,7 +1745,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath,
catch (FileError&) {}
#endif
- ZEN_ON_SCOPE_FAIL(try { removeDirectorySimple(targetPath); }
+ ZEN_ON_SCOPE_FAIL(try { removeDirectoryPlain(targetPath); }
catch (FileError&) {}); //ensure cleanup:
//enforce copying file permissions: it's advertized on GUI...
@@ -1625,11 +1760,13 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
const Zstring linkPath = getSymlinkTargetRaw(sourceLink); //throw FileError; accept broken symlinks
#ifdef ZEN_WIN
- const bool isDirLink = [&]() -> bool
- {
- const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(sourceLink).c_str());
- return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY);
- }();
+ WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {};
+ if (!::GetFileAttributesEx(applyLongPathPrefix(sourceLink).c_str(), //__in LPCTSTR lpFileName,
+ GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ &sourceAttr)) //__out LPVOID lpFileInformation
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"GetFileAttributesEx");
+
+ const bool isDirLink = (sourceAttr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
using CreateSymbolicLinkFunc = BOOLEAN (WINAPI*)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags);
const SysDllFun<CreateSymbolicLinkFunc> createSymbolicLink(L"kernel32.dll", "CreateSymbolicLinkW");
@@ -1649,30 +1786,11 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool
THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), functionName);
//allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist!
-
- auto cleanUp = [&]
- {
- try
- {
-#ifdef ZEN_WIN
- if (isDirLink)
- removeDirectorySimple(targetLink); //throw FileError
- else
-#endif
- removeFile(targetLink); //throw FileError
- }
- catch (FileError&) {}
- };
- ZEN_ON_SCOPE_FAIL(cleanUp());
+ ZEN_ON_SCOPE_FAIL(try { removeSymlinkPlain(targetLink); /*throw FileError*/ }
+ catch (FileError&) {});
//file times: essential for sync'ing a symlink: enforce this! (don't just try!)
#ifdef ZEN_WIN
- WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {};
- if (!::GetFileAttributesEx(applyLongPathPrefix(sourceLink).c_str(), //__in LPCTSTR lpFileName,
- GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
- &sourceAttr)) //__out LPVOID lpFileInformation
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"GetFileAttributesEx");
-
setWriteTimeNative(targetLink, sourceAttr.ftLastWriteTime, &sourceAttr.ftCreationTime, ProcSymlink::DIRECT); //throw FileError
#else
@@ -1924,8 +2042,6 @@ InSyncAttributes copyFileWindowsStream(const Zstring& sourceFile, //throw FileEr
ec == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6
throw ErrorTargetExisting(errorMsg, errorDescr);
- //if (ec == ERROR_PATH_NOT_FOUND) throw ErrorTargetPathMissing(errorMsg, errorDescr);
-
throw FileError(errorMsg, errorDescr);
}
#ifdef ZEN_WIN_VISTA_AND_LATER
@@ -1943,8 +2059,8 @@ InSyncAttributes copyFileWindowsStream(const Zstring& sourceFile, //throw FileEr
assert(false);
);
#else
- ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); }
- catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget
+ ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); }
+ catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget
FileOutput fileOut(hFileTarget, targetFile); //pass ownership
#endif
@@ -2071,7 +2187,7 @@ InSyncAttributes copyFileWindowsStream(const Zstring& sourceFile, //throw FileEr
}
//time needs to be set at the end: WriteFile/BackupWrite() change modification time
- setFileTimeByHandle(fileOut.getHandle(), &sourceInfo.ftCreationTime,sourceInfo.ftLastWriteTime, targetFile); //throw FileError
+ setFileTimeByHandle(fileOut.getHandle(), &sourceInfo.ftCreationTime, sourceInfo.ftLastWriteTime, targetFile); //throw FileError
return newAttrib;
}
@@ -2206,7 +2322,7 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE
try { activatePrivilege(PrivilegeName::RESTORE); }
catch (const FileError&) { backupPrivilegesActive = false; }
- auto guardTarget = zen::makeGuard<ScopeGuardRunMode::ON_FAIL>([&] { try { removeFile(targetFile); } catch (FileError&) {} });
+ auto guardTarget = zen::makeGuard<ScopeGuardRunMode::ON_FAIL>([&] { try { removeFilePlain(targetFile); } catch (FileError&) {} });
//transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we? ;)
DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS;
@@ -2277,16 +2393,20 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE
throw ErrorTargetExisting(errorMsg, errorDescr);
}
- //if (lastError == ERROR_PATH_NOT_FOUND) throw ErrorTargetPathMissing(errorMsg, errorDescr); //could this also be source path missing!?
+ //lastError == ERROR_PATH_NOT_FOUND: could this also be source path missing!?
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 (ec == ERROR_INVALID_PARAMETER &&
- isFatDrive(targetFile) &&
- getFilesize(sourceFile) >= 4U * std::uint64_t(1024U * 1024 * 1024)) //throw FileError
- errorDescr += L"\nFAT volumes cannot store files larger than 4 gigabytes.";
- //see "Limitations of the FAT32 File System": http://support.microsoft.com/kb/314463/en-us
+ if (ec == ERROR_INVALID_PARAMETER)
+ {
+ const FatType fatType = getFatType(targetFile);
+ if ((fatType == FatType::FAT ||
+ fatType == FatType::FAT32) && //no problem for exFAT (limit ca. 128 PB)
+ getFilesize(sourceFile) >= 4U * std::uint64_t(1024U * 1024 * 1024)) //throw FileError
+ errorDescr += L"\nFAT volumes cannot store files larger than 4 gigabytes.";
+ //see "Limitations of the FAT32 File System": http://support.microsoft.com/kb/314463/en-us
+ }
//note: ERROR_INVALID_PARAMETER can also occur when copying to a SharePoint server or MS SkyDrive and the target file path is of a restricted type.
}
@@ -2378,7 +2498,7 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError
throw FileError(errorMsg, errorDescr);
}
- ZEN_ON_SCOPE_FAIL( try { removeFile(targetFile); }
+ ZEN_ON_SCOPE_FAIL( try { removeFilePlain(targetFile); }
catch (FileError&) {} );
//transactional behavior: place guard after ::open() and before lifetime of FileOutput:
//=> don't delete file that existed previously!!!
@@ -2459,7 +2579,7 @@ InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targ
const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked
//at this point we know we created a new file, so it's fine to delete it for cleanup!
- ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); }
+ ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); }
catch (FileError&) {});
if (copyFilePermissions)
bgstack15