clang  10.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 
18 using namespace llvm;
19 using namespace clang;
20 
21 static void stopFSEventStream(FSEventStreamRef);
22 
23 namespace {
24 
25 /// This implementation is based on FSEvents API which implementation is
26 /// aggressively coallescing events. This can manifest as duplicate events.
27 ///
28 /// For example this scenario has been observed:
29 ///
30 /// create foo/bar
31 /// sleep 5 s
32 /// create DirectoryWatcherMac for dir foo
33 /// receive notification: bar EventKind::Modified
34 /// sleep 5 s
35 /// modify foo/bar
36 /// receive notification: bar EventKind::Modified
37 /// receive notification: bar EventKind::Modified
38 /// sleep 5 s
39 /// delete foo/bar
40 /// receive notification: bar EventKind::Modified
41 /// receive notification: bar EventKind::Modified
42 /// receive notification: bar EventKind::Removed
43 class DirectoryWatcherMac : public clang::DirectoryWatcher {
44 public:
45  DirectoryWatcherMac(
46  FSEventStreamRef EventStream,
47  std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
48  Receiver,
49  llvm::StringRef WatchedDirPath)
50  : EventStream(EventStream), Receiver(Receiver),
51  WatchedDirPath(WatchedDirPath) {}
52 
53  ~DirectoryWatcherMac() override {
54  stopFSEventStream(EventStream);
55  EventStream = nullptr;
56  // Now it's safe to use Receiver as the only other concurrent use would have
57  // been in EventStream processing.
58  Receiver(DirectoryWatcher::Event(
59  DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""),
60  false);
61  }
62 
63 private:
64  FSEventStreamRef EventStream;
65  std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;
66  const std::string WatchedDirPath;
67 };
68 
69 struct EventStreamContextData {
70  std::string WatchedPath;
71  std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver;
72 
73  EventStreamContextData(
74  std::string &&WatchedPath,
75  std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
76  Receiver)
77  : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {}
78 
79  // Needed for FSEvents
80  static void dispose(const void *ctx) {
81  delete static_cast<const EventStreamContextData *>(ctx);
82  }
83 };
84 } // namespace
85 
86 constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =
87  kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |
88  kFSEventStreamEventFlagMustScanSubDirs;
89 
90 constexpr const FSEventStreamEventFlags ModifyingFileEvents =
91  kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |
92  kFSEventStreamEventFlagItemModified;
93 
94 static void eventStreamCallback(ConstFSEventStreamRef Stream,
95  void *ClientCallBackInfo, size_t NumEvents,
96  void *EventPaths,
97  const FSEventStreamEventFlags EventFlags[],
98  const FSEventStreamEventId EventIds[]) {
99  auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo);
100 
101  std::vector<DirectoryWatcher::Event> Events;
102  for (size_t i = 0; i < NumEvents; ++i) {
103  StringRef Path = ((const char **)EventPaths)[i];
104  const FSEventStreamEventFlags Flags = EventFlags[i];
105 
106  if (Flags & StreamInvalidatingFlags) {
107  Events.emplace_back(DirectoryWatcher::Event{
108  DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
109  break;
110  } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) {
111  // Subdirectories aren't supported - if some directory got removed it
112  // must've been the watched directory itself.
113  if ((Flags & kFSEventStreamEventFlagItemRemoved) &&
114  Path == ctx->WatchedPath) {
115  Events.emplace_back(DirectoryWatcher::Event{
116  DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""});
117  Events.emplace_back(DirectoryWatcher::Event{
118  DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
119  break;
120  }
121  // No support for subdirectories - just ignore everything.
122  continue;
123  } else if (Flags & kFSEventStreamEventFlagItemRemoved) {
124  Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
125  llvm::sys::path::filename(Path));
126  continue;
127  } else if (Flags & ModifyingFileEvents) {
128  if (!getFileStatus(Path).hasValue()) {
129  Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
130  llvm::sys::path::filename(Path));
131  } else {
132  Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,
133  llvm::sys::path::filename(Path));
134  }
135  continue;
136  }
137 
138  // default
139  Events.emplace_back(DirectoryWatcher::Event{
140  DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
141  llvm_unreachable("Unknown FSEvent type.");
142  }
143 
144  if (!Events.empty()) {
145  ctx->Receiver(Events, /*IsInitial=*/false);
146  }
147 }
148 
149 FSEventStreamRef createFSEventStream(
150  StringRef Path,
151  std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
152  dispatch_queue_t Queue) {
153  if (Path.empty())
154  return nullptr;
155 
156  CFMutableArrayRef PathsToWatch = [&]() {
157  CFMutableArrayRef PathsToWatch =
158  CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
159  CFStringRef CfPathStr =
160  CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(),
161  Path.size(), kCFStringEncodingUTF8, false);
162  CFArrayAppendValue(PathsToWatch, CfPathStr);
163  CFRelease(CfPathStr);
164  return PathsToWatch;
165  }();
166 
167  FSEventStreamContext Context = [&]() {
168  std::string RealPath;
169  {
170  SmallString<128> Storage;
171  StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage);
172  char Buffer[PATH_MAX];
173  if (::realpath(P.begin(), Buffer) != nullptr)
174  RealPath = Buffer;
175  else
176  RealPath = Path;
177  }
178 
179  FSEventStreamContext Context;
180  Context.version = 0;
181  Context.info = new EventStreamContextData(std::move(RealPath), Receiver);
182  Context.retain = nullptr;
183  Context.release = EventStreamContextData::dispose;
184  Context.copyDescription = nullptr;
185  return Context;
186  }();
187 
188  FSEventStreamRef Result = FSEventStreamCreate(
189  nullptr, eventStreamCallback, &Context, PathsToWatch,
190  kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0,
191  kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
192  CFRelease(PathsToWatch);
193 
194  return Result;
195 }
196 
197 void stopFSEventStream(FSEventStreamRef EventStream) {
198  if (!EventStream)
199  return;
200  FSEventStreamStop(EventStream);
201  FSEventStreamInvalidate(EventStream);
202  FSEventStreamRelease(EventStream);
203 }
204 
206  StringRef Path,
207  std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
208  bool WaitForInitialSync) {
209  dispatch_queue_t Queue =
210  dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
211 
212  if (Path.empty())
213  llvm::report_fatal_error(
214  "DirectoryWatcher::create can not accept an empty Path.");
215 
216  auto EventStream = createFSEventStream(Path, Receiver, Queue);
217  assert(EventStream && "EventStream expected to be non-null");
218 
219  std::unique_ptr<DirectoryWatcher> Result =
220  std::make_unique<DirectoryWatcherMac>(EventStream, Receiver, Path);
221 
222  // We need to copy the data so the lifetime is ok after a const copy is made
223  // for the block.
224  const std::string CopiedPath = Path;
225 
226  auto InitWork = ^{
227  // We need to start watching the directory before we start scanning in order
228  // to not miss any event. By dispatching this on the same serial Queue as
229  // the FSEvents will be handled we manage to start watching BEFORE the
230  // inital scan and handling events ONLY AFTER the scan finishes.
231  FSEventStreamSetDispatchQueue(EventStream, Queue);
232  FSEventStreamStart(EventStream);
233  // We need to decrement the ref count for Queue as initialize() will return
234  // and FSEvents has incremented it. Since we have to wait for FSEvents to
235  // take ownership it's the easiest to do it here rather than main thread.
236  dispatch_release(Queue);
237  Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true);
238  };
239 
240  if (WaitForInitialSync) {
241  dispatch_sync(Queue, InitWork);
242  } else {
243  dispatch_async(Queue, InitWork);
244  }
245 
246  return Result;
247 }
Specialize PointerLikeTypeTraits to allow LazyGenerationalUpdatePtr to be placed into a PointerUnion...
Definition: Dominators.h:30
std::vector< DirectoryWatcher::Event > getAsFileEvents(const std::vector< std::string > &Scan)
Create event with EventKind::Added for every element in Scan.
StringRef P
std::vector< std::string > scanDirectory(StringRef Path)
constexpr const FSEventStreamEventFlags StreamInvalidatingFlags
Definition: Format.h:2445
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
constexpr const FSEventStreamEventFlags ModifyingFileEvents
#define bool
Definition: stdbool.h:15
static void stopFSEventStream(FSEventStreamRef)
Optional< sys::fs::file_status > getFileStatus(StringRef Path)
Dataflow Directional Tag Classes.
static void eventStreamCallback(ConstFSEventStreamRef Stream, void *ClientCallBackInfo, size_t NumEvents, void *EventPaths, const FSEventStreamEventFlags EventFlags[], const FSEventStreamEventId EventIds[])
FSEventStreamRef createFSEventStream(StringRef Path, std::function< void(llvm::ArrayRef< DirectoryWatcher::Event >, bool)> Receiver, dispatch_queue_t Queue)
Provides notifications for file changes in a directory.