From 46fc289a8776ba253e97d01d6948fb1031ea1973 Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 2 Oct 2015 14:55:19 +0200 Subject: 7.0 --- zen/dir_watcher.cpp | 108 +++++++------- zen/dir_watcher.h | 7 +- zen/file_access.cpp | 375 +++++++++++++++++-------------------------------- zen/file_access.h | 32 ++--- zen/file_id_def.h | 3 +- zen/file_io.cpp | 105 ++++++++------ zen/file_io.h | 37 ++++- zen/file_io_base.h | 64 --------- zen/file_traverser.cpp | 66 ++++----- zen/long_path_prefix.h | 4 +- zen/notify_removal.cpp | 212 ---------------------------- zen/notify_removal.h | 34 ----- zen/perf.h | 69 +++++++-- zen/recycler.cpp | 34 +++-- zen/serialize.h | 137 +++++++++--------- zen/string_base.h | 4 +- zen/string_tools.h | 19 ++- zen/string_traits.h | 8 +- zen/symlink_target.h | 14 +- zen/thread.h | 2 +- zen/time.h | 6 +- zen/type_traits.h | 4 +- zen/win_ver.h | 10 +- zen/xml_io.cpp | 28 ++-- zen/zstring.cpp | 23 +-- zen/zstring.h | 31 +++- 26 files changed, 564 insertions(+), 872 deletions(-) delete mode 100644 zen/file_io_base.h delete mode 100644 zen/notify_removal.cpp delete mode 100644 zen/notify_removal.h (limited to 'zen') diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 26645157..a948a5e8 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -11,7 +11,7 @@ #include "scope_guard.h" #ifdef ZEN_WIN - #include "notify_removal.h" + #include "device_notify.h" #include "win.h" //includes "windows.h" #include "long_path_prefix.h" @@ -161,12 +161,11 @@ public: //end of constructor, no need to start managing "hDir" } - ReadChangesAsync(ReadChangesAsync&& other) : - hDir(INVALID_HANDLE_VALUE) + ReadChangesAsync(ReadChangesAsync&& other) : shared_(std::move(other.shared_)), + dirpathPf(std::move(other.dirpathPf)), + hDir(other.hDir) { - shared_ = std::move(other.shared_); - dirpathPf = std::move(other.dirpathPf); - std::swap(hDir, other.hDir); + other.hDir = INVALID_HANDLE_VALUE; } ~ReadChangesAsync() @@ -271,15 +270,24 @@ private: }; -class HandleVolumeRemoval : public NotifyRequestDeviceRemoval +class HandleVolumeRemoval { public: HandleVolumeRemoval(HANDLE hDir, + const Zstring& displayPath, boost::thread& worker) : - NotifyRequestDeviceRemoval(hDir), //throw FileError - worker_(worker), - removalRequested(false), - operationComplete(false) {} + notificationHandle(registerFolderRemovalNotification(hDir, //throw FileError + displayPath, + [this] { this->onRequestRemoval (); }, //noexcept! + [this](bool successful) { this->onRemovalFinished(); })), // + worker_(worker), + removalRequested(false), + operationComplete(false) {} + + ~HandleVolumeRemoval() + { + unregisterDeviceNotification(notificationHandle); + } //all functions are called by main thread! @@ -287,7 +295,7 @@ public: bool finished() const { return operationComplete; } private: - void onRequestRemoval(HANDLE hnd) override + void onRequestRemoval() //noexcept! { //must release hDir immediately => stop monitoring! if (worker_.joinable()) //= join() precondition: play safe; can't trust Windows to only call-back once @@ -300,8 +308,9 @@ private: removalRequested = true; } //don't throw! - void onRemovalFinished(HANDLE hnd, bool successful) override { operationComplete = true; } //throw()! + void onRemovalFinished() { operationComplete = true; } //noexcept! + DeviceNotificationHandle* notificationHandle; boost::thread& worker_; bool removalRequested; bool operationComplete; @@ -313,20 +322,18 @@ struct DirWatcher::Pimpl { boost::thread worker; std::shared_ptr shared; - - Zstring dirpath; std::unique_ptr volRemoval; }; -DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError +DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError + baseDirPath(dirPath), pimpl_(zen::make_unique()) { pimpl_->shared = std::make_shared(); - pimpl_->dirpath = directory; - ReadChangesAsync reader(directory, pimpl_->shared); //throw FileError - pimpl_->volRemoval = zen::make_unique(reader.getDirHandle(), pimpl_->worker); //throw FileError + ReadChangesAsync reader(dirPath, pimpl_->shared); //throw FileError + pimpl_->volRemoval = zen::make_unique(reader.getDirHandle(), dirPath, pimpl_->worker); //throw FileError pimpl_->worker = boost::thread(std::move(reader)); } @@ -347,6 +354,7 @@ DirWatcher::~DirWatcher() std::vector DirWatcher::getChanges(const std::function& processGuiMessages) //throw FileError { std::vector output; + pimpl_->shared->fetchChanges(output); //throw FileError //wait until device removal is confirmed, to prevent locking hDir again by some new watch! if (pimpl_->volRemoval->requestReceived()) @@ -360,10 +368,9 @@ std::vector DirWatcher::getChanges(const std::functiondirpath); //report removal as change to main directory + output.emplace_back(ACTION_DELETE, baseDirPath); //report removal as change to main directory } - else //the normal case... - pimpl_->shared->fetchChanges(output); //throw FileError + return output; } @@ -373,32 +380,35 @@ struct DirWatcher::Pimpl { Pimpl() : notifDescr() {} - Zstring basedirpath; int notifDescr; std::map watchDescrs; //watch descriptor and (sub-)directory name (postfixed with separator) -> owned by "notifDescr" }; -DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError +DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError + baseDirPath(dirPath), pimpl_(zen::make_unique()) { //get all subdirectories - Zstring dirpathFmt = directory; - if (endsWith(dirpathFmt, FILE_NAME_SEPARATOR)) - dirpathFmt.resize(dirpathFmt.size() - 1); + std::vector fullDirList { baseDirPath }; + { + std::function traverse; - std::vector fullDirList { dirpathFmt }; + traverse = [&traverse, &fullDirList](const Zstring& path) + { + traverseFolder(path, nullptr, + [&](const DirInfo& di ) { fullDirList.push_back(di.fullPath); traverse(di.fullPath); }, + nullptr, //don't traverse into symlinks (analog to windows build) + [&](const std::wstring& errorMsg) { throw FileError(errorMsg); }); + }; -traverseFolder(dirpathFmt, nullptr, - [&](const DirInfo& di ){ fullDirList.push_back(di.fullPath); }, - nullptr, //don't traverse into symlinks (analog to windows build) -[&](const std::wstring& errorMsg){ throw FileError(errorMsg); }); + traverse(baseDirPath); + } //init - pimpl_->basedirpath = directory; - pimpl_->notifDescr = ::inotify_init(); + pimpl_->notifDescr = ::inotify_init(); if (pimpl_->notifDescr == -1) - throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"inotify_init", getLastError()); + throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"inotify_init", getLastError()); zen::ScopeGuard guardDescr = zen::makeGuard([&] { ::close(pimpl_->notifDescr); }); @@ -410,12 +420,12 @@ traverseFolder(dirpathFmt, nullptr, initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1; } if (!initSuccess) - throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"fcntl", getLastError()); + throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"fcntl", getLastError()); //add watches - for (const Zstring& subdir : fullDirList) + for (const Zstring& subDirPath : fullDirList) { - int wd = ::inotify_add_watch(pimpl_->notifDescr, subdir.c_str(), + int wd = ::inotify_add_watch(pimpl_->notifDescr, subDirPath.c_str(), IN_ONLYDIR | //"Only watch pathname if it is a directory." IN_DONT_FOLLOW | //don't follow symbolic links IN_CREATE | @@ -430,12 +440,13 @@ traverseFolder(dirpathFmt, nullptr, { const auto ec = getLastError(); if (ec == ENOSPC) //fix misleading system message "No space left on device" - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subdir)), formatSystemError(L"inotify_add_watch", ec, L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource.")); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subDirPath)), + formatSystemError(L"inotify_add_watch", ec, L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource.")); - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subdir)), formatSystemError(L"inotify_add_watch", ec)); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subDirPath)), formatSystemError(L"inotify_add_watch", ec)); } - pimpl_->watchDescrs.emplace(wd, appendSeparator(subdir)); + pimpl_->watchDescrs.emplace(wd, appendSeparator(subDirPath)); } guardDescr.dismiss(); @@ -465,7 +476,7 @@ std::vector DirWatcher::getChanges(const std::function(); - throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->basedirpath)), L"read", getLastError()); + throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"read", getLastError()); } std::vector output; @@ -523,7 +534,7 @@ void eventCallback(ConstFSEventStreamRef streamRef, //events are aggregated => it's possible to see a single event with flags //kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemModified | kFSEventStreamEventFlagItemRemoved - //https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags + //https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags if (eventFlags[i] & kFSEventStreamEventFlagItemCreated || eventFlags[i] & kFSEventStreamEventFlagMount) changedFiles.emplace_back(DirWatcher::ACTION_CREATE, paths[i]); @@ -555,12 +566,13 @@ struct DirWatcher::Pimpl }; -DirWatcher::DirWatcher(const Zstring& directory) : +DirWatcher::DirWatcher(const Zstring& dirPath) : + baseDirPath(dirPath), pimpl_(zen::make_unique()) { - CFStringRef dirpathCf = osx::createCFString(directory.c_str()); //returns nullptr on error + CFStringRef dirpathCf = osx::createCFString(baseDirPath.c_str()); //returns nullptr on error if (!dirpathCf) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"Function call failed: createCFString"); //no error code documented! + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"Function call failed: createCFString"); //no error code documented! ZEN_ON_SCOPE_EXIT(::CFRelease(dirpathCf)); CFArrayRef dirpathCfArray = ::CFArrayCreate(nullptr, //CFAllocatorRef allocator, @@ -568,7 +580,7 @@ DirWatcher::DirWatcher(const Zstring& directory) : 1, //CFIndex numValues, nullptr); //const CFArrayCallBacks* callBacks if (!dirpathCfArray) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"Function call failed: CFArrayCreate"); //no error code documented! + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"Function call failed: CFArrayCreate"); //no error code documented! ZEN_ON_SCOPE_EXIT(::CFRelease(dirpathCfArray)); FSEventStreamContext context = {}; @@ -594,7 +606,7 @@ DirWatcher::DirWatcher(const Zstring& directory) : zen::ScopeGuard guardRunloop = zen::makeGuard([&] { ::FSEventStreamInvalidate(pimpl_->eventStream); }); if (!::FSEventStreamStart(pimpl_->eventStream)) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"Function call failed: FSEventStreamStart"); //no error code documented! + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"Function call failed: FSEventStreamStart"); //no error code documented! guardCreate .dismiss(); guardRunloop.dismiss(); diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h index b5255898..cdc80165 100644 --- a/zen/dir_watcher.h +++ b/zen/dir_watcher.h @@ -21,7 +21,8 @@ namespace zen //watch directory including subdirectories /* !Note handling of directories!: - Windows: removal of top watched directory is NOT notified (e.g. brute force usb stick removal) + Windows: removal of top watched directory is NOT notified when watching the dir handle, e.g. brute force usb stick removal, + (watchting for GUID_DEVINTERFACE_WPD OTOH works fine!) however manual unmount IS notified (e.g. usb stick removal, then re-insert), but watching is stopped! Renaming of top watched directory handled incorrectly: Not notified(!) + additional changes in subfolders now do report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!) @@ -36,7 +37,7 @@ namespace zen class DirWatcher { public: - DirWatcher(const Zstring& directory); //throw FileError + DirWatcher(const Zstring& dirPath); //throw FileError ~DirWatcher(); enum ActionType @@ -62,6 +63,8 @@ private: DirWatcher (const DirWatcher&) = delete; DirWatcher& operator=(const DirWatcher&) = delete; + const Zstring baseDirPath; + struct Pimpl; std::unique_ptr pimpl_; }; diff --git a/zen/file_access.cpp b/zen/file_access.cpp index ffbdc813..b1b781ee 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -12,8 +12,8 @@ #include "file_traverser.h" #include "scope_guard.h" #include "symlink_target.h" -#include "file_io.h" #include "file_id_def.h" +#include "serialize.h" #ifdef ZEN_WIN #include @@ -133,67 +133,38 @@ bool zen::somethingExists(const Zstring& objname) namespace { #ifdef ZEN_WIN -//fast ::GetVolumePathName() clone: let's hope it's not too simple (doesn't honor mount points) -Zstring getVolumeNameFast(const Zstring& filepath) +bool isFatDrive(const Zstring& filePath) //throw() { - //this call is expensive: ~1.5 ms! - // if (!::GetVolumePathName(filepath.c_str(), //__in LPCTSTR lpszFileName, - // fsName, //__out LPTSTR lpszVolumePathName, - // BUFFER_SIZE)) //__in DWORD cchBufferLength - // ... - // Zstring volumePath = appendSeparator(fsName); - - const Zstring nameFmt = appendSeparator(removeLongPathPrefix(filepath)); //throw() + const DWORD bufferSize = MAX_PATH + 1; + std::vector buffer(bufferSize); - if (startsWith(nameFmt, Zstr("\\\\"))) //UNC path: "\\ComputerName\SharedFolder\" - { - size_t nameSize = nameFmt.size(); - const size_t posFirstSlash = nameFmt.find(Zstr("\\"), 2); - if (posFirstSlash != Zstring::npos) - { - nameSize = posFirstSlash + 1; - const size_t posSecondSlash = nameFmt.find(Zstr("\\"), posFirstSlash + 1); - if (posSecondSlash != Zstring::npos) - nameSize = posSecondSlash + 1; - } - return Zstring(nameFmt.c_str(), nameSize); //include trailing backslash! - } - else //local path: "C:\Folder\" + //this call is expensive: ~1.5 ms! + if (!::GetVolumePathName(filePath.c_str(), //__in LPCTSTR lpszFileName, + &buffer[0], //__out LPTSTR lpszVolumePathName, + bufferSize)) //__in DWORD cchBufferLength { - const size_t pos = nameFmt.find(Zstr(":\\")); - if (pos == 1) //expect single letter volume - return Zstring(nameFmt.c_str(), 3); - } - - return Zstring(); -} - - -bool isFatDrive(const Zstring& filepath) //throw() -{ - const Zstring volumePath = getVolumeNameFast(filepath); - if (volumePath.empty()) + assert(false); return false; + } - const DWORD bufferSize = MAX_PATH + 1; - wchar_t fsName[bufferSize] = {}; + const Zstring volumePath = appendSeparator(&buffer[0]); //suprisingly fast: ca. 0.03 ms per call! - if (!::GetVolumeInformation(appendSeparator(volumePath).c_str(), //__in_opt LPCTSTR lpRootPathName, + 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, - fsName, //__out LPTSTR lpFileSystemNameBuffer, + &buffer[0], //__out LPTSTR lpFileSystemNameBuffer, bufferSize)) //__in DWORD nFileSystemNameSize { - assert(false); //shouldn't happen + assert(false); return false; } - //DST hack seems to be working equally well for FAT and FAT32 (in particular creation time has 10^-2 s precision as advertised) - return fsName == Zstring(L"FAT") || - fsName == Zstring(L"FAT32"); + + return &buffer[0] == Zstring(L"FAT") || + &buffer[0] == Zstring(L"FAT32"); } @@ -267,7 +238,7 @@ std::uint64_t zen::getFilesize(const Zstring& filepath) //throw FileError } -std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError +std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, returns 0 if not available { #ifdef ZEN_WIN ULARGE_INTEGER bytesFree = {}; @@ -275,14 +246,15 @@ std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError &bytesFree, //__out_opt PULARGE_INTEGER lpFreeBytesAvailable, nullptr, //__out_opt PULARGE_INTEGER lpTotalNumberOfBytes, nullptr)) //__out_opt PULARGE_INTEGER lpTotalNumberOfFreeBytes - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(path)), L"GetDiskFreeSpaceEx", getLastError()); + throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"GetDiskFreeSpaceEx", getLastError()); + //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) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(path)), L"statfs", getLastError()); + throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"statfs", getLastError()); return static_cast(info.f_bsize) * info.f_bavail; #endif @@ -293,8 +265,8 @@ bool zen::removeFile(const Zstring& filepath) //throw FileError { #ifdef ZEN_WIN const wchar_t functionName[] = L"DeleteFile"; - const Zstring& filepathFmt = applyLongPathPrefix(filepath); - if (!::DeleteFile(filepathFmt.c_str())) + + 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) @@ -304,9 +276,9 @@ bool zen::removeFile(const Zstring& filepath) //throw FileError #ifdef ZEN_WIN if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only { - ::SetFileAttributes(filepathFmt.c_str(), FILE_ATTRIBUTE_NORMAL); //(try to) normalize file attributes + ::SetFileAttributes(applyLongPathPrefix(filepath).c_str(), FILE_ATTRIBUTE_NORMAL); //(try to) normalize file attributes - if (::DeleteFile(filepathFmt.c_str())) //now try again... + if (::DeleteFile(applyLongPathPrefix(filepath).c_str())) //now try again... return true; lastError = ::GetLastError(); } @@ -380,7 +352,7 @@ void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw File } } } - + //begin of "regular" error reporting const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(oldName)), L"%y", L"\n" + fmtFileName(newName)); std::wstring errorDescr = formatSystemError(L"MoveFileEx", lastError); @@ -480,8 +452,8 @@ bool have8dot3NameClash(const Zstring& filepath) if (!shortName.empty() && !longName .empty() && - EqualFilename()(origName, shortName) && - !EqualFilename()(shortName, longName)) + EqualFilePath()(origName, shortName) && + !EqualFilePath()(shortName, longName)) { //for filepath short and long file name are equal and another unrelated file happens to have the same short name //e.g. filepath == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1" @@ -526,21 +498,21 @@ private: //rename file: no copying!!! -void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting +void zen::renameFile(const Zstring& itemPathOld, const Zstring& itemPathNew) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting { try { - renameFile_sub(oldName, newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting + renameFile_sub(itemPathOld, itemPathNew); //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(newName)) + if (have8dot3NameClash(itemPathNew)) { - Fix8Dot3NameClash dummy(newName); //throw FileError; move clashing filepath to the side + Fix8Dot3NameClash dummy(itemPathNew); //throw FileError; move clashing filepath to the side //now try again... - renameFile_sub(oldName, newName); //throw FileError + renameFile_sub(itemPathOld, itemPathNew); //throw FileError return; } #endif @@ -964,8 +936,8 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink // - utimensat() is supposed to obsolete utime/utimes and is also used by "touch" //=> let's give utimensat another chance: struct ::timespec newTimes[2] = {}; - newTimes[0].tv_nsec = UTIME_OMIT; //omit access time - newTimes[1].tv_sec = modTime; //modification time (seconds) + newTimes[0].tv_sec = modTime; //access time: using UTIME_OMIT for tv_nsec would trigger even more bugs!! https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/218564cf/ + newTimes[1].tv_sec = modTime; //modification time (seconds) if (procSl == ProcSymlink::FOLLOW) { @@ -1016,13 +988,16 @@ bool zen::supportsPermissions(const Zstring& dirpath) //throw FileError #ifdef ZEN_WIN const DWORD bufferSize = MAX_PATH + 1; std::vector buffer(bufferSize); + if (!::GetVolumePathName(dirpath.c_str(), //__in LPCTSTR lpszFileName, &buffer[0], //__out LPTSTR lpszVolumePathName, bufferSize)) //__in DWORD cchBufferLength throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(dirpath)), L"GetVolumePathName", getLastError()); + const Zstring volumePath = appendSeparator(&buffer[0]); + DWORD fsFlags = 0; - if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName, + if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, nullptr, //__out LPTSTR lpVolumeNameBuffer, 0, //__in DWORD nVolumeNameSize, nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, @@ -1309,7 +1284,7 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget try { - makeDirectoryPlain(directory, Zstring(), false); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing + copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing } catch (const ErrorTargetPathMissing&) { @@ -1322,10 +1297,10 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget { makeDirectoryRecursively(dirParent); //throw FileError, (ErrorTargetExisting) } - catch (const ErrorTargetExisting&) { /*parent directory created externally in the meantime? => NOT AN ERROR*/ } + catch (const ErrorTargetExisting&) {} //parent directory created externally in the meantime? => NOT AN ERROR; not a directory? fail in next step! //now try again... - makeDirectoryPlain(directory, Zstring(), false); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing) + copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing) return; } throw; @@ -1334,7 +1309,7 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget } -void zen::makeDirectory(const Zstring& directory, bool failIfExists) //throw FileError, ErrorTargetExisting +void zen::makeNewDirectory(const Zstring& directory) //throw FileError, ErrorTargetExisting { //remove trailing separator (even for C:\ root directories) const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ? @@ -1345,40 +1320,22 @@ void zen::makeDirectory(const Zstring& directory, bool failIfExists) //throw Fil { makeDirectoryRecursively(dirFormatted); //FileError, ErrorTargetExisting } - catch (const ErrorTargetExisting&) - { - //avoid any file system race-condition by *not* checking directory existence again here!!! - if (failIfExists) - throw; - } - catch (const FileError&) + catch (const ErrorTargetExisting&) //*something* existing: folder or FILE! { - /* - could there be situations where a directory/network path exists, - but creation fails with error different than "ErrorTargetExisting"?? - - creation of C:\ fails with ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS - */ - if (somethingExists(directory)) //a file system race-condition! don't use dirExists() => harmonize with ErrorTargetExisting! - { - assert(false); - if (failIfExists) - throw; //do NOT convert to ErrorTargetExisting: if "failIfExists", not getting a ErrorTargetExisting *atomically* is unexpected! - } - else + //avoid any file system race-condition by *not* checking existence again here!!! throw; } } -void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing - const Zstring& templateDir, +void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing bool copyFilePermissions) { #ifdef ZEN_WIN //special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS! - Zstring dirTmp = removeLongPathPrefix(endsWith(directory, FILE_NAME_SEPARATOR) ? - beforeLast(directory, FILE_NAME_SEPARATOR) : - directory); + Zstring dirTmp = removeLongPathPrefix(endsWith(targetPath, FILE_NAME_SEPARATOR) ? + beforeLast(targetPath, FILE_NAME_SEPARATOR) : + targetPath); if (dirTmp.size() == 2 && std::iswalpha(dirTmp[0]) && dirTmp[1] == L':') { @@ -1398,19 +1355,19 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT //- 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(directory).c_str(), //__in LPCTSTR lpPathName, + if (!::CreateDirectory(applyLongPathPrefixCreateDir(targetPath).c_str(), //__in LPCTSTR lpPathName, nullptr)) //__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes { DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! //handle issues with already existing short 8.3 file names on Windows if (lastError == ERROR_ALREADY_EXISTS) - if (have8dot3NameClash(directory)) + if (have8dot3NameClash(targetPath)) { - Fix8Dot3NameClash dummy(directory); //throw FileError; move clashing object to the side + Fix8Dot3NameClash dummy(targetPath); //throw FileError; move clashing object to the side //now try again... - if (::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), nullptr)) + if (::CreateDirectory(applyLongPathPrefixCreateDir(targetPath).c_str(), nullptr)) lastError = ERROR_SUCCESS; else lastError = ::GetLastError(); @@ -1418,7 +1375,7 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT if (lastError != ERROR_SUCCESS) { - const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory)); + const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(targetPath)); const std::wstring errorDescr = formatSystemError(L"CreateDirectory", lastError); if (lastError == ERROR_ALREADY_EXISTS) @@ -1433,18 +1390,18 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory struct ::stat dirInfo = {}; - if (!templateDir.empty()) - if (::stat(templateDir.c_str(), &dirInfo) == 0) + if (!sourcePath.empty()) + if (::stat(sourcePath.c_str(), &dirInfo) == 0) { mode = dirInfo.st_mode; //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(directory.c_str(), mode) != 0) + 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", fmtFileName(directory)); + const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(targetPath)); const std::wstring errorDescr = formatSystemError(L"mkdir", lastError); if (lastError == EEXIST) @@ -1455,11 +1412,11 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT } #endif - if (!templateDir.empty()) + if (!sourcePath.empty()) { #ifdef ZEN_WIN //optional: try to copy file attributes (dereference symlinks and junctions) - const HANDLE hDirSrc = ::CreateFile(zen::applyLongPathPrefix(templateDir).c_str(), //_In_ LPCTSTR lpFileName, + 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, @@ -1474,16 +1431,16 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT BY_HANDLE_FILE_INFORMATION dirInfo = {}; if (::GetFileInformationByHandle(hDirSrc, &dirInfo)) { - ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), dirInfo.dwFileAttributes); + ::SetFileAttributes(applyLongPathPrefix(targetPath).c_str(), dirInfo.dwFileAttributes); //copy "read-only and system attributes": http://blogs.msdn.com/b/oldnewthing/archive/2003/09/30/55100.aspx const bool isEncrypted = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; const bool isCompressed = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; if (isEncrypted) - ::EncryptFile(directory.c_str()); //seems no long path is required (check passed!) + ::EncryptFile(targetPath.c_str()); //seems no long path is required (check passed!) - HANDLE hDirTrg = ::CreateFile(applyLongPathPrefix(directory).c_str(), //_In_ LPCTSTR lpFileName, + 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 | @@ -1521,14 +1478,14 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT } #elif defined ZEN_MAC - /*int rv =*/ ::copyfile(templateDir.c_str(), directory.c_str(), 0, COPYFILE_XATTR); + /*int rv =*/ ::copyfile(sourcePath.c_str(), targetPath.c_str(), 0, COPYFILE_XATTR); #endif - zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectory(directory); } catch (FileError&) {} }); //ensure cleanup: + zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectory(targetPath); } catch (FileError&) {} }); //ensure cleanup: //enforce copying file permissions: it's advertized on GUI... if (copyFilePermissions) - copyItemPermissions(templateDir, directory, ProcSymlink::FOLLOW); //throw FileError + copyItemPermissions(sourcePath, targetPath, ProcSymlink::FOLLOW); //throw FileError guardNewDir.dismiss(); //target has been created successfully! } @@ -1663,7 +1620,7 @@ bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw () return false; //small perf optimization: don't check "targetFile" if not needed //------------------------------------------------------------------------------------ - const DWORD bufferSize = 10000; + const DWORD bufferSize = MAX_PATH + 1; std::vector buffer(bufferSize); //full pathName need not yet exist! @@ -1672,8 +1629,10 @@ bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw () bufferSize)) //__in DWORD cchBufferLength return false; + const Zstring volumePath = appendSeparator(&buffer[0]); + DWORD fsFlagsTarget = 0; - if (!::GetVolumeInformation(&buffer[0], //__in_opt LPCTSTR lpRootPathName + if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName nullptr, //__out_opt LPTSTR lpVolumeNameBuffer, 0, //__in DWORD nVolumeNameSize, nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, @@ -1715,10 +1674,9 @@ bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //thr //precondition: canCopyAsSparse() must return "true"! -void copyFileWindowsSparse(const Zstring& sourceFile, - const Zstring& targetFile, - const std::function& onUpdateCopyStatus, - InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting, ErrorFileLocked +InSyncAttributes copyFileWindowsSparse(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked + const Zstring& targetFile, + const std::function& onUpdateCopyStatus) { assert(canCopyAsSparse(sourceFile, targetFile)); @@ -1810,13 +1768,11 @@ void copyFileWindowsSparse(const Zstring& sourceFile, throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"GetFileInformationByHandle", getLastError()); //return up-to-date file attributes - if (newAttrib) - { - newAttrib->fileSize = get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh); - newAttrib->modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); //no DST hack (yet) - newAttrib->sourceFileId = extractFileId(fileInfoSource); - newAttrib->targetFileId = extractFileId(fileInfoTarget); - } + InSyncAttributes newAttrib = {}; + newAttrib.fileSize = get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh); + newAttrib.modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); //no DST hack (yet) + newAttrib.sourceFileId = extractFileId(fileInfoSource); + newAttrib.targetFileId = extractFileId(fileInfoTarget); //#################### copy NTFS compressed attribute ######################### const bool sourceIsCompressed = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; @@ -1860,7 +1816,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile, } //---------------------------------------------------------------------- - const DWORD BUFFER_SIZE = 128 * 1024; //must be greater than sizeof(WIN32_STREAM_ID) + const DWORD BUFFER_SIZE = std::max(128 * 1024, static_cast(sizeof(WIN32_STREAM_ID))); //must be greater than sizeof(WIN32_STREAM_ID)! std::vector buffer(BUFFER_SIZE); LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite() @@ -1886,7 +1842,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile, throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"BackupRead", getLastError()); //better use fine-granular error messages "reading/writing"! if (bytesRead > BUFFER_SIZE) - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"buffer overflow"); //user should never see this + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"BackupRead: buffer overflow."); //user should never see this if (bytesRead < BUFFER_SIZE) eof = true; @@ -1902,7 +1858,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile, throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)), L"BackupWrite", getLastError()); if (bytesWritten != bytesRead) - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)), L"incomplete write"); //user should never see this + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(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)! @@ -1918,7 +1874,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile, //::BackupRead() silently fails reading encrypted files -> double check! if (!someBytesWritten && get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.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", fmtFileName(sourceFile)), L"unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()" + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(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: BackupWrite() changes modification time if (!::SetFileTime(hFileTarget, @@ -1928,6 +1884,7 @@ void copyFileWindowsSparse(const Zstring& sourceFile, throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)), L"SetFileTime", getLastError()); guardTarget.dismiss(); + return newAttrib; } @@ -2049,13 +2006,12 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, const bool supportNonEncryptedDestination = winXpOrLater(); //encrypted destination is not supported with Windows 2000 -//caveat: function scope static initialization is not thread-safe in VS 2010! +//caveat: function scope static initialization is not thread-safe in VS 2010! -> still not sufficient if multiple threads access during static init!!! -void copyFileWindowsDefault(const Zstring& sourceFile, - const Zstring& targetFile, - const std::function& onUpdateCopyStatus, - InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse +InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse + const Zstring& targetFile, + const std::function& onUpdateCopyStatus) { //try to get backup read and write privileges: who knows, maybe this helps solve some obscure "access denied" errors try { activatePrivilege(SE_BACKUP_NAME); } @@ -2129,7 +2085,7 @@ void copyFileWindowsDefault(const Zstring& sourceFile, if (lastError == ERROR_INVALID_PARAMETER && isFatDrive(targetFile) && getFilesize(sourceFile) >= 4U * std::uint64_t(1024U * 1024 * 1024)) //throw FileError - errorDescr += L"\nFAT volumes cannot store files larger than 4 gigabyte."; + 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 filepath is of a restricted type. @@ -2139,47 +2095,45 @@ void copyFileWindowsDefault(const Zstring& sourceFile, throw FileError(errorMsg, errorDescr); } - if (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); - } - //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! setFileTimeRaw(targetFile, &cbd.fileInfoSrc.ftCreationTime, cbd.fileInfoSrc.ftLastWriteTime, ProcSymlink::FOLLOW); //throw FileError guardTarget.dismiss(); //target has been created successfully! + + 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 inline -void copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function& onUpdateCopyStatus, InSyncAttributes* sourceAttr) +InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function& onUpdateCopyStatus) { try { - copyFileWindowsDefault(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse + return copyFileWindowsDefault(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse } catch (ErrorShouldCopyAsSparse&) //we quickly check for this condition within callback of ::CopyFileEx()! { - copyFileWindowsSparse(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked + return copyFileWindowsSparse(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked } } //another layer of indirection solving 8.3 name clashes inline -void copyFileOsSpecific(const Zstring& sourceFile, - const Zstring& targetFile, - const std::function& onUpdateCopyStatus, - InSyncAttributes* sourceAttr) +InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, + const Zstring& targetFile, + const std::function& onUpdateCopyStatus) { try { - copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked + return copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked } catch (const ErrorTargetExisting&) { @@ -2187,8 +2141,7 @@ void copyFileOsSpecific(const Zstring& sourceFile, if (have8dot3NameClash(targetFile)) { Fix8Dot3NameClash dummy(targetFile); //throw FileError; move clashing filepath to the side - copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError; the short filepath name clash is solved, this should work now - return; + return copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError; the short filepath name clash is solved, this should work now } throw; } @@ -2196,10 +2149,9 @@ void copyFileOsSpecific(const Zstring& sourceFile, #elif defined ZEN_LINUX || defined ZEN_MAC -void copyFileOsSpecific(const Zstring& sourceFile, - const Zstring& targetFile, - const std::function& onUpdateCopyStatus, - InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting +InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting + const Zstring& targetFile, + const std::function& onUpdateCopyStatus) { FileInput fileIn(sourceFile); //throw FileError @@ -2221,40 +2173,31 @@ void copyFileOsSpecific(const Zstring& sourceFile, throw FileError(errorMsg, errorDescr); } + if (onUpdateCopyStatus) onUpdateCopyStatus(0); //throw X! + InSyncAttributes newAttrib = {}; zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(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 (onUpdateCopyStatus) onUpdateCopyStatus(0); //throw X! - std::vector buffer(128 * 1024); - do - { - const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError - - fileOut.write(&buffer[0], bytesRead); //throw FileError - - if (onUpdateCopyStatus) - onUpdateCopyStatus(bytesRead); //throw X! - } - while (!fileIn.eof()); + copyStream(fileIn, fileOut, std::min(fileIn .optimalBlockSize(), + fileOut.optimalBlockSize()), onUpdateCopyStatus); //throw FileError, X struct ::stat targetInfo = {}; if (::fstat(fileOut.getHandle(), &targetInfo) != 0) throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError()); - if (newAttrib) //return file statistics - { - newAttrib->fileSize = sourceInfo.st_size; + newAttrib.fileSize = sourceInfo.st_size; #ifdef ZEN_MAC - newAttrib->modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setFileTimeRaw() for consistency + newAttrib.modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setFileTimeRaw() for consistency #else - newAttrib->modificationTime = sourceInfo.st_mtime; + newAttrib.modificationTime = sourceInfo.st_mtime; #endif - newAttrib->sourceFileId = extractFileId(sourceInfo); - newAttrib->targetFileId = extractFileId(targetInfo); - } + newAttrib.sourceFileId = extractFileId(sourceInfo); + newAttrib.targetFileId = extractFileId(targetInfo); #ifdef ZEN_MAC //using ::copyfile with COPYFILE_DATA seems to trigger bugs unlike our stream-based copying! @@ -2265,6 +2208,8 @@ void copyFileOsSpecific(const Zstring& sourceFile, if (::fcopyfile(fileIn.getHandle(), fileOut.getHandle(), 0, COPYFILE_XATTR) != 0) throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)), L"copyfile", getLastError()); #endif + +fileOut.close(); //throw FileError -> optional, but good place to catch errors when closing stream! } //close output file handle before setting file time //we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation: @@ -2272,7 +2217,7 @@ void copyFileOsSpecific(const Zstring& sourceFile, //Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236 // http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 //OS X: https://sourceforge.net/p/freefilesync/discussion/help/thread/881357c0/ -#ifdef ZEN_MAC +#ifdef ZEN_MAC setFileTimeRaw(targetFile, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::FOLLOW); //throw FileError //sourceInfo.st_birthtime; -> only seconds-precision //sourceInfo.st_mtime; -> @@ -2281,6 +2226,7 @@ void copyFileOsSpecific(const Zstring& sourceFile, #endif guardTarget.dismiss(); //target has been created successfully! + return newAttrib; } #endif @@ -2288,25 +2234,21 @@ void copyFileOsSpecific(const Zstring& sourceFile, ------------------ |File Copy Layers| ------------------ - copyFile (setup transactional behavior) + copyNewFile | - copyFileWithPermissions - | - copyFileOsSpecific (solve 8.3 issue) + copyFileOsSpecific (solve 8.3 issue on Windows) | copyFileWindowsSelectRoutine / \ copyFileWindowsDefault(::CopyFileEx) copyFileWindowsSparse(::BackupRead/::BackupWrite) */ +} -inline -void copyFileWithPermissions(const Zstring& sourceFile, - const Zstring& targetFile, - bool copyFilePermissions, - const std::function& onUpdateCopyStatus, - InSyncAttributes* sourceAttr) + +InSyncAttributes zen::copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked + const std::function& onUpdateCopyStatus) { - copyFileOsSpecific(sourceFile, targetFile, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked + const InSyncAttributes attr = copyFileOsSpecific(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked if (copyFilePermissions) { @@ -2317,67 +2259,6 @@ void copyFileWithPermissions(const Zstring& sourceFile, guardTargetFile.dismiss(); //target has been created successfully! } -} -} - - -void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorFileLocked - const Zstring& targetFile, - bool copyFilePermissions, - bool transactionalCopy, - const std::function& onDeleteTargetFile, - const std::function& onUpdateCopyStatus, - InSyncAttributes* sourceAttr) -{ - if (transactionalCopy) - { - Zstring tmpTarget = targetFile + TEMP_FILE_ENDING; - - for (int i = 0;; ++i) - try - { - copyFileWithPermissions(sourceFile, tmpTarget, copyFilePermissions, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked - break; - } - catch (const ErrorTargetExisting&) //optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses - { - if (i == 10) throw; //avoid endless recursion in pathological cases, e.g. https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/36adac33 - tmpTarget = targetFile + Zchar('_') + numberTo(i) + TEMP_FILE_ENDING; - } - - //transactional behavior: ensure cleanup; not needed before copyFileWithPermissions() which is already transactional - zen::ScopeGuard guardTempFile = zen::makeGuard([&] { try { removeFile(tmpTarget); } catch (FileError&) {} }); - - //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite - if (onDeleteTargetFile) - onDeleteTargetFile(); //throw X - - //perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick! - renameFile(tmpTarget, targetFile); //throw FileError - - /* - CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" does - NOT PRESERVE the creation time of the .ffs_tmp file, but SILENTLY "reuses" whatever creation time the old "file.txt" had! - This "feature" is called "File System Tunneling": - http://blogs.msdn.com/b/oldnewthing/archive/2005/07/15/439261.aspx - http://support.microsoft.com/kb/172190/en-us - */ - - guardTempFile.dismiss(); - } - else - { - /* - Note: non-transactional file copy solves at least four problems: - -> skydrive - doesn't allow for .ffs_tmp extension and returns ERROR_INVALID_PARAMETER - -> network renaming issues - -> allow for true delete before copy to handle low disk space problems - -> higher performance on non-buffered drives (e.g. usb sticks) - */ - if (onDeleteTargetFile) - onDeleteTargetFile(); - - copyFileWithPermissions(sourceFile, targetFile, copyFilePermissions, onUpdateCopyStatus, sourceAttr); //throw FileError, ErrorTargetExisting, ErrorFileLocked - } + return attr; } diff --git a/zen/file_access.h b/zen/file_access.h index bd1b0168..0dfb650e 100644 --- a/zen/file_access.h +++ b/zen/file_access.h @@ -29,7 +29,7 @@ void setFileTime(const Zstring& filepath, std::int64_t modificationTime, ProcSym //symlink handling: always evaluate target std::uint64_t getFilesize(const Zstring& filepath); //throw FileError -std::uint64_t getFreeDiskSpace(const Zstring& path); //throw FileError +std::uint64_t getFreeDiskSpace(const Zstring& path); //throw FileError, returns 0 if not available bool removeFile(const Zstring& filepath); //throw FileError; return "false" if file is not existing void removeDirectory(const Zstring& directory, //throw FileError @@ -37,17 +37,18 @@ void removeDirectory(const Zstring& directory, //throw FileError const std::function& onBeforeDirDeletion = nullptr); //one call for each *existing* object! //rename file or directory: no copying!!! -void renameFile(const Zstring& oldName, const Zstring& newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting +void renameFile(const Zstring& itemPathOld, const Zstring& itemPathNew); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting bool supportsPermissions(const Zstring& dirpath); //throw FileError, dereferences symlinks //if parent directory not existing: create recursively: -void makeDirectory(const Zstring& directory, bool failIfExists = false); //throw FileError, ErrorTargetExisting +void makeNewDirectory(const Zstring& directory); //throw FileError, ErrorTargetExisting //fail if already existing or parent directory not existing: //directory should not end with path separator -//templateDir may be empty -void makeDirectoryPlain(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing +void copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, bool copyFilePermissions); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing + +void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions); //throw FileError struct InSyncAttributes { @@ -57,24 +58,9 @@ struct InSyncAttributes FileId targetFileId; }; -void copyFile(const Zstring& sourceFile, //throw FileError, ErrorFileLocked (Windows-only) - const Zstring& targetFile, //symlink handling: dereference source - bool copyFilePermissions, - bool transactionalCopy, - //if target is existing user needs to implement deletion: copyFile() NEVER overwrites target if already existing! - //if transactionalCopy == true, full read access on source had been proven at this point, so it's safe to delete it. - const std::function& onDeleteTargetFile, //may be nullptr; may throw! - //accummulated delta != file size! consider ADS, sparse, compressed files - const std::function& onUpdateCopyStatus, //may be nullptr; may throw! - - InSyncAttributes* newAttrib = nullptr); //return current attributes at the time of copy - -//Note: it MAY happen that copyFile() leaves temp files behind, e.g. temporary network drop. -// => clean them up at an appropriate time (automatically set sync directions to delete them). They have the following ending: - -const Zchar TEMP_FILE_ENDING[] = Zstr(".ffs_tmp"); //don't use Zstring as global constant: avoid static initialization order problem in global namespace! - -void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions); //throw FileError +InSyncAttributes copyNewFile(const Zstring& sourceFile, const Zstring& targetFile, bool copyFilePermissions, //throw FileError, ErrorTargetExisting, ErrorFileLocked + //accummulated delta != file size! consider ADS, sparse, compressed files + const std::function& onUpdateCopyStatus); //may be nullptr; throw X! } #endif //FILE_ACCESS_H_8017341345614857 diff --git a/zen/file_id_def.h b/zen/file_id_def.h index a96e978b..7a2059a6 100644 --- a/zen/file_id_def.h +++ b/zen/file_id_def.h @@ -23,7 +23,8 @@ namespace zen typedef DWORD DeviceId; typedef ULONGLONG FileIndex; -typedef std::pair FileId; +typedef std::pair FileId; //optional! (however, always set on Linux, and *generally* available on Windows) + inline FileId extractFileId(const BY_HANDLE_FILE_INFORMATION& fileInfo) diff --git a/zen/file_io.cpp b/zen/file_io.cpp index d4bfdd9b..a5412f19 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -5,6 +5,7 @@ // ************************************************************************** #include "file_io.h" +#include "file_access.h" #ifdef ZEN_WIN #include "long_path_prefix.h" @@ -75,10 +76,10 @@ void checkForUnsupportedType(const Zstring& filepath) //throw FileError } -FileInput::FileInput(FileHandle handle, const Zstring& filepath) : FileInputBase(filepath), fileHandle(handle) {} +FileInput::FileInput(FileHandle handle, const Zstring& filepath) : FileBase(filepath), fileHandle(handle) {} -FileInput::FileInput(const Zstring& filepath) : FileInputBase(filepath) //throw FileError +FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileError, ErrorFileLocked { #ifdef ZEN_WIN auto createHandle = [&](DWORD dwShareMode) @@ -135,6 +136,7 @@ FileInput::FileInput(const Zstring& filepath) : FileInputBase(filepath) //throw const Zstring procList = getLockingProcessNames(filepath); //throw() if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; + throw ErrorFileLocked(errorMsg, errorDescr); } throw FileError(errorMsg, errorDescr); } @@ -169,62 +171,50 @@ FileInput::~FileInput() size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError; returns actual number of bytes read { - assert(!eof() || bytesToRead == 0); -#ifdef ZEN_WIN - if (bytesToRead == 0) return 0; - - DWORD bytesRead = 0; - if (!::ReadFile(fileHandle, //__in HANDLE hFile, - buffer, //__out LPVOID lpBuffer, - static_cast(bytesToRead), //__in DWORD nNumberOfBytesToRead, - &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, - nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped - throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"ReadFile", getLastError()); - - if (bytesRead < bytesToRead) //verify only! - setEof(); // - - if (bytesRead > bytesToRead) - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"buffer overflow"); //user should never see this - - return bytesRead; - -#elif defined ZEN_LINUX || defined ZEN_MAC - //Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-8.23.tar.xz size_t bytesReadTotal = 0; - while (bytesToRead > 0 && !eof()) //"read() with a count of 0 returns zero" => indistinguishable from eof! => check! + while (bytesToRead > 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! { +#ifdef ZEN_WIN + //test for end of file: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365690%28v=vs.85%29.aspx + DWORD bytesRead = 0; + if (!::ReadFile(fileHandle, //__in HANDLE hFile, + buffer, //__out LPVOID lpBuffer, + static_cast(bytesToRead), //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, + nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped + throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilePath())), L"ReadFile", getLastError()); + +#elif defined ZEN_LINUX || defined ZEN_MAC ssize_t bytesRead = 0; do { bytesRead = ::read(fileHandle, buffer, bytesToRead); } - while (bytesRead < 0 && errno == EINTR); + while (bytesRead < 0 && errno == EINTR); //Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-8.23.tar.xz if (bytesRead < 0) - throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"read", getLastError()); - else if (bytesRead == 0) //"zero indicates end of file" - setEof(); - else if (bytesRead > static_cast(bytesToRead)) //better safe than sorry - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"buffer overflow"); //user should never see this + throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilePath())), L"read", getLastError()); +#endif + if (bytesRead == 0) //"zero indicates end of file" + return bytesReadTotal; + else if (static_cast(bytesRead) > bytesToRead) //better safe than sorry + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilePath())), L"ReadFile: buffer overflow."); //user should never see this //if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead" => loop! buffer = static_cast(buffer) + bytesRead; //suppress warning about pointer arithmetics on void* bytesToRead -= bytesRead; bytesReadTotal += bytesRead; } - return bytesReadTotal; -#endif } //---------------------------------------------------------------------------------------------------- -FileOutput::FileOutput(FileHandle handle, const Zstring& filepath) : FileOutputBase(filepath), fileHandle(handle) {} +FileOutput::FileOutput(FileHandle handle, const Zstring& filepath) : FileBase(filepath), fileHandle(handle) {} -FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileOutputBase(filepath) //throw FileError, ErrorTargetExisting +FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileBase(filepath) //throw FileError, ErrorTargetExisting { #ifdef ZEN_WIN const DWORD dwCreationDisposition = access == FileOutput::ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW; @@ -307,12 +297,45 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileOutputB } +namespace +{ +inline +FileHandle getInvalidHandle() +{ +#ifdef ZEN_WIN + return INVALID_HANDLE_VALUE; +#elif defined ZEN_LINUX || defined ZEN_MAC + return -1; +#endif +} +} + + FileOutput::~FileOutput() { + if (fileHandle != getInvalidHandle()) + try + { + close(); //throw FileError + } + catch (FileError&) { assert(false); } +} + + +void FileOutput::close() //throw FileError +{ + if (fileHandle == getInvalidHandle()) + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"Contract error: close() called more than once."); + ZEN_ON_SCOPE_EXIT(fileHandle = getInvalidHandle()); + + //no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional! + #ifdef ZEN_WIN - ::CloseHandle(fileHandle); + if (!::CloseHandle(fileHandle)) + throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"CloseHandle", getLastError()); #elif defined ZEN_LINUX || defined ZEN_MAC - ::close(fileHandle); + if (::close(fileHandle) != 0) + throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"close", getLastError()); #endif } @@ -326,10 +349,10 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro static_cast(bytesToWrite), //__in DWORD nNumberOfBytesToWrite, &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped - throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"WriteFile", getLastError()); + throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"WriteFile", getLastError()); if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"Incomplete write."); //user should never see this + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"WriteFile: incomplete write."); //user should never see this #elif defined ZEN_LINUX || defined ZEN_MAC while (bytesToWrite > 0) @@ -346,10 +369,10 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers errno = ENOSPC; - throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"write", getLastError()); + throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"write", getLastError()); } if (bytesWritten > static_cast(bytesToWrite)) //better safe than sorry - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"buffer overflow"); //user should never see this + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"write: buffer overflow."); //user should never see this //if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"! buffer = static_cast(buffer) + bytesWritten; //suppress warning about pointer arithmetics on void* diff --git a/zen/file_io.h b/zen/file_io.h index ee7841ca..648bafe8 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -7,7 +7,6 @@ #ifndef FILEIO_89578342758342572345 #define FILEIO_89578342758342572345 -#include "file_io_base.h" #include "file_error.h" #ifdef ZEN_WIN @@ -31,30 +30,56 @@ namespace zen typedef int FileHandle; #endif -class FileInput : public FileInputBase +class FileBase { public: - FileInput(const Zstring& filepath); //throw FileError + const Zstring& getFilePath() const { return filename_; } + +protected: + FileBase(const Zstring& filename) : filename_(filename) {} + +private: + FileBase (const FileBase&) = delete; + FileBase& operator=(const FileBase&) = delete; + + const Zstring filename_; +}; + +//----------------------------------------------------------------------------------------------- + +class FileInput : public FileBase +{ +public: + FileInput(const Zstring& filepath); //throw FileError, ErrorFileLocked FileInput(FileHandle handle, const Zstring& filepath); //takes ownership! ~FileInput(); - size_t read(void* buffer, size_t bytesToRead) override; //throw FileError; returns actual number of bytes read + size_t read(void* buffer, size_t bytesToRead); //throw FileError; returns "bytesToRead", unless end of file! FileHandle getHandle() { return fileHandle; } + size_t optimalBlockSize() const { return 128 * 1024; } private: FileHandle fileHandle; }; -class FileOutput : public FileOutputBase +class FileOutput : public FileBase { public: + enum AccessFlag + { + ACC_OVERWRITE, + ACC_CREATE_NEW + }; + FileOutput(const Zstring& filepath, AccessFlag access); //throw FileError, ErrorTargetExisting FileOutput(FileHandle handle, const Zstring& filepath); //takes ownership! ~FileOutput(); + void close(); //throw FileError -> optional, but good place to catch errors when closing stream! - void write(const void* buffer, size_t bytesToWrite) override; //throw FileError + void write(const void* buffer, size_t bytesToWrite); //throw FileError FileHandle getHandle() { return fileHandle; } + size_t optimalBlockSize() const { return 128 * 1024; } private: FileHandle fileHandle; diff --git a/zen/file_io_base.h b/zen/file_io_base.h deleted file mode 100644 index 9b6b27ca..00000000 --- a/zen/file_io_base.h +++ /dev/null @@ -1,64 +0,0 @@ -// ************************************************************************** -// * 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 gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#ifndef FILEIO_BASE_H_INCLUDED_23432431789615314 -#define FILEIO_BASE_H_INCLUDED_23432431789615314 - -#include "zstring.h" - -namespace zen -{ -class FileBase -{ -public: - const Zstring& getFilename() const { return filename_; } - -protected: - FileBase(const Zstring& filename) : filename_(filename) {} - ~FileBase() {} - -private: - FileBase (const FileBase&) = delete; - FileBase& operator=(const FileBase&) = delete; - - const Zstring filename_; -}; - - -class FileInputBase : public FileBase -{ -public: - virtual size_t read(void* buffer, size_t bytesToRead) = 0; //throw FileError; returns actual number of bytes read - bool eof() const { return eofReached; } //end of file reached - -protected: - FileInputBase(const Zstring& filename) : FileBase(filename), eofReached(false) {} - ~FileInputBase() {} - void setEof() { eofReached = true; } - -private: - bool eofReached; -}; - - -class FileOutputBase : public FileBase -{ -public: - enum AccessFlag - { - ACC_OVERWRITE, - ACC_CREATE_NEW - }; - virtual void write(const void* buffer, size_t bytesToWrite) = 0; //throw FileError - -protected: - FileOutputBase(const Zstring& filename) : FileBase(filename) {} - ~FileOutputBase() {} -}; - -} - -#endif //FILEIO_BASE_H_INCLUDED_23432431789615314 diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index baf40e9e..66dcd198 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -13,12 +13,12 @@ #include "file_access.h" #include "symlink_target.h" #elif defined ZEN_MAC - #include "osx_string.h" + #include "osx_string.h" #endif #if defined ZEN_LINUX || defined ZEN_MAC #include //offsetof - #include //::pathconf() + #include //::pathconf() #include #include #endif @@ -27,9 +27,9 @@ using namespace zen; void zen::traverseFolder(const Zstring& dirPath, - const std::function& onFile, - const std::function& onDir, - const std::function& onLink, + const std::function& onFile, + const std::function& onDir, + const std::function& onLink, const std::function& onError) { try @@ -51,13 +51,12 @@ void zen::traverseFolder(const Zstring& dirPath, } ZEN_ON_SCOPE_EXIT(::FindClose(hDir)); - bool firstIteration = true; + bool firstIteration = true; for (;;) - { - if (firstIteration) //keep ::FindNextFile at the start of the for-loop to support "continue"! - firstIteration = false; - else - if (!::FindNextFile(hDir, &findData)) + { + if (firstIteration) //keep ::FindNextFile at the start of the for-loop to support "continue"! + firstIteration = false; + else if (!::FindNextFile(hDir, &findData)) { const DWORD lastError = ::GetLastError(); if (lastError == ERROR_NO_MORE_FILES) //not an error situation @@ -69,7 +68,7 @@ void zen::traverseFolder(const Zstring& dirPath, //skip "." and ".." const Zchar* const shortName = findData.cFileName; - if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"Data corruption: Found item without name."); + if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"FindNextFile: Data corruption, found item without name."); if (shortName[0] == L'.' && (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) continue; @@ -94,32 +93,27 @@ void zen::traverseFolder(const Zstring& dirPath, } #elif defined ZEN_LINUX || defined ZEN_MAC - const Zstring dirPathFmt = //remove trailing slash - dirPath.size() > 1 && endsWith(dirPath, FILE_NAME_SEPARATOR) ? //exception: allow '/' - beforeLast(dirPath, FILE_NAME_SEPARATOR) : - dirPath; - /* quote: "Since POSIX.1 does not specify the size of the d_name field, and other nonstandard fields may precede that field within the dirent structure, portable applications that use readdir_r() should allocate the buffer whose address is passed in entry as follows: len = offsetof(struct dirent, d_name) + pathconf(dirPath, _PC_NAME_MAX) + 1 entryp = malloc(len); */ - const size_t nameMax = std::max(::pathconf(dirPathFmt.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) - std::vector buffer(offsetof(struct ::dirent, d_name) + nameMax + 1); + const size_t nameMax = std::max(::pathconf(dirPath.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) + std::vector buffer(offsetof(struct ::dirent, d_name) + nameMax + 1); #ifdef ZEN_MAC - std::vector bufferUtfDecomposed; + std::vector bufferUtfDecomposed; #endif - DIR* dirObj = ::opendir(dirPathFmt.c_str()); //directory must NOT end with path separator, except "/" + DIR* dirObj = ::opendir(dirPath.c_str()); //directory must NOT end with path separator, except "/" if (!dirObj) - throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPathFmt)), L"opendir", getLastError()); + throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPath)), L"opendir", getLastError()); ZEN_ON_SCOPE_EXIT(::closedir(dirObj)); //never close nullptr handles! -> crash for (;;) { struct ::dirent* dirEntry = nullptr; if (::readdir_r(dirObj, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0) - throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPathFmt)), L"readdir_r", getLastError()); + throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"readdir_r", getLastError()); //don't retry but restart dir traversal on error! http://blogs.msdn.com/b/oldnewthing/archive/2014/06/12/10533529.aspx if (!dirEntry) //no more items @@ -128,7 +122,7 @@ void zen::traverseFolder(const Zstring& dirPath, //don't return "." and ".." const char* shortName = dirEntry->d_name; - if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"Data corruption: Found item without name."); + if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"readdir_r: Data corruption, found item without name."); if (shortName[0] == '.' && (shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0))) continue; @@ -152,20 +146,20 @@ void zen::traverseFolder(const Zstring& dirPath, //const char* sampleDecomposed = "\x6f\xcc\x81.txt"; //const char* samplePrecomposed = "\xc3\xb3.txt"; #endif - const Zstring& itempath = appendSeparator(dirPathFmt) + shortName; + const Zstring& itempath = appendSeparator(dirPath) + shortName; struct ::stat statData = {}; - try - { - if (::lstat(itempath.c_str(), &statData) != 0) //lstat() does not resolve symlinks - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(itempath)), L"lstat", getLastError()); - } - catch (const FileError& e) - { - if (onError) - onError(e.toString()); - continue; //ignore error: skip file - } + try + { + if (::lstat(itempath.c_str(), &statData) != 0) //lstat() does not resolve symlinks + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(itempath)), L"lstat", getLastError()); + } + catch (const FileError& e) + { + if (onError) + onError(e.toString()); + continue; //ignore error: skip file + } if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! { diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h index 19b838ef..dfdf60ba 100644 --- a/zen/long_path_prefix.h +++ b/zen/long_path_prefix.h @@ -120,8 +120,8 @@ Zstring zen::ntPathToWin32Path(const Zstring& path) //noexcept { std::vector buf(bufSize); const DWORD charsWritten = ::GetEnvironmentVariable(L"SystemRoot", //_In_opt_ LPCTSTR lpName, - &buf[0], //_Out_opt_ LPTSTR lpBuffer, - bufSize); //_In_ DWORD nSize + &buf[0], //_Out_opt_ LPTSTR lpBuffer, + bufSize); //_In_ DWORD nSize if (0 < charsWritten && charsWritten < bufSize) return replaceCpy(path, L"\\SystemRoot\\", appendSeparator(Zstring(&buf[0], charsWritten)), false); diff --git a/zen/notify_removal.cpp b/zen/notify_removal.cpp deleted file mode 100644 index f528e81b..00000000 --- a/zen/notify_removal.cpp +++ /dev/null @@ -1,212 +0,0 @@ -// ************************************************************************** -// * 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 gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#include "notify_removal.h" -#include -#include -#include -#include "scope_guard.h" - -using namespace zen; - - -class MessageProvider //administrates a single dummy window to receive messages -{ -public: - static MessageProvider& instance() //throw FileError - { - static MessageProvider inst; - return inst; - } - - struct Listener - { - virtual ~Listener() {} - virtual void onMessage(UINT message, WPARAM wParam, LPARAM lParam) = 0; //throw()! - }; - void registerListener(Listener& l) { listener.insert(&l); } - void unregisterListener(Listener& l) { listener.erase(&l); } //don't unregister objects with static lifetime - - HWND getWnd() const { return windowHandle; } //get handle in order to register additional notifications - -private: - MessageProvider(); - ~MessageProvider(); - MessageProvider (const MessageProvider&) = delete; - MessageProvider& operator=(const MessageProvider&) = delete; - - static const wchar_t dummyClassName[]; - - static LRESULT CALLBACK topWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - - void processMessage(UINT message, WPARAM wParam, LPARAM lParam); - - const HINSTANCE hMainModule; - HWND windowHandle; - - std::set listener; -}; - - -const wchar_t MessageProvider::dummyClassName[] = L"E6AD5EB1-527B-4EEF-AC75-27883B233380"; //random name - - -LRESULT CALLBACK MessageProvider::topWndProc(HWND hwnd, //handle to window - UINT uMsg, //message identifier - WPARAM wParam, //first message parameter - LPARAM lParam) //second message parameter -{ - if (auto pThis = reinterpret_cast(::GetWindowLongPtr(hwnd, GWLP_USERDATA))) - try - { - pThis->processMessage(uMsg, wParam, lParam); //not supposed to throw! - } - catch (...) { assert(false); } - - return ::DefWindowProc(hwnd, uMsg, wParam, lParam); -} - - -MessageProvider::MessageProvider() : - hMainModule(::GetModuleHandle(nullptr)), //get program's module handle - windowHandle(nullptr) -{ - if (!hMainModule) - throwFileError(_("Unable to register to receive system messages."), L"GetModuleHandle", getLastError()); - - //register the main window class - WNDCLASS wc = {}; - wc.lpfnWndProc = topWndProc; - wc.hInstance = hMainModule; - wc.lpszClassName = dummyClassName; - - if (::RegisterClass(&wc) == 0) - throwFileError(_("Unable to register to receive system messages."), L"RegisterClass", getLastError()); - - ScopeGuard guardConstructor = zen::makeGuard([&] { this->~MessageProvider(); }); - - //create dummy-window - windowHandle = ::CreateWindow(dummyClassName, //_In_opt_ LPCTSTR lpClassName, - nullptr, //_In_opt_ LPCTSTR lpWindowName, - 0, //_In_ DWORD dwStyle, - 0, 0, 0, 0, //_In_ int x, y, nWidth, nHeight, - nullptr, //_In_opt_ HWND hWndParent; we need a toplevel window to receive device arrival events, not a message-window (HWND_MESSAGE)! - nullptr, //_In_opt_ HMENU hMenu, - hMainModule, //_In_opt_ HINSTANCE hInstance, - nullptr); //_In_opt_ LPVOID lpParam - if (!windowHandle) - throwFileError(_("Unable to register to receive system messages."), L"CreateWindow", getLastError()); - - //store this-pointer for topWndProc() to use: do this AFTER CreateWindow() to avoid processing messages while this constructor is running!!! - //unlike: http://blogs.msdn.com/b/oldnewthing/archive/2014/02/03/10496248.aspx - ::SetLastError(ERROR_SUCCESS); //[!] required for proper error handling, see MSDN, SetWindowLongPtr - if (::SetWindowLongPtr(windowHandle, GWLP_USERDATA, reinterpret_cast(this)) == 0 && ::GetLastError() != ERROR_SUCCESS) - throwFileError(_("Unable to register to receive system messages."), L"SetWindowLongPtr", ::GetLastError()); - - guardConstructor.dismiss(); -} - - -MessageProvider::~MessageProvider() -{ - //clean-up in reverse order - if (windowHandle) - ::DestroyWindow(windowHandle); - ::UnregisterClass(dummyClassName, //LPCTSTR lpClassName OR ATOM in low-order word! - hMainModule); //HINSTANCE hInstance -} - - -void MessageProvider::processMessage(UINT message, WPARAM wParam, LPARAM lParam) -{ - for (Listener* ls : listener) - ls->onMessage(message, wParam, lParam); -} - -//#################################################################################################### - -class NotifyRequestDeviceRemoval::Pimpl : private MessageProvider::Listener -{ -public: - Pimpl(NotifyRequestDeviceRemoval& parent, HANDLE hDir) : //throw FileError - parent_(parent) - { - MessageProvider::instance().registerListener(*this); //throw FileError - - ScopeGuard guardProvider = makeGuard([&] { MessageProvider::instance().unregisterListener(*this); }); - - //register handles to receive notifications - DEV_BROADCAST_HANDLE filter = {}; - filter.dbch_size = sizeof(filter); - filter.dbch_devicetype = DBT_DEVTYP_HANDLE; - filter.dbch_handle = hDir; - - hNotification = ::RegisterDeviceNotification(MessageProvider::instance().getWnd(), //__in HANDLE hRecipient, - &filter, //__in LPVOID NotificationFilter, - DEVICE_NOTIFY_WINDOW_HANDLE); //__in DWORD Flags - if (!hNotification) - { - const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! - if (lastError != ERROR_CALL_NOT_IMPLEMENTED && //fail on SAMBA share: this shouldn't be a showstopper! - lastError != ERROR_SERVICE_SPECIFIC_ERROR && //neither should be fail for "Pogoplug" mapped network drives - lastError != ERROR_INVALID_DATA) //this seems to happen for a NetDrive-mapped FTP server - throwFileError(_("Unable to register to receive system messages."), L"RegisterDeviceNotification", lastError); - } - - guardProvider.dismiss(); - } - - ~Pimpl() - { - if (hNotification) - ::UnregisterDeviceNotification(hNotification); - MessageProvider::instance().unregisterListener(*this); - } - -private: - Pimpl (const Pimpl&) = delete; - Pimpl& operator=(const Pimpl&) = delete; - - void onMessage(UINT message, WPARAM wParam, LPARAM lParam) override //throw()! - { - //DBT_DEVICEQUERYREMOVE example: http://msdn.microsoft.com/en-us/library/aa363427(v=VS.85).aspx - if (message == WM_DEVICECHANGE) - if (wParam == DBT_DEVICEQUERYREMOVE || - wParam == DBT_DEVICEQUERYREMOVEFAILED || - wParam == DBT_DEVICEREMOVECOMPLETE) - { - PDEV_BROADCAST_HDR header = reinterpret_cast(lParam); - if (header->dbch_devicetype == DBT_DEVTYP_HANDLE) - { - PDEV_BROADCAST_HANDLE body = reinterpret_cast(lParam); - - if (body->dbch_hdevnotify == hNotification) //is it for the notification we registered? - switch (wParam) - { - case DBT_DEVICEQUERYREMOVE: - parent_.onRequestRemoval(body->dbch_handle); - break; - case DBT_DEVICEQUERYREMOVEFAILED: - parent_.onRemovalFinished(body->dbch_handle, false); - break; - case DBT_DEVICEREMOVECOMPLETE: - parent_.onRemovalFinished(body->dbch_handle, true); - break; - } - } - } - } - - NotifyRequestDeviceRemoval& parent_; - HDEVNOTIFY hNotification; -}; - -//#################################################################################################### - -NotifyRequestDeviceRemoval::NotifyRequestDeviceRemoval(HANDLE hDir) : pimpl(zen::make_unique(*this, hDir)) {} - - -NotifyRequestDeviceRemoval::~NotifyRequestDeviceRemoval() {} //make sure ~unique_ptr() works with complete type diff --git a/zen/notify_removal.h b/zen/notify_removal.h deleted file mode 100644 index 08d75818..00000000 --- a/zen/notify_removal.h +++ /dev/null @@ -1,34 +0,0 @@ -// ************************************************************************** -// * 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 gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#ifndef NOTIFY_H_INCLUDED_257804267562 -#define NOTIFY_H_INCLUDED_257804267562 - -#include -#include "win.h" //includes "windows.h" -#include "file_error.h" - -//handle (user-) request for device removal via template method pattern -//evaluate directly after processing window messages -class NotifyRequestDeviceRemoval -{ -public: - NotifyRequestDeviceRemoval(HANDLE hDir); //throw FileError - virtual ~NotifyRequestDeviceRemoval(); - -private: - virtual void onRequestRemoval(HANDLE hnd) = 0; //throw()! - //NOTE: onRemovalFinished is NOT guaranteed to execute after onRequestRemoval()! but most likely will - virtual void onRemovalFinished(HANDLE hnd, bool successful) = 0; //throw()! - - NotifyRequestDeviceRemoval (NotifyRequestDeviceRemoval&) = delete; - NotifyRequestDeviceRemoval& operator=(NotifyRequestDeviceRemoval&) = delete; - - class Pimpl; - std::unique_ptr pimpl; -}; - -#endif //NOTIFY_H_INCLUDED_257804267562 diff --git a/zen/perf.h b/zen/perf.h index 53519274..f43bc00f 100644 --- a/zen/perf.h +++ b/zen/perf.h @@ -30,41 +30,82 @@ public: ZEN_DEPRECATE PerfTimer() : //throw TimerError - ticksPerSec_(ticksPerSec()), startTime(getTicks()), resultShown(false) + ticksPerSec_(ticksPerSec()), + resultShown(false), + startTime(getTicksNow()), + paused(false), + elapsedUntilPause(0) { //std::clock() - "counts CPU time in Linux GCC and wall time in VC++" - WTF!??? - - if (ticksPerSec_ == 0 || !startTime.isValid()) + if (ticksPerSec_ == 0) throw TimerError(); } - ~PerfTimer() { if (!resultShown) showResult(); } + ~PerfTimer() { if (!resultShown) try { showResult(); } catch (TimerError&){} } + + void pause() + { + if (!paused) + { + paused = true; + elapsedUntilPause += dist(startTime, getTicksNow()); + } + } + + void resume() + { + if (paused) + { + paused = false; + startTime = getTicksNow(); + } + } + + void restart() + { + startTime = getTicksNow(); + paused = false; + elapsedUntilPause = 0; + } + + int64_t timeMs() const + { + int64_t ticksTotal = elapsedUntilPause; + if (!paused) + ticksTotal += dist(startTime, getTicksNow()); + return 1000 * ticksTotal / ticksPerSec_; + } void showResult() { - const TickVal now = getTicks(); - if (!now.isValid()) - throw TimerError(); + const bool wasRunning = !paused; + if (wasRunning) pause(); //don't include call to MessageBox()! + ZEN_ON_SCOPE_EXIT(if (wasRunning) resume()); - const std::int64_t delta = 1000 * dist(startTime, now) / ticksPerSec_; #ifdef ZEN_WIN std::wostringstream ss; - ss << delta << L" ms"; + ss << timeMs() << L" ms"; ::MessageBox(nullptr, ss.str().c_str(), L"Timer", MB_OK); #else - std::clog << "Perf: duration: " << delta << " ms\n"; + std::clog << "Perf: duration: " << timeMs() << " ms\n"; #endif resultShown = true; + } - startTime = getTicks(); //don't include call to MessageBox()! - if (!startTime.isValid()) +private: + TickVal getTicksNow() const + { + const TickVal now = getTicks(); + if (!now.isValid()) throw TimerError(); + return now; } -private: const std::int64_t ticksPerSec_; - TickVal startTime; bool resultShown; + TickVal startTime; + bool paused; + int64_t elapsedUntilPause; }; } diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 3b5ac421..ed6669ef 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -75,10 +75,9 @@ void zen::recycleOrDelete(const std::vector& itempaths, const std::func { if (itempaths.empty()) return; - //::SetFileAttributes(applyLongPathPrefix(itempath).c_str(), FILE_ATTRIBUTE_NORMAL); //warning: moving long file paths to recycler does not work! - //both ::SHFileOperation() and ::IFileOperation() cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success - //both ::SHFileOperation() and ::IFileOperation() can't handle \\?\-prefix! + //both ::SHFileOperation() and ::IFileOperation cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success + //both ::SHFileOperation() and ::IFileOperation can't handle \\?\-prefix! if (vistaOrLater()) //new recycle bin usage: available since Vista { @@ -95,20 +94,28 @@ void zen::recycleOrDelete(const std::vector& itempaths, const std::func for (auto it = itempaths.begin(); it != itempaths.end(); ++it) //CAUTION: do not create temporary strings here!! cNames.push_back(it->c_str()); + + CallbackData cbd(notifyDeletionStatus); if (!moveToRecycleBin(&cNames[0], cNames.size(), onRecyclerCallback, &cbd)) { if (cbd.exception) std::rethrow_exception(cbd.exception); - std::wstring itempathFmt = fmtFileName(itempaths[0]); //probably not the correct file name for file lists larger than 1! - if (itempaths.size() > 1) - itempathFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one + if (cNames.size() == 1) + throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[0])), getLastErrorMessage()); - throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt), getLastErrorMessage()); //already includes details about locking errors! + //batch recycling failed: retry one-by-one to get a better error message; see FileOperation.dll + for (size_t i = 0; i < cNames.size(); ++i) + { + if (notifyDeletionStatus) notifyDeletionStatus(itempaths[i]); + + if (!moveToRecycleBin(&cNames[i], 1, nullptr, nullptr)) + throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[i])), getLastErrorMessage()); //already includes details about locking errors! + } } } - else //regular recycle bin usage: available since XP + else //regular recycle bin usage: available since XP: 1. bad error reporting 2. early failure { Zstring itempathsDoubleNull; for (const Zstring& itempath : itempaths) @@ -122,7 +129,7 @@ void zen::recycleOrDelete(const std::vector& itempaths, const std::func fileOp.wFunc = FO_DELETE; fileOp.pFrom = itempathsDoubleNull.c_str(); fileOp.pTo = nullptr; - fileOp.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | FOF_NOERRORUI; + fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; fileOp.fAnyOperationsAborted = false; fileOp.hNameMappings = nullptr; fileOp.lpszProgressTitle = nullptr; @@ -130,7 +137,10 @@ void zen::recycleOrDelete(const std::vector& itempaths, const std::func //"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe." if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) { - throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[0]))); //probably not the correct file name for file list larger than 1! + std::wstring itempathFmt = fmtFileName(itempaths[0]); //probably not the correct file name for file lists larger than 1! + if (itempaths.size() > 1) + itempathFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one + throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt)); } } } @@ -143,7 +153,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it! #ifdef ZEN_WIN - recycleOrDelete({ itempath }, nullptr); //throw FileError + recycleOrDelete({ itempath }, nullptr); //throw FileError #elif defined ZEN_LINUX GFile* file = ::g_file_new_for_path(itempath.c_str()); //never fails according to docu @@ -157,7 +167,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempath)); if (!error) - throw FileError(errorMsg, L"Unknown error."); //user should never see this + throw FileError(errorMsg, L"g_file_trash: unknown error."); //user should never see this //implement same behavior as in Windows: if recycler is not existing, delete permanently if (error->code == G_IO_ERROR_NOT_SUPPORTED) diff --git a/zen/serialize.h b/zen/serialize.h index 54baf75a..4af12af1 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -25,12 +25,13 @@ binary container for data storage: must support "basic" std::vector interface (e //binary container reference implementations typedef Zbase Utf8String; //ref-counted + COW text stream + guaranteed performance: exponential growth -class BinaryStream; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?) +class ByteArray; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?) -class BinaryStream //essentially a std::vector with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite + +class ByteArray //essentially a std::vector with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite { public: - BinaryStream() : buffer(std::make_shared>()) {} + ByteArray() : buffer(std::make_shared>()) {} typedef std::vector::value_type value_type; typedef std::vector::iterator iterator; @@ -46,7 +47,7 @@ public: size_t size() const { return buffer->size(); } bool empty() const { return buffer->empty(); } - inline friend bool operator==(const BinaryStream& lhs, const BinaryStream& rhs) { return *lhs.buffer == *rhs.buffer; } + inline friend bool operator==(const ByteArray& lhs, const ByteArray& rhs) { return *lhs.buffer == *rhs.buffer; } private: std::shared_ptr> buffer; //always bound! @@ -65,7 +66,7 @@ template BinContainer loadBinStream(const Zstring& filepath ----------------------------- struct BinInputStream { - const void* requestRead(size_t len); //expect external read of len bytes + size_t read(void* data, size_t len); //return "len" bytes unless end of stream! }; ------------------------------ @@ -73,58 +74,62 @@ struct BinInputStream ------------------------------ struct BinOutputStream { - void* requestWrite(size_t len); //expect external write of len bytes + void write(const void* data, size_t len); }; */ -//binary input/output stream reference implementation -class UnexpectedEndOfStreamError {}; +//binary input/output stream reference implementations: -struct BinStreamIn //throw UnexpectedEndOfStreamError +template +struct MemoryStreamIn { - BinStreamIn(const BinaryStream& cont) : buffer(cont), pos(0) {} //this better be cheap! + MemoryStreamIn(const BinContainer& cont) : buffer(cont), pos(0) {} //this better be cheap! - const void* requestRead(size_t len) //throw UnexpectedEndOfStreamError + size_t read(void* data, size_t len) //return "len" bytes unless end of stream! { - if (len == 0) return nullptr; //don't allow for possibility to access empty buffer - if (pos + len > buffer.size()) - throw UnexpectedEndOfStreamError(); - size_t oldPos = pos; - pos += len; - return &*buffer.begin() + oldPos; + static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes + const size_t bytesRead = std::min(len, buffer.size() - pos); + auto itFirst = buffer.begin() + pos; + std::copy(itFirst, itFirst + bytesRead, static_cast(data)); + pos += bytesRead; + return bytesRead; } private: - BinaryStream buffer; + const BinContainer buffer; size_t pos; }; -struct BinStreamOut +template +struct MemoryStreamOut { - void* requestWrite(size_t len) + void write(const void* data, size_t len) { - if (len == 0) return nullptr; //don't allow for possibility to access empty buffer + static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes const size_t oldSize = buffer.size(); buffer.resize(oldSize + len); - return &*buffer.begin() + oldSize; + std::copy(static_cast(data), static_cast(data) + len, buffer.begin() + oldSize); } - BinaryStream get() { return buffer; } + const BinContainer& ref() const { return buffer; } private: - BinaryStream buffer; + BinContainer buffer; }; //---------------------------------------------------------------------- //functions based on binary stream abstraction +template +void copyStream(BinInputStream& streamIn, BinOutputStream& streamOut, const std::function& onNotifyCopyStatus); //optional + template void writeNumber (BinOutputStream& stream, const N& num); // template void writeContainer(BinOutputStream& stream, const C& str); //throw () template < class BinOutputStream> void writeArray (BinOutputStream& stream, const void* data, size_t len); // //---------------------------------------------------------------------- - -template N readNumber (BinInputStream& stream); // -template C readContainer(BinInputStream& stream); //throw UnexpectedEndOfStreamError (corrupted data) +class UnexpectedEndOfStreamError {}; +template N readNumber (BinInputStream& stream); //throw UnexpectedEndOfStreamError (corrupted data) +template C readContainer(BinInputStream& stream); // template < class BinInputStream> void readArray (BinInputStream& stream, void* data, size_t len); // @@ -135,66 +140,54 @@ template < class BinInputStream> void readArray (BinInputStream& stre //-----------------------implementation------------------------------- -template inline -void saveBinStream(const Zstring& filepath, //throw FileError - const BinContainer& cont, - const std::function& onUpdateStatus) //optional +template inline +void copyStream(BinInputStream& streamIn, BinOutputStream& streamOut, size_t blockSize, + const std::function& onNotifyCopyStatus) //optional { - static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes (until further) - - FileOutput fileOut(filepath, zen::FileOutput::ACC_OVERWRITE); //throw FileError - if (!cont.empty()) + assert(blockSize > 0); + std::vector buffer(blockSize); + for (;;) { - const size_t blockSize = 128 * 1024; - auto bytePtr = &*cont.begin(); - size_t bytesLeft = cont.size(); + const size_t bytesRead = streamIn.read(&buffer[0], buffer.size()); + streamOut.write(&buffer[0], bytesRead); - while (bytesLeft > blockSize) - { - fileOut.write(bytePtr, blockSize); //throw FileError - bytePtr += blockSize; - bytesLeft -= blockSize; - if (onUpdateStatus) onUpdateStatus(blockSize); - } + if (onNotifyCopyStatus) + onNotifyCopyStatus(bytesRead); //throw X! - fileOut.write(bytePtr, bytesLeft); //throw FileError - if (onUpdateStatus) onUpdateStatus(bytesLeft); + if (bytesRead != buffer.size()) //end of file + break; } } template inline -BinContainer loadBinStream(const Zstring& filepath, //throw FileError - const std::function& onUpdateStatus) //optional +void saveBinStream(const Zstring& filepath, //throw FileError + const BinContainer& cont, + const std::function& onUpdateStatus) //optional { - static_assert(sizeof(typename BinContainer::value_type) == 1, ""); //expect: bytes (until further) - - FileInput fileIn(filepath); //throw FileError - - BinContainer contOut; - const size_t blockSize = 128 * 1024; - do - { - contOut.resize(contOut.size() + blockSize); //container better implement exponential growth! - - const size_t bytesRead = fileIn.read(&*contOut.begin() + contOut.size() - blockSize, blockSize); //throw FileError - if (bytesRead < blockSize) - contOut.resize(contOut.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics + MemoryStreamIn streamIn(cont); + FileOutput streamOut(filepath, zen::FileOutput::ACC_OVERWRITE); //throw FileError, (ErrorTargetExisting) + if (onUpdateStatus) onUpdateStatus(0); //throw X! + copyStream(streamIn, streamOut, streamOut.optimalBlockSize(), onUpdateStatus); //throw FileError +} - if (onUpdateStatus) onUpdateStatus(bytesRead); - } - while (!fileIn.eof()); - return contOut; +template inline +BinContainer loadBinStream(const Zstring& filepath, //throw FileError + const std::function& onUpdateStatus) //optional +{ + FileInput streamIn(filepath); //throw FileError, ErrorFileLocked + if (onUpdateStatus) onUpdateStatus(0); //throw X! + MemoryStreamOut streamOut; + copyStream(streamIn, streamOut, streamIn.optimalBlockSize(), onUpdateStatus); //throw FileError + return streamOut.ref(); } template inline void writeArray(BinOutputStream& stream, const void* data, size_t len) { - std::copy(static_cast(data), - static_cast(data) + len, - static_cast< char*>(stream.requestWrite(len))); + stream.write(data, len); } @@ -219,9 +212,9 @@ void writeContainer(BinOutputStream& stream, const C& cont) //don't even conside template inline void readArray(BinInputStream& stream, void* data, size_t len) //throw UnexpectedEndOfStreamError { - //expect external write of len bytes: - const char* const src = static_cast(stream.requestRead(len)); //throw UnexpectedEndOfStreamError - std::copy(src, src + len, static_cast(data)); + const size_t bytesRead = stream.read(data, len); + if (bytesRead < len) + throw UnexpectedEndOfStreamError(); } diff --git a/zen/string_base.h b/zen/string_base.h index b1d7102e..be3b532e 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -287,8 +287,8 @@ template class SP, class AP> inline Zbase class SP, class AP> inline Zbase operator+(const Zbase& lhs, Char rhs) { return Zbase(lhs) += rhs; } //don't use unified first argument but save one move-construction in the r-value case instead! -template class SP, class AP> inline Zbase operator+(Zbase&& lhs, const Zbase& rhs) { return std::move(lhs += rhs); } //is the move really needed? -template class SP, class AP> inline Zbase operator+(Zbase&& lhs, const Char* rhs) { return std::move(lhs += rhs); } //lhs, is an l-vlaue in the function body... +template class SP, class AP> inline Zbase operator+(Zbase&& lhs, const Zbase& rhs) { return std::move(lhs += rhs); } //the move *is* needed!!! +template class SP, class AP> inline Zbase operator+(Zbase&& lhs, const Char* rhs) { return std::move(lhs += rhs); } //lhs, is an l-value parameter... template class SP, class AP> inline Zbase operator+(Zbase&& lhs, Char rhs) { return std::move(lhs += rhs); } //and not a local variable => no copy elision template class SP, class AP> inline Zbase operator+( Char lhs, const Zbase& rhs) { return Zbase(lhs) += rhs; } diff --git a/zen/string_tools.h b/zen/string_tools.h index 35a07b64..c8591522 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -35,7 +35,8 @@ template S afterFirst (const S& str, const T& term); //return template S beforeFirst(const S& str, const T& term); //returns the whole string if term not found template std::vector split(const S& str, const T& delimiter); -template void trim(S& str, bool fromLeft = true, bool fromRight = true); +template void trim ( S& str, bool fromLeft = true, bool fromRight = true); +template S trimCpy(const S& str, bool fromLeft = true, bool fromRight = true); template void replace ( S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true); template S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true); @@ -331,9 +332,9 @@ void trim(S& str, bool fromLeft, bool fromRight) assert(fromLeft || fromRight); typedef typename GetCharType::Type CharType; //don't use value_type! (wxString, Glib::ustring) - const CharType* const oldBegin = str.c_str(); - const CharType* newBegin = oldBegin; - const CharType* newEnd = oldBegin + str.length(); + const CharType* const oldBegin = strBegin(str); + const CharType* newBegin = oldBegin; + const CharType* newEnd = oldBegin + strLength(str); if (fromRight) while (newBegin != newEnd && isWhiteSpace(newEnd[-1])) @@ -350,6 +351,16 @@ void trim(S& str, bool fromLeft, bool fromRight) } +template inline +S trimCpy(const S& str, bool fromLeft, bool fromRight) +{ + //implementing trimCpy() in terms of trim(), instead of the other way round, avoids memory allocations when trimming from right! + S tmp = str; + trim(tmp, fromLeft, fromRight); + return tmp; +} + + namespace implementation { template diff --git a/zen/string_traits.h b/zen/string_traits.h index 8bc55a6a..8c4775f4 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -176,8 +176,8 @@ size_t cStringLength(const C* str) //naive implementation seems somewhat faster } -template inline -const typename GetCharType::Type* strBegin(const S& str, typename EnableIf::isStringClass>::Type* = nullptr) //SFINAE: T must be a "string" +template ::isStringClass>::Type> inline +const typename GetCharType::Type* strBegin(const S& str) //SFINAE: T must be a "string" { return str.c_str(); } @@ -190,8 +190,8 @@ inline const char* strBegin(const StringRef& ref) { return ref.data( inline const wchar_t* strBegin(const StringRef& ref) { return ref.data(); } -template inline -size_t strLength(const S& str, typename EnableIf::isStringClass>::Type* = nullptr) //SFINAE: T must be a "string" +template ::isStringClass>::Type> inline +size_t strLength(const S& str) //SFINAE: T must be a "string" { return str.length(); } diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 9239385a..c895d9a0 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -165,13 +165,13 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError const HANDLE hFile = ::CreateFile(applyLongPathPrefix(linkPath).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, - //needed to open a directory: - FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, - nullptr); //_In_opt_ HANDLE hTemplateFile + 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, + //needed to open a directory: + FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, + nullptr); //_In_opt_ HANDLE hTemplateFile if (hFile == INVALID_HANDLE_VALUE) throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), L"CreateFile", getLastError()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); diff --git a/zen/thread.h b/zen/thread.h index d860abd0..c9b4c76f 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -101,7 +101,7 @@ auto async(Function fun) -> boost::unique_future #endif auto fut = pt.get_future(); boost::thread(std::move(pt)).detach(); //we have to explicitly detach since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! - return std::move(fut); //compiler error without "move", why needed??? + return fut; } diff --git a/zen/time.h b/zen/time.h index 996593c8..9d821a1f 100644 --- a/zen/time.h +++ b/zen/time.h @@ -218,7 +218,7 @@ String formatTime(const String2& format, const TimeComp& comp, UserDefinedFormat std::mktime(&ctc); // unfortunately std::strftime() needs all elements of "struct tm" filled, e.g. tm_wday, tm_yday //note: although std::mktime() explicitly expects "local time", calculating weekday and day of year *should* be time-zone and DST independent - CharType buffer[256] = {}; + CharType buffer[256] = {}; const size_t charsWritten = strftimeWrap(buffer, 256, strBegin(format), &ctc); return String(buffer, charsWritten); } @@ -237,11 +237,11 @@ TimeComp localTime(time_t utc) { struct ::tm lt = {}; - //use thread-safe variants of localtime()! + //use thread-safe variants of localtime()! #ifdef ZEN_WIN if (::localtime_s(<, &utc) != 0) #else - if (::localtime_r(&utc, <) == nullptr) + if (::localtime_r(&utc, <) == nullptr) #endif return TimeComp(); diff --git a/zen/type_traits.h b/zen/type_traits.h index 082baea9..e03085d9 100644 --- a/zen/type_traits.h +++ b/zen/type_traits.h @@ -38,10 +38,10 @@ struct ResultType //Herb Sutter's signedness conversion helpers: http://herbsutter.com/2013/06/13/gotw-93-solution-auto-variables-part-2/ template inline -typename std::make_signed::type makeSigned(T t) { return typename std::make_signed::type(t); } +typename std::make_signed::type makeSigned(T t) { return static_cast::type>(t); } template inline -typename std::make_unsigned::type makeUnsigned(T t) { return typename std::make_unsigned::type(t); } +typename std::make_unsigned::type makeUnsigned(T t) { return static_cast::type>(t); } //################# Built-in Types ######################## //Example: "IsSignedInt::value" evaluates to "true" diff --git a/zen/win_ver.h b/zen/win_ver.h index 611a27c0..2e8f1ee7 100644 --- a/zen/win_ver.h +++ b/zen/win_ver.h @@ -40,7 +40,7 @@ const OsVersion osVersionWin2000 (5, 0); /* NOTE: there are two basic APIs to check Windows version: (empiric study following) GetVersionEx -> reports version considering compatibility mode (and compatibility setting in app manifest since Windows 8.1) - VerifyVersionInfo -> always reports *real* Windows Version + VerifyVersionInfo -> always reports *real* Windows Version *) Win10 Technical preview caveat: VerifyVersionInfo returns 6.3 unless manifest entry is added!!! */ @@ -118,11 +118,17 @@ bool runningWOW64() //test if process is running under WOW64: http://msdn.micros } +template inline +bool running64BitWindowsImpl() { return true; } + +template <> inline +bool running64BitWindowsImpl() { return runningWOW64(); } + inline bool running64BitWindows() //http://blogs.msdn.com/b/oldnewthing/archive/2005/02/01/364563.aspx { static_assert(zen::is32BitBuild || zen::is64BitBuild, ""); - return is64BitBuild || runningWOW64(); //should we bother to give a compile-time result in the first case? + return running64BitWindowsImpl(); } } diff --git a/zen/xml_io.cpp b/zen/xml_io.cpp index a070b526..a1bf05d0 100644 --- a/zen/xml_io.cpp +++ b/zen/xml_io.cpp @@ -16,36 +16,26 @@ XmlDoc zen::loadXmlDocument(const Zstring& filepath) //throw FileError { //can't simply use zen::loadBinStream() due to the short-circuit xml-validation below! - std::string stream; - - FileInput inputFile(filepath); //throw FileError + FileInput fileStreamIn(filepath); //throw FileError + MemoryStreamOut memStreamOut; { //quick test whether input is an XML: avoid loading large binary files up front! const std::string xmlBegin = " buf(xmlBegin.size() + strLength(BYTE_ORDER_MARK_UTF8)); - const size_t bytesRead = inputFile.read(&stream[0], stream.size()); //throw FileError - stream.resize(bytesRead); + const size_t bytesRead = fileStreamIn.read(&buf[0], buf.size()); + memStreamOut.write(&buf[0], bytesRead); - if (!startsWith(stream, xmlBegin) && - !startsWith(stream, BYTE_ORDER_MARK_UTF8 + xmlBegin)) //allow BOM! + if (!startsWith(memStreamOut.ref(), xmlBegin) && + !startsWith(memStreamOut.ref(), BYTE_ORDER_MARK_UTF8 + xmlBegin)) //allow BOM! throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtFileName(filepath))); } - const size_t blockSize = 128 * 1024; - do - { - stream.resize(stream.size() + blockSize); - - const size_t bytesRead = inputFile.read(&*stream.begin() + stream.size() - blockSize, blockSize); //throw FileError - if (bytesRead < blockSize) - stream.resize(stream.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics - } - while (!inputFile.eof()); + copyStream(fileStreamIn, memStreamOut, fileStreamIn.optimalBlockSize(), nullptr); //throw FileError try { - return parse(stream); //throw XmlParsingError + return parse(memStreamOut.ref()); //throw XmlParsingError } catch (const XmlParsingError& e) { diff --git a/zen/zstring.cpp b/zen/zstring.cpp index f803f160..73ef3ee9 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -17,7 +17,7 @@ #endif #ifndef NDEBUG - #include + #include "thread.h" #include #endif @@ -39,14 +39,14 @@ public: void insert(const void* ptr, size_t size) { - std::lock_guard dummy(lockActStrings); + boost::lock_guard dummy(lockActStrings); if (!activeStrings.emplace(ptr, size).second) reportProblem("Serious Error: New memory points into occupied space: " + rawMemToString(ptr, size)); } void remove(const void* ptr) { - std::lock_guard dummy(lockActStrings); + boost::lock_guard dummy(lockActStrings); if (activeStrings.erase(ptr) != 1) reportProblem("Serious Error: No memory available for deallocation at this location!"); } @@ -92,15 +92,15 @@ private: #else std::cerr << message; #endif - throw std::logic_error("Memory leak! " + message + "\n" + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::logic_error("Memory leak! " + message + "\n" + std::string(__FILE__) + ":" + numberTo(__LINE__)); } - std::mutex lockActStrings; + boost::mutex lockActStrings; std::unordered_map activeStrings; }; //caveat: function scope static initialization is not thread-safe in VS 2010! -auto& dummy = LeakChecker::get(); +auto& dummy = LeakChecker::get(); //still not sufficient if multiple threads access during static init!!! } void z_impl::leakCheckerInsert(const void* ptr, size_t size) { LeakChecker::get().insert(ptr, size); } @@ -143,8 +143,8 @@ const LCID ZSTRING_INVARIANT_LOCALE = zen::winXpOrLater() ? typedef int (WINAPI* CompareStringOrdinalFunc)(LPCWSTR lpString1, int cchCount1, LPCWSTR lpString2, int cchCount2, BOOL bIgnoreCase); const SysDllFun compareStringOrdinal = SysDllFun(L"kernel32.dll", "CompareStringOrdinal"); +//watch for dependencies in global namespace!!! //caveat: function scope static initialization is not thread-safe in VS 2010! -//No global dependencies => no static initialization order problem in global namespace! } @@ -158,7 +158,7 @@ int cmpFileName(const Zstring& lhs, const Zstring& rhs) static_cast(rhs.size()), //__in int cchCount2, true); //__in BOOL bIgnoreCase if (rv <= 0) - throw std::runtime_error("Error comparing strings (CompareStringOrdinal). " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::runtime_error("Error comparing strings (CompareStringOrdinal). " + std::string(__FILE__) + ":" + numberTo(__LINE__)); else return rv - 2; //convert to C-style string compare result } @@ -184,7 +184,7 @@ int cmpFileName(const Zstring& lhs, const Zstring& rhs) static_cast(minSize), //__in int cchSrc, strOut, //__out LPTSTR lpDestStr, static_cast(minSize)) == 0) //__in int cchDest - throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo(__LINE__)); }; auto eval = [&](wchar_t* bufL, wchar_t* bufR) @@ -231,7 +231,7 @@ Zstring makeUpperCopy(const Zstring& str) len, //__in int cchSrc, &*output.begin(), //__out LPTSTR lpDestStr, len) == 0) //__in int cchDest - throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo(__LINE__)); + throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo(__LINE__)); return output; } @@ -240,7 +240,8 @@ Zstring makeUpperCopy(const Zstring& str) #elif defined ZEN_MAC int cmpFileName(const Zstring& lhs, const Zstring& rhs) { - return ::strcasecmp(lhs.c_str(), rhs.c_str()); //locale-dependent! + const int rv = ::strcasecmp(lhs.c_str(), rhs.c_str()); //locale-dependent! + return rv; } diff --git a/zen/zstring.h b/zen/zstring.h index 0610a27f..7dcfbb69 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -68,12 +68,12 @@ typedef zen::Zbase= prefix.size() && + EqualFilePath()(Zstring(str.begin(), str.begin() + prefix.size()), prefix); +} + + +inline +bool pathEndsWith(const Zstring& str, const Zstring& postfix) +{ + return str.size() >= postfix.size() && + EqualFilePath()(Zstring(str.end() - postfix.size(), str.end()), postfix); +} -- cgit