diff options
Diffstat (limited to 'zen/file_access.cpp')
-rwxr-xr-x | zen/file_access.cpp | 1352 |
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; +} |