summaryrefslogtreecommitdiff
path: root/zen/file_traverser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zen/file_traverser.cpp')
-rw-r--r--zen/file_traverser.cpp613
1 files changed, 89 insertions, 524 deletions
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); }
bgstack15