summaryrefslogtreecommitdiff
path: root/shared/file_traverser.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:08:42 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:08:42 +0200
commitc32707148292d104c66276b43796d6057c8c7a5d (patch)
treebb83513f4aff24153e21a4ec92e34e4c27651b1f /shared/file_traverser.cpp
parent3.9 (diff)
downloadFreeFileSync-c32707148292d104c66276b43796d6057c8c7a5d.tar.gz
FreeFileSync-c32707148292d104c66276b43796d6057c8c7a5d.tar.bz2
FreeFileSync-c32707148292d104c66276b43796d6057c8c7a5d.zip
3.10
Diffstat (limited to 'shared/file_traverser.cpp')
-rw-r--r--shared/file_traverser.cpp419
1 files changed, 254 insertions, 165 deletions
diff --git a/shared/file_traverser.cpp b/shared/file_traverser.cpp
index 8e9d5f0d..5d3f75f4 100644
--- a/shared/file_traverser.cpp
+++ b/shared/file_traverser.cpp
@@ -11,11 +11,14 @@
#include "string_conv.h"
#include <boost/shared_ptr.hpp>
#include <boost/scoped_array.hpp>
+#include "assert_static.h"
+#include <limits>
#ifdef FFS_WIN
#include <wx/msw/wrapwin.h> //includes "windows.h"
#include "WinIoCtl.h"
#include "long_path_prefix.h"
+#include "dst_hack.h"
#elif defined FFS_LINUX
#include <sys/stat.h>
@@ -29,8 +32,8 @@
//{
//public:
// DisableWow64Redirection() :
-// wow64DisableWow64FsRedirection(util::loadDllFunction<Wow64DisableWow64FsRedirectionFunc>(L"kernel32.dll", "Wow64DisableWow64FsRedirection")),
-// wow64RevertWow64FsRedirection(util::loadDllFunction<Wow64RevertWow64FsRedirectionFunc>(L"kernel32.dll", "Wow64RevertWow64FsRedirection")),
+// wow64DisableWow64FsRedirection(util::getDllFun<Wow64DisableWow64FsRedirectionFunc>(L"kernel32.dll", "Wow64DisableWow64FsRedirection")),
+// wow64RevertWow64FsRedirection(util::getDllFun<Wow64RevertWow64FsRedirectionFunc>(L"kernel32.dll", "Wow64RevertWow64FsRedirection")),
// oldValue(NULL)
// {
// if ( wow64DisableWow64FsRedirection &&
@@ -153,9 +156,13 @@ inline
wxLongLong getWin32TimeInformation(const FILETIME& lastWriteTime)
{
//convert UTC FILETIME to ANSI C format (number of seconds since Jan. 1st 1970 UTC)
- wxLongLong writeTimeLong(wxInt32(lastWriteTime.dwHighDateTime), lastWriteTime.dwLowDateTime);
+ wxLongLong writeTimeLong(lastWriteTime.dwHighDateTime, lastWriteTime.dwLowDateTime);
writeTimeLong /= 10000000; //reduce precision to 1 second (FILETIME has unit 10^-7 s)
writeTimeLong -= wxLongLong(2, 3054539008UL); //timeshift between ansi C time and FILETIME in seconds == 11644473600s
+
+ assert(lastWriteTime.dwHighDateTime <= static_cast<unsigned long>(std::numeric_limits<long>::max()));
+ assert_static(sizeof(DWORD) == sizeof(long));
+ assert_static(sizeof(long) == 4);
return writeTimeLong;
}
@@ -198,224 +205,306 @@ bool setWin32FileInformationFromSymlink(const Zstring& linkName, ffs3::TraverseC
#endif
-template <bool followSymlinks>
-void traverseDirectory(const Zstring& directory, ffs3::TraverseCallback* sink, int level)
+class DirTraverser
{
- using namespace ffs3;
-
- if (level == 100) //catch endless recursion
+public:
+ DirTraverser(const Zstring& baseDirectory, bool followSymlinks, ffs3::TraverseCallback& sink, ffs3::DstHackCallback* dstCallback)
+#ifdef FFS_WIN
+ : isFatFileSystem(dst::isFatDrive(baseDirectory))
+#endif
{
- sink->onError(wxString(_("Endless loop when traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""));
- return;
- }
-
+ //format base directory name
#ifdef FFS_WIN
- //ensure directoryFormatted ends with backslash
- const Zstring directoryFormatted = directory.EndsWith(common::FILE_NAME_SEPARATOR) ?
- directory :
- directory + common::FILE_NAME_SEPARATOR;
+ const Zstring& directoryFormatted = baseDirectory;
- WIN32_FIND_DATA fileMetaData;
- HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(directoryFormatted + DefaultChar('*')).c_str(), //__in LPCTSTR lpFileName
- &fileMetaData); //__out LPWIN32_FIND_DATA lpFindFileData
- //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH
-
- if (searchHandle == INVALID_HANDLE_VALUE)
- {
- const DWORD lastError = ::GetLastError();
- if (lastError == ERROR_FILE_NOT_FOUND)
- return;
+#elif defined FFS_LINUX
+ const Zstring directoryFormatted = //remove trailing slash
+ baseDirectory.size() > 1 && baseDirectory.EndsWith(common::FILE_NAME_SEPARATOR) ? //exception: allow '/'
+ baseDirectory.BeforeLast(common::FILE_NAME_SEPARATOR) :
+ baseDirectory;
+#endif
- //else: we have a problem... report it:
- const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\"") + wxT("\n\n") +
- ffs3::getLastErrorFormatted(lastError);
+ //traverse directories
+ if (followSymlinks)
+ traverse<true>(directoryFormatted, sink, 0);
+ else
+ traverse<false>(directoryFormatted, sink, 0);
- sink->onError(errorMessage);
- return;
+ //apply daylight saving time hack AFTER file traversing, to give separate feedback to user
+#ifdef FFS_WIN
+ if (isFatFileSystem && dstCallback)
+ applyDstHack(*dstCallback);
+#endif
}
- boost::shared_ptr<void> dummy(searchHandle, ::FindClose);
-
- do
+private:
+ template <bool followSymlinks>
+ void traverse(const Zstring& directory, ffs3::TraverseCallback& sink, int level)
{
- //don't return "." and ".."
- const wxChar* const shortName = fileMetaData.cFileName;
- if ( shortName[0] == wxChar('.') &&
- ((shortName[1] == wxChar('.') && shortName[2] == wxChar('\0')) ||
- shortName[1] == wxChar('\0')))
- continue;
+ using namespace ffs3;
- const Zstring fullName = directoryFormatted + shortName;
-
- const bool isSymbolicLink = (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
-
- if (isSymbolicLink && !followSymlinks) //evaluate symlink directly
+ if (level == 100) //catch endless recursion
{
- TraverseCallback::SymlinkInfo details;
- details.lastWriteTimeRaw = getWin32TimeInformation(fileMetaData.ftLastWriteTime);
- details.targetPath = getSymlinkTarget(fullName); //throw(); returns empty string on error
- details.dirLink = (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; //directory symlinks have this flag on Windows
- sink->onSymlink(shortName, fullName, details);
+ sink.onError(wxString(_("Endless loop when traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""));
+ return;
}
- else if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... or symlink that needs to be followed (for directory symlinks this flag is set too!)
+
+#ifdef FFS_WIN
+ //ensure directoryFormatted ends with backslash
+ const Zstring& directoryFormatted = directory.EndsWith(common::FILE_NAME_SEPARATOR) ?
+ directory :
+ directory + common::FILE_NAME_SEPARATOR;
+
+ WIN32_FIND_DATA fileMetaData;
+ HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(directoryFormatted + Zchar('*')).c_str(), //__in LPCTSTR lpFileName
+ &fileMetaData); //__out LPWIN32_FIND_DATA lpFindFileData
+ //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH
+
+ if (searchHandle == INVALID_HANDLE_VALUE)
{
- const TraverseCallback::ReturnValDir rv = sink->onDir(shortName, fullName);
- switch (rv.returnCode)
- {
- case TraverseCallback::ReturnValDir::TRAVERSING_DIR_IGNORE:
- break;
+ const DWORD lastError = ::GetLastError();
+ if (lastError == ERROR_FILE_NOT_FOUND)
+ return;
- case TraverseCallback::ReturnValDir::TRAVERSING_DIR_CONTINUE:
- traverseDirectory<followSymlinks>(fullName, rv.subDirCb, level + 1);
- break;
- }
+ //else: we have a problem... report it:
+ const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\"") + wxT("\n\n") +
+ ffs3::getLastErrorFormatted(lastError);
+
+ sink.onError(errorMessage);
+ return;
}
- else //a file or symlink that is followed...
+
+ boost::shared_ptr<void> dummy(searchHandle, ::FindClose);
+
+ do
{
- TraverseCallback::FileInfo details;
+ //don't return "." and ".."
+ const Zchar* const shortName = fileMetaData.cFileName;
+ if ( shortName[0] == Zstr('.') &&
+ ((shortName[1] == Zstr('.') && shortName[2] == Zstr('\0')) ||
+ shortName[1] == Zstr('\0')))
+ continue;
+
+ const Zstring& fullName = directoryFormatted + shortName;
- if (isSymbolicLink) //dereference symlinks!
+ const bool isSymbolicLink = (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
+
+ if (isSymbolicLink && !followSymlinks) //evaluate symlink directly
+ {
+ TraverseCallback::SymlinkInfo details;
+ details.lastWriteTimeRaw = getWin32TimeInformation(fileMetaData.ftLastWriteTime);
+ details.targetPath = getSymlinkTarget(fullName); //throw(); returns empty string on error
+ details.dirLink = (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; //directory symlinks have this flag on Windows
+ sink.onSymlink(shortName, fullName, details);
+ }
+ else if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... or symlink that needs to be followed (for directory symlinks this flag is set too!)
{
- if (!setWin32FileInformationFromSymlink(fullName, details))
+ const TraverseCallback::ReturnValDir rv = sink.onDir(shortName, fullName);
+ switch (rv.returnCode)
{
- //broken symlink...
- details.lastWriteTimeRaw = 0; //we are not interested in the modifiation time of the link
- details.fileSize = 0;
+ case TraverseCallback::ReturnValDir::TRAVERSING_DIR_IGNORE:
+ break;
+
+ case TraverseCallback::ReturnValDir::TRAVERSING_DIR_CONTINUE:
+ traverse<followSymlinks>(fullName, *rv.subDirCb, level + 1);
+ break;
}
}
- else
- setWin32FileInformation(fileMetaData.ftLastWriteTime, fileMetaData.nFileSizeHigh, fileMetaData.nFileSizeLow, details);
+ else //a file or symlink that is followed...
+ {
+ TraverseCallback::FileInfo details;
- sink->onFile(shortName, fullName, details);
- }
- }
- while (::FindNextFile(searchHandle, // handle to search
- &fileMetaData)); // pointer to structure for data on found file
+ if (isSymbolicLink) //dereference symlinks!
+ {
+ if (!setWin32FileInformationFromSymlink(fullName, details))
+ {
+ //broken symlink...
+ details.lastWriteTimeRaw = 0; //we are not interested in the modification time of the link
+ details.fileSize = 0;
+ }
+ }
+ else
+ {
+//####################################### DST hack ###########################################
+ if (isFatFileSystem)
+ {
+ const dst::RawTime rawTime(fileMetaData.ftCreationTime, fileMetaData.ftLastWriteTime);
+
+ if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error)
+ fileMetaData.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return real UTC time; throw (std::runtime_error)
+ else
+ markForDstHack.push_back(std::make_pair(fullName, fileMetaData.ftLastWriteTime));
+ }
+//####################################### DST hack ###########################################
+ setWin32FileInformation(fileMetaData.ftLastWriteTime, fileMetaData.nFileSizeHigh, fileMetaData.nFileSizeLow, details);
+ }
- const DWORD lastError = ::GetLastError();
- if (lastError == ERROR_NO_MORE_FILES)
- return; //everything okay
+ sink.onFile(shortName, fullName, details);
+ }
+ }
+ while (::FindNextFile(searchHandle, // handle to search
+ &fileMetaData)); // pointer to structure for data on found file
- //else: we have a problem... report it:
- const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\"") ;
- sink->onError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError));
- return;
+ const DWORD lastError = ::GetLastError();
+ if (lastError == ERROR_NO_MORE_FILES)
+ return; //everything okay
-#elif defined FFS_LINUX
- DIR* dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/"
- if (dirObj == NULL)
- {
+ //else: we have a problem... report it:
const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\"") ;
- sink->onError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted());
+ sink.onError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted(lastError));
return;
- }
- boost::shared_ptr<DIR> dummy(dirObj, &::closedir); //never close NULL handles! -> crash
-
- while (true)
- {
- errno = 0; //set errno to 0 as unfortunately this isn't done when readdir() returns NULL because it can't find any files
- struct dirent* dirEntry = ::readdir(dirObj);
- if (dirEntry == NULL)
+#elif defined FFS_LINUX
+ DIR* dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/"
+ if (dirObj == NULL)
{
- if (errno == 0)
- return; //everything okay
-
- //else: we have a problem... report it:
const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\"") ;
- sink->onError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted());
+ sink.onError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted());
return;
}
- //don't return "." and ".."
- const DefaultChar* const shortName = dirEntry->d_name;
- if ( shortName[0] == wxChar('.') &&
- ((shortName[1] == wxChar('.') && shortName[2] == wxChar('\0')) ||
- shortName[1] == wxChar('\0')))
- continue;
+ boost::shared_ptr<DIR> dummy(dirObj, &::closedir); //never close NULL handles! -> crash
- const Zstring fullName = directory.EndsWith(common::FILE_NAME_SEPARATOR) ? //e.g. "/"
- directory + shortName :
- directory + common::FILE_NAME_SEPARATOR + shortName;
-
- struct stat fileInfo;
- if (::lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks
+ while (true)
{
- const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(fullName) + wxT("\"");
- sink->onError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted());
- continue;
- }
+ errno = 0; //set errno to 0 as unfortunately this isn't done when readdir() returns NULL because it can't find any files
+ struct dirent* dirEntry = ::readdir(dirObj);
+ if (dirEntry == NULL)
+ {
+ if (errno == 0)
+ return; //everything okay
- const bool isSymbolicLink = S_ISLNK(fileInfo.st_mode);
+ //else: we have a problem... report it:
+ const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\"") ;
+ sink.onError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted());
+ return;
+ }
- if (isSymbolicLink)
- {
- if (followSymlinks) //on Linux Symlinks need to be followed to evaluate whether they point to a file or directory
+ //don't return "." and ".."
+ const Zchar* const shortName = dirEntry->d_name;
+ if ( shortName[0] == Zstr('.') &&
+ ((shortName[1] == Zstr('.') && shortName[2] == Zstr('\0')) ||
+ shortName[1] == Zstr('\0')))
+ continue;
+
+ const Zstring& fullName = directory.EndsWith(common::FILE_NAME_SEPARATOR) ? //e.g. "/"
+ directory + shortName :
+ directory + common::FILE_NAME_SEPARATOR + shortName;
+
+ struct stat fileInfo;
+ if (::lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks
+ {
+ const wxString errorMessage = wxString(_("Error reading file attributes:")) + wxT("\n\"") + zToWx(fullName) + wxT("\"");
+ sink.onError(errorMessage + wxT("\n\n") + ffs3::getLastErrorFormatted());
+ continue;
+ }
+
+ const bool isSymbolicLink = S_ISLNK(fileInfo.st_mode);
+
+ if (isSymbolicLink)
{
- if (::stat(fullName.c_str(), &fileInfo) != 0) //stat() resolves symlinks
+ if (followSymlinks) //on Linux Symlinks need to be followed to evaluate whether they point to a file or directory
{
- //a broken symbolic link
- TraverseCallback::FileInfo details;
- details.lastWriteTimeRaw = 0; //we are not interested in the modifiation time of the link
- details.fileSize = 0;
- sink->onFile(shortName, fullName, details); //report broken symlink as file!
+ if (::stat(fullName.c_str(), &fileInfo) != 0) //stat() resolves symlinks
+ {
+ //a broken symbolic link
+ TraverseCallback::FileInfo details;
+ details.lastWriteTimeRaw = 0; //we are not interested in the modifiation time of the link
+ details.fileSize = 0;
+ sink.onFile(shortName, fullName, details); //report broken symlink as file!
+ continue;
+ }
+ }
+ else //evaluate symlink directly
+ {
+ TraverseCallback::SymlinkInfo details;
+ details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second
+ details.targetPath = getSymlinkTarget(fullName); //throw(); returns empty string on error
+ details.dirLink = ::stat(fullName.c_str(), &fileInfo) == 0 && S_ISDIR(fileInfo.st_mode); //S_ISDIR and S_ISLNK are mutually exclusive on Linux => need to follow link
+ sink.onSymlink(shortName, fullName, details);
continue;
}
}
- else //evaluate symlink directly
+
+ //fileInfo contains dereferenced data in any case from here on
+
+ if (S_ISDIR(fileInfo.st_mode)) //a directory... cannot be a symlink on Linux in this case
{
- TraverseCallback::SymlinkInfo details;
+ const TraverseCallback::ReturnValDir rv = sink.onDir(shortName, fullName);
+ switch (rv.returnCode)
+ {
+ case TraverseCallback::ReturnValDir::TRAVERSING_DIR_IGNORE:
+ break;
+
+ case TraverseCallback::ReturnValDir::TRAVERSING_DIR_CONTINUE:
+ traverse<followSymlinks>(fullName, *rv.subDirCb, level + 1);
+ break;
+ }
+ }
+ else //a file... (or symlink; pathological!)
+ {
+ TraverseCallback::FileInfo details;
details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second
- details.targetPath = getSymlinkTarget(fullName); //throw(); returns empty string on error
- details.dirLink = ::stat(fullName.c_str(), &fileInfo) == 0 && S_ISDIR(fileInfo.st_mode); //S_ISDIR and S_ISLNK are mutually exclusive on Linux => need to follow link
- sink->onSymlink(shortName, fullName, details);
- continue;
+ details.fileSize = fileInfo.st_size;
+
+ sink.onFile(shortName, fullName, details);
}
}
+#endif
+ }
- //fileInfo contains dereferenced data in any case from here on
- if (S_ISDIR(fileInfo.st_mode)) //a directory... cannot be a symlink on Linux in this case
+#ifdef FFS_WIN
+//####################################### DST hack ###########################################
+ void applyDstHack(ffs3::DstHackCallback& dstCallback)
+ {
+ for (FilenameTimeList::const_iterator i = markForDstHack.begin(); i != markForDstHack.end(); ++i)
{
- const TraverseCallback::ReturnValDir rv = sink->onDir(shortName, fullName);
- switch (rv.returnCode)
+ dstCallback.requestUiRefresh(i->first);
+
+ HANDLE hTarget = ::CreateFile(ffs3::applyLongPathPrefix(i->first).c_str(),
+ FILE_WRITE_ATTRIBUTES,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ if (hTarget == INVALID_HANDLE_VALUE)
+ assert(false); //don't throw exceptions due to dst hack here
+ else
{
- case TraverseCallback::ReturnValDir::TRAVERSING_DIR_IGNORE:
- break;
+ boost::shared_ptr<void> dummy2(hTarget, ::CloseHandle);
- case TraverseCallback::ReturnValDir::TRAVERSING_DIR_CONTINUE:
- traverseDirectory<followSymlinks>(fullName, rv.subDirCb, level + 1);
- break;
- }
- }
- else //a file... (or symlink; pathological!)
- {
- TraverseCallback::FileInfo details;
- details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second
- details.fileSize = fileInfo.st_size;
+ const dst::RawTime encodedTime = dst::fatEncodeUtcTime(i->second); //throw (std::runtime_error)
+
+ if (!::SetFileTime(hTarget,
+ &encodedTime.createTimeRaw,
+ NULL,
+ &encodedTime.writeTimeRaw))
+ assert(false); //don't throw exceptions due to dst hack here
+
+#ifndef NDEBUG //dst hack: verify data written; attention: this check may fail for "sync.ffs_lock"
+ WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {};
+ assert(::GetFileAttributesEx(ffs3::applyLongPathPrefix(i->first).c_str(), //__in LPCTSTR lpFileName,
+ GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId,
+ &debugeAttr)); //__out LPVOID lpFileInformation
- sink->onFile(shortName, fullName, details);
+ assert(::CompareFileTime(&debugeAttr.ftCreationTime, &encodedTime.createTimeRaw) == 0);
+ assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &encodedTime.writeTimeRaw) == 0);
+#endif
+ }
}
}
+
+ const bool isFatFileSystem;
+ typedef std::vector<std::pair<Zstring, FILETIME> > FilenameTimeList;
+ FilenameTimeList markForDstHack;
+//####################################### DST hack ###########################################
#endif
-}
+};
-void ffs3::traverseFolder(const Zstring& directory,
- bool followSymlinks,
- TraverseCallback* sink)
+void ffs3::traverseFolder(const Zstring& directory, bool followSymlinks, TraverseCallback& sink, DstHackCallback* dstCallback)
{
-#ifdef FFS_WIN
- const Zstring& directoryFormatted = directory;
-#elif defined FFS_LINUX
- const Zstring directoryFormatted = //remove trailing slash
- directory.size() > 1 && directory.EndsWith(common::FILE_NAME_SEPARATOR) ? //exception: allow '/'
- directory.BeforeLast(common::FILE_NAME_SEPARATOR) :
- directory;
-#endif
-
- if (followSymlinks)
- traverseDirectory<true>(directoryFormatted, sink, 0);
- else
- traverseDirectory<false>(directoryFormatted, sink, 0);
+ DirTraverser(directory, followSymlinks, sink, dstCallback);
}
bgstack15