|
|
- /**
- * @file Inotify.h
- * @author Erik Zenker
- * @date 02.11.2012
- * @copyright Gnu Public License
- **/
- #pragma once
- #include <sys/inotify.h>
- #include <string>
- #include <queue>
- #include <map>
- #include <vector>
- #include <assert.h>
- #include <unistd.h>
- #include <errno.h>
- #include <time.h>
- #include <poll.h>
- #include <errno.h>
- #include <string>
- #include <exception>
- #include <sstream>
- #include <experimental/filesystem>
- #include <experimental/optional>
-
- #include <FileSystemEvent.h>
-
- #define MAX_EVENTS 4096
- #define EVENT_SIZE (sizeof (inotify_event))
- #define EVENT_BUF_LEN (MAX_EVENTS * (EVENT_SIZE + 16))
-
- namespace fs = std::experimental::filesystem;
-
- /**
- * @brief C++ wrapper for linux inotify interface
- * @class Inotify
- * Inotify.h
- * "include/Inotify.h"
- *
- * folders will be watched by watchFolderRecursively or
- * files by watchFile. If there are changes inside this
- * folder or files events will be raised. This events
- * can be get by getNextEvent.
- *
- * @eventMask
- *
- * IN_ACCESS File was accessed (read) (*).
- * IN_ATTRIB Metadata changed—for example, permissions,
- * timestamps, extended attributes, link count
- * (since Linux 2.6.25), UID, or GID. (*).
- * IN_CLOSE_WRITE File opened for writing was closed (*).
- * IN_CLOSE_NOWRITE File not opened for writing was closed (*).
- * IN_CREATE File/directory created in watched directory(*).
- * IN_DELETE File/directory deleted from watched directory(*).
- * IN_DELETE_SELF Watched file/directory was itself deleted.
- * IN_MODIFY File was modified (*).
- * IN_MOVE_SELF Watched file/directory was itself moved.
- * IN_MOVED_FROM Generated for the directory containing the old
- * filename when a file is renamed (*).
- * IN_MOVED_TO Generated for the directory containing the new
- * filename when a file is renamed (*).
- * IN_OPEN File was opened (*).
- * IN_ALL_EVENTS macro is defined as a bit mask of all of the above
- * events
- * IN_MOVE IN_MOVED_FROM|IN_MOVED_TO
- * IN_CLOSE IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
- *
- * See inotify manpage for more event details
- *
- */
- class Inotify {
- public:
- Inotify();
- Inotify(uint32_t eventMask);
- Inotify(std::vector< std::string> ignoredDirectories, unsigned eventTimeout, uint32_t eventMask);
- ~Inotify();
- void watchDirectoryRecursively(fs::path path);
- void watchFile(fs::path file, std::optional<fs::path> recurseRoot = std::nullopt);
- void ignoreFileOnce(fs::path file);
- FileSystemEvent getNextEvent();
- std::optional<FileSystemEvent> nb_getNextEvent();
- int getLastErrno();
-
- private:
- fs::path wdToPath(int wd);
- bool isIgnored(std::string file);
- bool onTimeout(time_t eventTime);
- void removeWatch(int wd); // TODO
- void init();
-
- // Member
- int mError;
- time_t mEventTimeout;
- time_t mLastEventTime;
- uint32_t mEventMask;
- std::vector<std::string> mIgnoredDirectories;
- std::vector<std::string> mOnceIgnoredDirectories;
- std::queue<FileSystemEvent> mEventQueue;
- std::map<int, fs::path> mDirectorieMap;
- std::map<int, fs::path> mRecurseMap;
- int mInotifyFd;
-
-
- };
-
-
- inline Inotify::Inotify() :
- mError(0),
- mEventTimeout(0),
- mLastEventTime(0),
- mEventMask(IN_ALL_EVENTS),
- mIgnoredDirectories(std::vector<std::string>()),
- mInotifyFd(0){
-
- // Initialize inotify
- init();
- }
-
- inline Inotify::Inotify(uint32_t eventMask) :
- mError(0),
- mEventTimeout(0),
- mLastEventTime(0),
- mEventMask(eventMask),
- mIgnoredDirectories(std::vector<std::string>()),
- mInotifyFd(0){
-
- // Initialize inotify
- init();
- }
-
- inline Inotify::Inotify(std::vector<std::string> ignoredDirectories, unsigned eventTimeout, uint32_t eventMask) :
- mError(0),
- mEventTimeout(eventTimeout),
- mLastEventTime(0),
- mEventMask(eventMask),
- mIgnoredDirectories(ignoredDirectories),
- mInotifyFd(0){
-
- // Initialize inotify
- init();
- }
-
- inline Inotify::~Inotify(){
- if(!close(mInotifyFd)){
- mError = errno;
- }
-
- }
-
- inline void Inotify::init(){
- mInotifyFd = inotify_init();
- if(mInotifyFd == -1){
- mError = errno;
- std::stringstream errorStream;
- errorStream << "Can't initialize inotify ! " << strerror(mError) << ".";
- throw std::runtime_error(errorStream.str());
- }
-
- }
-
- /**
- * @brief Adds the given path and all files and subdirectories
- * to the set of watched files/directories.
- * Symlinks will be followed!
- *
- * @param path that will be watched recursively
- *
- */
- inline void Inotify::watchDirectoryRecursively(fs::path path){
- if(fs::exists(path)){
- if(fs::is_directory(path)){
- fs::recursive_directory_iterator it(path, fs::directory_options::follow_directory_symlink);
- fs::recursive_directory_iterator end;
-
- while(it != end){
- fs::path currentPath = *it;
-
- if(fs::is_directory(currentPath)){
- watchFile(currentPath, path);
- }
- if(fs::is_symlink(currentPath)){
- watchFile(currentPath, path);
- }
- ++it;
-
- }
-
- }
- watchFile(path);
- }
- else {
- throw std::invalid_argument("Can´t watch Path! Path does not exist. Path: " + path.string());
- }
-
- }
-
- /**
- * @brief Adds a single file/directorie to the list of
- * watches. Path and corresponding watchdescriptor
- * will be stored in the directorieMap. This is done
- * because events on watches just return this
- * watchdescriptor.
- *
- * @param path that will be watched
- *
- */
- inline void Inotify::watchFile(fs::path filePath, std::optional<fs::path> recurseRoot) {
- if(fs::exists(filePath)){
- mError = 0;
- int wd = 0;
- if(!isIgnored(filePath.string())){
- wd = inotify_add_watch(mInotifyFd, filePath.string().c_str(), mEventMask);
- }
-
- if(wd == -1){
- mError = errno;
- std::stringstream errorStream;
- if(mError == 28){
- errorStream << "Failed to watch! " << strerror(mError) << ". Please increase number of watches in \"/proc/sys/fs/inotify/max_user_watches\".";
- throw std::runtime_error(errorStream.str());
- }
-
- errorStream << "Failed to watch! " << strerror(mError) << ". Path: " << filePath.string();
- throw std::runtime_error(errorStream.str());
- }
- mDirectorieMap[wd] = filePath;
- if (recurseRoot)
- mRecurseMap[wd] = *recurseRoot;
- }
-
- }
-
-
- inline void Inotify::ignoreFileOnce(fs::path file){
- mOnceIgnoredDirectories.push_back(file.string());
-
- }
-
- /**
- * @brief Removes watch from set of watches. This
- * is not done recursively!
- *
- * @param wd watchdescriptor
- *
- */
- inline void Inotify::removeWatch(int wd){
- int result = inotify_rm_watch(mInotifyFd, wd);
- if(result == -1){
- mError = errno;
- std::stringstream errorStream;
- errorStream << "Failed to remove watch! " << strerror(mError) << ".";
- throw std::runtime_error(errorStream.str());
- }
- mDirectorieMap.erase(wd);
- }
-
-
- inline fs::path Inotify::wdToPath(int wd){
- return mDirectorieMap[wd];
-
- }
-
- /**
- * @brief Blocking wait on new events of watched files/directories
- * specified on the eventmask. FileSystemEvents
- * will be returned one by one. Thus this
- * function can be called in some while(true)
- * loop.
- *
- * @return A new FileSystemEvent
- *
- */
- inline FileSystemEvent Inotify::getNextEvent(){
- int length = 0;
- char buffer[EVENT_BUF_LEN];
- time_t currentEventTime = time(NULL);
- std::vector<FileSystemEvent> events;
-
- // Read Events from fd into buffer
- while(mEventQueue.empty()){
- length = 0;
- memset(&buffer, 0, EVENT_BUF_LEN);
- while(length <= 0 ){
- length = read(mInotifyFd, buffer, EVENT_BUF_LEN);
- currentEventTime = time(NULL);
- if(length == -1){
- mError = errno;
- if(mError != EINTR){
- continue;
-
- }
-
- }
-
- }
-
- // Read events from buffer into queue
- currentEventTime = time(NULL);
- int i = 0;
- while(i < length){
- inotify_event *event = ((struct inotify_event*) &buffer[i]);
- fs::path path(wdToPath(event->wd) / std::string(event->name));
- if(fs::is_directory(path)){
- event->mask |= IN_ISDIR;
- }
- FileSystemEvent fsEvent(event->wd, event->mask, path);
-
- if(!fsEvent.path.empty()){
- events.push_back(fsEvent);
-
- }
- else{
- // Event is not complete --> ignore
- }
-
- i += EVENT_SIZE + event->len;
-
- }
-
-
- // Filter events
- for(auto eventIt = events.begin(); eventIt < events.end(); ++eventIt){
- FileSystemEvent currentEvent = *eventIt;
- if(onTimeout(currentEventTime)){
- events.erase(eventIt);
-
- }
- else if(isIgnored(currentEvent.path.string())){
- events.erase(eventIt);
- }
- else{
- mLastEventTime = currentEventTime;
- mEventQueue.push(currentEvent);
- }
-
- }
-
- }
-
- // Return next event
- FileSystemEvent event = mEventQueue.front();
- mEventQueue.pop();
- return event;
- }
-
- std::optional<FileSystemEvent> Inotify::nb_getNextEvent() {
- char buffer[EVENT_BUF_LEN]
- __attribute__ ((aligned(__alignof__(struct inotify_event))));
- std::vector<FileSystemEvent> events;
-
- struct pollfd fd = {.fd = mInotifyFd, .events = POLLIN, 0};
- int pn = poll(&fd, 1, 0); // nonblocking poll(2)
- if (pn == -1 && errno != EINTR) {
- perror("poll()");
- exit(EXIT_FAILURE);
- }
-
- if (pn > 0) {
- ssize_t len = read(mInotifyFd, buffer, sizeof buffer);
- if (len <= 0)
- return std::nullopt;
-
- struct inotify_event *event;
- for (char *ptr = buffer; ptr < buffer + len;
- ptr += sizeof(struct inotify_event) + event->len) {
- event = (struct inotify_event*) ptr;
-
- fs::path path(wdToPath(event->wd) / std::string(event->name));
- if (fs::is_directory(path))
- event->mask |= IN_ISDIR;
-
- FileSystemEvent fsEvent(event->wd, event->mask, path);
-
- if (mRecurseMap.find(event->wd) != mRecurseMap.end()) {
- fsEvent.recursive_root_path = mRecurseMap[event->wd];
- }
-
- if (!fsEvent.path.empty())
- events.push_back(fsEvent);
- }
-
- for (auto eventIt = events.begin(); eventIt < events.end(); ++eventIt){
- FileSystemEvent currentEvent = *eventIt;
- if (isIgnored(currentEvent.path.string())) {
- events.erase(eventIt);
- } else {
- mEventQueue.push(currentEvent);
- }
- }
- }
-
- if (mEventQueue.empty())
- return std::nullopt;
-
- auto event = mEventQueue.front(); mEventQueue.pop();
- return event;
- }
-
- inline int Inotify::getLastErrno(){
- return mError;
-
- }
-
- inline bool Inotify::isIgnored(std::string file){
- if(mIgnoredDirectories.empty() and mOnceIgnoredDirectories.empty()){
- return false;
- }
-
- for(unsigned i = 0; i < mOnceIgnoredDirectories.size(); ++i){
- size_t pos = file.find(mOnceIgnoredDirectories[i]);
- if(pos!= std::string::npos){
- mOnceIgnoredDirectories.erase(mOnceIgnoredDirectories.begin() + i);
- return true;
- }
- }
-
- for(unsigned i = 0; i < mIgnoredDirectories.size(); ++i){
- size_t pos = file.find(mIgnoredDirectories[i]);
- if(pos!= std::string::npos){
- return true;
- }
- }
-
- return false;
- }
-
- inline bool Inotify::onTimeout(time_t eventTime){
- return (mLastEventTime + mEventTimeout) > eventTime;
- }
-
|