diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:28:01 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:28:01 +0200 |
commit | fe9eb89ebc1b3c33cbac00a3fa095a14faef9113 (patch) | |
tree | 8a3bb620a9acb83fe0057061a86e8f2cb91a9fe1 /zen/dir_watcher.cpp | |
parent | 5.21 (diff) | |
download | FreeFileSync-fe9eb89ebc1b3c33cbac00a3fa095a14faef9113.tar.gz FreeFileSync-fe9eb89ebc1b3c33cbac00a3fa095a14faef9113.tar.bz2 FreeFileSync-fe9eb89ebc1b3c33cbac00a3fa095a14faef9113.zip |
5.22
Diffstat (limited to 'zen/dir_watcher.cpp')
-rw-r--r-- | zen/dir_watcher.cpp | 236 |
1 files changed, 188 insertions, 48 deletions
diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index e53b63e2..17efda00 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -21,7 +21,10 @@ #include "file_traverser.h" #elif defined ZEN_MAC -//#include <CoreFoundation/FSEvents.h> +//#include <sys/types.h> +#include <sys/event.h> +//#include <sys/time.h> +#include "file_traverser.h" #endif using namespace zen; @@ -39,7 +42,7 @@ public: boost::lock_guard<boost::mutex> dummy(lockAccess); if (bytesWritten == 0) //according to docu this may happen in case of internal buffer overflow: report some "dummy" change - changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_CREATE, L"Overflow!")); + changedFiles.push_back(DirWatcher::Entry(DirWatcher::ACTION_CREATE, L"Overflow.")); else { const char* bufPos = &buffer[0]; @@ -49,13 +52,10 @@ public: const Zstring fullname = dirname + Zstring(notifyInfo.FileName, notifyInfo.FileNameLength / sizeof(WCHAR)); - //skip modifications sent by changed directories: reason for change, child element creation/deletion, will notify separately! - //and if this child element is a .ffs_lock file we'll want to ignore all associated events! [&] { - //if (notifyInfo.Action == FILE_ACTION_RENAMED_OLD_NAME) //reporting FILE_ACTION_RENAMED_NEW_NAME should suffice; - // return; //note: this is NOT a cross-directory move, which will show up as create + delete - + //skip modifications sent by changed directories: reason for change, child element creation/deletion, will notify separately! + //and if this child element is a .ffs_lock file we'll want to ignore all associated events! if (notifyInfo.Action == FILE_ACTION_MODIFIED) { //note: this check will not work if top watched directory has been renamed @@ -64,6 +64,7 @@ public: return; } + //note: a move across directories will show up as FILE_ACTION_ADDED/FILE_ACTION_REMOVED! switch (notifyInfo.Action) { case FILE_ACTION_ADDED: @@ -156,12 +157,7 @@ public: FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr); if (hDir == INVALID_HANDLE_VALUE) - { - const DWORD lastError = ::GetLastError(); //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)); - const std::wstring errorDescr = formatSystemError(L"CreateFile", lastError); - throw FileError(errorMsg, errorDescr); - } + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), formatSystemError(L"CreateFile", getLastError())); //end of constructor, no need to start managing "hDir" } @@ -374,14 +370,6 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() #elif defined ZEN_LINUX -struct DirWatcher::Pimpl -{ - Zstring dirname; - int notifDescr; - std::map<int, Zstring> watchDescrs; //watch descriptor and corresponding (sub-)directory name (postfixed with separator!) -}; - - namespace { class DirsOnlyTraverser : public zen::TraverseCallback @@ -396,8 +384,8 @@ public: dirs_.push_back(fullName); return this; } - virtual HandleError reportDirError (const std::wstring& msg) { throw FileError(msg); } - virtual HandleError reportItemError(const std::wstring& msg, const Zchar* shortName) { throw FileError(msg); } + 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_; @@ -405,26 +393,33 @@ private: } +struct DirWatcher::Pimpl +{ + Zstring baseDirname; + int notifDescr; + std::map<int, Zstring> watchDescrs; //watch descriptor and (sub-)directory name (postfixed with separator) -> owned by "notifDescr" +}; + + DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError pimpl_(new Pimpl) { - //still in main thread + //get all subdirectories Zstring dirname = directory; if (endsWith(dirname, FILE_NAME_SEPARATOR)) dirname.resize(dirname.size() - 1); - //get all subdirectories - std::vector<Zstring> fullDirList; - fullDirList.push_back(dirname); - - DirsOnlyTraverser traverser(fullDirList); //throw FileError - zen::traverseFolder(dirname, traverser); //don't traverse into symlinks (analog to windows build) + std::vector<Zstring> fullDirList { dirname }; + { + DirsOnlyTraverser traverser(fullDirList); //throw FileError + zen::traverseFolder(dirname, traverser); //don't traverse into symlinks (analog to windows build) + } //init - pimpl_->dirname = directory; + pimpl_->baseDirname = directory; pimpl_->notifDescr = ::inotify_init(); if (pimpl_->notifDescr == -1) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirname)), formatSystemError(L"inotify_init", getLastError())); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), formatSystemError(L"inotify_init", getLastError())); zen::ScopeGuard guardDescr = zen::makeGuard([&] { ::close(pimpl_->notifDescr); }); @@ -436,14 +431,13 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1; } if (!initSuccess) - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirname)), formatSystemError(L"fcntl", getLastError())); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(directory)), formatSystemError(L"fcntl", getLastError())); //add watches - std::for_each(fullDirList.begin(), fullDirList.end(), - [&](Zstring subdir) + for (const Zstring& subdir : fullDirList) { int wd = ::inotify_add_watch(pimpl_->notifDescr, subdir.c_str(), - IN_ONLYDIR | //watch directories only + IN_ONLYDIR | //"Only watch pathname if it is a directory." IN_DONT_FOLLOW | //don't follow symbolic links IN_CREATE | IN_MODIFY | @@ -454,15 +448,10 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError IN_MOVED_TO | IN_MOVE_SELF); if (wd == -1) - { - const ErrorCode lastError = getLastError(); //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subdir)); - const std::wstring errorDescr = formatSystemError(L"inotify_add_watch", lastError); - throw FileError(errorMsg, errorDescr); - } + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subdir)), formatSystemError(L"inotify_add_watch", getLastError())); pimpl_->watchDescrs.insert(std::make_pair(wd, appendSeparator(subdir))); - }); + } guardDescr.dismiss(); } @@ -476,12 +465,12 @@ DirWatcher::~DirWatcher() std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError { - //non-blocking call, see O_NONBLOCK - std::vector<char> buffer(1024 * (sizeof(struct ::inotify_event) + 16)); + std::vector<char> buffer(512 * (sizeof(struct ::inotify_event) + NAME_MAX + 1)); ssize_t bytesRead = 0; do { + //non-blocking call, see O_NONBLOCK bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size()); } while (bytesRead < 0 && errno == EINTR); //"Interrupted function call; When this happens, you should try the call again." @@ -491,7 +480,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>(); - throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->dirname)), formatSystemError(L"read", getLastError())); + throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->baseDirname)), formatSystemError(L"read", getLastError())); } std::vector<Entry> output; @@ -530,27 +519,178 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() } #elif defined ZEN_MAC +namespace +{ +class DirsOnlyTraverser : public zen::TraverseCallback +{ +public: + DirsOnlyTraverser(std::vector<Zstring>& dirs) : dirs_(dirs) {} + + 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) + { + dirs_.push_back(fullName); + return this; + } + 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; }; -DirWatcher::DirWatcher(const Zstring& directory) //throw FileError +DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError + pimpl_(new Pimpl) { - throw FileError(L"Dir Watcher is not yet implemented!"); + //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(); } DirWatcher::~DirWatcher() { + ::close(pimpl_->queueDescr); } std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError { 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."); + + //"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; } #endif |