xref: /llvm-project/clang/lib/DirectoryWatcher/mac/DirectoryWatcher-mac.cpp (revision e486e48c3d9e99e4c17d365bbc4b429c8e5b5999)
177dd8a79SJan Korous //===- DirectoryWatcher-mac.cpp - Mac-platform directory watching ---------===//
277dd8a79SJan Korous //
377dd8a79SJan Korous // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
477dd8a79SJan Korous // See https://llvm.org/LICENSE.txt for license information.
577dd8a79SJan Korous // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
677dd8a79SJan Korous //
777dd8a79SJan Korous //===----------------------------------------------------------------------===//
877dd8a79SJan Korous 
977dd8a79SJan Korous #include "DirectoryScanner.h"
1077dd8a79SJan Korous #include "clang/DirectoryWatcher/DirectoryWatcher.h"
1177dd8a79SJan Korous 
1277dd8a79SJan Korous #include "llvm/ADT/STLExtras.h"
1377dd8a79SJan Korous #include "llvm/ADT/StringRef.h"
14ef74924fSPuyan Lotfi #include "llvm/Support/Error.h"
1577dd8a79SJan Korous #include "llvm/Support/Path.h"
1677dd8a79SJan Korous #include <CoreServices/CoreServices.h>
17cfb4f8c5SVedant Kumar #include <TargetConditionals.h>
1877dd8a79SJan Korous 
1977dd8a79SJan Korous using namespace llvm;
2077dd8a79SJan Korous using namespace clang;
2177dd8a79SJan Korous 
22cfb4f8c5SVedant Kumar #if TARGET_OS_OSX
23cfb4f8c5SVedant Kumar 
2477dd8a79SJan Korous static void stopFSEventStream(FSEventStreamRef);
2577dd8a79SJan Korous 
2677dd8a79SJan Korous namespace {
2777dd8a79SJan Korous 
289debb024SJan Korous /// This implementation is based on FSEvents API which implementation is
299debb024SJan Korous /// aggressively coallescing events. This can manifest as duplicate events.
309debb024SJan Korous ///
319debb024SJan Korous /// For example this scenario has been observed:
329debb024SJan Korous ///
339debb024SJan Korous /// create foo/bar
349debb024SJan Korous /// sleep 5 s
359debb024SJan Korous /// create DirectoryWatcherMac for dir foo
369debb024SJan Korous /// receive notification: bar EventKind::Modified
379debb024SJan Korous /// sleep 5 s
389debb024SJan Korous /// modify foo/bar
399debb024SJan Korous /// receive notification: bar EventKind::Modified
409debb024SJan Korous /// receive notification: bar EventKind::Modified
419debb024SJan Korous /// sleep 5 s
429debb024SJan Korous /// delete foo/bar
439debb024SJan Korous /// receive notification: bar EventKind::Modified
449debb024SJan Korous /// receive notification: bar EventKind::Modified
459debb024SJan Korous /// receive notification: bar EventKind::Removed
4677dd8a79SJan Korous class DirectoryWatcherMac : public clang::DirectoryWatcher {
4777dd8a79SJan Korous public:
DirectoryWatcherMac(dispatch_queue_t Queue,FSEventStreamRef EventStream,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,llvm::StringRef WatchedDirPath)4877dd8a79SJan Korous   DirectoryWatcherMac(
492ac0c4b4SBen Langmuir       dispatch_queue_t Queue, FSEventStreamRef EventStream,
5077dd8a79SJan Korous       std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
5177dd8a79SJan Korous           Receiver,
5277dd8a79SJan Korous       llvm::StringRef WatchedDirPath)
532ac0c4b4SBen Langmuir       : Queue(Queue), EventStream(EventStream), Receiver(Receiver),
5477dd8a79SJan Korous         WatchedDirPath(WatchedDirPath) {}
5577dd8a79SJan Korous 
~DirectoryWatcherMac()5677dd8a79SJan Korous   ~DirectoryWatcherMac() override {
572ac0c4b4SBen Langmuir     // FSEventStreamStop and Invalidate must be called after Start and
582ac0c4b4SBen Langmuir     // SetDispatchQueue to follow FSEvents API contract. The call to Receiver
592ac0c4b4SBen Langmuir     // also uses Queue to not race with the initial scan.
602ac0c4b4SBen Langmuir     dispatch_sync(Queue, ^{
6177dd8a79SJan Korous       stopFSEventStream(EventStream);
6277dd8a79SJan Korous       EventStream = nullptr;
632ac0c4b4SBen Langmuir       Receiver(
642ac0c4b4SBen Langmuir           DirectoryWatcher::Event(
6577dd8a79SJan Korous               DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""),
6677dd8a79SJan Korous           false);
672ac0c4b4SBen Langmuir     });
682ac0c4b4SBen Langmuir 
692ac0c4b4SBen Langmuir     // Balance initial creation.
702ac0c4b4SBen Langmuir     dispatch_release(Queue);
7177dd8a79SJan Korous   }
7277dd8a79SJan Korous 
7377dd8a79SJan Korous private:
742ac0c4b4SBen Langmuir   dispatch_queue_t Queue;
7577dd8a79SJan Korous   FSEventStreamRef EventStream;
7677dd8a79SJan Korous   std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;
7777dd8a79SJan Korous   const std::string WatchedDirPath;
7877dd8a79SJan Korous };
7977dd8a79SJan Korous 
8077dd8a79SJan Korous struct EventStreamContextData {
8177dd8a79SJan Korous   std::string WatchedPath;
8277dd8a79SJan Korous   std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver;
8377dd8a79SJan Korous 
EventStreamContextData__anon0cb5cf400111::EventStreamContextData8477dd8a79SJan Korous   EventStreamContextData(
8577dd8a79SJan Korous       std::string &&WatchedPath,
8677dd8a79SJan Korous       std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
8777dd8a79SJan Korous           Receiver)
8877dd8a79SJan Korous       : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {}
8977dd8a79SJan Korous 
9077dd8a79SJan Korous   // Needed for FSEvents
dispose__anon0cb5cf400111::EventStreamContextData9177dd8a79SJan Korous   static void dispose(const void *ctx) {
9277dd8a79SJan Korous     delete static_cast<const EventStreamContextData *>(ctx);
9377dd8a79SJan Korous   }
9477dd8a79SJan Korous };
9577dd8a79SJan Korous } // namespace
9677dd8a79SJan Korous 
9777dd8a79SJan Korous constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =
9877dd8a79SJan Korous     kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |
9977dd8a79SJan Korous     kFSEventStreamEventFlagMustScanSubDirs;
10077dd8a79SJan Korous 
10177dd8a79SJan Korous constexpr const FSEventStreamEventFlags ModifyingFileEvents =
10277dd8a79SJan Korous     kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |
10377dd8a79SJan Korous     kFSEventStreamEventFlagItemModified;
10477dd8a79SJan Korous 
eventStreamCallback(ConstFSEventStreamRef Stream,void * ClientCallBackInfo,size_t NumEvents,void * EventPaths,const FSEventStreamEventFlags EventFlags[],const FSEventStreamEventId EventIds[])10577dd8a79SJan Korous static void eventStreamCallback(ConstFSEventStreamRef Stream,
10677dd8a79SJan Korous                                 void *ClientCallBackInfo, size_t NumEvents,
10777dd8a79SJan Korous                                 void *EventPaths,
10877dd8a79SJan Korous                                 const FSEventStreamEventFlags EventFlags[],
10977dd8a79SJan Korous                                 const FSEventStreamEventId EventIds[]) {
11077dd8a79SJan Korous   auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo);
11177dd8a79SJan Korous 
11277dd8a79SJan Korous   std::vector<DirectoryWatcher::Event> Events;
11377dd8a79SJan Korous   for (size_t i = 0; i < NumEvents; ++i) {
11477dd8a79SJan Korous     StringRef Path = ((const char **)EventPaths)[i];
11577dd8a79SJan Korous     const FSEventStreamEventFlags Flags = EventFlags[i];
11677dd8a79SJan Korous 
11777dd8a79SJan Korous     if (Flags & StreamInvalidatingFlags) {
11877dd8a79SJan Korous       Events.emplace_back(DirectoryWatcher::Event{
11977dd8a79SJan Korous           DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
12077dd8a79SJan Korous       break;
12177dd8a79SJan Korous     } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) {
12277dd8a79SJan Korous       // Subdirectories aren't supported - if some directory got removed it
12377dd8a79SJan Korous       // must've been the watched directory itself.
12477dd8a79SJan Korous       if ((Flags & kFSEventStreamEventFlagItemRemoved) &&
12577dd8a79SJan Korous           Path == ctx->WatchedPath) {
12677dd8a79SJan Korous         Events.emplace_back(DirectoryWatcher::Event{
12777dd8a79SJan Korous             DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""});
12877dd8a79SJan Korous         Events.emplace_back(DirectoryWatcher::Event{
12977dd8a79SJan Korous             DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
13077dd8a79SJan Korous         break;
13177dd8a79SJan Korous       }
13277dd8a79SJan Korous       // No support for subdirectories - just ignore everything.
13377dd8a79SJan Korous       continue;
13477dd8a79SJan Korous     } else if (Flags & kFSEventStreamEventFlagItemRemoved) {
13577dd8a79SJan Korous       Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
13677dd8a79SJan Korous                           llvm::sys::path::filename(Path));
13777dd8a79SJan Korous       continue;
13877dd8a79SJan Korous     } else if (Flags & ModifyingFileEvents) {
139*e486e48cSThorsten Schütt       if (!getFileStatus(Path).has_value()) {
14077dd8a79SJan Korous         Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
14177dd8a79SJan Korous                             llvm::sys::path::filename(Path));
14277dd8a79SJan Korous       } else {
14377dd8a79SJan Korous         Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,
14477dd8a79SJan Korous                             llvm::sys::path::filename(Path));
14577dd8a79SJan Korous       }
14677dd8a79SJan Korous       continue;
14777dd8a79SJan Korous     }
14877dd8a79SJan Korous 
14977dd8a79SJan Korous     // default
15077dd8a79SJan Korous     Events.emplace_back(DirectoryWatcher::Event{
15177dd8a79SJan Korous         DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
15277dd8a79SJan Korous     llvm_unreachable("Unknown FSEvent type.");
15377dd8a79SJan Korous   }
15477dd8a79SJan Korous 
15577dd8a79SJan Korous   if (!Events.empty()) {
15677dd8a79SJan Korous     ctx->Receiver(Events, /*IsInitial=*/false);
15777dd8a79SJan Korous   }
15877dd8a79SJan Korous }
15977dd8a79SJan Korous 
createFSEventStream(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,dispatch_queue_t Queue)16077dd8a79SJan Korous FSEventStreamRef createFSEventStream(
16177dd8a79SJan Korous     StringRef Path,
16277dd8a79SJan Korous     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
16377dd8a79SJan Korous     dispatch_queue_t Queue) {
1641dcf216fSPuyan Lotfi   if (Path.empty())
1651dcf216fSPuyan Lotfi     return nullptr;
16677dd8a79SJan Korous 
16777dd8a79SJan Korous   CFMutableArrayRef PathsToWatch = [&]() {
16877dd8a79SJan Korous     CFMutableArrayRef PathsToWatch =
16977dd8a79SJan Korous         CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
17077dd8a79SJan Korous     CFStringRef CfPathStr =
17177dd8a79SJan Korous         CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(),
17277dd8a79SJan Korous                                 Path.size(), kCFStringEncodingUTF8, false);
17377dd8a79SJan Korous     CFArrayAppendValue(PathsToWatch, CfPathStr);
17477dd8a79SJan Korous     CFRelease(CfPathStr);
17577dd8a79SJan Korous     return PathsToWatch;
17677dd8a79SJan Korous   }();
17777dd8a79SJan Korous 
17877dd8a79SJan Korous   FSEventStreamContext Context = [&]() {
17977dd8a79SJan Korous     std::string RealPath;
18077dd8a79SJan Korous     {
18177dd8a79SJan Korous       SmallString<128> Storage;
18277dd8a79SJan Korous       StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage);
18377dd8a79SJan Korous       char Buffer[PATH_MAX];
18477dd8a79SJan Korous       if (::realpath(P.begin(), Buffer) != nullptr)
18577dd8a79SJan Korous         RealPath = Buffer;
18677dd8a79SJan Korous       else
18700d834e0SJonas Devlieghere         RealPath = Path.str();
18877dd8a79SJan Korous     }
18977dd8a79SJan Korous 
19077dd8a79SJan Korous     FSEventStreamContext Context;
19177dd8a79SJan Korous     Context.version = 0;
19277dd8a79SJan Korous     Context.info = new EventStreamContextData(std::move(RealPath), Receiver);
19377dd8a79SJan Korous     Context.retain = nullptr;
19477dd8a79SJan Korous     Context.release = EventStreamContextData::dispose;
19577dd8a79SJan Korous     Context.copyDescription = nullptr;
19677dd8a79SJan Korous     return Context;
19777dd8a79SJan Korous   }();
19877dd8a79SJan Korous 
19977dd8a79SJan Korous   FSEventStreamRef Result = FSEventStreamCreate(
20077dd8a79SJan Korous       nullptr, eventStreamCallback, &Context, PathsToWatch,
20177dd8a79SJan Korous       kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0,
20277dd8a79SJan Korous       kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
20377dd8a79SJan Korous   CFRelease(PathsToWatch);
20477dd8a79SJan Korous 
20577dd8a79SJan Korous   return Result;
20677dd8a79SJan Korous }
20777dd8a79SJan Korous 
stopFSEventStream(FSEventStreamRef EventStream)20877dd8a79SJan Korous void stopFSEventStream(FSEventStreamRef EventStream) {
20977dd8a79SJan Korous   if (!EventStream)
21077dd8a79SJan Korous     return;
21177dd8a79SJan Korous   FSEventStreamStop(EventStream);
21277dd8a79SJan Korous   FSEventStreamInvalidate(EventStream);
21377dd8a79SJan Korous   FSEventStreamRelease(EventStream);
21477dd8a79SJan Korous }
21577dd8a79SJan Korous 
create(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,bool WaitForInitialSync)216ef74924fSPuyan Lotfi llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create(
21777dd8a79SJan Korous     StringRef Path,
21877dd8a79SJan Korous     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
21977dd8a79SJan Korous     bool WaitForInitialSync) {
22077dd8a79SJan Korous   dispatch_queue_t Queue =
22177dd8a79SJan Korous       dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
22277dd8a79SJan Korous 
2231dcf216fSPuyan Lotfi   if (Path.empty())
2241dcf216fSPuyan Lotfi     llvm::report_fatal_error(
2251dcf216fSPuyan Lotfi         "DirectoryWatcher::create can not accept an empty Path.");
2261dcf216fSPuyan Lotfi 
22777dd8a79SJan Korous   auto EventStream = createFSEventStream(Path, Receiver, Queue);
228fe08528cSShoaib Meenai   assert(EventStream && "EventStream expected to be non-null");
22977dd8a79SJan Korous 
23077dd8a79SJan Korous   std::unique_ptr<DirectoryWatcher> Result =
2312ac0c4b4SBen Langmuir       std::make_unique<DirectoryWatcherMac>(Queue, EventStream, Receiver, Path);
23277dd8a79SJan Korous 
23377dd8a79SJan Korous   // We need to copy the data so the lifetime is ok after a const copy is made
23477dd8a79SJan Korous   // for the block.
23543a1c805SJonas Devlieghere   const std::string CopiedPath = Path.str();
23677dd8a79SJan Korous 
23777dd8a79SJan Korous   auto InitWork = ^{
23877dd8a79SJan Korous     // We need to start watching the directory before we start scanning in order
23977dd8a79SJan Korous     // to not miss any event. By dispatching this on the same serial Queue as
24077dd8a79SJan Korous     // the FSEvents will be handled we manage to start watching BEFORE the
24177dd8a79SJan Korous     // inital scan and handling events ONLY AFTER the scan finishes.
24277dd8a79SJan Korous     FSEventStreamSetDispatchQueue(EventStream, Queue);
24377dd8a79SJan Korous     FSEventStreamStart(EventStream);
24477dd8a79SJan Korous     Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true);
24577dd8a79SJan Korous   };
24677dd8a79SJan Korous 
24777dd8a79SJan Korous   if (WaitForInitialSync) {
24877dd8a79SJan Korous     dispatch_sync(Queue, InitWork);
24977dd8a79SJan Korous   } else {
25077dd8a79SJan Korous     dispatch_async(Queue, InitWork);
25177dd8a79SJan Korous   }
25277dd8a79SJan Korous 
25377dd8a79SJan Korous   return Result;
25477dd8a79SJan Korous }
255cfb4f8c5SVedant Kumar 
256cfb4f8c5SVedant Kumar #else // TARGET_OS_OSX
257cfb4f8c5SVedant Kumar 
258cfb4f8c5SVedant Kumar llvm::Expected<std::unique_ptr<DirectoryWatcher>>
create(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,bool WaitForInitialSync)259cfb4f8c5SVedant Kumar clang::DirectoryWatcher::create(
260cfb4f8c5SVedant Kumar     StringRef Path,
261cfb4f8c5SVedant Kumar     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
262cfb4f8c5SVedant Kumar     bool WaitForInitialSync) {
263cfb4f8c5SVedant Kumar   return llvm::make_error<llvm::StringError>(
264cfb4f8c5SVedant Kumar       "DirectoryWatcher is not implemented for this platform!",
265cfb4f8c5SVedant Kumar       llvm::inconvertibleErrorCode());
266cfb4f8c5SVedant Kumar }
267cfb4f8c5SVedant Kumar 
268cfb4f8c5SVedant Kumar #endif // TARGET_OS_OSX
269