clang  19.0.0git
DirectoryWatcher-mac.cpp
Go to the documentation of this file.
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"
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:
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 
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(
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 
84  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
91  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 
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).has_value()) {
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 
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  {
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 
208 void stopFSEventStream(FSEventStreamRef EventStream) {
209  if (!EventStream)
210  return;
211  FSEventStreamStop(EventStream);
212  FSEventStreamInvalidate(EventStream);
213  FSEventStreamRelease(EventStream);
214 }
215 
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 
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
StringRef P
Provides notifications for file changes in a directory.
static llvm::Expected< std::unique_ptr< DirectoryWatcher > > create(llvm::StringRef Path, std::function< void(llvm::ArrayRef< DirectoryWatcher::Event > Events, bool IsInitial)> Receiver, bool WaitForInitialSync)
llvm fatal_error if
The JSON file list parser is used to communicate input to InstallAPI.
std::optional< sys::fs::file_status > getFileStatus(StringRef Path)
std::vector< std::string > scanDirectory(StringRef Path)
std::vector< DirectoryWatcher::Event > getAsFileEvents(const std::vector< std::string > &Scan)
Create event with EventKind::Added for every element in Scan.
Diagnostic wrappers for TextAPI types for error reporting.
Definition: Dominators.h:30
Definition: Format.h:5433