summaryrefslogtreecommitdiff
path: root/shared/file_traverser.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:12:46 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:12:46 +0200
commitb338e29fd3eaf700f8c8360aa0310048ba941d54 (patch)
tree122f8ef3790d12cd10275ef7453a9e8053322d78 /shared/file_traverser.cpp
parent3.18 (diff)
downloadFreeFileSync-b338e29fd3eaf700f8c8360aa0310048ba941d54.tar.gz
FreeFileSync-b338e29fd3eaf700f8c8360aa0310048ba941d54.tar.bz2
FreeFileSync-b338e29fd3eaf700f8c8360aa0310048ba941d54.zip
3.19
Diffstat (limited to 'shared/file_traverser.cpp')
-rw-r--r--shared/file_traverser.cpp214
1 files changed, 146 insertions, 68 deletions
diff --git a/shared/file_traverser.cpp b/shared/file_traverser.cpp
index 2525be58..aced3487 100644
--- a/shared/file_traverser.cpp
+++ b/shared/file_traverser.cpp
@@ -6,9 +6,7 @@
//
#include "file_traverser.h"
#include <limits>
-#include "system_constants.h"
#include "last_error.h"
-#include "string_conv.h"
#include "assert_static.h"
#include "symlink_target.h"
@@ -23,6 +21,36 @@
#include <cerrno>
#endif
+using namespace zen;
+
+
+namespace
+{
+//implement "retry" in a generic way:
+
+//returns "true" if "cmd" was invoked successfully, "false" if error occured and was ignored
+template <class Command> inline //function object with bool operator()(std::wstring& errorMsg), returns "true" on success, "false" on error and writes "errorMsg" in this case
+bool tryReportingError(Command cmd, zen::TraverseCallback& callback)
+{
+ for (;;)
+ {
+ std::wstring errorMsg;
+ if (cmd(errorMsg))
+ return true;
+
+ switch (callback.onError(errorMsg))
+ {
+ case TraverseCallback::TRAV_ERROR_RETRY:
+ break;
+ case TraverseCallback::TRAV_ERROR_IGNORE:
+ return false;
+ default:
+ assert(false);
+ break;
+ }
+ }
+}
+
#ifdef FFS_WIN
inline
@@ -62,6 +90,7 @@ bool setWin32FileInformationFromSymlink(const Zstring& linkName, zen::TraverseCa
return true;
}
#endif
+}
class DirTraverser
@@ -78,8 +107,8 @@ public:
#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.size() > 1 && baseDirectory.EndsWith(FILE_NAME_SEPARATOR) ? //exception: allow '/'
+ baseDirectory.BeforeLast(FILE_NAME_SEPARATOR) :
baseDirectory;
#endif
@@ -97,40 +126,56 @@ public:
}
private:
+ DirTraverser(const DirTraverser&);
+ DirTraverser& operator=(const DirTraverser&);
+
template <bool followSymlinks>
void traverse(const Zstring& directory, zen::TraverseCallback& sink, int level)
{
- using namespace zen;
+ using namespace zen;
- if (level == 100) //catch endless recursion
+ tryReportingError([&](std::wstring& errorMsg) -> bool
{
- sink.onError(wxString(_("Endless loop when traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\""));
- return;
- }
+ if (level == 100) //notify endless recursion
+ {
+ errorMsg = _("Endless loop when traversing directory:") + "\n\"" + directory + "\"";
+ return false;
+ }
+ return true;
+ }, sink);
+
#ifdef FFS_WIN
//ensure directoryFormatted ends with backslash
- const Zstring& directoryFormatted = directory.EndsWith(common::FILE_NAME_SEPARATOR) ?
+ const Zstring& directoryFormatted = directory.EndsWith(FILE_NAME_SEPARATOR) ?
directory :
- directory + common::FILE_NAME_SEPARATOR;
+ directory + FILE_NAME_SEPARATOR;
WIN32_FIND_DATA fileInfo = {};
- HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(directoryFormatted + Zchar('*')).c_str(), //__in LPCTSTR lpFileName
- &fileInfo); //__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)
+ HANDLE searchHandle = INVALID_HANDLE_VALUE;
+ tryReportingError([&](std::wstring& errorMsg) -> bool
{
- const DWORD lastError = ::GetLastError();
- if (lastError == ERROR_FILE_NOT_FOUND)
- return;
+ searchHandle = ::FindFirstFile(
+ applyLongPathPrefix(directoryFormatted + Zchar('*')).c_str(), //__in LPCTSTR lpFileName
+ &fileInfo); //__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
- //else: we have a problem... report it:
- const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\"");
+ if (searchHandle == INVALID_HANDLE_VALUE)
+ {
+ const DWORD lastError = ::GetLastError();
+ if (lastError == ERROR_FILE_NOT_FOUND)
+ return true; //fine: empty directory
- sink.onError(errorMessage + wxT("\n\n") +zen::getLastErrorFormatted(lastError));
- return;
- }
+ //else: we have a problem... report it:
+ errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError);
+ return false;
+ }
+ return true;
+ }, sink);
+
+ if (searchHandle == INVALID_HANDLE_VALUE)
+ return; //empty dir or ignore error
Loki::ScopeGuard dummy = Loki::MakeGuard(::FindClose, searchHandle);
(void)dummy; //silence warning "unused variable"
@@ -139,9 +184,8 @@ private:
{
//don't return "." and ".."
const Zchar* const shortName = fileInfo.cFileName;
- if ( shortName[0] == Zstr('.') &&
- ((shortName[1] == Zstr('.') && shortName[2] == Zstr('\0')) ||
- shortName[1] == Zstr('\0')))
+ if (shortName[0] == L'.' &&
+ (shortName[1] == L'\0' || (shortName[1] == L'.' && shortName[2] == L'\0')))
continue;
const Zstring& fullName = directoryFormatted + shortName;
@@ -210,65 +254,95 @@ private:
details.fileSize = zen::UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh);
}
- sink.onFile(shortName, fullName, details);
+ sink.onFile(shortName, fullName, details);
}
}
- while (::FindNextFile(searchHandle, // handle to search
- &fileInfo)); // pointer to structure for data on found file
+ while ([&]() -> bool
+ {
+ bool moreData = false;
- const DWORD lastError = ::GetLastError();
- if (lastError != ERROR_NO_MORE_FILES) //this is fine
+ tryReportingError([&](std::wstring& errorMsg) -> bool
{
- //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") + zen::getLastErrorFormatted(lastError));
- }
+ if (!::FindNextFile(searchHandle, // handle to search
+ &fileInfo)) // pointer to structure for data on found file
+ {
+ if (::GetLastError() == ERROR_NO_MORE_FILES) //this is fine
+ return true;
+
+ //else we have a problem... report it:
+ errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+
+ moreData = true;
+ return true;
+ }, sink);
+
+ return moreData;
+ }());
+
#elif defined FFS_LINUX
- DIR* dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/"
- if (dirObj == NULL)
- {
- const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + zToWx(directory) + wxT("\"") ;
- sink.onError(errorMessage + wxT("\n\n") + zen::getLastErrorFormatted());
- return;
- }
+ DIR* dirObj = NULL;
+ if (!tryReportingError([&](std::wstring& errorMsg) -> bool
+ {
+ dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/"
+ if (dirObj == NULL)
+ {
+ errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+ return true;
+ }, sink))
+ return;
Loki::ScopeGuard dummy = Loki::MakeGuard(::closedir, dirObj); //never close NULL handles! -> crash
(void)dummy; //silence warning "unused variable"
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)
+ struct dirent* dirEntry = NULL;
+ tryReportingError([&](std::wstring& errorMsg) -> bool
{
- if (errno == 0)
- return; //everything okay
+ errno = 0; //set errno to 0 as unfortunately this isn't done when readdir() returns NULL because it can't find any files
+ dirEntry = ::readdir(dirObj);
+ if (dirEntry == NULL)
+ {
+ if (errno == 0)
+ return true; //everything okay, not more items
- //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") + zen::getLastErrorFormatted());
+ //else: we have a problem... report it:
+ errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+ return true;
+ }, sink);
+ if (dirEntry == NULL) //no more items or ignore error
return;
- }
+
//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')))
+ if (shortName[0] == '.' &&
+ (shortName[1] == '\0' || (shortName[1] == '.' && shortName[2] == '\0')))
continue;
- const Zstring& fullName = directory.EndsWith(common::FILE_NAME_SEPARATOR) ? //e.g. "/"
+ const Zstring& fullName = directory.EndsWith(FILE_NAME_SEPARATOR) ? //e.g. "/"
directory + shortName :
- directory + common::FILE_NAME_SEPARATOR + shortName;
+ directory + FILE_NAME_SEPARATOR + shortName;
+
+ struct stat fileInfo = {};
- struct stat fileInfo;
+ if (!tryReportingError([&](std::wstring& errorMsg) -> bool
+ {
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") + zen::getLastErrorFormatted());
- continue;
- }
+ {
+ errorMsg = _("Error reading file attributes:") + "\n\"" + fullName + "\"" + "\n\n" + zen::getLastErrorFormatted();
+ return false;
+ }
+ return true;
+ }, sink))
+ continue; //ignore error: skip file
const bool isSymbolicLink = S_ISLNK(fileInfo.st_mode);
@@ -278,11 +352,7 @@ private:
{
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 = 0U;
- sink.onFile(shortName, fullName, details); //report broken symlink as file!
+ sink.onFile(shortName, fullName, TraverseCallback::FileInfo()); //report broken symlink as file!
continue;
}
}
@@ -352,7 +422,7 @@ private:
const dst::RawTime encodedTime = dst::fatEncodeUtcTime(i->second); //throw (std::runtime_error)
{
HANDLE hTarget = ::CreateFile(zen::applyLongPathPrefix(i->first).c_str(),
- FILE_WRITE_ATTRIBUTES,
+ GENERIC_WRITE, //just FILE_WRITE_ATTRIBUTES may not be enough for some NAS shares!
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
@@ -410,5 +480,13 @@ private:
void zen::traverseFolder(const Zstring& directory, bool followSymlinks, TraverseCallback& sink, DstHackCallback* dstCallback)
{
+#ifdef FFS_WIN
+ try //traversing certain folders with restricted permissions requires this privilege! (but copying these files may still fail)
+ {
+ zen::Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw (FileError)
+ }
+ catch (...) {} //don't cause issues in user mode
+#endif
+
DirTraverser(directory, followSymlinks, sink, dstCallback);
}
bgstack15