comparison src/core/filesystem.cc @ 413:192da585a0a8 default tip

*: fix formatting the clang-format file was broken andjust using LLVM defaults. now it's fixed and is more in line with what I actually prefer my code to look like -- esp. with regards to tabulators
author Paper <paper@tflc.us>
date Sat, 25 Jul 2026 14:54:32 -0400
parents 8d06825d96d1
children
comparison
equal deleted inserted replaced
412:05aed03e0111 413:192da585a0a8
10 #ifdef WIN32 10 #ifdef WIN32
11 # include <windows.h> 11 # include <windows.h>
12 #elif defined(HAVE_INOTIFY) 12 #elif defined(HAVE_INOTIFY)
13 /* ehhhh */ 13 /* ehhhh */
14 # include <fcntl.h> 14 # include <fcntl.h>
15 # include <sys/inotify.h>
15 # include <unistd.h> 16 # include <unistd.h>
16 # include <sys/inotify.h>
17 #elif defined(__MACH__) && defined(__APPLE__) 17 #elif defined(__MACH__) && defined(__APPLE__)
18 # include <CoreFoundation/CoreFoundation.h> 18 # include <CoreFoundation/CoreFoundation.h>
19 # include <CoreServices/CoreServices.h> 19 # include <CoreServices/CoreServices.h>
20 # include <mutex> 20 # include <mutex>
21 # include <thread> 21 # include <thread>
78 /* This is the "base class" for basically every watcher. */ 78 /* This is the "base class" for basically every watcher. */
79 79
80 class Watcher : public IWatcher { 80 class Watcher : public IWatcher {
81 public: 81 public:
82 Watcher(void *opaque, const std::filesystem::path &path, EventHandler handler) 82 Watcher(void *opaque, const std::filesystem::path &path, EventHandler handler)
83 : path_(path), handler_(handler), opaque_(opaque) 83 : path_(path), handler_(handler), opaque_(opaque)
84 { 84 {
85 } 85 }
86 86
87 virtual ~Watcher() override {} 87 virtual ~Watcher() override {}
88 88
105 completely stops. I am dumbfounded at this behavior, but 105 completely stops. I am dumbfounded at this behavior, but
106 nevertheless it means we simply can't use it, and must 106 nevertheless it means we simply can't use it, and must
107 resort to old-fashioned recursion. --paper 107 resort to old-fashioned recursion. --paper
108 */ 108 */
109 static void IterateDirectory(const std::filesystem::path &path, bool recursive, 109 static void IterateDirectory(const std::filesystem::path &path, bool recursive,
110 const std::function<void(const std::filesystem::path &path)> &func) 110 const std::function<void(const std::filesystem::path &path)> &func)
111 { 111 {
112 std::error_code ec; 112 std::error_code ec;
113 static const std::filesystem::directory_iterator end; 113 static const std::filesystem::directory_iterator end;
114 114
115 for (std::filesystem::directory_iterator item(path, ec); item != end; item.increment(ec)) { 115 for (std::filesystem::directory_iterator item(path, ec); item != end; item.increment(ec)) {
128 } 128 }
129 129
130 class StdFilesystemWatcher : public Watcher { 130 class StdFilesystemWatcher : public Watcher {
131 public: 131 public:
132 StdFilesystemWatcher(void *opaque, const std::filesystem::path &path, EventHandler handler, bool recursive) 132 StdFilesystemWatcher(void *opaque, const std::filesystem::path &path, EventHandler handler, bool recursive)
133 : Watcher(opaque, path, handler), recursive_(recursive) 133 : Watcher(opaque, path, handler), recursive_(recursive)
134 { 134 {
135 } 135 }
136 136
137 virtual ~StdFilesystemWatcher() override {} 137 virtual ~StdFilesystemWatcher() override {}
138 138
203 /* On Windows, we can ask the OS whether the directory tree has changed or not. 203 /* On Windows, we can ask the OS whether the directory tree has changed or not.
204 * This is great for us! */ 204 * This is great for us! */
205 class Win32Watcher : public StdFilesystemWatcher { 205 class Win32Watcher : public StdFilesystemWatcher {
206 public: 206 public:
207 Win32Watcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive) 207 Win32Watcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive)
208 : StdFilesystemWatcher(opaque, path, handler, recursive), dirwatcher_(INVALID_HANDLE_VALUE), first_(true) 208 : StdFilesystemWatcher(opaque, path, handler, recursive), dirwatcher_(INVALID_HANDLE_VALUE), first_(true)
209 { 209 {
210 } 210 }
211 211
212 virtual ~Win32Watcher() override 212 virtual ~Win32Watcher() override
213 { 213 {
237 } 237 }
238 238
239 protected: 239 protected:
240 bool TryCreateDirWatcher() 240 bool TryCreateDirWatcher()
241 { 241 {
242 dirwatcher_ = FindFirstChangeNotificationW(Watcher::path_.wstring().c_str(), recursive_, 242 dirwatcher_ = FindFirstChangeNotificationW(
243 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME); 243 Watcher::path_.wstring().c_str(), recursive_, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME);
244 244
245 return (dirwatcher_ != INVALID_HANDLE_VALUE); 245 return (dirwatcher_ != INVALID_HANDLE_VALUE);
246 } 246 }
247 247
248 /* variables */ 248 /* variables */
256 * Anyway, this is a version that doesn't need to keep the whole directory 256 * Anyway, this is a version that doesn't need to keep the whole directory
257 * tree in-memory if it is available. */ 257 * tree in-memory if it is available. */
258 class Win32WatcherVista : public Win32Watcher { 258 class Win32WatcherVista : public Win32Watcher {
259 public: 259 public:
260 Win32WatcherVista(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive) 260 Win32WatcherVista(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive)
261 : Win32Watcher(opaque, path, handler, recursive), dirhandle_(INVALID_HANDLE_VALUE) 261 : Win32Watcher(opaque, path, handler, recursive), dirhandle_(INVALID_HANDLE_VALUE)
262 { 262 {
263 ZeroMemory(&overlapped_, sizeof(overlapped_)); 263 ZeroMemory(&overlapped_, sizeof(overlapped_));
264 overlapped_.hEvent = nullptr; 264 overlapped_.hEvent = nullptr;
265 } 265 }
266 266
283 283
284 /* Avoid running Win32Watcher::Process, as it will read the whole 284 /* Avoid running Win32Watcher::Process, as it will read the whole
285 * directory tree into memory. Instead, iterate through the directory 285 * directory tree into memory. Instead, iterate through the directory
286 * ourselves. */ 286 * ourselves. */
287 IterateDirectory(path_, recursive_, 287 IterateDirectory(path_, recursive_,
288 [this](const std::filesystem::path &p) { handler_(opaque_, p, Event::Created); }); 288 [this](const std::filesystem::path &p) { handler_(opaque_, p, Event::Created); });
289 } else { 289 } else {
290 /* Uh oh; we might have to fall back to Win32Watcher. Call into it to 290 /* Uh oh; we might have to fall back to Win32Watcher. Call into it to
291 * load the tree into memory. */ 291 * load the tree into memory. */
292 Win32Watcher::Process(); 292 Win32Watcher::Process();
293 } 293 }
351 return false; 351 return false;
352 } 352 }
353 353
354 if (dirhandle_ == INVALID_HANDLE_VALUE) { 354 if (dirhandle_ == INVALID_HANDLE_VALUE) {
355 dirhandle_ = 355 dirhandle_ =
356 CreateFileW(Watcher::path_.wstring().c_str(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE, 356 CreateFileW(Watcher::path_.wstring().c_str(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE,
357 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr); 357 nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
358 if (dirhandle_ == INVALID_HANDLE_VALUE) 358 if (dirhandle_ == INVALID_HANDLE_VALUE)
359 return false; 359 return false;
360 } 360 }
361 361
362 /* We're done here */ 362 /* We're done here */
364 } 364 }
365 365
366 bool QueueDirectoryRead() 366 bool QueueDirectoryRead()
367 { 367 {
368 return ReadDirectoryChangesW(dirhandle_, change_buf_, sizeof(change_buf_), TRUE, 368 return ReadDirectoryChangesW(dirhandle_, change_buf_, sizeof(change_buf_), TRUE,
369 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, nullptr, &overlapped_, 369 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME, nullptr, &overlapped_, nullptr);
370 nullptr);
371 } 370 }
372 371
373 HANDLE dirhandle_; 372 HANDLE dirhandle_;
374 OVERLAPPED overlapped_; 373 OVERLAPPED overlapped_;
375 alignas(FILE_NOTIFY_INFORMATION) char change_buf_[4096]; 374 alignas(FILE_NOTIFY_INFORMATION) char change_buf_[4096];
379 #elif defined(HAVE_INOTIFY) 378 #elif defined(HAVE_INOTIFY)
380 /* Inotify watcher */ 379 /* Inotify watcher */
381 class InotifyWatcher : public StdFilesystemWatcher { 380 class InotifyWatcher : public StdFilesystemWatcher {
382 public: 381 public:
383 InotifyWatcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive) 382 InotifyWatcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive)
384 : StdFilesystemWatcher(opaque, path, handler, recursive) 383 : StdFilesystemWatcher(opaque, path, handler, recursive), first_(true), giveup_(false), ev_(nullptr),
385 , first_(true) 384 ev_size_(0)
386 , giveup_(false)
387 , ev_(nullptr)
388 , ev_size_(0)
389 { 385 {
390 } 386 }
391 387
392 virtual ~InotifyWatcher() override 388 virtual ~InotifyWatcher() override
393 { 389 {
465 /* Watched directory has been deleted */ 461 /* Watched directory has been deleted */
466 RemoveWatchDescriptor(ev->wd); 462 RemoveWatchDescriptor(ev->wd);
467 continue; 463 continue;
468 } 464 }
469 465
470 if (ev->mask & (IN_MOVE|IN_CREATE|IN_DELETE)) { 466 if (ev->mask & (IN_MOVE | IN_CREATE | IN_DELETE)) {
471 std::filesystem::path p = wds_[ev->wd] / ev->name; 467 std::filesystem::path p = wds_[ev->wd] / ev->name;
472 468
473 if (ev->mask & (IN_MOVED_TO|IN_CREATE)) { 469 if (ev->mask & (IN_MOVED_TO | IN_CREATE)) {
474 if (std::filesystem::is_directory(p)) 470 if (std::filesystem::is_directory(p))
475 AddWatchDescriptor(p); 471 AddWatchDescriptor(p);
476 handler_(opaque_, p, Event::Created); 472 handler_(opaque_, p, Event::Created);
477 } else if (ev->mask & (IN_MOVED_FROM|IN_DELETE)) { 473 } else if (ev->mask & (IN_MOVED_FROM | IN_DELETE)) {
478 474
479 handler_(opaque_, wds_[ev->wd] / ev->name, Event::Deleted); 475 handler_(opaque_, wds_[ev->wd] / ev->name, Event::Deleted);
480 } 476 }
481 } 477 }
482 478
488 protected: 484 protected:
489 /* File descriptor helper. Mostly follows std::unique_ptr, 485 /* File descriptor helper. Mostly follows std::unique_ptr,
490 * but has a function for toggling non-blocking */ 486 * but has a function for toggling non-blocking */
491 struct FileDescriptor { 487 struct FileDescriptor {
492 public: 488 public:
493 FileDescriptor() : fd_(-1) { } 489 FileDescriptor() : fd_(-1) {}
494 ~FileDescriptor() { reset(); } 490 ~FileDescriptor() { reset(); }
495 491
496 int get() { return fd_; } 492 int get() { return fd_; }
497 operator bool() { return (fd_ != -1); } 493 operator bool() { return (fd_ != -1); }
498 void reset(int fd = -1) 494 void reset(int fd = -1)
534 { 530 {
535 if (giveup_) 531 if (giveup_)
536 return false; 532 return false;
537 533
538 if (!fd_) { 534 if (!fd_) {
539 #ifdef HAVE_INOTIFY_INIT1 535 # ifdef HAVE_INOTIFY_INIT1
540 fd_.reset(inotify_init1(IN_NONBLOCK)); 536 fd_.reset(inotify_init1(IN_NONBLOCK));
541 #else 537 # else
542 fd_.reset(inotify_init()); 538 fd_.reset(inotify_init());
543 #endif 539 # endif
544 if (!fd_) 540 if (!fd_)
545 return false; 541 return false;
546 542
547 #ifndef HAVE_INOTIFY_INIT1 543 # ifndef HAVE_INOTIFY_INIT1
548 /* Very old linux */ 544 /* Very old linux */
549 if (!fd_.SetNonBlocking(true)) 545 if (!fd_.SetNonBlocking(true))
550 return false; 546 return false;
551 #endif 547 # endif
552 } 548 }
553 549
554 return !!fd_; 550 return !!fd_;
555 } 551 }
556 552
557 bool AddWatchDescriptor(const std::filesystem::path &p) 553 bool AddWatchDescriptor(const std::filesystem::path &p)
558 { 554 {
559 if (!fd_ || giveup_) 555 if (!fd_ || giveup_)
560 return false; 556 return false;
561 557
562 int wd = inotify_add_watch(fd_.get(), p.string().c_str(), IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE_SELF); 558 int wd = inotify_add_watch(
559 fd_.get(), p.string().c_str(), IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE_SELF);
563 if (wd < 0) { 560 if (wd < 0) {
564 /* Don't even try to watch any more */ 561 /* Don't even try to watch any more */
565 giveup_ = true; 562 giveup_ = true;
566 return false; 563 return false;
567 } 564 }
594 using DefaultWatcher = InotifyWatcher; 591 using DefaultWatcher = InotifyWatcher;
595 #elif defined(__APPLE__) && defined(__MACH__) 592 #elif defined(__APPLE__) && defined(__MACH__)
596 class FSEventsWatcher : public StdFilesystemWatcher { 593 class FSEventsWatcher : public StdFilesystemWatcher {
597 public: 594 public:
598 FSEventsWatcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive) 595 FSEventsWatcher(void *opaque, const std::filesystem::path &path, IWatcher::EventHandler handler, bool recursive)
599 : StdFilesystemWatcher(opaque, path, handler, recursive) 596 : StdFilesystemWatcher(opaque, path, handler, recursive), first_(true)
600 , first_(true)
601 { 597 {
602 FSEventStreamContext ctx; 598 FSEventStreamContext ctx;
603 599
604 ctx.copyDescription = NULL; 600 ctx.copyDescription = NULL;
605 ctx.info = this; 601 ctx.info = this;
606 ctx.release = NULL; 602 ctx.release = NULL;
607 ctx.retain = NULL; 603 ctx.retain = NULL;
608 ctx.version = 0; 604 ctx.version = 0;
609 605
610 CFStringRef str = Strings::ToCFString(path.u8string()); 606 CFStringRef str = Strings::ToCFString(path.u8string());
611 CFArrayRef arr = CFArrayCreate(kCFAllocatorDefault, reinterpret_cast<const void **>(&str), 1, &kCFTypeArrayCallBacks); 607 CFArrayRef arr =
612 608 CFArrayCreate(kCFAllocatorDefault, reinterpret_cast<const void **>(&str), 1, &kCFTypeArrayCallBacks);
613 stream_ = FSEventStreamCreate(kCFAllocatorDefault, callback_static, &ctx, arr, kFSEventStreamEventIdSinceNow, 0.5, 0); 609
610 stream_ =
611 FSEventStreamCreate(kCFAllocatorDefault, callback_static, &ctx, arr, kFSEventStreamEventIdSinceNow, 0.5, 0);
614 612
615 // kill these off now 613 // kill these off now
616 CFRelease(str); 614 CFRelease(str);
617 CFRelease(arr); 615 CFRelease(arr);
618 616
650 Watcher::handler_(Watcher::opaque_, ev.path, ev.type); 648 Watcher::handler_(Watcher::opaque_, ev.path, ev.type);
651 events_.clear(); 649 events_.clear();
652 } 650 }
653 651
654 private: 652 private:
655 void callback(ConstFSEventStreamRef streamRef, std::size_t numEvents, void *eventPaths, const FSEventStreamEventFlags *eventFlags, const FSEventStreamEventId *eventIds) 653 void callback(ConstFSEventStreamRef streamRef, std::size_t numEvents, void *eventPaths,
654 const FSEventStreamEventFlags *eventFlags, const FSEventStreamEventId *eventIds)
656 { 655 {
657 // assert(streamRef == stream_); 656 // assert(streamRef == stream_);
658 657
659 #if 0 658 # if 0
660 for (std::size_t i = 0; i < numEvents; i++) { 659 for (std::size_t i = 0; i < numEvents; i++) {
661 if ((eventFlags[i] == 0) || (eventFlags[i] & (kFSEventStreamEventFlagItemCreated|kFSEventStreamEventFlagItemRemoved|kFSEventStreamEventFlagMustScanSubDirs))) { 660 if ((eventFlags[i] == 0) || (eventFlags[i] & (kFSEventStreamEventFlagItemCreated|kFSEventStreamEventFlagItemRemoved|kFSEventStreamEventFlagMustScanSubDirs))) {
662 EventInfo ev; 661 EventInfo ev;
663 662
664 ev.path = std::filesystem::path(reinterpret_cast<const char **>(eventPaths)[i]); 663 ev.path = std::filesystem::path(reinterpret_cast<const char **>(eventPaths)[i]);
677 ev.type = IWatcher::Deleted; 676 ev.type = IWatcher::Deleted;
678 events_.push_back(ev); 677 events_.push_back(ev);
679 } 678 }
680 } 679 }
681 } 680 }
682 #else 681 # else
683 /* I only evr get eventFlags[i] == 0 so I think this is 682 /* I only evr get eventFlags[i] == 0 so I think this is
684 * probably the best way ??? */ 683 * probably the best way ??? */
685 rescan_.push_back(Watcher::path_); 684 rescan_.push_back(Watcher::path_);
686 #endif 685 # endif
687 } 686 }
688 687
689 static void callback_static(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, std::size_t numEvents, void *eventPaths, const FSEventStreamEventFlags *eventFlags, const FSEventStreamEventId *eventIds) 688 static void callback_static(ConstFSEventStreamRef streamRef, void *clientCallBackInfo, std::size_t numEvents,
690 { 689 void *eventPaths, const FSEventStreamEventFlags *eventFlags, const FSEventStreamEventId *eventIds)
691 reinterpret_cast<FSEventsWatcher *>(clientCallBackInfo)->callback(streamRef, numEvents, eventPaths, eventFlags, eventIds); 690 {
691 reinterpret_cast<FSEventsWatcher *>(clientCallBackInfo)
692 ->callback(streamRef, numEvents, eventPaths, eventFlags, eventIds);
692 } 693 }
693 694
694 void runloop_thread() 695 void runloop_thread()
695 { 696 {
696 { 697 {
705 // now.. loop was stopped from destructor. 706 // now.. loop was stopped from destructor.
706 // we don't need to do anything; destructor 707 // we don't need to do anything; destructor
707 // will handle everything from here 708 // will handle everything from here
708 } 709 }
709 710
710 static void runloop_thread_static(FSEventsWatcher *e) 711 static void runloop_thread_static(FSEventsWatcher *e) { e->runloop_thread(); }
711 {
712 e->runloop_thread();
713 }
714 712
715 struct EventInfo { 713 struct EventInfo {
716 enum Event type; 714 enum Event type;
717 std::filesystem::path path; 715 std::filesystem::path path;
718 }; 716 };