summaryrefslogtreecommitdiff
path: root/library/fileHandling.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 16:57:03 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 16:57:03 +0200
commit420fb6c9b3427f65cfe24411944ee46b58cfcfb4 (patch)
tree58269ba5ee7a22d2df004f03b100cc234b8c3f14 /library/fileHandling.cpp
parent1.16 (diff)
downloadFreeFileSync-420fb6c9b3427f65cfe24411944ee46b58cfcfb4.tar.gz
FreeFileSync-420fb6c9b3427f65cfe24411944ee46b58cfcfb4.tar.bz2
FreeFileSync-420fb6c9b3427f65cfe24411944ee46b58cfcfb4.zip
1.17
Diffstat (limited to 'library/fileHandling.cpp')
-rw-r--r--library/fileHandling.cpp802
1 files changed, 623 insertions, 179 deletions
diff --git a/library/fileHandling.cpp b/library/fileHandling.cpp
index cda81ef5..0dccdec7 100644
--- a/library/fileHandling.cpp
+++ b/library/fileHandling.cpp
@@ -6,7 +6,16 @@
#ifdef FFS_WIN
#include <wx/msw/wrapwin.h> //includes "windows.h"
-#endif // FFS_WIN
+
+#elif defined FFS_LINUX
+#include <sys/stat.h>
+#include <time.h>
+#include <utime.h>
+#include <fstream>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#endif
class RecycleBin
@@ -40,7 +49,7 @@ public:
fileOp.wFunc = FO_DELETE;
fileOp.pFrom = filenameDoubleNull.c_str();
fileOp.pTo = NULL;
- fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT;
+ fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI;
fileOp.fAnyOperationsAborted = false;
fileOp.hNameMappings = NULL;
fileOp.lpszProgressTitle = NULL;
@@ -75,10 +84,35 @@ bool moveToRecycleBin(const Zstring& filename) throw(RuntimeException)
}
-inline
+bool FreeFileSync::fileExists(const Zstring& filename)
+{ //symbolic links (broken or not) are also treated as existing files!
+#ifdef FFS_WIN
+ // we must use GetFileAttributes() instead of the ANSI C functions because
+ // it can cope with network (UNC) paths unlike them
+ const DWORD ret = ::GetFileAttributes(filename.c_str());
+
+ return (ret != INVALID_FILE_ATTRIBUTES) && !(ret & FILE_ATTRIBUTE_DIRECTORY);
+
+#elif defined FFS_LINUX
+ struct stat fileInfo;
+ return (lstat(filename.c_str(), &fileInfo) == 0 &&
+ (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)));
+#endif
+}
+
+
void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin)
{
- if (!wxFileExists(filename.c_str())) return; //this is NOT an error situation: the manual deletion relies on it!
+ //no error situation if file is not existing! manual deletion relies on it!
+#ifdef FFS_WIN
+ if (GetFileAttributes(filename.c_str()) == INVALID_FILE_ATTRIBUTES)
+ return; //neither file nor any other object with that name existing
+
+#elif defined FFS_LINUX
+ struct stat fileInfo;
+ if (lstat(filename.c_str(), &fileInfo) != 0)
+ return; //neither file nor any other object (e.g. broken symlink) with that name existing
+#endif
if (useRecycleBin)
{
@@ -103,61 +137,218 @@ void FreeFileSync::removeFile(const Zstring& filename, const bool useRecycleBin)
Zstring errorMessage = Zstring(_("Error deleting file:")) + wxT("\n\"") + filename + wxT("\"");
throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
}
-#else
- if (!wxRemoveFile(filename.c_str()))
- throw FileError(Zstring(_("Error deleting file:")) + wxT("\n\"") + filename + wxT("\""));
+#elif defined FFS_LINUX
+ if (unlink(filename.c_str()) != 0)
+ {
+ Zstring errorMessage = Zstring(_("Error deleting file:")) + wxT("\n\"") + filename + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
#endif
}
-void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecycleBin)
+class FilesDirsOnlyTraverser : public FreeFileSync::FullDetailFileTraverser
{
- if (!wxDirExists(directory)) return; //this is NOT an error situation: the manual deletion relies on it!
+public:
+ FilesDirsOnlyTraverser(std::vector<Zstring>& files, std::vector<Zstring>& dirs) :
+ m_files(files),
+ m_dirs(dirs) {}
- if (useRecycleBin)
+ virtual wxDirTraverseResult OnFile(const Zstring& filename, const FreeFileSync::FileInfo& details)
{
- if (!moveToRecycleBin(directory))
- throw FileError(Zstring(_("Error moving to Recycle Bin:")) + wxT("\n\"") + directory + wxT("\""));
- return;
+ m_files.push_back(filename);
+ return wxDIR_CONTINUE;
+ }
+ virtual wxDirTraverseResult OnDir(const Zstring& dirname)
+ {
+ m_dirs.push_back(dirname);
+ return wxDIR_IGNORE; //DON'T traverse into subdirs, removeDirectory works recursively!
+ }
+ virtual wxDirTraverseResult OnError(const Zstring& errorText)
+ {
+ throw FileError(errorText);
}
- std::vector<Zstring> fileList;
- std::vector<Zstring> dirList;
+private:
+ std::vector<Zstring>& m_files;
+ std::vector<Zstring>& m_dirs;
+};
- getAllFilesAndDirs(directory, fileList, dirList);
- for (unsigned int j = 0; j < fileList.size(); ++j)
- removeFile(fileList[j], false);
+void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecycleBin)
+{
+ //no error situation if directory is not existing! manual deletion relies on it!
+#ifdef FFS_WIN
+ const DWORD dirAttr = GetFileAttributes(directory.c_str()); //name of a file or directory
+ if (dirAttr == INVALID_FILE_ATTRIBUTES)
+ return; //neither directory nor any other object with that name existing
- dirList.insert(dirList.begin(), directory); //parent directory will be deleted last
+#elif defined FFS_LINUX
+ struct stat dirInfo;
+ if (lstat(directory.c_str(), &dirInfo) != 0)
+ return; //neither directory nor any other object (e.g. broken symlink) with that name existing
+#endif
- for (int j = int(dirList.size()) - 1; j >= 0 ; --j)
+ if (useRecycleBin)
{
+ if (!moveToRecycleBin(directory))
+ throw FileError(Zstring(_("Error moving to Recycle Bin:")) + wxT("\n\"") + directory + wxT("\""));
+ return;
+ }
+
+//attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!!
#ifdef FFS_WIN
- //initialize file attributes
- if (!SetFileAttributes(
- dirList[j].c_str(), // address of directory name
- FILE_ATTRIBUTE_NORMAL)) // attributes to set
+ if (dirAttr & FILE_ATTRIBUTE_REPARSE_POINT)
+ { //remove symlink directly, support for \\?\-prefix
+ if (RemoveDirectory(directory.c_str()) == 0)
{
- Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + dirList[j] + wxT("\"");
+ Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\"");
throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
}
+ return;
+ }
- //remove directory, support for \\?\-prefix
- if (RemoveDirectory(dirList[j].c_str()) == 0)
+#elif defined FFS_LINUX
+ if (S_ISLNK(dirInfo.st_mode))
+ {
+ if (unlink(directory.c_str()) != 0)
{
- Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + dirList[j] + wxT("\"");
+ Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\"");
throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
}
+ return;
+ }
+#endif
+
+ std::vector<Zstring> fileList;
+ std::vector<Zstring> dirList;
+
+ //get all files and directories from current directory (WITHOUT subdirectories!)
+ FilesDirsOnlyTraverser traverser(fileList, dirList);
+ FreeFileSync::traverseInDetail(directory, false, &traverser); //don't traverse into symlinks to directories
+
+ //delete files
+ for (std::vector<Zstring>::const_iterator j = fileList.begin(); j != fileList.end(); ++j)
+ FreeFileSync::removeFile(*j, false);
+
+ //delete directories recursively
+ for (std::vector<Zstring>::const_iterator j = dirList.begin(); j != dirList.end(); ++j)
+ FreeFileSync::removeDirectory(*j, false); //call recursively to correctly handle symbolic links
+
+ //parent directory is deleted last
+#ifdef FFS_WIN
+ //initialize file attributes
+ if (!SetFileAttributes(
+ directory.c_str(), // address of directory name
+ FILE_ATTRIBUTE_NORMAL)) // attributes to set
+ {
+ Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
+
+ //remove directory, support for \\?\-prefix
+ if (!RemoveDirectory(directory.c_str()))
+ {
+ Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
#else
- if (!wxRmdir(dirList[j].c_str()))
- throw FileError(Zstring(_("Error deleting directory:")) + wxT("\n\"") + dirList[j] + wxT("\""));
+ if (rmdir(directory.c_str()) != 0)
+ {
+ Zstring errorMessage = Zstring(_("Error deleting directory:")) + wxT("\n\"") + directory + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
#endif
+}
+
+
+#ifdef FFS_WIN
+class CloseHandleOnExit
+{
+public:
+ CloseHandleOnExit(HANDLE searchHandle) : m_searchHandle(searchHandle) {}
+
+ ~CloseHandleOnExit()
+ {
+ FindClose(m_searchHandle);
}
+
+private:
+ HANDLE m_searchHandle;
+};
+
+
+typedef DWORD WINAPI (*GetFinalPath)(
+ HANDLE hFile,
+ LPTSTR lpszFilePath,
+ DWORD cchFilePath,
+ DWORD dwFlags);
+
+
+class DllHandler //dynamically load windows API functions
+{
+public:
+ DllHandler() :
+ getFinalPathNameByHandle(NULL),
+ hKernel(NULL)
+ {
+ //get a handle to the DLL module containing required functionality
+ hKernel = ::LoadLibrary(wxT("kernel32.dll"));
+ if (hKernel)
+ getFinalPathNameByHandle = (GetFinalPath)(::GetProcAddress(hKernel, "GetFinalPathNameByHandleW")); //load unicode version!
+ }
+
+ ~DllHandler()
+ {
+ if (hKernel) ::FreeLibrary(hKernel);
+ }
+
+ GetFinalPath getFinalPathNameByHandle;
+
+private:
+ HINSTANCE hKernel;
+};
+
+//global instance
+DllHandler dynamicWinApi;
+
+
+Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target path of symbolic link to a directory
+{
+ //open handle to target of symbolic link
+ HANDLE hDir = CreateFile(dirLinkName.c_str(),
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ if (hDir == INVALID_HANDLE_VALUE)
+ return Zstring();
+
+ CloseHandleOnExit dummy(hDir);
+
+ if (dynamicWinApi.getFinalPathNameByHandle == NULL )
+ throw FileError(Zstring(_("Error loading library function:")) + wxT("\n\"") + wxT("GetFinalPathNameByHandleW") + wxT("\""));
+
+ const unsigned BUFFER_SIZE = 10000;
+ TCHAR targetPath[BUFFER_SIZE];
+
+ const DWORD rv = dynamicWinApi.getFinalPathNameByHandle(
+ hDir,
+ targetPath,
+ BUFFER_SIZE,
+ 0);
+
+ if (rv >= BUFFER_SIZE || rv == 0)
+ return Zstring();
+
+ return targetPath;
}
+#endif
-void FreeFileSync::createDirectory(const Zstring& directory, const int level)
+void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, const bool copyDirectorySymLinks, const int level)
{
if (wxDirExists(directory.c_str()))
return;
@@ -165,193 +356,355 @@ void FreeFileSync::createDirectory(const Zstring& directory, const int level)
if (level == 50) //catch endless recursion
return;
- //try to create directory, support for \\?\-prefix
-#ifdef FFS_WIN
- if (CreateDirectory(
- directory.c_str(), // pointer to a directory path string
- NULL // pointer to a security descriptor
- ) != 0)
- return;
-#else
- if (wxMkdir(directory.c_str())) //wxMkDir has different signature under Linux!
- return;
-#endif
-
- //if not successfull try to create parent folders first
- Zstring parentDir;
- if (endsWithPathSeparator(directory)) //may be valid on first level of recursion at most! Usually never true!
+ //try to create parent folders first
+ const Zstring dirParent = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR);
+ if (!dirParent.empty() && !wxDirExists(dirParent))
{
- parentDir = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR);
- parentDir = parentDir.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR);
+ //call function recursively
+ const Zstring templateParent = templateDir.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR);
+ createDirectoryRecursively(dirParent, templateParent, false, level + 1); //don't create symbolic links in recursion!
}
- else
- parentDir = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR);
-
- if (parentDir.empty()) return;
-
- //call function recursively
- createDirectory(parentDir, level + 1);
//now creation should be possible
#ifdef FFS_WIN
- if (CreateDirectory(
- directory.c_str(), // pointer to a directory path string
- NULL // pointer to a security descriptor
- ) == 0)
+ const DWORD templateAttr = ::GetFileAttributes(templateDir.c_str()); //replaces wxDirExists(): also returns successful for broken symlinks
+ if (templateAttr == INVALID_FILE_ATTRIBUTES) //fallback
{
- if (level == 0)
+ if (CreateDirectory(
+ directory.c_str(), // pointer to a directory path string
+ NULL) == 0 && level == 0)
{
- Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"");
+ const Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"");
throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
}
}
-#else
- if (!wxMkdir(directory.c_str())) //wxMkDir has different signature under Linux!
+ else
{
- if (level == 0)
- throw FileError(Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""));
+ //symbolic link handling
+ if (!copyDirectorySymLinks && templateAttr & FILE_ATTRIBUTE_REPARSE_POINT) //create directory based on target of symbolic link
+ {
+ //get target directory of symbolic link
+ const Zstring targetPath = resolveDirectorySymlink(templateDir);
+ if (targetPath.empty())
+ {
+ if (level == 0)
+ throw FileError(Zstring(_("Error resolving symbolic link:")) + wxT("\n\"") + templateDir + wxT("\""));
+ }
+ else
+ {
+ if (CreateDirectoryEx( // this function automatically copies symbolic links if encountered
+ targetPath.c_str(), // pointer to path string of template directory
+ directory.c_str(), // pointer to a directory path string
+ NULL) == 0 && level == 0)
+ {
+ const Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
+ }
+ }
+ else //in all other cases
+ {
+ if (CreateDirectoryEx( // this function automatically copies symbolic links if encountered
+ templateDir.c_str(), // pointer to path string of template directory
+ directory.c_str(), // pointer to a directory path string
+ NULL) == 0 && level == 0)
+ {
+ const Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
+ }
}
-#endif
-}
-
-
-void FreeFileSync::copyFolderAttributes(const Zstring& source, const Zstring& target)
-{
-#ifdef FFS_WIN
- DWORD attr = GetFileAttributes(source.c_str()); // address of the name of a file or directory
- if (attr == 0xFFFFFFFF)
+#elif defined FFS_LINUX
+ //symbolic link handling
+ if (copyDirectorySymLinks)
{
- Zstring errorMessage = Zstring(_("Error reading folder attributes:")) + wxT("\n\"") + source + wxT("\"");
- throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ //test if templateDir is a symbolic link
+ struct stat linkInfo;
+ if (lstat(templateDir.c_str(), &linkInfo) == 0 && S_ISLNK(linkInfo.st_mode))
+ {
+ //copy symbolic link
+ const int BUFFER_SIZE = 10000;
+ char buffer[BUFFER_SIZE];
+ const int bytesWritten = readlink(templateDir.c_str(), buffer, BUFFER_SIZE);
+ if (bytesWritten < 0 || bytesWritten == BUFFER_SIZE)
+ {
+ Zstring errorMessage = Zstring(_("Error resolving symbolic link:")) + wxT("\n\"") + templateDir + wxT("\"");
+ if (bytesWritten < 0) errorMessage += Zstring(wxT("\n\n")) + FreeFileSync::getLastErrorFormatted();
+ throw FileError(errorMessage);
+ }
+ //set null-terminating char
+ buffer[bytesWritten] = 0;
+
+ if (symlink(buffer, directory.c_str()) != 0)
+ {
+ Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
+ return; //symlink created successfully
+ }
}
- if (!SetFileAttributes(
- target.c_str(), // address of filename
- attr)) // address of attributes to set
+ //default directory creation
+ if (mkdir(directory.c_str(), 0755) != 0 && level == 0)
{
- Zstring errorMessage = Zstring(_("Error writing folder attributes:")) + wxT("\n\"") + target + wxT("\"");
+ Zstring errorMessage = Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\"");
throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
}
-#elif defined FFS_LINUX
-//Linux doesn't use attributes for files or folders
+
+//copy directory permissions: not sure if this is a good idea: if a directory is read-only copying/sync'ing of files will fail...
+ /*
+ if (templateDirExists)
+ {
+ struct stat fileInfo;
+ if (stat(templateDir.c_str(), &fileInfo) != 0) //read permissions from template directory
+ throw FileError(Zstring(_("Error reading file attributes:")) + wxT("\n\"") + templateDir + wxT("\""));
+
+ // reset the umask as we want to create the directory with exactly the same permissions as the template
+ wxCHANGE_UMASK(0);
+
+ if (mkdir(directory.c_str(), fileInfo.st_mode) != 0 && level == 0)
+ throw FileError(Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""));
+ }
+ else
+ {
+ if (mkdir(directory.c_str(), 0777) != 0 && level == 0)
+ throw FileError(Zstring(_("Error creating directory:")) + wxT("\n\"") + directory + wxT("\""));
+ }
+ */
#endif
}
-class FilesDirsOnlyTraverser : public FreeFileSync::FullDetailFileTraverser
+void FreeFileSync::createDirectory(const Zstring& directory, const Zstring& templateDir, const bool copyDirectorySymLinks)
{
-public:
- FilesDirsOnlyTraverser(std::vector<Zstring>& files, std::vector<Zstring>& dirs) :
- m_files(files),
- m_dirs(dirs) {}
+ //remove trailing separator
+ Zstring dirFormatted;
+ if (FreeFileSync::endsWithPathSeparator(directory))
+ dirFormatted = directory.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR);
+ else
+ dirFormatted = directory;
- virtual wxDirTraverseResult OnFile(const Zstring& filename, const FreeFileSync::FileInfo& details)
- {
- m_files.push_back(filename);
- return wxDIR_CONTINUE;
- }
- virtual wxDirTraverseResult OnDir(const Zstring& dirname)
+ Zstring templateFormatted;
+ if (FreeFileSync::endsWithPathSeparator(templateDir))
+ templateFormatted = templateDir.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR);
+ else
+ templateFormatted = templateDir;
+
+ createDirectoryRecursively(dirFormatted, templateFormatted, copyDirectorySymLinks, 0);
+}
+
+
+#ifdef FFS_LINUX
+struct MemoryAllocator
+{
+ MemoryAllocator()
{
- m_dirs.push_back(dirname);
- return wxDIR_CONTINUE;
+ buffer = new char[bufferSize];
}
- virtual wxDirTraverseResult OnError(const Zstring& errorText)
+
+ ~MemoryAllocator()
{
- throw FileError(errorText);
+ delete [] buffer;
}
-private:
- std::vector<Zstring>& m_files;
- std::vector<Zstring>& m_dirs;
+ static const unsigned int bufferSize = 512 * 1024;
+ char* buffer;
};
-void FreeFileSync::getAllFilesAndDirs(const Zstring& sourceDir, std::vector<Zstring>& files, std::vector<Zstring>& directories) throw(FileError)
+void FreeFileSync::copyFile(const Zstring& sourceFile,
+ const Zstring& targetFile,
+ const bool copyFileSymLinks,
+ CopyFileCallback callback,
+ void* data)
{
- files.clear();
- directories.clear();
+ try
+ {
+ if (FreeFileSync::fileExists(targetFile.c_str()))
+ throw FileError(Zstring(_("Error copying file:")) + wxT("\n\"") + sourceFile + wxT("\" -> \"") + targetFile + wxT("\"\n")
+ + _("Target file already existing!"));
- //get all files and directories from current directory (and subdirectories)
- FilesDirsOnlyTraverser traverser(files, directories);
- traverseInDetail(sourceDir, false, &traverser);
-}
+ //symbolic link handling
+ if (copyFileSymLinks)
+ {
+ //test if sourceFile is a symbolic link
+ struct stat linkInfo;
+ if (lstat(sourceFile.c_str(), &linkInfo) != 0)
+ {
+ Zstring errorMessage = Zstring(_("Error reading file attributes:")) + wxT("\n\"") + sourceFile + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
+ if (S_ISLNK(linkInfo.st_mode))
+ {
+ //copy symbolic link
+ const unsigned BUFFER_SIZE = 10000;
+ char buffer[BUFFER_SIZE];
+ const int bytesWritten = readlink(sourceFile.c_str(), buffer, BUFFER_SIZE - 1);
+ if (bytesWritten < 0)
+ {
+ Zstring errorMessage = Zstring(_("Error resolving symbolic link:")) + wxT("\n\"") + sourceFile + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
+ //set null-terminating char
+ buffer[bytesWritten] = 0;
-#ifdef FFS_WIN
-class CloseOnExit
-{
-public:
- CloseOnExit(HANDLE searchHandle) : m_searchHandle(searchHandle) {}
+ if (symlink(buffer, targetFile.c_str()) != 0)
+ {
+ Zstring errorMessage = Zstring(_("Error writing file:")) + wxT("\n\"") + targetFile + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
- ~CloseOnExit()
- {
- FindClose(m_searchHandle);
+ return; //symlink created successfully
+ }
+ }
+
+ //begin of regular file copy
+ struct stat fileInfo;
+ if (stat(sourceFile.c_str(), &fileInfo) != 0) //read file attributes from source file (resolve symlinks)
+ {
+ Zstring errorMessage = Zstring(_("Error reading file attributes:")) + wxT("\n\"") + sourceFile + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
+
+ //open sourceFile for reading
+ std::ifstream fileIn(sourceFile.c_str(), std::ios_base::binary);
+ if (fileIn.fail())
+ throw FileError(Zstring(_("Error opening file:")) + wxT("\n\"") + sourceFile + wxT("\""));
+
+ //create targetFile and open it for writing
+ std::ofstream fileOut(targetFile.c_str(), std::ios_base::binary);
+ if (fileOut.fail())
+ throw FileError(Zstring(_("Error opening file:")) + wxT("\n\"") + targetFile + wxT("\""));
+
+ //copy contents of sourceFile to targetFile
+ wxULongLong totalBytesTransferred;
+ static MemoryAllocator memory;
+ while (true)
+ {
+ fileIn.read(memory.buffer, memory.bufferSize);
+ if (fileIn.eof()) //end of file? fail bit is set in this case also!
+ {
+ fileOut.write(memory.buffer, fileIn.gcount());
+ if (fileOut.bad())
+ throw FileError(Zstring(_("Error writing file:")) + wxT("\n\"") + targetFile + wxT("\""));
+ break;
+ }
+ else if (fileIn.fail())
+ throw FileError(Zstring(_("Error reading file:")) + wxT("\n\"") + sourceFile + wxT("\""));
+
+
+ fileOut.write(memory.buffer, memory.bufferSize);
+ if (fileOut.bad())
+ throw FileError(Zstring(_("Error writing file:")) + wxT("\n\"") + targetFile + wxT("\""));
+
+ totalBytesTransferred += memory.bufferSize;
+
+ //invoke callback method to update progress indicators
+ if (callback)
+ callback(totalBytesTransferred, data);
+ }
+
+ //close streams before changing attributes
+ fileIn.close();
+ fileOut.close();
+
+ //adapt file modification time:
+ struct utimbuf newTimes;
+ time(&newTimes.actime); //set file access time to current time
+ newTimes.modtime = fileInfo.st_mtime;
+ if (utime(targetFile.c_str(), &newTimes) != 0)
+ {
+ Zstring errorMessage = Zstring(_("Error changing modification time:")) + wxT("\n\"") + targetFile + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
+
+ //set file access rights
+ if (chmod(targetFile.c_str(), fileInfo.st_mode) != 0)
+ {
+ Zstring errorMessage = Zstring(_("Error writing file attributes:")) + wxT("\n\"") + targetFile + wxT("\"");
+ throw FileError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted());
+ }
}
+ catch (...)
+ { //try to delete target file if error occured, or exception was thrown in callback function
+ if (FreeFileSync::fileExists(targetFile.c_str()))
+ wxRemoveFile(targetFile.c_str()); //don't handle error situations!
-private:
- HANDLE m_searchHandle;
-};
+ throw;
+ }
+}
+#endif
+#ifdef FFS_WIN
inline
-void getWin32FileInformation(const WIN32_FIND_DATA& input, FreeFileSync::FileInfo& output)
+void setWin32FileInformation(const FILETIME& lastWriteTime, const DWORD fileSizeHigh, const DWORD fileSizeLow, FreeFileSync::FileInfo& output)
{
//convert UTC FILETIME to ANSI C format (number of seconds since Jan. 1st 1970 UTC)
- wxULongLong writeTimeLong(input.ftLastWriteTime.dwHighDateTime, input.ftLastWriteTime.dwLowDateTime);
+ wxLongLong writeTimeLong(lastWriteTime.dwHighDateTime, lastWriteTime.dwLowDateTime);
writeTimeLong /= 10000000; //reduce precision to 1 second (FILETIME has unit 10^-7 s)
- writeTimeLong -= wxULongLong(2, 3054539008UL); //timeshift between ansi C time and FILETIME in seconds == 11644473600s
- assert(writeTimeLong.GetHi() == 0); //it should fit into a 32bit variable now
- output.lastWriteTimeRaw = writeTimeLong.GetLo();
+ writeTimeLong -= wxLongLong(2, 3054539008UL); //timeshift between ansi C time and FILETIME in seconds == 11644473600s
+ output.lastWriteTimeRaw = writeTimeLong;
- output.fileSize = wxULongLong(input.nFileSizeHigh, input.nFileSizeLow);
+ output.fileSize = wxULongLong(fileSizeHigh, fileSizeLow);
}
-#elif defined FFS_LINUX
-class EnhancedFileTraverser : public wxDirTraverser
+inline
+bool setWin32FileInformationFromSymlink(const Zstring linkName, FreeFileSync::FileInfo& output)
{
-public:
- EnhancedFileTraverser(FreeFileSync::FullDetailFileTraverser* sink) : m_sink(sink) {}
+ //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;
- wxDirTraverseResult OnFile(const wxString& filename) //virtual impl.
- {
- struct stat fileInfo;
- if (stat(filename.c_str(), &fileInfo) != 0)
- return m_sink->OnError(Zstring(_("Could not retrieve file info for:")) + wxT("\n\"") + filename.c_str() + wxT("\""));
+ CloseHandleOnExit dummy(hFile);
- FreeFileSync::FileInfo details;
- details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second
- details.fileSize = fileInfo.st_size;
+ BY_HANDLE_FILE_INFORMATION fileInfoByHandle;
- return m_sink->OnFile(filename.c_str(), details);
- }
+ if (!GetFileInformationByHandle(
+ hFile,
+ &fileInfoByHandle))
+ return false;
- wxDirTraverseResult OnDir(const wxString& dirname) //virtual impl.
- {
- return m_sink->OnDir(dirname.c_str());
- }
+ //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) {}
- wxDirTraverseResult OnOpenError(const wxString& errorText) //virtual impl.
+ ~CloseDirOnExit()
{
- return m_sink->OnError(errorText.c_str());
+ closedir(m_dir); //no error handling here
}
private:
- FreeFileSync::FullDetailFileTraverser* m_sink;
+ DIR* m_dir;
};
#endif
+template <bool traverseDirectorySymlinks>
class TraverseRecursively
{
public:
- TraverseRecursively(const bool traverseSymbolicLinks, FreeFileSync::FullDetailFileTraverser* sink) :
- m_traverseSymbolicLinks(traverseSymbolicLinks),
- m_sink(sink) {}
+ TraverseRecursively(FreeFileSync::FullDetailFileTraverser* sink) : m_sink(sink) {}
bool traverse(const Zstring& directory, const int level)
{
-#ifdef FFS_WIN
- if (level == 50) //catch endless recursion
+ if (level == 100) //catch endless recursion
{
if (m_sink->OnError(Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"")) == wxDIR_STOP)
return false;
@@ -359,6 +712,7 @@ public:
return true;
}
+#ifdef FFS_WIN
Zstring directoryFormatted = directory;
if (!FreeFileSync::endsWithPathSeparator(directoryFormatted))
directoryFormatted += GlobalResources::FILE_NAME_SEPARATOR;
@@ -376,13 +730,13 @@ public:
return true;
//else: we have a problem... report it:
- Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\" ") ;
+ Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"") ;
if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)) == wxDIR_STOP)
return false;
else
return true;
}
- CloseOnExit dummy(searchHandle);
+ CloseHandleOnExit dummy(searchHandle);
do
{ //don't return "." and ".."
@@ -394,7 +748,7 @@ public:
const Zstring fullName = directoryFormatted + name;
- if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory...
+ if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... (for directory symlinks this flag is set too!)
{
switch (m_sink->OnDir(fullName))
{
@@ -402,7 +756,7 @@ public:
break;
case wxDIR_CONTINUE:
//traverse into symbolic links, junctions, etc. if requested only:
- if (m_traverseSymbolicLinks || (~fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
+ if (traverseDirectorySymlinks || (~fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
if (!this->traverse(fullName, level + 1))
return false;
break;
@@ -410,25 +764,25 @@ public:
return false;
default:
assert(false);
- break;
}
}
else //a file...
{
FreeFileSync::FileInfo details;
- getWin32FileInformation(fileMetaData, details);
- switch (m_sink->OnFile(fullName, details))
+ if (fileMetaData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) //dereference symlinks!
{
- case wxDIR_IGNORE:
- case wxDIR_CONTINUE:
- break;
- case wxDIR_STOP:
- return false;
- default:
- assert(false);
- break;
+ 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);
+
+ if (m_sink->OnFile(fullName, details) == wxDIR_STOP)
+ return false;
}
}
while (FindNextFile(searchHandle, // handle to search
@@ -439,42 +793,131 @@ public:
return true; //everything okay
//else: we have a problem... report it:
- Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\" ") ;
+ Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"") ;
if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted(lastError)) == wxDIR_STOP)
return false;
else
return true;
+
#elif defined FFS_LINUX
+ Zstring directoryFormatted = directory;
+ if (FreeFileSync::endsWithPathSeparator(directoryFormatted))
+ directoryFormatted = directoryFormatted.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR);
- //use standard file traverser and enrich output with additional file information
- //could be improved with custom traversing algorithm for optimized performance
- EnhancedFileTraverser traverser(m_sink);
+ DIR* dirObj = opendir(directoryFormatted.c_str());
+ if (dirObj == NULL)
+ {
+ Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directoryFormatted + wxT("\"") ;
+ if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP)
+ return false;
+ else
+ return true;
+ }
+ CloseDirOnExit dummy(dirObj);
- wxDir dir(directory.c_str());
- if (dir.IsOpened())
- dir.Traverse(traverser);
+ struct dirent* dirEntry;
+ while (!(errno = 0) && (dirEntry = readdir(dirObj)) != NULL) //set errno to 0 as unfortunately this isn't done when readdir() returns NULL when it is finished
+ {
+ //don't return "." and ".."
+ const wxChar* name = dirEntry->d_name;
+ if ( name[0] == wxChar('.') &&
+ ((name[1] == wxChar('.') && name[2] == wxChar('\0')) ||
+ name[1] == wxChar('\0')))
+ continue;
- return true;
-#else
- adapt this
+ const Zstring fullName = directoryFormatted + GlobalResources::FILE_NAME_SEPARATOR + name;
+
+ struct stat fileInfo;
+ if (lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks
+ {
+ const Zstring errorMessage = Zstring(_("Error reading file attributes:")) + wxT("\n\"") + fullName + wxT("\"");
+ if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP)
+ return false;
+ 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
+ FreeFileSync::FileInfo details;
+ details.lastWriteTimeRaw = 0; //we are not interested in the modifiation time of the link
+ details.fileSize = 0;
+
+ if (m_sink->OnFile(fullName, details) == wxDIR_STOP)
+ return false;
+ continue;
+ }
+ }
+
+
+ if (S_ISDIR(fileInfo.st_mode)) //a directory... (note: symbolic links need to be dereferenced to test if they point to a directory!)
+ {
+ switch (m_sink->OnDir(fullName))
+ {
+ case wxDIR_IGNORE:
+ break;
+ case wxDIR_CONTINUE:
+ if (traverseDirectorySymlinks || !isSymbolicLink) //traverse into symbolic links if requested only
+ {
+ if (!this->traverse(fullName, level + 1))
+ return false;
+ }
+ break;
+ case wxDIR_STOP:
+ return false;
+ default:
+ assert(false);
+ }
+ }
+ else //a file...
+ {
+ FreeFileSync::FileInfo details;
+ details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second
+ details.fileSize = fileInfo.st_size;
+
+ if (m_sink->OnFile(fullName, details) == wxDIR_STOP)
+ return false;
+ }
+ }
+
+ if (errno == 0)
+ return true; //everything okay
+
+ //else: we have a problem... report it:
+ const Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directoryFormatted + wxT("\"") ;
+ if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP)
+ return false;
+ else
+ return true;
#endif
}
private:
- const bool m_traverseSymbolicLinks;
FreeFileSync::FullDetailFileTraverser* m_sink;
};
void FreeFileSync::traverseInDetail(const Zstring& directory,
- const bool traverseSymbolicLinks,
+ const bool traverseDirectorySymlinks,
FullDetailFileTraverser* sink)
{
- TraverseRecursively filewalker(traverseSymbolicLinks, sink);
- filewalker.traverse(directory, 0);
+ if (traverseDirectorySymlinks)
+ {
+ TraverseRecursively<true> filewalker(sink);
+ filewalker.traverse(directory, 0);
+ }
+ else
+ {
+ TraverseRecursively<false> filewalker(sink);
+ filewalker.traverse(directory, 0);
+ }
}
+/*
#ifdef FFS_WIN
inline
Zstring getDriveName(const Zstring& directoryName) //GetVolume() doesn't work under Linux!
@@ -500,3 +943,4 @@ bool FreeFileSync::isFatDrive(const Zstring& directoryName)
return Zstring(fileSystem).StartsWith(wxT("FAT"));
}
#endif //FFS_WIN
+*/
bgstack15