xref: /freebsd-src/contrib/llvm-project/clang/lib/DirectoryWatcher/mac/DirectoryWatcher-mac.cpp (revision bdd1243df58e60e85101c09001d9812a789b6bc4)
10b57cec5SDimitry Andric //===- DirectoryWatcher-mac.cpp - Mac-platform directory watching ---------===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric 
90b57cec5SDimitry Andric #include "DirectoryScanner.h"
100b57cec5SDimitry Andric #include "clang/DirectoryWatcher/DirectoryWatcher.h"
110b57cec5SDimitry Andric 
120b57cec5SDimitry Andric #include "llvm/ADT/STLExtras.h"
130b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h"
14a7dea167SDimitry Andric #include "llvm/Support/Error.h"
150b57cec5SDimitry Andric #include "llvm/Support/Path.h"
160b57cec5SDimitry Andric #include <CoreServices/CoreServices.h>
175ffd83dbSDimitry Andric #include <TargetConditionals.h>
180b57cec5SDimitry Andric 
190b57cec5SDimitry Andric using namespace llvm;
200b57cec5SDimitry Andric using namespace clang;
210b57cec5SDimitry Andric 
225ffd83dbSDimitry Andric #if TARGET_OS_OSX
235ffd83dbSDimitry Andric 
240b57cec5SDimitry Andric static void stopFSEventStream(FSEventStreamRef);
250b57cec5SDimitry Andric 
260b57cec5SDimitry Andric namespace {
270b57cec5SDimitry Andric 
28a7dea167SDimitry Andric /// This implementation is based on FSEvents API which implementation is
29a7dea167SDimitry Andric /// aggressively coallescing events. This can manifest as duplicate events.
30a7dea167SDimitry Andric ///
31a7dea167SDimitry Andric /// For example this scenario has been observed:
32a7dea167SDimitry Andric ///
33a7dea167SDimitry Andric /// create foo/bar
34a7dea167SDimitry Andric /// sleep 5 s
35a7dea167SDimitry Andric /// create DirectoryWatcherMac for dir foo
36a7dea167SDimitry Andric /// receive notification: bar EventKind::Modified
37a7dea167SDimitry Andric /// sleep 5 s
38a7dea167SDimitry Andric /// modify foo/bar
39a7dea167SDimitry Andric /// receive notification: bar EventKind::Modified
40a7dea167SDimitry Andric /// receive notification: bar EventKind::Modified
41a7dea167SDimitry Andric /// sleep 5 s
42a7dea167SDimitry Andric /// delete foo/bar
43a7dea167SDimitry Andric /// receive notification: bar EventKind::Modified
44a7dea167SDimitry Andric /// receive notification: bar EventKind::Modified
45a7dea167SDimitry Andric /// receive notification: bar EventKind::Removed
460b57cec5SDimitry Andric class DirectoryWatcherMac : public clang::DirectoryWatcher {
470b57cec5SDimitry Andric public:
DirectoryWatcherMac(dispatch_queue_t Queue,FSEventStreamRef EventStream,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,llvm::StringRef WatchedDirPath)480b57cec5SDimitry Andric   DirectoryWatcherMac(
495ffd83dbSDimitry Andric       dispatch_queue_t Queue, FSEventStreamRef EventStream,
500b57cec5SDimitry Andric       std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
510b57cec5SDimitry Andric           Receiver,
520b57cec5SDimitry Andric       llvm::StringRef WatchedDirPath)
535ffd83dbSDimitry Andric       : Queue(Queue), EventStream(EventStream), Receiver(Receiver),
540b57cec5SDimitry Andric         WatchedDirPath(WatchedDirPath) {}
550b57cec5SDimitry Andric 
~DirectoryWatcherMac()560b57cec5SDimitry Andric   ~DirectoryWatcherMac() override {
575ffd83dbSDimitry Andric     // FSEventStreamStop and Invalidate must be called after Start and
585ffd83dbSDimitry Andric     // SetDispatchQueue to follow FSEvents API contract. The call to Receiver
595ffd83dbSDimitry Andric     // also uses Queue to not race with the initial scan.
605ffd83dbSDimitry Andric     dispatch_sync(Queue, ^{
610b57cec5SDimitry Andric       stopFSEventStream(EventStream);
620b57cec5SDimitry Andric       EventStream = nullptr;
635ffd83dbSDimitry Andric       Receiver(
645ffd83dbSDimitry Andric           DirectoryWatcher::Event(
650b57cec5SDimitry Andric               DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""),
660b57cec5SDimitry Andric           false);
675ffd83dbSDimitry Andric     });
685ffd83dbSDimitry Andric 
695ffd83dbSDimitry Andric     // Balance initial creation.
705ffd83dbSDimitry Andric     dispatch_release(Queue);
710b57cec5SDimitry Andric   }
720b57cec5SDimitry Andric 
730b57cec5SDimitry Andric private:
745ffd83dbSDimitry Andric   dispatch_queue_t Queue;
750b57cec5SDimitry Andric   FSEventStreamRef EventStream;
760b57cec5SDimitry Andric   std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;
770b57cec5SDimitry Andric   const std::string WatchedDirPath;
780b57cec5SDimitry Andric };
790b57cec5SDimitry Andric 
800b57cec5SDimitry Andric struct EventStreamContextData {
810b57cec5SDimitry Andric   std::string WatchedPath;
820b57cec5SDimitry Andric   std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver;
830b57cec5SDimitry Andric 
EventStreamContextData__anone34d839f0111::EventStreamContextData840b57cec5SDimitry Andric   EventStreamContextData(
850b57cec5SDimitry Andric       std::string &&WatchedPath,
860b57cec5SDimitry Andric       std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
870b57cec5SDimitry Andric           Receiver)
880b57cec5SDimitry Andric       : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {}
890b57cec5SDimitry Andric 
900b57cec5SDimitry Andric   // Needed for FSEvents
dispose__anone34d839f0111::EventStreamContextData910b57cec5SDimitry Andric   static void dispose(const void *ctx) {
920b57cec5SDimitry Andric     delete static_cast<const EventStreamContextData *>(ctx);
930b57cec5SDimitry Andric   }
940b57cec5SDimitry Andric };
950b57cec5SDimitry Andric } // namespace
960b57cec5SDimitry Andric 
970b57cec5SDimitry Andric constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =
980b57cec5SDimitry Andric     kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |
990b57cec5SDimitry Andric     kFSEventStreamEventFlagMustScanSubDirs;
1000b57cec5SDimitry Andric 
1010b57cec5SDimitry Andric constexpr const FSEventStreamEventFlags ModifyingFileEvents =
1020b57cec5SDimitry Andric     kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |
1030b57cec5SDimitry Andric     kFSEventStreamEventFlagItemModified;
1040b57cec5SDimitry Andric 
eventStreamCallback(ConstFSEventStreamRef Stream,void * ClientCallBackInfo,size_t NumEvents,void * EventPaths,const FSEventStreamEventFlags EventFlags[],const FSEventStreamEventId EventIds[])1050b57cec5SDimitry Andric static void eventStreamCallback(ConstFSEventStreamRef Stream,
1060b57cec5SDimitry Andric                                 void *ClientCallBackInfo, size_t NumEvents,
1070b57cec5SDimitry Andric                                 void *EventPaths,
1080b57cec5SDimitry Andric                                 const FSEventStreamEventFlags EventFlags[],
1090b57cec5SDimitry Andric                                 const FSEventStreamEventId EventIds[]) {
1100b57cec5SDimitry Andric   auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo);
1110b57cec5SDimitry Andric 
1120b57cec5SDimitry Andric   std::vector<DirectoryWatcher::Event> Events;
1130b57cec5SDimitry Andric   for (size_t i = 0; i < NumEvents; ++i) {
1140b57cec5SDimitry Andric     StringRef Path = ((const char **)EventPaths)[i];
1150b57cec5SDimitry Andric     const FSEventStreamEventFlags Flags = EventFlags[i];
1160b57cec5SDimitry Andric 
1170b57cec5SDimitry Andric     if (Flags & StreamInvalidatingFlags) {
1180b57cec5SDimitry Andric       Events.emplace_back(DirectoryWatcher::Event{
1190b57cec5SDimitry Andric           DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
1200b57cec5SDimitry Andric       break;
1210b57cec5SDimitry Andric     } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) {
1220b57cec5SDimitry Andric       // Subdirectories aren't supported - if some directory got removed it
1230b57cec5SDimitry Andric       // must've been the watched directory itself.
1240b57cec5SDimitry Andric       if ((Flags & kFSEventStreamEventFlagItemRemoved) &&
1250b57cec5SDimitry Andric           Path == ctx->WatchedPath) {
1260b57cec5SDimitry Andric         Events.emplace_back(DirectoryWatcher::Event{
1270b57cec5SDimitry Andric             DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""});
1280b57cec5SDimitry Andric         Events.emplace_back(DirectoryWatcher::Event{
1290b57cec5SDimitry Andric             DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
1300b57cec5SDimitry Andric         break;
1310b57cec5SDimitry Andric       }
1320b57cec5SDimitry Andric       // No support for subdirectories - just ignore everything.
1330b57cec5SDimitry Andric       continue;
1340b57cec5SDimitry Andric     } else if (Flags & kFSEventStreamEventFlagItemRemoved) {
1350b57cec5SDimitry Andric       Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
1360b57cec5SDimitry Andric                           llvm::sys::path::filename(Path));
1370b57cec5SDimitry Andric       continue;
1380b57cec5SDimitry Andric     } else if (Flags & ModifyingFileEvents) {
139*bdd1243dSDimitry Andric       if (!getFileStatus(Path).has_value()) {
1400b57cec5SDimitry Andric         Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
1410b57cec5SDimitry Andric                             llvm::sys::path::filename(Path));
1420b57cec5SDimitry Andric       } else {
1430b57cec5SDimitry Andric         Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,
1440b57cec5SDimitry Andric                             llvm::sys::path::filename(Path));
1450b57cec5SDimitry Andric       }
1460b57cec5SDimitry Andric       continue;
1470b57cec5SDimitry Andric     }
1480b57cec5SDimitry Andric 
1490b57cec5SDimitry Andric     // default
1500b57cec5SDimitry Andric     Events.emplace_back(DirectoryWatcher::Event{
1510b57cec5SDimitry Andric         DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
1520b57cec5SDimitry Andric     llvm_unreachable("Unknown FSEvent type.");
1530b57cec5SDimitry Andric   }
1540b57cec5SDimitry Andric 
1550b57cec5SDimitry Andric   if (!Events.empty()) {
1560b57cec5SDimitry Andric     ctx->Receiver(Events, /*IsInitial=*/false);
1570b57cec5SDimitry Andric   }
1580b57cec5SDimitry Andric }
1590b57cec5SDimitry Andric 
createFSEventStream(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,dispatch_queue_t Queue)1600b57cec5SDimitry Andric FSEventStreamRef createFSEventStream(
1610b57cec5SDimitry Andric     StringRef Path,
1620b57cec5SDimitry Andric     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
1630b57cec5SDimitry Andric     dispatch_queue_t Queue) {
1640b57cec5SDimitry Andric   if (Path.empty())
1650b57cec5SDimitry Andric     return nullptr;
1660b57cec5SDimitry Andric 
1670b57cec5SDimitry Andric   CFMutableArrayRef PathsToWatch = [&]() {
1680b57cec5SDimitry Andric     CFMutableArrayRef PathsToWatch =
1690b57cec5SDimitry Andric         CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
1700b57cec5SDimitry Andric     CFStringRef CfPathStr =
1710b57cec5SDimitry Andric         CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(),
1720b57cec5SDimitry Andric                                 Path.size(), kCFStringEncodingUTF8, false);
1730b57cec5SDimitry Andric     CFArrayAppendValue(PathsToWatch, CfPathStr);
1740b57cec5SDimitry Andric     CFRelease(CfPathStr);
1750b57cec5SDimitry Andric     return PathsToWatch;
1760b57cec5SDimitry Andric   }();
1770b57cec5SDimitry Andric 
1780b57cec5SDimitry Andric   FSEventStreamContext Context = [&]() {
1790b57cec5SDimitry Andric     std::string RealPath;
1800b57cec5SDimitry Andric     {
1810b57cec5SDimitry Andric       SmallString<128> Storage;
1820b57cec5SDimitry Andric       StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage);
1830b57cec5SDimitry Andric       char Buffer[PATH_MAX];
1840b57cec5SDimitry Andric       if (::realpath(P.begin(), Buffer) != nullptr)
1850b57cec5SDimitry Andric         RealPath = Buffer;
1860b57cec5SDimitry Andric       else
1875ffd83dbSDimitry Andric         RealPath = Path.str();
1880b57cec5SDimitry Andric     }
1890b57cec5SDimitry Andric 
1900b57cec5SDimitry Andric     FSEventStreamContext Context;
1910b57cec5SDimitry Andric     Context.version = 0;
1920b57cec5SDimitry Andric     Context.info = new EventStreamContextData(std::move(RealPath), Receiver);
1930b57cec5SDimitry Andric     Context.retain = nullptr;
1940b57cec5SDimitry Andric     Context.release = EventStreamContextData::dispose;
1950b57cec5SDimitry Andric     Context.copyDescription = nullptr;
1960b57cec5SDimitry Andric     return Context;
1970b57cec5SDimitry Andric   }();
1980b57cec5SDimitry Andric 
1990b57cec5SDimitry Andric   FSEventStreamRef Result = FSEventStreamCreate(
2000b57cec5SDimitry Andric       nullptr, eventStreamCallback, &Context, PathsToWatch,
2010b57cec5SDimitry Andric       kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0,
2020b57cec5SDimitry Andric       kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
2030b57cec5SDimitry Andric   CFRelease(PathsToWatch);
2040b57cec5SDimitry Andric 
2050b57cec5SDimitry Andric   return Result;
2060b57cec5SDimitry Andric }
2070b57cec5SDimitry Andric 
stopFSEventStream(FSEventStreamRef EventStream)2080b57cec5SDimitry Andric void stopFSEventStream(FSEventStreamRef EventStream) {
2090b57cec5SDimitry Andric   if (!EventStream)
2100b57cec5SDimitry Andric     return;
2110b57cec5SDimitry Andric   FSEventStreamStop(EventStream);
2120b57cec5SDimitry Andric   FSEventStreamInvalidate(EventStream);
2130b57cec5SDimitry Andric   FSEventStreamRelease(EventStream);
2140b57cec5SDimitry Andric }
2150b57cec5SDimitry Andric 
create(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,bool WaitForInitialSync)216a7dea167SDimitry Andric llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create(
2170b57cec5SDimitry Andric     StringRef Path,
2180b57cec5SDimitry Andric     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
2190b57cec5SDimitry Andric     bool WaitForInitialSync) {
2200b57cec5SDimitry Andric   dispatch_queue_t Queue =
2210b57cec5SDimitry Andric       dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
2220b57cec5SDimitry Andric 
2230b57cec5SDimitry Andric   if (Path.empty())
224a7dea167SDimitry Andric     llvm::report_fatal_error(
225a7dea167SDimitry Andric         "DirectoryWatcher::create can not accept an empty Path.");
2260b57cec5SDimitry Andric 
2270b57cec5SDimitry Andric   auto EventStream = createFSEventStream(Path, Receiver, Queue);
228a7dea167SDimitry Andric   assert(EventStream && "EventStream expected to be non-null");
2290b57cec5SDimitry Andric 
2300b57cec5SDimitry Andric   std::unique_ptr<DirectoryWatcher> Result =
2315ffd83dbSDimitry Andric       std::make_unique<DirectoryWatcherMac>(Queue, EventStream, Receiver, Path);
2320b57cec5SDimitry Andric 
2330b57cec5SDimitry Andric   // We need to copy the data so the lifetime is ok after a const copy is made
2340b57cec5SDimitry Andric   // for the block.
2355ffd83dbSDimitry Andric   const std::string CopiedPath = Path.str();
2360b57cec5SDimitry Andric 
2370b57cec5SDimitry Andric   auto InitWork = ^{
2380b57cec5SDimitry Andric     // We need to start watching the directory before we start scanning in order
2390b57cec5SDimitry Andric     // to not miss any event. By dispatching this on the same serial Queue as
2400b57cec5SDimitry Andric     // the FSEvents will be handled we manage to start watching BEFORE the
2410b57cec5SDimitry Andric     // inital scan and handling events ONLY AFTER the scan finishes.
2420b57cec5SDimitry Andric     FSEventStreamSetDispatchQueue(EventStream, Queue);
2430b57cec5SDimitry Andric     FSEventStreamStart(EventStream);
2440b57cec5SDimitry Andric     Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true);
2450b57cec5SDimitry Andric   };
2460b57cec5SDimitry Andric 
2470b57cec5SDimitry Andric   if (WaitForInitialSync) {
2480b57cec5SDimitry Andric     dispatch_sync(Queue, InitWork);
2490b57cec5SDimitry Andric   } else {
2500b57cec5SDimitry Andric     dispatch_async(Queue, InitWork);
2510b57cec5SDimitry Andric   }
2520b57cec5SDimitry Andric 
2530b57cec5SDimitry Andric   return Result;
2540b57cec5SDimitry Andric }
2555ffd83dbSDimitry Andric 
2565ffd83dbSDimitry Andric #else // TARGET_OS_OSX
2575ffd83dbSDimitry Andric 
2585ffd83dbSDimitry Andric llvm::Expected<std::unique_ptr<DirectoryWatcher>>
create(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,bool WaitForInitialSync)2595ffd83dbSDimitry Andric clang::DirectoryWatcher::create(
2605ffd83dbSDimitry Andric     StringRef Path,
2615ffd83dbSDimitry Andric     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
2625ffd83dbSDimitry Andric     bool WaitForInitialSync) {
2635ffd83dbSDimitry Andric   return llvm::make_error<llvm::StringError>(
2645ffd83dbSDimitry Andric       "DirectoryWatcher is not implemented for this platform!",
2655ffd83dbSDimitry Andric       llvm::inconvertibleErrorCode());
2665ffd83dbSDimitry Andric }
2675ffd83dbSDimitry Andric 
2685ffd83dbSDimitry Andric #endif // TARGET_OS_OSX
269