10b57cec5SDimitry Andric //===- DirectoryWatcher-mac.cpp - Mac-platform directory watching ---------===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric
90b57cec5SDimitry Andric #include "DirectoryScanner.h"
100b57cec5SDimitry Andric #include "clang/DirectoryWatcher/DirectoryWatcher.h"
110b57cec5SDimitry Andric
120b57cec5SDimitry Andric #include "llvm/ADT/STLExtras.h"
130b57cec5SDimitry Andric #include "llvm/ADT/StringRef.h"
14a7dea167SDimitry Andric #include "llvm/Support/Error.h"
150b57cec5SDimitry Andric #include "llvm/Support/Path.h"
160b57cec5SDimitry Andric #include <CoreServices/CoreServices.h>
175ffd83dbSDimitry Andric #include <TargetConditionals.h>
180b57cec5SDimitry Andric
190b57cec5SDimitry Andric using namespace llvm;
200b57cec5SDimitry Andric using namespace clang;
210b57cec5SDimitry Andric
225ffd83dbSDimitry Andric #if TARGET_OS_OSX
235ffd83dbSDimitry Andric
240b57cec5SDimitry Andric static void stopFSEventStream(FSEventStreamRef);
250b57cec5SDimitry Andric
260b57cec5SDimitry Andric namespace {
270b57cec5SDimitry Andric
28a7dea167SDimitry Andric /// This implementation is based on FSEvents API which implementation is
29a7dea167SDimitry Andric /// aggressively coallescing events. This can manifest as duplicate events.
30a7dea167SDimitry Andric ///
31a7dea167SDimitry Andric /// For example this scenario has been observed:
32a7dea167SDimitry Andric ///
33a7dea167SDimitry Andric /// create foo/bar
34a7dea167SDimitry Andric /// sleep 5 s
35a7dea167SDimitry Andric /// create DirectoryWatcherMac for dir foo
36a7dea167SDimitry Andric /// receive notification: bar EventKind::Modified
37a7dea167SDimitry Andric /// sleep 5 s
38a7dea167SDimitry Andric /// modify foo/bar
39a7dea167SDimitry Andric /// receive notification: bar EventKind::Modified
40a7dea167SDimitry Andric /// receive notification: bar EventKind::Modified
41a7dea167SDimitry Andric /// sleep 5 s
42a7dea167SDimitry Andric /// delete foo/bar
43a7dea167SDimitry Andric /// receive notification: bar EventKind::Modified
44a7dea167SDimitry Andric /// receive notification: bar EventKind::Modified
45a7dea167SDimitry Andric /// receive notification: bar EventKind::Removed
460b57cec5SDimitry Andric class DirectoryWatcherMac : public clang::DirectoryWatcher {
470b57cec5SDimitry Andric public:
DirectoryWatcherMac(dispatch_queue_t Queue,FSEventStreamRef EventStream,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,llvm::StringRef WatchedDirPath)480b57cec5SDimitry Andric DirectoryWatcherMac(
495ffd83dbSDimitry Andric dispatch_queue_t Queue, FSEventStreamRef EventStream,
500b57cec5SDimitry Andric std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
510b57cec5SDimitry Andric Receiver,
520b57cec5SDimitry Andric llvm::StringRef WatchedDirPath)
535ffd83dbSDimitry Andric : Queue(Queue), EventStream(EventStream), Receiver(Receiver),
540b57cec5SDimitry Andric WatchedDirPath(WatchedDirPath) {}
550b57cec5SDimitry Andric
~DirectoryWatcherMac()560b57cec5SDimitry Andric ~DirectoryWatcherMac() override {
575ffd83dbSDimitry Andric // FSEventStreamStop and Invalidate must be called after Start and
585ffd83dbSDimitry Andric // SetDispatchQueue to follow FSEvents API contract. The call to Receiver
595ffd83dbSDimitry Andric // also uses Queue to not race with the initial scan.
605ffd83dbSDimitry Andric dispatch_sync(Queue, ^{
610b57cec5SDimitry Andric stopFSEventStream(EventStream);
620b57cec5SDimitry Andric EventStream = nullptr;
635ffd83dbSDimitry Andric Receiver(
645ffd83dbSDimitry Andric DirectoryWatcher::Event(
650b57cec5SDimitry Andric DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""),
660b57cec5SDimitry Andric false);
675ffd83dbSDimitry Andric });
685ffd83dbSDimitry Andric
695ffd83dbSDimitry Andric // Balance initial creation.
705ffd83dbSDimitry Andric dispatch_release(Queue);
710b57cec5SDimitry Andric }
720b57cec5SDimitry Andric
730b57cec5SDimitry Andric private:
745ffd83dbSDimitry Andric dispatch_queue_t Queue;
750b57cec5SDimitry Andric FSEventStreamRef EventStream;
760b57cec5SDimitry Andric std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;
770b57cec5SDimitry Andric const std::string WatchedDirPath;
780b57cec5SDimitry Andric };
790b57cec5SDimitry Andric
800b57cec5SDimitry Andric struct EventStreamContextData {
810b57cec5SDimitry Andric std::string WatchedPath;
820b57cec5SDimitry Andric std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver;
830b57cec5SDimitry Andric
EventStreamContextData__anone34d839f0111::EventStreamContextData840b57cec5SDimitry Andric EventStreamContextData(
850b57cec5SDimitry Andric std::string &&WatchedPath,
860b57cec5SDimitry Andric std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
870b57cec5SDimitry Andric Receiver)
880b57cec5SDimitry Andric : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {}
890b57cec5SDimitry Andric
900b57cec5SDimitry Andric // Needed for FSEvents
dispose__anone34d839f0111::EventStreamContextData910b57cec5SDimitry Andric static void dispose(const void *ctx) {
920b57cec5SDimitry Andric delete static_cast<const EventStreamContextData *>(ctx);
930b57cec5SDimitry Andric }
940b57cec5SDimitry Andric };
950b57cec5SDimitry Andric } // namespace
960b57cec5SDimitry Andric
970b57cec5SDimitry Andric constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =
980b57cec5SDimitry Andric kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |
990b57cec5SDimitry Andric kFSEventStreamEventFlagMustScanSubDirs;
1000b57cec5SDimitry Andric
1010b57cec5SDimitry Andric constexpr const FSEventStreamEventFlags ModifyingFileEvents =
1020b57cec5SDimitry Andric kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |
1030b57cec5SDimitry Andric kFSEventStreamEventFlagItemModified;
1040b57cec5SDimitry Andric
eventStreamCallback(ConstFSEventStreamRef Stream,void * ClientCallBackInfo,size_t NumEvents,void * EventPaths,const FSEventStreamEventFlags EventFlags[],const FSEventStreamEventId EventIds[])1050b57cec5SDimitry Andric static void eventStreamCallback(ConstFSEventStreamRef Stream,
1060b57cec5SDimitry Andric void *ClientCallBackInfo, size_t NumEvents,
1070b57cec5SDimitry Andric void *EventPaths,
1080b57cec5SDimitry Andric const FSEventStreamEventFlags EventFlags[],
1090b57cec5SDimitry Andric const FSEventStreamEventId EventIds[]) {
1100b57cec5SDimitry Andric auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo);
1110b57cec5SDimitry Andric
1120b57cec5SDimitry Andric std::vector<DirectoryWatcher::Event> Events;
1130b57cec5SDimitry Andric for (size_t i = 0; i < NumEvents; ++i) {
1140b57cec5SDimitry Andric StringRef Path = ((const char **)EventPaths)[i];
1150b57cec5SDimitry Andric const FSEventStreamEventFlags Flags = EventFlags[i];
1160b57cec5SDimitry Andric
1170b57cec5SDimitry Andric if (Flags & StreamInvalidatingFlags) {
1180b57cec5SDimitry Andric Events.emplace_back(DirectoryWatcher::Event{
1190b57cec5SDimitry Andric DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
1200b57cec5SDimitry Andric break;
1210b57cec5SDimitry Andric } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) {
1220b57cec5SDimitry Andric // Subdirectories aren't supported - if some directory got removed it
1230b57cec5SDimitry Andric // must've been the watched directory itself.
1240b57cec5SDimitry Andric if ((Flags & kFSEventStreamEventFlagItemRemoved) &&
1250b57cec5SDimitry Andric Path == ctx->WatchedPath) {
1260b57cec5SDimitry Andric Events.emplace_back(DirectoryWatcher::Event{
1270b57cec5SDimitry Andric DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""});
1280b57cec5SDimitry Andric Events.emplace_back(DirectoryWatcher::Event{
1290b57cec5SDimitry Andric DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
1300b57cec5SDimitry Andric break;
1310b57cec5SDimitry Andric }
1320b57cec5SDimitry Andric // No support for subdirectories - just ignore everything.
1330b57cec5SDimitry Andric continue;
1340b57cec5SDimitry Andric } else if (Flags & kFSEventStreamEventFlagItemRemoved) {
1350b57cec5SDimitry Andric Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
1360b57cec5SDimitry Andric llvm::sys::path::filename(Path));
1370b57cec5SDimitry Andric continue;
1380b57cec5SDimitry Andric } else if (Flags & ModifyingFileEvents) {
139*bdd1243dSDimitry Andric if (!getFileStatus(Path).has_value()) {
1400b57cec5SDimitry Andric Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
1410b57cec5SDimitry Andric llvm::sys::path::filename(Path));
1420b57cec5SDimitry Andric } else {
1430b57cec5SDimitry Andric Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,
1440b57cec5SDimitry Andric llvm::sys::path::filename(Path));
1450b57cec5SDimitry Andric }
1460b57cec5SDimitry Andric continue;
1470b57cec5SDimitry Andric }
1480b57cec5SDimitry Andric
1490b57cec5SDimitry Andric // default
1500b57cec5SDimitry Andric Events.emplace_back(DirectoryWatcher::Event{
1510b57cec5SDimitry Andric DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
1520b57cec5SDimitry Andric llvm_unreachable("Unknown FSEvent type.");
1530b57cec5SDimitry Andric }
1540b57cec5SDimitry Andric
1550b57cec5SDimitry Andric if (!Events.empty()) {
1560b57cec5SDimitry Andric ctx->Receiver(Events, /*IsInitial=*/false);
1570b57cec5SDimitry Andric }
1580b57cec5SDimitry Andric }
1590b57cec5SDimitry Andric
createFSEventStream(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,dispatch_queue_t Queue)1600b57cec5SDimitry Andric FSEventStreamRef createFSEventStream(
1610b57cec5SDimitry Andric StringRef Path,
1620b57cec5SDimitry Andric std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
1630b57cec5SDimitry Andric dispatch_queue_t Queue) {
1640b57cec5SDimitry Andric if (Path.empty())
1650b57cec5SDimitry Andric return nullptr;
1660b57cec5SDimitry Andric
1670b57cec5SDimitry Andric CFMutableArrayRef PathsToWatch = [&]() {
1680b57cec5SDimitry Andric CFMutableArrayRef PathsToWatch =
1690b57cec5SDimitry Andric CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
1700b57cec5SDimitry Andric CFStringRef CfPathStr =
1710b57cec5SDimitry Andric CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(),
1720b57cec5SDimitry Andric Path.size(), kCFStringEncodingUTF8, false);
1730b57cec5SDimitry Andric CFArrayAppendValue(PathsToWatch, CfPathStr);
1740b57cec5SDimitry Andric CFRelease(CfPathStr);
1750b57cec5SDimitry Andric return PathsToWatch;
1760b57cec5SDimitry Andric }();
1770b57cec5SDimitry Andric
1780b57cec5SDimitry Andric FSEventStreamContext Context = [&]() {
1790b57cec5SDimitry Andric std::string RealPath;
1800b57cec5SDimitry Andric {
1810b57cec5SDimitry Andric SmallString<128> Storage;
1820b57cec5SDimitry Andric StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage);
1830b57cec5SDimitry Andric char Buffer[PATH_MAX];
1840b57cec5SDimitry Andric if (::realpath(P.begin(), Buffer) != nullptr)
1850b57cec5SDimitry Andric RealPath = Buffer;
1860b57cec5SDimitry Andric else
1875ffd83dbSDimitry Andric RealPath = Path.str();
1880b57cec5SDimitry Andric }
1890b57cec5SDimitry Andric
1900b57cec5SDimitry Andric FSEventStreamContext Context;
1910b57cec5SDimitry Andric Context.version = 0;
1920b57cec5SDimitry Andric Context.info = new EventStreamContextData(std::move(RealPath), Receiver);
1930b57cec5SDimitry Andric Context.retain = nullptr;
1940b57cec5SDimitry Andric Context.release = EventStreamContextData::dispose;
1950b57cec5SDimitry Andric Context.copyDescription = nullptr;
1960b57cec5SDimitry Andric return Context;
1970b57cec5SDimitry Andric }();
1980b57cec5SDimitry Andric
1990b57cec5SDimitry Andric FSEventStreamRef Result = FSEventStreamCreate(
2000b57cec5SDimitry Andric nullptr, eventStreamCallback, &Context, PathsToWatch,
2010b57cec5SDimitry Andric kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0,
2020b57cec5SDimitry Andric kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
2030b57cec5SDimitry Andric CFRelease(PathsToWatch);
2040b57cec5SDimitry Andric
2050b57cec5SDimitry Andric return Result;
2060b57cec5SDimitry Andric }
2070b57cec5SDimitry Andric
stopFSEventStream(FSEventStreamRef EventStream)2080b57cec5SDimitry Andric void stopFSEventStream(FSEventStreamRef EventStream) {
2090b57cec5SDimitry Andric if (!EventStream)
2100b57cec5SDimitry Andric return;
2110b57cec5SDimitry Andric FSEventStreamStop(EventStream);
2120b57cec5SDimitry Andric FSEventStreamInvalidate(EventStream);
2130b57cec5SDimitry Andric FSEventStreamRelease(EventStream);
2140b57cec5SDimitry Andric }
2150b57cec5SDimitry Andric
create(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,bool WaitForInitialSync)216a7dea167SDimitry Andric llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create(
2170b57cec5SDimitry Andric StringRef Path,
2180b57cec5SDimitry Andric std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
2190b57cec5SDimitry Andric bool WaitForInitialSync) {
2200b57cec5SDimitry Andric dispatch_queue_t Queue =
2210b57cec5SDimitry Andric dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
2220b57cec5SDimitry Andric
2230b57cec5SDimitry Andric if (Path.empty())
224a7dea167SDimitry Andric llvm::report_fatal_error(
225a7dea167SDimitry Andric "DirectoryWatcher::create can not accept an empty Path.");
2260b57cec5SDimitry Andric
2270b57cec5SDimitry Andric auto EventStream = createFSEventStream(Path, Receiver, Queue);
228a7dea167SDimitry Andric assert(EventStream && "EventStream expected to be non-null");
2290b57cec5SDimitry Andric
2300b57cec5SDimitry Andric std::unique_ptr<DirectoryWatcher> Result =
2315ffd83dbSDimitry Andric std::make_unique<DirectoryWatcherMac>(Queue, EventStream, Receiver, Path);
2320b57cec5SDimitry Andric
2330b57cec5SDimitry Andric // We need to copy the data so the lifetime is ok after a const copy is made
2340b57cec5SDimitry Andric // for the block.
2355ffd83dbSDimitry Andric const std::string CopiedPath = Path.str();
2360b57cec5SDimitry Andric
2370b57cec5SDimitry Andric auto InitWork = ^{
2380b57cec5SDimitry Andric // We need to start watching the directory before we start scanning in order
2390b57cec5SDimitry Andric // to not miss any event. By dispatching this on the same serial Queue as
2400b57cec5SDimitry Andric // the FSEvents will be handled we manage to start watching BEFORE the
2410b57cec5SDimitry Andric // inital scan and handling events ONLY AFTER the scan finishes.
2420b57cec5SDimitry Andric FSEventStreamSetDispatchQueue(EventStream, Queue);
2430b57cec5SDimitry Andric FSEventStreamStart(EventStream);
2440b57cec5SDimitry Andric Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true);
2450b57cec5SDimitry Andric };
2460b57cec5SDimitry Andric
2470b57cec5SDimitry Andric if (WaitForInitialSync) {
2480b57cec5SDimitry Andric dispatch_sync(Queue, InitWork);
2490b57cec5SDimitry Andric } else {
2500b57cec5SDimitry Andric dispatch_async(Queue, InitWork);
2510b57cec5SDimitry Andric }
2520b57cec5SDimitry Andric
2530b57cec5SDimitry Andric return Result;
2540b57cec5SDimitry Andric }
2555ffd83dbSDimitry Andric
2565ffd83dbSDimitry Andric #else // TARGET_OS_OSX
2575ffd83dbSDimitry Andric
2585ffd83dbSDimitry Andric llvm::Expected<std::unique_ptr<DirectoryWatcher>>
create(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,bool WaitForInitialSync)2595ffd83dbSDimitry Andric clang::DirectoryWatcher::create(
2605ffd83dbSDimitry Andric StringRef Path,
2615ffd83dbSDimitry Andric std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
2625ffd83dbSDimitry Andric bool WaitForInitialSync) {
2635ffd83dbSDimitry Andric return llvm::make_error<llvm::StringError>(
2645ffd83dbSDimitry Andric "DirectoryWatcher is not implemented for this platform!",
2655ffd83dbSDimitry Andric llvm::inconvertibleErrorCode());
2665ffd83dbSDimitry Andric }
2675ffd83dbSDimitry Andric
2685ffd83dbSDimitry Andric #endif // TARGET_OS_OSX
269