diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:30:42 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:30:42 +0200 |
commit | 767bb3951c65e38627cb0bbad9a3756e1cda2520 (patch) | |
tree | 460b18606d2c3472d5aa08444db4db62c6410248 /zen | |
parent | 6.0 (diff) | |
download | FreeFileSync-767bb3951c65e38627cb0bbad9a3756e1cda2520.tar.gz FreeFileSync-767bb3951c65e38627cb0bbad9a3756e1cda2520.tar.bz2 FreeFileSync-767bb3951c65e38627cb0bbad9a3756e1cda2520.zip |
6.1
Diffstat (limited to 'zen')
-rw-r--r-- | zen/FindFilePlus/find_file_plus.cpp | 8 | ||||
-rw-r--r-- | zen/debug_minidump.cpp | 55 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 272 | ||||
-rw-r--r-- | zen/dir_watcher.h | 15 | ||||
-rw-r--r-- | zen/file_handling.cpp | 102 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 72 | ||||
-rw-r--r-- | zen/int64.h | 3 | ||||
-rw-r--r-- | zen/osx_string.h | 2 | ||||
-rw-r--r-- | zen/privilege.cpp | 6 | ||||
-rw-r--r-- | zen/stl_tools.h | 11 | ||||
-rw-r--r-- | zen/string_base.h | 11 | ||||
-rw-r--r-- | zen/thread.h | 2 | ||||
-rw-r--r-- | zen/zstring.cpp | 8 |
13 files changed, 270 insertions, 297 deletions
diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp index ea58005e..247b916c 100644 --- a/zen/FindFilePlus/find_file_plus.cpp +++ b/zen/FindFilePlus/find_file_plus.cpp @@ -285,15 +285,15 @@ bool FileSearcher::readDirImpl(FileInformation& output) //throw NtFileError; ret /* corresponding first access in ::FindFirstFileW() NTSTATUS rv = ntQueryDirectoryFile(hDir, //__in HANDLE fileHandle, - nullptr, //__in_opt HANDLE event, - nullptr, //__in_opt PIO_APC_ROUTINE apcRoutine, - nullptr, //__in_opt PVOID apcContext, + nullptr, //__in_opt HANDLE event, + nullptr, //__in_opt PIO_APC_ROUTINE apcRoutine, + nullptr, //__in_opt PVOID apcContext, &status, //__out PIO_STATUS_BLOCK ioStatusBlock, &buffer, //__out_bcount(Length) PVOID fileInformation, BUFFER_SIZE, //__in ULONG length, ::FindFirstFileW() on all XP/Win7/Win8 uses sizeof(FILE_BOTH_DIR_INFORMATION) + sizeof(TCHAR) * MAX_PATH == 0x268 FileIdBothDirectoryInformation, //__in FILE_INFORMATION_CLASS fileInformationClass - all XP/Win7/Win8 use "FileBothDirectoryInformation" true, //__in BOOLEAN returnSingleEntry, - nullptr, //__in_opt PUNICODE_STRING mask, + nullptr, //__in_opt PUNICODE_STRING mask, false); //__in BOOLEAN restartScan */ diff --git a/zen/debug_minidump.cpp b/zen/debug_minidump.cpp index 1b625015..29500ae4 100644 --- a/zen/debug_minidump.cpp +++ b/zen/debug_minidump.cpp @@ -9,18 +9,33 @@ #include <sstream> #include <cassert> #include <cstdlib> //malloc(), free() +#include <zen/file_error.h> +#include <zen/scope_guard.h> +#include <zen/time.h> #include "win.h" //includes "windows.h" #include "DbgHelp.h" //available for MSC only #pragma comment(lib, "Dbghelp.lib") +using namespace zen; + namespace { -LONG WINAPI writeDumpOnException(EXCEPTION_POINTERS* pExceptionInfo) +LONG WINAPI writeDumpOnException(EXCEPTION_POINTERS* pExceptionInfo) //blocks showing message boxes on success and error! { - HANDLE hFile = ::CreateFile(L"exception.dmp", GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - if (hFile != INVALID_HANDLE_VALUE) + assert(false); + + const Zstring filename = L"CrashDump " + formatTime<Zstring>(L"%Y-%m-%d %H%M%S") + L".dmp"; + { + HANDLE hFile = ::CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + if (hFile == INVALID_HANDLE_VALUE) + { + ::MessageBox(nullptr, (replaceCpy<std::wstring>(L"Cannot write file %x.", L"%x", fmtFileName(filename)) + L"\n\n" + formatSystemError(L"CreateFile", ::GetLastError())).c_str(), L"Application Crash", MB_SERVICE_NOTIFICATION | MB_ICONERROR); + std::terminate(); + } + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); + MINIDUMP_EXCEPTION_INFORMATION exInfo = {}; exInfo.ThreadId = ::GetCurrentThreadId(); exInfo.ExceptionPointers = pExceptionInfo; @@ -28,23 +43,33 @@ LONG WINAPI writeDumpOnException(EXCEPTION_POINTERS* pExceptionInfo) MINIDUMP_EXCEPTION_INFORMATION* exceptParam = pExceptionInfo ? &exInfo : nullptr; - /*bool rv = */ - ::MiniDumpWriteDump(::GetCurrentProcess (), //__in HANDLE hProcess, - ::GetCurrentProcessId(), //__in DWORD ProcessId, - hFile, //__in HANDLE hFile, - MiniDumpWithDataSegs, //__in MINIDUMP_TYPE DumpType, ->Standard: MiniDumpNormal, Medium: MiniDumpWithDataSegs, Full: MiniDumpWithFullMemory - exceptParam, //__in PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, - nullptr, //__in PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, - nullptr); //__in PMINIDUMP_CALLBACK_INFORMATION CallbackParam + if (!::MiniDumpWriteDump(::GetCurrentProcess (), //__in HANDLE hProcess, + ::GetCurrentProcessId(), //__in DWORD ProcessId, + hFile, //__in HANDLE hFile, + MiniDumpWithDataSegs, //__in MINIDUMP_TYPE DumpType, ->Standard: MiniDumpNormal, Medium: MiniDumpWithDataSegs, Full: MiniDumpWithFullMemory + exceptParam, //__in PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + nullptr, //__in PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + nullptr)) //__in PMINIDUMP_CALLBACK_INFORMATION CallbackParam + { + ::MessageBox(nullptr, (replaceCpy<std::wstring>(L"Cannot write file %x.", L"%x", fmtFileName(filename)) + L"\n\n" + formatSystemError(L"MiniDumpWriteDump", ::GetLastError())).c_str(), L"Application Crash", MB_SERVICE_NOTIFICATION | MB_ICONERROR); + std::terminate(); + } + } //close file before showing success message + + //attention: the app has not yet officially crashed! => use MB_SERVICE_NOTIFICATION to avoid Win32 GUI callbacks while message box is shown! + ::MessageBox(nullptr, replaceCpy<std::wstring>(L"Crash dump file %x written!", L"%x", fmtFileName(filename)).c_str(), L"Application Crash", MB_SERVICE_NOTIFICATION | MB_ICONERROR); + std::terminate(); - ::CloseHandle(hFile); - } - assert(false); return EXCEPTION_EXECUTE_HANDLER; } //ensure that a dump-file is written for uncaught exceptions -struct OnStartup { OnStartup() { ::SetUnhandledExceptionFilter(writeDumpOnException); }} dummy; +struct OnStartup { + OnStartup() + { + /*LPTOP_LEVEL_EXCEPTION_FILTER oldFilter = */ ::SetUnhandledExceptionFilter(writeDumpOnException); + //oldFilter == &__CxxUnhandledExceptionFilter() by default! + }} dummy; } diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 1945ada7..951dd3ae 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -21,10 +21,8 @@ #include "file_traverser.h" #elif defined ZEN_MAC -//#include <sys/types.h> -//#include <sys/event.h> -//#include <sys/time.h> -#include "file_traverser.h" +#include <CoreServices/CoreServices.h> +#include <zen/osx_string.h> #endif using namespace zen; @@ -97,7 +95,7 @@ public: //context of main thread - void getChanges(std::vector<DirWatcher::Entry>& output) //throw FileError + void fetchChanges(std::vector<DirWatcher::Entry>& output) //throw FileError { boost::lock_guard<boost::mutex> dummy(lockAccess); @@ -162,6 +160,14 @@ public: //end of constructor, no need to start managing "hDir" } + ReadChangesAsync(ReadChangesAsync&& other) : + hDir(INVALID_HANDLE_VALUE) + { + shared_ = std::move(other.shared_); + dirnamePf = std::move(other.dirnamePf); + std::swap(hDir, other.hDir); + } + ~ReadChangesAsync() { if (hDir != INVALID_HANDLE_VALUE) //valid hDir is NOT an invariant, see move constructor! @@ -250,14 +256,6 @@ public: } } - ReadChangesAsync(ReadChangesAsync&& other) : - hDir(INVALID_HANDLE_VALUE) - { - shared_ = std::move(other.shared_); - dirnamePf = std::move(other.dirnamePf); - std::swap(hDir, other.hDir); - } - HANDLE getDirHandle() const { return hDir; } //for reading/monitoring purposes only, don't abuse (e.g. close handle)! private: @@ -364,7 +362,7 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() output.push_back(Entry(ACTION_DELETE, pimpl_->dirname)); //report removal as change to main directory } else //the normal case... - pimpl_->shared->getChanges(output); //throw FileError + pimpl_->shared->fetchChanges(output); //throw FileError return output; } @@ -395,6 +393,8 @@ private: struct DirWatcher::Pimpl { + Pimpl() : notifDescr() {} + Zstring baseDirname; int notifDescr; std::map<int, Zstring> watchDescrs; //watch descriptor and (sub-)directory name (postfixed with separator) -> owned by "notifDescr" @@ -519,188 +519,116 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() } #elif defined ZEN_MAC -warn_static("finish"); -struct DirWatcher::Pimpl {}; -DirWatcher::DirWatcher(const Zstring& directory) {} -DirWatcher::~DirWatcher() {} -std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) { return std::vector<DirWatcher::Entry>(); } - -#if 0 namespace { -class DirsOnlyTraverser : public zen::TraverseCallback +void eventCallback(ConstFSEventStreamRef streamRef, + void* clientCallBackInfo, + size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) { -public: - DirsOnlyTraverser(std::vector<Zstring>& dirs) : dirs_(dirs) {} + std::vector<DirWatcher::Entry>& changedFiles = *static_cast<std::vector<DirWatcher::Entry>*>(clientCallBackInfo); - virtual void onFile (const Zchar* shortName, const Zstring& fullName, const FileInfo& details) {} - virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) { return LINK_SKIP; } - virtual TraverseCallback* onDir(const Zchar* shortName, const Zstring& fullName) + auto paths = static_cast<const char**>(eventPaths); + for (size_t i = 0; i < numEvents; ++i) { - dirs_.push_back(fullName); - return this; + //::printf("0x%08x\t%s\n", static_cast<unsigned int>(eventFlags[i]), paths[i]); + + //events are aggregated => it's possible to see a single event with flags + //kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemModified | kFSEventStreamEventFlagItemRemoved + + if (eventFlags[i] & kFSEventStreamEventFlagItemCreated || + eventFlags[i] & kFSEventStreamEventFlagMount) + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_CREATE, paths[i])); + if (eventFlags[i] & kFSEventStreamEventFlagItemModified || // + eventFlags[i] & kFSEventStreamEventFlagItemXattrMod || // + eventFlags[i] & kFSEventStreamEventFlagItemChangeOwner || //aggregate these into a single event + eventFlags[i] & kFSEventStreamEventFlagItemInodeMetaMod || // + eventFlags[i] & kFSEventStreamEventFlagItemFinderInfoMod || // + eventFlags[i] & kFSEventStreamEventFlagItemRenamed || //OS X sends the same event flag for both old and new names!!! + eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs) //something changed in one of the subdirs: NOT expected due to kFSEventStreamCreateFlagFileEvents + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_UPDATE, paths[i])); + if (eventFlags[i] & kFSEventStreamEventFlagItemRemoved || + eventFlags[i] & kFSEventStreamEventFlagRootChanged || //root is (indirectly) deleted or renamed + eventFlags[i] & kFSEventStreamEventFlagUnmount) + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_DELETE, paths[i])); + + //kFSEventStreamEventFlagEventIdsWrapped -> irrelevant! + //kFSEventStreamEventFlagHistoryDone -> not expected due to kFSEventStreamEventIdSinceNow below } - virtual HandleError reportDirError (const std::wstring& msg, size_t retryNumber) { throw FileError(msg); } - virtual HandleError reportItemError(const std::wstring& msg, size_t retryNumber, const Zchar* shortName) { throw FileError(msg); } - -private: - std::vector<Zstring>& dirs_; -}; - - -class DirDescriptor //throw FileError -{ -public: - DirDescriptor(const Zstring& dirname) : dirname_(dirname) - { - fdDir = ::open(dirname.c_str(), O_EVTONLY); //"descriptor requested for event notifications only"; O_EVTONLY does not exist on Linux - if (fdDir == -1) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirname)), formatSystemError(L"open", getLastError())); - } - - ~DirDescriptor() { if (fdDir != -1) ::close(fdDir); } //check for "-1" only needed by move-constructor - - DirDescriptor(DirDescriptor&& other) : fdDir(other.fdDir), dirname_(std::move(other.dirname_)) { other.fdDir = -1; } - - int getDescriptor() const { return fdDir; } - Zstring getDirname() const { return dirname_; } - -private: - DirDescriptor(const DirDescriptor&) = delete; - DirDescriptor& operator=(const DirDescriptor&) = delete; - - int fdDir; - Zstring dirname_; -}; +} } -warn_static("finish") + struct DirWatcher::Pimpl { - Zstring baseDirname; - int queueDescr; - std::map<int, DirDescriptor> watchDescrs; //directory descriptors and corresponding (sub-)directory name (postfixed with separator!) - std::vector<struct ::kevent> changelist; + Pimpl() : eventStream() {} + FSEventStreamRef eventStream; + std::vector<DirWatcher::Entry> changedFiles; }; -DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError +DirWatcher::DirWatcher(const Zstring& directory) : pimpl_(new Pimpl) { - //get all subdirectories - Zstring dirname = directory; - if (endsWith(dirname, FILE_NAME_SEPARATOR)) - dirname.resize(dirname.size() - 1); - - std::vector<Zstring> fullDirList { dirname }; - { - DirsOnlyTraverser traverser(fullDirList); //throw FileError - zen::traverseFolder(dirname, traverser); //don't traverse into symlinks (analog to windows build) - } - - pimpl_->baseDirname = directory; - - pimpl_->queueDescr = ::kqueue(); - if (pimpl_->queueDescr == -1) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), formatSystemError(L"kqueue", getLastError())); - zen::ScopeGuard guardDescr = zen::makeGuard([&] { ::close(pimpl_->queueDescr); }); - - for (const Zstring& subdir : fullDirList) - { - DirDescriptor descr(subdir); - const int rawDescr = descr.getDescriptor(); - pimpl_->watchDescrs.insert(std::make_pair(rawDescr, std::move(descr))); - - pimpl_->changelist.push_back({}); - EV_SET(&pimpl_->changelist.back(), - rawDescr, //identifier for this event - EVFILT_VNODE, //filter for event - EV_ADD | EV_CLEAR, //general flags - NOTE_DELETE | NOTE_REVOKE | NOTE_RENAME | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB, //filter-specific flags - 0, //filter-specific data - nullptr); //opaque user data identifier - } - - //what about EINTR? - struct ::timespec timeout = {}; //=> poll - if (::kevent(pimpl_->queueDescr, &pimpl_->changelist[0], pimpl_->changelist.size(), nullptr, 0, &timeout) < 0) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), formatSystemError(L"kevent", getLastError())); - - guardDescr.dismiss(); + CFStringRef dirnameCf = osx::createCFString(directory.c_str()); //returns nullptr on error + if (!dirnameCf) + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"Function call failed: createCFString"); //no error code documented! + ZEN_ON_SCOPE_EXIT(::CFRelease(dirnameCf)); + + CFArrayRef dirnameCfArray = ::CFArrayCreate(nullptr, //CFAllocatorRef allocator, + reinterpret_cast<const void**>(&dirnameCf), //const void** values, + 1, //CFIndex numValues, + nullptr); //const CFArrayCallBacks* callBacks + if (!dirnameCfArray) + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"Function call failed: CFArrayCreate"); //no error code documented! + ZEN_ON_SCOPE_EXIT(::CFRelease(dirnameCfArray)); + + FSEventStreamContext context = {}; + context.info = &pimpl_->changedFiles; + + pimpl_->eventStream = ::FSEventStreamCreate(nullptr, //CFAllocatorRef allocator, + &eventCallback, //FSEventStreamCallback callback, + &context, //FSEventStreamContext* context, + dirnameCfArray, //CFArrayRef pathsToWatch, + kFSEventStreamEventIdSinceNow, //FSEventStreamEventId sinceWhen, + 0, //CFTimeInterval latency, in seconds + kFSEventStreamCreateFlagWatchRoot | + kFSEventStreamCreateFlagFileEvents); //FSEventStreamCreateFlags flags + //can this fail?? not documented! + + zen::ScopeGuard guardCreate = zen::makeGuard([&] { ::FSEventStreamRelease(pimpl_->eventStream); }); + + ::FSEventStreamScheduleWithRunLoop(pimpl_->eventStream, //FSEventStreamRef streamRef, + ::CFRunLoopGetCurrent(), //CFRunLoopRef runLoop; CFRunLoopGetCurrent(): failure not documented! + kCFRunLoopDefaultMode); //CFStringRef runLoopMode + //no-fail + + zen::ScopeGuard guardRunloop = zen::makeGuard([&] { ::FSEventStreamInvalidate(pimpl_->eventStream); }); + + if (!::FSEventStreamStart(pimpl_->eventStream)) + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), L"Function call failed: FSEventStreamStart"); //no error code documented! + + guardCreate .dismiss(); + guardRunloop.dismiss(); } DirWatcher::~DirWatcher() { - ::close(pimpl_->queueDescr); + ::FSEventStreamStop (pimpl_->eventStream); + ::FSEventStreamInvalidate(pimpl_->eventStream); + ::FSEventStreamRelease (pimpl_->eventStream); } -std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError +std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) { - std::vector<Entry> output; - - std::vector<struct ::kevent> events(512); - for (;;) - { - assert(!pimpl_->changelist.empty()); //contains at least parent directory - struct ::timespec timeout = {}; //=> poll - - int evtCount = 0; - do - { - evtCount = ::kevent(pimpl_->queueDescr, //int kq, - &pimpl_->changelist[0], //const struct kevent* changelist, - pimpl_->changelist.size(), //int nchanges, - &events[0], //struct kevent* eventlist, - events.size(), //int nevents, - &timeout); //const struct timespec* timeout - } - while (evtCount < 0 && errno == EINTR); - - if (evtCount == -1) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->baseDirname)), formatSystemError(L"kevent", getLastError())); - - for (int i = 0; i < evtCount; ++i) - { - const auto& evt = events[i]; - - auto it = pimpl_->watchDescrs.find(static_cast<int>(evt.ident)); - if (it == pimpl_->watchDescrs.end()) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->baseDirname)), L"Received event from unknown source."); + ::FSEventStreamFlushSync(pimpl_->eventStream); //flushes pending events + execs runloop - //"If an error occurs [...] and there is enough room in the eventlist, then the event will - // be placed in the eventlist with EV_ERROR set in flags and the system error in data." - if (evt.flags & EV_ERROR) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(it->second.getDirname())), formatSystemError(L"kevent", static_cast<ErrorCode>(evt.data))); - - assert(evt.filter == EVFILT_VNODE); - if (evt.filter == EVFILT_VNODE) - { - if (evt.fflags & NOTE_DELETE) - wxMessageBox(L"NOTE_DELETE "+ it->second.getDirname()); - else if (evt.fflags & NOTE_REVOKE) - wxMessageBox(L"NOTE_REVOKE "+ it->second.getDirname()); - else if (evt.fflags & NOTE_RENAME) - wxMessageBox(L"NOTE_RENAME "+ it->second.getDirname()); - else if (evt.fflags & NOTE_WRITE) - wxMessageBox(L"NOTE_WRITE "+ it->second.getDirname()); - else if (evt.fflags & NOTE_EXTEND) - wxMessageBox(L"NOTE_EXTEND "+ it->second.getDirname()); - else if (evt.fflags & NOTE_ATTRIB) - wxMessageBox(L"NOTE_ATTRIB "+ it->second.getDirname()); - else - assert(false); - } - } - - if (evtCount < events.size()) - break; - } - - return output; + std::vector<DirWatcher::Entry> changes; + changes.swap(pimpl_->changedFiles); + return changes; } #endif - - -#endif diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h index 233bdc59..bc9714a0 100644 --- a/zen/dir_watcher.h +++ b/zen/dir_watcher.h @@ -21,13 +21,16 @@ namespace zen //watch directory including subdirectories /* !Note handling of directories!: - Linux: newly added subdirectories are reported but not automatically added for watching! -> reset Dirwatcher! - removal of top watched directory is NOT notified! - Windows: removal of top watched directory also NOT notified (e.g. brute force usb stick removal) + Windows: removal of top watched directory is NOT notified (e.g. brute force usb stick removal) however manual unmount IS notified (e.g. usb stick removal, then re-insert), but watching is stopped! Renaming of top watched directory handled incorrectly: Not notified(!) + additional changes in subfolders now do report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!) + Linux: newly added subdirectories are reported but not automatically added for watching! -> reset Dirwatcher! + removal of top watched directory is NOT notified! + + OS X: everything works as expected; renaming of top level folder is also detected + Overcome all issues portably: check existence of top watched directory externally + reinstall watch after changes in directory structure (added directories) are detected */ class DirWatcher @@ -38,9 +41,9 @@ public: enum ActionType { - ACTION_CREATE, - ACTION_UPDATE, - ACTION_DELETE, + ACTION_CREATE, //informal only! + ACTION_UPDATE, //use for debugging/logging only! + ACTION_DELETE, // }; struct Entry diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index 8c5584db..563a07ce 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -14,7 +14,7 @@ #include "file_io.h" #include "assert_static.h" #include "file_id_def.h" -#include <boost/thread/tss.hpp> +//#include <boost/thread/tss.hpp> #ifdef ZEN_WIN #include <Aclapi.h> @@ -1303,36 +1303,42 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), dirInfo.dwFileAttributes); //copy "read-only and system attributes": http://blogs.msdn.com/b/oldnewthing/archive/2003/09/30/55100.aspx - const bool isCompressed = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; const bool isEncrypted = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; + const bool isCompressed = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; if (isEncrypted) ::EncryptFile(directory.c_str()); //seems no long path is required (check passed!) - if (isCompressed) + HANDLE hDirTrg = ::CreateFile(applyLongPathPrefix(directory).c_str(), + GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + nullptr); + if (hDirTrg != INVALID_HANDLE_VALUE) { - HANDLE hDirTrg = ::CreateFile(applyLongPathPrefix(directory).c_str(), - GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - nullptr, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - nullptr); - if (hDirTrg != INVALID_HANDLE_VALUE) - { - ZEN_ON_SCOPE_EXIT(::CloseHandle(hDirTrg)); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hDirTrg)); + if (isCompressed) + { USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; DWORD bytesReturned = 0; - ::DeviceIoControl(hDirTrg, //handle to file or directory - FSCTL_SET_COMPRESSION, //dwIoControlCode - &cmpState, //input buffer - sizeof(cmpState), //size of input buffer - nullptr, //lpOutBuffer - 0, //OutBufferSize - &bytesReturned, //number of bytes returned - nullptr); //OVERLAPPED structure + /*bool rv = */::DeviceIoControl(hDirTrg, //handle to file or directory + FSCTL_SET_COMPRESSION, //dwIoControlCode + &cmpState, //input buffer + sizeof(cmpState), //size of input buffer + nullptr, //lpOutBuffer + 0, //OutBufferSize + &bytesReturned, //number of bytes returned + nullptr); //OVERLAPPED structure } + + //(try to) set creation and modification time + /*bool rv = */::SetFileTime(hDirTrg, //_In_ HANDLE hFile, + &dirInfo.ftCreationTime, //_Out_opt_ LPFILETIME lpCreationTime, + nullptr, //_Out_opt_ LPFILETIME lpLastAccessTime, + &dirInfo.ftLastWriteTime); //_Out_opt_ LPFILETIME lpLastWriteTime } } } @@ -1783,12 +1789,12 @@ public: throw ErrorShouldCopyAsSparse(L"sparse dummy value"); if (exceptionInUserCallback) - try - { - exceptionInUserCallback(0); //should throw again!!! - assert(false); - } - catch (...) { throw; } + try + { + exceptionInUserCallback(0); //should throw again!!! + assert(false); + } + catch (...) { throw; } if (!errorMsg.first.empty()) throw FileError(errorMsg.first, errorMsg.second); @@ -2171,19 +2177,6 @@ void copyFileLinuxMac(const Zstring& sourceFile, } #endif - -Zstring findUnusedTempName(const Zstring& filename) -{ - Zstring output = filename + zen::TEMP_FILE_ENDING; - - //ensure uniqueness (+ minor file system race condition!) - for (int i = 1; somethingExists(output); ++i) - output = filename + Zchar('_') + numberTo<Zstring>(i) + zen::TEMP_FILE_ENDING; - - return output; -} - - /* ------------------ |File Copy Layers| @@ -2225,25 +2218,22 @@ void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPath { if (transactionalCopy) { - Zstring temporary = targetFile + zen::TEMP_FILE_ENDING; //use temporary file until a correct date has been set + Zstring tmpTarget = targetFile + TEMP_FILE_ENDING; //use temporary file until a correct date has been set //raw file copy - try - { - copyFileSelectOs(sourceFile, temporary, onUpdateCopyStatus, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked - } - catch (ErrorTargetExisting&) - { - //determine non-used temp file name "first": - //using optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses - temporary = findUnusedTempName(targetFile); - - //retry - copyFileSelectOs(sourceFile, temporary, onUpdateCopyStatus, sourceAttr); //throw FileError - } + for (int i = 1;; ++i) + try + { + copyFileSelectOs(sourceFile, tmpTarget, onUpdateCopyStatus, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked + break; + } + catch (const ErrorTargetExisting&) //using optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses + { + tmpTarget = targetFile + Zchar('_') + numberTo<Zstring>(i) + TEMP_FILE_ENDING; + } //transactional behavior: ensure cleanup; not needed before copyFileSelectOs() which is already transactional - zen::ScopeGuard guardTempFile = zen::makeGuard([&] { try { removeFile(temporary); } catch (FileError&) {} }); + zen::ScopeGuard guardTempFile = zen::makeGuard([&] { try { removeFile(tmpTarget); } catch (FileError&) {} }); //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite if (onDeleteTargetFile) @@ -2251,7 +2241,7 @@ void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPath //rename temporary file: //perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick! - renameFile(temporary, targetFile); //throw FileError + renameFile(tmpTarget, targetFile); //throw FileError /* CAVEAT on FAT/FAT32: the sequence of deleting the target file and renaming "file.txt.ffs_tmp" to "file.txt" does diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 9c1b01a4..70593d1d 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -22,6 +22,7 @@ #endif #if defined ZEN_LINUX || defined ZEN_MAC +#include <cstddef> //required by GCC 4.8.1 to find ptrdiff_t #include <sys/stat.h> #include <dirent.h> #endif @@ -153,8 +154,7 @@ typedef ... FindData; static void create(const Zstring& directory, DirHandle& hnd); //throw FileError - *no* concession to FindFirstFile(): open handle only, *no* return of data! static void destroy(DirHandle hnd); //throw() -template <class FallbackFun> -static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo, FallbackFun fb) //throw FileError -> fb: fallback to FindFirstFile()/FindNextFile() +static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError, NeedFallbackToWin32Traverser -> fallback to FindFirstFile()/FindNextFile() //FindData "member" functions static void extractFileInfo (const FindData& fileInfo, DWORD volumeSerial, TraverseCallback::FileInfo& output); //volumeSerial may be 0 if not available! @@ -207,8 +207,7 @@ struct Win32Traverser static void destroy(const DirHandle& hnd) { ::FindClose(hnd.searchHandle); } //throw() - template <class FallbackFun> - static bool getEntry(DirHandle& hnd, const Zstring& dirname, FindData& fileInfo, FallbackFun) //throw FileError + static bool getEntry(DirHandle& hnd, const Zstring& dirname, FindData& fileInfo) //throw FileError { if (hnd.searchHandle == INVALID_HANDLE_VALUE) //handle special case of "truly empty directories" return false; @@ -247,6 +246,9 @@ struct Win32Traverser }; +class NeedFallbackToWin32Traverser {}; //special exception class + + struct FilePlusTraverser { struct DirHandle @@ -267,8 +269,7 @@ struct FilePlusTraverser static void destroy(DirHandle hnd) { ::closeDir(hnd.searchHandle); } //throw() - template <class FallbackFun> - static bool getEntry(DirHandle hnd, const Zstring& dirname, FindData& fileInfo, FallbackFun fb) //throw FileError + static bool getEntry(DirHandle hnd, const Zstring& dirname, FindData& fileInfo) //throw FileError, NeedFallbackToWin32Traverser { if (!::readDir(hnd.searchHandle, fileInfo)) { @@ -281,10 +282,8 @@ struct FilePlusTraverser this is required for NetDrive mounted Webdav, e.g. www.box.net and NT4, 2000 remote drives, et al. */ if (lastError == ERROR_NOT_SUPPORTED) - { - fb(); //fallback should apply to whole directory sub-tree! => client needs to handle duplicate file notifications! - return false; - } + throw NeedFallbackToWin32Traverser(); + //fallback should apply to whole directory sub-tree! => client needs to handle duplicate file notifications! //else we have a problem... report it: throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirname)), formatSystemError(L"readDir", lastError)); @@ -312,6 +311,12 @@ struct FilePlusTraverser class DirTraverser { public: + static void execute(const Zstring& baseDirectory, TraverseCallback& sink, DstHackCallback* dstCallback) + { + DirTraverser(baseDirectory, sink, dstCallback); + } + +private: DirTraverser(const Zstring& baseDirectory, TraverseCallback& sink, DstHackCallback* dstCallback) : needDstHack(dstCallback ? dst::isFatDrive(baseDirectory) : false) { @@ -322,7 +327,13 @@ public: catch (FileError&) {} //don't cause issues in user mode if (::openDir && ::readDir && ::closeDir) - traverse<FilePlusTraverser>(baseDirectory, sink, retrieveVolumeSerial(baseDirectory)); //retrieveVolumeSerial returns 0 on error + { + try + { + traverse<FilePlusTraverser>(baseDirectory, sink, retrieveVolumeSerial(baseDirectory)); //throw NeedFallbackToWin32Traverser; retrieveVolumeSerial returns 0 on error + } + catch (NeedFallbackToWin32Traverser&) { traverse<Win32Traverser>(baseDirectory, sink, 0); } + } else //fallback traverse<Win32Traverser>(baseDirectory, sink, 0); @@ -332,33 +343,29 @@ public: applyDstHack(*dstCallback); } -private: DirTraverser(const DirTraverser&); DirTraverser& operator=(const DirTraverser&); template <class Trav> - void traverse(const Zstring& dirname, zen::TraverseCallback& sink, DWORD volumeSerial /*may be 0!*/) + void traverse(const Zstring& dirname, zen::TraverseCallback& sink, DWORD volumeSerial /*may be 0!*/) //throw NeedFallbackToWin32Traverser { //no need to check for endless recursion: Windows seems to have an internal path limit of about 700 chars typename Trav::DirHandle searchHandle; - if (!tryReportingDirError([&] - { - typedef Trav Trav; //f u VS! - Trav::create(dirname, searchHandle); //throw FileError - }, sink)) + if (!tryReportingDirError([&] { Trav::create(dirname, searchHandle); /*throw FileError*/ }, sink)) return; //ignored error - ZEN_ON_SCOPE_EXIT(typedef Trav Trav; Trav::destroy(searchHandle)); + ZEN_ON_SCOPE_EXIT(Trav::destroy(searchHandle)); typename Trav::FindData findData = {}; - auto fallback = [&] { this->traverse<Win32Traverser>(dirname, sink, volumeSerial); }; //help VS2010 a little by avoiding too deeply nested lambdas - for (;;) { bool gotEntry = false; - tryReportingDirError([&] { typedef Trav Trav; /*VS 2010 bug*/ gotEntry = Trav::getEntry(searchHandle, dirname, findData, fallback); }, sink); //throw FileError + tryReportingDirError([&] + { + gotEntry = Trav::getEntry(searchHandle, dirname, findData); //throw FileError, NeedFallbackToWin32Traverser + }, sink); if (!gotEntry) //no more items or ignored error return; @@ -383,7 +390,11 @@ private: if (TraverseCallback* trav = sink.onDir(shortName, fullName)) { ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); - traverse<Trav>(fullName, *trav, retrieveVolumeSerial(fullName)); //symlink may link to different volume => redetermine volume serial! + try + { + traverse<Trav>(fullName, *trav, retrieveVolumeSerial(fullName)); //throw NeedFallbackToWin32Traverser; symlink may link to different volume => redetermine volume serial! + } + catch (NeedFallbackToWin32Traverser&) { traverse<Win32Traverser>(fullName, *trav, 0); } } } else //a file @@ -410,7 +421,11 @@ private: if (TraverseCallback* trav = sink.onDir(shortName, fullName)) { ZEN_ON_SCOPE_EXIT(sink.releaseDirTraverser(trav)); - traverse<Trav>(fullName, *trav, volumeSerial); + try + { + traverse<Trav>(fullName, *trav, volumeSerial); //throw NeedFallbackToWin32Traverser + } + catch (NeedFallbackToWin32Traverser&) { traverse<Win32Traverser>(fullName, *trav, 0); } } } else //a file @@ -493,6 +508,12 @@ private: class DirTraverser { public: + static void execute(const Zstring& baseDirectory, TraverseCallback& sink, DstHackCallback* dstCallback) + { + DirTraverser(baseDirectory, sink, dstCallback); + } + +private: DirTraverser(const Zstring& baseDirectory, zen::TraverseCallback& sink, zen::DstHackCallback* dstCallback) { const Zstring directoryFormatted = //remove trailing slash @@ -511,7 +532,6 @@ public: traverse(directoryFormatted, sink); } -private: DirTraverser(const DirTraverser&); DirTraverser& operator=(const DirTraverser&); @@ -658,5 +678,5 @@ private: void zen::traverseFolder(const Zstring& dirname, TraverseCallback& sink, DstHackCallback* dstCallback) { - DirTraverser(dirname, sink, dstCallback); + DirTraverser::execute(dirname, sink, dstCallback); } diff --git a/zen/int64.h b/zen/int64.h index 6901ebeb..f658e034 100644 --- a/zen/int64.h +++ b/zen/int64.h @@ -39,9 +39,6 @@ void checkRange(U value) //caveat: std::numeric_limits<T>::min returns minimum positive(!) number for T = double, while behaving correctly for integer types... sigh assert(double(std::numeric_limits<T>::lowest()) <= double(value) && //new with C++11! double(std::numeric_limits<T>::max() ) >= double(value)); - - // assert(double(boost::numeric::bounds<T>::lowest ()) <= double(value) && - // double(boost::numeric::bounds<T>::highest()) >= double(value)); } class Int64 diff --git a/zen/osx_string.h b/zen/osx_string.h index 38c54023..ba83ca27 100644 --- a/zen/osx_string.h +++ b/zen/osx_string.h @@ -15,7 +15,7 @@ namespace osx Zstring cfStringToZstring(const CFStringRef& cfStr); CFStringRef createCFString (const char* utf8Str); //returns nullptr on error -CFMutableStringRef createMutableCFString(const char* utf8Str); //pass ownership! => ZEN_ON_SCOPE_EXIT(::CFRelease(utf8Str)); +CFMutableStringRef createMutableCFString(const char* utf8Str); //pass ownership! => ZEN_ON_SCOPE_EXIT(::CFRelease(str)); diff --git a/zen/privilege.cpp b/zen/privilege.cpp index 98eaad43..90612756 100644 --- a/zen/privilege.cpp +++ b/zen/privilege.cpp @@ -1,6 +1,6 @@ #include "privilege.h" #include <map> -#include "thread.h" //includes <boost/thread.hpp> +#include <mutex> #include "zstring.h" #include "scope_guard.h" #include "win_ver.h" @@ -127,12 +127,12 @@ private: std::map<Zstring, bool> activePrivileges; //bool: enabled by this application }; -boost::mutex lockPrivileges; +std::mutex lockPrivileges; } void zen::activatePrivilege(LPCTSTR privilege) //throw FileError { - boost::lock_guard<boost::mutex> dummy(lockPrivileges); + std::lock_guard<std::mutex> dummy(lockPrivileges); Privileges::getInstance().ensureActive(privilege); } diff --git a/zen/stl_tools.h b/zen/stl_tools.h index 2bb4fd3f..92cd86ad 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -25,6 +25,9 @@ namespace zen template <class V, class Predicate> void vector_remove_if(V& vec, Predicate p); +template <class V, class W> +void vector_append(V& vec, W& vec2); + template <class S, class Predicate> void set_remove_if(S& set, Predicate p); @@ -88,6 +91,13 @@ void vector_remove_if(V& vec, Predicate p) } +template <class V, class W> inline +void vector_append(V& vec, W& vec2) +{ +vec.insert(vec.end(), vec2.begin(), vec2.end()); +} + + template <class S, class Predicate> inline void set_remove_if(S& set, Predicate p) { @@ -196,6 +206,7 @@ template<class T, class Arg1, class Arg2, class Arg3> inline std::u template<class T, class Arg1, class Arg2, class Arg3, class Arg4> inline std::unique_ptr<T> make_unique(Arg1&& arg1, Arg2&& arg2, Arg3&& arg3, Arg4&& arg4) { return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3), std::forward<Arg4>(arg4))); } template<class T, class Arg1, class Arg2, class Arg3, class Arg4, class Arg5> inline std::unique_ptr<T> make_unique(Arg1&& arg1, Arg2&& arg2, Arg3&& arg3, Arg4&& arg4, Arg5&& arg5) { return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3), std::forward<Arg4>(arg4), std::forward<Arg5>(arg5))); } template<class T, class Arg1, class Arg2, class Arg3, class Arg4, class Arg5, class Arg6> inline std::unique_ptr<T> make_unique(Arg1&& arg1, Arg2&& arg2, Arg3&& arg3, Arg4&& arg4, Arg5&& arg5, Arg6&& arg6) { return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3), std::forward<Arg4>(arg4), std::forward<Arg5>(arg5), std::forward<Arg6>(arg6))); } +template<class T, class Arg1, class Arg2, class Arg3, class Arg4, class Arg5, class Arg6, class Arg7> inline std::unique_ptr<T> make_unique(Arg1&& arg1, Arg2&& arg2, Arg3&& arg3, Arg4&& arg4, Arg5&& arg5, Arg6&& arg6, Arg7&& arg7) { return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3), std::forward<Arg4>(arg4), std::forward<Arg5>(arg5), std::forward<Arg6>(arg6), std::forward<Arg7>(arg7))); } //template<typename T, typename ...Args> inline //std::unique_ptr<T> make_unique(Args&& ...args) diff --git a/zen/string_base.h b/zen/string_base.h index c828b240..0e3bbdd3 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -10,8 +10,8 @@ #include <algorithm> #include <cassert> #include <cstdint> +#include <atomic> #include "string_tools.h" -#include <boost/detail/atomic_count.hpp> //Zbase - a policy based string class optimizing performance and flexibility @@ -168,15 +168,14 @@ protected: private: struct Descriptor { - Descriptor(long rc, size_t len, size_t cap) : - refCount(rc), + Descriptor(int rc, size_t len, size_t cap) : length (static_cast<std::uint32_t>(len)), - capacity(static_cast<std::uint32_t>(cap)) {} + capacity(static_cast<std::uint32_t>(cap)), + refCount(rc) { assert_static(ATOMIC_INT_LOCK_FREE == 2); } //2: "the types are always lock-free" - boost::detail::atomic_count refCount; //practically no perf loss: ~0.2%! (FFS comparison) - //replace by #include <atomic> std::atomic_int when finally getting rid of VS2010 std::uint32_t length; std::uint32_t capacity; //allocated size without null-termination + std::atomic<int> refCount; //practically no perf loss: ~0.2%! (FFS comparison) }; static Descriptor* descr( Char* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; } diff --git a/zen/thread.h b/zen/thread.h index 1fa2c951..5c6eecc5 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -184,7 +184,7 @@ template <class T> template <class Fun> inline void RunUntilFirstHit<T>::addJob(Fun f) //f must return a std::unique_ptr<T> containing a value on success { - auto result2 = result; //MSVC2010: this is ridiculous!!! + auto result2 = result; //MSVC 2010: this is ridiculous!!! boost::thread t([result2, f] { result2->reportFinished(f()); }); ++jobsTotal; t.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 83eb5127..c33ea707 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -17,7 +17,7 @@ #endif #ifndef NDEBUG -#include "thread.h" //includes <boost/thread.hpp> +#include <mutex> #include <iostream> #endif @@ -32,14 +32,14 @@ class LeakChecker //small test for memory leaks public: void insert(const void* ptr, size_t size) { - boost::lock_guard<boost::mutex> dummy(lockActStrings); + std::lock_guard<std::mutex> dummy(lockActStrings); if (!activeStrings.insert(std::make_pair(ptr, size)).second) reportProblem("Serious Error: New memory points into occupied space: " + rawMemToString(ptr, size)); } void remove(const void* ptr) { - boost::lock_guard<boost::mutex> dummy(lockActStrings); + std::lock_guard<std::mutex> dummy(lockActStrings); if (activeStrings.erase(ptr) != 1) reportProblem("Serious Error: No memory available for deallocation at this location!"); } @@ -89,7 +89,7 @@ private: throw std::logic_error("Memory leak! " + message); } - boost::mutex lockActStrings; + std::mutex lockActStrings; zen::hash_map<const void*, size_t> activeStrings; }; |