Mercurial > minori
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) |
