/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/DirectoryWatcher/mac/DirectoryWatcher-mac.cpp
Line | Count | Source (jump to first uncovered line) |
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/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 | 8 | WatchedDirPath(WatchedDirPath) {} |
55 | | |
56 | 8 | ~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 | 8 | dispatch_sync(Queue, ^{ |
61 | 8 | stopFSEventStream(EventStream); |
62 | 8 | EventStream = nullptr; |
63 | 8 | Receiver( |
64 | 8 | DirectoryWatcher::Event( |
65 | 8 | DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""), |
66 | 8 | false); |
67 | 8 | }); |
68 | | |
69 | | // Balance initial creation. |
70 | 8 | dispatch_release(Queue); |
71 | 8 | } |
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 | 8 | : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {} |
89 | | |
90 | | // Needed for FSEvents |
91 | 8 | static void dispose(const void *ctx) { |
92 | 8 | delete static_cast<const EventStreamContextData *>(ctx); |
93 | 8 | } |
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 | 4 | const FSEventStreamEventId EventIds[]) { |
110 | 4 | auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo); |
111 | | |
112 | 4 | std::vector<DirectoryWatcher::Event> Events; |
113 | 12 | for (size_t i = 0; i < NumEvents; ++i8 ) { |
114 | 9 | StringRef Path = ((const char **)EventPaths)[i]; |
115 | 9 | const FSEventStreamEventFlags Flags = EventFlags[i]; |
116 | | |
117 | 9 | if (Flags & StreamInvalidatingFlags) { |
118 | 0 | Events.emplace_back(DirectoryWatcher::Event{ |
119 | 0 | DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}); |
120 | 0 | break; |
121 | 9 | } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) { |
122 | | // Subdirectories aren't supported - if some directory got removed it |
123 | | // must've been the watched directory itself. |
124 | 4 | if ((Flags & kFSEventStreamEventFlagItemRemoved) && |
125 | 4 | Path == ctx->WatchedPath1 ) { |
126 | 1 | Events.emplace_back(DirectoryWatcher::Event{ |
127 | 1 | DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""}); |
128 | 1 | Events.emplace_back(DirectoryWatcher::Event{ |
129 | 1 | DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}); |
130 | 1 | break; |
131 | 1 | } |
132 | | // No support for subdirectories - just ignore everything. |
133 | 3 | continue; |
134 | 5 | } else if (Flags & kFSEventStreamEventFlagItemRemoved) { |
135 | 1 | Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed, |
136 | 1 | llvm::sys::path::filename(Path)); |
137 | 1 | continue; |
138 | 4 | } else if (Flags & ModifyingFileEvents) { |
139 | 4 | if (!getFileStatus(Path).has_value()) { |
140 | 0 | Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed, |
141 | 0 | llvm::sys::path::filename(Path)); |
142 | 4 | } else { |
143 | 4 | Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified, |
144 | 4 | llvm::sys::path::filename(Path)); |
145 | 4 | } |
146 | 4 | continue; |
147 | 4 | } |
148 | | |
149 | | // default |
150 | 0 | Events.emplace_back(DirectoryWatcher::Event{ |
151 | 0 | DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}); |
152 | 0 | llvm_unreachable("Unknown FSEvent type."); |
153 | 0 | } |
154 | | |
155 | 4 | if (!Events.empty()) { |
156 | 4 | ctx->Receiver(Events, /*IsInitial=*/false); |
157 | 4 | } |
158 | 4 | } |
159 | | |
160 | | FSEventStreamRef createFSEventStream( |
161 | | StringRef Path, |
162 | | std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver, |
163 | 8 | dispatch_queue_t Queue) { |
164 | 8 | if (Path.empty()) |
165 | 0 | return nullptr; |
166 | | |
167 | 8 | CFMutableArrayRef PathsToWatch = [&]() { |
168 | 8 | CFMutableArrayRef PathsToWatch = |
169 | 8 | CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); |
170 | 8 | CFStringRef CfPathStr = |
171 | 8 | CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(), |
172 | 8 | Path.size(), kCFStringEncodingUTF8, false); |
173 | 8 | CFArrayAppendValue(PathsToWatch, CfPathStr); |
174 | 8 | CFRelease(CfPathStr); |
175 | 8 | return PathsToWatch; |
176 | 8 | }(); |
177 | | |
178 | 8 | FSEventStreamContext Context = [&]() { |
179 | 8 | std::string RealPath; |
180 | 8 | { |
181 | 8 | SmallString<128> Storage; |
182 | 8 | StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage); |
183 | 8 | char Buffer[PATH_MAX]; |
184 | 8 | if (::realpath(P.begin(), Buffer) != nullptr) |
185 | 8 | RealPath = Buffer; |
186 | 0 | else |
187 | 0 | RealPath = Path.str(); |
188 | 8 | } |
189 | | |
190 | 8 | FSEventStreamContext Context; |
191 | 8 | Context.version = 0; |
192 | 8 | Context.info = new EventStreamContextData(std::move(RealPath), Receiver); |
193 | 8 | Context.retain = nullptr; |
194 | 8 | Context.release = EventStreamContextData::dispose; |
195 | 8 | Context.copyDescription = nullptr; |
196 | 8 | return Context; |
197 | 8 | }(); |
198 | | |
199 | 8 | FSEventStreamRef Result = FSEventStreamCreate( |
200 | 8 | nullptr, eventStreamCallback, &Context, PathsToWatch, |
201 | 8 | kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0, |
202 | 8 | kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer); |
203 | 8 | CFRelease(PathsToWatch); |
204 | | |
205 | 8 | return Result; |
206 | 8 | } |
207 | | |
208 | 8 | void stopFSEventStream(FSEventStreamRef EventStream) { |
209 | 8 | if (!EventStream) |
210 | 0 | return; |
211 | 8 | FSEventStreamStop(EventStream); |
212 | 8 | FSEventStreamInvalidate(EventStream); |
213 | 8 | FSEventStreamRelease(EventStream); |
214 | 8 | } |
215 | | |
216 | | llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create( |
217 | | StringRef Path, |
218 | | std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver, |
219 | 8 | bool WaitForInitialSync) { |
220 | 8 | dispatch_queue_t Queue = |
221 | 8 | dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL); |
222 | | |
223 | 8 | if (Path.empty()) |
224 | 0 | llvm::report_fatal_error( |
225 | 0 | "DirectoryWatcher::create can not accept an empty Path."); |
226 | | |
227 | 8 | auto EventStream = createFSEventStream(Path, Receiver, Queue); |
228 | 8 | assert(EventStream && "EventStream expected to be non-null"); |
229 | | |
230 | 8 | std::unique_ptr<DirectoryWatcher> Result = |
231 | 8 | 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 | 8 | const std::string CopiedPath = Path.str(); |
236 | | |
237 | 8 | 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 | 8 | FSEventStreamSetDispatchQueue(EventStream, Queue); |
243 | 8 | FSEventStreamStart(EventStream); |
244 | 8 | Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true); |
245 | 8 | }; |
246 | | |
247 | 8 | if (WaitForInitialSync) { |
248 | 6 | dispatch_sync(Queue, InitWork); |
249 | 6 | } else { |
250 | 2 | dispatch_async(Queue, InitWork); |
251 | 2 | } |
252 | | |
253 | 8 | return Result; |
254 | 8 | } |
255 | | |
256 | | #else // TARGET_OS_OSX |
257 | | |
258 | | llvm::Expected<std::unique_ptr<DirectoryWatcher>> |
259 | | clang::DirectoryWatcher::create( |
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 |