| //===- DirectoryWatcher-mac.cpp - Mac-platform directory watching ---------===// | 
 | // | 
 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
 | // See https://llvm.org/LICENSE.txt for license information. | 
 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | #include "DirectoryScanner.h" | 
 | #include "clang/DirectoryWatcher/DirectoryWatcher.h" | 
 |  | 
 | #include "llvm/ADT/STLExtras.h" | 
 | #include "llvm/ADT/StringRef.h" | 
 | #include "llvm/Support/Error.h" | 
 | #include "llvm/Support/Path.h" | 
 | #include <CoreServices/CoreServices.h> | 
 | #include <TargetConditionals.h> | 
 |  | 
 | using namespace llvm; | 
 | using namespace clang; | 
 |  | 
 | #if TARGET_OS_OSX | 
 |  | 
 | static void stopFSEventStream(FSEventStreamRef); | 
 |  | 
 | namespace { | 
 |  | 
 | /// This implementation is based on FSEvents API which implementation is | 
 | /// aggressively coallescing events. This can manifest as duplicate events. | 
 | /// | 
 | /// For example this scenario has been observed: | 
 | /// | 
 | /// create foo/bar | 
 | /// sleep 5 s | 
 | /// create DirectoryWatcherMac for dir foo | 
 | /// receive notification: bar EventKind::Modified | 
 | /// sleep 5 s | 
 | /// modify foo/bar | 
 | /// receive notification: bar EventKind::Modified | 
 | /// receive notification: bar EventKind::Modified | 
 | /// sleep 5 s | 
 | /// delete foo/bar | 
 | /// receive notification: bar EventKind::Modified | 
 | /// receive notification: bar EventKind::Modified | 
 | /// receive notification: bar EventKind::Removed | 
 | class DirectoryWatcherMac : public clang::DirectoryWatcher { | 
 | public: | 
 |   DirectoryWatcherMac( | 
 |       dispatch_queue_t Queue, FSEventStreamRef EventStream, | 
 |       std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> | 
 |           Receiver, | 
 |       llvm::StringRef WatchedDirPath) | 
 |       : Queue(Queue), EventStream(EventStream), Receiver(Receiver), | 
 |         WatchedDirPath(WatchedDirPath) {} | 
 |  | 
 |   ~DirectoryWatcherMac() override { | 
 |     // FSEventStreamStop and Invalidate must be called after Start and | 
 |     // SetDispatchQueue to follow FSEvents API contract. The call to Receiver | 
 |     // also uses Queue to not race with the initial scan. | 
 |     dispatch_sync(Queue, ^{ | 
 |       stopFSEventStream(EventStream); | 
 |       EventStream = nullptr; | 
 |       Receiver( | 
 |           DirectoryWatcher::Event( | 
 |               DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""), | 
 |           false); | 
 |     }); | 
 |  | 
 |     // Balance initial creation. | 
 |     dispatch_release(Queue); | 
 |   } | 
 |  | 
 | private: | 
 |   dispatch_queue_t Queue; | 
 |   FSEventStreamRef EventStream; | 
 |   std::function<void(llvm::ArrayRef<Event>, bool)> Receiver; | 
 |   const std::string WatchedDirPath; | 
 | }; | 
 |  | 
 | struct EventStreamContextData { | 
 |   std::string WatchedPath; | 
 |   std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver; | 
 |  | 
 |   EventStreamContextData( | 
 |       std::string &&WatchedPath, | 
 |       std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> | 
 |           Receiver) | 
 |       : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {} | 
 |  | 
 |   // Needed for FSEvents | 
 |   static void dispose(const void *ctx) { | 
 |     delete static_cast<const EventStreamContextData *>(ctx); | 
 |   } | 
 | }; | 
 | } // namespace | 
 |  | 
 | constexpr const FSEventStreamEventFlags StreamInvalidatingFlags = | 
 |     kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped | | 
 |     kFSEventStreamEventFlagMustScanSubDirs; | 
 |  | 
 | constexpr const FSEventStreamEventFlags ModifyingFileEvents = | 
 |     kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed | | 
 |     kFSEventStreamEventFlagItemModified; | 
 |  | 
 | static void eventStreamCallback(ConstFSEventStreamRef Stream, | 
 |                                 void *ClientCallBackInfo, size_t NumEvents, | 
 |                                 void *EventPaths, | 
 |                                 const FSEventStreamEventFlags EventFlags[], | 
 |                                 const FSEventStreamEventId EventIds[]) { | 
 |   auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo); | 
 |  | 
 |   std::vector<DirectoryWatcher::Event> Events; | 
 |   for (size_t i = 0; i < NumEvents; ++i) { | 
 |     StringRef Path = ((const char **)EventPaths)[i]; | 
 |     const FSEventStreamEventFlags Flags = EventFlags[i]; | 
 |  | 
 |     if (Flags & StreamInvalidatingFlags) { | 
 |       Events.emplace_back(DirectoryWatcher::Event{ | 
 |           DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}); | 
 |       break; | 
 |     } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) { | 
 |       // Subdirectories aren't supported - if some directory got removed it | 
 |       // must've been the watched directory itself. | 
 |       if ((Flags & kFSEventStreamEventFlagItemRemoved) && | 
 |           Path == ctx->WatchedPath) { | 
 |         Events.emplace_back(DirectoryWatcher::Event{ | 
 |             DirectoryWatcher::Event::EventKind::WatchedDirRemoved, ""}); | 
 |         Events.emplace_back(DirectoryWatcher::Event{ | 
 |             DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}); | 
 |         break; | 
 |       } | 
 |       // No support for subdirectories - just ignore everything. | 
 |       continue; | 
 |     } else if (Flags & kFSEventStreamEventFlagItemRemoved) { | 
 |       Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed, | 
 |                           llvm::sys::path::filename(Path)); | 
 |       continue; | 
 |     } else if (Flags & ModifyingFileEvents) { | 
 |       if (!getFileStatus(Path).hasValue()) { | 
 |         Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed, | 
 |                             llvm::sys::path::filename(Path)); | 
 |       } else { | 
 |         Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified, | 
 |                             llvm::sys::path::filename(Path)); | 
 |       } | 
 |       continue; | 
 |     } | 
 |  | 
 |     // default | 
 |     Events.emplace_back(DirectoryWatcher::Event{ | 
 |         DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""}); | 
 |     llvm_unreachable("Unknown FSEvent type."); | 
 |   } | 
 |  | 
 |   if (!Events.empty()) { | 
 |     ctx->Receiver(Events, /*IsInitial=*/false); | 
 |   } | 
 | } | 
 |  | 
 | FSEventStreamRef createFSEventStream( | 
 |     StringRef Path, | 
 |     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver, | 
 |     dispatch_queue_t Queue) { | 
 |   if (Path.empty()) | 
 |     return nullptr; | 
 |  | 
 |   CFMutableArrayRef PathsToWatch = [&]() { | 
 |     CFMutableArrayRef PathsToWatch = | 
 |         CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks); | 
 |     CFStringRef CfPathStr = | 
 |         CFStringCreateWithBytes(nullptr, (const UInt8 *)Path.data(), | 
 |                                 Path.size(), kCFStringEncodingUTF8, false); | 
 |     CFArrayAppendValue(PathsToWatch, CfPathStr); | 
 |     CFRelease(CfPathStr); | 
 |     return PathsToWatch; | 
 |   }(); | 
 |  | 
 |   FSEventStreamContext Context = [&]() { | 
 |     std::string RealPath; | 
 |     { | 
 |       SmallString<128> Storage; | 
 |       StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage); | 
 |       char Buffer[PATH_MAX]; | 
 |       if (::realpath(P.begin(), Buffer) != nullptr) | 
 |         RealPath = Buffer; | 
 |       else | 
 |         RealPath = Path.str(); | 
 |     } | 
 |  | 
 |     FSEventStreamContext Context; | 
 |     Context.version = 0; | 
 |     Context.info = new EventStreamContextData(std::move(RealPath), Receiver); | 
 |     Context.retain = nullptr; | 
 |     Context.release = EventStreamContextData::dispose; | 
 |     Context.copyDescription = nullptr; | 
 |     return Context; | 
 |   }(); | 
 |  | 
 |   FSEventStreamRef Result = FSEventStreamCreate( | 
 |       nullptr, eventStreamCallback, &Context, PathsToWatch, | 
 |       kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0, | 
 |       kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer); | 
 |   CFRelease(PathsToWatch); | 
 |  | 
 |   return Result; | 
 | } | 
 |  | 
 | void stopFSEventStream(FSEventStreamRef EventStream) { | 
 |   if (!EventStream) | 
 |     return; | 
 |   FSEventStreamStop(EventStream); | 
 |   FSEventStreamInvalidate(EventStream); | 
 |   FSEventStreamRelease(EventStream); | 
 | } | 
 |  | 
 | llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create( | 
 |     StringRef Path, | 
 |     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver, | 
 |     bool WaitForInitialSync) { | 
 |   dispatch_queue_t Queue = | 
 |       dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL); | 
 |  | 
 |   if (Path.empty()) | 
 |     llvm::report_fatal_error( | 
 |         "DirectoryWatcher::create can not accept an empty Path."); | 
 |  | 
 |   auto EventStream = createFSEventStream(Path, Receiver, Queue); | 
 |   assert(EventStream && "EventStream expected to be non-null"); | 
 |  | 
 |   std::unique_ptr<DirectoryWatcher> Result = | 
 |       std::make_unique<DirectoryWatcherMac>(Queue, EventStream, Receiver, Path); | 
 |  | 
 |   // We need to copy the data so the lifetime is ok after a const copy is made | 
 |   // for the block. | 
 |   const std::string CopiedPath = Path.str(); | 
 |  | 
 |   auto InitWork = ^{ | 
 |     // We need to start watching the directory before we start scanning in order | 
 |     // to not miss any event. By dispatching this on the same serial Queue as | 
 |     // the FSEvents will be handled we manage to start watching BEFORE the | 
 |     // inital scan and handling events ONLY AFTER the scan finishes. | 
 |     FSEventStreamSetDispatchQueue(EventStream, Queue); | 
 |     FSEventStreamStart(EventStream); | 
 |     Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true); | 
 |   }; | 
 |  | 
 |   if (WaitForInitialSync) { | 
 |     dispatch_sync(Queue, InitWork); | 
 |   } else { | 
 |     dispatch_async(Queue, InitWork); | 
 |   } | 
 |  | 
 |   return Result; | 
 | } | 
 |  | 
 | #else // TARGET_OS_OSX | 
 |  | 
 | llvm::Expected<std::unique_ptr<DirectoryWatcher>> | 
 | clang::DirectoryWatcher::create( | 
 |     StringRef Path, | 
 |     std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver, | 
 |     bool WaitForInitialSync) { | 
 |   return llvm::make_error<llvm::StringError>( | 
 |       "DirectoryWatcher is not implemented for this platform!", | 
 |       llvm::inconvertibleErrorCode()); | 
 | } | 
 |  | 
 | #endif // TARGET_OS_OSX |