1 //===- DirectoryWatcher-mac.cpp - Mac-platform directory watching ---------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "DirectoryScanner.h"
10 #include "clang/DirectoryWatcher/DirectoryWatcher.h"
12 #include "llvm/ADT/STLExtras.h"
13 #include "llvm/ADT/StringRef.h"
14 #include "llvm/Support/Path.h"
15 #include <CoreServices/CoreServices.h>
18 using namespace clang;
20 static FSEventStreamRef createFSEventStream(
22 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>,
24 static void stopFSEventStream(FSEventStreamRef);
28 class DirectoryWatcherMac : public clang::DirectoryWatcher {
31 FSEventStreamRef EventStream,
32 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
34 llvm::StringRef WatchedDirPath)
35 : EventStream(EventStream), Receiver(Receiver),
36 WatchedDirPath(WatchedDirPath) {}
38 ~DirectoryWatcherMac() override {
39 stopFSEventStream(EventStream);
40 EventStream = nullptr;
41 // Now it's safe to use Receiver as the only other concurrent use would have
42 // been in EventStream processing.
43 Receiver(DirectoryWatcher::Event(
44 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""),
49 FSEventStreamRef EventStream;
50 std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;
51 const std::string WatchedDirPath;
54 struct EventStreamContextData {
55 std::string WatchedPath;
56 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver;
58 EventStreamContextData(
59 std::string &&WatchedPath,
60 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
62 : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {}
64 // Needed for FSEvents
65 static void dispose(const void *ctx) {
66 delete static_cast<const EventStreamContextData *>(ctx);
71 constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =
72 kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |
73 kFSEventStreamEventFlagMustScanSubDirs;
75 constexpr const FSEventStreamEventFlags ModifyingFileEvents =
76 kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |
77 kFSEventStreamEventFlagItemModified;
79 static void eventStreamCallback(ConstFSEventStreamRef Stream,
80 void *ClientCallBackInfo, size_t NumEvents,
82 const FSEventStreamEventFlags EventFlags[],
83 const FSEventStreamEventId EventIds[]) {
84 auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo);
86 std::vector<DirectoryWatcher::Event> Events;
87 for (size_t i = 0; i < NumEvents; ++i) {
88 StringRef Path = ((const char **)EventPaths)[i];
89 const FSEventStreamEventFlags Flags = EventFlags[i];
91 if (Flags & StreamInvalidatingFlags) {
92 Events.emplace_back(DirectoryWatcher::Event{
93 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
95 } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) {
96 // Subdirectories aren't supported - if some directory got removed it
97 // must've been the watched directory itself.
98 if ((Flags & kFSEventStreamEventFlagItemRemoved) &&
99 Path == ctx->WatchedPath) {
100 Events.emplace_back(DirectoryWatcher::Event{
101 DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""});
102 Events.emplace_back(DirectoryWatcher::Event{
103 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
106 // No support for subdirectories - just ignore everything.
108 } else if (Flags & kFSEventStreamEventFlagItemRemoved) {
109 Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
110 llvm::sys::path::filename(Path));
112 } else if (Flags & ModifyingFileEvents) {
113 if (!getFileStatus(Path).hasValue()) {
114 Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
115 llvm::sys::path::filename(Path));
117 Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,
118 llvm::sys::path::filename(Path));
124 Events.emplace_back(DirectoryWatcher::Event{
125 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
126 llvm_unreachable("Unknown FSEvent type.");
129 if (!Events.empty()) {
130 ctx->Receiver(Events, /*IsInitial=*/false);
134 FSEventStreamRef createFSEventStream(
136 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
137 dispatch_queue_t Queue) {
141 CFMutableArrayRef PathsToWatch = [&]() {
142 CFMutableArrayRef PathsToWatch =
143 CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
144 CFStringRef CfPathStr =
145 CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(),
146 Path.size(), kCFStringEncodingUTF8, false);
147 CFArrayAppendValue(PathsToWatch, CfPathStr);
148 CFRelease(CfPathStr);
152 FSEventStreamContext Context = [&]() {
153 std::string RealPath;
155 SmallString<128> Storage;
156 StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage);
157 char Buffer[PATH_MAX];
158 if (::realpath(P.begin(), Buffer) != nullptr)
164 FSEventStreamContext Context;
166 Context.info = new EventStreamContextData(std::move(RealPath), Receiver);
167 Context.retain = nullptr;
168 Context.release = EventStreamContextData::dispose;
169 Context.copyDescription = nullptr;
173 FSEventStreamRef Result = FSEventStreamCreate(
174 nullptr, eventStreamCallback, &Context, PathsToWatch,
175 kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0,
176 kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
177 CFRelease(PathsToWatch);
182 void stopFSEventStream(FSEventStreamRef EventStream) {
185 FSEventStreamStop(EventStream);
186 FSEventStreamInvalidate(EventStream);
187 FSEventStreamRelease(EventStream);
190 std::unique_ptr<DirectoryWatcher> clang::DirectoryWatcher::create(
192 std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
193 bool WaitForInitialSync) {
194 dispatch_queue_t Queue =
195 dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
200 auto EventStream = createFSEventStream(Path, Receiver, Queue);
205 std::unique_ptr<DirectoryWatcher> Result =
206 llvm::make_unique<DirectoryWatcherMac>(EventStream, Receiver, Path);
208 // We need to copy the data so the lifetime is ok after a const copy is made
210 const std::string CopiedPath = Path;
213 // We need to start watching the directory before we start scanning in order
214 // to not miss any event. By dispatching this on the same serial Queue as
215 // the FSEvents will be handled we manage to start watching BEFORE the
216 // inital scan and handling events ONLY AFTER the scan finishes.
217 FSEventStreamSetDispatchQueue(EventStream, Queue);
218 FSEventStreamStart(EventStream);
219 // We need to decrement the ref count for Queue as initialize() will return
220 // and FSEvents has incremented it. Since we have to wait for FSEvents to
221 // take ownership it's the easiest to do it here rather than main thread.
222 dispatch_release(Queue);
223 Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true);
226 if (WaitForInitialSync) {
227 dispatch_sync(Queue, InitWork);
229 dispatch_async(Queue, InitWork);