summaryrefslogtreecommitdiff
path: root/zen/file_traverser.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:20:07 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:20:07 +0200
commit88a8b528e20013c0aa3cc6bcd9659b0b5ddd9170 (patch)
treec6c5babb49b90293380106b81ae5c446959ac70f /zen/file_traverser.cpp
parent5.3 (diff)
downloadFreeFileSync-88a8b528e20013c0aa3cc6bcd9659b0b5ddd9170.tar.gz
FreeFileSync-88a8b528e20013c0aa3cc6bcd9659b0b5ddd9170.tar.bz2
FreeFileSync-88a8b528e20013c0aa3cc6bcd9659b0b5ddd9170.zip
5.4
Diffstat (limited to 'zen/file_traverser.cpp')
-rw-r--r--zen/file_traverser.cpp297
1 files changed, 145 insertions, 152 deletions
diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp
index ea6aa289..3acb0edf 100644
--- a/zen/file_traverser.cpp
+++ b/zen/file_traverser.cpp
@@ -43,9 +43,9 @@ bool tryReportingError(Command cmd, zen::TraverseCallback& callback) //return "t
{
switch (callback.onError(e.toString()))
{
- case TraverseCallback::TRAV_ERROR_RETRY:
+ case TraverseCallback::ON_ERROR_RETRY:
break;
- case TraverseCallback::TRAV_ERROR_IGNORE:
+ case TraverseCallback::ON_ERROR_IGNORE:
return false;
//default:
// assert(false);
@@ -57,7 +57,7 @@ bool tryReportingError(Command cmd, zen::TraverseCallback& callback) //return "t
#ifdef FFS_WIN
inline
-bool extractFileInfoFromSymlink(const Zstring& linkName, zen::TraverseCallback::FileInfo& output)
+bool getTargetInfoFromSymlink(const Zstring& linkName, zen::TraverseCallback::FileInfo& output)
{
//open handle to target of symbolic link
HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(linkName).c_str(),
@@ -65,7 +65,7 @@ bool extractFileInfoFromSymlink(const Zstring& linkName, zen::TraverseCallback::
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS,
+ FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory
nullptr);
if (hFile == INVALID_HANDLE_VALUE)
return false;
@@ -179,7 +179,7 @@ struct Win32Traverser
{
struct DirHandle
{
- DirHandle() : searchHandle(nullptr), firstRead(true) {}
+ DirHandle() : searchHandle(nullptr), firstRead(true), firstData() {}
HANDLE searchHandle;
bool firstRead;
@@ -268,7 +268,7 @@ struct FilePlusTraverser
{
hnd.searchHandle = ::openDir(applyLongPathPrefix(directory).c_str());
if (hnd.searchHandle == nullptr)
- throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted());
+ throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted() + L" (+)");
}
static void destroy(DirHandle hnd) { ::closeDir(hnd.searchHandle); } //throw()
@@ -285,32 +285,15 @@ struct FilePlusTraverser
/*
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.
-
- NT status code | Win32 error code
- --------------------------------|--------------------------
- STATUS_INVALID_LEVEL | ERROR_INVALID_LEVEL
- STATUS_NOT_SUPPORTED | ERROR_NOT_SUPPORTED
- STATUS_INVALID_PARAMETER | ERROR_INVALID_PARAMETER
- STATUS_INVALID_NETWORK_RESPONSE | ERROR_BAD_NET_RESP
- STATUS_INVALID_INFO_CLASS | ERROR_INVALID_PARAMETER
- STATUS_UNSUCCESSFUL | ERROR_GEN_FAILURE
- STATUS_ACCESS_VIOLATION | ERROR_NOACCESS ->FileIdBothDirectoryInformation on XP accessing UDF
*/
-
- if (lastError == ERROR_INVALID_LEVEL ||
- lastError == ERROR_NOT_SUPPORTED ||
- lastError == ERROR_INVALID_PARAMETER ||
- lastError == ERROR_BAD_NET_RESP ||
- lastError == ERROR_UNEXP_NET_ERR || //traverse network drive hosted by Win98
- lastError == ERROR_GEN_FAILURE ||
- lastError == ERROR_NOACCESS)
+ if (lastError == ERROR_NOT_SUPPORTED)
{
fb(); //fallback should apply to whole directory sub-tree!
return false;
}
//else we have a problem... report it:
- throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted());
+ throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (+)");
}
return true;
}
@@ -346,9 +329,8 @@ struct FilePlusTraverser
class DirTraverser
{
public:
- DirTraverser(const Zstring& baseDirectory, bool followSymlinks, zen::TraverseCallback& sink, zen::DstHackCallback* dstCallback) :
+ DirTraverser(const Zstring& baseDirectory, TraverseCallback& sink, DstHackCallback* dstCallback) :
isFatFileSystem(dst::isFatDrive(baseDirectory)),
- followSymlinks_(followSymlinks),
volumeSerial(retrieveVolumeSerial(baseDirectory)) //return 0 on error
{
try //traversing certain folders with restricted permissions requires this privilege! (but copying these files may still fail)
@@ -377,96 +359,103 @@ private:
tryReportingError([&]
{
if (level == 100) //notify endless recursion
- throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + _("Endless loop."));
+ throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + _("Detected endless directory recursion."));
}, sink);
typename Trav::DirHandle searchHandle;
- const bool openSuccess = tryReportingError([&]
- {
- typedef Trav Trav; //f u VS!
- Trav::create(directory, searchHandle); //throw FileError
- }, sink);
-
- if (!openSuccess)
- return; //ignored error
+ if (!tryReportingError([&]
+ {
+ typedef Trav Trav; //f u VS!
+ Trav::create(directory, searchHandle); //throw FileError
+ }, sink))
+ return; //ignored error
ZEN_ON_SCOPE_EXIT(typedef Trav Trav; Trav::destroy(searchHandle));
- typename Trav::FindData fileInfo = {};
+ typename Trav::FindData findData = {};
auto fallback = [&] { this->traverse<Win32Traverser>(directory, sink, level); }; //help VS2010 a little by avoiding too deeply nested lambdas
- while ([&]() -> bool
- {
- bool gotEntry = false;
-
- typedef Trav Trav1; //f u VS!
- tryReportingError([&]
+ for (;;)
{
- typedef Trav1 Trav; //f u VS!
- gotEntry = Trav::getEntry(searchHandle, directory, fileInfo, fallback); //throw FileError
- }, sink);
+ bool gotEntry = false;
+ tryReportingError([&] { typedef Trav Trav; /*VS 2010 bug*/ gotEntry = Trav::getEntry(searchHandle, directory, findData, fallback); }, sink); //throw FileError
+ if (!gotEntry) //no more items or ignored error
+ return;
- return gotEntry;
- }())
- {
//skip "." and ".."
- const Zchar* const shortName = Trav::getShortName(fileInfo);
+ const Zchar* const shortName = Trav::getShortName(findData);
if (shortName[0] == L'.' &&
- (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0)))
+ (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0)))
continue;
const Zstring& fullName = appendSeparator(directory) + shortName;
- if (Trav::isSymlink(fileInfo) && !followSymlinks_) //evaluate symlink directly
+ if (Trav::isSymlink(findData)) //check first!
{
- TraverseCallback::SymlinkInfo details;
+ TraverseCallback::SymlinkInfo linkInfo;
try
{
- details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError
+ linkInfo.targetPath = getSymlinkRawTargetString(fullName); //throw FileError
}
-#ifdef NDEBUG //Release
- catch (FileError&) {}
-#else
- catch (FileError& e) { sink.onError(e.toString()); } //show broken symlink / access errors in debug build!
-#endif
+ catch (FileError&) { assert(false); }
+ linkInfo.lastWriteTimeRaw = Trav::getModTime (findData);
+ linkInfo.dirLink = Trav::isDirectory(findData); //directory symlinks have this flag on Windows
+
+ switch (sink.onSymlink(shortName, fullName, linkInfo))
+ {
+ case TraverseCallback::LINK_FOLLOW:
+ {
+ //try to resolve symlink (and report error on failure!!!)
+ TraverseCallback::FileInfo targetInfo;
+ const bool validLink = tryReportingError([&]
+ {
+ if (!getTargetInfoFromSymlink(fullName, targetInfo))
+ throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(fullName)) + L"\n\n" + getLastErrorFormatted());
+ }, sink);
+
+ if (validLink)
+ {
+ if (Trav::isDirectory(findData))
+ {
+ if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName))
+ traverse<Trav>(fullName, *rv, level + 1);
+ }
+ else //a file
+ sink.onFile(shortName, fullName, targetInfo);
+ }
+ else //report broken symlink as file!
+ sink.onFile(shortName, fullName, TraverseCallback::FileInfo());
+ }
+ break;
- details.lastWriteTimeRaw = Trav::getModTime (fileInfo);
- details.dirLink = Trav::isDirectory(fileInfo); //directory symlinks have this flag on Windows
- sink.onSymlink(shortName, fullName, details);
+ case TraverseCallback::LINK_SKIP:
+ break;
+ }
}
- else if (Trav::isDirectory(fileInfo)) //a directory... or symlink that needs to be followed (for directory symlinks this flag is set too!)
+ else if (Trav::isDirectory(findData))
{
- if (const std::shared_ptr<TraverseCallback> rv = sink.onDir(shortName, fullName))
+ if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName))
traverse<Trav>(fullName, *rv, level + 1);
}
- else //a file or symlink that is followed...
+ else //a file
{
- TraverseCallback::FileInfo details;
+ TraverseCallback::FileInfo fileInfo;
+ Trav::extractFileInfo(findData, volumeSerial, fileInfo);
- if (Trav::isSymlink(fileInfo)) //dereference symlinks!
- {
- extractFileInfoFromSymlink(fullName, details); //try to...
- //keep details initial if symlink is broken
- }
- else
+ //####################################### DST hack ###########################################
+ if (isFatFileSystem)
{
- Trav::extractFileInfo(fileInfo, volumeSerial, details);
-
- //####################################### DST hack ###########################################
- if (isFatFileSystem)
- {
- const dst::RawTime rawTime(Trav::getCreateTimeRaw(fileInfo), Trav::getModTimeRaw(fileInfo));
+ const dst::RawTime rawTime(Trav::getCreateTimeRaw(findData), Trav::getModTimeRaw(findData));
- if (dst::fatHasUtcEncoded(rawTime)) //throw std::runtime_error
- details.lastWriteTimeRaw = toTimeT(dst::fatDecodeUtcTime(rawTime)); //return real UTC time; throw (std::runtime_error)
- else
- markForDstHack.push_back(std::make_pair(fullName, Trav::getModTimeRaw(fileInfo)));
- }
- //####################################### DST hack ###########################################
+ if (dst::fatHasUtcEncoded(rawTime)) //throw std::runtime_error
+ fileInfo.lastWriteTimeRaw = toTimeT(dst::fatDecodeUtcTime(rawTime)); //return real UTC time; throw (std::runtime_error)
+ else
+ markForDstHack.push_back(std::make_pair(fullName, Trav::getModTimeRaw(findData)));
}
+ //####################################### DST hack ###########################################
- sink.onFile(shortName, fullName, details);
+ sink.onFile(shortName, fullName, fileInfo);
}
}
}
@@ -491,11 +480,11 @@ private:
FileUpdateHandle updateHandle(i->first, [=]
{
return ::CreateFile(zen::applyLongPathPrefix(i->first).c_str(),
- GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp
+ FILE_WRITE_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS,
+ FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory
nullptr);
});
if (updateHandle.get() == INVALID_HANDLE_VALUE)
@@ -543,7 +532,6 @@ private:
FilenameTimeList markForDstHack;
//####################################### DST hack ###########################################
- const bool followSymlinks_;
const DWORD volumeSerial; //may be 0!
};
@@ -552,8 +540,7 @@ private:
class DirTraverser
{
public:
- DirTraverser(const Zstring& baseDirectory, bool followSymlinks, zen::TraverseCallback& sink, zen::DstHackCallback* dstCallback) :
- followSymlinks_(followSymlinks)
+ DirTraverser(const Zstring& baseDirectory, zen::TraverseCallback& sink, zen::DstHackCallback* dstCallback)
{
const Zstring directoryFormatted = //remove trailing slash
baseDirectory.size() > 1 && endsWith(baseDirectory, FILE_NAME_SEPARATOR) ? //exception: allow '/'
@@ -580,23 +567,21 @@ private:
tryReportingError([&]
{
if (level == 100) //notify endless recursion
- throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + _("Endless loop."));
+ throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + _("Detected endless directory recursion."));
}, sink);
DIR* dirObj = nullptr;
- tryReportingError([&]
- {
- dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/"
+ if (!tryReportingError([&]
+ {
+ dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/"
if (!dirObj)
throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted());
- }, sink);
-
- if (!dirObj)
- return; //ignored error
+ }, sink))
+ return; //ignored error
ZEN_ON_SCOPE_EXIT(::closedir(dirObj)); //never close nullptr handles! -> crash
- while (true)
+ for (;;)
{
struct ::dirent* dirEntry = nullptr;
tryReportingError([&]
@@ -604,7 +589,7 @@ private:
if (::readdir_r(dirObj, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0)
throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted());
}, sink);
- if (!dirEntry) //no more items or ignore error
+ if (!dirEntry) //no more items or ignored error
return;
@@ -616,80 +601,88 @@ private:
const Zstring& fullName = appendSeparator(directory) + shortName;
- struct ::stat fileInfo = {};
- bool haveData = false;
- tryReportingError([&]
- {
- if (::lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks
+ struct ::stat statData = {};
+ if (!tryReportingError([&]
+ {
+ if (::lstat(fullName.c_str(), &statData) != 0) //lstat() does not resolve symlinks
throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(fullName)) + L"\n\n" + getLastErrorFormatted());
- haveData = true;
- }, sink);
- if (!haveData)
- continue; //ignore error: skip file
+ }, sink))
+ continue; //ignore error: skip file
- if (S_ISLNK(fileInfo.st_mode))
+ if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks!
{
- if (followSymlinks_) //on Linux Symlinks need to be followed to evaluate whether they point to a file or directory
- {
- if (::stat(fullName.c_str(), &fileInfo) != 0) //stat() resolves symlinks
- {
- sink.onFile(shortName, fullName, TraverseCallback::FileInfo()); //report broken symlink as file!
- continue;
- }
+ struct ::stat statDataTrg = {};
+ bool validLink = ::stat(fullName.c_str(), &statDataTrg) == 0; //if "LINK_SKIP", a broken link is no error!
- fileInfo.st_dev = 0; //id from dereferenced symlink is problematic, since renaming will consider the link, not the target!
- fileInfo.st_ino = 0; //
- }
- else //evaluate symlink directly
+ TraverseCallback::SymlinkInfo linkInfo;
+ try
{
- TraverseCallback::SymlinkInfo details;
- try
- {
- details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError
- }
- catch (FileError& e)
- {
-#ifndef NDEBUG //show broken symlink / access errors in debug build!
- sink.onError(e.toString());
-#endif
- }
+ linkInfo.targetPath = getSymlinkRawTargetString(fullName); //throw FileError
+ }
+ catch (FileError&) { assert(false); }
+ linkInfo.lastWriteTimeRaw = statData.st_mtime; //UTC time (ANSI C format); unit: 1 second
+ linkInfo.dirLink = validLink && S_ISDIR(statDataTrg.st_mode);
+ //S_ISDIR and S_ISLNK are mutually exclusive on Linux => explicitly need to follow link
- details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second
- details.dirLink = ::stat(fullName.c_str(), &fileInfo) == 0 && S_ISDIR(fileInfo.st_mode);
- //S_ISDIR and S_ISLNK are mutually exclusive on Linux => explicitly need to follow link
- sink.onSymlink(shortName, fullName, details);
- continue;
+ switch (sink.onSymlink(shortName, fullName, linkInfo))
+ {
+ case TraverseCallback::LINK_FOLLOW:
+ //try to resolve symlink (and report error on failure!!!)
+ validLink = tryReportingError([&]
+ {
+ if (validLink) return; //no need to check twice
+ if (::stat(fullName.c_str(), &statDataTrg) != 0)
+ throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(fullName)) + L"\n\n" + getLastErrorFormatted());
+ }, sink);
+
+ if (validLink)
+ {
+ if (S_ISDIR(statDataTrg.st_mode)) //a directory
+ {
+ if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName))
+ traverse(fullName, *rv, level + 1);
+ }
+ else //a file
+ {
+ TraverseCallback::FileInfo fileInfo;
+ fileInfo.fileSize = zen::UInt64(statDataTrg.st_size);
+ fileInfo.lastWriteTimeRaw = statDataTrg.st_mtime; //UTC time (time_t format); unit: 1 second
+ //fileInfo.id = extractFileID(statDataTrg); -> id from dereferenced symlink is problematic, since renaming will consider the link, not the target!
+ sink.onFile(shortName, fullName, fileInfo);
+ }
+ }
+ else //report broken symlink as file!
+ sink.onFile(shortName, fullName, TraverseCallback::FileInfo());
+ break;
+
+ case TraverseCallback::LINK_SKIP:
+ break;
}
}
-
- //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
+ else if (S_ISDIR(statData.st_mode)) //a directory
{
- const std::shared_ptr<TraverseCallback> rv = sink.onDir(shortName, fullName);
- if (rv)
+ if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName))
traverse(fullName, *rv, level + 1);
}
- else //a file... (or symlink; pathological!)
+ else //a file
{
- TraverseCallback::FileInfo details;
- details.fileSize = zen::UInt64(fileInfo.st_size);
- details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time (time_t format); unit: 1 second
- details.id = extractFileID(fileInfo);
+ TraverseCallback::FileInfo fileInfo;
+ fileInfo.fileSize = zen::UInt64(statData.st_size);
+ fileInfo.lastWriteTimeRaw = statData.st_mtime; //UTC time (time_t format); unit: 1 second
+ fileInfo.id = extractFileID(statData);
- sink.onFile(shortName, fullName, details);
+ sink.onFile(shortName, fullName, fileInfo);
}
}
}
std::vector<char> buffer;
- const bool followSymlinks_;
};
#endif
}
-void zen::traverseFolder(const Zstring& directory, bool followSymlinks, TraverseCallback& sink, DstHackCallback* dstCallback)
+void zen::traverseFolder(const Zstring& directory, TraverseCallback& sink, DstHackCallback* dstCallback)
{
- DirTraverser(directory, followSymlinks, sink, dstCallback);
+ DirTraverser(directory, sink, dstCallback);
}
bgstack15