Mercurial > minori
diff src/core/filesystem.cc @ 401:2f89797b6a44
filesystem: add linux inotify watcher
I don't really like this, but eh
| author | Paper <paper@tflc.us> |
|---|---|
| date | Fri, 07 Nov 2025 18:28:36 -0500 |
| parents | a0bc3ae5164a |
| children | d859306e2db4 |
line wrap: on
line diff
--- a/src/core/filesystem.cc Fri Nov 07 15:40:56 2025 -0500 +++ b/src/core/filesystem.cc Fri Nov 07 18:28:36 2025 -0500 @@ -8,8 +8,15 @@ #ifdef WIN32 # include <windows.h> +#elif defined(linux) +/* ehhhh */ +# include <fcntl.h> +# include <unistd.h> +# include <sys/inotify.h> #endif +#include <iostream> + namespace Filesystem { /* this runs fs::create_directories() on the @@ -361,6 +368,222 @@ }; using DefaultWatcher = Win32WatcherVista; +#elif defined(__linux__) +/* Inotify watcher */ +class InotifyWatcher : public StdFilesystemWatcher { +public: + InotifyWatcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive) + : StdFilesystemWatcher(opaque, path, handler, recursive) + , first_(true) + , giveup_(false) + , ev_(nullptr) + , ev_size_(0) + { + } + + virtual ~InotifyWatcher() override + { + /* We don't need to free our watch descriptors; + * they are automatically free'd up by the kernel */ + std::free(ev_); + } + + virtual void Process() override + { + if (giveup_) { + StdFilesystemWatcher::Process(); + return; + } + + if (first_) { + /* Try creating the file descriptor */ + if (TryCreateFd()) { + /* Add toplevel directory */ + AddWatchDescriptor(Watcher::path_); + + /* Yay, we don't need to keep the dir structure in memory */ + IterateDirectory(Watcher::path_, recursive_, [this](const std::filesystem::path &p) { + /* hmm, we're stat'ing the file twice now... */ + if (std::filesystem::is_directory(p)) + AddWatchDescriptor(p); + handler_(opaque_, p, Event::Created); + }); + } else { + /* Uh oh */ + giveup_ = true; + } + + if (giveup_) { + /* ;p */ + StdFilesystemWatcher::Process(); + return; + } + + first_ = false; + return; + } + + if (!fd_) { + /* oh what the hell */ + giveup_ = true; + StdFilesystemWatcher::Process(); + return; + } + + /* Read in everything */ + for (;;) { + int r; + do { + ev_size_ = (ev_size_) ? (ev_size_ * 2) : (sizeof(struct inotify_event) + 4096); + ev_ = reinterpret_cast<struct inotify_event *>(std::realloc(ev_, ev_size_)); + if (!ev_) { + /* uh oh */ + ev_size_ = 0; + return; + } + + r = read(fd_.get(), ev_, ev_size_); + } while (r == 0 || (r < 0 && errno == EINVAL)); + + if (r < 0 && errno == EAGAIN) { + /* No more events to process */ + break; + } + + for (int i = 0; i < r; /* ok */) { + struct inotify_event *ev = reinterpret_cast<struct inotify_event *>(reinterpret_cast<char *>(ev_) + i); + + if (ev->mask & (IN_DELETE_SELF)) { + /* Watched directory has been deleted */ + RemoveWatchDescriptor(ev->wd); + continue; + } + + if (ev->mask & (IN_MOVE|IN_CREATE|IN_DELETE)) { + std::filesystem::path p = wds_[ev->wd] / ev->name; + + if (ev->mask & (IN_MOVED_TO|IN_CREATE)) { + if (std::filesystem::is_directory(p)) + AddWatchDescriptor(p); + handler_(opaque_, p, Event::Created); + } else if (ev->mask & (IN_MOVED_FROM|IN_DELETE)) { + + handler_(opaque_, wds_[ev->wd] / ev->name, Event::Deleted); + } + } + + i += sizeof(inotify_event) + ev->len; + } + } + } + +protected: + /* File descriptor helper. Mostly follows std::unique_ptr, + * but has a function for toggling non-blocking */ + struct FileDescriptor { + public: + FileDescriptor() : fd_(-1) { } + ~FileDescriptor() { reset(); } + + int get() { return fd_; } + operator bool() { return (fd_ != -1); } + void reset(int fd = -1) + { + /* Close anything we already have */ + if (fd_ != -1) + ::close(fd_); + /* Put the new one in */ + fd_ = fd; + } + + bool SetNonBlocking(bool on) + { + if (fd_ < 0) + return false; + + int x = fcntl(fd_, F_GETFL); + if (x < 0) + return false; + + if (on) { + x |= O_NONBLOCK; + } else { + x &= ~O_NONBLOCK; + } + + int r = fcntl(fd_, F_SETFL, x); + if (r < 0) + return false; + + return true; + } + + private: + int fd_; + }; + + bool TryCreateFd() + { + if (giveup_) + return false; + + if (!fd_) { +#ifdef HAVE_INOTIFY_INIT1 + fd_.reset(inotify_init1(IN_NONBLOCK)); +#else + fd_.reset(inotify_init()); +#endif + if (!fd_) + return false; + +#ifndef HAVE_INOTIFY_INIT1 + /* Very old linux */ + if (!fd_.SetNonBlocking(true)) + return false; +#endif + } + + return !!fd_; + } + + bool AddWatchDescriptor(const std::filesystem::path &p) + { + if (!fd_ || giveup_) + return false; + + int wd = inotify_add_watch(fd_.get(), p.string().c_str(), IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE_SELF); + if (wd < 0) { + /* Don't even try to watch any more */ + giveup_ = true; + return false; + } + + /* Add to our list; these IDs (should be) unique */ + wds_[wd] = p; + return true; + } + + void RemoveWatchDescriptor(int wd) + { + inotify_rm_watch(fd_.get(), wd); + wds_.erase(wd); + } + + /* variables */ + FileDescriptor fd_; + std::unordered_map<int, std::filesystem::path> wds_; /* watch descriptors */ + bool first_; + + /* set this variable if we've completely run out of + * resources (watch descriptors) and need to fall + * back to std::filesystem or fear ultimate damage */ + bool giveup_; + + struct inotify_event *ev_; + std::size_t ev_size_; +}; + +using DefaultWatcher = InotifyWatcher; #else using DefaultWatcher = StdFilesystemWatcher; #endif
