diff options
Diffstat (limited to 'zen/file_access.cpp')
-rwxr-xr-x[-rw-r--r--] | zen/file_access.cpp | 3265 |
1 files changed, 676 insertions, 2589 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp index ac68330e..61a003bb 100644..100755 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -1,2589 +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" - -#ifdef ZEN_WIN - #include <Aclapi.h> - #include "int64.h" - #include "privilege.h" - #include "long_path_prefix.h" - #include "win_ver.h" - #ifdef ZEN_WIN_VISTA_AND_LATER - #include <zen/vista_file_op.h> //requires COM initialization! - #endif - -#elif defined ZEN_LINUX - #include <sys/vfs.h> //statfs - #include <sys/time.h> //lutimes - #ifdef HAVE_SELINUX - #include <selinux/selinux.h> - #endif - -#elif defined ZEN_MAC - #include <sys/mount.h> //statfs - #include <copyfile.h> -#endif - -#if defined ZEN_LINUX || defined ZEN_MAC - #include <fcntl.h> //open, close, AT_SYMLINK_NOFOLLOW, UTIME_OMIT - #include <sys/stat.h> -#endif - -using namespace zen; - - -Opt<Zstring> zen::getParentFolderPath(const Zstring& itemPath) -{ -#ifdef ZEN_WIN - assert(startsWith(itemPath, L"\\\\") || //we do NOT support relative paths! - (itemPath.size() >= 3 && isAlpha(itemPath[0]) && itemPath[1] == L':' && itemPath[2] == L'\\')); - - //remove trailing separator (even for C:\ root directories) - const Zstring itemPathFmt = endsWith(itemPath, FILE_NAME_SEPARATOR) ? - beforeLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) : - itemPath; - - if (startsWith(itemPathFmt, L"\\\\")) //UNC-name, e.g. \\server-name\share - if (std::count(itemPathFmt.begin(), itemPathFmt.end(), FILE_NAME_SEPARATOR) <= 3) - return NoValue(); - - const Zstring parentDir = beforeLast(itemPathFmt, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); - if (parentDir.empty()) - return NoValue(); - if (parentDir.size() == 2 && isAlpha(parentDir[0]) && parentDir[1] == L':') - return appendSeparator(parentDir); - -#elif defined ZEN_LINUX || defined ZEN_MAC - assert(startsWith(itemPath, L"/")); //we do NOT support relative paths! - - if (itemPath == "/") - return NoValue(); - - const Zstring parentDir = beforeLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); - if (parentDir.empty()) - return Zstring("/"); -#endif - return parentDir; -} - - -ItemType zen::getItemType(const Zstring& itemPath) //throw FileError -{ -#ifdef ZEN_WIN - const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str()); - if (attr == INVALID_FILE_ATTRIBUTES) - { - const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - if (ec == ERROR_PATH_NOT_FOUND || // - ec == ERROR_FILE_NOT_FOUND || //perf: short circuit for common "not existing" error codes - ec == ERROR_BAD_NETPATH || // - ec == ERROR_BAD_NET_NAME) // - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"GetFileAttributes"); - } - - if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0) - return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 ? ItemType::FOLDER : ItemType::FILE; - - //handle obscure file permission problem where ::GetFileAttributes() fails with ERROR_ACCESS_DENIED or ERROR_SHARING_VIOLATION - //while parent directory traversal is successful: e.g. "C:\pagefile.sys" - WIN32_FIND_DATA itemInfo = {}; - const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(itemPath).c_str(), &itemInfo); - if (searchHandle == INVALID_HANDLE_VALUE) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"FindFirstFile"); - ::FindClose(searchHandle); - - if (isSymlink(itemInfo)) //not every FILE_ATTRIBUTE_REPARSE_POINT is a symlink!!! - return ItemType::SYMLINK; - return (itemInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ? ItemType::FOLDER : ItemType::FILE; - -#elif defined ZEN_LINUX || defined ZEN_MAC - 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 -#endif -} - - -PathDetails zen::getPathDetails(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()); - - PathDetails pd = getPathDetails(*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 PathDetails pd = getPathDetails(itemPath); //throw FileError -#ifndef NDEBUG - Zstring reconstructedPath = pd.existingPath; - for (const Zstring& itemName : pd.relPath) - reconstructedPath += endsWith(reconstructedPath, FILE_NAME_SEPARATOR) ? itemName : FILE_NAME_SEPARATOR + itemName; - assert(itemPath == reconstructedPath); -#endif - 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! -#ifdef ZEN_WIN - const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str()); - if (attr != INVALID_FILE_ATTRIBUTES) - return (attr & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also - -#elif defined ZEN_LINUX || defined ZEN_MAC - struct ::stat fileInfo = {}; - if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks! - return S_ISREG(fileInfo.st_mode); -#endif - return false; -} - - -bool zen::dirAvailable(const Zstring& dirPath) //noexcept -{ - //symbolic links (broken or not) are also treated as existing directories! -#ifdef ZEN_WIN - const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirPath).c_str()); - if (attr != INVALID_FILE_ATTRIBUTES) - return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for ((broken) dir-)symlinks also - -#elif defined ZEN_LINUX || defined ZEN_MAC - struct ::stat dirInfo = {}; - if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks! - return S_ISDIR(dirInfo.st_mode); -#endif - return false; -} - - -warn_static("remove following test functions after refactoring") - - - -bool zen::fileExists(const Zstring& filePath) -{ - //symbolic links (broken or not) are also treated as existing files! -#ifdef ZEN_WIN - const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str()); - if (attr != INVALID_FILE_ATTRIBUTES) - return (attr & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also - -#elif defined ZEN_LINUX || defined ZEN_MAC - struct ::stat fileInfo = {}; - if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks! - return S_ISREG(fileInfo.st_mode); -#endif - return false; -} - - -bool zen::dirExists(const Zstring& dirPath) -{ - //symbolic links (broken or not) are also treated as existing directories! -#ifdef ZEN_WIN - const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirPath).c_str()); - if (attr != INVALID_FILE_ATTRIBUTES) - return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for ((broken) dir-)symlinks also - -#elif defined ZEN_LINUX || defined ZEN_MAC - struct ::stat dirInfo = {}; - if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks! - return S_ISDIR(dirInfo.st_mode); -#endif - return false; -} - - -namespace -{ -#ifdef ZEN_WIN -enum class FatType -{ - NONE, - FAT, - FAT32, - EXFAT, -}; - -FatType getFatType(const Zstring& filePath) //noexcept -{ - const DWORD bufferSize = MAX_PATH + 1; - std::vector<wchar_t> buffer(bufferSize); - - //this call is expensive: ~1.5 ms! - if (!::GetVolumePathName(filePath.c_str(), //__in LPCTSTR lpszFileName, - &buffer[0], //__out LPTSTR lpszVolumePathName, - bufferSize)) //__in DWORD cchBufferLength - { - assert(false); - return FatType::NONE; - } - - const Zstring volumePath = appendSeparator(&buffer[0]); - - //suprisingly fast: ca. 0.03 ms per call! - if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, - nullptr, //__out LPTSTR lpVolumeNameBuffer, - 0, //__in DWORD nVolumeNameSize, - nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, - nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - nullptr, //__out_opt LPDWORD lpFileSystemFlags, - &buffer[0], //__out LPTSTR lpFileSystemNameBuffer, - bufferSize)) //__in DWORD nFileSystemNameSize - { - assert(false); - return FatType::NONE; - } - - if (&buffer[0] == Zstring(L"FAT")) - return FatType::FAT; - if (&buffer[0] == Zstring(L"FAT32")) - return FatType::FAT32; - if (&buffer[0] == Zstring(L"exFAT")) - return FatType::EXFAT; - return FatType::NONE; -} - - -#ifdef ZEN_WIN_VISTA_AND_LATER -FatType getFatType(HANDLE hFile) //noexcept -{ - const DWORD bufferSize = MAX_PATH + 1; //"The length of the file system name buffer, in TCHARs. The maximum buffer size is MAX_PATH + 1" - std::vector<wchar_t> buffer(bufferSize); - - if (!::GetVolumeInformationByHandleW(hFile, //_In_ HANDLE hFile, - nullptr, //_Out_writes_opt_(nVolumeNameSize) LPWSTR lpVolumeNameBuffer, - 0, //_In_ DWORD nVolumeNameSize, - nullptr, //_Out_opt_ LPDWORD lpVolumeSerialNumber, - nullptr, //_Out_opt_ LPDWORD lpMaximumComponentLength, - nullptr, //_Out_opt_ LPDWORD lpFileSystemFlags, - &buffer[0], //_Out_writes_opt_(nFileSystemNameSize) LPWSTR lpFileSystemNameBuffer, - bufferSize)) //_In_ DWORD nFileSystemNameSize - { - assert(false); - return FatType::NONE; - } - - if (&buffer[0] == Zstring(L"FAT")) - return FatType::FAT; - if (&buffer[0] == Zstring(L"FAT32")) - return FatType::FAT32; - if (&buffer[0] == Zstring(L"exFAT")) - return FatType::EXFAT; - return FatType::NONE; -} -#endif -#endif -} - - -std::uint64_t zen::getFilesize(const Zstring& filePath) //throw FileError -{ -#ifdef ZEN_WIN - { - WIN32_FIND_DATA fileInfo = {}; - const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(filePath).c_str(), &fileInfo); - if (searchHandle == INVALID_HANDLE_VALUE) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"FindFirstFile"); - ::FindClose(searchHandle); - - if (!isSymlink(fileInfo)) - return get64BitUInt(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); - } - // WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {}; - // if (!::GetFileAttributesEx(applyLongPathPrefix(filePath).c_str(), //__in LPCTSTR lpFileName, - // GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, - // &sourceAttr)) //__out LPVOID lpFileInformation - - //open handle to target of symbolic link - const HANDLE hFile = ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName, - 0, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes, - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hFile == INVALID_HANDLE_VALUE) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"CreateFile"); - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - - LARGE_INTEGER fileSize = {}; - if (!::GetFileSizeEx(hFile, //_In_ HANDLE hFile, - &fileSize)) //_Out_ PLARGE_INTEGER lpFileSize - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileSizeEx"); - return fileSize.QuadPart; - - //alternative: - //BY_HANDLE_FILE_INFORMATION fileInfoHnd = {}; - //if (!::GetFileInformationByHandle(hFile, &fileInfoHnd)) - // THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileInformationByHandle"); - //return get64BitUInt(fileInfoHnd.nFileSizeLow, fileInfoHnd.nFileSizeHigh); - -#elif defined ZEN_LINUX || defined ZEN_MAC - 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; -#endif -} - - -std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0 if not available -{ -#ifdef ZEN_WIN - ULARGE_INTEGER bytesFree = {}; - if (!::GetDiskFreeSpaceEx(appendSeparator(path).c_str(), //__in_opt LPCTSTR lpDirectoryName, -> "UNC name [...] must include a trailing backslash, for example, "\\MyServer\MyShare\" - &bytesFree, //__out_opt PULARGE_INTEGER lpFreeBytesAvailable, - nullptr, //__out_opt PULARGE_INTEGER lpTotalNumberOfBytes, - nullptr)) //__out_opt PULARGE_INTEGER lpTotalNumberOfFreeBytes - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), L"GetDiskFreeSpaceEx"); - //succeeds even if path is not yet existing! - - //return 0 if info is not available: "The GetDiskFreeSpaceEx function returns zero for lpFreeBytesAvailable for all CD requests" - return get64BitUInt(bytesFree.LowPart, bytesFree.HighPart); - -#elif defined ZEN_LINUX || defined ZEN_MAC - 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<std::uint64_t>(info.f_bsize) * info.f_bavail; -#endif -} - - -VolumeId zen::getVolumeId(const Zstring& itemPath) //throw FileError -{ -#ifdef ZEN_WIN - //this works for: - //- root paths "C:\", "D:\" - //- network shares: \\share\dirname - //- indirection: subst S: %USERPROFILE% - // -> GetVolumePathName() + GetVolumeInformation() OTOH incorrectly resolves "S:\Desktop\somedir" to "S:\Desktop\" - nice try... - const HANDLE hItem = ::CreateFile(zen::applyLongPathPrefix(itemPath).c_str(), //_In_ LPCTSTR lpFileName, - 0, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - // FILE_FLAG_OPEN_REPARSE_POINT -> no, we follow symlinks! - FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, - /*needed to open a directory*/ - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hItem == INVALID_HANDLE_VALUE) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"CreateFile"); - ZEN_ON_SCOPE_EXIT(::CloseHandle(hItem)); - - BY_HANDLE_FILE_INFORMATION fileInfo = {}; - if (!::GetFileInformationByHandle(hItem, &fileInfo)) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"GetFileInformationByHandle"); - - return fileInfo.dwVolumeSerialNumber; - -#elif defined ZEN_LINUX || defined ZEN_MAC - 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; -#endif -} - - -Zstring zen::getTempFolderPath() //throw FileError -{ -#ifdef ZEN_WIN - const DWORD bufSize = MAX_PATH + 1; //MSDN: maximum value - std::vector<wchar_t> buf(bufSize); - const DWORD rv = ::GetTempPath(bufSize, //_In_ DWORD nBufferLength, - &buf[0]); //_Out_ LPTSTR lpBuffer - if (rv == 0) - THROW_LAST_FILE_ERROR(_("Cannot get process information."), L"GetTempPath"); - if (rv >= bufSize) - throw FileError(_("Cannot get process information."), L"GetTempPath: insufficient buffer size."); - return &buf[0]; - -#elif defined ZEN_LINUX || defined ZEN_MAC - const char* buf = ::getenv("TMPDIR"); //no extended error reporting - if (!buf) - throw FileError(_("Cannot get process information."), L"getenv: TMPDIR not found."); - return buf; -#endif -} - - -void zen::removeFilePlain(const Zstring& filePath) //throw FileError -{ -#ifdef ZEN_WIN - const wchar_t functionName[] = L"DeleteFile"; - - if (!::DeleteFile(applyLongPathPrefix(filePath).c_str())) -#elif defined ZEN_LINUX || defined ZEN_MAC - const wchar_t functionName[] = L"unlink"; - if (::unlink(filePath.c_str()) != 0) -#endif - { - ErrorCode ec = getLastError(); //copy before directly/indirectly making other system calls! -#ifdef ZEN_WIN - if (ec == ERROR_ACCESS_DENIED) //function fails if file is read-only - if (::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), FILE_ATTRIBUTE_NORMAL)) //(try to) normalize file attributes - { - if (::DeleteFile(applyLongPathPrefix(filePath).c_str())) //now try again... - return; - ec = ::GetLastError(); - } -#endif - //begin of "regular" error reporting - std::wstring errorDescr = formatSystemError(functionName, ec); - -#ifdef ZEN_WIN_VISTA_AND_LATER - if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message! - ec == ERROR_LOCK_VIOLATION) - { - const std::wstring procList = vista::getLockingProcesses(filePath); //noexcept - if (!procList.empty()) - errorDescr = _("The file is locked by another process:") + L"\n" + procList; - } -#endif - throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath)), errorDescr); - } -} - - -void zen::removeSymlinkPlain(const Zstring& linkPath) //throw FileError -{ -#ifdef ZEN_WIN - const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(linkPath).c_str()); - if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) - removeDirectoryPlain(linkPath); //throw FileError - else - removeFilePlain(linkPath); //throw FileError - -#elif defined ZEN_LINUX || defined ZEN_MAC - removeFilePlain(linkPath); //throw FileError -#endif -} - - -void zen::removeDirectoryPlain(const Zstring& dirPath) //throw FileError -{ -#ifdef ZEN_WIN - const wchar_t functionName[] = L"RemoveDirectory"; - if (!::RemoveDirectory(applyLongPathPrefix(dirPath).c_str())) -#elif defined ZEN_LINUX || defined ZEN_MAC - const wchar_t functionName[] = L"rmdir"; - if (::rmdir(dirPath.c_str()) != 0) -#endif - { - ErrorCode ec = getLastError(); //copy before making other system calls! -#ifdef ZEN_WIN - if (ec == ERROR_ACCESS_DENIED) //(try to) normalize file attributes: NEEDED! even folders and symlinks can have FILE_ATTRIBUTE_READONLY set! - if (::SetFileAttributes(applyLongPathPrefix(dirPath).c_str(), FILE_ATTRIBUTE_NORMAL)) - { - if (::RemoveDirectory(applyLongPathPrefix(dirPath).c_str())) //now try again... - return; - ec = ::GetLastError(); - } -#elif defined ZEN_LINUX || defined ZEN_MAC - 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; - } -#endif - 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 -{ -#ifndef NDEBUG //[!] no symlinks in this context!!! Do NOT traverse into it deleting contained files!!! - try { assert(getItemType(folderPath) != ItemType::SYMLINK); /*throw FileError*/ } - catch (FileError&) {} -#endif - 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 -{ -#ifdef ZEN_WIN - const Zstring pathSourceFmt = applyLongPathPrefix(pathSource); - const Zstring pathTargetFmt = applyLongPathPrefix(pathTarget); - - if (!::MoveFileEx(pathSourceFmt.c_str(), //__in LPCTSTR lpExistingFileName, - pathTargetFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, - 0)) //__in DWORD dwFlags - { - DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - - if (ec == ERROR_ACCESS_DENIED) //MoveFileEx may fail to rename a read-only file on a SAMBA-share -> (try to) handle this - { - const DWORD oldAttr = ::GetFileAttributes(pathSourceFmt.c_str()); - if (oldAttr != INVALID_FILE_ATTRIBUTES && (oldAttr & FILE_ATTRIBUTE_READONLY)) - { - if (::SetFileAttributes(pathSourceFmt.c_str(), FILE_ATTRIBUTE_NORMAL)) //remove readonly-attribute - { - //try again... - if (::MoveFileEx(pathSourceFmt.c_str(), //__in LPCTSTR lpExistingFileName, - pathTargetFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, - 0)) //__in DWORD dwFlags - { - //(try to) restore file attributes - ::SetFileAttributes(pathTargetFmt.c_str(), oldAttr); //don't handle error - return; - } - else - { - ec = ::GetLastError(); //use error code from second call to ::MoveFileEx() - //cleanup: (try to) restore file attributes: assume pathSource is still existing - ::SetFileAttributes(pathSourceFmt.c_str(), oldAttr); - } - } - } - } - //begin of "regular" error reporting - const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathSource)), L"%y", L"\n" + fmtPath(pathTarget)); - std::wstring errorDescr = formatSystemError(L"MoveFileEx", ec); - -#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message - if (ec == ERROR_SHARING_VIOLATION || - ec == ERROR_LOCK_VIOLATION) - { - const std::wstring procList = vista::getLockingProcesses(pathSource); //noexcept - if (!procList.empty()) - errorDescr = _("The file is locked by another process:") + L"\n" + procList; - } -#endif - if (ec == ERROR_NOT_SAME_DEVICE) - throw ErrorDifferentVolume(errorMsg, errorDescr); - if (ec == ERROR_ALREADY_EXISTS || //-> used on Win7 x64 - ec == ERROR_FILE_EXISTS) //-> used by XP??? - throw ErrorTargetExisting(errorMsg, errorDescr); - throw FileError(errorMsg, errorDescr); - } - -#elif defined ZEN_LINUX || defined ZEN_MAC - //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); -#endif -} - - -#ifdef ZEN_WIN -/*small wrapper around -::GetShortPathName() -::GetLongPathName() */ -template <typename Function> -Zstring getFilenameFmt(const Zstring& filePath, Function fun) //throw(); returns empty string on error -{ - const Zstring filePathFmt = applyLongPathPrefix(filePath); - - const DWORD bufferSize = fun(filePathFmt.c_str(), nullptr, 0); - if (bufferSize == 0) - return Zstring(); - - std::vector<wchar_t> buffer(bufferSize); - - const DWORD charsWritten = fun(filePathFmt.c_str(), //__in LPCTSTR lpszShortPath, - &buffer[0], //__out LPTSTR lpszLongPath, - bufferSize); //__in DWORD cchBuffer - if (charsWritten == 0 || charsWritten >= bufferSize) - return Zstring(); - - return &buffer[0]; -} - - -Zstring findUnused8Dot3Name(const Zstring& filePath) //throw FileError -{ - Opt<Zstring> parentPath = getParentFolderPath(filePath); - assert(parentPath); - const Zstring pathPrefix = parentPath ? appendSeparator(*parentPath) : Zstring(); - - //extension needn't contain reasonable data - Zstring extension = getFileExtension(filePath); - if (extension.empty()) - extension = Zstr("FFS"); - else if (extension.length() > 3) - extension.resize(3); - - for (int index = 0; index < 100000000; ++index) //filePath must be representable by <= 8 characters - { - const Zstring testPath = pathPrefix + numberTo<Zstring>(index) + Zchar('.') + extension; - if (!getItemTypeIfExists(testPath)) //throw FileError - return testPath; - } - throw std::runtime_error(std::string("100,000,000 files, one for each number, exist in this directory? You're kidding...") + utfCvrtTo<std::string>(pathPrefix) + - "\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); -} - - -bool have8dot3NameClash(const Zstring& itemPath) -{ - try - { - /*ItemType type =*/ getItemType(itemPath); //throw FileError - } - catch (FileError&) { return false; } - - const Zstring nameOrig = afterLast(itemPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); - const Zstring nameShort = afterLast(getFilenameFmt(itemPath, ::GetShortPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error - const Zstring nameLong = afterLast(getFilenameFmt(itemPath, ::GetLongPathName ), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); // - - if (!nameShort.empty() && - !nameLong .empty() && - equalFilePath(nameOrig, nameShort) && - !equalFilePath(nameShort, nameLong)) - { - //for itemPath short and long file name are equal and another unrelated file happens to have the same short name - //e.g. itemPath == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1" - return true; - } - return false; -} - -class Fix8Dot3NameClash //throw FileError -{ -public: - Fix8Dot3NameClash(const Zstring& filePath) - { - const Zstring longName = afterLast(getFilenameFmt(filePath, ::GetLongPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error - - Opt<Zstring> parentPath = getParentFolderPath(filePath); - assert(parentPath); - unrelatedFilePath_ = parentPath ? appendSeparator(*parentPath) : Zstring(); - unrelatedFilePath_ += longName; - - //find another name in short format: this ensures the actual short name WILL be renamed as well! - parkedFilePath_ = findUnused8Dot3Name(filePath); //throw FileError - - //move already existing short name out of the way for now - renameFile_sub(unrelatedFilePath_, parkedFilePath_); //throw FileError, ErrorDifferentVolume - //DON'T call renameFile() to avoid reentrance! - } - - ~Fix8Dot3NameClash() - { - //the file system should assign this unrelated file a new (unique) short name - try - { - renameFile_sub(parkedFilePath_, unrelatedFilePath_); //throw FileError, ErrorDifferentVolume - } - catch (FileError&) {} - } -private: - Zstring unrelatedFilePath_; - Zstring parkedFilePath_; -}; -#endif -} - - -//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&) - { -#ifdef ZEN_WIN - //try to handle issues with already existing short 8.3 file names on Windows - if (have8dot3NameClash(pathTarget)) - { - Fix8Dot3NameClash dummy(pathTarget); //throw FileError; move clashing file path to the side - //now try again... - renameFile_sub(pathSource, pathTarget); //throw FileError - return; - } -#endif - throw; - } -} - - -namespace -{ -#ifdef ZEN_WIN -void setFileTimeByHandle(HANDLE hFile, //throw FileError - const FILETIME* creationTime, //optional - const FILETIME& lastWriteTime, - const Zstring& filePath) //for error message only -{ - if (!::SetFileTime(hFile, //__in HANDLE hFile, - creationTime, //__in_opt const FILETIME *lpCreationTime, - nullptr, //__in_opt const FILETIME *lpLastAccessTime, - &lastWriteTime)) //__in_opt const FILETIME *lpLastWriteTime - { - const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - - auto toLargeInteger = [](const FILETIME& ft) -> LARGE_INTEGER - { - LARGE_INTEGER tmp = {}; - tmp.LowPart = ft.dwLowDateTime; - tmp.HighPart = ft.dwHighDateTime; - return tmp; - }; - -#ifdef ZEN_WIN_VISTA_AND_LATER - //function may fail if file is read-only: https://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 - if (ec == ERROR_ACCESS_DENIED) - { - BY_HANDLE_FILE_INFORMATION fileInfo = {}; - if (::GetFileInformationByHandle(hFile, &fileInfo)) - if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY) - { - FILE_BASIC_INFO basicInfo = {}; //undocumented: file times of "0" stand for "don't change" - basicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; //[!] the bug in the ticket above requires we set this together with file times!!! - basicInfo.LastWriteTime = toLargeInteger(lastWriteTime); // - if (creationTime) - basicInfo.CreationTime = toLargeInteger(*creationTime); - - //set file time + attributes - if (!::SetFileInformationByHandle(hFile, //__in HANDLE hFile, - FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, - &basicInfo, //__in LPVOID lpFileInformation, - sizeof(basicInfo))) //__in DWORD dwBufferSize - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(filePath)), L"SetFileInformationByHandle"); - - //(try to) restore original file attributes - FILE_BASIC_INFO basicInfo2 = {}; - basicInfo2.FileAttributes = fileInfo.dwFileAttributes; - ::SetFileInformationByHandle(hFile, //__in HANDLE hFile, - FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, - &basicInfo2, //__in LPVOID lpFileInformation, - sizeof(basicInfo2)); //__in DWORD dwBufferSize - return; - } - } -#endif - - std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)); - - //add more meaningful message: FAT accepts only a subset of the NTFS date range - if (ec == ERROR_INVALID_PARAMETER) -#ifdef ZEN_WIN_VISTA_AND_LATER - if (getFatType(hFile) != FatType::NONE) //exFAT has the same date range like FAT/FAT32: http://www.freefilesync.org/forum/viewtopic.php?t=4051 -#else - if (getFatType(filePath) != FatType::NONE) -#endif - { - //let's not fail due to an invalid creation time on FAT: http://www.freefilesync.org/forum/viewtopic.php?t=2278 - if (creationTime) //retry (single-level recursion at most!) - return setFileTimeByHandle(hFile, nullptr, lastWriteTime, filePath); //throw FileError - - //if the ERROR_INVALID_PARAMETER is due to an invalid date, enhance message: - const LARGE_INTEGER writeTimeInt = toLargeInteger(lastWriteTime); - if (writeTimeInt.QuadPart < 0x01a8e79fe1d58000 || //1980-01-01 https://en.wikipedia.org/wiki/Time_formatting_and_storage_bugs#Year_2100 - writeTimeInt.QuadPart >= 0x022f716377640000) //2100-01-01 - { - errorMsg += L"\nA FAT volume can only store dates from 1980 to 2099:\n" "\twrite time (UTC):"; - SYSTEMTIME st = {}; - if (::FileTimeToSystemTime(&lastWriteTime, //__in const FILETIME *lpFileTime, - &st)) //__out LPSYSTEMTIME lpSystemTime - { - //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!! - int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); - if (bufferSize > 0) - { - std::vector<wchar_t> buffer(bufferSize); - if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale, - 0, //_In_ DWORD dwFlags, - &st, //_In_opt_ const SYSTEMTIME *lpDate, - nullptr, //_In_opt_ LPCTSTR lpFormat, - &buffer[0], //_Out_opt_ LPTSTR lpDateStr, - bufferSize) > 0) //_In_ int cchDate - errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! - } - - bufferSize = ::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); - if (bufferSize > 0) - { - std::vector<wchar_t> buffer(bufferSize); - if (::GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, &buffer[0], bufferSize) > 0) - errorMsg += std::wstring(L" ") + &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! - } - } - errorMsg += L" (" + numberTo<std::wstring>(writeTimeInt.QuadPart) + L")"; //just in case the above date formatting fails - } - } - - throw FileError(errorMsg, formatSystemError(L"SetFileTime", ec)); - } -} - - -void setWriteTimeNative(const Zstring& itemPath, - const FILETIME& lastWriteTime, - const FILETIME* creationTime, //optional - ProcSymlink procSl) //throw FileError -{ - //privilege SE_BACKUP_NAME doesn't seem to be required here for symbolic links - //note: setting privileges requires admin rights! - - //opening newly created target file may fail due to some AV-software scanning it: no error, we will wait! - //http://support.microsoft.com/?scid=kb%3Ben-us%3B316609&x=17&y=20 - //-> enable as soon it turns out it is required! - - /*const int retryInterval = 50; - const int maxRetries = 2000 / retryInterval; - for (int i = 0; i < maxRetries; ++i) - { - */ - - /* - if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION) - ::Sleep(retryInterval); //wait then retry - else //success or unknown failure - break; - } - */ - //temporarily reset read-only flag if required - DWORD attribsToRestore = INVALID_FILE_ATTRIBUTES; - ZEN_ON_SCOPE_EXIT( - if (attribsToRestore != INVALID_FILE_ATTRIBUTES) - ::SetFileAttributes(applyLongPathPrefix(itemPath).c_str(), attribsToRestore); - ); - - auto removeReadonly = [&]() -> bool //throw FileError; may need to remove the readonly-attribute (e.g. on FAT usb drives) - { - if (attribsToRestore == INVALID_FILE_ATTRIBUTES) - { - const DWORD attribs = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str()); - if (attribs == INVALID_FILE_ATTRIBUTES) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"GetFileAttributes"); - - if (attribs & FILE_ATTRIBUTE_READONLY) - { - if (!::SetFileAttributes(applyLongPathPrefix(itemPath).c_str(), FILE_ATTRIBUTE_NORMAL)) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(itemPath)), L"SetFileAttributes"); - - attribsToRestore = attribs; //reapplied on scope exit - return true; - } - } - return false; - }; - - auto openFile = [&](bool conservativeApproach) - { - return ::CreateFile(applyLongPathPrefix(itemPath).c_str(), //_In_ LPCTSTR lpFileName, - (conservativeApproach ? - //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES: they silently fail later during SetFileTime()! - //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430 - //Citrix shares seem to have this issue, too, but at least fail with "access denied" => try generic access first: - GENERIC_READ | GENERIC_WRITE : - //avoids mysterious "access denied" when using "GENERIC_READ | GENERIC_WRITE" on a read-only file, even *after* read-only was removed directly before the call! - //http://sourceforge.net/tracker/?func=detail&atid=1093080&aid=3514569&group_id=234430 - //since former gives an error notification we may very well try FILE_WRITE_ATTRIBUTES second. - FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES), //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - (procSl == ProcSymlink::DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0) | - FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes, - nullptr); //_In_opt_ HANDLE hTemplateFile - }; - { - //extra scope for debug check below - - HANDLE hFile = INVALID_HANDLE_VALUE; - for (int i = 0; i < 2; ++i) //we will get this handle, no matter what! :) - { - //1. be conservative - hFile = openFile(true /*GENERIC_WRITE*/); - if (hFile == INVALID_HANDLE_VALUE) - { - if (::GetLastError() == ERROR_ACCESS_DENIED) //fails if file is read-only (or for "other" reasons) - if (removeReadonly()) //throw FileError - continue; - - //2. be a *little* fancy - hFile = openFile(false /*FILE_WRITE_ATTRIBUTES*/); - if (hFile == INVALID_HANDLE_VALUE) - { - const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - if (ec == ERROR_ACCESS_DENIED) - if (removeReadonly()) //throw FileError - continue; - - //3. after these herculean stunts we give up... - throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), formatSystemError(L"CreateFile", ec)); - } - } - break; - } - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - -#if 0 //waiting for user feedback... -#ifdef ZEN_WIN_VISTA_AND_LATER - //bugs, bugs, bugs.... on "SharePoint" SetFileAttributes() seems to affect file modification time: http://www.freefilesync.org/forum/viewtopic.php?t=3699 - //on Vista we can avoid reopening the file (and the SharePoint bug) - ZEN_ON_SCOPE_EXIT( - if (attribsToRestore != INVALID_FILE_ATTRIBUTES) - { - FILE_BASIC_INFO basicInfo = {}; - basicInfo.FileAttributes = attribsToRestore; - ::SetFileInformationByHandle(hFile, //__in HANDLE hFile, - FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, - &basicInfo, //__in LPVOID lpFileInformation, - sizeof(basicInfo)); //__in DWORD dwBufferSize - attribsToRestore = INVALID_FILE_ATTRIBUTES; - } - ); -#endif -#endif - - setFileTimeByHandle(hFile, creationTime, lastWriteTime, itemPath); //throw FileError - } -#ifndef NDEBUG //verify written data - FILETIME creationTimeDbg = {}; - FILETIME lastWriteTimeDbg = {}; - - HANDLE hFile = ::CreateFile(applyLongPathPrefix(itemPath).c_str(), //_In_ LPCTSTR lpFileName, - FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - (procSl == ProcSymlink::DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0) | - FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes, - nullptr); //_In_opt_ HANDLE hTemplateFile - assert(hFile != INVALID_HANDLE_VALUE); - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - - assert(::GetFileTime(hFile, //probably more up to date than GetFileAttributesEx()!? - &creationTimeDbg, - nullptr, - &lastWriteTimeDbg)); - - assert(std::abs(filetimeToTimeT(lastWriteTimeDbg) - filetimeToTimeT(lastWriteTime)) <= 2); //respect 2 second FAT/FAT32 precision - //assert(std::abs(filetimeToTimeT(creationTimeDbg ) - filetimeToTimeT(creationTime )) <= 2); -> creation time not available for Linux-hosted Samba shares! -#endif -} - - -#elif defined ZEN_LINUX -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"); - } -} - - -#elif defined ZEN_MAC -struct AttrBufFileTimes -{ - std::uint32_t length = 0; - struct ::timespec createTime = {}; //keep order; see docs! - struct ::timespec writeTime = {}; // -} __attribute__((aligned(4), packed)); - - -void setWriteTimeNative(const Zstring& itemPath, - const struct ::timespec& writeTime, - const struct ::timespec* createTime, //optional - ProcSymlink procSl) //throw FileError -{ - //OS X: utime() is obsoleted by utimes()! utimensat() not yet implemented - //use ::setattrlist() instead of ::utimes() => 1. set file creation times 2. nanosecond precision - //https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/setattrlist.2.html - - struct ::attrlist attribs = {}; - attribs.bitmapcount = ATTR_BIT_MAP_COUNT; - attribs.commonattr = (createTime ? ATTR_CMN_CRTIME : 0) | ATTR_CMN_MODTIME; - - AttrBufFileTimes attrBuf; - if (createTime) - { - attrBuf.createTime.tv_sec = createTime->tv_sec; - attrBuf.createTime.tv_nsec = createTime->tv_nsec; - } - attrBuf.writeTime.tv_sec = writeTime.tv_sec; - attrBuf.writeTime.tv_nsec = writeTime.tv_nsec; - - const int rv = ::setattrlist(itemPath.c_str(), //const char* path, - &attribs, //struct ::attrlist* attrList, - createTime ? &attrBuf.createTime : &attrBuf.writeTime, //void* attrBuf, - (createTime ? sizeof(attrBuf.createTime) : 0) + sizeof(attrBuf.writeTime), //size_t attrBufSize, - procSl == ProcSymlink::DIRECT ? FSOPT_NOFOLLOW : 0); //unsigned long options - if (rv != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(itemPath)), L"setattrlist"); -} - -/* -void getFileTimeRaw(int fd, //throw FileError - const Zstring& itemPath, //for error reporting only - struct ::timespec& createTime, //out - struct ::timespec& writeTime) // -{ - //https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/getattrlist.2.html - struct ::attrlist attribs = {}; - attribs.bitmapcount = ATTR_BIT_MAP_COUNT; - attribs.commonattr = ATTR_CMN_CRTIME | ATTR_CMN_MODTIME; - - AttrBufFileTimes attrBuf; - - const int rv = ::fgetattrlist(fd, //int fd, - &attribs, //struct ::attrlist* attrList, - &attrBuf, //void* attrBuf, - sizeof(attrBuf), //size_t attrBufSize, - 0); //unsigned long options - if (rv != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itemPath)), L"fgetattrlist"); - - createTime.tv_sec = attrBuf.createTime.tv_sec; - createTime.tv_nsec = attrBuf.createTime.tv_nsec; - writeTime.tv_sec = attrBuf.writeTime.tv_sec; - writeTime.tv_nsec = attrBuf.writeTime.tv_nsec; -} -*/ - -//PERF: ~10µs per call for "int fd" variant (irrespective if file is local or on mounted USB; test case: 3000 files) -bool hasNativeSupportForExtendedAtrributes(const Zstring& filePath, //throw FileError - int fd = -1) //speed up!? -{ - struct ::statfs volInfo = {}; - if ((fd != -1 ? ::fstatfs(fd, &volInfo) : ::statfs(filePath.c_str(), &volInfo)) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), fd != -1 ? L"fstatfs" : L"statfs"); - - struct ::attrlist attribs = {}; - attribs.bitmapcount = ATTR_BIT_MAP_COUNT; - attribs.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES; - - struct - { - std::uint32_t length = 0; - vol_capabilities_attr_t volCaps{}; - } __attribute__((aligned(4), packed)) attrBuf; - - const int rv = ::getattrlist(volInfo.f_mntonname, //const char* path, - &attribs, //struct ::attrlist* attrList, - &attrBuf, //void* attrBuf, - sizeof(attrBuf), //size_t attrBufSize, - 0); //unsigned long options - if (rv != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(volInfo.f_mntonname)), L"getattrlist"); - - return (attrBuf.volCaps.valid [VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) != 0 && - (attrBuf.volCaps.capabilities[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) != 0; -} -#endif -} - - -void zen::setFileTime(const Zstring& filePath, std::int64_t modTime, ProcSymlink procSl) //throw FileError -{ -#ifdef ZEN_WIN - setWriteTimeNative(filePath, timetToFileTime(modTime), nullptr, procSl); //throw FileError - -#elif defined ZEN_LINUX - struct ::timespec writeTime = {}; - writeTime.tv_sec = modTime; - setWriteTimeNative(filePath, writeTime, procSl); //throw FileError - -#elif defined ZEN_MAC - struct ::timespec writeTime = {}; - writeTime.tv_sec = modTime; - setWriteTimeNative(filePath, writeTime, nullptr, procSl); //throw FileError -#endif -} - - -bool zen::supportsPermissions(const Zstring& dirPath) //throw FileError -{ -#ifdef ZEN_WIN - const DWORD bufferSize = MAX_PATH + 1; - std::vector<wchar_t> buffer(bufferSize); - - if (!::GetVolumePathName(dirPath.c_str(), //__in LPCTSTR lpszFileName, - &buffer[0], //__out LPTSTR lpszVolumePathName, - bufferSize)) //__in DWORD cchBufferLength - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(dirPath)), L"GetVolumePathName"); - - const Zstring volumePath = appendSeparator(&buffer[0]); - - DWORD fsFlags = 0; - if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, - nullptr, //__out LPTSTR lpVolumeNameBuffer, - 0, //__in DWORD nVolumeNameSize, - nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, - nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - &fsFlags, //__out_opt LPDWORD lpFileSystemFlags, - nullptr, //__out LPTSTR lpFileSystemNameBuffer, - 0)) //__in DWORD nFileSystemNameSize - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(dirPath)), L"GetVolumeInformation"); - - return (fsFlags & FILE_PERSISTENT_ACLS) != 0; - -#elif defined ZEN_LINUX || defined ZEN_MAC - return true; -#endif -} - - -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 ZEN_WIN - //in contrast to ::SetSecurityInfo(), ::SetFileSecurity() seems to honor the "inherit DACL/SACL" flags - //CAVEAT: if a file system does not support ACLs, GetFileSecurity() will return successfully with a *valid* security descriptor containing *no* ACL entries! - - const bool isSymlinkSource = getItemType(sourcePath) == ItemType::SYMLINK; //throw FileError - const bool isSymlinkTarget = getItemType(targetPath) == ItemType::SYMLINK; //throw FileError - - //NOTE: ::GetFileSecurity()/::SetFileSecurity() do NOT follow Symlinks! getResolvedSymlinkPath() requires Vista or later! - const Zstring sourceResolved = procSl == ProcSymlink::FOLLOW && isSymlinkSource ? getResolvedSymlinkPath(sourcePath) : sourcePath; //throw FileError - const Zstring targetResolved = procSl == ProcSymlink::FOLLOW && isSymlinkTarget ? getResolvedSymlinkPath(targetPath) : targetPath; // - - //setting privileges requires admin rights! - try - { - //enable privilege: required to read/write SACL information (only) - activatePrivilege(PrivilegeName::SECURITY); //throw FileError - //Note: trying to copy SACL (SACL_SECURITY_INFORMATION) may return ERROR_PRIVILEGE_NOT_HELD (1314) on Samba shares. This is not due to missing privileges! - //However, this is okay, since copying NTFS permissions doesn't make sense in this case anyway - - //the following privilege may be required according to https://msdn.microsoft.com/en-us/library/aa364399 (although not needed nor active in my tests) - activatePrivilege(PrivilegeName::BACKUP); //throw FileError - - //enable privilege: required to copy owner information - activatePrivilege(PrivilegeName::RESTORE); //throw FileError - } - catch (const FileError& e)//add some more context description (e.g. user is not an admin) - { - throw FileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourceResolved)), e.toString()); - } - - - std::vector<char> buffer(10000); //example of actually required buffer size: 192 bytes - for (;;) - { - DWORD bytesNeeded = 0; - if (::GetFileSecurity(applyLongPathPrefix(sourceResolved).c_str(), //__in LPCTSTR lpFileName, -> long path prefix IS needed, although it is NOT mentioned on MSDN!!! - DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | //__in SECURITY_INFORMATION RequestedInformation, - OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, - reinterpret_cast<PSECURITY_DESCRIPTOR>(&buffer[0]), //__out_opt PSECURITY_DESCRIPTOR pSecurityDescriptor, - static_cast<DWORD>(buffer.size()), //__in DWORD nLength, - &bytesNeeded)) //__out LPDWORD lpnLengthNeeded - break; - //failure: ... - if (bytesNeeded > buffer.size()) - buffer.resize(bytesNeeded); - else - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourceResolved)), L"GetFileSecurity"); - } - SECURITY_DESCRIPTOR& secDescr = reinterpret_cast<SECURITY_DESCRIPTOR&>(buffer[0]); - - /* - SECURITY_DESCRIPTOR_CONTROL secCtrl = 0; - { - DWORD ctrlRev = 0; - if (!::GetSecurityDescriptorControl(&secDescr, // __in PSECURITY_DESCRIPTOR pSecurityDescriptor, - &secCtrl, // __out PSECURITY_DESCRIPTOR_CONTROL pControl, - &ctrlRev)) //__out LPDWORD lpdwRevision - throw FileErro - } - //interesting flags: - //#define SE_DACL_PRESENT (0x0004) - //#define SE_SACL_PRESENT (0x0010) - //#define SE_DACL_PROTECTED (0x1000) - //#define SE_SACL_PROTECTED (0x2000) - */ - - if (!::SetFileSecurity(applyLongPathPrefix(targetResolved).c_str(), //__in LPCTSTR lpFileName, -> long path prefix IS needed, although it is NOT mentioned on MSDN!!! - OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | - DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInformation, - &secDescr)) //__in PSECURITY_DESCRIPTOR pSecurityDescriptor - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetResolved)), L"SetFileSecurity"); - - /* - PSECURITY_DESCRIPTOR buffer = nullptr; - PSID owner = nullptr; - PSID group = nullptr; - PACL dacl = nullptr; - PACL sacl = nullptr; - - //File Security and Access Rights: https://msdn.microsoft.com/en-us/library/aa364399 - //SECURITY_INFORMATION Access Rights: https://msdn.microsoft.com/en-us/library/windows/desktop/aa379573 - const HANDLE hSource = ::CreateFile(applyLongPathPrefix(source).c_str(), - READ_CONTROL | ACCESS_SYSTEM_SECURITY, //ACCESS_SYSTEM_SECURITY required for SACL access - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - nullptr, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | (procSl == SymLinkHandling::DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory - nullptr); - if (hSource == INVALID_HANDLE_VALUE) - throw FileError - ZEN_ON_SCOPE_EXIT(::CloseHandle(hSource)); - - // DWORD rc = ::GetNamedSecurityInfo(const_cast<WCHAR*>(applyLongPathPrefix(source).c_str()), -> does NOT dereference symlinks! - DWORD rc = ::GetSecurityInfo(hSource, //__in LPTSTR pObjectName, - SE_FILE_OBJECT, //__in SE_OBJECT_TYPE ObjectType, - OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | - DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInfo, - &owner, //__out_opt PSID *ppsidOwner, - &group, //__out_opt PSID *ppsidGroup, - &dacl, //__out_opt PACL *ppDacl, - &sacl, //__out_opt PACL *ppSacl, - &buffer); //__out_opt PSECURITY_DESCRIPTOR *ppSecurityDescriptor - if (rc != ERROR_SUCCESS) - throw FileError - ZEN_ON_SCOPE_EXIT(::LocalFree(buffer)); - - SECURITY_DESCRIPTOR_CONTROL secCtrl = 0; - { - DWORD ctrlRev = 0; - if (!::GetSecurityDescriptorControl(buffer, // __in PSECURITY_DESCRIPTOR pSecurityDescriptor, - &secCtrl, // __out PSECURITY_DESCRIPTOR_CONTROL pControl, - &ctrlRev))//__out LPDWORD lpdwRevision - throw FileError - } - - //may need to remove the readonly-attribute - FileUpdateHandle targetHandle(target, [=] - { - return ::CreateFile(applyLongPathPrefix(target).c_str(), // lpFileName - GENERIC_WRITE | WRITE_OWNER | WRITE_DAC | ACCESS_SYSTEM_SECURITY, // dwDesiredAccess: all four seem to be required!!! - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // dwShareMode - nullptr, // lpSecurityAttributes - OPEN_EXISTING, // dwCreationDisposition - FILE_FLAG_BACKUP_SEMANTICS | (procSl == SymLinkHandling::DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), // dwFlagsAndAttributes - nullptr); // hTemplateFile - }); - - if (targetHandle.get() == INVALID_HANDLE_VALUE) - throw FileError - - SECURITY_INFORMATION secFlags = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION; - - //SACL/DACL inheritence flag is NOT copied by default: we have to tell ::SetSecurityInfo(() to enable/disable it manually! - //if (secCtrl & SE_DACL_PRESENT) - secFlags |= (secCtrl & SE_DACL_PROTECTED) ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION; - //if (secCtrl & SE_SACL_PRESENT) - secFlags |= (secCtrl & SE_SACL_PROTECTED) ? PROTECTED_SACL_SECURITY_INFORMATION : UNPROTECTED_SACL_SECURITY_INFORMATION; - - - // rc = ::SetNamedSecurityInfo(const_cast<WCHAR*>(applyLongPathPrefix(target).c_str()), //__in LPTSTR pObjectName, -> does NOT dereference symlinks! - rc = ::SetSecurityInfo(targetHandle.get(), //__in LPTSTR pObjectName, - SE_FILE_OBJECT, //__in SE_OBJECT_TYPE ObjectType, - secFlags, //__in SECURITY_INFORMATION SecurityInfo, - owner, //__in_opt PSID psidOwner, - group, //__in_opt PSID psidGroup, - dacl, //__in_opt PACL pDacl, - sacl); //__in_opt PACL pSacl - - if (rc != ERROR_SUCCESS) - throw FileError - */ - -#elif defined ZEN_LINUX - -#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"); - } - -#elif defined ZEN_MAC - copyfile_flags_t flags = COPYFILE_ACL | COPYFILE_STAT; //unfortunately COPYFILE_STAT copies modtime, too! - if (procSl == ProcSymlink::DIRECT) - flags |= COPYFILE_NOFOLLOW; - - if (::copyfile(sourcePath.c_str(), targetPath.c_str(), nullptr, flags) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtPath(sourcePath)), L"%y", L"\n" + fmtPath(targetPath)), L"copyfile"); - - //owner is *not* copied with ::copyfile(): - - 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"); - } - 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"); - } -#endif -} -} - - -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<PathDetails> pd; - try { pd = getPathDetails(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) -{ -#ifdef ZEN_WIN - auto getErrorMsg = [](const Zstring& path) { return replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(path)); }; - - //deliberately don't support creating irregular folders like "...." https://social.technet.microsoft.com/Forums/windows/en-US/ffee2322-bb6b-4fdf-86f9-8f93cf1fa6cb/ - if (endsWith(targetPath, L' ') || - endsWith(targetPath, L'.')) - throw FileError(getErrorMsg(targetPath), replaceCpy(_("%x is not a regular directory name."), L"%x", fmtPath(afterLast(targetPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL)))); - - //don't use ::CreateDirectoryEx: - //- it may fail with "wrong parameter (error code 87)" when source is on mapped online storage - //- automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)! - //- it isn't able to copy most junctions because of missing permissions (although target path can be retrieved alternatively!) - if (!::CreateDirectory(applyLongPathPrefixCreateDir(targetPath).c_str(), //__in LPCTSTR lpPathName, - nullptr)) //__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes - { - DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - - //handle issues with already existing short 8.3 file names on Windows - if (ec == ERROR_ALREADY_EXISTS) - if (have8dot3NameClash(targetPath)) - { - Fix8Dot3NameClash dummy(targetPath); //throw FileError; move clashing object to the side - - //now try again... - if (::CreateDirectory(applyLongPathPrefixCreateDir(targetPath).c_str(), nullptr)) - ec = ERROR_SUCCESS; - else - ec = ::GetLastError(); - } - - if (ec != ERROR_SUCCESS) - { - const std::wstring errorDescr = formatSystemError(L"CreateDirectory", ec); - - if (ec == ERROR_ALREADY_EXISTS) - throw ErrorTargetExisting(getErrorMsg(targetPath), errorDescr); - //else if (ec == ERROR_PATH_NOT_FOUND || //the ususal suspect - // ec == ERROR_FILE_NOT_FOUND) //Webdav incorrectly returns this one: http://www.freefilesync.org/forum/viewtopic.php?t=4053 - // throw ErrorTargetPathMissing(getErrorMsg(targetPath), errorDescr); - throw FileError(getErrorMsg(targetPath), errorDescr); - } - } - -#elif defined ZEN_LINUX || defined ZEN_MAC - 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); - } -#endif - - if (!sourcePath.empty()) - { -#ifdef ZEN_WIN - //optional: try to copy file attributes (dereference symlinks and junctions) - const HANDLE hDirSrc = ::CreateFile(zen::applyLongPathPrefix(sourcePath).c_str(), //_In_ LPCTSTR lpFileName, - 0, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - // FILE_FLAG_OPEN_REPARSE_POINT -> no, we follow symlinks! - FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes, - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hDirSrc != INVALID_HANDLE_VALUE) //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error... - { - ZEN_ON_SCOPE_EXIT(::CloseHandle(hDirSrc)); - - BY_HANDLE_FILE_INFORMATION dirInfo = {}; - if (::GetFileInformationByHandle(hDirSrc, &dirInfo)) - { - ::SetFileAttributes(applyLongPathPrefix(targetPath).c_str(), dirInfo.dwFileAttributes); - //copy "read-only and system attributes": https://blogs.msdn.microsoft.com/oldnewthing/20030930-00/?p=42353/ - - const bool isEncrypted = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; - const bool isCompressed = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; - - if (isEncrypted) - ::EncryptFile(targetPath.c_str()); //seems no long path is required (check passed!) - - HANDLE hDirTrg = ::CreateFile(applyLongPathPrefix(targetPath).c_str(), //_In_ LPCTSTR lpFileName, - GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess, - /*read access required for FSCTL_SET_COMPRESSION*/ - FILE_SHARE_READ | - FILE_SHARE_WRITE | - FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hDirTrg != INVALID_HANDLE_VALUE) - { - ZEN_ON_SCOPE_EXIT(::CloseHandle(hDirTrg)); - - if (isCompressed) - { - USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; - DWORD bytesReturned = 0; - /*bool rv = */::DeviceIoControl(hDirTrg, //_In_ HANDLE hDevice, - FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode, - &cmpState, //_In_opt_ LPVOID lpInBuffer, - sizeof(cmpState), //_In_ DWORD nInBufferSize, - nullptr, //_Out_opt_ LPVOID lpOutBuffer, - 0, //_In_ DWORD nOutBufferSize, - &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned, - nullptr); //_Inout_opt_ LPOVERLAPPED lpOverlapped - } - - //(try to) set creation (and modification) time - /*bool rv = */::SetFileTime(hDirTrg, //_In_ HANDLE hFile, - &dirInfo.ftCreationTime, //_Out_opt_ LPFILETIME lpCreationTime, - nullptr, //_Out_opt_ LPFILETIME lpLastAccessTime, - &dirInfo.ftLastWriteTime); //_Out_opt_ LPFILETIME lpLastWriteTime - } - } - } - -#elif defined ZEN_MAC - if (hasNativeSupportForExtendedAtrributes(targetPath)) //throw FileError - ::copyfile(sourcePath.c_str(), targetPath.c_str(), nullptr, COPYFILE_XATTR); //ignore errors, see related comments in copyFileOsSpecific() - - //(try to) set creation (and modification) time - if (dirInfo.st_birthtimespec.tv_sec != 0) - try - { - setWriteTimeNative(targetPath, dirInfo.st_mtimespec, &dirInfo.st_birthtimespec, ProcSymlink::FOLLOW); //throw FileError - //dirInfo.st_birthtime; -> only seconds-precision - //dirInfo.st_mtime; -> - } - catch (FileError&) {} -#endif - - 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 - -#ifdef ZEN_WIN - WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {}; - if (!::GetFileAttributesEx(applyLongPathPrefix(sourceLink).c_str(), //__in LPCTSTR lpFileName, - GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, - &sourceAttr)) //__out LPVOID lpFileInformation - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"GetFileAttributesEx"); - - const bool isDirLink = (sourceAttr.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - - using CreateSymbolicLinkFunc = BOOLEAN (WINAPI*)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags); - const SysDllFun<CreateSymbolicLinkFunc> createSymbolicLink(L"kernel32.dll", "CreateSymbolicLinkW"); - - if (!createSymbolicLink) - throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), - replaceCpy(_("Cannot find system function %x."), L"%x", L"\"CreateSymbolicLinkW\"")); - - const wchar_t functionName[] = L"CreateSymbolicLinkW"; - if (!createSymbolicLink(targetLink.c_str(), //__in LPTSTR lpSymlinkFileName, - seems no long path prefix is required... - linkPath.c_str(), //__in LPTSTR lpTargetFileName, - (isDirLink ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0))) //__in DWORD dwFlags -#elif defined ZEN_LINUX || defined ZEN_MAC - const wchar_t functionName[] = L"symlink"; - if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0) -#endif - 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!) -#ifdef ZEN_WIN - setWriteTimeNative(targetLink, sourceAttr.ftLastWriteTime, &sourceAttr.ftCreationTime, ProcSymlink::DIRECT); //throw FileError - -#else - 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"); - -#ifdef ZEN_LINUX - setWriteTimeNative(targetLink, sourceInfo.st_mtim, ProcSymlink::DIRECT); //throw FileError -#elif defined ZEN_MAC - if (hasNativeSupportForExtendedAtrributes(targetLink)) //throw FileError - ::copyfile(sourceLink.c_str(), targetLink.c_str(), nullptr, COPYFILE_XATTR | COPYFILE_NOFOLLOW); //ignore errors, see related comments in copyFileOsSpecific() - - setWriteTimeNative(targetLink, sourceInfo.st_mtimespec, &sourceInfo.st_birthtimespec, ProcSymlink::DIRECT); //throw FileError -#else -#error WTF -#endif -#endif - - if (copyFilePermissions) - copyItemPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError -} - - -namespace -{ -#ifdef ZEN_WIN -/* - CopyFileEx() BackupRead() ReadFile() - -------------------------------------------- -Attributes YES NO NO -create time NO NO NO -ADS YES YES NO -Encrypted YES NO(silent fail!) NO -Compressed NO NO NO -Sparse NO YES NO -Nonstandard FS YES UNKNOWN -> error writing ADS to Samba, issues reading from NAS, error copying files having "blocked" state... ect. -PERF - 6% faster - -Mark stream as compressed: FSCTL_SET_COMPRESSION - compatible with both BackupRead() and ReadFile() - - -Current support for combinations of NTFS extended attributes: - -source attr | tf normal | tf compressed | tf encrypted | handled by -============|================================================================== - --- | --- -C- E-- copyFileWindowsDefault - --S | --S -CS E-S copyFileWindowsStream - -C- | -C- -C- E-- copyFileWindowsDefault - -CS | -CS -CS E-S copyFileWindowsStream - E-- | E-- E-- E-- copyFileWindowsDefault - E-S | E-- (NOK) E-- (NOK) E-- (NOK) copyFileWindowsDefault -> may fail with ERROR_DISK_FULL for large sparse files!! - -tf := target folder -E := encrypted -C := compressed -S := sparse -NOK := current behavior is not optimal/OK yet. - -Note: - if target parent folder is compressed or encrypted, both attributes are added automatically during file creation! - - "compressed" and "encrypted" are mutually exclusive: http://support.microsoft.com/kb/223093/en-us -*/ - - -//due to issues on non-NTFS volumes, we should use the copy-as-sparse routine only if required and supported! -template <class Function> -bool canCopyAsSparse(DWORD fileAttrSource, Function getTargetFsFlags) //throw () -{ - const bool sourceIsEncrypted = (fileAttrSource & FILE_ATTRIBUTE_ENCRYPTED) != 0; - const bool sourceIsSparse = (fileAttrSource & FILE_ATTRIBUTE_SPARSE_FILE) != 0; - - if (sourceIsEncrypted || !sourceIsSparse) //BackupRead() silently fails reading encrypted files! - return false; //small perf optimization: don't check "targetFile" if not needed - - DWORD targetFsFlags = 0; - if (!getTargetFsFlags(targetFsFlags)) - { - assert(false); - return false; - } - assert(targetFsFlags != 0); - - return (targetFsFlags & FILE_SUPPORTS_SPARSE_FILES) != 0; -} - - -#ifdef ZEN_WIN_VISTA_AND_LATER -bool canCopyAsSparse(DWORD fileAttrSource, HANDLE hTargetFile) //throw () -{ - return canCopyAsSparse(fileAttrSource, [&](DWORD& targetFsFlags) -> bool - { - return ::GetVolumeInformationByHandleW(hTargetFile, //_In_ HANDLE hFile, - nullptr, //_Out_writes_opt_(nVolumeNameSize) LPWSTR lpVolumeNameBuffer, - 0, //_In_ DWORD nVolumeNameSize, - nullptr, //_Out_opt_ LPDWORD lpVolumeSerialNumber, - nullptr, //_Out_opt_ LPDWORD lpMaximumComponentLength, - &targetFsFlags, //_Out_opt_ LPDWORD lpFileSystemFlags, - nullptr, //_Out_writes_opt_(nFileSystemNameSize) LPWSTR lpFileSystemNameBuffer, - 0) != 0; //_In_ DWORD nFileSystemNameSize - }); -} -#endif - - -bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw () -{ - return canCopyAsSparse(fileAttrSource, [&targetFile](DWORD& targetFsFlags) -> bool - { - const DWORD bufferSize = MAX_PATH + 1; - std::vector<wchar_t> buffer(bufferSize); - - //full pathName need not yet exist! - if (!::GetVolumePathName(targetFile.c_str(), //__in LPCTSTR lpszFileName, - &buffer[0], //__out LPTSTR lpszVolumePathName, - bufferSize)) //__in DWORD cchBufferLength - return false; - - const Zstring volumePath = appendSeparator(&buffer[0]); - - return ::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName - nullptr, //__out_opt LPTSTR lpVolumeNameBuffer, - 0, //__in DWORD nVolumeNameSize, - nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, - nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - &targetFsFlags, //__out_opt LPDWORD lpFileSystemFlags, - nullptr, //__out LPTSTR lpFileSystemNameBuffer, - 0) != 0; //__in DWORD nFileSystemNameSize - }); -} - - -bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //throw () -{ - //follow symlinks! - HANDLE hSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(), //_In_ LPCTSTR lpFileName, - 0, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | //all shared modes are required to read files that are open in other applications - FILE_SHARE_WRITE | - FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - 0, //_In_ DWORD dwFlagsAndAttributes, - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hSource == INVALID_HANDLE_VALUE) - return false; - ZEN_ON_SCOPE_EXIT(::CloseHandle(hSource)); - - BY_HANDLE_FILE_INFORMATION sourceInfo = {}; - if (!::GetFileInformationByHandle(hSource, &sourceInfo)) - return false; - - return canCopyAsSparse(sourceInfo.dwFileAttributes, targetFile); //throw () -} - -//============================================================================================= - -enum class StreamCopyType -{ - READ_FILE, - BACKUP_READ, -}; - - -InSyncAttributes copyFileWindowsStream(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked - const Zstring& targetFile, - StreamCopyType scType, - const std::function<void(std::int64_t bytesDelta)>& notifyProgress) -{ - //try to get backup read and write privileges: help solve most "access denied" errors with FILE_FLAG_BACKUP_SEMANTICS: - //http://www.freefilesync.org/forum/viewtopic.php?t=1714 - try { activatePrivilege(PrivilegeName::BACKUP); } - catch (const FileError&) {} - try { activatePrivilege(PrivilegeName::RESTORE); } - catch (const FileError&) {} - - HANDLE hFileSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(), //_In_ LPCTSTR lpFileName, - GENERIC_READ, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - //FILE_FLAG_OVERLAPPED must not be used! - //FILE_FLAG_NO_BUFFERING should not be used! - FILE_FLAG_SEQUENTIAL_SCAN | - FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hFileSource == INVALID_HANDLE_VALUE) - { - const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - - const std::wstring errorMsg = replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)); - std::wstring errorDescr = formatSystemError(L"CreateFile", ec); - - //if file is locked throw "ErrorFileLocked" instead! - if (ec == ERROR_SHARING_VIOLATION || - ec == ERROR_LOCK_VIOLATION) - { -#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message - const std::wstring procList = vista::getLockingProcesses(sourceFile); //noexcept - if (!procList.empty()) - errorDescr = _("The file is locked by another process:") + L"\n" + procList; -#endif - throw ErrorFileLocked(errorMsg, errorDescr); - } - - throw FileError(errorMsg, errorDescr); - } - FileInput fileIn(hFileSource, sourceFile); //pass ownership - - if (notifyProgress) notifyProgress(0); //throw X! - - //---------------------------------------------------------------------- - BY_HANDLE_FILE_INFORMATION sourceInfo = {}; - if (!::GetFileInformationByHandle(fileIn.getHandle(), &sourceInfo)) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"GetFileInformationByHandle"); - - //encrypted files cannot be read with BackupRead which would fail silently! - const bool sourceIsEncrypted = (sourceInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; - if (sourceIsEncrypted && scType == StreamCopyType::BACKUP_READ) - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: Source file is encrypted."); - //---------------------------------------------------------------------- - - const DWORD validAttribs = FILE_ATTRIBUTE_NORMAL | //"This attribute is valid only if used alone." - FILE_ATTRIBUTE_READONLY | - FILE_ATTRIBUTE_HIDDEN | - FILE_ATTRIBUTE_SYSTEM | - FILE_ATTRIBUTE_ARCHIVE | //those two are not set properly (not worse than ::CopyFileEx()) - FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; // - //FILE_ATTRIBUTE_ENCRYPTED -> no! - - HANDLE hFileTarget = ::CreateFile(applyLongPathPrefix(targetFile).c_str(), //_In_ LPCTSTR lpFileName, - GENERIC_READ | GENERIC_WRITE | DELETE, //_In_ DWORD dwDesiredAccess, - //GENERIC_READ required for FSCTL_SET_COMPRESSION, DELETE for ::SetFileInformationByHandle(),FileDispositionInfo - FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - //FILE_SHARE_DELETE is required to rename file while handle is open! - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - CREATE_NEW, //_In_ DWORD dwCreationDisposition, - //FILE_FLAG_OVERLAPPED must not be used! FILE_FLAG_NO_BUFFERING should not be used! - (sourceInfo.dwFileAttributes & validAttribs) | - FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes, - FILE_FLAG_BACKUP_SEMANTICS, //-> also required by FSCTL_SET_SPARSE - nullptr); //_In_opt_ HANDLE hTemplateFile - if (hFileTarget == INVALID_HANDLE_VALUE) - { - const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)); - const std::wstring errorDescr = formatSystemError(L"CreateFile", ec); - - if (ec == ERROR_FILE_EXISTS || //confirmed to be used - ec == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 - throw ErrorTargetExisting(errorMsg, errorDescr); - - throw FileError(errorMsg, errorDescr); - } -#ifdef ZEN_WIN_VISTA_AND_LATER - FileOutput fileOut(hFileTarget, targetFile); //pass ownership - - //no need for ::DeleteFile(), we already have an open handle! Maybe this also prevents needless buffer-flushing in ::CloseHandle()??? Anyway, same behavior like ::CopyFileEx() - ZEN_ON_SCOPE_FAIL - ( - FILE_DISPOSITION_INFO di = {}; - di.DeleteFile = true; - if (!::SetFileInformationByHandle(fileOut.getHandle(), //_In_ HANDLE hFile, - FileDispositionInfo, //_In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass, - &di, //_In_ LPVOID lpFileInformation, - sizeof(di))) //_In_ DWORD dwBufferSize - assert(false); - ); -#else - ZEN_ON_SCOPE_FAIL(try { removeFilePlain(targetFile); } - catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget - - FileOutput fileOut(hFileTarget, targetFile); //pass ownership -#endif - if (notifyProgress) notifyProgress(0); //throw X! - - //---------------------------------------------------------------------- - BY_HANDLE_FILE_INFORMATION targetInfo = {}; - if (!::GetFileInformationByHandle(fileOut.getHandle(), &targetInfo)) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"GetFileInformationByHandle"); - - //return up-to-date file attributes - InSyncAttributes newAttrib; - newAttrib.fileSize = get64BitUInt(sourceInfo.nFileSizeLow, sourceInfo.nFileSizeHigh); - newAttrib.modificationTime = filetimeToTimeT(sourceInfo.ftLastWriteTime); - newAttrib.sourceFileId = extractFileId(sourceInfo); - newAttrib.targetFileId = extractFileId(targetInfo); - - //#################### copy NTFS compressed attribute ######################### - const bool sourceIsCompressed = (sourceInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; - const bool targetIsCompressed = (targetInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CreateFile if target parent folder is compressed! - if (sourceIsCompressed && !targetIsCompressed) - { - USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; - DWORD bytesReturned = 0; - if (!::DeviceIoControl(fileOut.getHandle(), //_In_ HANDLE hDevice, - FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode, - &cmpState, //_In_opt_ LPVOID lpInBuffer, - sizeof(cmpState), //_In_ DWORD nInBufferSize, - nullptr, //_Out_opt_ LPVOID lpOutBuffer, - 0, //_In_ DWORD nOutBufferSize, - &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned, - nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped - {} //may legitimately fail with ERROR_INVALID_FUNCTION if: - // - target folder is encrypted - // - target volume does not support compressed attribute -> unlikely in this context - } - //############################################################################# - - //although it seems the sparse attribute is set automatically by BackupWrite, we are required to do this manually: http://support.microsoft.com/kb/271398/en-us - //Quote: It is the responsibility of the backup utility to apply file attributes to a file after it is restored by using BackupWrite. - //The application should retrieve the attributes by using GetFileAttributes prior to creating a backup with BackupRead. - //If a file originally had the sparse attribute (FILE_ATTRIBUTE_SPARSE_FILE), the backup utility must explicitly set the - //attribute on the restored file. - if (scType == StreamCopyType::BACKUP_READ) -#ifdef ZEN_WIN_VISTA_AND_LATER - if (canCopyAsSparse(sourceInfo.dwFileAttributes, fileOut.getHandle())) //throw () -#else - if (canCopyAsSparse(sourceInfo.dwFileAttributes, targetFile)) //throw () -#endif - { - DWORD bytesReturned = 0; - if (!::DeviceIoControl(fileOut.getHandle(), //_In_ HANDLE hDevice, - FSCTL_SET_SPARSE, //_In_ DWORD dwIoControlCode, - nullptr, //_In_opt_ LPVOID lpInBuffer, - 0, //_In_ DWORD nInBufferSize, - nullptr, //_Out_opt_ LPVOID lpOutBuffer, - 0, //_In_ DWORD nOutBufferSize, - &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned, - nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(targetFile)), L"DeviceIoControl, FSCTL_SET_SPARSE"); - } - - //---------------------------------------------------------------------- - if (scType == StreamCopyType::READ_FILE) - unbufferedStreamCopy(fileIn, fileOut, notifyProgress); //throw FileError, X - else - { - const DWORD BUFFER_SIZE = std::max(128 * 1024, static_cast<int>(sizeof(WIN32_STREAM_ID))); //must be greater than sizeof(WIN32_STREAM_ID)! - std::vector<BYTE> buffer(BUFFER_SIZE); - - LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite() - LPVOID contextWrite = nullptr; // - - ZEN_ON_SCOPE_EXIT( - if (contextRead ) ::BackupRead (0, nullptr, 0, nullptr, true, false, &contextRead); //MSDN: "lpContext must be passed [...] all other parameters are ignored." - if (contextWrite) ::BackupWrite(0, nullptr, 0, nullptr, true, false, &contextWrite); ); // - - //stream-copy sourceFile to targetFile - bool eof = false; - bool someBytesRead = false; //try to detect failure reading encrypted files - do - { - DWORD bytesRead = 0; - if (!::BackupRead(fileIn.getHandle(), //__in HANDLE hFile, - &buffer[0], //__out LPBYTE lpBuffer, - BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, - &bytesRead, //__out LPDWORD lpNumberOfBytesRead, - false, //__in BOOL bAbort, - false, //__in BOOL bProcessSecurity, - &contextRead)) //__out LPVOID *lpContext - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead"); //better use fine-granular error messages "reading/writing"! - - if (bytesRead > BUFFER_SIZE) - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: buffer overflow."); //user should never see this - - if (bytesRead < BUFFER_SIZE) - eof = true; - - DWORD bytesWritten = 0; - if (!::BackupWrite(fileOut.getHandle(), //__in HANDLE hFile, - &buffer[0], //__in LPBYTE lpBuffer, - bytesRead, //__in DWORD nNumberOfBytesToWrite, - &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten, - false, //__in BOOL bAbort, - false, //__in BOOL bProcessSecurity, - &contextWrite)) //__out LPVOID *lpContext - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite"); - - if (bytesWritten != bytesRead) - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite: incomplete write."); //user should never see this - - //total bytes transferred may be larger than file size! context information + ADS or smaller (sparse, compressed)! - if (notifyProgress) notifyProgress(bytesRead); //throw X! - - if (bytesRead > 0) - someBytesRead = true; - } - while (!eof); - - //::BackupRead() silently fails reading encrypted files -> double check! - if (!someBytesRead && get64BitUInt(sourceInfo.nFileSizeLow, sourceInfo.nFileSizeHigh) != 0U) - //note: there is no guaranteed ordering relation beween bytes transferred and file size! Consider ADS (>) and compressed/sparse files (<)! - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()" - } - - //time needs to be set at the end: WriteFile/BackupWrite() change modification time - setFileTimeByHandle(fileOut.getHandle(), &sourceInfo.ftCreationTime, sourceInfo.ftLastWriteTime, targetFile); //throw FileError - - return newAttrib; -} - - -DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyViaBackupRead); -DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyViaReadFile); - - -struct CallbackData -{ - CallbackData(const std::function<void(std::int64_t bytesDelta)>& notifyProgress, - const Zstring& sourceFile, - const Zstring& targetFile) : - sourceFile_(sourceFile), - targetFile_(targetFile), - notifyProgress_(notifyProgress) {} - - const Zstring& sourceFile_; - const Zstring& targetFile_; - const std::function<void(std::int64_t bytesDelta)>& notifyProgress_; //optional - - std::exception_ptr exception; //out - BY_HANDLE_FILE_INFORMATION fileInfoSrc{}; //out: modified by CopyFileEx() at beginning - BY_HANDLE_FILE_INFORMATION fileInfoTrg{}; // - - std::int64_t bytesReported = 0; //used internally to calculate bytes transferred delta -}; - - -DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, - LARGE_INTEGER totalBytesTransferred, - LARGE_INTEGER streamSize, - LARGE_INTEGER streamBytesTransferred, - DWORD dwStreamNumber, - DWORD dwCallbackReason, - HANDLE hSourceFile, - HANDLE hDestinationFile, - LPVOID lpData) -{ - /* - this callback is invoked for block sizes managed by Windows, these may vary from e.g. 64 kB up to 1MB. It seems this depends on file size amongst others. - Note: for 0-sized files this callback is invoked just ONCE! - - symlink handling: - if source is a symlink and COPY_FILE_COPY_SYMLINK is specified, this callback is NOT invoked! - if source is a symlink and COPY_FILE_COPY_SYMLINK is NOT specified, this callback is called and hSourceFile is a handle to the *target* of the link! - - file time handling: - ::CopyFileEx() will (only) copy file modification time over from source file AFTER the last invokation of this callback - => it is possible to adapt file creation time of target in here, but NOT file modification time! - CAVEAT: if ::CopyFileEx() fails to set modification time, it silently ignores this error and returns success!!! (confirmed with Process Monitor) - - alternate data stream handling: - CopyFileEx() processes multiple streams one after another, stream 1 is the file data stream and always available! - Each stream is initialized with CALLBACK_STREAM_SWITCH and provides *new* hSourceFile, hDestinationFile. - Calling GetFileInformationByHandle() on hDestinationFile for stream > 1 results in ERROR_ACCESS_DENIED! - totalBytesTransferred contains size of *all* streams and so can be larger than the "file size" file attribute - */ - - CallbackData& cbd = *static_cast<CallbackData*>(lpData); - - try - { - if (dwCallbackReason == CALLBACK_STREAM_SWITCH && //called up-front for every file (even if 0-sized) - dwStreamNumber == 1) //consider ADS! - { - //#################### return source file attributes ################################ - if (!::GetFileInformationByHandle(hSourceFile, &cbd.fileInfoSrc)) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(cbd.sourceFile_)), L"GetFileInformationByHandle"); - - if (!::GetFileInformationByHandle(hDestinationFile, &cbd.fileInfoTrg)) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(cbd.targetFile_)), L"GetFileInformationByHandle"); - - //#################### switch to sparse file copy if req. ####################### -#ifdef ZEN_WIN_VISTA_AND_LATER - if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, hDestinationFile)) //throw () -#else - if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw () -#endif - throw ErrorFallbackToCopyViaBackupRead(L"sparse, callback"); //use a different copy routine! - - //#################### copy file creation time ################################ - ::SetFileTime(hDestinationFile, &cbd.fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling! - //=> not really needed here, creation time is set anyway at the end of copyFileWindowsDefault()! - - //#################### copy NTFS compressed attribute ######################### - const bool sourceIsCompressed = (cbd.fileInfoSrc.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; - const bool targetIsCompressed = (cbd.fileInfoTrg.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CopyFileEx if target parent folder is compressed! - if (sourceIsCompressed && !targetIsCompressed) - { - USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; - DWORD bytesReturned = 0; - if (!::DeviceIoControl(hDestinationFile, //_In_ HANDLE hDevice, - FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode, - &cmpState, //_In_opt_ LPVOID lpInBuffer, - sizeof(cmpState), //_In_ DWORD nInBufferSize, - nullptr, //_Out_opt_ LPVOID lpOutBuffer, - 0, //_In_ DWORD nOutBufferSize, - &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned - nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped - {} //may legitimately fail with ERROR_INVALID_FUNCTION if: - // - target folder is encrypted - // - target volume does not support compressed attribute - //############################################################################# - } - } - - if (cbd.notifyProgress_ && totalBytesTransferred.QuadPart >= 0) //should always be true, but let's still check - { - cbd.notifyProgress_(totalBytesTransferred.QuadPart - cbd.bytesReported); //throw X! - cbd.bytesReported = totalBytesTransferred.QuadPart; - } - } - catch (...) - { - cbd.exception = std::current_exception(); - return PROGRESS_CANCEL; - } - return PROGRESS_CONTINUE; -} - - -InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, - const Zstring& targetFile, // ErrorFallbackToCopyViaReadFile, ErrorFallbackToCopyViaBackupRead - const std::function<void(std::int64_t bytesDelta)>& notifyProgress) -{ - //try to get backup read and write privileges: may help solve some "access denied" errors - bool backupPrivilegesActive = true; - try { activatePrivilege(PrivilegeName::BACKUP); } - catch (const FileError&) { backupPrivilegesActive = false; } - try { activatePrivilege(PrivilegeName::RESTORE); } - catch (const FileError&) { backupPrivilegesActive = false; } - - auto guardTarget = zen::makeGuard<ScopeGuardRunMode::ON_FAIL>([&] { try { removeFilePlain(targetFile); } catch (FileError&) {} }); - //transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we? ;) - - DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS; - - //encrypted destination is not supported with Windows 2000! -> whatever - copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; //allow copying from encrypted to non-encrypted location - - //if (vistaOrLater()) //see https://blogs.technet.microsoft.com/askperf/2007/05/08/slow-large-file-copy-issues/ - // copyFlags |= COPY_FILE_NO_BUFFERING; //no perf difference at worst, improvement for large files (20% in test NTFS -> NTFS) - // - this flag may cause file corruption! http://www.freefilesync.org/forum/viewtopic.php?t=1857 - // - documentation on CopyFile2() even states: "It is not recommended to pause copies that are using this flag." - //=> it's not worth it! instead of skipping buffering at kernel-level (=> also NO prefetching!!!), skip it at user-level: memory mapped files! - // however, perf-measurements for memory mapped files show: it's also not worth it! - - CallbackData cbd(notifyProgress, sourceFile, targetFile); - - const bool success = ::CopyFileEx(applyLongPathPrefix(sourceFile).c_str(), //__in LPCTSTR lpExistingFileName, - applyLongPathPrefix(targetFile).c_str(), //__in LPCTSTR lpNewFileName, - copyCallbackInternal, //__in_opt LPPROGRESS_ROUTINE lpProgressRoutine, - &cbd, //__in_opt LPVOID lpData, - nullptr, //__in_opt LPBOOL pbCancel, - copyFlags) != FALSE; //__in DWORD dwCopyFlags - if (cbd.exception) - std::rethrow_exception(cbd.exception); //throw X, process errors in callback first! - - if (!success) - { - const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - - //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user-aborted operation IS an error condition! - - //trying to copy huge sparse files may directly fail with ERROR_DISK_FULL before entering the callback function - if (canCopyAsSparse(sourceFile, targetFile)) //noexcept - throw ErrorFallbackToCopyViaBackupRead(L"sparse, copy failure"); - - if (ec == ERROR_ACCESS_DENIED && backupPrivilegesActive) - //chances are good this will work with copyFileWindowsStream: http://www.freefilesync.org/forum/viewtopic.php?t=1714 - throw ErrorFallbackToCopyViaReadFile(L"access denied"); - - //- copying ADS may incorrectly fail with ERROR_FILE_NOT_FOUND: http://www.freefilesync.org/forum/viewtopic.php?t=446 - //- even BackupWrite may fail => use ReadFile: http://www.freefilesync.org/forum/viewtopic.php?t=2321 - if (ec == ERROR_FILE_NOT_FOUND && - cbd.fileInfoSrc.nNumberOfLinks > 0 && - cbd.fileInfoTrg.nNumberOfLinks > 0) - throw ErrorFallbackToCopyViaReadFile(L"bogus file not found"); - - //assemble error message... - const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtPath(sourceFile)), L"%y", L"\n" + fmtPath(targetFile)); - std::wstring errorDescr = formatSystemError(L"CopyFileEx", ec); - - //if file is locked throw "ErrorFileLocked" instead! - if (ec == ERROR_SHARING_VIOLATION || - ec == ERROR_LOCK_VIOLATION) - { -#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message - const std::wstring procList = vista::getLockingProcesses(sourceFile); //noexcept - if (!procList.empty()) - errorDescr = _("The file is locked by another process:") + L"\n" + procList; -#endif - throw ErrorFileLocked(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), errorDescr); - } - - //if target is existing this functions is expected to throw ErrorTargetExisting!!! - if (ec == ERROR_FILE_EXISTS || //confirmed to be used - ec == ERROR_ALREADY_EXISTS) //not sure if used -> better be safe than sorry!!! - { - guardTarget.dismiss(); //don't delete file that existed previously! - throw ErrorTargetExisting(errorMsg, errorDescr); - } - - //lastError == ERROR_PATH_NOT_FOUND: could this also be source path missing!? - - try //add more meaningful message - { - //trying to copy > 4GB file to FAT/FAT32 volume gives obscure ERROR_INVALID_PARAMETER (FAT can indeed handle files up to 4 Gig, tested!) - if (ec == ERROR_INVALID_PARAMETER) - { - const FatType fatType = getFatType(targetFile); - if ((fatType == FatType::FAT || - fatType == FatType::FAT32) && //no problem for exFAT (limit ca. 128 PB) - getFilesize(sourceFile) >= 4U * std::uint64_t(1024U * 1024 * 1024)) //throw FileError - errorDescr += L"\nFAT volumes cannot store files larger than 4 gigabytes."; - //see "Limitations of the FAT32 File System": http://support.microsoft.com/kb/314463/en-us - } - - //note: ERROR_INVALID_PARAMETER can also occur when copying to a SharePoint server or MS SkyDrive and the target file path is of a restricted type. - } - catch (FileError&) {} - - throw FileError(errorMsg, errorDescr); - } - - //caveat: - ::CopyFileEx() silently *ignores* failure to set modification time!!! => we always need to set it again but with proper error checking! - // - perf: recent measurements show no slow down at all for buffered USB sticks! - setWriteTimeNative(targetFile, cbd.fileInfoSrc.ftLastWriteTime, &cbd.fileInfoSrc.ftCreationTime, ProcSymlink::FOLLOW); //throw FileError - - InSyncAttributes newAttrib; - newAttrib.fileSize = get64BitUInt(cbd.fileInfoSrc.nFileSizeLow, cbd.fileInfoSrc.nFileSizeHigh); - newAttrib.modificationTime = filetimeToTimeT(cbd.fileInfoSrc.ftLastWriteTime); - newAttrib.sourceFileId = extractFileId(cbd.fileInfoSrc); - newAttrib.targetFileId = extractFileId(cbd.fileInfoTrg); - return newAttrib; -} - - -//another layer to support copying sparse files and handle some access denied errors -inline -InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& notifyProgress) -{ - try - { - return copyFileWindowsDefault(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyViaReadFile, ErrorFallbackToCopyViaBackupRead - } - catch (ErrorFallbackToCopyViaReadFile&) - { - return copyFileWindowsStream(sourceFile, targetFile, StreamCopyType::READ_FILE, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked - } - catch (ErrorFallbackToCopyViaBackupRead&) - { - return copyFileWindowsStream(sourceFile, targetFile, StreamCopyType::BACKUP_READ, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked - } -} - - -//another layer of indirection solving 8.3 name clashes -inline -InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, - const Zstring& targetFile, - const std::function<void(std::int64_t bytesDelta)>& notifyProgress) -{ - try - { - return copyFileWindowsSelectRoutine(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked - } - catch (const ErrorTargetExisting&) - { - //try to handle issues with already existing short 8.3 file names on Windows - if (have8dot3NameClash(targetFile)) - { - Fix8Dot3NameClash dummy(targetFile); //throw FileError; move clashing file path to the side - return copyFileWindowsSelectRoutine(sourceFile, targetFile, notifyProgress); //throw FileError; the short file path name clash is solved, this should work now - } - throw; - } -} - - -#elif defined ZEN_LINUX || defined ZEN_MAC -InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting - const Zstring& targetFile, - const std::function<void(std::int64_t bytesDelta)>& notifyProgress) -{ - FileInput fileIn(sourceFile); //throw FileError, (ErrorFileLocked -> Windows-only) - if (notifyProgress) notifyProgress(0); //throw X! - - 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&) {} ); - //transactional behavior: place guard after ::open() and before lifetime of FileOutput: - //=> don't delete file that existed previously!!! - FileOutput fileOut(fdTarget, targetFile); //pass ownership - if (notifyProgress) notifyProgress(0); //throw X! - - unbufferedStreamCopy(fileIn, fileOut, notifyProgress); //throw FileError, X - -#ifdef ZEN_MAC - //using ::copyfile with COPYFILE_DATA seems to trigger bugs unlike our stream-based copying! - //=> use ::copyfile for extended attributes only: http://www.freefilesync.org/forum/viewtopic.php?t=401 - //http://blog.plasticsfuture.org/2006/03/05/the-state-of-backup-and-cloning-tools-under-mac-os-x/ - //docs: http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html - //source: http://www.opensource.apple.com/source/copyfile/copyfile-103.92.1/copyfile.c - - //avoid creation of ._ files if target doesn't support extended attributes: http://www.freefilesync.org/forum/viewtopic.php?t=2226 - if (hasNativeSupportForExtendedAtrributes(targetFile, fileOut.getHandle())) //throw FileError - if (::fcopyfile(fileIn.getHandle(), fileOut.getHandle(), nullptr, COPYFILE_XATTR) != 0) - ; //THROW_LAST_FILE_ERROR(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtPath(sourceFile)), L"%y", L"\n" + fmtPath(targetFile)), L"fcopyfile"); - /* - both problems still occur even with the "hasNativeSupportForExtendedAtrributes" check above: - E2BIG - reference email: "Re: FFS V7.8 on Mac with 10.11.2 ElCapitan" - EINVAL - reference email: "Error Code 22: Invalid argument (copyfile)" - */ -#endif - 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.close(); //throw FileError - if (notifyProgress) notifyProgress(0); //throw X! - - //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 -#ifdef ZEN_MAC - setWriteTimeNative(targetFile, sourceInfo.st_mtimespec, &sourceInfo.st_birthtimespec, ProcSymlink::FOLLOW); //throw FileError - //sourceInfo.st_birthtime; -> only seconds-precision - //sourceInfo.st_mtime; -> -#else - setWriteTimeNative(targetFile, sourceInfo.st_mtim, ProcSymlink::FOLLOW); //throw FileError -#endif - - InSyncAttributes newAttrib; - newAttrib.fileSize = sourceInfo.st_size; -#ifdef ZEN_MAC - newAttrib.modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setWriteTimeNative() for consistency -#else - newAttrib.modificationTime = sourceInfo.st_mtim.tv_sec; // -#endif - newAttrib.sourceFileId = extractFileId(sourceInfo); - newAttrib.targetFileId = extractFileId(targetInfo); - return newAttrib; -} -#endif - -/* - ------------------ - |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 std::function<void(std::int64_t bytesDelta)>& notifyProgress) -{ - const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, notifyProgress); //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::getPathComponents(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 = getPathComponents(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
+}
+
+
+PathDetails zen::getPathDetails(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());
+
+ PathDetails pd = getPathDetails(*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 PathDetails pd = getPathDetails(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<PathDetails> pd;
+ try { pd = getPathDetails(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;
+}
|