xref: /openbsd-src/gnu/llvm/clang/lib/DirectoryWatcher/mac/DirectoryWatcher-mac.cpp (revision 12c855180aad702bbcca06e0398d774beeafb155)
1e5dd7070Spatrick //===- DirectoryWatcher-mac.cpp - Mac-platform directory watching ---------===//
2e5dd7070Spatrick //
3e5dd7070Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e5dd7070Spatrick // See https://llvm.org/LICENSE.txt for license information.
5e5dd7070Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e5dd7070Spatrick //
7e5dd7070Spatrick //===----------------------------------------------------------------------===//
8e5dd7070Spatrick 
9e5dd7070Spatrick #include "DirectoryScanner.h"
10e5dd7070Spatrick #include "clang/DirectoryWatcher/DirectoryWatcher.h"
11e5dd7070Spatrick 
12e5dd7070Spatrick #include "llvm/ADT/STLExtras.h"
13e5dd7070Spatrick #include "llvm/ADT/StringRef.h"
14e5dd7070Spatrick #include "llvm/Support/Error.h"
15e5dd7070Spatrick #include "llvm/Support/Path.h"
16e5dd7070Spatrick #include <CoreServices/CoreServices.h>
17ec727ea7Spatrick #include <TargetConditionals.h>
18e5dd7070Spatrick 
19e5dd7070Spatrick using namespace llvm;
20e5dd7070Spatrick using namespace clang;
21e5dd7070Spatrick 
22ec727ea7Spatrick #if TARGET_OS_OSX
23ec727ea7Spatrick 
24e5dd7070Spatrick static void stopFSEventStream(FSEventStreamRef);
25e5dd7070Spatrick 
26e5dd7070Spatrick namespace {
27e5dd7070Spatrick 
28e5dd7070Spatrick /// This implementation is based on FSEvents API which implementation is
29e5dd7070Spatrick /// aggressively coallescing events. This can manifest as duplicate events.
30e5dd7070Spatrick ///
31e5dd7070Spatrick /// For example this scenario has been observed:
32e5dd7070Spatrick ///
33e5dd7070Spatrick /// create foo/bar
34e5dd7070Spatrick /// sleep 5 s
35e5dd7070Spatrick /// create DirectoryWatcherMac for dir foo
36e5dd7070Spatrick /// receive notification: bar EventKind::Modified
37e5dd7070Spatrick /// sleep 5 s
38e5dd7070Spatrick /// modify foo/bar
39e5dd7070Spatrick /// receive notification: bar EventKind::Modified
40e5dd7070Spatrick /// receive notification: bar EventKind::Modified
41e5dd7070Spatrick /// sleep 5 s
42e5dd7070Spatrick /// delete foo/bar
43e5dd7070Spatrick /// receive notification: bar EventKind::Modified
44e5dd7070Spatrick /// receive notification: bar EventKind::Modified
45e5dd7070Spatrick /// receive notification: bar EventKind::Removed
46e5dd7070Spatrick class DirectoryWatcherMac : public clang::DirectoryWatcher {
47e5dd7070Spatrick public:
DirectoryWatcherMac(dispatch_queue_t Queue,FSEventStreamRef EventStream,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,llvm::StringRef WatchedDirPath)48e5dd7070Spatrick   DirectoryWatcherMac(
49ec727ea7Spatrick       dispatch_queue_t Queue, FSEventStreamRef EventStream,
50e5dd7070Spatrick       std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
51e5dd7070Spatrick           Receiver,
52e5dd7070Spatrick       llvm::StringRef WatchedDirPath)
53ec727ea7Spatrick       : Queue(Queue), EventStream(EventStream), Receiver(Receiver),
54e5dd7070Spatrick         WatchedDirPath(WatchedDirPath) {}
55e5dd7070Spatrick 
~DirectoryWatcherMac()56e5dd7070Spatrick   ~DirectoryWatcherMac() override {
57ec727ea7Spatrick     // FSEventStreamStop and Invalidate must be called after Start and
58ec727ea7Spatrick     // SetDispatchQueue to follow FSEvents API contract. The call to Receiver
59ec727ea7Spatrick     // also uses Queue to not race with the initial scan.
60ec727ea7Spatrick     dispatch_sync(Queue, ^{
61e5dd7070Spatrick       stopFSEventStream(EventStream);
62e5dd7070Spatrick       EventStream = nullptr;
63ec727ea7Spatrick       Receiver(
64ec727ea7Spatrick           DirectoryWatcher::Event(
65e5dd7070Spatrick               DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""),
66e5dd7070Spatrick           false);
67ec727ea7Spatrick     });
68ec727ea7Spatrick 
69ec727ea7Spatrick     // Balance initial creation.
70ec727ea7Spatrick     dispatch_release(Queue);
71e5dd7070Spatrick   }
72e5dd7070Spatrick 
73e5dd7070Spatrick private:
74ec727ea7Spatrick   dispatch_queue_t Queue;
75e5dd7070Spatrick   FSEventStreamRef EventStream;
76e5dd7070Spatrick   std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;
77e5dd7070Spatrick   const std::string WatchedDirPath;
78e5dd7070Spatrick };
79e5dd7070Spatrick 
80e5dd7070Spatrick struct EventStreamContextData {
81e5dd7070Spatrick   std::string WatchedPath;
82e5dd7070Spatrick   std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver;
83e5dd7070Spatrick 
EventStreamContextData__anon0fd2c5240111::EventStreamContextData84e5dd7070Spatrick   EventStreamContextData(
85e5dd7070Spatrick       std::string &&WatchedPath,
86e5dd7070Spatrick       std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
87e5dd7070Spatrick           Receiver)
88e5dd7070Spatrick       : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {}
89e5dd7070Spatrick 
90e5dd7070Spatrick   // Needed for FSEvents
dispose__anon0fd2c5240111::EventStreamContextData91e5dd7070Spatrick   static void dispose(const void *ctx) {
92e5dd7070Spatrick     delete static_cast<const EventStreamContextData *>(ctx);
93e5dd7070Spatrick   }
94e5dd7070Spatrick };
95e5dd7070Spatrick } // namespace
96e5dd7070Spatrick 
97e5dd7070Spatrick constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =
98e5dd7070Spatrick     kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |
99e5dd7070Spatrick     kFSEventStreamEventFlagMustScanSubDirs;
100e5dd7070Spatrick 
101e5dd7070Spatrick constexpr const FSEventStreamEventFlags ModifyingFileEvents =
102e5dd7070Spatrick     kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |
103e5dd7070Spatrick     kFSEventStreamEventFlagItemModified;
104e5dd7070Spatrick 
eventStreamCallback(ConstFSEventStreamRef Stream,void * ClientCallBackInfo,size_t NumEvents,void * EventPaths,const FSEventStreamEventFlags EventFlags[],const FSEventStreamEventId EventIds[])105e5dd7070Spatrick static void eventStreamCallback(ConstFSEventStreamRef Stream,
106e5dd7070Spatrick                                 void *ClientCallBackInfo, size_t NumEvents,
107e5dd7070Spatrick                                 void *EventPaths,
108e5dd7070Spatrick                                 const FSEventStreamEventFlags EventFlags[],
109e5dd7070Spatrick                                 const FSEventStreamEventId EventIds[]) {
110e5dd7070Spatrick   auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo);
111e5dd7070Spatrick 
112e5dd7070Spatrick   std::vector<DirectoryWatcher::Event> Events;
113e5dd7070Spatrick   for (size_t i = 0; i < NumEvents; ++i) {
114e5dd7070Spatrick     StringRef Path = ((const char **)EventPaths)[i];
115e5dd7070Spatrick     const FSEventStreamEventFlags Flags = EventFlags[i];
116e5dd7070Spatrick 
117e5dd7070Spatrick     if (Flags & StreamInvalidatingFlags) {
118e5dd7070Spatrick       Events.emplace_back(DirectoryWatcher::Event{
119e5dd7070Spatrick           DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
120e5dd7070Spatrick       break;
121e5dd7070Spatrick     } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) {
122e5dd7070Spatrick       // Subdirectories aren't supported - if some directory got removed it
123e5dd7070Spatrick       // must've been the watched directory itself.
124e5dd7070Spatrick       if ((Flags & kFSEventStreamEventFlagItemRemoved) &&
125e5dd7070Spatrick           Path == ctx->WatchedPath) {
126e5dd7070Spatrick         Events.emplace_back(DirectoryWatcher::Event{
127e5dd7070Spatrick             DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""});
128e5dd7070Spatrick         Events.emplace_back(DirectoryWatcher::Event{
129e5dd7070Spatrick             DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
130e5dd7070Spatrick         break;
131e5dd7070Spatrick       }
132e5dd7070Spatrick       // No support for subdirectories - just ignore everything.
133e5dd7070Spatrick       continue;
134e5dd7070Spatrick     } else if (Flags & kFSEventStreamEventFlagItemRemoved) {
135e5dd7070Spatrick       Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
136e5dd7070Spatrick                           llvm::sys::path::filename(Path));
137e5dd7070Spatrick       continue;
138e5dd7070Spatrick     } else if (Flags & ModifyingFileEvents) {
139*12c85518Srobert       if (!getFileStatus(Path).has_value()) {
140e5dd7070Spatrick         Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
141e5dd7070Spatrick                             llvm::sys::path::filename(Path));
142e5dd7070Spatrick       } else {
143e5dd7070Spatrick         Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,
144e5dd7070Spatrick                             llvm::sys::path::filename(Path));
145e5dd7070Spatrick       }
146e5dd7070Spatrick       continue;
147e5dd7070Spatrick     }
148e5dd7070Spatrick 
149e5dd7070Spatrick     // default
150e5dd7070Spatrick     Events.emplace_back(DirectoryWatcher::Event{
151e5dd7070Spatrick         DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
152e5dd7070Spatrick     llvm_unreachable("Unknown FSEvent type.");
153e5dd7070Spatrick   }
154e5dd7070Spatrick 
155e5dd7070Spatrick   if (!Events.empty()) {
156e5dd7070Spatrick     ctx->Receiver(Events, /*IsInitial=*/false);
157e5dd7070Spatrick   }
158e5dd7070Spatrick }
159e5dd7070Spatrick 
createFSEventStream(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,dispatch_queue_t Queue)160e5dd7070Spatrick FSEventStreamRef createFSEventStream(
161e5dd7070Spatrick     StringRef Path,
162e5dd7070Spatrick     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
163e5dd7070Spatrick     dispatch_queue_t Queue) {
164e5dd7070Spatrick   if (Path.empty())
165e5dd7070Spatrick     return nullptr;
166e5dd7070Spatrick 
167e5dd7070Spatrick   CFMutableArrayRef PathsToWatch = [&]() {
168e5dd7070Spatrick     CFMutableArrayRef PathsToWatch =
169e5dd7070Spatrick         CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
170e5dd7070Spatrick     CFStringRef CfPathStr =
171e5dd7070Spatrick         CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(),
172e5dd7070Spatrick                                 Path.size(), kCFStringEncodingUTF8, false);
173e5dd7070Spatrick     CFArrayAppendValue(PathsToWatch, CfPathStr);
174e5dd7070Spatrick     CFRelease(CfPathStr);
175e5dd7070Spatrick     return PathsToWatch;
176e5dd7070Spatrick   }();
177e5dd7070Spatrick 
178e5dd7070Spatrick   FSEventStreamContext Context = [&]() {
179e5dd7070Spatrick     std::string RealPath;
180e5dd7070Spatrick     {
181e5dd7070Spatrick       SmallString<128> Storage;
182e5dd7070Spatrick       StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage);
183e5dd7070Spatrick       char Buffer[PATH_MAX];
184e5dd7070Spatrick       if (::realpath(P.begin(), Buffer) != nullptr)
185e5dd7070Spatrick         RealPath = Buffer;
186e5dd7070Spatrick       else
187ec727ea7Spatrick         RealPath = Path.str();
188e5dd7070Spatrick     }
189e5dd7070Spatrick 
190e5dd7070Spatrick     FSEventStreamContext Context;
191e5dd7070Spatrick     Context.version = 0;
192e5dd7070Spatrick     Context.info = new EventStreamContextData(std::move(RealPath), Receiver);
193e5dd7070Spatrick     Context.retain = nullptr;
194e5dd7070Spatrick     Context.release = EventStreamContextData::dispose;
195e5dd7070Spatrick     Context.copyDescription = nullptr;
196e5dd7070Spatrick     return Context;
197e5dd7070Spatrick   }();
198e5dd7070Spatrick 
199e5dd7070Spatrick   FSEventStreamRef Result = FSEventStreamCreate(
200e5dd7070Spatrick       nullptr, eventStreamCallback, &Context, PathsToWatch,
201e5dd7070Spatrick       kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0,
202e5dd7070Spatrick       kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
203e5dd7070Spatrick   CFRelease(PathsToWatch);
204e5dd7070Spatrick 
205e5dd7070Spatrick   return Result;
206e5dd7070Spatrick }
207e5dd7070Spatrick 
stopFSEventStream(FSEventStreamRef EventStream)208e5dd7070Spatrick void stopFSEventStream(FSEventStreamRef EventStream) {
209e5dd7070Spatrick   if (!EventStream)
210e5dd7070Spatrick     return;
211e5dd7070Spatrick   FSEventStreamStop(EventStream);
212e5dd7070Spatrick   FSEventStreamInvalidate(EventStream);
213e5dd7070Spatrick   FSEventStreamRelease(EventStream);
214e5dd7070Spatrick }
215e5dd7070Spatrick 
create(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,bool WaitForInitialSync)216e5dd7070Spatrick llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create(
217e5dd7070Spatrick     StringRef Path,
218e5dd7070Spatrick     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
219e5dd7070Spatrick     bool WaitForInitialSync) {
220e5dd7070Spatrick   dispatch_queue_t Queue =
221e5dd7070Spatrick       dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
222e5dd7070Spatrick 
223e5dd7070Spatrick   if (Path.empty())
224e5dd7070Spatrick     llvm::report_fatal_error(
225e5dd7070Spatrick         "DirectoryWatcher::create can not accept an empty Path.");
226e5dd7070Spatrick 
227e5dd7070Spatrick   auto EventStream = createFSEventStream(Path, Receiver, Queue);
228e5dd7070Spatrick   assert(EventStream && "EventStream expected to be non-null");
229e5dd7070Spatrick 
230e5dd7070Spatrick   std::unique_ptr<DirectoryWatcher> Result =
231ec727ea7Spatrick       std::make_unique<DirectoryWatcherMac>(Queue, EventStream, Receiver, Path);
232e5dd7070Spatrick 
233e5dd7070Spatrick   // We need to copy the data so the lifetime is ok after a const copy is made
234e5dd7070Spatrick   // for the block.
235ec727ea7Spatrick   const std::string CopiedPath = Path.str();
236e5dd7070Spatrick 
237e5dd7070Spatrick   auto InitWork = ^{
238e5dd7070Spatrick     // We need to start watching the directory before we start scanning in order
239e5dd7070Spatrick     // to not miss any event. By dispatching this on the same serial Queue as
240e5dd7070Spatrick     // the FSEvents will be handled we manage to start watching BEFORE the
241e5dd7070Spatrick     // inital scan and handling events ONLY AFTER the scan finishes.
242e5dd7070Spatrick     FSEventStreamSetDispatchQueue(EventStream, Queue);
243e5dd7070Spatrick     FSEventStreamStart(EventStream);
244e5dd7070Spatrick     Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true);
245e5dd7070Spatrick   };
246e5dd7070Spatrick 
247e5dd7070Spatrick   if (WaitForInitialSync) {
248e5dd7070Spatrick     dispatch_sync(Queue, InitWork);
249e5dd7070Spatrick   } else {
250e5dd7070Spatrick     dispatch_async(Queue, InitWork);
251e5dd7070Spatrick   }
252e5dd7070Spatrick 
253e5dd7070Spatrick   return Result;
254e5dd7070Spatrick }
255ec727ea7Spatrick 
256ec727ea7Spatrick #else // TARGET_OS_OSX
257ec727ea7Spatrick 
258ec727ea7Spatrick llvm::Expected<std::unique_ptr<DirectoryWatcher>>
create(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,bool WaitForInitialSync)259ec727ea7Spatrick clang::DirectoryWatcher::create(
260ec727ea7Spatrick     StringRef Path,
261ec727ea7Spatrick     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
262ec727ea7Spatrick     bool WaitForInitialSync) {
263ec727ea7Spatrick   return llvm::make_error<llvm::StringError>(
264ec727ea7Spatrick       "DirectoryWatcher is not implemented for this platform!",
265ec727ea7Spatrick       llvm::inconvertibleErrorCode());
266ec727ea7Spatrick }
267ec727ea7Spatrick 
268ec727ea7Spatrick #endif // TARGET_OS_OSX
269