diff options
author | Daniel Wilhelm <daniel@wili.li> | 2015-10-02 14:56:07 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2015-10-02 14:56:07 +0200 |
commit | de73d25e0b27f4bee2de116d19cab32800785d64 (patch) | |
tree | 21de1736d12a92223ad04c02a5b0826d77e5e71c /zen | |
parent | 7.1 (diff) | |
download | FreeFileSync-de73d25e0b27f4bee2de116d19cab32800785d64.tar.gz FreeFileSync-de73d25e0b27f4bee2de116d19cab32800785d64.tar.bz2 FreeFileSync-de73d25e0b27f4bee2de116d19cab32800785d64.zip |
7.2
Diffstat (limited to 'zen')
-rw-r--r-- | zen/async_task.h | 4 | ||||
-rw-r--r-- | zen/basic_math.h | 2 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 157 | ||||
-rw-r--r-- | zen/file_access.cpp | 489 | ||||
-rw-r--r-- | zen/file_access.h | 28 | ||||
-rw-r--r-- | zen/file_error.h | 18 | ||||
-rw-r--r-- | zen/file_io.cpp | 99 | ||||
-rw-r--r-- | zen/file_io.h | 2 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 14 | ||||
-rw-r--r-- | zen/long_path_prefix.h | 2 | ||||
-rw-r--r-- | zen/perf.h | 1 | ||||
-rw-r--r-- | zen/recycler.cpp | 16 | ||||
-rw-r--r-- | zen/recycler.h | 2 | ||||
-rw-r--r-- | zen/scope_guard.h | 2 | ||||
-rw-r--r-- | zen/shell_execute.h | 10 | ||||
-rw-r--r-- | zen/stl_tools.h | 5 | ||||
-rw-r--r-- | zen/string_base.h | 25 | ||||
-rw-r--r-- | zen/string_tools.h | 79 | ||||
-rw-r--r-- | zen/string_traits.h | 41 | ||||
-rw-r--r-- | zen/symlink_target.h | 22 | ||||
-rw-r--r-- | zen/sys_error.h | 3 | ||||
-rw-r--r-- | zen/thread.h | 63 | ||||
-rw-r--r-- | zen/type_tools.h | 3 | ||||
-rw-r--r-- | zen/win.h | 4 | ||||
-rw-r--r-- | zen/xml_io.cpp | 8 | ||||
-rw-r--r-- | zen/zstring.cpp | 16 | ||||
-rw-r--r-- | zen/zstring.h | 42 |
27 files changed, 615 insertions, 542 deletions
diff --git a/zen/async_task.h b/zen/async_task.h index b8d72fbe..5c6f7f6e 100644 --- a/zen/async_task.h +++ b/zen/async_task.h @@ -27,7 +27,7 @@ public: // -> doAsync: the usual thread-safety requirements apply! // -> evalOnGui: no thread-safety concerns, but must only reference variables with greater-equal lifetime than the AsyncTask instance! { - tasks.push_back(zen::async([=]() -> std::function<void()> + tasks.push_back(zen::runAsync([=]() -> std::function<void()> { auto result = doAsync(); return [=]{ evalOnGui(result); }; @@ -37,7 +37,7 @@ public: template <class Fun, class Fun2> void add2(Fun doAsync, Fun2 evalOnGui) //for evalOnGui taking no parameters { - tasks.push_back(zen::async([=]() -> std::function<void()> { doAsync(); return [=]{ evalOnGui(); }; })); + tasks.push_back(zen::runAsync([doAsync, evalOnGui]() -> std::function<void()> { doAsync(); return [evalOnGui]{ evalOnGui(); }; })); } void evalResults() //call from gui thread repreatedly diff --git a/zen/basic_math.h b/zen/basic_math.h index 227d1cd4..14fcae9c 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -92,7 +92,7 @@ const double ln2 = 0.693147180559945309417; template <class T> inline T abs(T value) { - static_assert(std::is_signed<T>::value, ""); + //static_assert(std::is_signed<T>::value, ""); if (value < 0) return -value; //operator "?:" caveat: may be different type than "value" else diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index a97ea80d..e93e2b06 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -156,7 +156,7 @@ public: FILE_FLAG_OVERLAPPED, //_In_ DWORD dwFlagsAndAttributes, nullptr); //_In_opt_ HANDLE hTemplateFile if (hDir == INVALID_HANDLE_VALUE) - throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"CreateFile", getLastError()); + throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(directory)), L"CreateFile", getLastError()); //end of constructor, no need to start managing "hDir" } @@ -176,86 +176,79 @@ public: void operator()() //thread entry { - try - { - std::vector<char> buffer(64 * 1024); //needs to be aligned on a DWORD boundary; maximum buffer size restricted by some networks protocols (according to docu) + std::vector<char> buffer(64 * 1024); //needs to be aligned on a DWORD boundary; maximum buffer size restricted by some networks protocols (according to docu) - for (;;) + for (;;) + { + boost::this_thread::interruption_point(); + + //actual work + OVERLAPPED overlapped = {}; + overlapped.hEvent = ::CreateEvent(nullptr, //__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes, + true, //__in BOOL bManualReset, + false, //__in BOOL bInitialState, + nullptr); //__in_opt LPCTSTR lpName + if (overlapped.hEvent == nullptr) { - boost::this_thread::interruption_point(); - - //actual work - OVERLAPPED overlapped = {}; - overlapped.hEvent = ::CreateEvent(nullptr, //__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes, - true, //__in BOOL bManualReset, - false, //__in BOOL bInitialState, - nullptr); //__in_opt LPCTSTR lpName - if (overlapped.hEvent == nullptr) - { - const DWORD ec = ::GetLastError(); //copy before directly or indirectly making other system calls! - return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirpathPf)), formatSystemError(L"CreateEvent", ec), ec); - } - ZEN_ON_SCOPE_EXIT(::CloseHandle(overlapped.hEvent)); - - DWORD bytesReturned = 0; //should not be needed for async calls, still pass it to help broken drivers - - //asynchronous variant: runs on this thread's APC queue! - if (!::ReadDirectoryChangesW(hDir, // __in HANDLE hDirectory, - &buffer[0], // __out LPVOID lpBuffer, - static_cast<DWORD>(buffer.size()), // __in DWORD nBufferLength, - true, // __in BOOL bWatchSubtree, - FILE_NOTIFY_CHANGE_FILE_NAME | - FILE_NOTIFY_CHANGE_DIR_NAME | - FILE_NOTIFY_CHANGE_SIZE | - FILE_NOTIFY_CHANGE_LAST_WRITE, // __in DWORD dwNotifyFilter, - &bytesReturned, // __out_opt LPDWORD lpBytesReturned, - &overlapped, // __inout_opt LPOVERLAPPED lpOverlapped, - nullptr)) // __in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine - { - const DWORD ec = ::GetLastError(); //copy before directly or indirectly making other system calls! - return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirpathPf)), formatSystemError(L"ReadDirectoryChangesW", ec), ec); - } + const DWORD ec = ::GetLastError(); //copy before directly or indirectly making other system calls! + return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(dirpathPf)), formatSystemError(L"CreateEvent", ec), ec); + } + ZEN_ON_SCOPE_EXIT(::CloseHandle(overlapped.hEvent)); + + DWORD bytesReturned = 0; //should not be needed for async calls, still pass it to help broken drivers + + //asynchronous variant: runs on this thread's APC queue! + if (!::ReadDirectoryChangesW(hDir, // __in HANDLE hDirectory, + &buffer[0], // __out LPVOID lpBuffer, + static_cast<DWORD>(buffer.size()), // __in DWORD nBufferLength, + true, // __in BOOL bWatchSubtree, + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE, // __in DWORD dwNotifyFilter, + &bytesReturned, // __out_opt LPDWORD lpBytesReturned, + &overlapped, // __inout_opt LPOVERLAPPED lpOverlapped, + nullptr)) // __in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine + { + const DWORD ec = ::GetLastError(); //copy before directly or indirectly making other system calls! + return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(dirpathPf)), formatSystemError(L"ReadDirectoryChangesW", ec), ec); + } - //async I/O is a resource that needs to be guarded since it will write to local variable "buffer"! - zen::ScopeGuard guardAio = zen::makeGuard([&] - { - //Canceling Pending I/O Operations: http://msdn.microsoft.com/en-us/library/aa363789(v=vs.85).aspx + //async I/O is a resource that needs to be guarded since it will write to local variable "buffer"! + zen::ScopeGuard guardAio = zen::makeGuard([&] + { + //Canceling Pending I/O Operations: http://msdn.microsoft.com/en-us/library/aa363789(v=vs.85).aspx #ifdef ZEN_WIN_VISTA_AND_LATER - if (::CancelIoEx(hDir, &overlapped) /*!= FALSE*/ || ::GetLastError() != ERROR_NOT_FOUND) + if (::CancelIoEx(hDir, &overlapped) /*!= FALSE*/ || ::GetLastError() != ERROR_NOT_FOUND) #else - if (::CancelIo(hDir) /*!= FALSE*/ || ::GetLastError() != ERROR_NOT_FOUND) + if (::CancelIo(hDir) /*!= FALSE*/ || ::GetLastError() != ERROR_NOT_FOUND) #endif - { - DWORD bytesWritten = 0; - ::GetOverlappedResult(hDir, &overlapped, &bytesWritten, true); //wait until cancellation is complete - } - }); - - //wait for results - DWORD bytesWritten = 0; - while (!::GetOverlappedResult(hDir, //__in HANDLE hFile, - &overlapped, //__in LPOVERLAPPED lpOverlapped, - &bytesWritten, //__out LPDWORD lpNumberOfBytesTransferred, - false)) //__in BOOL bWait { - const DWORD ec = ::GetLastError(); //copy before directly or indirectly making other system calls! - if (ec != ERROR_IO_INCOMPLETE) - return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirpathPf)), formatSystemError(L"GetOverlappedResult", ec), ec); - - //execute asynchronous procedure calls (APC) queued on this thread - ::SleepEx(50, // __in DWORD dwMilliseconds, - true); // __in BOOL bAlertable - - boost::this_thread::interruption_point(); + DWORD bytesWritten = 0; + ::GetOverlappedResult(hDir, &overlapped, &bytesWritten, true); //must wait until cancellation is complete! } - guardAio.dismiss(); + }); + + //wait for results + DWORD bytesWritten = 0; + while (!::GetOverlappedResult(hDir, //__in HANDLE hFile, + &overlapped, //__in LPOVERLAPPED lpOverlapped, + &bytesWritten, //__out LPDWORD lpNumberOfBytesTransferred, + false)) //__in BOOL bWait + { + const DWORD ec = ::GetLastError(); //copy before directly or indirectly making other system calls! + if (ec != ERROR_IO_INCOMPLETE) + return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(dirpathPf)), formatSystemError(L"GetOverlappedResult", ec), ec); + + //execute asynchronous procedure calls (APC) queued on this thread + ::SleepEx(50, // __in DWORD dwMilliseconds, + true); // __in BOOL bAlertable - shared_->addChanges(&buffer[0], bytesWritten, dirpathPf); //throw () + boost::this_thread::interruption_point(); } - } - catch (boost::thread_interrupted&) - { - throw; //this is the only exception expected! + guardAio.dismiss(); + + shared_->addChanges(&buffer[0], bytesWritten, dirpathPf); //throw () } } @@ -362,13 +355,13 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() //wait until device removal is confirmed, to prevent locking hDir again by some new watch! if (pimpl_->volRemoval->requestReceived()) { - const boost::system_time maxwait = boost::get_system_time() + boost::posix_time::seconds(15); + const boost::chrono::steady_clock::time_point endTime = boost::chrono::steady_clock::now() + boost::chrono::seconds(15); //HandleVolumeRemoval::finished() not guaranteed! note: Windows gives unresponsive applications ca. 10 seconds until unmounting the usb stick in worst case - while (!pimpl_->volRemoval->finished() && boost::get_system_time() < maxwait) + while (!pimpl_->volRemoval->finished() && boost::chrono::steady_clock::now() < endTime) { processGuiMessages(); //DBT_DEVICEREMOVECOMPLETE message is sent here! - boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(50)); + boost::this_thread::sleep_for(boost::chrono::milliseconds(50)); //throw boost::thread_interrupted } output.emplace_back(ACTION_DELETE, baseDirPath); //report removal as change to main directory @@ -411,7 +404,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError //init pimpl_->notifDescr = ::inotify_init(); if (pimpl_->notifDescr == -1) - throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"inotify_init", getLastError()); + throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"inotify_init", getLastError()); zen::ScopeGuard guardDescr = zen::makeGuard([&] { ::close(pimpl_->notifDescr); }); @@ -423,7 +416,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1; } if (!initSuccess) - throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"fcntl", getLastError()); + throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"fcntl", getLastError()); //add watches for (const Zstring& subDirPath : fullDirList) @@ -443,10 +436,10 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError { 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(subDirPath)), + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(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(subDirPath)), formatSystemError(L"inotify_add_watch", ec)); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(subDirPath)), formatSystemError(L"inotify_add_watch", ec)); } pimpl_->watchDescrs.emplace(wd, appendSeparator(subDirPath)); @@ -479,7 +472,7 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() if (errno == EAGAIN) //this error is ignored in all inotify wrappers I found return std::vector<Entry>(); - throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"read", getLastError()); + throwFileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"read", getLastError()); } std::vector<Entry> output; @@ -575,7 +568,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : { CFStringRef dirpathCf = osx::createCFString(baseDirPath.c_str()); //returns nullptr on error if (!dirpathCf) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"Function call failed: createCFString"); //no error code documented! + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"Function call failed: createCFString"); //no error code documented! ZEN_ON_SCOPE_EXIT(::CFRelease(dirpathCf)); CFArrayRef dirpathCfArray = ::CFArrayCreate(nullptr, //CFAllocatorRef allocator, @@ -583,7 +576,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : 1, //CFIndex numValues, nullptr); //const CFArrayCallBacks* callBacks if (!dirpathCfArray) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"Function call failed: CFArrayCreate"); //no error code documented! + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"Function call failed: CFArrayCreate"); //no error code documented! ZEN_ON_SCOPE_EXIT(::CFRelease(dirpathCfArray)); FSEventStreamContext context = {}; @@ -609,7 +602,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : zen::ScopeGuard guardRunloop = zen::makeGuard([&] { ::FSEventStreamInvalidate(pimpl_->eventStream); }); if (!::FSEventStreamStart(pimpl_->eventStream)) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(baseDirPath)), L"Function call failed: FSEventStreamStart"); //no error code documented! + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(baseDirPath)), L"Function call failed: FSEventStreamStart"); //no error code documented! guardCreate .dismiss(); guardRunloop.dismiss(); diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 96aac081..09a1eb07 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -45,45 +45,45 @@ using namespace zen; -bool zen::fileExists(const Zstring& filepath) +bool zen::fileExists(const Zstring& filePath) { //symbolic links (broken or not) are also treated as existing files! #ifdef ZEN_WIN - const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(filepath).c_str()); + const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str()); if (attr != INVALID_FILE_ATTRIBUTES) return (attr & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also #elif defined ZEN_LINUX || defined ZEN_MAC struct ::stat fileInfo = {}; - if (::stat(filepath.c_str(), &fileInfo) == 0) //follow symlinks! + if (::stat(filePath.c_str(), &fileInfo) == 0) //follow symlinks! return S_ISREG(fileInfo.st_mode); #endif return false; } -bool zen::dirExists(const Zstring& dirpath) +bool zen::dirExists(const Zstring& dirPath) { //symbolic links (broken or not) are also treated as existing directories! #ifdef ZEN_WIN - const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirpath).c_str()); + const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirPath).c_str()); if (attr != INVALID_FILE_ATTRIBUTES) return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for (dir-)symlinks also #elif defined ZEN_LINUX || defined ZEN_MAC struct ::stat dirInfo = {}; - if (::stat(dirpath.c_str(), &dirInfo) == 0) //follow symlinks! + if (::stat(dirPath.c_str(), &dirInfo) == 0) //follow symlinks! return S_ISDIR(dirInfo.st_mode); #endif return false; } -bool zen::symlinkExists(const Zstring& linkname) +bool zen::symlinkExists(const Zstring& linkPath) { #ifdef ZEN_WIN WIN32_FIND_DATA linkInfo = {}; - const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(linkname).c_str(), &linkInfo); + const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(linkPath).c_str(), &linkInfo); if (searchHandle != INVALID_HANDLE_VALUE) { ::FindClose(searchHandle); @@ -92,17 +92,17 @@ bool zen::symlinkExists(const Zstring& linkname) #elif defined ZEN_LINUX || defined ZEN_MAC struct ::stat linkInfo = {}; - if (::lstat(linkname.c_str(), &linkInfo) == 0) + if (::lstat(linkPath.c_str(), &linkInfo) == 0) return S_ISLNK(linkInfo.st_mode); #endif return false; } -bool zen::somethingExists(const Zstring& objname) +bool zen::somethingExists(const Zstring& itemPath) { #ifdef ZEN_WIN - const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(objname).c_str()); + const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(itemPath).c_str()); if (attr != INVALID_FILE_ATTRIBUTES) return true; const DWORD lastError = ::GetLastError(); @@ -115,7 +115,7 @@ bool zen::somethingExists(const Zstring& objname) lastError != ERROR_BAD_NET_NAME) // { WIN32_FIND_DATA fileInfo = {}; - const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(objname).c_str(), &fileInfo); + const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(itemPath).c_str(), &fileInfo); if (searchHandle != INVALID_HANDLE_VALUE) { ::FindClose(searchHandle); @@ -125,7 +125,7 @@ bool zen::somethingExists(const Zstring& objname) #elif defined ZEN_LINUX || defined ZEN_MAC struct ::stat fileInfo = {}; - if (::lstat(objname.c_str(), &fileInfo) == 0) + if (::lstat(itemPath.c_str(), &fileInfo) == 0) return true; #endif return false; @@ -172,26 +172,26 @@ bool isFatDrive(const Zstring& filePath) //throw() } -std::uint64_t zen::getFilesize(const Zstring& filepath) //throw FileError +std::uint64_t zen::getFilesize(const Zstring& filePath) //throw FileError { #ifdef ZEN_WIN { WIN32_FIND_DATA fileInfo = {}; - const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(filepath).c_str(), &fileInfo); + const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(filePath).c_str(), &fileInfo); if (searchHandle == INVALID_HANDLE_VALUE) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"FindFirstFile", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"FindFirstFile", getLastError()); ::FindClose(searchHandle); if (!isSymlink(fileInfo)) return get64BitUInt(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); } // WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {}; - // if (!::GetFileAttributesEx(applyLongPathPrefix(filepath).c_str(), //__in LPCTSTR lpFileName, + // if (!::GetFileAttributesEx(applyLongPathPrefix(filePath).c_str(), //__in LPCTSTR lpFileName, // GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, // &sourceAttr)) //__out LPVOID lpFileInformation //open handle to target of symbolic link - const HANDLE hFile = ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, + const HANDLE hFile = ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName, 0, //_In_ DWORD dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, @@ -199,20 +199,20 @@ std::uint64_t zen::getFilesize(const Zstring& filepath) //throw FileError FILE_FLAG_BACKUP_SEMANTICS, /*needed to open a directory*/ //_In_ DWORD dwFlagsAndAttributes, nullptr); //_In_opt_ HANDLE hTemplateFile if (hFile == INVALID_HANDLE_VALUE) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"CreateFile", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"CreateFile", getLastError()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); //why not use ::GetFileSizeEx() instead??? BY_HANDLE_FILE_INFORMATION fileInfoHnd = {}; if (!::GetFileInformationByHandle(hFile, &fileInfoHnd)) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"GetFileInformationByHandle", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileInformationByHandle", getLastError()); return get64BitUInt(fileInfoHnd.nFileSizeLow, fileInfoHnd.nFileSizeHigh); #elif defined ZEN_LINUX || defined ZEN_MAC struct ::stat fileInfo = {}; - if (::stat(filepath.c_str(), &fileInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"stat", getLastError()); + if (::stat(filePath.c_str(), &fileInfo) != 0) + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"stat", getLastError()); return fileInfo.st_size; #endif @@ -227,7 +227,7 @@ std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, retu &bytesFree, //__out_opt PULARGE_INTEGER lpFreeBytesAvailable, nullptr, //__out_opt PULARGE_INTEGER lpTotalNumberOfBytes, nullptr)) //__out_opt PULARGE_INTEGER lpTotalNumberOfFreeBytes - throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"GetDiskFreeSpaceEx", getLastError()); + throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(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); @@ -235,47 +235,47 @@ std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, retu #elif defined ZEN_LINUX || defined ZEN_MAC struct ::statfs info = {}; if (::statfs(path.c_str(), &info) != 0) - throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"statfs", getLastError()); + throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(path)), L"statfs", getLastError()); return static_cast<std::uint64_t>(info.f_bsize) * info.f_bavail; #endif } -bool zen::removeFile(const Zstring& filepath) //throw FileError +bool zen::removeFile(const Zstring& filePath) //throw FileError { #ifdef ZEN_WIN const wchar_t functionName[] = L"DeleteFile"; - if (!::DeleteFile(applyLongPathPrefix(filepath).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) + if (::unlink(filePath.c_str()) != 0) #endif { ErrorCode lastError = getLastError(); #ifdef ZEN_WIN if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only { - ::SetFileAttributes(applyLongPathPrefix(filepath).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(applyLongPathPrefix(filepath).c_str())) //now try again... + if (::DeleteFile(applyLongPathPrefix(filePath).c_str())) //now try again... return true; lastError = ::GetLastError(); } #endif - if (!somethingExists(filepath)) //warning: changes global error code!! + if (!somethingExists(filePath)) //warning: changes global error code!! return false; //neither file nor any other object (e.g. broken symlink) with that name existing - caveat: what if "access is denied"!?!??!?!? //begin of "regular" error reporting - const std::wstring errorMsg = replaceCpy(_("Cannot delete file %x."), L"%x", fmtFileName(filepath)); + const std::wstring errorMsg = replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(filePath)); std::wstring errorDescr = formatSystemError(functionName, lastError); #ifdef ZEN_WIN_VISTA_AND_LATER if (lastError == ERROR_SHARING_VIOLATION || //-> enhance error message! lastError == ERROR_LOCK_VIOLATION) { - const std::wstring procList = vista::getLockingProcesses(filepath); //noexcept + const std::wstring procList = vista::getLockingProcesses(filePath); //noexcept if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; } @@ -286,6 +286,91 @@ bool zen::removeFile(const Zstring& filepath) //throw FileError } +void zen::removeDirectorySimple(const Zstring& dirPath) //throw FileError +{ +#ifdef ZEN_WIN + //(try to) normalize file attributes: actually NEEDED for symbolic links also! + ::SetFileAttributes(applyLongPathPrefix(dirPath).c_str(), FILE_ATTRIBUTE_NORMAL); + + const wchar_t functionName[] = L"RemoveDirectory"; + if (!::RemoveDirectory(applyLongPathPrefix(dirPath).c_str())) +#elif defined ZEN_LINUX || defined ZEN_MAC + const wchar_t functionName[] = L"rmdir"; + if (::rmdir(dirPath.c_str()) != 0) +#endif + { + const ErrorCode ec = getLastError(); + + if (!somethingExists(dirPath)) //warning: changes global error code!! + return; + +#if defined ZEN_LINUX || defined ZEN_MAC + if (symlinkExists(dirPath)) + { + if (::unlink(dirPath.c_str()) != 0) + throwFileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), L"unlink", getLastError()); + return; + } +#endif + + throwFileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(dirPath)), functionName, ec); + } + //may spuriously fail with ERROR_DIR_NOT_EMPTY(145) even though all child items have + //successfully been *marked* for deletion, but some application still has a handle open! + //e.g. Open "C:\Test\Dir1\Dir2" (filled with lots of files) in Explorer, then delete "C:\Test\Dir1" via ::RemoveDirectory() => Error 145 + //Sample code: http://us.generation-nt.com/answer/createfile-directory-handles-removing-parent-help-29126332.html +} + + +namespace +{ +void removeDirectoryImpl(const Zstring& dirPath) //throw FileError +{ + assert(dirExists(dirPath)); //[!] no symlinks in this context!!! + //attention: check if dirPath is a symlink! Do NOT traverse into it deleting contained files!!! + + std::vector<Zstring> fileList; + std::vector<Zstring> dirLinkList; + std::vector<Zstring> dirList; + //get all files and directories from current directory (WITHOUT subdirectories!) + traverseFolder(dirPath, + [&](const FileInfo& fi) { fileList.push_back(fi.fullPath); }, + [&](const DirInfo& di) { dirList .push_back(di.fullPath); }, + [&](const SymlinkInfo& si) + { +#ifdef ZEN_WIN + if (dirExists(si.fullPath)) //dir symlink + dirLinkList.push_back(si.fullPath); + else //file symlink, broken symlink +#endif + fileList.push_back(si.fullPath); + }, + [](const std::wstring& errorMsg) { throw FileError(errorMsg); }); + + for (const Zstring& filePath : fileList) + removeFile(filePath); //throw FileError + + for (const Zstring& dirLinkPath : dirLinkList) + removeDirectorySimple(dirLinkPath); //throw FileError + + //delete directories recursively + for (const Zstring& subDirPath : dirList) + removeDirectoryImpl(subDirPath); //throw FileError; call recursively to correctly handle symbolic links + + removeDirectorySimple(dirPath); //throw FileError +} +} + + +void zen::removeDirectoryRecursively(const Zstring& dirPath) //throw FileError +{ + if (symlinkExists(dirPath)) + removeDirectorySimple(dirPath); //throw FileError + else if (somethingExists(dirPath)) + removeDirectoryImpl(dirPath); //throw FileError +} + + namespace { /* Usage overview: (avoid circular pattern!) @@ -334,7 +419,7 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro } } //begin of "regular" error reporting - const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(pathSource)), L"%y", L"\n" + fmtFileName(pathTarget)); + const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathSource)), L"%y", L"\n" + fmtPath(pathTarget)); std::wstring errorDescr = formatSystemError(L"MoveFileEx", lastError); #ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message @@ -360,7 +445,7 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0) { const int lastError = errno; //copy before directly or indirectly making other system calls! - const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(pathSource)), L"%y", L"\n" + fmtFileName(pathTarget)); + const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathSource)), L"%y", L"\n" + fmtPath(pathTarget)); const std::wstring errorDescr = formatSystemError(L"rename", lastError); if (lastError == EXDEV) @@ -379,17 +464,17 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro ::GetShortPathName() ::GetLongPathName() */ template <typename Function> -Zstring getFilenameFmt(const Zstring& filepath, Function fun) //throw(); returns empty string on error +Zstring getFilenameFmt(const Zstring& filePath, Function fun) //throw(); returns empty string on error { - const Zstring filepathFmt = applyLongPathPrefix(filepath); + const Zstring filePathFmt = applyLongPathPrefix(filePath); - const DWORD bufferSize = fun(filepathFmt.c_str(), nullptr, 0); + const DWORD bufferSize = fun(filePathFmt.c_str(), nullptr, 0); if (bufferSize == 0) return Zstring(); std::vector<wchar_t> buffer(bufferSize); - const DWORD charsWritten = fun(filepathFmt.c_str(), //__in LPCTSTR lpszShortPath, + const DWORD charsWritten = fun(filePathFmt.c_str(), //__in LPCTSTR lpszShortPath, &buffer[0], //__out LPTSTR lpszLongPath, bufferSize); //__in DWORD cchBuffer if (charsWritten == 0 || charsWritten >= bufferSize) @@ -399,18 +484,19 @@ Zstring getFilenameFmt(const Zstring& filepath, Function fun) //throw(); returns } -Zstring findUnused8Dot3Name(const Zstring& filepath) //find a unique 8.3 short name +Zstring findUnused8Dot3Name(const Zstring& filePath) //find a unique 8.3 short name { - const Zstring pathPrefix = contains(filepath, FILE_NAME_SEPARATOR) ? - (beforeLast(filepath, FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR) : Zstring(); + const Zstring pathPrefix = contains(filePath, FILE_NAME_SEPARATOR) ? + (beforeLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) + FILE_NAME_SEPARATOR) : Zstring(); - Zstring extension = afterLast(afterLast(filepath, FILE_NAME_SEPARATOR), Zchar('.')); //extension needn't contain reasonable data + //extension needn't contain reasonable data + Zstring extension = getFileExtension(filePath); if (extension.empty()) extension = Zstr("FFS"); else if (extension.length() > 3) extension.resize(3); - for (int index = 0; index < 100000000; ++index) //filepath must be representable by <= 8 characters + for (int index = 0; index < 100000000; ++index) //filePath must be representable by <= 8 characters { const Zstring output = pathPrefix + numberTo<Zstring>(index) + Zchar('.') + extension; if (!somethingExists(output)) //ensure uniqueness @@ -421,24 +507,24 @@ Zstring findUnused8Dot3Name(const Zstring& filepath) //find a unique 8.3 short n } -bool have8dot3NameClash(const Zstring& filepath) +bool have8dot3NameClash(const Zstring& filePath) { - if (!contains(filepath, FILE_NAME_SEPARATOR)) + if (!contains(filePath, FILE_NAME_SEPARATOR)) return false; - if (somethingExists(filepath)) //name OR directory! + if (somethingExists(filePath)) //name OR directory! { - const Zstring origName = afterLast(filepath, FILE_NAME_SEPARATOR); //returns the whole string if ch not found - const Zstring shortName = afterLast(getFilenameFmt(filepath, ::GetShortPathName), FILE_NAME_SEPARATOR); //throw() returns empty string on error - const Zstring longName = afterLast(getFilenameFmt(filepath, ::GetLongPathName ), FILE_NAME_SEPARATOR); // + const Zstring origName = afterLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); + const Zstring shortName = afterLast(getFilenameFmt(filePath, ::GetShortPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error + const Zstring longName = afterLast(getFilenameFmt(filePath, ::GetLongPathName ), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); // if (!shortName.empty() && !longName .empty() && 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" + //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" return true; } } @@ -448,14 +534,17 @@ bool have8dot3NameClash(const Zstring& filepath) class Fix8Dot3NameClash //throw FileError { public: - Fix8Dot3NameClash(const Zstring& filepath) + Fix8Dot3NameClash(const Zstring& filePath) { - const Zstring longName = afterLast(getFilenameFmt(filepath, ::GetLongPathName), FILE_NAME_SEPARATOR); //throw() returns empty string on error + const Zstring longName = afterLast(getFilenameFmt(filePath, ::GetLongPathName), FILE_NAME_SEPARATOR, IF_MISSING_RETURN_ALL); //throw() returns empty string on error - unrelatedFile = beforeLast(filepath, FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + longName; + unrelatedFile = beforeLast(filePath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + if (!unrelatedFile.empty()) + unrelatedFile += FILE_NAME_SEPARATOR; + unrelatedFile += longName; //find another name in short format: this ensures the actual short name WILL be renamed as well! - unrelatedFileParked = findUnused8Dot3Name(filepath); + unrelatedFileParked = findUnused8Dot3Name(filePath); //move already existing short name out of the way for now renameFile_sub(unrelatedFile, unrelatedFileParked); //throw FileError, ErrorDifferentVolume @@ -492,7 +581,7 @@ void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //thr //try to handle issues with already existing short 8.3 file names on Windows if (have8dot3NameClash(pathTarget)) { - Fix8Dot3NameClash dummy(pathTarget); //throw FileError; move clashing filepath to the side + Fix8Dot3NameClash dummy(pathTarget); //throw FileError; move clashing file path to the side //now try again... renameFile_sub(pathSource, pathTarget); //throw FileError return; @@ -505,83 +594,9 @@ void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //thr namespace { -void removeDirectoryImpl(const Zstring& directory, //throw FileError - const std::function<void (const Zstring& filepath)>& onBeforeFileDeletion, - const std::function<void (const Zstring& dirpath )>& onBeforeDirDeletion) -{ - assert(somethingExists(directory)); //[!] #ifdef ZEN_WIN - const Zstring directoryFmt = applyLongPathPrefix(directory); //support for \\?\-prefix - - //(try to) normalize file attributes: actually NEEDED for symbolic links also! - ::SetFileAttributes(directoryFmt.c_str(), FILE_ATTRIBUTE_NORMAL); -#endif - - //attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!! - if (symlinkExists(directory)) //remove symlink directly - { - if (onBeforeDirDeletion) - onBeforeDirDeletion(directory); //once per symlink -#ifdef ZEN_WIN - const wchar_t functionName[] = L"RemoveDirectory"; - if (!::RemoveDirectory(directoryFmt.c_str())) -#elif defined ZEN_LINUX || defined ZEN_MAC - const wchar_t functionName[] = L"unlink"; - if (::unlink(directory.c_str()) != 0) -#endif - throwFileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtFileName(directory)), functionName, getLastError()); - } - else - { - std::vector<Zstring> fileList; - std::vector<Zstring> dirList; - //get all files and directories from current directory (WITHOUT subdirectories!) - traverseFolder(directory, - [&](const FileInfo& fi) { fileList.push_back(fi.fullPath); }, - [&](const DirInfo& di) { dirList .push_back(di.fullPath); }, - [&](const SymlinkInfo& si) - { - if (dirExists(si.fullPath)) //dir symlink - dirList.push_back(si.fullPath); - else //file symlink, broken symlink - fileList.push_back(si.fullPath); - }, - [&](const std::wstring& errorMsg) { throw FileError(errorMsg); }); - - //delete directories recursively - for (const Zstring& dirpath : dirList) - removeDirectoryImpl(dirpath, onBeforeFileDeletion, onBeforeDirDeletion); //throw FileError; call recursively to correctly handle symbolic links - - //delete files - for (const Zstring& filepath : fileList) - { - if (onBeforeFileDeletion) - onBeforeFileDeletion(filepath); //call once per file - removeFile(filepath); //throw FileError - } - - //parent directory is deleted last - if (onBeforeDirDeletion) - onBeforeDirDeletion(directory); //and once per folder -#ifdef ZEN_WIN - const wchar_t functionName[] = L"RemoveDirectory"; - if (!::RemoveDirectory(directoryFmt.c_str())) -#elif defined ZEN_LINUX || defined ZEN_MAC - const wchar_t functionName[] = L"rmdir"; - if (::rmdir(directory.c_str()) != 0) -#endif - throwFileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtFileName(directory)), functionName, getLastError()); - //may spuriously fail with ERROR_DIR_NOT_EMPTY(145) even though all child items have - //successfully been *marked* for deletion, but some application still has a handle open! - //e.g. Open "C:\Test\Dir1\Dir2" (filled with lots of files) in Explorer, then delete "C:\Test\Dir1" via ::RemoveDirectory() => Error 145 - //Sample code: http://us.generation-nt.com/answer/createfile-directory-handles-removing-parent-help-29126332.html - } -} - - -#ifdef ZEN_WIN -void setFileTimeRaw(const Zstring& filepath, +void setFileTimeRaw(const Zstring& filePath, const FILETIME* creationTime, //optional const FILETIME& lastWriteTime, ProcSymlink procSl) //throw FileError @@ -613,21 +628,21 @@ void setFileTimeRaw(const Zstring& filepath, DWORD attribs = INVALID_FILE_ATTRIBUTES; ZEN_ON_SCOPE_EXIT( if (attribs != INVALID_FILE_ATTRIBUTES) - ::SetFileAttributes(applyLongPathPrefix(filepath).c_str(), attribs); + ::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), attribs); ); auto removeReadonly = [&]() -> bool //throw FileError; may need to remove the readonly-attribute (e.g. on FAT usb drives) { if (attribs == INVALID_FILE_ATTRIBUTES) { - const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(filepath).c_str()); + const DWORD tmpAttr = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str()); if (tmpAttr == INVALID_FILE_ATTRIBUTES) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"GetFileAttributes", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"GetFileAttributes", getLastError()); if (tmpAttr & FILE_ATTRIBUTE_READONLY) { - if (!::SetFileAttributes(applyLongPathPrefix(filepath).c_str(), FILE_ATTRIBUTE_NORMAL)) - throwFileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filepath)), L"SetFileAttributes", getLastError()); + if (!::SetFileAttributes(applyLongPathPrefix(filePath).c_str(), FILE_ATTRIBUTE_NORMAL)) + throwFileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(filePath)), L"SetFileAttributes", getLastError()); attribs = tmpAttr; //reapplied on scope exit return true; @@ -638,7 +653,7 @@ void setFileTimeRaw(const Zstring& filepath, auto openFile = [&](bool conservativeApproach) { - return ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, + return ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName, (conservativeApproach ? //some NAS seem to have issues with FILE_WRITE_ATTRIBUTES, even worse, they may fail silently! //http://sourceforge.net/tracker/?func=detail&atid=1093081&aid=3536680&group_id=234430 @@ -677,7 +692,7 @@ void setFileTimeRaw(const Zstring& filepath, continue; //3. after these herculean stunts we give up... - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"CreateFile", lastError); + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"CreateFile", lastError); } } break; @@ -707,7 +722,7 @@ void setFileTimeRaw(const Zstring& filepath, FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, &basicInfo, //__in LPVOID lpFileInformation, sizeof(basicInfo))) //__in DWORD dwBufferSize - throwFileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(filepath)), L"SetFileInformationByHandle", getLastError()); + throwFileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(filePath)), L"SetFileInformationByHandle", getLastError()); }; auto toLargeInteger = [](const FILETIME& ft) -> LARGE_INTEGER @@ -745,32 +760,32 @@ void setFileTimeRaw(const Zstring& filepath, } } - std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)); + std::wstring errorMsg = replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)); //add more meaningful message: FAT accepts only a subset of the NTFS date range if (lastError == ERROR_INVALID_PARAMETER && - isFatDrive(filepath)) + isFatDrive(filePath)) { //we need a low-level reliable routine to format a potentially invalid date => don't use strftime!!! - auto fmtDate = [](const FILETIME& ft) -> Zstring + auto fmtDate = [](const FILETIME& ft) { SYSTEMTIME st = {}; if (!::FileTimeToSystemTime(&ft, //__in const FILETIME *lpFileTime, &st)) //__out LPSYSTEMTIME lpSystemTime - return Zstring(); + return std::wstring(); - Zstring dateTime; + std::wstring dateTime; { const int bufferSize = ::GetDateFormat(LOCALE_USER_DEFAULT, 0, &st, nullptr, nullptr, 0); if (bufferSize > 0) { std::vector<wchar_t> buffer(bufferSize); if (::GetDateFormat(LOCALE_USER_DEFAULT, //_In_ LCID Locale, - 0, //_In_ DWORD dwFlags, - &st, //_In_opt_ const SYSTEMTIME *lpDate, - nullptr, //_In_opt_ LPCTSTR lpFormat, - &buffer[0], //_Out_opt_ LPTSTR lpDateStr, - bufferSize) > 0) //_In_ int cchDate + 0, //_In_ DWORD dwFlags, + &st, //_In_opt_ const SYSTEMTIME *lpDate, + nullptr, //_In_opt_ LPCTSTR lpFormat, + &buffer[0], //_Out_opt_ LPTSTR lpDateStr, + bufferSize) > 0) //_In_ int cchDate dateTime = &buffer[0]; //GetDateFormat() returns char count *including* 0-termination! } } @@ -801,7 +816,7 @@ void setFileTimeRaw(const Zstring& filepath, FILETIME creationTimeDbg = {}; FILETIME lastWriteTimeDbg = {}; - HANDLE hFile = ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, + HANDLE hFile = ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, //_In_ DWORD dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, @@ -854,17 +869,17 @@ void setFileTimeRaw(const Zstring& filePath, const struct ::timespec& modTime, P if (errno == EACCES) //bullshit, access denied even with 0777 permissions! => utimes should work! throw ErrorLinuxFallbackToUtimes(L""); - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filePath)), L"open", getLastError()); + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"open", getLastError()); } ZEN_ON_SCOPE_EXIT(::close(fdFile)); if (::futimens(fdFile, newTimes) != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filePath)), L"futimens", getLastError()); + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"futimens", getLastError()); } else { if (::utimensat(AT_FDCWD, filePath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filePath)), L"utimensat", getLastError()); + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"utimensat", getLastError()); } } @@ -878,7 +893,7 @@ struct AttrBufFileTimes } __attribute__((aligned(4), packed)); -void setFileTimeRaw(const Zstring& filepath, +void setFileTimeRaw(const Zstring& filePath, const struct ::timespec* createTime, //optional const struct ::timespec& writeTime, ProcSymlink procSl) //throw FileError @@ -900,18 +915,18 @@ void setFileTimeRaw(const Zstring& filepath, newTimes.writeTime.tv_sec = writeTime.tv_sec; newTimes.writeTime.tv_nsec = writeTime.tv_nsec; - const int rv = ::setattrlist(filepath.c_str(), //const char* path, + const int rv = ::setattrlist(filePath.c_str(), //const char* path, &attribs, //struct ::attrlist* attrList, createTime ? &newTimes.createTime : &newTimes.writeTime, //void* attrBuf, (createTime ? sizeof(newTimes.createTime) : 0) + sizeof(newTimes.writeTime), //size_t attrBufSize, procSl == ProcSymlink::DIRECT ? FSOPT_NOFOLLOW : 0); //unsigned long options if (rv != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"setattrlist", getLastError()); + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"setattrlist", getLastError()); } /* void getFileTimeRaw(int fd, //throw FileError - const Zstring& filepath, //for error reporting only + const Zstring& filePath, //for error reporting only struct ::timespec& createTime, //out struct ::timespec& writeTime) // { @@ -928,7 +943,7 @@ void getFileTimeRaw(int fd, //throw FileError sizeof(fileTimes), //size_t attrBufSize, 0); //unsigned long options if (rv != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"getattrlist", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath)), L"getattrlist", getLastError()); createTime.tv_sec = fileTimes.createTime.tv_sec; createTime.tv_nsec = fileTimes.createTime.tv_nsec; @@ -940,17 +955,6 @@ void getFileTimeRaw(int fd, //throw FileError } -void zen::removeDirectory(const Zstring& directory, //throw FileError - const std::function<void (const Zstring& filepath)>& onBeforeFileDeletion, - const std::function<void (const Zstring& dirpath)>& onBeforeDirDeletion) -{ - //no error situation if directory is not existing! manual deletion relies on it! - if (!somethingExists(directory)) - return; //neither directory nor any other object (e.g. broken symlink) with that name existing - removeDirectoryImpl(directory, onBeforeFileDeletion, onBeforeDirDeletion); -} - - void zen::setFileTime(const Zstring& filePath, std::int64_t modTime, ProcSymlink procSl) //throw FileError { #ifdef ZEN_WIN @@ -972,12 +976,12 @@ void zen::setFileTime(const Zstring& filePath, std::int64_t modTime, ProcSymlink if (procSl == ProcSymlink::FOLLOW) { if (::utimes(filePath.c_str(), writeTime) != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filePath)), L"utimes", getLastError()); + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"utimes", getLastError()); } else { if (::lutimes(filePath.c_str(), writeTime) != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filePath)), L"lutimes", getLastError()); + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(filePath)), L"lutimes", getLastError()); } } @@ -998,7 +1002,7 @@ bool zen::supportsPermissions(const Zstring& dirpath) //throw FileError 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()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(dirpath)), L"GetVolumePathName", getLastError()); const Zstring volumePath = appendSeparator(&buffer[0]); @@ -1011,7 +1015,7 @@ bool zen::supportsPermissions(const Zstring& dirpath) //throw FileError &fsFlags, //__out_opt LPDWORD lpFileSystemFlags, nullptr, //__out LPTSTR lpFileSystemNameBuffer, 0)) //__in DWORD nFileSystemNameSize - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(dirpath)), L"GetVolumeInformation", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(dirpath)), L"GetVolumeInformation", getLastError()); return (fsFlags & FILE_PERSISTENT_ACLS) != 0; @@ -1037,7 +1041,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli errno == EOPNOTSUPP) //extended attributes are not supported by the filesystem return; - throwFileError(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtFileName(source)), L"getfilecon", getLastError()); + throwFileError(replaceCpy(_("Cannot read security context of %x."), L"%x", fmtPath(source)), L"getfilecon", getLastError()); } ZEN_ON_SCOPE_EXIT(::freecon(contextSource)); @@ -1065,7 +1069,7 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli ::setfilecon(target.c_str(), contextSource) : ::lsetfilecon(target.c_str(), contextSource); if (rv3 < 0) - throwFileError(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtFileName(target)), L"setfilecon", getLastError()); + throwFileError(replaceCpy(_("Cannot write security context of %x."), L"%x", fmtPath(target)), L"setfilecon", getLastError()); } #endif //HAVE_SELINUX @@ -1097,7 +1101,7 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P } catch (const FileError& e)//add some more context description (e.g. user is not an admin) { - throw FileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourceResolved)), e.toString()); + throw FileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourceResolved)), e.toString()); } @@ -1116,7 +1120,7 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P if (bytesNeeded > buffer.size()) buffer.resize(bytesNeeded); else - throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourceResolved)), L"GetFileSecurity", getLastError()); + throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourceResolved)), L"GetFileSecurity", getLastError()); } SECURITY_DESCRIPTOR& secDescr = reinterpret_cast<SECURITY_DESCRIPTOR&>(buffer[0]); @@ -1140,7 +1144,7 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInformation, &secDescr)) //__in PSECURITY_DESCRIPTOR pSecurityDescriptor - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetResolved)), L"SetFileSecurity", getLastError()); + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetResolved)), L"SetFileSecurity", getLastError()); /* PSECURITY_DESCRIPTOR buffer = nullptr; @@ -1232,25 +1236,25 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P if (procSl == ProcSymlink::FOLLOW) { if (::stat(sourcePath.c_str(), &fileInfo) != 0) - throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"stat", getLastError()); + throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"stat", getLastError()); if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chown", getLastError()); + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chown", getLastError()); if (::chmod(targetPath.c_str(), fileInfo.st_mode) != 0) - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chmod", getLastError()); + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod", getLastError()); } else { if (::lstat(sourcePath.c_str(), &fileInfo) != 0) - throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"lstat", getLastError()); + throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"lstat", getLastError()); if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"lchown", getLastError()); + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown", getLastError()); if (!symlinkExists(targetPath) && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod() ::chmod(targetPath.c_str(), fileInfo.st_mode) != 0) - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chmod", getLastError()); + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chmod", getLastError()); } #elif defined ZEN_MAC @@ -1259,7 +1263,7 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P flags |= COPYFILE_NOFOLLOW; if (::copyfile(sourcePath.c_str(), targetPath.c_str(), 0, flags) != 0) - throwFileError(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtFileName(sourcePath)), L"%y", L"\n" + fmtFileName(targetPath)), L"copyfile", getLastError()); + throwFileError(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtPath(sourcePath)), L"%y", L"\n" + fmtPath(targetPath)), L"copyfile", getLastError()); //owner is *not* copied with ::copyfile(): @@ -1267,24 +1271,24 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P if (procSl == ProcSymlink::FOLLOW) { if (::stat(sourcePath.c_str(), &fileInfo) != 0) - throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"stat", getLastError()); + throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"stat", getLastError()); if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chown", getLastError()); + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"chown", getLastError()); } else { if (::lstat(sourcePath.c_str(), &fileInfo) != 0) - throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"lstat", getLastError()); + throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtPath(sourcePath)), L"lstat", getLastError()); if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"lchown", getLastError()); + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(targetPath)), L"lchown", getLastError()); } #endif } -void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTargetExisting +void makeDirectoryRecursivelyImpl(const Zstring& directory) //FileError { assert(!endsWith(directory, FILE_NAME_SEPARATOR)); //even "C:\" should be "C:" as input! @@ -1292,18 +1296,15 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget { copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing } + catch (const ErrorTargetExisting&) {} //*something* existing: folder or FILE! catch (const ErrorTargetPathMissing&) { //we need to create parent directories first - const Zstring dirParent = beforeLast(directory, FILE_NAME_SEPARATOR); + const Zstring dirParent = beforeLast(directory, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); if (!dirParent.empty()) { //recurse... - try - { - makeDirectoryRecursively(dirParent); //throw FileError, (ErrorTargetExisting) - } - catch (const ErrorTargetExisting&) {} //parent directory created externally in the meantime? => NOT AN ERROR; not a directory? fail in next step! + makeDirectoryRecursivelyImpl(dirParent); //throw FileError //now try again... copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing) @@ -1315,32 +1316,24 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget } -void zen::makeNewDirectory(const Zstring& directory) //throw FileError, ErrorTargetExisting +void zen::makeDirectoryRecursively(const Zstring& dirpath) //throw FileError { //remove trailing separator (even for C:\ root directories) - const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ? - beforeLast(directory, FILE_NAME_SEPARATOR) : - directory; - - try - { - makeDirectoryRecursively(dirFormatted); //FileError, ErrorTargetExisting - } - catch (const ErrorTargetExisting&) //*something* existing: folder or FILE! - { - //avoid any file system race-condition by *not* checking existence again here!!! - throw; - } + const Zstring dirFormatted = endsWith(dirpath, FILE_NAME_SEPARATOR) ? + beforeLast(dirpath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) : + dirpath; + makeDirectoryRecursivelyImpl(dirFormatted); //FileError } +//source path is optional (may be empty) 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(targetPath, FILE_NAME_SEPARATOR) ? - beforeLast(targetPath, FILE_NAME_SEPARATOR) : + beforeLast(targetPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE) : targetPath); if (dirTmp.size() == 2 && isAlpha(dirTmp[0]) && dirTmp[1] == L':') @@ -1349,7 +1342,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, const ErrorCode lastError = somethingExists(dirTmp) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; //don't use dirExists() => harmonize with ErrorTargetExisting! - const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(dirTmp)); + const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(dirTmp)); const std::wstring errorDescr = formatSystemError(L"CreateDirectory", lastError); if (lastError == ERROR_ALREADY_EXISTS) @@ -1381,7 +1374,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, if (lastError != ERROR_SUCCESS) { - const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(targetPath)); + const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(targetPath)); const std::wstring errorDescr = formatSystemError(L"CreateDirectory", lastError); if (lastError == ERROR_ALREADY_EXISTS) @@ -1393,7 +1386,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, } #elif defined ZEN_LINUX || defined ZEN_MAC - mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //= default for newly created directory + mode_t mode = S_IRWXU | S_IRWXG | S_IRWXO; //0777, default for newly created directories struct ::stat dirInfo = {}; if (!sourcePath.empty()) @@ -1407,7 +1400,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, 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(targetPath)); + const std::wstring errorMsg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(targetPath)); const std::wstring errorDescr = formatSystemError(L"mkdir", lastError); if (lastError == EEXIST) @@ -1487,7 +1480,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, /*int rv =*/ ::copyfile(sourcePath.c_str(), targetPath.c_str(), 0, COPYFILE_XATTR); #endif - zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectory(targetPath); } catch (FileError&) {} }); //ensure cleanup: + zen::ScopeGuard guardNewDir = zen::makeGuard([&] { try { removeDirectorySimple(targetPath); } catch (FileError&) {} }); //ensure cleanup: //enforce copying file permissions: it's advertized on GUI... if (copyFilePermissions) @@ -1513,7 +1506,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool const SysDllFun<CreateSymbolicLinkFunc> createSymbolicLink(L"kernel32.dll", "CreateSymbolicLinkW"); if (!createSymbolicLink) - throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)), + throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), replaceCpy(_("Cannot find system function %x."), L"%x", L"\"CreateSymbolicLinkW\"")); const wchar_t functionName[] = L"CreateSymbolicLinkW"; @@ -1524,7 +1517,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool const wchar_t functionName[] = L"symlink"; if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0) #endif - throwFileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)), functionName, getLastError()); + throwFileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), functionName, getLastError()); //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist! zen::ScopeGuard guardNewLink = zen::makeGuard([&] @@ -1533,7 +1526,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool { #ifdef ZEN_WIN if (isDirLink) - removeDirectory(targetLink); //throw FileError + removeDirectorySimple(targetLink); //throw FileError else #endif removeFile(targetLink); //throw FileError @@ -1547,24 +1540,24 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool if (!::GetFileAttributesEx(applyLongPathPrefix(sourceLink).c_str(), //__in LPCTSTR lpFileName, GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, &sourceAttr)) //__out LPVOID lpFileInformation - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceLink)), L"GetFileAttributesEx", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"GetFileAttributesEx", getLastError()); setFileTimeRaw(targetLink, &sourceAttr.ftCreationTime, sourceAttr.ftLastWriteTime, ProcSymlink::DIRECT); //throw FileError #elif defined ZEN_LINUX struct ::stat sourceInfo = {}; if (::lstat(sourceLink.c_str(), &sourceInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceLink)), L"lstat", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"lstat", getLastError()); setFileTime(targetLink, sourceInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError #elif defined ZEN_MAC struct ::stat sourceInfo = {}; if (::lstat(sourceLink.c_str(), &sourceInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceLink)), L"lstat", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceLink)), L"lstat", getLastError()); if (::copyfile(sourceLink.c_str(), targetLink.c_str(), 0, COPYFILE_XATTR | COPYFILE_NOFOLLOW) != 0) - throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)), L"copyfile", getLastError()); + throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtPath(sourceLink)), L"%y", L"\n" + fmtPath(targetLink)), L"copyfile", getLastError()); setFileTimeRaw(targetLink, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::DIRECT); //throw FileError #endif @@ -1731,7 +1724,7 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw { const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)); + const std::wstring errorMsg = replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)); std::wstring errorDescr = formatSystemError(L"CreateFile", lastError); //if file is locked throw "ErrorFileLocked" instead! @@ -1753,12 +1746,12 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw //---------------------------------------------------------------------- BY_HANDLE_FILE_INFORMATION fileInfoSource = {}; if (!::GetFileInformationByHandle(hFileSource, &fileInfoSource)) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"GetFileInformationByHandle", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"GetFileInformationByHandle", getLastError()); //encrypted files cannot be read with BackupRead which would fail silently! const bool sourceIsEncrypted = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; if (sourceIsEncrypted) - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"BackupRead: Source file is encrypted."); + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: Source file is encrypted."); //---------------------------------------------------------------------- const DWORD validAttribs = FILE_ATTRIBUTE_NORMAL | //"This attribute is valid only if used alone." @@ -1785,7 +1778,7 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw if (hFileTarget == INVALID_HANDLE_VALUE) { const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)); + const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)); const std::wstring errorDescr = formatSystemError(L"CreateFile", lastError); if (lastError == ERROR_FILE_EXISTS || //confirmed to be used @@ -1802,7 +1795,7 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw //---------------------------------------------------------------------- BY_HANDLE_FILE_INFORMATION fileInfoTarget = {}; if (!::GetFileInformationByHandle(hFileTarget, &fileInfoTarget)) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"GetFileInformationByHandle", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"GetFileInformationByHandle", getLastError()); //return up-to-date file attributes InSyncAttributes newAttrib = {}; @@ -1853,7 +1846,7 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw 0, //_In_ DWORD nOutBufferSize, &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned, nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped - throwFileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtFileName(targetFile)), L"DeviceIoControl, FSCTL_SET_SPARSE", getLastError()); + throwFileError(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(targetFile)), L"DeviceIoControl, FSCTL_SET_SPARSE", getLastError()); } //---------------------------------------------------------------------- @@ -1880,10 +1873,10 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw false, //__in BOOL bAbort, false, //__in BOOL bProcessSecurity, &contextRead)) //__out LPVOID *lpContext - throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"BackupRead", getLastError()); //better use fine-granular error messages "reading/writing"! + throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(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"BackupRead: buffer overflow."); //user should never see this + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: buffer overflow."); //user should never see this if (bytesRead < BUFFER_SIZE) eof = true; @@ -1896,10 +1889,10 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw false, //__in BOOL bAbort, false, //__in BOOL bProcessSecurity, &contextWrite)) //__out LPVOID *lpContext - throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)), L"BackupWrite", getLastError()); + throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite", getLastError()); if (bytesWritten != bytesRead) - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)), L"BackupWrite: incomplete write."); //user should never see this + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite: incomplete write."); //user should never see this //total bytes transferred may be larger than file size! context information + ADS or smaller (sparse, compressed)! if (onUpdateCopyStatus) onUpdateCopyStatus(bytesRead); //throw X! @@ -1912,14 +1905,14 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw //::BackupRead() silently fails reading encrypted files -> double check! if (!someBytesRead && 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"BackupRead: unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()" + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()" //time needs to be set at the end: BackupWrite() changes modification time if (!::SetFileTime(hFileTarget, &fileInfoSource.ftCreationTime, nullptr, &fileInfoSource.ftLastWriteTime)) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)), L"SetFileTime", getLastError()); + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(targetFile)), L"SetFileTime", getLastError()); guardTarget.dismiss(); return newAttrib; @@ -1992,10 +1985,10 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, { //#################### return source file attributes ################################ if (!::GetFileInformationByHandle(hSourceFile, &cbd.fileInfoSrc)) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.sourceFile_)), L"GetFileInformationByHandle", ::GetLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(cbd.sourceFile_)), L"GetFileInformationByHandle", ::GetLastError()); if (!::GetFileInformationByHandle(hDestinationFile, &cbd.fileInfoTrg)) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.targetFile_)), L"GetFileInformationByHandle", ::GetLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(cbd.targetFile_)), L"GetFileInformationByHandle", ::GetLastError()); //#################### switch to sparse file copy if req. ####################### #ifdef ZEN_WIN_VISTA_AND_LATER @@ -2046,7 +2039,9 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, return PROGRESS_CONTINUE; } - +#if defined _MSC_VER && _MSC_VER > 1800 + #error get rid! +#endif const bool supportNonEncryptedDestination = winXpOrLater(); //encrypted destination is not supported with Windows 2000 //caveat: function scope static initialization is not thread-safe in VS 2010! -> still not sufficient if multiple threads access during static init!!! @@ -2109,7 +2104,7 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE throw ErrorFallbackToCopyAsBackupStream(L"bogus file not found"); //assemble error message... - const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); + const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtPath(sourceFile)), L"%y", L"\n" + fmtPath(targetFile)); std::wstring errorDescr = formatSystemError(L"CopyFileEx", lastError); //if file is locked throw "ErrorFileLocked" instead! @@ -2121,7 +2116,7 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; #endif - throw ErrorFileLocked(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), errorDescr); + throw ErrorFileLocked(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), errorDescr); } //if target is existing this functions is expected to throw ErrorTargetExisting!!! @@ -2143,7 +2138,7 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE 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. + //note: ERROR_INVALID_PARAMETER can also occur when copying to a SharePoint server or MS SkyDrive and the target file path is of a restricted type. } catch (FileError&) {} @@ -2195,8 +2190,8 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //try to handle issues with already existing short 8.3 file names on Windows if (have8dot3NameClash(targetFile)) { - Fix8Dot3NameClash dummy(targetFile); //throw FileError; move clashing filepath to the side - return copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError; the short filepath name clash is solved, this should work now + Fix8Dot3NameClash dummy(targetFile); //throw FileError; move clashing file path to the side + return copyFileWindowsSelectRoutine(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError; the short file path name clash is solved, this should work now } throw; } @@ -2212,7 +2207,7 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError struct ::stat sourceInfo = {}; if (::fstat(fileIn.getHandle(), &sourceInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"fstat", getLastError()); const int fdTarget = ::open(targetFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, sourceInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); //analog to "cp" which copies "mode" (considering umask) by default @@ -2220,7 +2215,7 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError if (fdTarget == -1) { const int ec = errno; //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)); + const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)); const std::wstring errorDescr = formatSystemError(L"open", ec); if (ec == EEXIST) @@ -2243,7 +2238,7 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError struct ::stat targetInfo = {}; if (::fstat(fileOut.getHandle(), &targetInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"fstat", getLastError()); newAttrib.fileSize = sourceInfo.st_size; #ifdef ZEN_MAC @@ -2261,7 +2256,7 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError //docs: http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html //source: http://www.opensource.apple.com/source/copyfile/copyfile-103.92.1/copyfile.c 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()); + throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtPath(sourceFile)), L"%y", L"\n" + fmtPath(targetFile)), L"copyfile", getLastError()); #endif fileOut.close(); //throw FileError -> optional, but good place to catch errors when closing stream! diff --git a/zen/file_access.h b/zen/file_access.h index 0dfb650e..4b1c31dd 100644 --- a/zen/file_access.h +++ b/zen/file_access.h @@ -14,10 +14,10 @@ namespace zen { -bool fileExists (const Zstring& filepath); //noexcept; check whether file or file-symlink exists -bool dirExists (const Zstring& dirpath ); //noexcept; check whether directory or dir-symlink exists -bool symlinkExists (const Zstring& linkname); //noexcept; check whether a symbolic link exists -bool somethingExists(const Zstring& objname ); //noexcept; check whether any object with this name exists +bool fileExists (const Zstring& filePath); //noexcept; check whether file or file-symlink exists +bool dirExists (const Zstring& dirPath ); //noexcept; check whether directory or dir-symlink exists +bool symlinkExists (const Zstring& linkPath); //noexcept; check whether a symbolic link exists +bool somethingExists(const Zstring& itemPath); //noexcept; check whether any object with this name exists enum class ProcSymlink { @@ -25,27 +25,29 @@ enum class ProcSymlink FOLLOW }; -void setFileTime(const Zstring& filepath, std::int64_t modificationTime, ProcSymlink procSl); //throw FileError +void setFileTime(const Zstring& filePath, std::int64_t modificationTime, ProcSymlink procSl); //throw FileError //symlink handling: always evaluate target -std::uint64_t getFilesize(const Zstring& filepath); //throw FileError +std::uint64_t getFilesize(const Zstring& filePath); //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 - const std::function<void (const Zstring& filepath)>& onBeforeFileDeletion = nullptr, //optional; - const std::function<void (const Zstring& dirpath )>& onBeforeDirDeletion = nullptr); //one call for each *existing* object! +bool removeFile(const Zstring& filePath); //throw FileError; return "false" if file is not existing + +void removeDirectorySimple(const Zstring& dirPath); //throw FileError + +void removeDirectoryRecursively(const Zstring& dirPath); //throw FileError //rename file or directory: no copying!!! 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 makeNewDirectory(const Zstring& directory); //throw FileError, ErrorTargetExisting +//- no error if already existing +//- create recursively if parent directory is not existing +void makeDirectoryRecursively(const Zstring& dirpath); //throw FileError //fail if already existing or parent directory not existing: -//directory should not end with path separator +//source path is optional (may be empty) 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 diff --git a/zen/file_error.h b/zen/file_error.h index 4ba05107..d8c6224c 100644 --- a/zen/file_error.h +++ b/zen/file_error.h @@ -47,22 +47,14 @@ void throwFileError(const std::wstring& msg, const std::wstring& functionName, c //----------- facilitate usage of std::wstring for error messages -------------------- -//allow implicit UTF8 conversion: since std::wstring models a GUI string, convenience is more important than performance inline -std::wstring operator+(const std::wstring& lhs, const Zstring& rhs) { return std::wstring(lhs) += utfCvrtTo<std::wstring>(rhs); } - -//we musn't put our overloads in namespace std, but namespace zen (+ using directive) is sufficient - - -inline -std::wstring fmtFileName(const Zstring& filepath) +std::wstring fmtPath(const std::wstring& displayPath) { - std::wstring output; - output += L'\"'; - output += utfCvrtTo<std::wstring>(filepath); - output += L'\"'; - return output; + return L'\"' + displayPath + L'\"'; } + +inline std::wstring fmtPath(const Zstring& displayPath) { return fmtPath(utfCvrtTo<std::wstring>(displayPath)); } +inline std::wstring fmtPath(const wchar_t* displayPath) { return fmtPath(std::wstring(displayPath)); } } #endif //FILEERROR_H_INCLUDED_839567308565656789 diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 5d7fa8c5..1ea8b1b1 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -9,7 +9,7 @@ #ifdef ZEN_WIN #include "long_path_prefix.h" -#include "privilege.h" + #include "privilege.h" #ifdef ZEN_WIN_VISTA_AND_LATER #include "vista_file_op.h" #endif @@ -49,17 +49,28 @@ void checkForUnsupportedType(const Zstring& filepath) //throw FileError const std::wstring numFmt = printNumber<std::wstring>(L"0%06o", m & S_IFMT); return name ? numFmt + L", " + name : numFmt; }; - throw FileError(replaceCpy(_("Type of item %x is not supported:"), L"%x", fmtFileName(filepath)) + L" " + getTypeName(fileInfo.st_mode)); + throw FileError(replaceCpy(_("Type of item %x is not supported:"), L"%x", fmtPath(filepath)) + L" " + getTypeName(fileInfo.st_mode)); } } #endif + +inline +FileHandle getInvalidHandle() +{ +#ifdef ZEN_WIN + return INVALID_HANDLE_VALUE; +#elif defined ZEN_LINUX || defined ZEN_MAC + return -1; +#endif +} } FileInput::FileInput(FileHandle handle, const Zstring& filepath) : FileBase(filepath), fileHandle(handle) {} -FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileError, ErrorFileLocked +FileInput::FileInput(const Zstring& filepath) : //throw FileError, ErrorFileLocked + FileBase(filepath), fileHandle(getInvalidHandle()) { #ifdef ZEN_WIN try { activatePrivilege(SE_BACKUP_NAME); } @@ -97,7 +108,7 @@ FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileE for FFS most comparisons are probably between different disks => let's use FILE_FLAG_SEQUENTIAL_SCAN */ - | FILE_FLAG_BACKUP_SEMANTICS, + | FILE_FLAG_BACKUP_SEMANTICS, nullptr); //_In_opt_ HANDLE hTemplateFile }; fileHandle = createHandle(FILE_SHARE_READ | FILE_SHARE_DELETE); @@ -111,7 +122,7 @@ FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileE if (fileHandle == INVALID_HANDLE_VALUE) { const DWORD ec = ::GetLastError(); //copy before directly or indirectly making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filepath)); + const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filepath)); std::wstring errorDescr = formatSystemError(L"CreateFile", ec); if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message! @@ -134,27 +145,40 @@ FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileE //don't use O_DIRECT: http://yarchive.net/comp/linux/o_direct.html fileHandle = ::open(filepath.c_str(), O_RDONLY); if (fileHandle == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle - throwFileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filepath)), L"open", getLastError()); + throwFileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filepath)), L"open", getLastError()); +#endif + //------------------------------------------------------------------------------------------------------ -#ifdef ZEN_LINUX + ScopeGuard constructorGuard = zen::makeGuard([&] //destructor call would lead to member double clean-up!!! + { +#ifdef ZEN_WIN + ::CloseHandle(fileHandle); +#elif defined ZEN_LINUX || defined ZEN_MAC + ::close(fileHandle); +#endif + }); + +#ifdef ZEN_LINUX //handle still un-owned => need constructor guard //optimize read-ahead on input file: if (::posix_fadvise(fileHandle, 0, 0, POSIX_FADV_SEQUENTIAL) != 0) - throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filepath)), L"posix_fadvise", getLastError()); + throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filepath)), L"posix_fadvise", getLastError()); #elif defined ZEN_MAC //"dtruss" doesn't show use of "fcntl() F_RDAHEAD/F_RDADVISE" for "cp") #endif -#endif + + constructorGuard.dismiss(); } FileInput::~FileInput() { + if (fileHandle != getInvalidHandle()) #ifdef ZEN_WIN - ::CloseHandle(fileHandle); + ::CloseHandle(fileHandle); #elif defined ZEN_LINUX || defined ZEN_MAC - ::close(fileHandle); + ::close(fileHandle); #endif } @@ -173,7 +197,7 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError; retu static_cast<DWORD>(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()); + throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"ReadFile", getLastError()); #elif defined ZEN_LINUX || defined ZEN_MAC ssize_t bytesRead = 0; @@ -184,12 +208,13 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError; retu 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(getFilePath())), L"read", getLastError()); + throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"read", getLastError()); #endif if (bytesRead == 0) //"zero indicates end of file" return bytesReadTotal; - else if (static_cast<size_t>(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 (static_cast<size_t>(bytesRead) > bytesToRead) //better safe than sorry + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(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<char*>(buffer) + bytesRead; //suppress warning about pointer arithmetics on void* @@ -204,7 +229,8 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError; retu FileOutput::FileOutput(FileHandle handle, const Zstring& filepath) : FileBase(filepath), fileHandle(handle) {} -FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileBase(filepath) //throw FileError, ErrorTargetExisting +FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw FileError, ErrorTargetExisting + FileBase(filepath), fileHandle(getInvalidHandle()) { #ifdef ZEN_WIN try { activatePrivilege(SE_BACKUP_NAME); } @@ -229,7 +255,7 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileBase(fi dwCreationDisposition, //_In_ DWORD dwCreationDisposition, dwFlagsAndAttributes | FILE_FLAG_SEQUENTIAL_SCAN //_In_ DWORD dwFlagsAndAttributes, - | FILE_FLAG_BACKUP_SEMANTICS, + | FILE_FLAG_BACKUP_SEMANTICS, nullptr); //_In_opt_ HANDLE hTemplateFile }; @@ -252,7 +278,7 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileBase(fi //begin of "regular" error reporting if (fileHandle == INVALID_HANDLE_VALUE) { - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filepath)); + const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filepath)); std::wstring errorDescr = formatSystemError(L"CreateFile", ec); #ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message @@ -281,7 +307,7 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileBase(fi if (fileHandle == -1) { const int ec = errno; //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filepath)); + const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filepath)); const std::wstring errorDescr = formatSystemError(L"open", ec); if (ec == EEXIST) @@ -291,23 +317,20 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileBase(fi throw FileError(errorMsg, errorDescr); } #endif -} + //------------------------------------------------------------------------------------------------------ -namespace -{ -inline -FileHandle getInvalidHandle() -{ -#ifdef ZEN_WIN - return INVALID_HANDLE_VALUE; -#elif defined ZEN_LINUX || defined ZEN_MAC - return -1; -#endif -} + //ScopeGuard constructorGuard = zen::makeGuard + + //guard handle when adding code!!! + + //constructorGuard.dismiss(); } +FileOutput::FileOutput(FileOutput&& tmp) : FileBase(tmp.getFilePath()), fileHandle(tmp.fileHandle) { tmp.fileHandle = getInvalidHandle(); } + + FileOutput::~FileOutput() { if (fileHandle != getInvalidHandle()) @@ -322,17 +345,17 @@ FileOutput::~FileOutput() 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."); + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(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 if (!::CloseHandle(fileHandle)) - throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"CloseHandle", getLastError()); + throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"CloseHandle", getLastError()); #elif defined ZEN_LINUX || defined ZEN_MAC if (::close(fileHandle) != 0) - throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"close", getLastError()); + throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"close", getLastError()); #endif } @@ -346,10 +369,10 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToWrite, &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped - throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"WriteFile", getLastError()); + throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"WriteFile", getLastError()); if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"WriteFile: incomplete write."); //user should never see this + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"WriteFile: incomplete write."); //user should never see this #elif defined ZEN_LINUX || defined ZEN_MAC while (bytesToWrite > 0) @@ -366,10 +389,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(getFilePath())), L"write", getLastError()); + throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write", getLastError()); } if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"write: buffer overflow."); //user should never see this + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(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<const char*>(buffer) + bytesWritten; //suppress warning about pointer arithmetics on void* diff --git a/zen/file_io.h b/zen/file_io.h index 648bafe8..52be7f95 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -81,6 +81,8 @@ public: FileHandle getHandle() { return fileHandle; } size_t optimalBlockSize() const { return 128 * 1024; } + FileOutput(FileOutput&& tmp); + private: FileHandle fileHandle; }; diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 66dcd198..3ef94032 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -47,7 +47,7 @@ void zen::traverseFolder(const Zstring& dirPath, if (dirExists(dirPath)) //yes, a race-condition, still the best we can do return; } - throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirPath)), L"FindFirstFile", lastError); + throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(dirPath)), L"FindFirstFile", lastError); } ZEN_ON_SCOPE_EXIT(::FindClose(hDir)); @@ -62,13 +62,13 @@ void zen::traverseFolder(const Zstring& dirPath, if (lastError == ERROR_NO_MORE_FILES) //not an error situation return; //else we have a problem... report it: - throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"FindNextFile", lastError); + throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile", lastError); } //skip "." and ".." const Zchar* const shortName = findData.cFileName; - 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] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"FindNextFile: Data corruption; item is missing a name."); if (shortName[0] == L'.' && (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) continue; @@ -106,14 +106,14 @@ void zen::traverseFolder(const Zstring& dirPath, 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(dirPath)), L"opendir", getLastError()); + throwFileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtPath(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(dirPath)), L"readdir_r", getLastError()); + throwFileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(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 @@ -122,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"readdir_r: Data corruption, found item without name."); + if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtPath(dirPath)), L"readdir_r: Data corruption; item is missing a name."); if (shortName[0] == '.' && (shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0))) continue; @@ -152,7 +152,7 @@ void zen::traverseFolder(const Zstring& dirPath, 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()); + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(itempath)), L"lstat", getLastError()); } catch (const FileError& e) { diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h index dfdf60ba..f7ceb5e7 100644 --- a/zen/long_path_prefix.h +++ b/zen/long_path_prefix.h @@ -70,7 +70,7 @@ Zstring applyLongPathPrefixImpl(const Zstring& path) if (!startsWith(path, LONG_PATH_PREFIX)) { if (startsWith(path, L"\\\\")) //UNC-name, e.g. \\zenju-pc\Users - return LONG_PATH_PREFIX_UNC + afterFirst(path, L'\\'); //convert to \\?\UNC\zenju-pc\Users + return LONG_PATH_PREFIX_UNC + afterFirst(path, L'\\', zen::IF_MISSING_RETURN_NONE); //convert to \\?\UNC\zenju-pc\Users else return LONG_PATH_PREFIX + path; //prepend \\?\ prefix } @@ -9,6 +9,7 @@ #include "deprecate.h" #include "tick_count.h" +#include "scope_guard.h" #ifdef ZEN_WIN #include <sstream> diff --git a/zen/recycler.cpp b/zen/recycler.cpp index a4f6c128..6cd34a17 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -27,7 +27,7 @@ using namespace zen; #ifdef ZEN_WIN -void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::function<void (const Zstring& currentItem)>& onRecycleItem) +void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::function<void (const std::wstring& displayPath)>& onRecycleItem) { if (itempaths.empty()) return; //warning: moving long file paths to recycler does not work! @@ -70,7 +70,7 @@ void zen::recycleOrDelete(const std::vector<Zstring>& 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) { - std::wstring itempathFmt = fmtFileName(itempaths[0]); //probably not the correct file name for file lists larger than 1! + std::wstring itempathFmt = fmtPath(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)); @@ -97,7 +97,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError if (!::g_file_trash(file, nullptr, &error)) { - const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempath)); + const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itempath)); if (!error) throw FileError(errorMsg, L"g_file_trash: unknown error."); //user should never see this @@ -112,7 +112,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError if (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)) removeFile(itempath); //throw FileError else if (S_ISDIR(fileInfo.st_mode)) - removeDirectory(itempath); //throw FileError + removeDirectoryRecursively(itempath); //throw FileError return true; } @@ -128,7 +128,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError auto throwFileError = [&](OSStatus oss) { - const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempath)); + const std::wstring errorMsg = replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtPath(itempath)); std::wstring errorDescr = L"OSStatus Code " + numberTo<std::wstring>(oss); if (const char* description = ::GetMacOSStatusCommentString(oss)) //found no documentation for proper use of GetMacOSStatusCommentString @@ -160,7 +160,7 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError if (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)) removeFile(itempath); //throw FileError else if (S_ISDIR(fileInfo.st_mode)) - removeDirectory(itempath); //throw FileError + removeDirectoryRecursively(itempath); //throw FileError return true; } @@ -179,7 +179,7 @@ bool zen::recycleBinExists(const Zstring& dirpath, const std::function<void ()>& #else //excessive runtime if recycle bin exists, is full and drive is slow: - auto ft = async([dirpath]() + auto ft = runAsync([dirpath]() { SHQUERYRBINFO recInfo = {}; recInfo.cbSize = sizeof(recInfo); @@ -187,7 +187,7 @@ bool zen::recycleBinExists(const Zstring& dirpath, const std::function<void ()>& &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo }); - while (!ft.timed_wait(boost::posix_time::milliseconds(50))) + while (ft.wait_for(boost::chrono::milliseconds(50)) != boost::future_status::ready) if (onUpdateGui) onUpdateGui(); //may throw! diff --git a/zen/recycler.h b/zen/recycler.h index 3f48452e..61a721cd 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -39,7 +39,7 @@ bool recycleOrDelete(const Zstring& itempath); //throw FileError, return "true" bool recycleBinExists(const Zstring& dirpath, const std::function<void ()>& onUpdateGui); //throw FileError void recycleOrDelete(const std::vector<Zstring>& filepaths, //throw FileError, return "true" if file/dir was actually deleted - const std::function<void (const Zstring& currentItem)>& onRecycleItem); //optional; currentItem may be empty + const std::function<void (const std::wstring& displayPath)>& onRecycleItem); //optional; currentItem may be empty #endif } diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 706b20dc..8477c7ee 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -17,7 +17,7 @@ namespace zen { //Scope Guard /* - zen::ScopeGuard lockAio = zen::makeGuard([&] { ::CancelIo(hDir); }); + zen::ScopeGuard lockAio = zen::makeGuard([&] { ::CloseHandle(hDir); }); ... lockAio.dismiss(); */ diff --git a/zen/shell_execute.h b/zen/shell_execute.h index 628e957a..b36bc5ea 100644 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -60,14 +60,14 @@ bool shellExecuteImpl(Function fillExecInfo, ExecutionType type) } -void shellExecute(const void* /*PCIDLIST_ABSOLUTE*/ shellItemPidl, const Zstring& displayPath, ExecutionType type) //throw FileError +void shellExecute(const void* /*PCIDLIST_ABSOLUTE*/ shellItemPidl, const std::wstring& displayPath, ExecutionType type) //throw FileError { if (!shellExecuteImpl([&](SHELLEXECUTEINFO& execInfo) { execInfo.fMask |= SEE_MASK_IDLIST; execInfo.lpIDList = const_cast<void*>(shellItemPidl); //lpIDList is documented as PCIDLIST_ABSOLUTE! }, type)) //throw FileError - throwFileError(_("Incorrect command line:") + L"\n" + fmtFileName(displayPath), L"ShellExecuteEx", ::GetLastError()); + throwFileError(_("Incorrect command line:") + L"\n" + fmtPath(displayPath), L"ShellExecuteEx", ::GetLastError()); } #endif @@ -102,7 +102,7 @@ void shellExecute(const Zstring& command, ExecutionType type) //throw FileError execInfo.lpFile = filepath.c_str(); execInfo.lpParameters = arguments.c_str(); }, type)) - throwFileError(_("Incorrect command line:") + L"\nFile: " + fmtFileName(filepath) + L"\nArg: " + arguments, L"ShellExecuteEx", ::GetLastError()); + throwFileError(_("Incorrect command line:") + L"\nFile: " + fmtPath(filepath) + L"\nArg: " + copyStringTo<std::wstring>(arguments), L"ShellExecuteEx", ::GetLastError()); #elif defined ZEN_LINUX || defined ZEN_MAC /* @@ -117,10 +117,10 @@ void shellExecute(const Zstring& command, ExecutionType type) //throw FileError //Posix::system - execute a shell command int rv = ::system(command.c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect... if (rv == -1 || WEXITSTATUS(rv) == 127) //http://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)" - throw FileError(_("Incorrect command line:") + L"\n" + command); + throw FileError(_("Incorrect command line:") + L"\n" + utfCvrtTo<std::wstring>(command)); } else - async([=] { int rv = ::system(command.c_str()); (void)rv; }); + runAsync([=] { int rv = ::system(command.c_str()); (void)rv; }); #endif } } diff --git a/zen/stl_tools.h b/zen/stl_tools.h index d2d4ee1a..bd76e264 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -9,6 +9,7 @@ #include <memory> #include <algorithm> +#include <zen/type_tools.h> //enhancements for <algorithm> @@ -66,6 +67,7 @@ std::unique_ptr<T> make_unique(Args&& ... args) { return std::unique_ptr<T>(new template <class V, class Predicate> inline void vector_remove_if(V& vec, Predicate p) { + static_assert(IsSameType<typename std::iterator_traits<typename V::iterator>::iterator_category, std::random_access_iterator_tag>::value, "poor man's check for vector"); vec.erase(std::remove_if(vec.begin(), vec.end(), p), vec.end()); } @@ -87,6 +89,9 @@ void set_append(V& s, const W& s2) template <class S, class Predicate> inline void set_remove_if(S& set, Predicate p) { + //function compiles and fails (if we're lucky) not before runtime for std::vector!!! + static_assert(!IsSameType<typename std::iterator_traits<typename S::iterator>::iterator_category, std::random_access_iterator_tag>::value, "poor man's check for non-vector"); + for (auto iter = set.begin(); iter != set.end();) if (p(*iter)) set.erase(iter++); diff --git a/zen/string_base.h b/zen/string_base.h index be3b532e..f4ca5f2e 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -28,8 +28,8 @@ class AllocatorOptimalSpeed //exponential growth + min size { public: //::operator new/ ::operator delete show same performance characterisics like malloc()/free()! - static void* allocate(size_t size) { return ::operator new(size); } //throw std::bad_alloc - static void deallocate(void* ptr) { ::operator delete(ptr); } + static void* allocate(size_t size) { return ::malloc(size); } //throw std::bad_alloc + static void deallocate(void* ptr) { ::free(ptr); } static size_t calcCapacity(size_t length) { return std::max<size_t>(16, std::max(length + length / 2, length)); } //- size_t might overflow! => better catch here than return a too small size covering up the real error: a way too large length! //- any growth rate should not exceed golden ratio: 1.618033989 @@ -39,8 +39,8 @@ public: class AllocatorOptimalMemory //no wasted memory, but more reallocations required when manipulating string { public: - static void* allocate(size_t size) { return ::operator new(size); } //throw std::bad_alloc - static void deallocate(void* ptr) { ::operator delete(ptr); } + static void* allocate(size_t size) { return ::malloc(size); } //throw std::bad_alloc + static void deallocate(void* ptr) { ::free(ptr); } static size_t calcCapacity(size_t length) { return length; } }; @@ -148,9 +148,21 @@ protected: return ptr; } +#ifdef NDEBUG void destroy(Char* ptr) +#else + void destroy(Char*& ptr) +#endif { - if (!ptr) return; //support "destroy(nullptr)" + assert(ptr != reinterpret_cast<Char*>(0x1)); //detect double-deletion + + if (!ptr) //support "destroy(nullptr)" + { +#ifndef NDEBUG + ptr = reinterpret_cast<Char*>(0x1); +#endif + return; + } Descriptor* const d = descr(ptr); @@ -158,6 +170,9 @@ protected: { d->~Descriptor(); this->deallocate(d); +#ifndef NDEBUG + ptr = reinterpret_cast<Char*>(0x1); +#endif } } diff --git a/zen/string_tools.h b/zen/string_tools.h index 03094c96..c04adf96 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -30,10 +30,16 @@ template <class S, class T> bool startsWith(const S& str, const T& prefix); // template <class S, class T> bool endsWith (const S& str, const T& postfix); //both S and T can be strings or char/wchar_t arrays or simple char/wchar_t template <class S, class T> bool contains (const S& str, const T& term); // -template <class S, class T> S afterLast (const S& str, const T& term); //returns the whole string if term not found -template <class S, class T> S beforeLast (const S& str, const T& term); //returns empty string if term not found -template <class S, class T> S afterFirst (const S& str, const T& term); //returns empty string if term not found -template <class S, class T> S beforeFirst(const S& str, const T& term); //returns the whole string if term not found +enum FailureReturnVal +{ + IF_MISSING_RETURN_ALL, + IF_MISSING_RETURN_NONE +}; + +template <class S, class T> S afterLast (const S& str, const T& term, FailureReturnVal rv); +template <class S, class T> S beforeLast (const S& str, const T& term, FailureReturnVal rv); +template <class S, class T> S afterFirst (const S& str, const T& term, FailureReturnVal rv); +template <class S, class T> S beforeFirst(const S& str, const T& term, FailureReturnVal rv); template <class S, class T> std::vector<S> split(const S& str, const T& delimiter); template <class S> void trim ( S& str, bool fromLeft = true, bool fromRight = true); @@ -96,6 +102,7 @@ template <> inline bool isAlpha(wchar_t ch) { return std::iswalpha(ch) != 0; } template <class S, class T> inline bool startsWith(const S& str, const T& prefix) { + static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); const size_t pfLen = strLength(prefix); if (strLength(str) < pfLen) return false; @@ -109,6 +116,7 @@ bool startsWith(const S& str, const T& prefix) template <class S, class T> inline bool endsWith(const S& str, const T& postfix) { + static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); const size_t strLen = strLength(str); const size_t pfLen = strLength(postfix); if (strLen < pfLen) @@ -123,6 +131,7 @@ bool endsWith(const S& str, const T& postfix) template <class S, class T> inline bool contains(const S& str, const T& term) { + static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); const size_t strLen = strLength(str); const size_t termLen = strLength(term); if (strLen < termLen) @@ -137,77 +146,83 @@ bool contains(const S& str, const T& term) } -//returns the whole string if term not found template <class S, class T> inline -S afterLast(const S& str, const T& term) +S afterLast(const S& str, const T& term, FailureReturnVal rv) { + static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); const size_t termLen = strLength(term); const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - const auto* iter = search_last(strFirst, strLast, - termFirst, termFirst + termLen); - if (iter == strLast) - return str; + const auto* it = search_last(strFirst, strLast, + termFirst, termFirst + termLen); + if (it == strLast) + return rv == IF_MISSING_RETURN_ALL ? str : S(); - iter += termLen; - return S(iter, strLast - iter); + it += termLen; + return S(it, strLast - it); } -//returns empty string if term not found template <class S, class T> inline -S beforeLast(const S& str, const T& term) +S beforeLast(const S& str, const T& term, FailureReturnVal rv) { + static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - const auto* iter = search_last(strFirst, strLast, - termFirst, termFirst + strLength(term)); - if (iter == strLast) - return S(); + const auto* it = search_last(strFirst, strLast, + termFirst, termFirst + strLength(term)); + if (it == strLast) + return rv == IF_MISSING_RETURN_ALL ? str : S(); - return S(strFirst, iter - strFirst); + return S(strFirst, it - strFirst); } -//returns empty string if term not found template <class S, class T> inline -S afterFirst(const S& str, const T& term) +S afterFirst(const S& str, const T& term, FailureReturnVal rv) { + static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); const size_t termLen = strLength(term); const auto* const strFirst = strBegin(str); const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - const auto* iter = std::search(strFirst, strLast, - termFirst, termFirst + termLen); - if (iter == strLast) - return S(); - iter += termLen; + const auto* it = std::search(strFirst, strLast, + termFirst, termFirst + termLen); + if (it == strLast) + return rv == IF_MISSING_RETURN_ALL ? str : S(); - return S(iter, strLast - iter); + it += termLen; + return S(it, strLast - it); } -//returns the whole string if term not found template <class S, class T> inline -S beforeFirst(const S& str, const T& term) +S beforeFirst(const S& str, const T& term, FailureReturnVal rv) { + static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); const auto* const strFirst = strBegin(str); + const auto* const strLast = strFirst + strLength(str); const auto* const termFirst = strBegin(term); - return S(strFirst, std::search(strFirst, strFirst + strLength(str), - termFirst, termFirst + strLength(term)) - strFirst); + auto it = std::search(strFirst, strLast, + termFirst, termFirst + strLength(term)); + if (it == strLast) + return rv == IF_MISSING_RETURN_ALL ? str : S(); + + return S(strFirst, it - strFirst); } template <class S, class T> inline std::vector<S> split(const S& str, const T& delimiter) { + static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); std::vector<S> output; const size_t delimLen = strLength(delimiter); @@ -253,6 +268,8 @@ typename EnableIf<!HasMember_append<S>::value>::Type stringAppend(S& str, const template <class S, class T, class U> inline S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll) { + static_assert(IsSameType<typename GetCharType<S>::Type, typename GetCharType<T>::Type>::value, ""); + static_assert(IsSameType<typename GetCharType<T>::Type, typename GetCharType<U>::Type>::value, ""); const size_t oldLen = strLength(oldTerm); if (oldLen == 0) { diff --git a/zen/string_traits.h b/zen/string_traits.h index 12a7f87c..5f91bdc4 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -38,14 +38,15 @@ class StringRef { public: template <class Iterator> - StringRef(Iterator first, Iterator last) : length_(last - first), data_(first != last ? &*first : nullptr) {} + StringRef(Iterator first, Iterator last) : len_(last - first), str_(first != last ? &*first : nullptr) {} + //StringRef(const Char* str, size_t len) : str_(str), len_(len) {} -> needless constraint! Char* not available for empty range! - size_t length() const { return length_; } - const Char* data() const { return data_; } //1. no null-termination! 2. may be nullptr! + const Char* data() const { return str_; } //1. no null-termination! 2. may be nullptr! + size_t length() const { return len_; } private: - size_t length_; - const Char* data_; + size_t len_; + const Char* str_; }; @@ -98,6 +99,10 @@ struct GetCharTypeImpl<S, true> : template <> struct GetCharTypeImpl<char, false> : ResultType<char > {}; template <> struct GetCharTypeImpl<wchar_t, false> : ResultType<wchar_t> {}; +template <> struct GetCharTypeImpl<StringRef<char >, false> : ResultType<char > {}; +template <> struct GetCharTypeImpl<StringRef<wchar_t>, false> : ResultType<wchar_t> {}; + + ZEN_INIT_DETECT_MEMBER_TYPE(value_type); ZEN_INIT_DETECT_MEMBER(c_str); //we don't know the exact declaration of the member attribute and it may be in a base class! ZEN_INIT_DETECT_MEMBER(length); // @@ -127,28 +132,6 @@ public: IsSameType<CharType, wchar_t>::value }; }; - - -template <> class StringTraits<StringRef<char>> -{ -public: - enum - { - isStringClass = false, - isStringLike = true - }; - typedef char CharType; -}; -template <> class StringTraits<StringRef<wchar_t>> -{ -public: - enum - { - isStringClass = false, - isStringLike = true - }; - typedef wchar_t CharType; -}; } template <class T> @@ -174,7 +157,7 @@ size_t cStringLength(const C* str) //naive implementation seems somewhat faster return len; } -template <class S, typename = typename EnableIf<StringTraits<S>::isStringClass>::Type> inline +template <class S, typename = typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type> inline const typename GetCharType<S>::Type* strBegin(const S& str) //SFINAE: T must be a "string" { return str.c_str(); @@ -188,7 +171,7 @@ inline const char* strBegin(const StringRef<char >& ref) { return ref.data( inline const wchar_t* strBegin(const StringRef<wchar_t>& ref) { return ref.data(); } -template <class S, typename = typename EnableIf<StringTraits<S>::isStringClass>::Type> inline +template <class S, typename = typename EnableIf<implementation::StringTraits<S>::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 c895d9a0..c8c8c4be 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -100,7 +100,7 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, //_In_ DWORD dwFlagsAndAttributes, nullptr); //_In_opt_ HANDLE hTemplateFile if (hLink == INVALID_HANDLE_VALUE) - throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)), L"CreateFile", getLastError()); + throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"CreateFile", getLastError()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hLink)); //respect alignment issues... @@ -116,7 +116,7 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro bufferSize, //__in DWORD nOutBufferSize, &bytesReturned, //__out_opt LPDWORD lpBytesReturned, nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped - throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)), L"DeviceIoControl, FSCTL_GET_REPARSE_POINT", getLastError()); + throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"DeviceIoControl, FSCTL_GET_REPARSE_POINT", getLastError()); REPARSE_DATA_BUFFER& reparseData = *reinterpret_cast<REPARSE_DATA_BUFFER*>(&buffer[0]); //REPARSE_DATA_BUFFER needs to be artificially enlarged! @@ -132,7 +132,7 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro reparseData.MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); } else - throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)), L"Not a symbolic link or junction."); + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"Not a symbolic link or junction."); //absolute symlinks and junctions use NT namespace naming convention while relative ones do not: //http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx#NT_Namespaces @@ -144,9 +144,9 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro const ssize_t bytesWritten = ::readlink(linkPath.c_str(), &buffer[0], BUFFER_SIZE); if (bytesWritten < 0) - throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)), L"readlink", getLastError()); - if (bytesWritten >= static_cast<ssize_t>(BUFFER_SIZE)) //detect truncation! - throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath))); + throwFileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"readlink", getLastError()); + if (bytesWritten >= static_cast<ssize_t>(BUFFER_SIZE)) //detect truncation, not an error for readlink! + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(linkPath)), L"readlink: buffer truncated."); return Zstring(&buffer[0], bytesWritten); //readlink does not append 0-termination! #endif @@ -161,7 +161,7 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError typedef DWORD (WINAPI* GetFinalPathNameByHandleWFunc)(HANDLE hFile, LPTSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags); const SysDllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW"); if (!getFinalPathNameByHandle) - throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); + throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); const HANDLE hFile = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), //_In_ LPCTSTR lpFileName, @@ -173,12 +173,12 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError 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()); + throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"CreateFile", getLastError()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); const DWORD bufferSize = getFinalPathNameByHandle(hFile, nullptr, 0, 0); if (bufferSize == 0) - throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), L"GetFinalPathNameByHandle", getLastError()); + throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"GetFinalPathNameByHandle", getLastError()); std::vector<wchar_t> targetPath(bufferSize); const DWORD charsWritten = getFinalPathNameByHandle(hFile, //__in HANDLE hFile, @@ -187,7 +187,7 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError 0); //__in DWORD dwFlags if (charsWritten == 0 || charsWritten >= bufferSize) { - const std::wstring errorMsg = replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)); + const std::wstring errorMsg = replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)); if (charsWritten == 0) throwFileError(errorMsg, L"GetFinalPathNameByHandle", getLastError()); throw FileError(errorMsg); @@ -198,7 +198,7 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError #elif defined ZEN_LINUX || defined ZEN_MAC char* targetPath = ::realpath(linkPath.c_str(), nullptr); if (!targetPath) - throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), L"realpath", getLastError()); + throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), L"realpath", getLastError()); ZEN_ON_SCOPE_EXIT(::free(targetPath)); return targetPath; #endif diff --git a/zen/sys_error.h b/zen/sys_error.h index 7fb12d31..16e59266 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -46,6 +46,7 @@ private: std::wstring msg_; }; +#define DEFINE_NEW_SYS_ERROR(X) struct X : public SysError { X(const std::wstring& msg) : SysError(msg) {} }; @@ -92,7 +93,7 @@ std::wstring formatSystemErrorRaw(ErrorCode ec) //return empty string on error errorMsg = utfCvrtTo<std::wstring>(::strerror(ec)); #endif - trim(errorMsg); //Windows messages seem to end with a blank... + trim(errorMsg); //Windows messages seem to end with a blank... return errorMsg; } diff --git a/zen/thread.h b/zen/thread.h index c9b4c76f..6d647de8 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -39,23 +39,23 @@ namespace zen { /* std::async replacement without crappy semantics: - 1. guaranteed to run asynchronous + 1. guaranteed to run asynchronously 2. does not follow C++11 [futures.async], Paragraph 5, where std::future waits for thread in destructor Example: Zstring dirpath = ... - auto ft = zen::async([=](){ return zen::dirExists(dirpath); }); - if (ft.timed_wait(boost::posix_time::milliseconds(200)) && ft.get()) + auto ft = zen::runAsync([=](){ return zen::dirExists(dirpath); }); + if (ft.wait_for(boost::chrono::milliseconds(200)) == boost::future_status::ready && ft.get()) //dir exising */ template <class Function> -auto async(Function fun) -> boost::unique_future<decltype(fun())>; +auto runAsync(Function fun) -> boost::unique_future<decltype(fun())>; //wait for all with a time limit: return true if *all* results are available! template<class InputIterator, class Duration> bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration); -//wait until first job is successful or all failed +//wait until first job is successful or all failed: substitute until std::when_any is available template <class T> class GetFirstResult { @@ -73,11 +73,36 @@ public: private: class AsyncResult; - std::shared_ptr<AsyncResult> asyncResult; + std::shared_ptr<AsyncResult> asyncResult_; size_t jobsTotal_; }; +//value associated with mutex and guaranteed protected access: +template <class T> +class Protected +{ +public: + Protected() : value_() {} + Protected(const T& value) : value_(value) {} + + template <class Function> + void access(Function fun) + { + boost::lock_guard<boost::mutex> dummy(lockValue); + fun(value_); + } + +private: + Protected (const Protected&) = delete; + Protected& operator=(const Protected&) = delete; + + boost::mutex lockValue; + T value_; +}; + + + @@ -90,12 +115,12 @@ private: #endif template <class Function> inline -auto async(Function fun) -> boost::unique_future<decltype(fun())> +auto runAsync(Function fun) -> boost::unique_future<decltype(fun())> { typedef decltype(fun()) ResultType; #if defined BOOST_THREAD_PROVIDES_SIGNATURE_PACKAGED_TASK //mirror "boost/thread/future.hpp", hopefully they know what they're doing - boost::packaged_task<ResultType()> pt(std::move(fun)); //packaged task seems to even require r-value reference: https://sourceforge.net/p/freefilesync/bugs/234/ + boost::packaged_task<ResultType()> pt(std::move(fun)); #else boost::packaged_task<ResultType> pt(std::move(fun)); #endif @@ -106,11 +131,11 @@ auto async(Function fun) -> boost::unique_future<decltype(fun())> template<class InputIterator, class Duration> inline -bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration) +bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& duration) { - const boost::system_time endTime = boost::get_system_time() + wait_duration; + const boost::chrono::steady_clock::time_point endTime = boost::chrono::steady_clock::now() + duration; for (; first != last; ++first) - if (!first->timed_wait_until(endTime)) + if (first->wait_until(endTime) != boost::future_status::ready) return false; //time elapsed return true; } @@ -144,15 +169,13 @@ public: bool waitForResult(size_t jobsTotal, const Duration& duration) { boost::unique_lock<boost::mutex> dummy(lockResult); - return conditionJobDone.timed_wait(dummy, duration, [&] { return this->jobDone(jobsTotal); }); - //use timed_wait predicate if exitting before condition is reached: http://www.boost.org/doc/libs/1_49_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref.condition_variable.timed_wait_rel + return conditionJobDone.wait_for(dummy, duration, [&] { return this->jobDone(jobsTotal); }); //throw boost::thread_interrupted } std::unique_ptr<T> getResult(size_t jobsTotal) { boost::unique_lock<boost::mutex> dummy(lockResult); - while (!jobDone(jobsTotal)) - conditionJobDone.timed_wait(dummy, boost::posix_time::milliseconds(50)); //interruption point! + conditionJobDone.wait(dummy, [&] { return this->jobDone(jobsTotal); }); //throw boost::thread_interrupted #ifndef NDEBUG assert(!returnedResult); @@ -177,15 +200,15 @@ private: template <class T> inline -GetFirstResult<T>::GetFirstResult() : asyncResult(std::make_shared<AsyncResult>()), jobsTotal_(0) {} +GetFirstResult<T>::GetFirstResult() : asyncResult_(std::make_shared<AsyncResult>()), jobsTotal_(0) {} template <class T> template <class Fun> inline void GetFirstResult<T>::addJob(Fun f) //f must return a std::unique_ptr<T> containing a value on success { - auto asyncResult2 = asyncResult; //capture member variable, not "this"! - boost::thread t([asyncResult2, f] { asyncResult2->reportFinished(f()); }); + auto asyncResult = this->asyncResult_; //capture member variable, not "this"! + boost::thread t([asyncResult, f] { asyncResult->reportFinished(f()); }); ++jobsTotal_; t.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! } @@ -193,11 +216,11 @@ void GetFirstResult<T>::addJob(Fun f) //f must return a std::unique_ptr<T> conta template <class T> template <class Duration> inline -bool GetFirstResult<T>::timedWait(const Duration& duration) const { return asyncResult->waitForResult(jobsTotal_, duration); } +bool GetFirstResult<T>::timedWait(const Duration& duration) const { return asyncResult_->waitForResult(jobsTotal_, duration); } template <class T> inline -std::unique_ptr<T> GetFirstResult<T>::get() const { return asyncResult->getResult(jobsTotal_); } +std::unique_ptr<T> GetFirstResult<T>::get() const { return asyncResult_->getResult(jobsTotal_); } } #endif //BOOST_THREAD_WRAP_H_78963234 diff --git a/zen/type_tools.h b/zen/type_tools.h index 95e49769..ac365f05 100644 --- a/zen/type_tools.h +++ b/zen/type_tools.h @@ -46,6 +46,9 @@ struct RemoveRef : ResultType<T> {}; template <class T> struct RemoveRef<T&> : ResultType<T> {}; + +template <class T> +struct RemoveRef<T&&> : ResultType<T> {}; //------------------------------------------------------ template <class T> struct RemoveConst : ResultType<T> {}; @@ -7,6 +7,10 @@ #ifndef YAWFWH_YET_ANOTHER_WRAPPER_FOR_WINDOWS_H #define YAWFWH_YET_ANOTHER_WRAPPER_FOR_WINDOWS_H +#ifndef _WINSOCKAPI_ //prevent inclusion of winsock.h in windows.h: obsoleted by and conflicting with winsock2.h + #define _WINSOCKAPI_ +#endif + //------------------------------------------------------ #ifdef __WXMSW__ //we have wxWidgets #include <wx/msw/wrapwin.h> //includes "windows.h" diff --git a/zen/xml_io.cpp b/zen/xml_io.cpp index a1bf05d0..5b2cfdca 100644 --- a/zen/xml_io.cpp +++ b/zen/xml_io.cpp @@ -28,7 +28,7 @@ XmlDoc zen::loadXmlDocument(const Zstring& filepath) //throw FileError 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))); + throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filepath))); } copyStream(fileStreamIn, memStreamOut, fileStreamIn.optimalBlockSize(), nullptr); //throw FileError @@ -41,7 +41,7 @@ XmlDoc zen::loadXmlDocument(const Zstring& filepath) //throw FileError { throw FileError( replaceCpy(replaceCpy(replaceCpy(_("Error parsing file %x, row %y, column %z."), - L"%x", fmtFileName(filepath)), + L"%x", fmtPath(filepath)), L"%y", numberTo<std::wstring>(e.row + 1)), L"%z", numberTo<std::wstring>(e.col + 1))); } @@ -70,10 +70,10 @@ void zen::checkForMappingErrors(const XmlIn& xmlInput, const Zstring& filepath) { if (xmlInput.errorsOccured()) { - std::wstring msg = _("Cannot read the following XML elements:") + L"\n"; + std::wstring msg = _("The following XML elements could not be read:") + L"\n"; for (const std::wstring& elem : xmlInput.getErrorsAs<std::wstring>()) msg += L"\n" + elem; - throw FileError(replaceCpy(_("Configuration file %x loaded partially only."), L"%x", fmtFileName(filepath)) + L"\n\n" + msg); + throw FileError(replaceCpy(_("Configuration file %x is incomplete. The missing elements will be set to their default values."), L"%x", fmtPath(filepath)) + L"\n\n" + msg); } } diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 68934e19..8dcd4736 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -55,6 +55,10 @@ typedef int (WINAPI* CompareStringOrdinalFunc)(LPCWSTR lpString1, int cchCount1, const SysDllFun<CompareStringOrdinalFunc> compareStringOrdinal = SysDllFun<CompareStringOrdinalFunc>(L"kernel32.dll", "CompareStringOrdinal"); //watch for dependencies in global namespace!!! //caveat: function scope static initialization is not thread-safe in VS 2010! +#if defined _MSC_VER && _MSC_VER > 1800 + #error not true anymore +#endif + } @@ -148,18 +152,6 @@ Zstring makeUpperCopy(const Zstring& str) #elif defined ZEN_MAC -int cmpFilePath(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) -{ - assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls! - assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); // - - const int rv = ::strncasecmp(lhs, rhs, std::min(lhsLen, rhsLen)); //locale-dependent! - if (rv != 0) - return rv; - return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); -} - - Zstring makeUpperCopy(const Zstring& str) { const size_t len = str.size(); diff --git a/zen/zstring.h b/zen/zstring.h index 9822e504..462ea39c 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -14,7 +14,6 @@ #endif - #ifdef ZEN_WIN //Windows encodes Unicode as UTF-16 wchar_t typedef wchar_t Zchar; #define Zstr(x) L ## x @@ -27,13 +26,19 @@ #endif //"The reason for all the fuss above" - Loki/SmartPtr -//a high-performance string for interfacing with native OS APIs and multithreaded contexts +//a high-performance string for interfacing with native OS APIs in multithreaded contexts typedef zen::Zbase<Zchar, zen::StorageRefCountThreadSafe, zen::AllocatorOptimalSpeed> Zstring; //Compare filepaths: Windows does NOT distinguish between upper/lower-case, while Linux DOES -int cmpFilePath(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen); +int cmpFilePath(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen); + +#if defined ZEN_LINUX || defined ZEN_MAC + int cmpFilePath(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen); +#endif + + struct LessFilePath //case-insensitive on Windows, case-sensitive on Linux @@ -64,11 +69,8 @@ Zstring appendSeparator(Zstring path) //support rvalue references! inline Zstring getFileExtension(const Zstring& filePath) { - const Zstring shortName = afterLast(filePath, FILE_NAME_SEPARATOR); //returns the whole string if term not found - - return contains(shortName, Zchar('.')) ? - afterLast(filePath, Zchar('.')) : - Zstring(); + const Zstring shortName = afterLast(filePath, FILE_NAME_SEPARATOR, zen::IF_MISSING_RETURN_ALL); + return afterLast(shortName, Zchar('.'), zen::IF_MISSING_RETURN_NONE); } @@ -102,14 +104,34 @@ bool pathEndsWith(const S& str, const T& postfix) //################################# inline implementation ######################################## -#ifdef ZEN_LINUX +#if defined ZEN_LINUX || defined ZEN_MAC inline -int cmpFilePath(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) +int cmpFilePath(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen) { assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls! assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); // +#if defined ZEN_LINUX const int rv = std::strncmp(lhs, rhs, std::min(lhsLen, rhsLen)); +#elif defined ZEN_MAC + const int rv = ::strncasecmp(lhs, rhs, std::min(lhsLen, rhsLen)); //locale-dependent! +#endif + if (rv != 0) + return rv; + return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); +} + +inline +int cmpFilePath(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen) +{ + assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls! + assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); // + +#if defined ZEN_LINUX + const int rv = std::wcsncmp(lhs, rhs, std::min(lhsLen, rhsLen)); +#elif defined ZEN_MAC + const int rv = ::wcsncasecmp(lhs, rhs, std::min(lhsLen, rhsLen)); //locale-dependent! +#endif if (rv != 0) return rv; return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); |