diff options
Diffstat (limited to 'zen/file_access.cpp')
-rw-r--r-- | zen/file_access.cpp | 620 |
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) |