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