1 //===- DirectoryWatcher-mac.cpp - Mac-platform directory watching ---------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "DirectoryScanner.h"
10 #include "clang/DirectoryWatcher/DirectoryWatcher.h"
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>
20 using namespace clang
;
24 static void stopFSEventStream(FSEventStreamRef
);
28 /// This implementation is based on FSEvents API which implementation is
29 /// aggressively coallescing events. This can manifest as duplicate events.
31 /// For example this scenario has been observed:
35 /// create DirectoryWatcherMac for dir foo
36 /// receive notification: bar EventKind::Modified
39 /// receive notification: bar EventKind::Modified
40 /// receive notification: bar EventKind::Modified
43 /// receive notification: bar EventKind::Modified
44 /// receive notification: bar EventKind::Modified
45 /// receive notification: bar EventKind::Removed
46 class DirectoryWatcherMac
: public clang::DirectoryWatcher
{
49 dispatch_queue_t Queue
, FSEventStreamRef EventStream
,
50 std::function
<void(llvm::ArrayRef
<DirectoryWatcher::Event
>, bool)>
52 llvm::StringRef WatchedDirPath
)
53 : Queue(Queue
), EventStream(EventStream
), Receiver(Receiver
),
54 WatchedDirPath(WatchedDirPath
) {}
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;
64 DirectoryWatcher::Event(
65 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated
, ""),
69 // Balance initial creation.
70 dispatch_release(Queue
);
74 dispatch_queue_t Queue
;
75 FSEventStreamRef EventStream
;
76 std::function
<void(llvm::ArrayRef
<Event
>, bool)> Receiver
;
77 const std::string WatchedDirPath
;
80 struct EventStreamContextData
{
81 std::string WatchedPath
;
82 std::function
<void(llvm::ArrayRef
<DirectoryWatcher::Event
>, bool)> Receiver
;
84 EventStreamContextData(
85 std::string
&&WatchedPath
,
86 std::function
<void(llvm::ArrayRef
<DirectoryWatcher::Event
>, bool)>
88 : WatchedPath(std::move(WatchedPath
)), Receiver(Receiver
) {}
90 // Needed for FSEvents
91 static void dispose(const void *ctx
) {
92 delete static_cast<const EventStreamContextData
*>(ctx
);
97 constexpr const FSEventStreamEventFlags StreamInvalidatingFlags
=
98 kFSEventStreamEventFlagUserDropped
| kFSEventStreamEventFlagKernelDropped
|
99 kFSEventStreamEventFlagMustScanSubDirs
;
101 constexpr const FSEventStreamEventFlags ModifyingFileEvents
=
102 kFSEventStreamEventFlagItemCreated
| kFSEventStreamEventFlagItemRenamed
|
103 kFSEventStreamEventFlagItemModified
;
105 static void eventStreamCallback(ConstFSEventStreamRef Stream
,
106 void *ClientCallBackInfo
, size_t NumEvents
,
108 const FSEventStreamEventFlags EventFlags
[],
109 const FSEventStreamEventId EventIds
[]) {
110 auto *ctx
= static_cast<EventStreamContextData
*>(ClientCallBackInfo
);
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
];
117 if (Flags
& StreamInvalidatingFlags
) {
118 Events
.emplace_back(DirectoryWatcher::Event
{
119 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated
, ""});
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
, ""});
132 // No support for subdirectories - just ignore everything.
134 } else if (Flags
& kFSEventStreamEventFlagItemRemoved
) {
135 Events
.emplace_back(DirectoryWatcher::Event::EventKind::Removed
,
136 llvm::sys::path::filename(Path
));
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
));
143 Events
.emplace_back(DirectoryWatcher::Event::EventKind::Modified
,
144 llvm::sys::path::filename(Path
));
150 Events
.emplace_back(DirectoryWatcher::Event
{
151 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated
, ""});
152 llvm_unreachable("Unknown FSEvent type.");
155 if (!Events
.empty()) {
156 ctx
->Receiver(Events
, /*IsInitial=*/false);
160 FSEventStreamRef
createFSEventStream(
162 std::function
<void(llvm::ArrayRef
<DirectoryWatcher::Event
>, bool)> Receiver
,
163 dispatch_queue_t Queue
) {
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
);
178 FSEventStreamContext Context
= [&]() {
179 std::string RealPath
;
181 SmallString
<128> Storage
;
182 StringRef P
= llvm::Twine(Path
).toNullTerminatedStringRef(Storage
);
183 char Buffer
[PATH_MAX
];
184 if (::realpath(P
.begin(), Buffer
) != nullptr)
187 RealPath
= Path
.str();
190 FSEventStreamContext Context
;
192 Context
.info
= new EventStreamContextData(std::move(RealPath
), Receiver
);
193 Context
.retain
= nullptr;
194 Context
.release
= EventStreamContextData::dispose
;
195 Context
.copyDescription
= nullptr;
199 FSEventStreamRef Result
= FSEventStreamCreate(
200 nullptr, eventStreamCallback
, &Context
, PathsToWatch
,
201 kFSEventStreamEventIdSinceNow
, /* latency in seconds */ 0.0,
202 kFSEventStreamCreateFlagFileEvents
| kFSEventStreamCreateFlagNoDefer
);
203 CFRelease(PathsToWatch
);
208 void stopFSEventStream(FSEventStreamRef EventStream
) {
211 FSEventStreamStop(EventStream
);
212 FSEventStreamInvalidate(EventStream
);
213 FSEventStreamRelease(EventStream
);
216 llvm::Expected
<std::unique_ptr
<DirectoryWatcher
>> clang::DirectoryWatcher::create(
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
);
224 llvm::report_fatal_error(
225 "DirectoryWatcher::create can not accept an empty Path.");
227 auto EventStream
= createFSEventStream(Path
, Receiver
, Queue
);
228 assert(EventStream
&& "EventStream expected to be non-null");
230 std::unique_ptr
<DirectoryWatcher
> Result
=
231 std::make_unique
<DirectoryWatcherMac
>(Queue
, EventStream
, Receiver
, Path
);
233 // We need to copy the data so the lifetime is ok after a const copy is made
235 const std::string CopiedPath
= Path
.str();
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);
247 if (WaitForInitialSync
) {
248 dispatch_sync(Queue
, InitWork
);
250 dispatch_async(Queue
, InitWork
);
256 #else // TARGET_OS_OSX
258 llvm::Expected
<std::unique_ptr
<DirectoryWatcher
>>
259 clang::DirectoryWatcher::create(
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());
268 #endif // TARGET_OS_OSX