comparison 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
comparison
equal deleted inserted replaced
400:2f4dc1580b84 401:2f89797b6a44
6 6
7 #include <filesystem> 7 #include <filesystem>
8 8
9 #ifdef WIN32 9 #ifdef WIN32
10 # include <windows.h> 10 # include <windows.h>
11 #elif defined(linux)
12 /* ehhhh */
13 # include <fcntl.h>
14 # include <unistd.h>
15 # include <sys/inotify.h>
11 #endif 16 #endif
17
18 #include <iostream>
12 19
13 namespace Filesystem { 20 namespace Filesystem {
14 21
15 /* this runs fs::create_directories() on the 22 /* this runs fs::create_directories() on the
16 PARENT directory. */ 23 PARENT directory. */
359 OVERLAPPED overlapped_; 366 OVERLAPPED overlapped_;
360 alignas(FILE_NOTIFY_INFORMATION) char change_buf_[4096]; 367 alignas(FILE_NOTIFY_INFORMATION) char change_buf_[4096];
361 }; 368 };
362 369
363 using DefaultWatcher = Win32WatcherVista; 370 using DefaultWatcher = Win32WatcherVista;
371 #elif defined(__linux__)
372 /* Inotify watcher */
373 class InotifyWatcher : public StdFilesystemWatcher {
374 public:
375 InotifyWatcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive)
376 : StdFilesystemWatcher(opaque, path, handler, recursive)
377 , first_(true)
378 , giveup_(false)
379 , ev_(nullptr)
380 , ev_size_(0)
381 {
382 }
383
384 virtual ~InotifyWatcher() override
385 {
386 /* We don't need to free our watch descriptors;
387 * they are automatically free'd up by the kernel */
388 std::free(ev_);
389 }
390
391 virtual void Process() override
392 {
393 if (giveup_) {
394 StdFilesystemWatcher::Process();
395 return;
396 }
397
398 if (first_) {
399 /* Try creating the file descriptor */
400 if (TryCreateFd()) {
401 /* Add toplevel directory */
402 AddWatchDescriptor(Watcher::path_);
403
404 /* Yay, we don't need to keep the dir structure in memory */
405 IterateDirectory(Watcher::path_, recursive_, [this](const std::filesystem::path &p) {
406 /* hmm, we're stat'ing the file twice now... */
407 if (std::filesystem::is_directory(p))
408 AddWatchDescriptor(p);
409 handler_(opaque_, p, Event::Created);
410 });
411 } else {
412 /* Uh oh */
413 giveup_ = true;
414 }
415
416 if (giveup_) {
417 /* ;p */
418 StdFilesystemWatcher::Process();
419 return;
420 }
421
422 first_ = false;
423 return;
424 }
425
426 if (!fd_) {
427 /* oh what the hell */
428 giveup_ = true;
429 StdFilesystemWatcher::Process();
430 return;
431 }
432
433 /* Read in everything */
434 for (;;) {
435 int r;
436 do {
437 ev_size_ = (ev_size_) ? (ev_size_ * 2) : (sizeof(struct inotify_event) + 4096);
438 ev_ = reinterpret_cast<struct inotify_event *>(std::realloc(ev_, ev_size_));
439 if (!ev_) {
440 /* uh oh */
441 ev_size_ = 0;
442 return;
443 }
444
445 r = read(fd_.get(), ev_, ev_size_);
446 } while (r == 0 || (r < 0 && errno == EINVAL));
447
448 if (r < 0 && errno == EAGAIN) {
449 /* No more events to process */
450 break;
451 }
452
453 for (int i = 0; i < r; /* ok */) {
454 struct inotify_event *ev = reinterpret_cast<struct inotify_event *>(reinterpret_cast<char *>(ev_) + i);
455
456 if (ev->mask & (IN_DELETE_SELF)) {
457 /* Watched directory has been deleted */
458 RemoveWatchDescriptor(ev->wd);
459 continue;
460 }
461
462 if (ev->mask & (IN_MOVE|IN_CREATE|IN_DELETE)) {
463 std::filesystem::path p = wds_[ev->wd] / ev->name;
464
465 if (ev->mask & (IN_MOVED_TO|IN_CREATE)) {
466 if (std::filesystem::is_directory(p))
467 AddWatchDescriptor(p);
468 handler_(opaque_, p, Event::Created);
469 } else if (ev->mask & (IN_MOVED_FROM|IN_DELETE)) {
470
471 handler_(opaque_, wds_[ev->wd] / ev->name, Event::Deleted);
472 }
473 }
474
475 i += sizeof(inotify_event) + ev->len;
476 }
477 }
478 }
479
480 protected:
481 /* File descriptor helper. Mostly follows std::unique_ptr,
482 * but has a function for toggling non-blocking */
483 struct FileDescriptor {
484 public:
485 FileDescriptor() : fd_(-1) { }
486 ~FileDescriptor() { reset(); }
487
488 int get() { return fd_; }
489 operator bool() { return (fd_ != -1); }
490 void reset(int fd = -1)
491 {
492 /* Close anything we already have */
493 if (fd_ != -1)
494 ::close(fd_);
495 /* Put the new one in */
496 fd_ = fd;
497 }
498
499 bool SetNonBlocking(bool on)
500 {
501 if (fd_ < 0)
502 return false;
503
504 int x = fcntl(fd_, F_GETFL);
505 if (x < 0)
506 return false;
507
508 if (on) {
509 x |= O_NONBLOCK;
510 } else {
511 x &= ~O_NONBLOCK;
512 }
513
514 int r = fcntl(fd_, F_SETFL, x);
515 if (r < 0)
516 return false;
517
518 return true;
519 }
520
521 private:
522 int fd_;
523 };
524
525 bool TryCreateFd()
526 {
527 if (giveup_)
528 return false;
529
530 if (!fd_) {
531 #ifdef HAVE_INOTIFY_INIT1
532 fd_.reset(inotify_init1(IN_NONBLOCK));
533 #else
534 fd_.reset(inotify_init());
535 #endif
536 if (!fd_)
537 return false;
538
539 #ifndef HAVE_INOTIFY_INIT1
540 /* Very old linux */
541 if (!fd_.SetNonBlocking(true))
542 return false;
543 #endif
544 }
545
546 return !!fd_;
547 }
548
549 bool AddWatchDescriptor(const std::filesystem::path &p)
550 {
551 if (!fd_ || giveup_)
552 return false;
553
554 int wd = inotify_add_watch(fd_.get(), p.string().c_str(), IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE_SELF);
555 if (wd < 0) {
556 /* Don't even try to watch any more */
557 giveup_ = true;
558 return false;
559 }
560
561 /* Add to our list; these IDs (should be) unique */
562 wds_[wd] = p;
563 return true;
564 }
565
566 void RemoveWatchDescriptor(int wd)
567 {
568 inotify_rm_watch(fd_.get(), wd);
569 wds_.erase(wd);
570 }
571
572 /* variables */
573 FileDescriptor fd_;
574 std::unordered_map<int, std::filesystem::path> wds_; /* watch descriptors */
575 bool first_;
576
577 /* set this variable if we've completely run out of
578 * resources (watch descriptors) and need to fall
579 * back to std::filesystem or fear ultimate damage */
580 bool giveup_;
581
582 struct inotify_event *ev_;
583 std::size_t ev_size_;
584 };
585
586 using DefaultWatcher = InotifyWatcher;
364 #else 587 #else
365 using DefaultWatcher = StdFilesystemWatcher; 588 using DefaultWatcher = StdFilesystemWatcher;
366 #endif 589 #endif
367 590
368 IWatcher *GetRecursiveFilesystemWatcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler) 591 IWatcher *GetRecursiveFilesystemWatcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler)