xref: /freebsd/contrib/llvm-project/clang/lib/DirectoryWatcher/mac/DirectoryWatcher-mac.cpp (revision f976241773df2260e6170317080761d1c5814fe5)
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/Path.h"
15 #include <CoreServices/CoreServices.h>
16 
17 using namespace llvm;
18 using namespace clang;
19 
20 static FSEventStreamRef createFSEventStream(
21     StringRef Path,
22     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>,
23     dispatch_queue_t);
24 static void stopFSEventStream(FSEventStreamRef);
25 
26 namespace {
27 
28 class DirectoryWatcherMac : public clang::DirectoryWatcher {
29 public:
30   DirectoryWatcherMac(
31       FSEventStreamRef EventStream,
32       std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
33           Receiver,
34       llvm::StringRef WatchedDirPath)
35       : EventStream(EventStream), Receiver(Receiver),
36         WatchedDirPath(WatchedDirPath) {}
37 
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, ""),
45              false);
46   }
47 
48 private:
49   FSEventStreamRef EventStream;
50   std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;
51   const std::string WatchedDirPath;
52 };
53 
54 struct EventStreamContextData {
55   std::string WatchedPath;
56   std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver;
57 
58   EventStreamContextData(
59       std::string &&WatchedPath,
60       std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
61           Receiver)
62       : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {}
63 
64   // Needed for FSEvents
65   static void dispose(const void *ctx) {
66     delete static_cast<const EventStreamContextData *>(ctx);
67   }
68 };
69 } // namespace
70 
71 constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =
72     kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |
73     kFSEventStreamEventFlagMustScanSubDirs;
74 
75 constexpr const FSEventStreamEventFlags ModifyingFileEvents =
76     kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |
77     kFSEventStreamEventFlagItemModified;
78 
79 static void eventStreamCallback(ConstFSEventStreamRef Stream,
80                                 void *ClientCallBackInfo, size_t NumEvents,
81                                 void *EventPaths,
82                                 const FSEventStreamEventFlags EventFlags[],
83                                 const FSEventStreamEventId EventIds[]) {
84   auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo);
85 
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];
90 
91     if (Flags & StreamInvalidatingFlags) {
92       Events.emplace_back(DirectoryWatcher::Event{
93           DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
94       break;
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, ""});
104         break;
105       }
106       // No support for subdirectories - just ignore everything.
107       continue;
108     } else if (Flags & kFSEventStreamEventFlagItemRemoved) {
109       Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
110                           llvm::sys::path::filename(Path));
111       continue;
112     } else if (Flags & ModifyingFileEvents) {
113       if (!getFileStatus(Path).hasValue()) {
114         Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
115                             llvm::sys::path::filename(Path));
116       } else {
117         Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,
118                             llvm::sys::path::filename(Path));
119       }
120       continue;
121     }
122 
123     // default
124     Events.emplace_back(DirectoryWatcher::Event{
125         DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
126     llvm_unreachable("Unknown FSEvent type.");
127   }
128 
129   if (!Events.empty()) {
130     ctx->Receiver(Events, /*IsInitial=*/false);
131   }
132 }
133 
134 FSEventStreamRef createFSEventStream(
135     StringRef Path,
136     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
137     dispatch_queue_t Queue) {
138   if (Path.empty())
139     return nullptr;
140 
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);
149     return PathsToWatch;
150   }();
151 
152   FSEventStreamContext Context = [&]() {
153     std::string RealPath;
154     {
155       SmallString<128> Storage;
156       StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage);
157       char Buffer[PATH_MAX];
158       if (::realpath(P.begin(), Buffer) != nullptr)
159         RealPath = Buffer;
160       else
161         RealPath = Path;
162     }
163 
164     FSEventStreamContext Context;
165     Context.version = 0;
166     Context.info = new EventStreamContextData(std::move(RealPath), Receiver);
167     Context.retain = nullptr;
168     Context.release = EventStreamContextData::dispose;
169     Context.copyDescription = nullptr;
170     return Context;
171   }();
172 
173   FSEventStreamRef Result = FSEventStreamCreate(
174       nullptr, eventStreamCallback, &Context, PathsToWatch,
175       kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0,
176       kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
177   CFRelease(PathsToWatch);
178 
179   return Result;
180 }
181 
182 void stopFSEventStream(FSEventStreamRef EventStream) {
183   if (!EventStream)
184     return;
185   FSEventStreamStop(EventStream);
186   FSEventStreamInvalidate(EventStream);
187   FSEventStreamRelease(EventStream);
188 }
189 
190 std::unique_ptr<DirectoryWatcher> clang::DirectoryWatcher::create(
191     StringRef Path,
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);
196 
197   if (Path.empty())
198     return nullptr;
199 
200   auto EventStream = createFSEventStream(Path, Receiver, Queue);
201   if (!EventStream) {
202     return nullptr;
203   }
204 
205   std::unique_ptr<DirectoryWatcher> Result =
206       llvm::make_unique<DirectoryWatcherMac>(EventStream, Receiver, Path);
207 
208   // We need to copy the data so the lifetime is ok after a const copy is made
209   // for the block.
210   const std::string CopiedPath = Path;
211 
212   auto InitWork = ^{
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);
224   };
225 
226   if (WaitForInitialSync) {
227     dispatch_sync(Queue, InitWork);
228   } else {
229     dispatch_async(Queue, InitWork);
230   }
231 
232   return Result;
233 }
234