diff options
Diffstat (limited to 'zen/file_access.cpp')
-rw-r--r-- | zen/file_access.cpp | 205 |
1 files changed, 123 insertions, 82 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp index ef6cdc80..c5cbf095 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -8,12 +8,14 @@ #include <map> #include <algorithm> #include <chrono> +#include <variant> #include "file_traverser.h" #include "scope_guard.h" #include "symlink_target.h" #include "file_io.h" #include "crc.h" #include "guid.h" +#include "ring_buffer.h" #include <sys/vfs.h> //statfs #ifdef HAVE_SELINUX @@ -51,72 +53,79 @@ ItemType getItemTypeImpl(const Zstring& itemPath) //throw SysErrorCode return ItemType::folder; return ItemType::file; //S_ISREG || S_ISCHR || S_ISBLK || S_ISFIFO || S_ISSOCK } -} -ItemType zen::getItemType(const Zstring& itemPath) //throw FileError +std::variant<ItemType, Zstring /*last existing parent path*/> getItemTypeIfExistsImpl(const Zstring& itemPath) //throw SysError { try { + //fast check: 1. perf 2. expected by getFolderStatusNonBlocking() return getItemTypeImpl(itemPath); //throw SysErrorCode } - catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), e.toString()); } -} - - -std::variant<ItemType, Zstring /*last existing parent path*/> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError -{ - try + catch (const SysErrorCode& e) //let's dig deeper, but *only* if error code sounds like "not existing" { - try - { - //fast check: 1. perf 2. expected by getFolderStatusNonBlocking() - return getItemTypeImpl(itemPath); //throw SysErrorCode - } - catch (const SysErrorCode& e) //let's dig deeper, but *only* if error code sounds like "not existing" + const std::optional<Zstring> parentPath = getParentFolderPath(itemPath); + if (!parentPath) //device root => quick access test + throw; + if (e.errorCode == ENOENT) { - const std::optional<Zstring> parentPath = getParentFolderPath(itemPath); - if (!parentPath) //device root => quick access test - throw; - if (e.errorCode == ENOENT) - { - const std::variant<ItemType, Zstring /*last existing parent path*/> parentTypeOrPath = getItemTypeIfExists(*parentPath); //throw FileError + const std::variant<ItemType, Zstring /*last existing parent path*/> parentTypeOrPath = getItemTypeIfExistsImpl(*parentPath); //throw SysError - if (const ItemType* parentType = std::get_if<ItemType>(&parentTypeOrPath)) - { - if (*parentType == ItemType::file /*obscure, but possible*/) - throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(*parentPath)))); + if (const ItemType* parentType = std::get_if<ItemType>(&parentTypeOrPath)) + { + if (*parentType == ItemType::file /*obscure, but possible*/) + throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(*parentPath)))); - const Zstring itemName = getItemName(itemPath); - assert(!itemName.empty()); + const Zstring itemName = getItemName(itemPath); + assert(!itemName.empty()); + try + { traverseFolder(*parentPath, //throw FileError [&](const FileInfo& fi) { if (fi.itemName == itemName) throw SysError(_("Temporary access error:") + L' ' + e.toString()); }, [&](const FolderInfo& fi) { if (fi.itemName == itemName) throw SysError(_("Temporary access error:") + L' ' + e.toString()); }, [&](const SymlinkInfo& si) { if (si.itemName == itemName) throw SysError(_("Temporary access error:") + L' ' + e.toString()); }); //- case-sensitive comparison! itemPath must be normalized! //- finding the item after getItemType() previously failed is exceptional - - return *parentPath; } - else - return parentTypeOrPath; + catch (const FileError& e) { throw SysError(replaceCpy(e.toString(), L"\n\n", L'\n')); } + + return *parentPath; } else - throw; + return parentTypeOrPath; } + else + throw; } - catch (const SysError& e) +} +} + + +ItemType zen::getItemType(const Zstring& itemPath) //throw FileError +{ + try { - throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), e.toString()); + return getItemTypeImpl(itemPath); //throw SysErrorCode } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), e.toString()); } } -bool zen::itemExists(const Zstring& itemPath) //throw FileError +std::optional<ItemType> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError { - const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExists(itemPath); //throw FileError - return std::get_if<ItemType>(&typeOrPath); + try + { + const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExistsImpl(itemPath); //throw SysError + if (const ItemType* type = std::get_if<ItemType>(&typeOrPath)) + return *type; + else + return std::nullopt; + } + catch (const SysError& e) + { + throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), e.toString()); + } } @@ -130,16 +139,16 @@ namespace //- folderPath does not need to exist (yet) int64_t zen::getFreeDiskSpace(const Zstring& folderPath) //throw FileError { - const Zstring existingPath = [&] - { - const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExists(folderPath); //throw FileError - if (std::get_if<ItemType>(&typeOrPath)) - return folderPath; - else - return std::get<Zstring>(typeOrPath); - }(); try { + const Zstring existingPath = [&] + { + const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExistsImpl(folderPath); //throw SysError + if (std::get_if<ItemType>(&typeOrPath)) + return folderPath; + else + return std::get<Zstring>(typeOrPath); + }(); struct statfs info = {}; if (::statfs(existingPath.c_str(), &info) != 0) //follows symlinks! THROW_LAST_SYS_ERROR("statfs"); @@ -253,10 +262,14 @@ void removeDirectoryImpl(const Zstring& folderPath) //throw FileError void zen::removeDirectoryPlainRecursion(const Zstring& dirPath) //throw FileError { - if (getItemType(dirPath) == ItemType::symlink) //throw FileError - removeSymlinkPlain(dirPath); //throw FileError - else - removeDirectoryImpl(dirPath); //throw FileError + try + { + if (getItemTypeImpl(dirPath) == ItemType::symlink) //throw SysErrorCode + removeSymlinkPlain(dirPath); //throw FileError + else + removeDirectoryImpl(dirPath); //throw FileError + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), e.toString()); } } @@ -354,16 +367,15 @@ void setWriteTimeNative(const Zstring& itemPath, const timespec& modTime, ProcSy //https://freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works (but not for gvfs SFTP) if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, procSl == ProcSymlink::asLink ? AT_SYMLINK_NOFOLLOW : 0) == 0) return; + const ErrorCode ecUtimensat = errno; try { if (procSl == ProcSymlink::asLink) - try - { - if (getItemType(itemPath) == ItemType::symlink) //throw FileError - THROW_LAST_SYS_ERROR("utimensat(AT_SYMLINK_NOFOLLOW)"); //use lutimes()? just a wrapper around utimensat()! - //else: fall back - } - catch (const FileError& e) { throw SysError(e.toString()); } + { + if (getItemTypeImpl(itemPath) == ItemType::symlink) //throw SysErrorCode + throw SysError(formatSystemError("utimensat(AT_SYMLINK_NOFOLLOW)", ecUtimensat)); //use lutimes()? just a wrapper around utimensat()! + //else: fall back + } //in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: https://freefilesync.org/forum/viewtopic.php?t=387 //2017-07-04: O_WRONLY | O_APPEND seems to avoid EOPNOTSUPP on gvfs SFTP! @@ -475,10 +487,14 @@ void zen::copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPa 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)), "lchown"); - 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)), "chmod"); + try + { + if (getItemTypeImpl(targetPath) != ItemType::symlink && //throw SysErrorCode + //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_SYS_ERROR("chmod"); + } + catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), e.toString()); } } } @@ -517,29 +533,47 @@ void zen::createDirectory(const Zstring& dirPath) //throw FileError, ErrorTarget void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw FileError { - //expect that path already exists (see: versioning, base folder, log file path) => check first - const std::variant<ItemType, Zstring /*last existing parent path*/> typeOrPath = getItemTypeIfExists(dirPath); //throw FileError - - if (const ItemType* type = std::get_if<ItemType>(&typeOrPath)) + auto getItemType2 = [&](const Zstring& itemPath) //throw FileError { - if (*type == ItemType::file /*obscure, but possible*/) + try + { return getItemTypeImpl(itemPath); } //throw SysErrorCode + catch (const SysErrorCode& e) //need to add context! + { throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)), - replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(dirPath)))); - } - else - { - const Zstring existingDirPath = std::get<Zstring>(typeOrPath); - assert(startsWith(dirPath, existingDirPath)); + replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getParentFolderPath(itemPath) ? getItemName(itemPath) : itemPath)) + L'\n' + + e.toString()); + } + }; - const ZstringView relPath = makeStringView(dirPath.begin() + existingDirPath.size(), dirPath.end()); - const std::vector<ZstringView> namesMissing = splitCpy(relPath, FILE_NAME_SEPARATOR, SplitOnEmpty::skip); - assert(!namesMissing.empty()); + try + { + //- path most likely already exists (see: versioning, base folder, log file path) => check first + //- do NOT use getItemTypeIfExists()! race condition when multiple threads are calling createDirectoryIfMissingRecursion(): https://freefilesync.org/forum/viewtopic.php?t=10137#p38062 + //- find first existing + accessible parent folder (backwards iteration): + Zstring dirPathEx = dirPath; + RingBuffer<Zstring> dirNames; //caveat: 1. might have been created in the meantime 2. getItemType2() may have failed with access error + for (;;) + try + { + if (getItemType2(dirPathEx) == ItemType::file /*obscure, but possible*/) //throw FileError + throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(dirPathEx)))); + break; + } + catch (FileError&) //not yet existing or access error + { + const std::optional<Zstring> parentPath = getParentFolderPath(dirPathEx); + if (!parentPath)//device root => quick access test + throw; + dirNames.push_front(getItemName(dirPathEx)); + dirPathEx = *parentPath; + } + //----------------------------------------------------------- - Zstring dirPathNew = existingDirPath; - for (const ZstringView folderName : namesMissing) + Zstring dirPathNew = dirPathEx; + for (const Zstring& dirName : dirNames) try { - dirPathNew = appendPath(dirPathNew, Zstring(folderName)); + dirPathNew = appendPath(dirPathNew, dirName); createDirectory(dirPathNew); //throw FileError } @@ -547,18 +581,24 @@ void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw File { try { - if (getItemType(dirPathNew) != ItemType::file /*obscure, but possible*/) //throw FileError - continue; //already existing => possible, if createFolderIfMissingRecursion() is run in parallel + if (getItemType2(dirPathNew) == ItemType::file /*obscure, but possible*/) //throw FileError + throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(getItemName(dirPathNew)))); + else + continue; //already existing => possible, if createDirectoryIfMissingRecursion() is run in parallel } catch (FileError&) {} //not yet existing or access error throw; } } + catch (const SysError& e) + { + throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirPath)), e.toString()); + } } -void zen::tryCopyDirectoryAttributes(const Zstring& sourcePath, const Zstring& targetPath) //throw FileError +void zen::copyDirectoryAttributes(const Zstring& sourcePath, const Zstring& targetPath) //throw FileError { //do NOT copy attributes for volume root paths which return as: FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY //https://freefilesync.org/forum/viewtopic.php?t=5550 @@ -570,10 +610,11 @@ void zen::tryCopyDirectoryAttributes(const Zstring& sourcePath, const Zstring& t void zen::copySymlink(const Zstring& sourcePath, const Zstring& targetPath) //throw FileError { - const SymlinkRawContent linkContent = getSymlinkRawContent(sourcePath); //throw FileError; accept broken symlinks - + SymlinkRawContent linkContent{}; try //harmonize with NativeFileSystem::equalSymlinkContentForSameAfsType() { + linkContent = getSymlinkRawContent_impl(sourcePath); //throw SysError; accept broken symlinks + if (::symlink(linkContent.targetPath.c_str(), targetPath.c_str()) != 0) THROW_LAST_SYS_ERROR("symlink"); } |