diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:08:42 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:08:42 +0200 |
commit | c32707148292d104c66276b43796d6057c8c7a5d (patch) | |
tree | bb83513f4aff24153e21a4ec92e34e4c27651b1f /shared/file_traverser.cpp | |
parent | 3.9 (diff) | |
download | FreeFileSync-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.cpp | 419 |
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); } |