summaryrefslogtreecommitdiff
path: root/zen/file_access.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <shieldwed@outlook.com>2017-04-20 16:55:28 -0600
committerDaniel Wilhelm <shieldwed@outlook.com>2017-04-20 16:59:56 -0600
commit823740e1ffa2b3bd39f8dea8062f5c5a0d9c741b (patch)
tree63222010af3b90a36f29c1c9c360116973cdff38 /zen/file_access.cpp
parentadd .gitattributes for less line ending hassles (diff)
downloadFreeFileSync-823740e1ffa2b3bd39f8dea8062f5c5a0d9c741b.tar.gz
FreeFileSync-823740e1ffa2b3bd39f8dea8062f5c5a0d9c741b.tar.bz2
FreeFileSync-823740e1ffa2b3bd39f8dea8062f5c5a0d9c741b.zip
normalize most lineendings
Diffstat (limited to 'zen/file_access.cpp')
-rwxr-xr-xzen/file_access.cpp1352
1 files changed, 676 insertions, 676 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp
index 71d00386..ed66aac4 100755
--- a/zen/file_access.cpp
+++ b/zen/file_access.cpp
@@ -1,676 +1,676 @@
-// *****************************************************************************
-// * This file is part of the FreeFileSync project. It is distributed under *
-// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
-// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
-// *****************************************************************************
-
-#include "file_access.h"
-#include <map>
-#include <algorithm>
-#include <stdexcept>
-#include "file_traverser.h"
-#include "scope_guard.h"
-#include "symlink_target.h"
-#include "file_id_def.h"
-#include "file_io.h"
-
- #include <sys/vfs.h> //statfs
- #include <sys/time.h> //lutimes
- #ifdef HAVE_SELINUX
- #include <selinux/selinux.h>
- #endif
-
-
- #include <fcntl.h> //open, close, AT_SYMLINK_NOFOLLOW, UTIME_OMIT
- #include <sys/stat.h>
-
-using namespace zen;
-
-
-Opt<PathComponents> zen::parsePathComponents(const Zstring& itemPath)
-{
- if (startsWith(itemPath, "/"))
- {
- Zstring relPath(itemPath.c_str() + 1);
- if (endsWith(relPath, FILE_NAME_SEPARATOR))
- relPath.pop_back();
- return PathComponents({ "/", relPath });
- }
- //we do NOT support relative paths!
- return NoValue();
-}
-
-
-
-Opt<Zstring> zen::getParentFolderPath(const Zstring& itemPath)
-{
- if (const Opt<PathComponents> comp = parsePathComponents(itemPath))
- {
- if (comp->relPath.empty())
- return NoValue();
-
- const Zstring parentRelPath = beforeLast(comp->relPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
- if (parentRelPath.empty())
- return comp->rootPath;
- return appendSeparator(comp->rootPath) + parentRelPath;
- }
- assert(false);
- return NoValue();
-}
-
-
-ItemType zen::getItemType(const Zstring& itemPath) //throw FileError
-{
- struct ::stat itemInfo = {};
- if (::lstat(itemPath.c_str(), &itemInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
-
- if (S_ISLNK(itemInfo.st_mode))
- return ItemType::SYMLINK;
- if (S_ISDIR(itemInfo.st_mode))
- return ItemType::FOLDER;
- return ItemType::FILE; //S_ISREG || S_ISCHR || S_ISBLK || S_ISFIFO || S_ISSOCK
-}
-
-
-PathStatus zen::getPathStatus(const Zstring& itemPath) //throw FileError
-{
- const Opt<Zstring> parentPath = getParentFolderPath(itemPath);
- try
- {
- return { getItemType(itemPath), itemPath, {} }; //throw FileError
- }
- catch (FileError&)
- {
- if (!parentPath) //device root
- throw;
- //else: let's dig deeper... don't bother checking Win32 codes; e.g. not existing item may have the codes:
- // ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE,
- // ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH => not reliable
- }
- const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
- assert(!itemName.empty());
-
- PathStatus pd = getPathStatus(*parentPath); //throw FileError
- if (!pd.relPath.empty())
- {
- pd.relPath.push_back(itemName);
- return { pd.existingType, pd.existingPath, pd.relPath };
- }
-
- try
- {
- traverseFolder(*parentPath,
- [&](const FileInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FILE; },
- [&](const FolderInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FOLDER; },
- [&](const SymlinkInfo& si) { if (equalFilePath(si.itemName, itemName)) throw ItemType::SYMLINK; },
- [](const std::wstring& errorMsg) { throw FileError(errorMsg); });
-
- return { pd.existingType, *parentPath, { itemName } }; //throw FileError
- }
- catch (const ItemType& type) { return { type, itemPath, {} }; } //yes, exceptions for control-flow are bad design... but, but...
- //we're not CPU-bound here and finding the item after getItemType() previously failed is exceptional (even C:\pagefile.sys should be found)
-}
-
-
-Opt<ItemType> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError
-{
- const PathStatus pd = getPathStatus(itemPath); //throw FileError
- if (pd.relPath.empty())
- return pd.existingType;
- return NoValue();
-}
-
-
-bool zen::fileAvailable(const Zstring& filePath) //noexcept
-{
- //symbolic links (broken or not) are also treated as existing files!
- struct ::stat fileInfo = {};
- if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks!
- return S_ISREG(fileInfo.st_mode);
- return false;
-}
-
-
-bool zen::dirAvailable(const Zstring& dirPath) //noexcept
-{
- //symbolic links (broken or not) are also treated as existing directories!
- struct ::stat dirInfo = {};
- if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks!
- return S_ISDIR(dirInfo.st_mode);
- return false;
-}
-
-
-bool zen::itemNotExisting(const Zstring& itemPath)
-{
- try
- {
- return !getItemTypeIfExists(itemPath); //throw FileError
- }
- catch (FileError&) { return false; }
-}
-
-
-namespace
-{
-}
-
-
-uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError
-{
- struct ::stat fileInfo = {};
- if (::stat(filePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"stat");
-
- return fileInfo.st_size;
-}
-
-
-uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0 if not available
-{
- struct ::statfs info = {};
- if (::statfs(path.c_str(), &info) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), L"statfs");
-
- return static_cast<uint64_t>(info.f_bsize) * info.f_bavail;
-}
-
-
-VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError
-{
- struct ::stat fileInfo = {};
- if (::stat(itemPath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"stat");
-
- return fileInfo.st_dev;
-}
-
-
-Zstring zen::getTempFolderPath() //throw FileError
-{
- const char* buf = ::getenv("TMPDIR"); //no extended error reporting
- if (!buf)
- throw FileError(_("Cannot get process information."), L"getenv: TMPDIR not found.");
- return buf;
-}
-
-
-void zen::removeFilePlain(const Zstring& filePath) //throw FileError
-{
- const wchar_t functionName[] = L"unlink";
- if (::unlink(filePath.c_str()) != 0)
- {
- ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
- //begin of "regular" error reporting
- std::wstring errorDescr = formatSystemError(functionName, ec);
-
- throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath)), errorDescr);
- }
-}
-
-
-void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError
-{
- removeFilePlain(linkPath); //throw FileError
-}
-
-
-void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError
-{
- const wchar_t functionName[] = L"rmdir";
- if (::rmdir(dirPath.c_str()) != 0)
- {
- ErrorCode ec = getLastError(); //copy before making other system calls!
- bool symlinkExists = false;
- try { symlinkExists = getItemType(dirPath) == ItemType::SYMLINK; } /*throw FileError*/ catch (FileError&) {} //previous exception is more relevant
-
- if (symlinkExists)
- {
- if (::unlink(dirPath.c_str()) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), L"unlink");
- return;
- }
- throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(functionName, ec));
- }
- /*
- Windows: may spuriously fail with ERROR_DIR_NOT_EMPTY(145) even though all child items have
- successfully been *marked* for deletion, but some application still has a handle open!
- e.g. Open "C:\Test\Dir1\Dir2" (filled with lots of files) in Explorer, then delete "C:\Test\Dir1" via ::RemoveDirectory() => Error 145
- Sample code: http://us.generation-nt.com/answer/createfile-directory-handles-removing-parent-help-29126332.html
- Alternatives: 1. move file/empty folder to some other location, then DeleteFile()/RemoveDirectory()
- 2. use CreateFile/FILE_FLAG_DELETE_ON_CLOSE *without* FILE_SHARE_DELETE instead of DeleteFile() => early failure
- */
-}
-
-
-namespace
-{
-void removeDirectoryImpl(const Zstring& folderPath) //throw FileError
-{
- std::vector<Zstring> filePaths;
- std::vector<Zstring> symlinkPaths;
- std::vector<Zstring> folderPaths;
-
- //get all files and directories from current directory (WITHOUT subdirectories!)
- traverseFolder(folderPath,
- [&](const FileInfo& fi) { filePaths .push_back(fi.fullPath); },
- [&](const FolderInfo& fi) { folderPaths .push_back(fi.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies!
- [&](const SymlinkInfo& si) { symlinkPaths.push_back(si.fullPath); },
- [](const std::wstring& errorMsg) { throw FileError(errorMsg); });
-
- for (const Zstring& filePath : filePaths)
- removeFilePlain(filePath); //throw FileError
-
- for (const Zstring& symlinkPath : symlinkPaths)
- removeSymlinkPlain(symlinkPath); //throw FileError
-
- //delete directories recursively
- for (const Zstring& subFolderPath : folderPaths)
- removeDirectoryImpl(subFolderPath); //throw FileError; call recursively to correctly handle symbolic links
-
- removeDirectoryPlain(folderPath); //throw FileError
-}
-}
-
-
-void zen::removeDirectoryPlainRecursion(const Zstring& dirPath) //throw FileError
-{
- if (getItemType(dirPath) == ItemType::SYMLINK) //throw FileError
- removeSymlinkPlain(dirPath); //throw FileError
- else
- removeDirectoryImpl(dirPath); //throw FileError
-}
-
-
-namespace
-{
-/* Usage overview: (avoid circular pattern!)
-
- renameFile() --> renameFile_sub()
- | /|\
- \|/ |
- Fix8Dot3NameClash()
-*/
-//wrapper for file system rename function:
-void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
-{
- //rename() will never fail with EEXIST, but always (atomically) overwrite!
- //=> equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists or ::MoveFileEx + MOVEFILE_REPLACE_EXISTING
- //=> Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy
- //=> OS X: no solution
-
- auto throwException = [&](int ec)
- {
- const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathSource)), L"%y", L"\n" + fmtPath(pathTarget));
- const std::wstring errorDescr = formatSystemError(L"rename", ec);
-
- if (ec == EXDEV)
- throw ErrorDifferentVolume(errorMsg, errorDescr);
- if (ec == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
- throw FileError(errorMsg, errorDescr);
- };
-
- if (!equalFilePath(pathSource, pathTarget)) //exception for OS X: changing file name case is not an "already exists" situation!
- {
- bool alreadyExists = true;
- try { /*ItemType type = */getItemType(pathTarget); } /*throw FileError*/ catch (FileError&) { alreadyExists = false; }
-
- if (alreadyExists)
- throwException(EEXIST);
- //else: nothing exists or other error (hopefully ::rename will also fail!)
- }
-
- if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0)
- throwException(errno);
-}
-
-
-}
-
-
-//rename file: no copying!!!
-void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
-{
- try
- {
- renameFile_sub(pathSource, pathTarget); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
- }
- catch (const ErrorTargetExisting&)
- {
- throw;
- }
-}
-
-
-namespace
-{
-void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTime, ProcSymlink procSl) //throw FileError
-{
- /*
- [2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit???
- => fallback to "retarded-idiot version"! -- DarkByte
-
- [2015-03-09]
- - cannot reproduce issues with NTFS and utimensat() on Ubuntu
- - utimensat() is supposed to obsolete utime/utimes and is also used by "cp" and "touch"
- => let's give utimensat another chance:
- using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"!
- */
- struct ::timespec newTimes[2] = {};
- newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs: http://www.freefilesync.org/forum/viewtopic.php?t=1701
- newTimes[1] = modTime; //modification time
-
- if (procSl == ProcSymlink::FOLLOW)
- {
- //hell knows why files on gvfs-mounted Samba shares fail to open(O_WRONLY) returning EOPNOTSUPP:
- //http://www.freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works
- if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, 0) == 0)
- return;
-
- //in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: http://www.freefilesync.org/forum/viewtopic.php?t=387
- const int fdFile = ::open(itemPath.c_str(), O_WRONLY);
- if (fdFile == -1)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"open");
- ZEN_ON_SCOPE_EXIT(::close(fdFile));
-
- if (::futimens(fdFile, newTimes) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"futimens");
- }
- else
- {
- if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"utimensat");
- }
-}
-
-
-}
-
-
-void zen::setFileTime(const Zstring& filePath, int64_t modTime, ProcSymlink procSl) //throw FileError
-{
- struct ::timespec writeTime = {};
- writeTime.tv_sec = modTime;
- setWriteTimeNative(filePath, writeTime, procSl); //throw FileError
-
-}
-
-
-bool zen::supportsPermissions(const Zstring& dirPath) //throw FileError
-{
- return true;
-}
-
-
-namespace
-{
-#ifdef HAVE_SELINUX
-//copy SELinux security context
-void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError
-{
- security_context_t contextSource = nullptr;
- const int rv = procSl == ProcSymlink::FOLLOW ?
- ::getfilecon(source.c_str(), &contextSource) :
- ::lgetfilecon(source.c_str(), &contextSource);
- if (rv < 0)
- {
- if (errno == ENODATA || //no security context (allegedly) is not an error condition on SELinux
- errno == EOPNOTSUPP) //extended attributes are not supported by the filesystem
- return;
-
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtPath(source)), L"getfilecon");
- }
- ZEN_ON_SCOPE_EXIT(::freecon(contextSource));
-
- {
- security_context_t contextTarget = nullptr;
- const int rv2 = procSl == ProcSymlink::FOLLOW ?
- ::getfilecon(target.c_str(), &contextTarget) :
- ::lgetfilecon(target.c_str(), &contextTarget);
- if (rv2 < 0)
- {
- if (errno == EOPNOTSUPP)
- return;
- //else: still try to set security context
- }
- else
- {
- ZEN_ON_SCOPE_EXIT(::freecon(contextTarget));
-
- if (::strcmp(contextSource, contextTarget) == 0) //nothing to do
- return;
- }
- }
-
- const int rv3 = procSl == ProcSymlink::FOLLOW ?
- ::setfilecon(target.c_str(), contextSource) :
- ::lsetfilecon(target.c_str(), contextSource);
- if (rv3 < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtPath(target)), L"setfilecon");
-}
-#endif
-
-
-//copy permissions for files, directories or symbolic links: requires admin rights
-void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, ProcSymlink procSl) //throw FileError
-{
-
-#ifdef HAVE_SELINUX //copy SELinux security context
- copySecurityContext(sourcePath, targetPath, procSl); //throw FileError
-#endif
-
- struct ::stat fileInfo = {};
- if (procSl == ProcSymlink::FOLLOW)
- {
- if (::stat(sourcePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"stat");
-
- if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chown");
-
- if (::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
- }
- else
- {
- if (::lstat(sourcePath.c_str(), &fileInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"lstat");
-
- if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown");
-
- const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
- if (!isSymlinkTarget && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
- ::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
- }
-
-}
-}
-
-
-void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw FileError
-{
- if (!getParentFolderPath(dirPath)) //device root
- return static_cast<void>(/*ItemType =*/ getItemType(dirPath)); //throw FileError
-
- try
- {
- copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting
- }
- catch (FileError&)
- {
- Opt<PathStatus> pd;
- try { pd = getPathStatus(dirPath); /*throw FileError*/ }
- catch (FileError&) {} //previous exception is more relevant
-
- if (pd && pd->existingType != ItemType::FILE)
- {
- Zstring intermediatePath = pd->existingPath;
- for (const Zstring& itemName : pd->relPath)
- {
- intermediatePath = appendSeparator(intermediatePath) + itemName;
- copyNewDirectory(Zstring(), intermediatePath, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting)
- }
- return;
- }
- throw;
- }
-}
-
-
-//source path is optional (may be empty)
-void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting
- bool copyFilePermissions)
-{
- mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777, default for newly created directories
-
- struct ::stat dirInfo = {};
- if (!sourcePath.empty())
- if (::stat(sourcePath.c_str(), &dirInfo) == 0)
- {
- mode = dirInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
- mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items
- }
- //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
-
- if (::mkdir(targetPath.c_str(), mode) != 0)
- {
- const int lastError = errno; //copy before directly or indirectly making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(targetPath));
- const std::wstring errorDescr = formatSystemError(L"mkdir", lastError);
-
- if (lastError == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
- //else if (lastError == ENOENT)
- // throw ErrorTargetPathMissing(errorMsg, errorDescr);
- throw FileError(errorMsg, errorDescr);
- }
-
- if (!sourcePath.empty())
- {
-
- ZEN_ON_SCOPE_FAIL(try { removeDirectoryPlain(targetPath); }
- catch (FileError&) {}); //ensure cleanup:
-
- //enforce copying file permissions: it's advertized on GUI...
- if (copyFilePermissions)
- copyItemPermissions(sourcePath, targetPath, ProcSymlink::FOLLOW); //throw FileError
- }
-}
-
-
-void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw FileError
-{
- const Zstring linkPath = getSymlinkTargetRaw(sourceLink); //throw FileError; accept broken symlinks
-
- const wchar_t functionName[] = L"symlink";
- if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), functionName);
-
- //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist!
- ZEN_ON_SCOPE_FAIL(try { removeSymlinkPlain(targetLink); /*throw FileError*/ }
- catch (FileError&) {});
-
- //file times: essential for sync'ing a symlink: enforce this! (don't just try!)
- struct ::stat sourceInfo = {};
- if (::lstat(sourceLink.c_str(), &sourceInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"lstat");
-
- setWriteTimeNative(targetLink, sourceInfo.st_mtim, ProcSymlink::DIRECT); //throw FileError
-
- if (copyFilePermissions)
- copyItemPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError
-}
-
-
-namespace
-{
-InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting
- const Zstring& targetFile,
- const IOCallback& notifyUnbufferedIO)
-{
- int64_t totalUnbufferedIO = 0;
-
- FileInput fileIn(sourceFile, IOCallbackDivider(notifyUnbufferedIO, totalUnbufferedIO)); //throw FileError, (ErrorFileLocked -> Windows-only)
-
- struct ::stat sourceInfo = {};
- if (::fstat(fileIn.getHandle(), &sourceInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"fstat");
-
- const mode_t mode = sourceInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
- //it seems we don't need S_IWUSR, not even for the setFileTime() below! (tested with source file having different user/group!)
-
- //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
- const int fdTarget = ::open(targetFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, mode);
- if (fdTarget == -1)
- {
- const int ec = errno; //copy before making other system calls!
- const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile));
- const std::wstring errorDescr = formatSystemError(L"open", ec);
-
- if (ec == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
-
- throw FileError(errorMsg, errorDescr);
- }
- ZEN_ON_SCOPE_FAIL( try { removeFilePlain(targetFile); }
- catch (FileError&) {} );
- //place guard AFTER ::open() and BEFORE lifetime of FileOutput:
- //=> don't delete file that existed previously!!!
- FileOutput fileOut(fdTarget, targetFile, IOCallbackDivider(notifyUnbufferedIO, totalUnbufferedIO)); //pass ownership
-
- bufferedStreamCopy(fileIn, fileOut); //throw FileError, X
- fileOut.flushBuffers(); //throw FileError, X
- struct ::stat targetInfo = {};
- if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"fstat");
-
- //close output file handle before setting file time; also good place to catch errors when closing stream!
- fileOut.finalize(); //throw FileError, (X) essentially a close() since buffers were already flushed
-
- //we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
- //this triggers bugs on samba shares where the modification time is set to current time instead.
- //Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
- // http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
- //OS X: http://www.freefilesync.org/forum/viewtopic.php?t=356
- setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::FOLLOW); //throw FileError
-
- InSyncAttributes newAttrib;
- newAttrib.fileSize = sourceInfo.st_size;
- newAttrib.modificationTime = sourceInfo.st_mtim.tv_sec; //
- newAttrib.sourceFileId = extractFileId(sourceInfo);
- newAttrib.targetFileId = extractFileId(targetInfo);
- return newAttrib;
-}
-
-/* ------------------
- |File Copy Layers|
- ------------------
- copyNewFile
- |
- copyFileOsSpecific (solve 8.3 issue on Windows)
- |
- copyFileWindowsSelectRoutine
- / \
-copyFileWindowsDefault(::CopyFileEx) copyFileWindowsStream(::BackupRead/::BackupWrite)
-*/
-}
-
-
-InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked
- const IOCallback& notifyUnbufferedIO)
-{
- const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, notifyUnbufferedIO); //throw FileError, ErrorTargetExisting, ErrorFileLocked
-
- //at this point we know we created a new file, so it's fine to delete it for cleanup!
- ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); }
- catch (FileError&) {});
-
- if (copyFilePermissions)
- copyItemPermissions(sourceFile, targetFile, ProcSymlink::FOLLOW); //throw FileError
-
- return attr;
-}
+// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "file_access.h"
+#include <map>
+#include <algorithm>
+#include <stdexcept>
+#include "file_traverser.h"
+#include "scope_guard.h"
+#include "symlink_target.h"
+#include "file_id_def.h"
+#include "file_io.h"
+
+ #include <sys/vfs.h> //statfs
+ #include <sys/time.h> //lutimes
+ #ifdef HAVE_SELINUX
+ #include <selinux/selinux.h>
+ #endif
+
+
+ #include <fcntl.h> //open, close, AT_SYMLINK_NOFOLLOW, UTIME_OMIT
+ #include <sys/stat.h>
+
+using namespace zen;
+
+
+Opt<PathComponents> zen::parsePathComponents(const Zstring& itemPath)
+{
+ if (startsWith(itemPath, "/"))
+ {
+ Zstring relPath(itemPath.c_str() + 1);
+ if (endsWith(relPath, FILE_NAME_SEPARATOR))
+ relPath.pop_back();
+ return PathComponents({ "/", relPath });
+ }
+ //we do NOT support relative paths!
+ return NoValue();
+}
+
+
+
+Opt<Zstring> zen::getParentFolderPath(const Zstring& itemPath)
+{
+ if (const Opt<PathComponents> comp = parsePathComponents(itemPath))
+ {
+ if (comp->relPath.empty())
+ return NoValue();
+
+ const Zstring parentRelPath = beforeLast(comp->relPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE);
+ if (parentRelPath.empty())
+ return comp->rootPath;
+ return appendSeparator(comp->rootPath) + parentRelPath;
+ }
+ assert(false);
+ return NoValue();
+}
+
+
+ItemType zen::getItemType(const Zstring& itemPath) //throw FileError
+{
+ struct ::stat itemInfo = {};
+ if (::lstat(itemPath.c_str(), &itemInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"lstat");
+
+ if (S_ISLNK(itemInfo.st_mode))
+ return ItemType::SYMLINK;
+ if (S_ISDIR(itemInfo.st_mode))
+ return ItemType::FOLDER;
+ return ItemType::FILE; //S_ISREG || S_ISCHR || S_ISBLK || S_ISFIFO || S_ISSOCK
+}
+
+
+PathStatus zen::getPathStatus(const Zstring& itemPath) //throw FileError
+{
+ const Opt<Zstring> parentPath = getParentFolderPath(itemPath);
+ try
+ {
+ return { getItemType(itemPath), itemPath, {} }; //throw FileError
+ }
+ catch (FileError&)
+ {
+ if (!parentPath) //device root
+ throw;
+ //else: let's dig deeper... don't bother checking Win32 codes; e.g. not existing item may have the codes:
+ // ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_INVALID_NAME, ERROR_INVALID_DRIVE,
+ // ERROR_NOT_READY, ERROR_INVALID_PARAMETER, ERROR_BAD_PATHNAME, ERROR_BAD_NETPATH => not reliable
+ }
+ const Zstring itemName = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL);
+ assert(!itemName.empty());
+
+ PathStatus pd = getPathStatus(*parentPath); //throw FileError
+ if (!pd.relPath.empty())
+ {
+ pd.relPath.push_back(itemName);
+ return { pd.existingType, pd.existingPath, pd.relPath };
+ }
+
+ try
+ {
+ traverseFolder(*parentPath,
+ [&](const FileInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FILE; },
+ [&](const FolderInfo& fi) { if (equalFilePath(fi.itemName, itemName)) throw ItemType::FOLDER; },
+ [&](const SymlinkInfo& si) { if (equalFilePath(si.itemName, itemName)) throw ItemType::SYMLINK; },
+ [](const std::wstring& errorMsg) { throw FileError(errorMsg); });
+
+ return { pd.existingType, *parentPath, { itemName } }; //throw FileError
+ }
+ catch (const ItemType& type) { return { type, itemPath, {} }; } //yes, exceptions for control-flow are bad design... but, but...
+ //we're not CPU-bound here and finding the item after getItemType() previously failed is exceptional (even C:\pagefile.sys should be found)
+}
+
+
+Opt<ItemType> zen::getItemTypeIfExists(const Zstring& itemPath) //throw FileError
+{
+ const PathStatus pd = getPathStatus(itemPath); //throw FileError
+ if (pd.relPath.empty())
+ return pd.existingType;
+ return NoValue();
+}
+
+
+bool zen::fileAvailable(const Zstring& filePath) //noexcept
+{
+ //symbolic links (broken or not) are also treated as existing files!
+ struct ::stat fileInfo = {};
+ if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks!
+ return S_ISREG(fileInfo.st_mode);
+ return false;
+}
+
+
+bool zen::dirAvailable(const Zstring& dirPath) //noexcept
+{
+ //symbolic links (broken or not) are also treated as existing directories!
+ struct ::stat dirInfo = {};
+ if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks!
+ return S_ISDIR(dirInfo.st_mode);
+ return false;
+}
+
+
+bool zen::itemNotExisting(const Zstring& itemPath)
+{
+ try
+ {
+ return !getItemTypeIfExists(itemPath); //throw FileError
+ }
+ catch (FileError&) { return false; }
+}
+
+
+namespace
+{
+}
+
+
+uint64_t zen::getFileSize(const Zstring& filePath) //throw FileError
+{
+ struct ::stat fileInfo = {};
+ if (::stat(filePath.c_str(), &fileInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"stat");
+
+ return fileInfo.st_size;
+}
+
+
+uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0 if not available
+{
+ struct ::statfs info = {};
+ if (::statfs(path.c_str(), &info) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), L"statfs");
+
+ return static_cast<uint64_t>(info.f_bsize) * info.f_bavail;
+}
+
+
+VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError
+{
+ struct ::stat fileInfo = {};
+ if (::stat(itemPath.c_str(), &fileInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"stat");
+
+ return fileInfo.st_dev;
+}
+
+
+Zstring zen::getTempFolderPath() //throw FileError
+{
+ const char* buf = ::getenv("TMPDIR"); //no extended error reporting
+ if (!buf)
+ throw FileError(_("Cannot get process information."), L"getenv: TMPDIR not found.");
+ return buf;
+}
+
+
+void zen::removeFilePlain(const Zstring& filePath) //throw FileError
+{
+ const wchar_t functionName[] = L"unlink";
+ if (::unlink(filePath.c_str()) != 0)
+ {
+ ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls!
+ //begin of "regular" error reporting
+ std::wstring errorDescr = formatSystemError(functionName, ec);
+
+ throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath)), errorDescr);
+ }
+}
+
+
+void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError
+{
+ removeFilePlain(linkPath); //throw FileError
+}
+
+
+void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError
+{
+ const wchar_t functionName[] = L"rmdir";
+ if (::rmdir(dirPath.c_str()) != 0)
+ {
+ ErrorCode ec = getLastError(); //copy before making other system calls!
+ bool symlinkExists = false;
+ try { symlinkExists = getItemType(dirPath) == ItemType::SYMLINK; } /*throw FileError*/ catch (FileError&) {} //previous exception is more relevant
+
+ if (symlinkExists)
+ {
+ if (::unlink(dirPath.c_str()) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), L"unlink");
+ return;
+ }
+ throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), formatSystemError(functionName, ec));
+ }
+ /*
+ Windows: may spuriously fail with ERROR_DIR_NOT_EMPTY(145) even though all child items have
+ successfully been *marked* for deletion, but some application still has a handle open!
+ e.g. Open "C:\Test\Dir1\Dir2" (filled with lots of files) in Explorer, then delete "C:\Test\Dir1" via ::RemoveDirectory() => Error 145
+ Sample code: http://us.generation-nt.com/answer/createfile-directory-handles-removing-parent-help-29126332.html
+ Alternatives: 1. move file/empty folder to some other location, then DeleteFile()/RemoveDirectory()
+ 2. use CreateFile/FILE_FLAG_DELETE_ON_CLOSE *without* FILE_SHARE_DELETE instead of DeleteFile() => early failure
+ */
+}
+
+
+namespace
+{
+void removeDirectoryImpl(const Zstring& folderPath) //throw FileError
+{
+ std::vector<Zstring> filePaths;
+ std::vector<Zstring> symlinkPaths;
+ std::vector<Zstring> folderPaths;
+
+ //get all files and directories from current directory (WITHOUT subdirectories!)
+ traverseFolder(folderPath,
+ [&](const FileInfo& fi) { filePaths .push_back(fi.fullPath); },
+ [&](const FolderInfo& fi) { folderPaths .push_back(fi.fullPath); }, //defer recursion => save stack space and allow deletion of extremely deep hierarchies!
+ [&](const SymlinkInfo& si) { symlinkPaths.push_back(si.fullPath); },
+ [](const std::wstring& errorMsg) { throw FileError(errorMsg); });
+
+ for (const Zstring& filePath : filePaths)
+ removeFilePlain(filePath); //throw FileError
+
+ for (const Zstring& symlinkPath : symlinkPaths)
+ removeSymlinkPlain(symlinkPath); //throw FileError
+
+ //delete directories recursively
+ for (const Zstring& subFolderPath : folderPaths)
+ removeDirectoryImpl(subFolderPath); //throw FileError; call recursively to correctly handle symbolic links
+
+ removeDirectoryPlain(folderPath); //throw FileError
+}
+}
+
+
+void zen::removeDirectoryPlainRecursion(const Zstring& dirPath) //throw FileError
+{
+ if (getItemType(dirPath) == ItemType::SYMLINK) //throw FileError
+ removeSymlinkPlain(dirPath); //throw FileError
+ else
+ removeDirectoryImpl(dirPath); //throw FileError
+}
+
+
+namespace
+{
+/* Usage overview: (avoid circular pattern!)
+
+ renameFile() --> renameFile_sub()
+ | /|\
+ \|/ |
+ Fix8Dot3NameClash()
+*/
+//wrapper for file system rename function:
+void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+{
+ //rename() will never fail with EEXIST, but always (atomically) overwrite!
+ //=> equivalent to SetFileInformationByHandle() + FILE_RENAME_INFO::ReplaceIfExists or ::MoveFileEx + MOVEFILE_REPLACE_EXISTING
+ //=> Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy
+ //=> OS X: no solution
+
+ auto throwException = [&](int ec)
+ {
+ const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathSource)), L"%y", L"\n" + fmtPath(pathTarget));
+ const std::wstring errorDescr = formatSystemError(L"rename", ec);
+
+ if (ec == EXDEV)
+ throw ErrorDifferentVolume(errorMsg, errorDescr);
+ if (ec == EEXIST)
+ throw ErrorTargetExisting(errorMsg, errorDescr);
+ throw FileError(errorMsg, errorDescr);
+ };
+
+ if (!equalFilePath(pathSource, pathTarget)) //exception for OS X: changing file name case is not an "already exists" situation!
+ {
+ bool alreadyExists = true;
+ try { /*ItemType type = */getItemType(pathTarget); } /*throw FileError*/ catch (FileError&) { alreadyExists = false; }
+
+ if (alreadyExists)
+ throwException(EEXIST);
+ //else: nothing exists or other error (hopefully ::rename will also fail!)
+ }
+
+ if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0)
+ throwException(errno);
+}
+
+
+}
+
+
+//rename file: no copying!!!
+void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+{
+ try
+ {
+ renameFile_sub(pathSource, pathTarget); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting
+ }
+ catch (const ErrorTargetExisting&)
+ {
+ throw;
+ }
+}
+
+
+namespace
+{
+void setWriteTimeNative(const Zstring& itemPath, const struct ::timespec& modTime, ProcSymlink procSl) //throw FileError
+{
+ /*
+ [2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit???
+ => fallback to "retarded-idiot version"! -- DarkByte
+
+ [2015-03-09]
+ - cannot reproduce issues with NTFS and utimensat() on Ubuntu
+ - utimensat() is supposed to obsolete utime/utimes and is also used by "cp" and "touch"
+ => let's give utimensat another chance:
+ using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"!
+ */
+ struct ::timespec newTimes[2] = {};
+ newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs: http://www.freefilesync.org/forum/viewtopic.php?t=1701
+ newTimes[1] = modTime; //modification time
+
+ if (procSl == ProcSymlink::FOLLOW)
+ {
+ //hell knows why files on gvfs-mounted Samba shares fail to open(O_WRONLY) returning EOPNOTSUPP:
+ //http://www.freefilesync.org/forum/viewtopic.php?t=2803 => utimensat() works
+ if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, 0) == 0)
+ return;
+
+ //in other cases utimensat() returns EINVAL for CIFS/NTFS drives, but open+futimens works: http://www.freefilesync.org/forum/viewtopic.php?t=387
+ const int fdFile = ::open(itemPath.c_str(), O_WRONLY);
+ if (fdFile == -1)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"open");
+ ZEN_ON_SCOPE_EXIT(::close(fdFile));
+
+ if (::futimens(fdFile, newTimes) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"futimens");
+ }
+ else
+ {
+ if (::utimensat(AT_FDCWD, itemPath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"utimensat");
+ }
+}
+
+
+}
+
+
+void zen::setFileTime(const Zstring& filePath, int64_t modTime, ProcSymlink procSl) //throw FileError
+{
+ struct ::timespec writeTime = {};
+ writeTime.tv_sec = modTime;
+ setWriteTimeNative(filePath, writeTime, procSl); //throw FileError
+
+}
+
+
+bool zen::supportsPermissions(const Zstring& dirPath) //throw FileError
+{
+ return true;
+}
+
+
+namespace
+{
+#ifdef HAVE_SELINUX
+//copy SELinux security context
+void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError
+{
+ security_context_t contextSource = nullptr;
+ const int rv = procSl == ProcSymlink::FOLLOW ?
+ ::getfilecon(source.c_str(), &contextSource) :
+ ::lgetfilecon(source.c_str(), &contextSource);
+ if (rv < 0)
+ {
+ if (errno == ENODATA || //no security context (allegedly) is not an error condition on SELinux
+ errno == EOPNOTSUPP) //extended attributes are not supported by the filesystem
+ return;
+
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtPath(source)), L"getfilecon");
+ }
+ ZEN_ON_SCOPE_EXIT(::freecon(contextSource));
+
+ {
+ security_context_t contextTarget = nullptr;
+ const int rv2 = procSl == ProcSymlink::FOLLOW ?
+ ::getfilecon(target.c_str(), &contextTarget) :
+ ::lgetfilecon(target.c_str(), &contextTarget);
+ if (rv2 < 0)
+ {
+ if (errno == EOPNOTSUPP)
+ return;
+ //else: still try to set security context
+ }
+ else
+ {
+ ZEN_ON_SCOPE_EXIT(::freecon(contextTarget));
+
+ if (::strcmp(contextSource, contextTarget) == 0) //nothing to do
+ return;
+ }
+ }
+
+ const int rv3 = procSl == ProcSymlink::FOLLOW ?
+ ::setfilecon(target.c_str(), contextSource) :
+ ::lsetfilecon(target.c_str(), contextSource);
+ if (rv3 < 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtPath(target)), L"setfilecon");
+}
+#endif
+
+
+//copy permissions for files, directories or symbolic links: requires admin rights
+void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, ProcSymlink procSl) //throw FileError
+{
+
+#ifdef HAVE_SELINUX //copy SELinux security context
+ copySecurityContext(sourcePath, targetPath, procSl); //throw FileError
+#endif
+
+ struct ::stat fileInfo = {};
+ if (procSl == ProcSymlink::FOLLOW)
+ {
+ if (::stat(sourcePath.c_str(), &fileInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"stat");
+
+ if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chown");
+
+ if (::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
+ }
+ else
+ {
+ if (::lstat(sourcePath.c_str(), &fileInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"lstat");
+
+ if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights!
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown");
+
+ const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError
+ if (!isSymlinkTarget && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod()
+ ::chmod(targetPath.c_str(), fileInfo.st_mode) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod");
+ }
+
+}
+}
+
+
+void zen::createDirectoryIfMissingRecursion(const Zstring& dirPath) //throw FileError
+{
+ if (!getParentFolderPath(dirPath)) //device root
+ return static_cast<void>(/*ItemType =*/ getItemType(dirPath)); //throw FileError
+
+ try
+ {
+ copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting
+ }
+ catch (FileError&)
+ {
+ Opt<PathStatus> pd;
+ try { pd = getPathStatus(dirPath); /*throw FileError*/ }
+ catch (FileError&) {} //previous exception is more relevant
+
+ if (pd && pd->existingType != ItemType::FILE)
+ {
+ Zstring intermediatePath = pd->existingPath;
+ for (const Zstring& itemName : pd->relPath)
+ {
+ intermediatePath = appendSeparator(intermediatePath) + itemName;
+ copyNewDirectory(Zstring(), intermediatePath, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting)
+ }
+ return;
+ }
+ throw;
+ }
+}
+
+
+//source path is optional (may be empty)
+void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting
+ bool copyFilePermissions)
+{
+ mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777, default for newly created directories
+
+ struct ::stat dirInfo = {};
+ if (!sourcePath.empty())
+ if (::stat(sourcePath.c_str(), &dirInfo) == 0)
+ {
+ mode = dirInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
+ mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items
+ }
+ //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
+
+ if (::mkdir(targetPath.c_str(), mode) != 0)
+ {
+ const int lastError = errno; //copy before directly or indirectly making other system calls!
+ const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(targetPath));
+ const std::wstring errorDescr = formatSystemError(L"mkdir", lastError);
+
+ if (lastError == EEXIST)
+ throw ErrorTargetExisting(errorMsg, errorDescr);
+ //else if (lastError == ENOENT)
+ // throw ErrorTargetPathMissing(errorMsg, errorDescr);
+ throw FileError(errorMsg, errorDescr);
+ }
+
+ if (!sourcePath.empty())
+ {
+
+ ZEN_ON_SCOPE_FAIL(try { removeDirectoryPlain(targetPath); }
+ catch (FileError&) {}); //ensure cleanup:
+
+ //enforce copying file permissions: it's advertized on GUI...
+ if (copyFilePermissions)
+ copyItemPermissions(sourcePath, targetPath, ProcSymlink::FOLLOW); //throw FileError
+ }
+}
+
+
+void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw FileError
+{
+ const Zstring linkPath = getSymlinkTargetRaw(sourceLink); //throw FileError; accept broken symlinks
+
+ const wchar_t functionName[] = L"symlink";
+ if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), functionName);
+
+ //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist!
+ ZEN_ON_SCOPE_FAIL(try { removeSymlinkPlain(targetLink); /*throw FileError*/ }
+ catch (FileError&) {});
+
+ //file times: essential for sync'ing a symlink: enforce this! (don't just try!)
+ struct ::stat sourceInfo = {};
+ if (::lstat(sourceLink.c_str(), &sourceInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"lstat");
+
+ setWriteTimeNative(targetLink, sourceInfo.st_mtim, ProcSymlink::DIRECT); //throw FileError
+
+ if (copyFilePermissions)
+ copyItemPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError
+}
+
+
+namespace
+{
+InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting
+ const Zstring& targetFile,
+ const IOCallback& notifyUnbufferedIO)
+{
+ int64_t totalUnbufferedIO = 0;
+
+ FileInput fileIn(sourceFile, IOCallbackDivider(notifyUnbufferedIO, totalUnbufferedIO)); //throw FileError, (ErrorFileLocked -> Windows-only)
+
+ struct ::stat sourceInfo = {};
+ if (::fstat(fileIn.getHandle(), &sourceInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"fstat");
+
+ const mode_t mode = sourceInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); //analog to "cp" which copies "mode" (considering umask) by default
+ //it seems we don't need S_IWUSR, not even for the setFileTime() below! (tested with source file having different user/group!)
+
+ //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions
+ const int fdTarget = ::open(targetFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, mode);
+ if (fdTarget == -1)
+ {
+ const int ec = errno; //copy before making other system calls!
+ const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile));
+ const std::wstring errorDescr = formatSystemError(L"open", ec);
+
+ if (ec == EEXIST)
+ throw ErrorTargetExisting(errorMsg, errorDescr);
+
+ throw FileError(errorMsg, errorDescr);
+ }
+ ZEN_ON_SCOPE_FAIL( try { removeFilePlain(targetFile); }
+ catch (FileError&) {} );
+ //place guard AFTER ::open() and BEFORE lifetime of FileOutput:
+ //=> don't delete file that existed previously!!!
+ FileOutput fileOut(fdTarget, targetFile, IOCallbackDivider(notifyUnbufferedIO, totalUnbufferedIO)); //pass ownership
+
+ bufferedStreamCopy(fileIn, fileOut); //throw FileError, X
+ fileOut.flushBuffers(); //throw FileError, X
+ struct ::stat targetInfo = {};
+ if (::fstat(fileOut.getHandle(), &targetInfo) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"fstat");
+
+ //close output file handle before setting file time; also good place to catch errors when closing stream!
+ fileOut.finalize(); //throw FileError, (X) essentially a close() since buffers were already flushed
+
+ //we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation:
+ //this triggers bugs on samba shares where the modification time is set to current time instead.
+ //Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236
+ // http://comments.gmane.org/gmane.linux.file-systems.cifs/2854
+ //OS X: http://www.freefilesync.org/forum/viewtopic.php?t=356
+ setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::FOLLOW); //throw FileError
+
+ InSyncAttributes newAttrib;
+ newAttrib.fileSize = sourceInfo.st_size;
+ newAttrib.modificationTime = sourceInfo.st_mtim.tv_sec; //
+ newAttrib.sourceFileId = extractFileId(sourceInfo);
+ newAttrib.targetFileId = extractFileId(targetInfo);
+ return newAttrib;
+}
+
+/* ------------------
+ |File Copy Layers|
+ ------------------
+ copyNewFile
+ |
+ copyFileOsSpecific (solve 8.3 issue on Windows)
+ |
+ copyFileWindowsSelectRoutine
+ / \
+copyFileWindowsDefault(::CopyFileEx) copyFileWindowsStream(::BackupRead/::BackupWrite)
+*/
+}
+
+
+InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked
+ const IOCallback& notifyUnbufferedIO)
+{
+ const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, notifyUnbufferedIO); //throw FileError, ErrorTargetExisting, ErrorFileLocked
+
+ //at this point we know we created a new file, so it's fine to delete it for cleanup!
+ ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); }
+ catch (FileError&) {});
+
+ if (copyFilePermissions)
+ copyItemPermissions(sourceFile, targetFile, ProcSymlink::FOLLOW); //throw FileError
+
+ return attr;
+}
bgstack15