From 6011596a2f247bde24095e985f6e2547e4b191ef Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Mon, 9 Jan 2017 10:54:11 +0100 Subject: 8.8 --- zen/dir_watcher.cpp | 2 +- zen/file_access.cpp | 620 +++++++++++++++++++++++++++++-------------------- zen/file_access.h | 42 +++- zen/file_error.h | 2 +- zen/file_traverser.cpp | 42 ++-- zen/file_traverser.h | 15 +- zen/fixed_list.h | 6 +- zen/recycler.cpp | 104 +++++---- zen/recycler.h | 6 +- zen/serialize.h | 2 +- zen/shell_execute.h | 2 +- zen/thread.h | 4 +- 12 files changed, 511 insertions(+), 336 deletions(-) (limited to 'zen') diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 98190bba..79e8aeb7 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -396,7 +396,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError traverse = [&traverse, &fullFolderList](const Zstring& path) { traverseFolder(path, nullptr, - [&](const DirInfo& di ) { fullFolderList.push_back(di.fullPath); traverse(di.fullPath); }, + [&](const FolderInfo& fi ) { fullFolderList.push_back(fi.fullPath); traverse(fi.fullPath); }, nullptr, //don't traverse into symlinks (analog to windows build) [&](const std::wstring& errorMsg) { throw FileError(errorMsg); }); }; 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 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 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 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 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 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 filePaths; - std::vector folderSymlinkPaths; + std::vector symlinkPaths; std::vector 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 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(index) + Zchar('.') + extension; - if (!somethingExists(output)) //ensure uniqueness - return output; + const Zstring testPath = pathPrefix + numberTo(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(pathPrefix) + "\n" + std::string(__FILE__) + ":" + numberTo(__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 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 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 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 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 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(writeTimeInt.QuadPart) + L")"; //just in case the above date formatting fails } - errorMsg += L" (" + numberTo(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(/*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 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 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([&] { try { removeFile(targetFile); } catch (FileError&) {} }); + auto guardTarget = zen::makeGuard([&] { 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) diff --git a/zen/file_access.h b/zen/file_access.h index fdb787bd..0586ea8f 100644 --- a/zen/file_access.h +++ b/zen/file_access.h @@ -17,17 +17,40 @@ namespace zen { //note: certain functions require COM initialization! (vista_file_op.h) +Opt getParentFolderPath(const Zstring& itemPath); + +//POSITIVE existence checks; if negative: 1. item not existing 2. different type 3.access error or similar +bool fileAvailable(const Zstring& filePath); //noexcept +bool dirAvailable (const Zstring& dirPath ); // + + bool fileExists (const Zstring& filePath); //noexcept; check whether file or file-symlink exists bool dirExists (const Zstring& dirPath ); //noexcept; check whether directory or dir-symlink exists -bool symlinkExists (const Zstring& linkPath); //noexcept; check whether a symbolic link exists -bool somethingExists(const Zstring& itemPath); //noexcept; check whether any object with this name exists + +enum class ItemType +{ + FILE, + FOLDER, + SYMLINK, +}; +//(hopefully) fast: does not distinguish between error/not existing +ItemType getItemType (const Zstring& itemPath); //throw FileError +//execute potentially SLOW folder traversal but distinguish error/not existing +Opt getItemTypeIfExists(const Zstring& itemPath); //throw FileError + +struct PathDetails +{ + ItemType existingType; + Zstring existingPath; //itemPath =: existingPath + relPath + std::vector relPath; // +}; +PathDetails getPathDetails(const Zstring& itemPath); //throw FileError enum class ProcSymlink { DIRECT, FOLLOW }; - void setFileTime(const Zstring& filePath, std::int64_t modificationTime, ProcSymlink procSl); //throw FileError //symlink handling: always evaluate target @@ -37,11 +60,10 @@ VolumeId getVolumeId(const Zstring& itemPath); //throw FileError //get per-user directory designated for temporary files: Zstring getTempFolderPath(); //throw FileError -bool removeFile(const Zstring& filePath); //throw FileError; return "false" if file is not existing - -void removeDirectorySimple(const Zstring& dirPath); //throw FileError - -void removeDirectoryRecursively(const Zstring& dirPath); //throw FileError +void removeFilePlain (const Zstring& filePath); //throw FileError; ERROR if not existing +void removeSymlinkPlain (const Zstring& linkPath); //throw FileError; ERROR if not existing +void removeDirectoryPlain(const Zstring& dirPath ); //throw FileError; ERROR if not existing +void removeDirectoryPlainRecursion(const Zstring& dirPath); //throw FileError; ERROR if not existing //rename file or directory: no copying!!! void renameFile(const Zstring& itemPathOld, const Zstring& itemPathNew); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting @@ -50,11 +72,11 @@ bool supportsPermissions(const Zstring& dirPath); //throw FileError, dereference //- no error if already existing //- create recursively if parent directory is not existing -void makeDirectoryRecursively(const Zstring& dirPath); //throw FileError +void createDirectoryIfMissingRecursion(const Zstring& dirPath); //throw FileError //fail if already existing or parent directory not existing: //source path is optional (may be empty) -void copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, bool copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing +void copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, bool copyFilePermissions); //throw FileError, ErrorTargetExisting void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions); //throw FileError diff --git a/zen/file_error.h b/zen/file_error.h index 374d0b2c..aa41040d 100644 --- a/zen/file_error.h +++ b/zen/file_error.h @@ -31,7 +31,7 @@ private: #define DEFINE_NEW_FILE_ERROR(X) struct X : public FileError { X(const std::wstring& msg) : FileError(msg) {} X(const std::wstring& msg, const std::wstring& descr) : FileError(msg, descr) {} }; DEFINE_NEW_FILE_ERROR(ErrorTargetExisting); -DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing); +//DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing); DEFINE_NEW_FILE_ERROR(ErrorFileLocked); DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume); diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 3eb284e1..ef6d255c 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -28,8 +28,8 @@ using namespace zen; void zen::traverseFolder(const Zstring& dirPath, const std::function& onFile, - const std::function& onDir, - const std::function& onLink, + const std::function& onFolder, + const std::function& onSymlink, const std::function& onError) { try @@ -41,12 +41,15 @@ void zen::traverseFolder(const Zstring& dirPath, { const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! if (ec == ERROR_FILE_NOT_FOUND) - { - //1. directory may not exist *or* 2. it is completely empty: not all directories contain "., .." entries, e.g. a drive's root directory; NetDrive - // -> FindFirstFile() is a nice example of violation of API design principle of single responsibility - if (dirExists(dirPath)) //yes, a race-condition, still the best we can do - return; - } + try + { + //1. directory may not exist *or* 2. it is completely empty: not all directories contain "., .." entries, e.g. a drive's root directory; NetDrive + // -> FindFirstFile() is a nice example of violating the API design principle of single responsibility + if (getItemType(dirPath) == ItemType::FOLDER) //throw FileError + return; + } + catch (FileError&) {} //previous exception is more relevant + throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(L"FindFirstFile", ec)); } ZEN_ON_SCOPE_EXIT(::FindClose(hDir)); @@ -75,22 +78,23 @@ void zen::traverseFolder(const Zstring& dirPath, if (itemNameRaw[0] == 0) throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile: Data corruption; item with empty name."); - const Zstring& itemPath = appendSeparator(dirPath) + itemNameRaw; + const Zstring& itemName = itemNameRaw; + const Zstring& itemPath = appendSeparator(dirPath) + itemName; if (zen::isSymlink(findData)) //check first! { - if (onLink) - onLink({ itemPath, filetimeToTimeT(findData.ftLastWriteTime) }); + if (onSymlink) + onSymlink({ itemName, itemPath, filetimeToTimeT(findData.ftLastWriteTime) }); } else if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - if (onDir) - onDir({ itemPath }); + if (onFolder) + onFolder({ itemName, itemPath }); } else //a file { if (onFile) - onFile({ itemPath, get64BitUInt(findData.nFileSizeLow, findData.nFileSizeHigh), filetimeToTimeT(findData.ftLastWriteTime) }); + onFile({ itemName, itemPath, get64BitUInt(findData.nFileSizeLow, findData.nFileSizeHigh), filetimeToTimeT(findData.ftLastWriteTime) }); } } @@ -159,18 +163,18 @@ void zen::traverseFolder(const Zstring& dirPath, if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! { - if (onLink) - onLink({ itemPath, statData.st_mtime}); + if (onSymlink) + onSymlink({ itemName, itemPath, statData.st_mtime}); } else if (S_ISDIR(statData.st_mode)) //a directory { - if (onDir) - onDir({ itemPath }); + if (onFolder) + onFolder({ itemName, itemPath }); } else //a file or named pipe, ect. { if (onFile) - onFile({ itemPath, makeUnsigned(statData.st_size), statData.st_mtime }); + onFile({ itemName, itemPath, makeUnsigned(statData.st_size), statData.st_mtime }); } /* It may be a good idea to not check "S_ISREG(statData.st_mode)" explicitly and to not issue an error message on other types to support these scenarios: diff --git a/zen/file_traverser.h b/zen/file_traverser.h index 5fffe0e2..1badddf9 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -16,19 +16,22 @@ namespace zen { struct FileInfo { - const Zstring& fullPath; + Zstring itemName; + Zstring fullPath; std::uint64_t fileSize; //[bytes] std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC }; -struct DirInfo +struct FolderInfo { - const Zstring& fullPath; + Zstring itemName; + Zstring fullPath; }; struct SymlinkInfo { - const Zstring& fullPath; + Zstring itemName; + Zstring fullPath; std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC }; @@ -36,8 +39,8 @@ struct SymlinkInfo //- directory path may end with PATH_SEPARATOR void traverseFolder(const Zstring& dirPath, //noexcept const std::function& onFile, // - const std::function& onDir, //optional - const std::function& onLink, // + const std::function& onFolder, //optional + const std::function& onSymlink, // const std::function& onError); // } diff --git a/zen/fixed_list.h b/zen/fixed_list.h index 81197eb4..27eb488c 100644 --- a/zen/fixed_list.h +++ b/zen/fixed_list.h @@ -139,8 +139,8 @@ public: void swap(FixedList& other) { std::swap(firstInsert_, other.firstInsert_); - std::swap(lastInsert_ , other.lastInsert_); - std::swap(sz_ , other.sz_); + std::swap(lastInsert_, other.lastInsert_); + std::swap(sz_, other.sz_); } private: @@ -194,7 +194,7 @@ public: }; using value_type = T; - using iterator = FixedIterator>::iterator , T>; + using iterator = FixedIterator>::iterator, T>; using const_iterator = FixedIterator>::const_iterator, const T>; using reference = T&; using const_reference = const T&; diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 7f9e0a01..d8ee58c4 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -29,9 +29,8 @@ using namespace zen; #ifdef ZEN_WIN -void zen::recycleOrDelete(const std::vector& itempaths, const std::function& onRecycleItem) +void zen::recycleOrDeleteIfExists(const std::vector& itemPaths, const std::function& onRecycleItem) { - if (itempaths.empty()) return; //warning: moving long file paths to recycler does not work! //both ::SHFileOperation() and ::IFileOperation cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success //both ::SHFileOperation() and ::IFileOperation can't handle \\?\-prefix! @@ -48,21 +47,25 @@ void zen::recycleOrDelete(const std::vector& itempaths, const std::func Nevertheless, let's use IFileOperation for better error reporting (including details on locked files)! */ -#ifdef ZEN_WIN_VISTA_AND_LATER - vista::moveToRecycleBin(itempaths, onRecycleItem); //throw FileError + +#ifdef ZEN_WIN_VISTA_AND_LATER //Win Vista recycle bin usage: 1. good error reporting 2. late failure + vista::moveToRecycleBinIfExists(itemPaths, onRecycleItem); //throw FileError #else //regular recycle bin usage: available since XP: 1. bad error reporting 2. early failure - Zstring itempathsDoubleNull; - for (const Zstring& itempath : itempaths) + //TODO: this XP version fails if any item is not existing violating this function's API + if (itemPaths.empty()) return; + + Zstring itemPathsDoubleNull; + for (const Zstring& itemPath : itemPaths) { - itempathsDoubleNull += itempath; - itempathsDoubleNull += L'\0'; + itemPathsDoubleNull += itemPath; + itemPathsDoubleNull += L'\0'; } SHFILEOPSTRUCT fileOp = {}; fileOp.hwnd = nullptr; fileOp.wFunc = FO_DELETE; - fileOp.pFrom = itempathsDoubleNull.c_str(); + fileOp.pFrom = itemPathsDoubleNull.c_str(); fileOp.pTo = nullptr; fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; fileOp.fAnyOperationsAborted = false; @@ -70,10 +73,10 @@ void zen::recycleOrDelete(const std::vector& itempaths, const std::func fileOp.lpszProgressTitle = nullptr; //"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe." - if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) + if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) //fails if items are not existing! { - std::wstring itempathFmt = fmtPath(itempaths[0]); //probably not the correct file name for file lists larger than 1! - if (itempaths.size() > 1) + std::wstring itempathFmt = fmtPath(itemPaths[0]); //probably not the correct file name for file lists larger than 1! + if (itemPaths.size() > 1) itempathFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt)); } @@ -82,16 +85,37 @@ void zen::recycleOrDelete(const std::vector& itempaths, const std::func #endif -bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError +bool zen::recycleOrDeleteIfExists(const Zstring& itemPath) //throw FileError { - if (!somethingExists(itempath)) //[!] do not optimize away, OS X needs this for reliable detection of "recycle bin missing" - return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it! - #ifdef ZEN_WIN - recycleOrDelete({ itempath }, nullptr); //throw FileError +#ifdef ZEN_WIN_VISTA_AND_LATER + return vista::moveToRecycleBinIfExists({ itemPath }, nullptr) != 0; //throw FileError + +#else + Zstring itemPathDoubleNull = itemPath; + itemPathDoubleNull += L'\0'; + + SHFILEOPSTRUCT fileOp = {}; + fileOp.wFunc = FO_DELETE; + fileOp.pFrom = itemPathDoubleNull.c_str(); + fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; + + if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) //fails if item is not existing! + { + try + { + if (!getItemTypeIfExists(itemPath)) //throw FileError + return false; + } + catch (FileError&) {} //previous exception is more relevant + + throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath))); + } + return true; +#endif #elif defined ZEN_LINUX - GFile* file = ::g_file_new_for_path(itempath.c_str()); //never fails according to docu + GFile* file = ::g_file_new_for_path(itemPath.c_str()); //never fails according to docu ZEN_ON_SCOPE_EXIT(g_object_unref(file);) GError* error = nullptr; @@ -99,38 +123,38 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError if (!::g_file_trash(file, nullptr, &error)) { - const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itempath)); + const Opt type = getItemTypeIfExists(itemPath); //throw FileError + if (!type) + return false; + const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)); if (!error) throw FileError(errorMsg, L"g_file_trash: unknown error."); //user should never see this //implement same behavior as in Windows: if recycler is not existing, delete permanently if (error->code == G_IO_ERROR_NOT_SUPPORTED) { - struct ::stat fileInfo = {}; - if (::lstat(itempath.c_str(), &fileInfo) != 0) - return false; - - if (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)) - removeFile(itempath); //throw FileError - else if (S_ISDIR(fileInfo.st_mode)) - removeDirectoryRecursively(itempath); //throw FileError + if (*type == ItemType::FOLDER) + removeDirectoryPlainRecursion(itemPath); //throw FileError + else + removeFilePlain(itemPath); //throw FileError return true; } throw FileError(errorMsg, replaceCpy(L"Glib Error Code %x:", L"%x", numberTo(error->code)) + L" " + utfCvrtTo(error->message)); //g_quark_to_string(error->domain) } + return true; #elif defined ZEN_MAC //we cannot use FSPathMoveObjectToTrashSync directly since it follows symlinks! static_assert(sizeof(Zchar) == sizeof(char), ""); - const UInt8* itempathUtf8 = reinterpret_cast(itempath.c_str()); + const UInt8* itemPathUtf8 = reinterpret_cast(itemPath.c_str()); auto throwFileError = [&](OSStatus oss) { - const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itempath)); + const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itemPath)); std::wstring errorDescr = L"OSStatus Code " + numberTo(oss); if (const char* description = ::GetMacOSStatusCommentString(oss)) //found no documentation for proper use of GetMacOSStatusCommentString @@ -138,8 +162,14 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError throw FileError(errorMsg, errorDescr); }; + //[!] do not optimize away, OS X needs this for reliable detection of "recycle bin unsupported" + //both "not supported" and "item missing" let FSMoveObjectToTrashSync fail with -120 + const Opt type = getItemTypeIfExists(itemPath); //throw FileError + if (!type) + return false; + FSRef objectRef = {}; //= POD structure not a pointer type! - OSStatus rv = ::FSPathMakeRefWithOptions(itempathUtf8, //const UInt8 *path, + OSStatus rv = ::FSPathMakeRefWithOptions(itemPathUtf8, //const UInt8 *path, kFSPathMakeRefDoNotFollowLeafSymlink, //OptionBits options, &objectRef, //FSRef *ref, nullptr); //Boolean *isDirectory @@ -155,21 +185,17 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError //implement same behavior as in Windows: if recycler is not existing, delete permanently if (rv2 == -120) //=="Directory not found or incomplete pathname." but should really be "recycle bin directory not found"! { - struct ::stat fileInfo = {}; - if (::lstat(itempath.c_str(), &fileInfo) != 0) - return false; - - if (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)) - removeFile(itempath); //throw FileError - else if (S_ISDIR(fileInfo.st_mode)) - removeDirectoryRecursively(itempath); //throw FileError + if (*type == ItemType::FOLDER) + removeDirectoryPlainRecursion(itemPath); //throw FileError + else + removeFilePlain(itemPath); //throw FileError return true; } throwFileError(rv2); } -#endif return true; +#endif } diff --git a/zen/recycler.h b/zen/recycler.h index 4a5f4b2b..ec2a8672 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -35,7 +35,7 @@ Already included in package "gtk+-2.0"! //move a file or folder to Recycle Bin (deletes permanently if recycler is not available) -> crappy semantics, but we have no choice thanks to Windows' design -bool recycleOrDelete(const Zstring& itemPath); //throw FileError, return "true" if file/dir was actually deleted +bool recycleOrDeleteIfExists(const Zstring& itemPath); //throw FileError, return "true" if file/dir was actually deleted #ifdef ZEN_WIN @@ -43,8 +43,8 @@ bool recycleOrDelete(const Zstring& itemPath); //throw FileError, return "true" //Vista and later: dirPath must exist for a valid check! bool recycleBinExists(const Zstring& dirPath, const std::function& onUpdateGui); //throw FileError -void recycleOrDelete(const std::vector& filePaths, //throw FileError, return "true" if file/dir was actually deleted - const std::function& onRecycleItem); //optional; currentItem may be empty +void recycleOrDeleteIfExists(const std::vector& filePaths, //throw FileError + const std::function& onRecycleItem); //optional; currentItem may be empty #endif } diff --git a/zen/serialize.h b/zen/serialize.h index 7322cb07..290d9200 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -126,7 +126,7 @@ struct MemoryStreamIn return bytesRead; } - size_t pos() const { return pos_; } + size_t pos() const { return pos_; } private: const BinContainer buffer_; diff --git a/zen/shell_execute.h b/zen/shell_execute.h index 2f73fc38..b5e04469 100644 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -68,7 +68,7 @@ void shellExecute(const void* /*PCIDLIST_ABSOLUTE*/ shellItemPidl, const std::ws execInfo.lpIDList = const_cast(shellItemPidl); //lpIDList is documented as PCIDLIST_ABSOLUTE! }; - if (!shellExecuteImpl(fillExecInfo , type)) //throw FileError + if (!shellExecuteImpl(fillExecInfo, type)) //throw FileError THROW_LAST_FILE_ERROR(_("Incorrect command line:") + L"\n" + fmtPath(displayPath), L"ShellExecuteEx"); } #endif diff --git a/zen/thread.h b/zen/thread.h index 5bb02a0e..f6c3ae01 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -398,8 +398,8 @@ InterruptibleThread::InterruptibleThread(Function&& f) : intStatus_(std::make_sh threadCompleted = pFinished.get_future(); stdThread = std::thread([f = std::forward(f), - intStatus = this->intStatus_, - pFinished = std::move(pFinished)]() mutable + intStatus = this->intStatus_, + pFinished = std::move(pFinished)]() mutable { assert(!impl::refThreadLocalInterruptionStatus()); impl::refThreadLocalInterruptionStatus() = intStatus.get(); -- cgit