177dd8a79SJan Korous //===- DirectoryWatcher-mac.cpp - Mac-platform directory watching ---------===//
277dd8a79SJan Korous //
377dd8a79SJan Korous // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
477dd8a79SJan Korous // See https://llvm.org/LICENSE.txt for license information.
577dd8a79SJan Korous // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
677dd8a79SJan Korous //
777dd8a79SJan Korous //===----------------------------------------------------------------------===//
877dd8a79SJan Korous
977dd8a79SJan Korous #include "DirectoryScanner.h"
1077dd8a79SJan Korous #include "clang/DirectoryWatcher/DirectoryWatcher.h"
1177dd8a79SJan Korous
1277dd8a79SJan Korous #include "llvm/ADT/STLExtras.h"
1377dd8a79SJan Korous #include "llvm/ADT/StringRef.h"
14ef74924fSPuyan Lotfi #include "llvm/Support/Error.h"
1577dd8a79SJan Korous #include "llvm/Support/Path.h"
1677dd8a79SJan Korous #include <CoreServices/CoreServices.h>
17cfb4f8c5SVedant Kumar #include <TargetConditionals.h>
1877dd8a79SJan Korous
1977dd8a79SJan Korous using namespace llvm;
2077dd8a79SJan Korous using namespace clang;
2177dd8a79SJan Korous
22cfb4f8c5SVedant Kumar #if TARGET_OS_OSX
23cfb4f8c5SVedant Kumar
2477dd8a79SJan Korous static void stopFSEventStream(FSEventStreamRef);
2577dd8a79SJan Korous
2677dd8a79SJan Korous namespace {
2777dd8a79SJan Korous
289debb024SJan Korous /// This implementation is based on FSEvents API which implementation is
299debb024SJan Korous /// aggressively coallescing events. This can manifest as duplicate events.
309debb024SJan Korous ///
319debb024SJan Korous /// For example this scenario has been observed:
329debb024SJan Korous ///
339debb024SJan Korous /// create foo/bar
349debb024SJan Korous /// sleep 5 s
359debb024SJan Korous /// create DirectoryWatcherMac for dir foo
369debb024SJan Korous /// receive notification: bar EventKind::Modified
379debb024SJan Korous /// sleep 5 s
389debb024SJan Korous /// modify foo/bar
399debb024SJan Korous /// receive notification: bar EventKind::Modified
409debb024SJan Korous /// receive notification: bar EventKind::Modified
419debb024SJan Korous /// sleep 5 s
429debb024SJan Korous /// delete foo/bar
439debb024SJan Korous /// receive notification: bar EventKind::Modified
449debb024SJan Korous /// receive notification: bar EventKind::Modified
459debb024SJan Korous /// receive notification: bar EventKind::Removed
4677dd8a79SJan Korous class DirectoryWatcherMac : public clang::DirectoryWatcher {
4777dd8a79SJan Korous public:
DirectoryWatcherMac(dispatch_queue_t Queue,FSEventStreamRef EventStream,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,llvm::StringRef WatchedDirPath)4877dd8a79SJan Korous DirectoryWatcherMac(
492ac0c4b4SBen Langmuir dispatch_queue_t Queue, FSEventStreamRef EventStream,
5077dd8a79SJan Korous std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
5177dd8a79SJan Korous Receiver,
5277dd8a79SJan Korous llvm::StringRef WatchedDirPath)
532ac0c4b4SBen Langmuir : Queue(Queue), EventStream(EventStream), Receiver(Receiver),
5477dd8a79SJan Korous WatchedDirPath(WatchedDirPath) {}
5577dd8a79SJan Korous
~DirectoryWatcherMac()5677dd8a79SJan Korous ~DirectoryWatcherMac() override {
572ac0c4b4SBen Langmuir // FSEventStreamStop and Invalidate must be called after Start and
582ac0c4b4SBen Langmuir // SetDispatchQueue to follow FSEvents API contract. The call to Receiver
592ac0c4b4SBen Langmuir // also uses Queue to not race with the initial scan.
602ac0c4b4SBen Langmuir dispatch_sync(Queue, ^{
6177dd8a79SJan Korous stopFSEventStream(EventStream);
6277dd8a79SJan Korous EventStream = nullptr;
632ac0c4b4SBen Langmuir Receiver(
642ac0c4b4SBen Langmuir DirectoryWatcher::Event(
6577dd8a79SJan Korous DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""),
6677dd8a79SJan Korous false);
672ac0c4b4SBen Langmuir });
682ac0c4b4SBen Langmuir
692ac0c4b4SBen Langmuir // Balance initial creation.
702ac0c4b4SBen Langmuir dispatch_release(Queue);
7177dd8a79SJan Korous }
7277dd8a79SJan Korous
7377dd8a79SJan Korous private:
742ac0c4b4SBen Langmuir dispatch_queue_t Queue;
7577dd8a79SJan Korous FSEventStreamRef EventStream;
7677dd8a79SJan Korous std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;
7777dd8a79SJan Korous const std::string WatchedDirPath;
7877dd8a79SJan Korous };
7977dd8a79SJan Korous
8077dd8a79SJan Korous struct EventStreamContextData {
8177dd8a79SJan Korous std::string WatchedPath;
8277dd8a79SJan Korous std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver;
8377dd8a79SJan Korous
EventStreamContextData__anon0cb5cf400111::EventStreamContextData8477dd8a79SJan Korous EventStreamContextData(
8577dd8a79SJan Korous std::string &&WatchedPath,
8677dd8a79SJan Korous std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
8777dd8a79SJan Korous Receiver)
8877dd8a79SJan Korous : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {}
8977dd8a79SJan Korous
9077dd8a79SJan Korous // Needed for FSEvents
dispose__anon0cb5cf400111::EventStreamContextData9177dd8a79SJan Korous static void dispose(const void *ctx) {
9277dd8a79SJan Korous delete static_cast<const EventStreamContextData *>(ctx);
9377dd8a79SJan Korous }
9477dd8a79SJan Korous };
9577dd8a79SJan Korous } // namespace
9677dd8a79SJan Korous
9777dd8a79SJan Korous constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =
9877dd8a79SJan Korous kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |
9977dd8a79SJan Korous kFSEventStreamEventFlagMustScanSubDirs;
10077dd8a79SJan Korous
10177dd8a79SJan Korous constexpr const FSEventStreamEventFlags ModifyingFileEvents =
10277dd8a79SJan Korous kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |
10377dd8a79SJan Korous kFSEventStreamEventFlagItemModified;
10477dd8a79SJan Korous
eventStreamCallback(ConstFSEventStreamRef Stream,void * ClientCallBackInfo,size_t NumEvents,void * EventPaths,const FSEventStreamEventFlags EventFlags[],const FSEventStreamEventId EventIds[])10577dd8a79SJan Korous static void eventStreamCallback(ConstFSEventStreamRef Stream,
10677dd8a79SJan Korous void *ClientCallBackInfo, size_t NumEvents,
10777dd8a79SJan Korous void *EventPaths,
10877dd8a79SJan Korous const FSEventStreamEventFlags EventFlags[],
10977dd8a79SJan Korous const FSEventStreamEventId EventIds[]) {
11077dd8a79SJan Korous auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo);
11177dd8a79SJan Korous
11277dd8a79SJan Korous std::vector<DirectoryWatcher::Event> Events;
11377dd8a79SJan Korous for (size_t i = 0; i < NumEvents; ++i) {
11477dd8a79SJan Korous StringRef Path = ((const char **)EventPaths)[i];
11577dd8a79SJan Korous const FSEventStreamEventFlags Flags = EventFlags[i];
11677dd8a79SJan Korous
11777dd8a79SJan Korous if (Flags & StreamInvalidatingFlags) {
11877dd8a79SJan Korous Events.emplace_back(DirectoryWatcher::Event{
11977dd8a79SJan Korous DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
12077dd8a79SJan Korous break;
12177dd8a79SJan Korous } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) {
12277dd8a79SJan Korous // Subdirectories aren't supported - if some directory got removed it
12377dd8a79SJan Korous // must've been the watched directory itself.
12477dd8a79SJan Korous if ((Flags & kFSEventStreamEventFlagItemRemoved) &&
12577dd8a79SJan Korous Path == ctx->WatchedPath) {
12677dd8a79SJan Korous Events.emplace_back(DirectoryWatcher::Event{
12777dd8a79SJan Korous DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""});
12877dd8a79SJan Korous Events.emplace_back(DirectoryWatcher::Event{
12977dd8a79SJan Korous DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
13077dd8a79SJan Korous break;
13177dd8a79SJan Korous }
13277dd8a79SJan Korous // No support for subdirectories - just ignore everything.
13377dd8a79SJan Korous continue;
13477dd8a79SJan Korous } else if (Flags & kFSEventStreamEventFlagItemRemoved) {
13577dd8a79SJan Korous Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
13677dd8a79SJan Korous llvm::sys::path::filename(Path));
13777dd8a79SJan Korous continue;
13877dd8a79SJan Korous } else if (Flags & ModifyingFileEvents) {
139*e486e48cSThorsten Schütt if (!getFileStatus(Path).has_value()) {
14077dd8a79SJan Korous Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,
14177dd8a79SJan Korous llvm::sys::path::filename(Path));
14277dd8a79SJan Korous } else {
14377dd8a79SJan Korous Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,
14477dd8a79SJan Korous llvm::sys::path::filename(Path));
14577dd8a79SJan Korous }
14677dd8a79SJan Korous continue;
14777dd8a79SJan Korous }
14877dd8a79SJan Korous
14977dd8a79SJan Korous // default
15077dd8a79SJan Korous Events.emplace_back(DirectoryWatcher::Event{
15177dd8a79SJan Korous DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});
15277dd8a79SJan Korous llvm_unreachable("Unknown FSEvent type.");
15377dd8a79SJan Korous }
15477dd8a79SJan Korous
15577dd8a79SJan Korous if (!Events.empty()) {
15677dd8a79SJan Korous ctx->Receiver(Events, /*IsInitial=*/false);
15777dd8a79SJan Korous }
15877dd8a79SJan Korous }
15977dd8a79SJan Korous
createFSEventStream(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,dispatch_queue_t Queue)16077dd8a79SJan Korous FSEventStreamRef createFSEventStream(
16177dd8a79SJan Korous StringRef Path,
16277dd8a79SJan Korous std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
16377dd8a79SJan Korous dispatch_queue_t Queue) {
1641dcf216fSPuyan Lotfi if (Path.empty())
1651dcf216fSPuyan Lotfi return nullptr;
16677dd8a79SJan Korous
16777dd8a79SJan Korous CFMutableArrayRef PathsToWatch = [&]() {
16877dd8a79SJan Korous CFMutableArrayRef PathsToWatch =
16977dd8a79SJan Korous CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
17077dd8a79SJan Korous CFStringRef CfPathStr =
17177dd8a79SJan Korous CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(),
17277dd8a79SJan Korous Path.size(), kCFStringEncodingUTF8, false);
17377dd8a79SJan Korous CFArrayAppendValue(PathsToWatch, CfPathStr);
17477dd8a79SJan Korous CFRelease(CfPathStr);
17577dd8a79SJan Korous return PathsToWatch;
17677dd8a79SJan Korous }();
17777dd8a79SJan Korous
17877dd8a79SJan Korous FSEventStreamContext Context = [&]() {
17977dd8a79SJan Korous std::string RealPath;
18077dd8a79SJan Korous {
18177dd8a79SJan Korous SmallString<128> Storage;
18277dd8a79SJan Korous StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage);
18377dd8a79SJan Korous char Buffer[PATH_MAX];
18477dd8a79SJan Korous if (::realpath(P.begin(), Buffer) != nullptr)
18577dd8a79SJan Korous RealPath = Buffer;
18677dd8a79SJan Korous else
18700d834e0SJonas Devlieghere RealPath = Path.str();
18877dd8a79SJan Korous }
18977dd8a79SJan Korous
19077dd8a79SJan Korous FSEventStreamContext Context;
19177dd8a79SJan Korous Context.version = 0;
19277dd8a79SJan Korous Context.info = new EventStreamContextData(std::move(RealPath), Receiver);
19377dd8a79SJan Korous Context.retain = nullptr;
19477dd8a79SJan Korous Context.release = EventStreamContextData::dispose;
19577dd8a79SJan Korous Context.copyDescription = nullptr;
19677dd8a79SJan Korous return Context;
19777dd8a79SJan Korous }();
19877dd8a79SJan Korous
19977dd8a79SJan Korous FSEventStreamRef Result = FSEventStreamCreate(
20077dd8a79SJan Korous nullptr, eventStreamCallback, &Context, PathsToWatch,
20177dd8a79SJan Korous kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0,
20277dd8a79SJan Korous kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
20377dd8a79SJan Korous CFRelease(PathsToWatch);
20477dd8a79SJan Korous
20577dd8a79SJan Korous return Result;
20677dd8a79SJan Korous }
20777dd8a79SJan Korous
stopFSEventStream(FSEventStreamRef EventStream)20877dd8a79SJan Korous void stopFSEventStream(FSEventStreamRef EventStream) {
20977dd8a79SJan Korous if (!EventStream)
21077dd8a79SJan Korous return;
21177dd8a79SJan Korous FSEventStreamStop(EventStream);
21277dd8a79SJan Korous FSEventStreamInvalidate(EventStream);
21377dd8a79SJan Korous FSEventStreamRelease(EventStream);
21477dd8a79SJan Korous }
21577dd8a79SJan Korous
create(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,bool WaitForInitialSync)216ef74924fSPuyan Lotfi llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create(
21777dd8a79SJan Korous StringRef Path,
21877dd8a79SJan Korous std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
21977dd8a79SJan Korous bool WaitForInitialSync) {
22077dd8a79SJan Korous dispatch_queue_t Queue =
22177dd8a79SJan Korous dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
22277dd8a79SJan Korous
2231dcf216fSPuyan Lotfi if (Path.empty())
2241dcf216fSPuyan Lotfi llvm::report_fatal_error(
2251dcf216fSPuyan Lotfi "DirectoryWatcher::create can not accept an empty Path.");
2261dcf216fSPuyan Lotfi
22777dd8a79SJan Korous auto EventStream = createFSEventStream(Path, Receiver, Queue);
228fe08528cSShoaib Meenai assert(EventStream && "EventStream expected to be non-null");
22977dd8a79SJan Korous
23077dd8a79SJan Korous std::unique_ptr<DirectoryWatcher> Result =
2312ac0c4b4SBen Langmuir std::make_unique<DirectoryWatcherMac>(Queue, EventStream, Receiver, Path);
23277dd8a79SJan Korous
23377dd8a79SJan Korous // We need to copy the data so the lifetime is ok after a const copy is made
23477dd8a79SJan Korous // for the block.
23543a1c805SJonas Devlieghere const std::string CopiedPath = Path.str();
23677dd8a79SJan Korous
23777dd8a79SJan Korous auto InitWork = ^{
23877dd8a79SJan Korous // We need to start watching the directory before we start scanning in order
23977dd8a79SJan Korous // to not miss any event. By dispatching this on the same serial Queue as
24077dd8a79SJan Korous // the FSEvents will be handled we manage to start watching BEFORE the
24177dd8a79SJan Korous // inital scan and handling events ONLY AFTER the scan finishes.
24277dd8a79SJan Korous FSEventStreamSetDispatchQueue(EventStream, Queue);
24377dd8a79SJan Korous FSEventStreamStart(EventStream);
24477dd8a79SJan Korous Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true);
24577dd8a79SJan Korous };
24677dd8a79SJan Korous
24777dd8a79SJan Korous if (WaitForInitialSync) {
24877dd8a79SJan Korous dispatch_sync(Queue, InitWork);
24977dd8a79SJan Korous } else {
25077dd8a79SJan Korous dispatch_async(Queue, InitWork);
25177dd8a79SJan Korous }
25277dd8a79SJan Korous
25377dd8a79SJan Korous return Result;
25477dd8a79SJan Korous }
255cfb4f8c5SVedant Kumar
256cfb4f8c5SVedant Kumar #else // TARGET_OS_OSX
257cfb4f8c5SVedant Kumar
258cfb4f8c5SVedant Kumar llvm::Expected<std::unique_ptr<DirectoryWatcher>>
create(StringRef Path,std::function<void (llvm::ArrayRef<DirectoryWatcher::Event>,bool)> Receiver,bool WaitForInitialSync)259cfb4f8c5SVedant Kumar clang::DirectoryWatcher::create(
260cfb4f8c5SVedant Kumar StringRef Path,
261cfb4f8c5SVedant Kumar std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
262cfb4f8c5SVedant Kumar bool WaitForInitialSync) {
263cfb4f8c5SVedant Kumar return llvm::make_error<llvm::StringError>(
264cfb4f8c5SVedant Kumar "DirectoryWatcher is not implemented for this platform!",
265cfb4f8c5SVedant Kumar llvm::inconvertibleErrorCode());
266cfb4f8c5SVedant Kumar }
267cfb4f8c5SVedant Kumar
268cfb4f8c5SVedant Kumar #endif // TARGET_OS_OSX
269