summaryrefslogtreecommitdiff
path: root/shared/fileTraverser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'shared/fileTraverser.cpp')
-rw-r--r--shared/fileTraverser.cpp361
1 files changed, 361 insertions, 0 deletions
diff --git a/shared/fileTraverser.cpp b/shared/fileTraverser.cpp
new file mode 100644
index 00000000..6438b358
--- /dev/null
+++ b/shared/fileTraverser.cpp
@@ -0,0 +1,361 @@
+#include "fileTraverser.h"
+#include "globalFunctions.h"
+#include "systemFunctions.h"
+#include <wx/intl.h>
+
+#ifdef FFS_WIN
+#include <wx/msw/wrapwin.h> //includes "windows.h"
+
+#elif defined FFS_LINUX
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#endif
+
+
+#ifdef FFS_WIN
+class CloseHandleOnExit
+{
+public:
+ CloseHandleOnExit(HANDLE fileHandle) : fileHandle_(fileHandle) {}
+
+ ~CloseHandleOnExit()
+ {
+ CloseHandle(fileHandle_);
+ }
+
+private:
+ HANDLE fileHandle_;
+};
+
+
+class CloseFindHandleOnExit
+{
+public:
+ CloseFindHandleOnExit(HANDLE searchHandle) : searchHandle_(searchHandle) {}
+
+ ~CloseFindHandleOnExit()
+ {
+ FindClose(searchHandle_);
+ }
+
+private:
+ HANDLE searchHandle_;
+};
+
+
+inline
+void setWin32FileInformation(const FILETIME& lastWriteTime,
+ const DWORD fileSizeHigh,
+ const DWORD fileSizeLow,
+ FreeFileSync::TraverseCallback::FileInfo& output)
+{
+ //convert UTC FILETIME to ANSI C format (number of seconds since Jan. 1st 1970 UTC)
+ wxLongLong writeTimeLong(wxInt32(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
+ output.lastWriteTimeRaw = writeTimeLong;
+
+ output.fileSize = wxULongLong(fileSizeHigh, fileSizeLow);
+}
+
+
+inline
+bool setWin32FileInformationFromSymlink(const Zstring linkName, FreeFileSync::TraverseCallback::FileInfo& output)
+{
+ //open handle to target of symbolic link
+ HANDLE hFile = ::CreateFile(linkName.c_str(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ if (hFile == INVALID_HANDLE_VALUE)
+ return false;
+
+ CloseHandleOnExit dummy(hFile);
+
+ BY_HANDLE_FILE_INFORMATION fileInfoByHandle;
+
+ if (!::GetFileInformationByHandle(
+ hFile,
+ &fileInfoByHandle))
+ return false;
+
+ //write output
+ setWin32FileInformation(fileInfoByHandle.ftLastWriteTime, fileInfoByHandle.nFileSizeHigh, fileInfoByHandle.nFileSizeLow, output);
+ return true;
+}
+
+#elif defined FFS_LINUX
+class CloseDirOnExit
+{
+public:
+ CloseDirOnExit(DIR* dir) : m_dir(dir) {}
+
+ ~CloseDirOnExit()
+ {
+ ::closedir(m_dir); //no error handling here
+ }
+
+private:
+ DIR* m_dir;
+};
+#endif
+
+
+template <bool traverseDirectorySymlinks>
+bool traverseDirectory(const Zstring& directory, FreeFileSync::TraverseCallback* sink, const int level)
+{
+ using FreeFileSync::TraverseCallback;
+
+ if (level == 100) //catch endless recursion
+ {
+ switch (sink->onError(wxString(_("Endless loop when traversing directory:")) + wxT("\n\"") + directory + wxT("\"")))
+ {
+ case TraverseCallback::TRAVERSING_STOP:
+ return false;
+ case TraverseCallback::TRAVERSING_CONTINUE:
+ return true;
+ }
+ }
+
+#ifdef FFS_WIN
+ //ensure directoryFormatted ends with backslash
+ const Zstring directoryFormatted = directory.EndsWith(globalFunctions::FILE_NAME_SEPARATOR) ?
+ directory :
+ directory + globalFunctions::FILE_NAME_SEPARATOR;
+
+ WIN32_FIND_DATA fileMetaData;
+ HANDLE searchHandle = FindFirstFile((directoryFormatted + DefaultChar('*')).c_str(), //pointer to name of file to search for
+ &fileMetaData); //pointer to returned information
+
+ if (searchHandle == INVALID_HANDLE_VALUE)
+ {
+ const DWORD lastError = ::GetLastError();
+ if (lastError == ERROR_FILE_NOT_FOUND)
+ return true;
+
+ //else: we have a problem... report it:
+ const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + directory.c_str() + wxT("\"") ;
+ switch (sink->onError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)))
+ {
+ case TraverseCallback::TRAVERSING_STOP:
+ return false;
+ case TraverseCallback::TRAVERSING_CONTINUE:
+ return true;
+ }
+ }
+ CloseFindHandleOnExit dummy(searchHandle);
+
+ do
+ { //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;
+
+ const Zstring fullName = directoryFormatted + shortName;
+
+ if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... (for directory symlinks this flag is set too!)
+ {
+ const TraverseCallback::ReturnValDir rv = sink->onDir(shortName, fullName);
+ switch (rv.returnCode)
+ {
+ case TraverseCallback::ReturnValDir::TRAVERSING_STOP:
+ return false;
+
+ case TraverseCallback::ReturnValDir::TRAVERSING_IGNORE_DIR:
+ break;
+
+ case TraverseCallback::ReturnValDir::TRAVERSING_CONTINUE:
+ //traverse into symbolic links, junctions, etc. if requested only:
+ if (traverseDirectorySymlinks || (~fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
+ if (!traverseDirectory<traverseDirectorySymlinks>(fullName, rv.subDirCb, level + 1))
+ return false;
+ break;
+ }
+ }
+ else //a file...
+ {
+ TraverseCallback::FileInfo details;
+
+ if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) //dereference symlinks!
+ {
+ if (!setWin32FileInformationFromSymlink(fullName, details)) //broken symlink
+ {
+ details.lastWriteTimeRaw = 0; //we are not interested in the modifiation time of the link
+ details.fileSize = 0;
+ }
+ }
+ else
+ setWin32FileInformation(fileMetaData.ftLastWriteTime, fileMetaData.nFileSizeHigh, fileMetaData.nFileSizeLow, details);
+
+ switch (sink->onFile(shortName, fullName, details))
+ {
+ case TraverseCallback::TRAVERSING_STOP:
+ return false;
+ case TraverseCallback::TRAVERSING_CONTINUE:
+ break;
+ }
+ }
+ }
+ while (FindNextFile(searchHandle, // handle to search
+ &fileMetaData)); // pointer to structure for data on found file
+
+ const DWORD lastError = ::GetLastError();
+ if (lastError == ERROR_NO_MORE_FILES)
+ return true; //everything okay
+
+ //else: we have a problem... report it:
+ const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + directory.c_str() + wxT("\"") ;
+ switch (sink->onError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)))
+ {
+ case TraverseCallback::TRAVERSING_STOP:
+ return false;
+ case TraverseCallback::TRAVERSING_CONTINUE:
+ return true;
+ }
+
+#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\"") + directory.c_str() + wxT("\"") ;
+ switch (sink->onError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()))
+ {
+ case TraverseCallback::TRAVERSING_STOP:
+ return false;
+ case TraverseCallback::TRAVERSING_CONTINUE:
+ return true;
+ }
+ }
+ CloseDirOnExit dummy(dirObj);
+
+ 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)
+ {
+ if (errno == 0)
+ return true; //everything okay
+
+ //else: we have a problem... report it:
+ const wxString errorMessage = wxString(_("Error traversing directory:")) + wxT("\n\"") + directory.c_str() + wxT("\"") ;
+ switch (sink->onError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()))
+ {
+ case TraverseCallback::TRAVERSING_STOP:
+ return false;
+ case TraverseCallback::TRAVERSING_CONTINUE:
+ return true;
+ }
+ }
+
+ //don't return "." and ".."
+ const wxChar* const shortName = dirEntry->d_name;
+ if ( shortName[0] == wxChar('.') &&
+ ((shortName[1] == wxChar('.') && shortName[2] == wxChar('\0')) ||
+ shortName[1] == wxChar('\0')))
+ continue;
+
+ const Zstring fullName = directory.EndsWith(globalFunctions::FILE_NAME_SEPARATOR) ? //e.g. "/"
+ directory + shortName :
+ directory + globalFunctions::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\"") + fullName.c_str() + wxT("\"");
+ switch (sink->onError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()))
+ {
+ case TraverseCallback::TRAVERSING_STOP:
+ return false;
+ case TraverseCallback::TRAVERSING_CONTINUE:
+ break;
+ }
+ continue;
+ }
+
+ const bool isSymbolicLink = S_ISLNK(fileInfo.st_mode);
+ if (isSymbolicLink) //dereference symbolic links
+ {
+ 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;
+
+ switch (sink->onFile(shortName, fullName, details))
+ {
+ case TraverseCallback::TRAVERSING_STOP:
+ return false;
+ case TraverseCallback::TRAVERSING_CONTINUE:
+ break;
+ }
+ continue;
+ }
+ }
+
+
+ if (S_ISDIR(fileInfo.st_mode)) //a directory... (note: symbolic links need to be dereferenced to test if they point to a directory!)
+ {
+ const TraverseCallback::ReturnValDir rv = sink->onDir(shortName, fullName);
+ switch (rv.returnCode)
+ {
+ case TraverseCallback::ReturnValDir::TRAVERSING_STOP:
+ return false;
+
+ case TraverseCallback::ReturnValDir::TRAVERSING_IGNORE_DIR:
+ break;
+
+ case TraverseCallback::ReturnValDir::TRAVERSING_CONTINUE:
+ //traverse into symbolic links, junctions, etc. if requested only:
+ if (traverseDirectorySymlinks || !isSymbolicLink) //traverse into symbolic links if requested only
+ if (!traverseDirectory<traverseDirectorySymlinks>(fullName, rv.subDirCb, level + 1))
+ return false;
+ break;
+ }
+ }
+ else //a file...
+ {
+ TraverseCallback::FileInfo details;
+ details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second
+ details.fileSize = fileInfo.st_size;
+
+ switch (sink->onFile(shortName, fullName, details))
+ {
+ case TraverseCallback::TRAVERSING_STOP:
+ return false;
+ case TraverseCallback::TRAVERSING_CONTINUE:
+ break;
+ }
+ }
+ }
+#endif
+
+ return true; //dummy value
+}
+
+
+void FreeFileSync::traverseFolder(const Zstring& directory,
+ const bool traverseDirectorySymlinks,
+ TraverseCallback* sink)
+{
+#ifdef FFS_WIN
+ const Zstring& directoryFormatted = directory;
+#elif defined FFS_LINUX
+ const Zstring directoryFormatted = //remove trailing slash
+ directory.size() > 1 && directory.EndsWith(globalFunctions::FILE_NAME_SEPARATOR) ? //exception: allow '/'
+ directory.BeforeLast(globalFunctions::FILE_NAME_SEPARATOR) :
+ directory;
+#endif
+
+ if (traverseDirectorySymlinks)
+ traverseDirectory<true>(directoryFormatted, sink, 0);
+ else
+ traverseDirectory<false>(directoryFormatted, sink, 0);
+}
bgstack15