diff options
author | Daniel Wilhelm <daniel@wili.li> | 2015-10-02 14:54:34 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2015-10-02 14:54:34 +0200 |
commit | 96e20826f358a32e38c3f052243375982543c05b (patch) | |
tree | 691efa86265fbb35cc60a1ce816423bb2a41f17b /zen | |
parent | 6.13 (diff) | |
download | FreeFileSync-96e20826f358a32e38c3f052243375982543c05b.tar.gz FreeFileSync-96e20826f358a32e38c3f052243375982543c05b.tar.bz2 FreeFileSync-96e20826f358a32e38c3f052243375982543c05b.zip |
6.14
Diffstat (limited to 'zen')
-rw-r--r-- | zen/basic_math.h | 11 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 33 | ||||
-rw-r--r-- | zen/file_access.cpp | 291 | ||||
-rw-r--r-- | zen/file_io.cpp | 7 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 613 | ||||
-rw-r--r-- | zen/file_traverser.h | 75 | ||||
-rw-r--r-- | zen/long_path_prefix.h | 13 | ||||
-rw-r--r-- | zen/notify_removal.cpp | 7 | ||||
-rw-r--r-- | zen/recycler.cpp | 20 | ||||
-rw-r--r-- | zen/recycler.h | 4 | ||||
-rw-r--r-- | zen/stl_tools.h | 2 | ||||
-rw-r--r-- | zen/win_ver.h | 2 |
12 files changed, 318 insertions, 760 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h index 9a3d195e..227d1cd4 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -88,22 +88,13 @@ const double ln2 = 0.693147180559945309417; - - - - - - - - - //################# inline implementation ######################### template <class T> inline T abs(T value) { static_assert(std::is_signed<T>::value, ""); if (value < 0) - return -value; // operator "?:" caveat: may be different type than "value" + return -value; //operator "?:" caveat: may be different type than "value" else return value; } diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 916f6c69..26645157 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -369,29 +369,6 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() #elif defined ZEN_LINUX -namespace -{ -class DirsOnlyTraverser : public zen::TraverseCallback -{ -public: - DirsOnlyTraverser(std::vector<Zstring>& dirs) : dirs_(dirs) {} - - void onFile (const Zchar* shortName, const Zstring& filepath, const FileInfo& details ) override {} - HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override { return LINK_SKIP; } - TraverseCallback* onDir (const Zchar* shortName, const Zstring& dirpath ) override - { - dirs_.push_back(dirpath); - return this; - } - HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); } - HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override { throw FileError(msg); } - -private: - std::vector<Zstring>& dirs_; -}; -} - - struct DirWatcher::Pimpl { Pimpl() : notifDescr() {} @@ -411,10 +388,11 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError dirpathFmt.resize(dirpathFmt.size() - 1); std::vector<Zstring> fullDirList { dirpathFmt }; - { - DirsOnlyTraverser traverser(fullDirList); //throw FileError - zen::traverseFolder(dirpathFmt, traverser); //don't traverse into symlinks (analog to windows build) - } + +traverseFolder(dirpathFmt, nullptr, + [&](const DirInfo& di ){ fullDirList.push_back(di.fullPath); }, + nullptr, //don't traverse into symlinks (analog to windows build) +[&](const std::wstring& errorMsg){ throw FileError(errorMsg); }); //init pimpl_->basedirpath = directory; @@ -545,6 +523,7 @@ void eventCallback(ConstFSEventStreamRef streamRef, //events are aggregated => it's possible to see a single event with flags //kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemModified | kFSEventStreamEventFlagItemRemoved + //https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags if (eventFlags[i] & kFSEventStreamEventFlagItemCreated || eventFlags[i] & kFSEventStreamEventFlagMount) changedFiles.emplace_back(DirWatcher::ACTION_CREATE, paths[i]); diff --git a/zen/file_access.cpp b/zen/file_access.cpp index e32a27a7..ca07e76a 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -206,11 +206,12 @@ Zstring getLockingProcessNames(const Zstring& filepath) //throw(), empty string const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses); const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); + const wchar_t* processList = nullptr; if (getLockingProcesses && freeString) - if (const wchar_t* procList = getLockingProcesses(filepath.c_str())) + if (getLockingProcesses(filepath.c_str(), processList)) { - ZEN_ON_SCOPE_EXIT(freeString(procList)); - return procList; + ZEN_ON_SCOPE_EXIT(freeString(processList)); + return processList; } } return Zstring(); @@ -249,6 +250,7 @@ std::uint64_t zen::getFilesize(const Zstring& filepath) //throw FileError throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"CreateFile", getLastError()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); + //why not use ::GetFileSizeEx() instead??? BY_HANDLE_FILE_INFORMATION fileInfoHnd = {}; if (!::GetFileInformationByHandle(hFile, &fileInfoHnd)) throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"GetFileInformationByHandle", getLastError()); @@ -548,42 +550,6 @@ void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw Fil namespace { -class CollectFilesFlat : public zen::TraverseCallback -{ -public: - CollectFilesFlat(std::vector<Zstring>& files, std::vector<Zstring>& dirs) : - files_(files), - dirs_(dirs) {} - - void onFile(const Zchar* shortName, const Zstring& filepath, const FileInfo& details) override - { - files_.push_back(filepath); - } - HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) override - { - if (dirExists(linkpath)) //dir symlink - dirs_.push_back(linkpath); - else //file symlink, broken symlink - files_.push_back(linkpath); - return LINK_SKIP; - } - TraverseCallback* onDir(const Zchar* shortName, const Zstring& dirpath) override - { - dirs_.push_back(dirpath); - return nullptr; //DON'T traverse into subdirs; removeDirectory works recursively! - } - HandleError reportDirError (const std::wstring& msg, size_t retryNumber) override { throw FileError(msg); } - HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) override { throw FileError(msg); } - -private: - CollectFilesFlat (const CollectFilesFlat&) = delete; - CollectFilesFlat& operator=(const CollectFilesFlat&) = delete; - - std::vector<Zstring>& files_; - std::vector<Zstring>& dirs_; -}; - - void removeDirectoryImpl(const Zstring& directory, //throw FileError const std::function<void (const Zstring& filepath)>& onBeforeFileDeletion, const std::function<void (const Zstring& dirpath )>& onBeforeDirDeletion) @@ -615,11 +581,18 @@ void removeDirectoryImpl(const Zstring& directory, //throw FileError { std::vector<Zstring> fileList; std::vector<Zstring> dirList; - { - //get all files and directories from current directory (WITHOUT subdirectories!) - CollectFilesFlat cff(fileList, dirList); - traverseFolder(directory, cff); //don't follow symlinks - } + //get all files and directories from current directory (WITHOUT subdirectories!) +traverseFolder(directory, + [&](const FileInfo& fi){ fileList.push_back(fi.fullPath); }, + [&](const DirInfo& di){ dirList .push_back(di.fullPath); }, + [&](const SymlinkInfo& si) +{ + if (dirExists(si.fullPath)) //dir symlink + dirList.push_back(si.fullPath); + else //file symlink, broken symlink + fileList.push_back(si.fullPath); +}, +[&](const std::wstring& errorMsg){ throw FileError(errorMsg); }); //delete directories recursively for (const Zstring& dirpath : dirList) @@ -893,6 +866,74 @@ void setFileTimeRaw(const Zstring& filepath, //assert(std::abs(filetimeToTimeT(creationTimeDbg ) - filetimeToTimeT(creationTime )) <= 2); -> creation time not available for Linux-hosted Samba shares! #endif } + +#elif defined ZEN_MAC +struct AttrBufFileTimes +{ + std::uint32_t length; + struct ::timespec createTime; //keep order; see docs! + struct ::timespec writeTime; // +} __attribute__((aligned(4), packed)); + + +void setFileTimeRaw(const Zstring& filepath, + const struct ::timespec* createTime, //optional + const struct ::timespec& writeTime, + ProcSymlink procSl) //throw FileError +{ + //OS X: utime() is obsoleted by utimes()! utimensat() not yet implemented + //use ::setattrlist() instead of ::utimes() => 1. set file creation times 2. nanosecond precision + //https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/setattrlist.2.html + + struct ::attrlist attribs = {}; + attribs.bitmapcount = ATTR_BIT_MAP_COUNT; + attribs.commonattr = (createTime ? ATTR_CMN_CRTIME : 0) | ATTR_CMN_MODTIME; + + AttrBufFileTimes newTimes = {}; + if (createTime) + { + newTimes.createTime.tv_sec = createTime->tv_sec; + newTimes.createTime.tv_nsec = createTime->tv_nsec; + } + newTimes.writeTime.tv_sec = writeTime.tv_sec; + newTimes.writeTime.tv_nsec = writeTime.tv_nsec; + + const int rv = ::setattrlist(filepath.c_str(), //const char* path, + &attribs, //struct ::attrlist* attrList, + createTime ? &newTimes.createTime : &newTimes.writeTime, //void* attrBuf, + (createTime ? sizeof(newTimes.createTime) : 0) + sizeof(newTimes.writeTime), //size_t attrBufSize, + procSl == ProcSymlink::DIRECT ? FSOPT_NOFOLLOW : 0); //unsigned long options + if (rv != 0) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"setattrlist", getLastError()); +} + +/* +void getFileTimeRaw(int fd, //throw FileError + const Zstring& filepath, //for error reporting only + struct ::timespec& createTime, //out + struct ::timespec& writeTime) // +{ + //https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getattrlist.2.html + struct ::attrlist attribs = {}; + attribs.bitmapcount = ATTR_BIT_MAP_COUNT; + attribs.commonattr = ATTR_CMN_CRTIME | ATTR_CMN_MODTIME; + + AttrBufFileTimes fileTimes = {}; + + const int rv = ::fgetattrlist(fd, //int fd, + &attribs, //struct ::attrlist* attrList, + &fileTimes, //void* attrBuf, + sizeof(fileTimes), //size_t attrBufSize, + 0); //unsigned long options + if (rv != 0) + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"getattrlist", getLastError()); + + createTime.tv_sec = fileTimes.createTime.tv_sec; + createTime.tv_nsec = fileTimes.createTime.tv_nsec; + writeTime.tv_sec = fileTimes.writeTime.tv_sec; + writeTime.tv_nsec = fileTimes.writeTime.tv_nsec; +} +*/ #endif } @@ -913,7 +954,7 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink #ifdef ZEN_WIN setFileTimeRaw(filepath, nullptr, timetToFileTime(modTime), procSl); //throw FileError -#elif defined ZEN_LINUX || defined ZEN_MAC +#elif defined ZEN_LINUX //sigh, we can't use utimensat on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit??? // struct ::timespec newTimes[2] = {}; @@ -925,8 +966,6 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink //=> fallback to "retarded-idiot version"! -- DarkByte - //OS X: utime() is obsoleted by utimes()! utimensat() not yet implemented - struct ::timeval newTimes[2] = {}; newTimes[0].tv_sec = ::time(nullptr); //access time (seconds) newTimes[1].tv_sec = modTime; //modification time (seconds) @@ -936,6 +975,11 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink ::lutimes(filepath.c_str(), newTimes); if (rv != 0) throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimes", getLastError()); + +#elif defined ZEN_MAC + struct ::timespec writeTime = {}; + writeTime.tv_sec = modTime; + setFileTimeRaw(filepath, nullptr, writeTime, procSl); //throw FileError #endif } @@ -1209,7 +1253,7 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym if (::copyfile(source.c_str(), target.c_str(), 0, flags) != 0) throwFileError(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtFileName(source)), L"%y", L"\n" + fmtFileName(target)), L"copyfile", getLastError()); - //owner is *not* copied with ::copyfile(): + //owner is *not* copied with ::copyfile(): struct ::stat fileInfo = {}; if (procSl == ProcSymlink::FOLLOW) @@ -1359,16 +1403,16 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT } #elif defined ZEN_LINUX || defined ZEN_MAC - mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory + mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory struct ::stat dirInfo = {}; - if (!templateDir.empty()) + if (!templateDir.empty()) if (::stat(templateDir.c_str(), &dirInfo) == 0) - { - mode = dirInfo.st_mode; //analog to "cp" which copies "mode" (considering umask) by default - mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items - } - //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions + { + mode = dirInfo.st_mode; //analog to "cp" which copies "mode" (considering umask) by default + mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items + } + //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions if (::mkdir(directory.c_str(), mode) != 0) { @@ -1518,17 +1562,19 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool setFileTimeRaw(targetLink, &sourceAttr.ftCreationTime, sourceAttr.ftLastWriteTime, ProcSymlink::DIRECT); //throw FileError #elif defined ZEN_LINUX || defined ZEN_MAC - struct ::stat srcInfo = {}; - if (::lstat(sourceLink.c_str(), &srcInfo) != 0) + struct ::stat sourceInfo = {}; + if (::lstat(sourceLink.c_str(), &sourceInfo) != 0) throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceLink)), L"lstat", getLastError()); - setFileTime(targetLink, srcInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError -#endif +#ifdef ZEN_LINUX + setFileTime(targetLink, sourceInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError +#elif defined ZEN_MAC + setFileTimeRaw(targetLink, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::DIRECT); //throw FileError -#ifdef ZEN_MAC if (::copyfile(sourceLink.c_str(), targetLink.c_str(), 0, COPYFILE_XATTR | COPYFILE_NOFOLLOW) != 0) throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)), L"copyfile", getLastError()); #endif +#endif if (copyFilePermissions) copyObjectPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError @@ -1933,8 +1979,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, file time handling: ::CopyFileEx() will (only) copy file modification time over from source file AFTER the last invokation of this callback => it is possible to adapt file creation time of target in here, but NOT file modification time! - CAVEAT: if ::CopyFileEx() fails to set modification time, it silently ignores this error and returns success!!! - see procmon log in: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 + CAVEAT: if ::CopyFileEx() fails to set modification time, it silently ignores this error and returns success!!! (confirmed with Process Monitor) alternate data stream handling: CopyFileEx() processes multiple streams one after another, stream 1 is the file data stream and always available! @@ -2110,10 +2155,9 @@ void copyFileWindowsDefault(const Zstring& sourceFile, newAttrib->sourceFileId = extractFileId(cbd.fileInfoSrc); newAttrib->targetFileId = extractFileId(cbd.fileInfoTrg); } - warn_static("new perf check + investigate improvements now that DST hcak is gone! =>") //caveat: - ::CopyFileEx() silently *ignores* failure to set modification time!!! => we always need to set it again but with proper error checking! - // - perf-loss on USB sticks with many small files of about 30%! + // - perf: recent measurements show no slow down at all for buffered USB sticks! setFileTimeRaw(targetFile, &cbd.fileInfoSrc.ftCreationTime, cbd.fileInfoSrc.ftLastWriteTime, ProcSymlink::FOLLOW); //throw FileError guardTarget.dismiss(); //target has been created successfully! @@ -2176,8 +2220,8 @@ void copyFileOsSpecific(const Zstring& sourceFile, try { FileOutputUnbuffered fileOut(targetFile, //throw FileError, ErrorTargetExisting - sourceInfo.st_mode); //analog to "cp" which copies "mode" (considering umask) by default - //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions + sourceInfo.st_mode); //analog to "cp" which copies "mode" (considering umask) by default + //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions std::vector<char> buffer(128 * 1024); //see comment in FileInputUnbuffered::read do @@ -2213,7 +2257,7 @@ void copyFileOsSpecific(const Zstring& sourceFile, throw; } - //we cannot set the target file times while the file descriptor is open and being written: + //we cannot set the target file times while the file descriptor is still open after a write operation: //this triggers bugs on samba shares where the modification time is set to current time instead. //http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236 //http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 @@ -2278,78 +2322,87 @@ void copyFileOsSpecific(const Zstring& sourceFile, const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus, InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting { + struct ::stat sourceInfo = {}; + //http://blog.plasticsfuture.org/2006/03/05/the-state-of-backup-and-cloning-tools-under-mac-os-x/ + { + auto getCopyErrorMessage = [&] { return replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); }; - auto getCopyErrorMessage = [&] { return replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); }; + copyfile_state_t copyState = ::copyfile_state_alloc(); + ZEN_ON_SCOPE_EXIT(::copyfile_state_free(copyState)); - copyfile_state_t copyState = ::copyfile_state_alloc(); - ZEN_ON_SCOPE_EXIT(::copyfile_state_free(copyState)); + CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile); - CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile); + if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CTX, &cbd) != 0) + throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CTX", getLastError()); - if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CTX, &cbd) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CTX", getLastError()); + if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CB, reinterpret_cast<const void*>(©FileCallback)) != 0) + throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CB", getLastError()); - if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CB, reinterpret_cast<const void*>(©FileCallback)) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CB", getLastError()); + zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: docs seem to indicate that copyfile does not clean up - zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: docs seem to indicate that copyfile does not clean up + //http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html + if (::copyfile(sourceFile.c_str(), targetFile.c_str(), + copyState, + COPYFILE_XATTR | COPYFILE_DATA | COPYFILE_EXCL) != 0) + //- even though we don't use COPYFILE_STAT, "mode" (considering umask) is still copied! => harmonized with Linux file copy! + //- COPYFILE_STAT does not copy file creation time + { + //evaluate first! errno is not set for COPYFILE_QUIT! + if (cbd.exception) + std::rethrow_exception(cbd.exception); - //http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html - if (::copyfile(sourceFile.c_str(), targetFile.c_str(), - copyState, - COPYFILE_XATTR | COPYFILE_DATA | COPYFILE_EXCL) != 0) //even though we don't use COPYFILE_STAT, "mode" (considering umask) is still copied! => harmonized with Linux file copy! - { - //evaluate first! errno is not set for COPYFILE_QUIT! - if (cbd.exception) - std::rethrow_exception(cbd.exception); + if (!cbd.errorMsg.first.empty()) + throw FileError(cbd.errorMsg.first, cbd.errorMsg.second); - if (!cbd.errorMsg.first.empty()) - throw FileError(cbd.errorMsg.first, cbd.errorMsg.second); + const int lastError = errno; + std::wstring errorDescr = formatSystemError(L"copyfile", lastError); - const int lastError = errno; - std::wstring errorDescr = formatSystemError(L"copyfile", lastError); + if (lastError == EEXIST) + { + guardTarget.dismiss(); //don't delete file that existed previously! + throw ErrorTargetExisting(getCopyErrorMessage(), errorDescr); + } - if (lastError == EEXIST) - { - guardTarget.dismiss(); //don't delete file that existed previously! - throw ErrorTargetExisting(getCopyErrorMessage(), errorDescr); + throw FileError(getCopyErrorMessage(), errorDescr); } - throw FileError(getCopyErrorMessage(), errorDescr); - } - - int fdSource = 0; - if (::copyfile_state_get(copyState, COPYFILE_STATE_SRC_FD, &fdSource) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_SRC_FD", getLastError()); + int fdSource = 0; + if (::copyfile_state_get(copyState, COPYFILE_STATE_SRC_FD, &fdSource) != 0) + throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_SRC_FD", getLastError()); - int fdTarget = 0; - if (::copyfile_state_get(copyState, COPYFILE_STATE_DST_FD, &fdTarget) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_DST_FD", getLastError()); + int fdTarget = 0; + if (::copyfile_state_get(copyState, COPYFILE_STATE_DST_FD, &fdTarget) != 0) + throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_DST_FD", getLastError()); - struct ::stat sourceInfo = {}; - if (::fstat(fdSource, &sourceInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError()); + if (::fstat(fdSource, &sourceInfo) != 0) + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError()); - struct ::stat targetInfo = {}; - if (::fstat(fdTarget, &targetInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError()); + struct ::stat targetInfo = {}; + if (::fstat(fdTarget, &targetInfo) != 0) + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError()); - struct ::timeval newTimes[2] = {}; - newTimes[0].tv_sec = sourceInfo.st_atime; //access time (seconds) - newTimes[1].tv_sec = sourceInfo.st_mtime; //modification time (seconds) + if (newAttrib) + { + newAttrib->fileSize = sourceInfo.st_size; + newAttrib->modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable as setFileTimeRaw() for consistency + //newAttrib->modificationTime = sourceInfo.st_mtime; // + newAttrib->sourceFileId = extractFileId(sourceInfo); + newAttrib->targetFileId = extractFileId(targetInfo); + } - if (::futimes(fdTarget, newTimes) != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)), L"futimes", getLastError()); + guardTarget.dismiss(); + } //make sure target file handle is closed before setting modification time! - if (newAttrib) - { - newAttrib->fileSize = sourceInfo.st_size; - newAttrib->modificationTime = sourceInfo.st_mtime; - newAttrib->sourceFileId = extractFileId(sourceInfo); - newAttrib->targetFileId = extractFileId(targetInfo); - } + zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); + //same issue like on Linux: we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation: + //this triggers bugs on samba shares where the modification time is set to current time instead. + //https://sourceforge.net/p/freefilesync/discussion/help/thread/881357c0/ + //http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 + setFileTimeRaw(targetFile, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::FOLLOW); //throw FileError + //sourceInfo.st_birthtime; -> only seconds-preicions + //sourceInfo.st_mtime; -> guardTarget.dismiss(); } #endif diff --git a/zen/file_io.cpp b/zen/file_io.cpp index c56d6ac0..00e33a60 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -32,11 +32,12 @@ Zstring getLockingProcessNames(const Zstring& filepath) //throw(), empty string const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses); const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); + const wchar_t* processList = nullptr; if (getLockingProcesses && freeString) - if (const wchar_t* procList = getLockingProcesses(filepath.c_str())) + if (getLockingProcesses(filepath.c_str(), processList)) { - ZEN_ON_SCOPE_EXIT(freeString(procList)); - return procList; + ZEN_ON_SCOPE_EXIT(freeString(processList)); + return processList; } } return Zstring(); diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index f03cf464..2d652d2b 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -5,23 +5,20 @@ // ************************************************************************** #include "file_traverser.h" -#include "sys_error.h" -#include "symlink_target.h" +#include "file_error.h" #include "int64.h" #ifdef ZEN_WIN - #include "win_ver.h" #include "long_path_prefix.h" #include "file_access.h" - #include "dll.h" - #include "FindFilePlus/find_file_plus.h" - + #include "symlink_target.h" #elif defined ZEN_MAC - #include "osx_string.h" + #include "osx_string.h" #endif #if defined ZEN_LINUX || defined ZEN_MAC - #include <cstddef> //required by GCC 4.8.1 to find ptrdiff_t + #include <cstddef> //offsetof + #include <unistd.h> //::pathconf() #include <sys/stat.h> #include <dirent.h> #endif @@ -29,491 +26,105 @@ using namespace zen; -namespace -{ -//implement "retry" in a generic way: - -template <class Command> inline //function object expecting to throw FileError if operation fails -bool tryReportingDirError(Command cmd, zen::TraverseCallback& callback) //return "true" on success, "false" if error was ignored -{ - for (size_t retryNumber = 0;; ++retryNumber) - try - { - cmd(); //throw FileError - return true; - } - catch (const FileError& e) - { - switch (callback.reportDirError(e.toString(), retryNumber)) - { - case TraverseCallback::ON_ERROR_RETRY: - break; - case TraverseCallback::ON_ERROR_IGNORE: - return false; - } - } -} - -template <class Command> inline //function object expecting to throw FileError if operation fails -bool tryReportingItemError(Command cmd, zen::TraverseCallback& callback, const Zchar* shortName) //return "true" on success, "false" if error was ignored -{ - for (size_t retryNumber = 0;; ++retryNumber) - try - { - cmd(); //throw FileError - return true; - } - catch (const FileError& e) - { - switch (callback.reportItemError(e.toString(), retryNumber, shortName)) - { - case TraverseCallback::ON_ERROR_RETRY: - break; - case TraverseCallback::ON_ERROR_IGNORE: - return false; - } - } -} - - -#ifdef ZEN_WIN -TraverseCallback::FileInfo getInfoFromFileSymlink(const Zstring& linkName) //throw FileError -{ - //open handle to target of symbolic link - HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(linkName).c_str(), //_In_ LPCTSTR lpFileName, - 0, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, - //needed to open a directory -> keep it even if we expect to open a file! See comment below - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hFile == INVALID_HANDLE_VALUE) - throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)), L"CreateFile", getLastError()); - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - - BY_HANDLE_FILE_INFORMATION fileInfo = {}; - if (!::GetFileInformationByHandle(hFile, &fileInfo)) - throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)), L"GetFileInformationByHandle", getLastError()); - - //a file symlink may incorrectly point to a directory, but both CreateFile() and GetFileInformationByHandle() will succeed and return garbage! - //- if we did not use FILE_FLAG_BACKUP_SEMANTICS above, CreateFile() would error out with an even less helpful ERROR_ACCESS_DENIED! - //- reinterpreting the link as a directory symlink would still fail during traversal, so just show an error here - //- OTOH a directory symlink that points to a file fails immediately in ::FindFirstFile() with ERROR_DIRECTORY! -> nothing to do in this case - if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)), formatSystemError(L"GetFileInformationByHandle", static_cast<DWORD>(ERROR_FILE_INVALID))); - - TraverseCallback::FileInfo output; - output.fileSize = get64BitUInt(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); - output.lastWriteTime = filetimeToTimeT(fileInfo.ftLastWriteTime); - output.id = extractFileId(fileInfo); //consider detection of moved files: allow for duplicate file ids, renaming affects symlink, not target, ... - //output.symlinkInfo -> not filled here - return output; -} - - -DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error or if serial is not supported! -{ - //this works for: - //- root paths "C:\", "D:\" - //- network shares: \\share\dirname - //- indirection: subst S: %USERPROFILE% - // -> GetVolumePathName() + GetVolumeInformation() OTOH incorrectly resolves "S:\Desktop\somedir" to "S:\Desktop\" - nice try... - const HANDLE hDir = ::CreateFile(zen::applyLongPathPrefix(pathName).c_str(), //_In_ LPCTSTR lpFileName, - 0, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - // FILE_FLAG_OPEN_REPARSE_POINT -> no, we follow symlinks! - FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, - /*needed to open a directory*/ - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hDir == INVALID_HANDLE_VALUE) - return 0; - ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); - - BY_HANDLE_FILE_INFORMATION fileInfo = {}; - if (!::GetFileInformationByHandle(hDir, &fileInfo)) - return 0; - - return fileInfo.dwVolumeSerialNumber; -} - - -const bool isXpOrLater = winXpOrLater(); //VS2010 compiled DLLs are not supported on Win 2000: Popup dialog "DecodePointer not found" - -#define DEF_DLL_FUN(name) const auto name = isXpOrLater ? DllFun<findplus::FunType_##name>(findplus::getDllName(), findplus::funName_##name) : DllFun<findplus::FunType_##name>(); -DEF_DLL_FUN(openDir); // -DEF_DLL_FUN(readDir); //load at startup: avoid pre C++11 static initialization MT issues -DEF_DLL_FUN(closeDir); // - -/* -Common C-style interface for Win32 FindFirstFile(), FindNextFile() and FileFilePlus openDir(), closeDir(): -struct TraverserPolicy //see "policy based design" -{ -typedef ... DirHandle; -typedef ... FindData; - -static DirHandle create(const Zstring& directory); //throw FileError - don't follow FindFirstFile() design: open handle only, *no* return of data! -static void destroy(DirHandle hnd); //throw() - -static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError, NeedFallbackToWin32Traverser -> fallback to FindFirstFile()/FindNextFile() - -//FindData "member" functions -static TraverseCallback::FileInfo extractFileInfo(const FindData& fileInfo, DWORD volumeSerial); //volumeSerial may be 0 if not available! -static std::int64_t getModTime (const FindData& fileInfo); -static const FILETIME& getModTimeRaw (const FindData& fileInfo); //yet another concession to DST hack -static const FILETIME& getCreateTimeRaw(const FindData& fileInfo); // -static const wchar_t* getItemName (const FindData& fileInfo); -static bool isDirectory (const FindData& fileInfo); -static bool isSymlink (const FindData& fileInfo); -} - -Note: Win32 FindFirstFile(), FindNextFile() is a weaker abstraction than FileFilePlus openDir(), readDir(), closeDir() and Unix opendir(), closedir(), stat() -*/ - - -struct Win32Traverser +void zen::traverseFolder(const Zstring& dirPath, + const std::function<void (const FileInfo& fi)>& onFile, + const std::function<void (const DirInfo& di)>& onDir, + const std::function<void (const SymlinkInfo& si)>& onLink, + const std::function<void (const std::wstring& errorMsg)>& onError) { - struct DirHandle - { - DirHandle(HANDLE hnd, const WIN32_FIND_DATA& d) : searchHandle(hnd), haveData(true), data(d) {} - explicit DirHandle(HANDLE hnd) : searchHandle(hnd), haveData(false) {} - - HANDLE searchHandle; - bool haveData; - WIN32_FIND_DATA data; - }; - - typedef WIN32_FIND_DATA FindData; - - static DirHandle create(const Zstring& dirpath) //throw FileError + try { - const Zstring& dirpathPf = appendSeparator(dirpath); - - WIN32_FIND_DATA fileData = {}; - HANDLE hnd = ::FindFirstFile(applyLongPathPrefix(dirpathPf + L'*').c_str(), &fileData); - //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH - if (hnd == INVALID_HANDLE_VALUE) +#ifdef ZEN_WIN + WIN32_FIND_DATA findData = {}; + HANDLE hDir = ::FindFirstFile(applyLongPathPrefix(appendSeparator(dirPath) + L'*').c_str(), &findData); + if (hDir == INVALID_HANDLE_VALUE) { const DWORD lastError = ::GetLastError(); //copy before making other system calls! if (lastError == 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 DirHandle(hnd); + if (dirExists(dirPath)) //yes, a race-condition, still the best we can do + return; } - throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirpath)), L"FindFirstFile", lastError); - } - return DirHandle(hnd, fileData); - } - - static void destroy(const DirHandle& hnd) { ::FindClose(hnd.searchHandle); } //throw() - - static bool getEntry(DirHandle& hnd, const Zstring& dirpath, FindData& fileInfo) //throw FileError - { - if (hnd.searchHandle == INVALID_HANDLE_VALUE) //handle special case of "truly empty directories" - return false; - - if (hnd.haveData) - { - hnd.haveData = false; - ::memcpy(&fileInfo, &hnd.data, sizeof(fileInfo)); - return true; - } - - if (!::FindNextFile(hnd.searchHandle, &fileInfo)) - { - const DWORD lastError = ::GetLastError(); //copy before making other system calls! - if (lastError == ERROR_NO_MORE_FILES) //not an error situation - return false; - //else we have a problem... report it: - throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirpath)), L"FindNextFile", lastError); - } - return true; - } - - static TraverseCallback::FileInfo extractFileInfo(const FindData& fileInfo, DWORD volumeSerial) - { - TraverseCallback::FileInfo output; - output.fileSize = get64BitUInt(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); - output.lastWriteTime = getModTime(fileInfo); - //output.id = FileId(); - //output.symlinkInfo = nullptr; - return output; - } - - static std::int64_t getModTime (const FindData& fileInfo) { return filetimeToTimeT(fileInfo.ftLastWriteTime); } - static const FILETIME& getModTimeRaw (const FindData& fileInfo) { return fileInfo.ftLastWriteTime; } - static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.ftCreationTime; } - static const wchar_t* getItemName (const FindData& fileInfo) { return fileInfo.cFileName; } - static bool isDirectory (const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - static bool isSymlink (const FindData& fileInfo) { return zen::isSymlink(fileInfo); } //[!] keep namespace -}; - - -class NeedFallbackToWin32Traverser {}; //special exception class - - -struct FilePlusTraverser -{ - struct DirHandle - { - explicit DirHandle(findplus::FindHandle hnd) : searchHandle(hnd) {} - - findplus::FindHandle searchHandle; - }; - - typedef findplus::FileInformation FindData; - - static DirHandle create(const Zstring& dirpath) //throw FileError - { - const findplus::FindHandle hnd = ::openDir(applyLongPathPrefix(dirpath).c_str()); - if (!hnd) - throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirpath)), L"openDir", getLastError()); - - return DirHandle(hnd); - } - - static void destroy(DirHandle hnd) { ::closeDir(hnd.searchHandle); } //throw() - - static bool getEntry(DirHandle hnd, const Zstring& dirpath, FindData& fileInfo) //throw FileError, NeedFallbackToWin32Traverser - { - if (!::readDir(hnd.searchHandle, fileInfo)) - { - const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! - if (lastError == ERROR_NO_MORE_FILES) //not an error situation - return false; - - /* - fallback to default directory query method, if FileIdBothDirectoryInformation is not properly implemented - this is required for NetDrive mounted Webdav, e.g. www.box.net and NT4, 2000 remote drives, et al. - */ - if (lastError == ERROR_NOT_SUPPORTED) - throw NeedFallbackToWin32Traverser(); - //fallback should apply to whole directory sub-tree! => client needs to handle duplicate file notifications! - - //else we have a problem... report it: - throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirpath)), L"readDir", lastError); + throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPath)), L"FindFirstFile", lastError); } + ZEN_ON_SCOPE_EXIT(::FindClose(hDir)); - return true; - } - - static TraverseCallback::FileInfo extractFileInfo(const FindData& fileInfo, DWORD volumeSerial) - { - TraverseCallback::FileInfo output; - output.fileSize = fileInfo.fileSize; - output.lastWriteTime = getModTime(fileInfo); - output.id = extractFileId(volumeSerial, fileInfo.fileId); - //output.symlinkInfo = nullptr; - return output; - } - - static std::int64_t getModTime (const FindData& fileInfo) { return filetimeToTimeT(fileInfo.lastWriteTime); } - static const FILETIME& getModTimeRaw (const FindData& fileInfo) { return fileInfo.lastWriteTime; } - static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.creationTime; } - static const wchar_t* getItemName (const FindData& fileInfo) { return fileInfo.shortName; } - static bool isDirectory (const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - static bool isSymlink (const FindData& fileInfo) { return zen::isSymlink(fileInfo.fileAttributes, fileInfo.reparseTag); } //[!] keep namespace -}; - - -class DirTraverser -{ -public: - static void execute(const Zstring& baseDirectory, TraverseCallback& sink) - { - DirTraverser(baseDirectory, sink); - } - -private: - DirTraverser(const Zstring& baseDirectory, TraverseCallback& sink); - DirTraverser (const DirTraverser&) = delete; - DirTraverser& operator=(const DirTraverser&) = delete; - - template <class Trav> - void traverse(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial); - - template <class Trav> - void traverseWithException(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial /*may be 0!*/); //throw FileError, NeedFallbackToWin32Traverser -}; - - -template <> inline -void DirTraverser::traverse<Win32Traverser>(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial) -{ - tryReportingDirError([&] - { - traverseWithException<Win32Traverser>(dirpath, sink, 0); //throw FileError - }, sink); -} - - -template <> inline -void DirTraverser::traverse<FilePlusTraverser>(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial) -{ - try - { - tryReportingDirError([&] - { - traverseWithException<FilePlusTraverser>(dirpath, sink, volumeSerial); //throw FileError, NeedFallbackToWin32Traverser - }, sink); - } - catch (NeedFallbackToWin32Traverser&) { traverse<Win32Traverser>(dirpath, sink, 0); } -} - - -inline -DirTraverser::DirTraverser(const Zstring& baseDirectory, TraverseCallback& sink) -{ - try //traversing certain folders with restricted permissions requires this privilege! (but copying these files may still fail) - { - activatePrivilege(SE_BACKUP_NAME); //throw FileError - } - catch (FileError&) {} //don't cause issues in user mode - - if (::openDir && ::readDir && ::closeDir) - traverse<FilePlusTraverser>(baseDirectory, sink, retrieveVolumeSerial(baseDirectory)); //retrieveVolumeSerial returns 0 on error - else //fallback - traverse<Win32Traverser>(baseDirectory, sink, 0); -} - - -template <class Trav> -void DirTraverser::traverseWithException(const Zstring& dirpath, TraverseCallback& sink, DWORD volumeSerial /*may be 0!*/) //throw FileError, NeedFallbackToWin32Traverser -{ - //no need to check for endless recursion: Windows seems to have an internal path limit of about 700 chars - - typename Trav::DirHandle searchHandle = Trav::create(dirpath); //throw FileError - ZEN_ON_SCOPE_EXIT(Trav::destroy(searchHandle)); - - typename Trav::FindData findData = {}; - - while (Trav::getEntry(searchHandle, dirpath, findData)) //throw FileError, NeedFallbackToWin32Traverser - //don't retry but restart dir traversal on error! http://blogs.msdn.com/b/oldnewthing/archive/2014/06/12/10533529.aspx - { - //skip "." and ".." - const Zchar* const shortName = Trav::getItemName(findData); - if (shortName[0] == L'.' && - (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) - continue; + bool firstIteration = true; + for (;;) + { + if (firstIteration) //keep ::FindNextFile at the start of the for-loop to support "continue"! + firstIteration = false; + else + if (!::FindNextFile(hDir, &findData)) + { + const DWORD lastError = ::GetLastError(); + if (lastError == ERROR_NO_MORE_FILES) //not an error situation + return; + //else we have a problem... report it: + throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"FindNextFile", lastError); + } - const Zstring& itempath = appendSeparator(dirpath) + shortName; + //skip "." and ".." + const Zchar* const shortName = findData.cFileName; + if (shortName[0] == L'.' && + (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) + continue; - if (Trav::isSymlink(findData)) //check first! - { - TraverseCallback::SymlinkInfo linkInfo; - linkInfo.lastWriteTime = Trav::getModTime (findData); + const Zstring& itempath = appendSeparator(dirPath) + shortName; - switch (sink.onSymlink(shortName, itempath, linkInfo)) + if (zen::isSymlink(findData)) //check first! { - case TraverseCallback::LINK_FOLLOW: - if (Trav::isDirectory(findData)) - { - if (TraverseCallback* trav = sink.onDir(shortName, itempath)) - { - ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); - traverse<Trav>(itempath, *trav, retrieveVolumeSerial(itempath)); //symlink may link to different volume => redetermine volume serial! - } - } - else //a file - { - TraverseCallback::FileInfo targetInfo; - const bool validLink = tryReportingItemError([&] //try to resolve symlink (and report error on failure!!!) - { - targetInfo = getInfoFromFileSymlink(itempath); //throw FileError - targetInfo.symlinkInfo = &linkInfo; - }, sink, shortName); - - if (validLink) - sink.onFile(shortName, itempath, targetInfo); - // else //broken symlink -> ignore: it's client's responsibility to handle error! - } - break; - - case TraverseCallback::LINK_SKIP: - break; + if (onLink) + onLink({ shortName, itempath, filetimeToTimeT(findData.ftLastWriteTime) }); } - } - else if (Trav::isDirectory(findData)) - { - if (TraverseCallback* trav = sink.onDir(shortName, itempath)) + else if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); - traverse<Trav>(itempath, *trav, volumeSerial); + if (onDir) + onDir({ shortName, itempath }); + } + else //a file + { + if (onFile) + onFile({ shortName, itempath, get64BitUInt(findData.nFileSizeLow, findData.nFileSizeHigh), filetimeToTimeT(findData.ftLastWriteTime) }); } } - else //a file - { - const TraverseCallback::FileInfo fileInfo = Trav::extractFileInfo(findData, volumeSerial); - sink.onFile(shortName, itempath, fileInfo); - } - } -} - #elif defined ZEN_LINUX || defined ZEN_MAC -class DirTraverser -{ -public: - static void execute(const Zstring& baseDirectory, TraverseCallback& sink) - { - DirTraverser(baseDirectory, sink); - } - -private: - DirTraverser(const Zstring& baseDirectory, zen::TraverseCallback& sink) - { - const Zstring directoryFormatted = //remove trailing slash - baseDirectory.size() > 1 && endsWith(baseDirectory, FILE_NAME_SEPARATOR) ? //exception: allow '/' - beforeLast(baseDirectory, FILE_NAME_SEPARATOR) : - baseDirectory; + const Zstring dirPathFmt = //remove trailing slash + dirPath.size() > 1 && endsWith(dirPath, FILE_NAME_SEPARATOR) ? //exception: allow '/' + beforeLast(dirPath, FILE_NAME_SEPARATOR) : + dirPath; /* quote: "Since POSIX.1 does not specify the size of the d_name field, and other nonstandard fields may precede that field within the dirent structure, portable applications that use readdir_r() should allocate the buffer whose address is passed in entry as follows: - len = offsetof(struct dirent, d_name) + pathconf(dirpath, _PC_NAME_MAX) + 1 + len = offsetof(struct dirent, d_name) + pathconf(dirPath, _PC_NAME_MAX) + 1 entryp = malloc(len); */ - const size_t nameMax = std::max<long>(::pathconf(directoryFormatted.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) - buffer.resize(offsetof(struct ::dirent, d_name) + nameMax + 1); - - traverse(directoryFormatted, sink); - } - - DirTraverser (const DirTraverser&) = delete; - DirTraverser& operator=(const DirTraverser&) = delete; - - void traverse(const Zstring& dirpath, TraverseCallback& sink) - { - tryReportingDirError([&] - { - traverseWithException(dirpath, sink); //throw FileError - }, sink); - } - - void traverseWithException(const Zstring& dirpath, TraverseCallback& sink) //throw FileError - { - //no need to check for endless recursion: Linux has a fixed limit on the number of symbolic links in a path + const size_t nameMax = std::max<long>(::pathconf(dirPathFmt.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) + std::vector<char> buffer(offsetof(struct ::dirent, d_name) + nameMax + 1); +#ifdef ZEN_MAC + std::vector<char> bufferUtfDecomposed; +#endif - DIR* dirObj = ::opendir(dirpath.c_str()); //directory must NOT end with path separator, except "/" + DIR* dirObj = ::opendir(dirPathFmt.c_str()); //directory must NOT end with path separator, except "/" if (!dirObj) - throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirpath)), L"opendir", getLastError()); + throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPathFmt)), L"opendir", getLastError()); ZEN_ON_SCOPE_EXIT(::closedir(dirObj)); //never close nullptr handles! -> crash for (;;) { struct ::dirent* dirEntry = nullptr; if (::readdir_r(dirObj, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0) - throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirpath)), L"readdir_r", getLastError()); + throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPathFmt)), L"readdir_r", getLastError()); //don't retry but restart dir traversal on error! http://blogs.msdn.com/b/oldnewthing/archive/2014/06/12/10533529.aspx if (!dirEntry) //no more items return; //don't return "." and ".." - const char* shortName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"! + const char* shortName = dirEntry->d_name; if (shortName[0] == '.' && (shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0))) continue; @@ -531,83 +142,41 @@ private: { bufferUtfDecomposed.resize(lenMax); if (::CFStringGetFileSystemRepresentation(cfStr, &bufferUtfDecomposed[0], lenMax)) //get decomposed UTF form (verified!) despite ambiguous documentation - shortName = &bufferUtfDecomposed[0]; //attention: => don't access "shortName" after recursion in "traverse"! + shortName = &bufferUtfDecomposed[0]; } } //const char* sampleDecomposed = "\x6f\xcc\x81.txt"; //const char* samplePrecomposed = "\xc3\xb3.txt"; #endif - const Zstring& itempath = appendSeparator(dirpath) + shortName; + const Zstring& itempath = appendSeparator(dirPathFmt) + shortName; struct ::stat statData = {}; - if (!tryReportingItemError([&] - { - if (::lstat(itempath.c_str(), &statData) != 0) //lstat() does not resolve symlinks - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(itempath)), L"lstat", getLastError()); - }, sink, shortName)) - continue; //ignore error: skip file + try + { + if (::lstat(itempath.c_str(), &statData) != 0) //lstat() does not resolve symlinks + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(itempath)), L"lstat", getLastError()); + } + catch (const FileError& e) + { + if (onError) + onError(e.toString()); + continue; //ignore error: skip file + } if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! { - TraverseCallback::SymlinkInfo linkInfo; - linkInfo.lastWriteTime = statData.st_mtime; //UTC time (ANSI C format); unit: 1 second - - switch (sink.onSymlink(shortName, itempath, linkInfo)) - { - case TraverseCallback::LINK_FOLLOW: - { - //try to resolve symlink (and report error on failure!!!) - struct ::stat statDataTrg = {}; - bool validLink = tryReportingItemError([&] - { - if (::stat(itempath.c_str(), &statDataTrg) != 0) - throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(itempath)), L"stat", getLastError()); - }, sink, shortName); - - if (validLink) - { - if (S_ISDIR(statDataTrg.st_mode)) //a directory - { - if (TraverseCallback* trav = sink.onDir(shortName, itempath)) - { - ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); - traverse(itempath, *trav); - } - } - else //a file or named pipe, ect. - { - TraverseCallback::FileInfo fileInfo; - fileInfo.fileSize = statDataTrg.st_size; - fileInfo.lastWriteTime = statDataTrg.st_mtime; //UTC time (time_t format); unit: 1 second - fileInfo.id = extractFileId(statDataTrg); - fileInfo.symlinkInfo = &linkInfo; - sink.onFile(shortName, itempath, fileInfo); - } - } - // else //broken symlink -> ignore: it's client's responsibility to handle error! - } - break; - - case TraverseCallback::LINK_SKIP: - break; - } + if (onLink) + onLink({ shortName, itempath, statData.st_mtime}); } else if (S_ISDIR(statData.st_mode)) //a directory { - if (TraverseCallback* trav = sink.onDir(shortName, itempath)) - { - ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); - traverse(itempath, *trav); - } + if (onDir) + onDir({ shortName, itempath }); } else //a file or named pipe, ect. { - TraverseCallback::FileInfo fileInfo; - fileInfo.fileSize = statData.st_size; - fileInfo.lastWriteTime = statData.st_mtime; //UTC time (time_t format); unit: 1 second - fileInfo.id = extractFileId(statData); - - sink.onFile(shortName, itempath, fileInfo); + if (onFile) + onFile({ shortName, 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: @@ -617,15 +186,11 @@ private: However an "open" on a pipe will block (https://sourceforge.net/p/freefilesync/bugs/221/), so the copy routines need to be smarter!! */ } - } - - std::vector<char> buffer; -#ifdef ZEN_MAC - std::vector<char> bufferUtfDecomposed; -#endif -}; #endif + } + catch (const FileError& e) + { + if (onError) + onError(e.toString()); + } } - - -void zen::traverseFolder(const Zstring& dirpath, TraverseCallback& sink) { DirTraverser::execute(dirpath, sink); } diff --git a/zen/file_traverser.h b/zen/file_traverser.h index 174503b5..9cba9e58 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -4,64 +4,43 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef FILETRAVERSER_H_INCLUDED -#define FILETRAVERSER_H_INCLUDED +#ifndef FOLDER_TRAVERSER_H_INCLUDED_3127463214871234 +#define FOLDER_TRAVERSER_H_INCLUDED_3127463214871234 #include <cstdint> +#include <functional> #include "zstring.h" -#include "file_id_def.h" - -//advanced file traverser returning metadata and hierarchical information on files and directories namespace zen { -struct TraverseCallback +struct FileInfo { - virtual ~TraverseCallback() {} - - struct SymlinkInfo - { - SymlinkInfo() : lastWriteTime() {} - - std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC - }; - - struct FileInfo - { - FileInfo() : fileSize(), lastWriteTime(), symlinkInfo() {} - - std::uint64_t fileSize; //unit: bytes! - std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC - FileId id; //optional: initial if not supported! - const SymlinkInfo* symlinkInfo; //only filled if file is a followed symlink - }; - - enum HandleLink - { - LINK_FOLLOW, //dereferences link, then calls "onDir()" or "onFile()" - LINK_SKIP - }; - - enum HandleError - { - ON_ERROR_RETRY, - ON_ERROR_IGNORE - }; + const Zchar* shortName; + const Zstring& fullPath; + std::uint64_t fileSize; //[bytes] + std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC +}; - virtual void onFile (const Zchar* shortName, const Zstring& filepath, const FileInfo& details) = 0; - virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& linkpath, const SymlinkInfo& details) = 0; - virtual TraverseCallback* onDir (const Zchar* shortName, const Zstring& dirpath) = 0; - //nullptr: ignore directory, non-nullptr: traverse into using the (new) callback => implement releaseDirTraverser() if necessary! - virtual void releaseDirTraverser(TraverseCallback* trav) {} +struct DirInfo +{ + const Zchar* shortName; + const Zstring& fullPath; +}; - virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) = 0; //failed directory traversal -> consider directory data at current level as incomplete! - virtual HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) = 0; //failed to get data for single file/dir/symlink only! +struct SymlinkInfo +{ + const Zchar* shortName; + const Zstring& fullPath; + std::int64_t lastWriteTime; //number of seconds since Jan. 1st 1970 UTC }; -//custom traverser with detail information about files -//- client needs to handle duplicate file reports! (FilePlusTraverser fallback, retrying to read directory contents, ...) -//- directory may end with PATH_SEPARATOR -void traverseFolder(const Zstring& dirpath, TraverseCallback& sink); //noexcept +//- non-recursive +//- directory path may end with PATH_SEPARATOR +void traverseFolder(const Zstring& dirPath, //noexcept + const std::function<void (const FileInfo& fi)>& onFile, // + const std::function<void (const DirInfo& di)>& onDir, //optional + const std::function<void (const SymlinkInfo& si)>& onLink, // + const std::function<void (const std::wstring& errorMsg)>& onError); // } -#endif // FILETRAVERSER_H_INCLUDED +#endif //FOLDER_TRAVERSER_H_INCLUDED_3127463214871234 diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h index d2289330..19b838ef 100644 --- a/zen/long_path_prefix.h +++ b/zen/long_path_prefix.h @@ -47,15 +47,6 @@ As used by GetModuleFileNameEx() and symlinks (FSCTL_GET_REPARSE_POINT): - - - - - - - - - //################## implementation ################## //there are two flavors of long path prefix: one for UNC paths, one for regular paths @@ -128,11 +119,11 @@ Zstring zen::ntPathToWin32Path(const Zstring& path) //noexcept if (bufSize > 0) { std::vector<wchar_t> buf(bufSize); - DWORD charsWritten = ::GetEnvironmentVariable(L"SystemRoot", //_In_opt_ LPCTSTR lpName, + const DWORD charsWritten = ::GetEnvironmentVariable(L"SystemRoot", //_In_opt_ LPCTSTR lpName, &buf[0], //_Out_opt_ LPTSTR lpBuffer, bufSize); //_In_ DWORD nSize - if (charsWritten != 0 && charsWritten < bufSize) + if (0 < charsWritten && charsWritten < bufSize) return replaceCpy(path, L"\\SystemRoot\\", appendSeparator(Zstring(&buf[0], charsWritten)), false); } } diff --git a/zen/notify_removal.cpp b/zen/notify_removal.cpp index 37f305c9..f528e81b 100644 --- a/zen/notify_removal.cpp +++ b/zen/notify_removal.cpp @@ -86,7 +86,7 @@ MessageProvider::MessageProvider() : if (::RegisterClass(&wc) == 0) throwFileError(_("Unable to register to receive system messages."), L"RegisterClass", getLastError()); - ScopeGuard guardClass = makeGuard([&] { ::UnregisterClass(dummyClassName, hMainModule); }); + ScopeGuard guardConstructor = zen::makeGuard([&] { this->~MessageProvider(); }); //create dummy-window windowHandle = ::CreateWindow(dummyClassName, //_In_opt_ LPCTSTR lpClassName, @@ -106,14 +106,15 @@ MessageProvider::MessageProvider() : if (::SetWindowLongPtr(windowHandle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this)) == 0 && ::GetLastError() != ERROR_SUCCESS) throwFileError(_("Unable to register to receive system messages."), L"SetWindowLongPtr", ::GetLastError()); - guardClass.dismiss(); + guardConstructor.dismiss(); } MessageProvider::~MessageProvider() { //clean-up in reverse order - ::DestroyWindow(windowHandle); + if (windowHandle) + ::DestroyWindow(windowHandle); ::UnregisterClass(dummyClassName, //LPCTSTR lpClassName OR ATOM in low-order word! hMainModule); //HINSTANCE hInstance } diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 649bbb8e..3b5ac421 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -143,9 +143,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it! #ifdef ZEN_WIN - std::vector<Zstring> itempaths; - itempaths.push_back(itempath); - recycleOrDelete(itempaths, nullptr); //throw FileError + recycleOrDelete({ itempath }, nullptr); //throw FileError #elif defined ZEN_LINUX GFile* file = ::g_file_new_for_path(itempath.c_str()); //never fails according to docu @@ -231,7 +229,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError #ifdef ZEN_WIN -bool zen::recycleBinExists(const Zstring& pathName, const std::function<void ()>& onUpdateGui) //throw FileError +bool zen::recycleBinExists(const Zstring& dirpath, const std::function<void ()>& onUpdateGui) //throw FileError { if (vistaOrLater()) { @@ -240,24 +238,24 @@ bool zen::recycleBinExists(const Zstring& pathName, const std::function<void ()> const DllFun<FunType_getLastErrorMessage> getLastErrorMessage(getDllName(), funName_getLastErrorMessage); if (!getRecycleBinStatus || !getLastErrorMessage) - throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(pathName)), + throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(dirpath)), replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(getDllName()))); bool hasRecycler = false; - if (!getRecycleBinStatus(pathName.c_str(), hasRecycler)) - throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(pathName)), getLastErrorMessage()); + if (!getRecycleBinStatus(dirpath.c_str(), hasRecycler)) + throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(dirpath)), getLastErrorMessage()); return hasRecycler; } else { //excessive runtime if recycle bin exists, is full and drive is slow: - auto ft = async([pathName]() + auto ft = async([dirpath]() { SHQUERYRBINFO recInfo = {}; recInfo.cbSize = sizeof(recInfo); - return ::SHQueryRecycleBin(pathName.c_str(), //__in_opt LPCTSTR pszRootPath, - &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo + return ::SHQueryRecycleBin(dirpath.c_str(), //__in_opt LPCTSTR pszRootPath, + &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo }); while (!ft.timed_wait(boost::posix_time::milliseconds(50))) @@ -278,7 +276,7 @@ bool zen::recycleBinExists(const Zstring& pathName, const std::function<void ()> // -> not upward-compatible, wrong result for subst-alias: recycler assumed existing, although it is not! //5. alternative approach a'la Raymond Chen: http://blogs.msdn.com/b/oldnewthing/archive/2008/09/18/8956382.aspx - //caveat: might not be reliable, e.g. "subst"-alias of volume contains "$Recycle.Bin" although it is not available! + //caveat: might not be reliable, e.g. "subst"-alias of volume contains "$Recycle.Bin" although recycler is not available! /* Zstring rootPathPf = appendSeparator(&buffer[0]); diff --git a/zen/recycler.h b/zen/recycler.h index d86fbb12..2319f7b6 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -35,8 +35,8 @@ bool recycleOrDelete(const Zstring& itempath); //throw FileError, return "true" #ifdef ZEN_WIN -//can take a long time if recycle bin is full and drive is slow!!! => buffer volume ids! -bool recycleBinExists(const Zstring& pathName, const std::function<void ()>& onUpdateGui); //throw FileError +//can take a long time if recycle bin is full and drive is slow!!! => buffer! +bool recycleBinExists(const Zstring& dirpath, const std::function<void ()>& onUpdateGui); //throw FileError void recycleOrDelete(const std::vector<Zstring>& filepaths, //throw FileError, return "true" if file/dir was actually deleted const std::function<void (const Zstring& currentItem)>& notifyDeletionStatus); //optional; currentItem may be empty diff --git a/zen/stl_tools.h b/zen/stl_tools.h index d00fc732..d2d4ee1a 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -80,7 +80,7 @@ void vector_append(V& vec, const W& vec2) template <class V, class W> inline void set_append(V& s, const W& s2) { - s.insert(s2.begin(), s2.end()); + s.insert(s2.begin(), s2.end()); } diff --git a/zen/win_ver.h b/zen/win_ver.h index 97b6d7e1..9cf792f2 100644 --- a/zen/win_ver.h +++ b/zen/win_ver.h @@ -125,7 +125,7 @@ inline bool running64BitWindows() //http://blogs.msdn.com/b/oldnewthing/archive/2005/02/01/364563.aspx { static_assert(zen::is32BitBuild || zen::is64BitBuild, ""); - return is64BitBuild || runningWOW64(); //should we bother to make this a compile-time check? + return is64BitBuild || runningWOW64(); //should we bother to make this a compile-time check for the first case? } } |