clang  9.0.0
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/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 }
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
static FSEventStreamRef createFSEventStream(StringRef Path, std::function< void(llvm::ArrayRef< DirectoryWatcher::Event >, bool)>, dispatch_queue_t)
std::vector< std::string > scanDirectory(StringRef Path)
long i
Definition: xmmintrin.h:1456
constexpr const FSEventStreamEventFlags StreamInvalidatingFlags
Definition: Format.h:2274
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[])
static std::unique_ptr< DirectoryWatcher > create(llvm::StringRef Path, std::function< void(llvm::ArrayRef< DirectoryWatcher::Event > Events, bool IsInitial)> Receiver, bool WaitForInitialSync)
Returns nullptr if.
Provides notifications for file changes in a directory.