brintos

brintos / llvm-project-archived public Read only

0
0
Text · 9.2 KiB · b8788ba Raw
269 lines · cpp
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-exception6//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 19using namespace llvm;20using namespace clang;21 22#if TARGET_OS_OSX23 24static void stopFSEventStream(FSEventStreamRef);25 26namespace {27 28/// This implementation is based on FSEvents API which implementation is29/// aggressively coallescing events. This can manifest as duplicate events.30///31/// For example this scenario has been observed:32///33/// create foo/bar34/// sleep 5 s35/// create DirectoryWatcherMac for dir foo36/// receive notification: bar EventKind::Modified37/// sleep 5 s38/// modify foo/bar39/// receive notification: bar EventKind::Modified40/// receive notification: bar EventKind::Modified41/// sleep 5 s42/// delete foo/bar43/// receive notification: bar EventKind::Modified44/// receive notification: bar EventKind::Modified45/// receive notification: bar EventKind::Removed46class DirectoryWatcherMac : public clang::DirectoryWatcher {47public: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        WatchedDirPath(WatchedDirPath) {}55 56  ~DirectoryWatcherMac() override {57    // FSEventStreamStop and Invalidate must be called after Start and58    // SetDispatchQueue to follow FSEvents API contract. The call to Receiver59    // also uses Queue to not race with the initial scan.60    dispatch_sync(Queue, ^{61      stopFSEventStream(EventStream);62      EventStream = nullptr;63      Receiver(64          DirectoryWatcher::Event(65              DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""),66          false);67    });68 69    // Balance initial creation.70    dispatch_release(Queue);71  }72 73private:74  dispatch_queue_t Queue;75  FSEventStreamRef EventStream;76  std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;77  const std::string WatchedDirPath;78};79 80struct 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      : WatchedPath(std::move(WatchedPath)), Receiver(Receiver) {}89 90  // Needed for FSEvents91  static void dispose(const void *ctx) {92    delete static_cast<const EventStreamContextData *>(ctx);93  }94};95} // namespace96 97constexpr const FSEventStreamEventFlags StreamInvalidatingFlags =98    kFSEventStreamEventFlagUserDropped | kFSEventStreamEventFlagKernelDropped |99    kFSEventStreamEventFlagMustScanSubDirs;100 101constexpr const FSEventStreamEventFlags ModifyingFileEvents =102    kFSEventStreamEventFlagItemCreated | kFSEventStreamEventFlagItemRenamed |103    kFSEventStreamEventFlagItemModified;104 105static void eventStreamCallback(ConstFSEventStreamRef Stream,106                                void *ClientCallBackInfo, size_t NumEvents,107                                void *EventPaths,108                                const FSEventStreamEventFlags EventFlags[],109                                const FSEventStreamEventId EventIds[]) {110  auto *ctx = static_cast<EventStreamContextData *>(ClientCallBackInfo);111 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];116 117    if (Flags & StreamInvalidatingFlags) {118      Events.emplace_back(DirectoryWatcher::Event{119          DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});120      break;121    } else if (!(Flags & kFSEventStreamEventFlagItemIsFile)) {122      // Subdirectories aren't supported - if some directory got removed it123      // 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, ""});130        break;131      }132      // No support for subdirectories - just ignore everything.133      continue;134    } else if (Flags & kFSEventStreamEventFlagItemRemoved) {135      Events.emplace_back(DirectoryWatcher::Event::EventKind::Removed,136                          llvm::sys::path::filename(Path));137      continue;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));142      } else {143        Events.emplace_back(DirectoryWatcher::Event::EventKind::Modified,144                            llvm::sys::path::filename(Path));145      }146      continue;147    }148 149    // default150    Events.emplace_back(DirectoryWatcher::Event{151        DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""});152    llvm_unreachable("Unknown FSEvent type.");153  }154 155  if (!Events.empty()) {156    ctx->Receiver(Events, /*IsInitial=*/false);157  }158}159 160FSEventStreamRef createFSEventStream(161    StringRef Path,162    std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,163    dispatch_queue_t Queue) {164  if (Path.empty())165    return nullptr;166 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);175    return PathsToWatch;176  }();177 178  FSEventStreamContext Context = [&]() {179    std::string RealPath;180    {181      SmallString<128> Storage;182      StringRef P = llvm::Twine(Path).toNullTerminatedStringRef(Storage);183      char Buffer[PATH_MAX];184      if (::realpath(P.begin(), Buffer) != nullptr)185        RealPath = Buffer;186      else187        RealPath = Path.str();188    }189 190    FSEventStreamContext Context;191    Context.version = 0;192    Context.info = new EventStreamContextData(std::move(RealPath), Receiver);193    Context.retain = nullptr;194    Context.release = EventStreamContextData::dispose;195    Context.copyDescription = nullptr;196    return Context;197  }();198 199  FSEventStreamRef Result = FSEventStreamCreate(200      nullptr, eventStreamCallback, &Context, PathsToWatch,201      kFSEventStreamEventIdSinceNow, /* latency in seconds */ 0.0,202      kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);203  CFRelease(PathsToWatch);204 205  return Result;206}207 208void stopFSEventStream(FSEventStreamRef EventStream) {209  if (!EventStream)210    return;211  FSEventStreamStop(EventStream);212  FSEventStreamInvalidate(EventStream);213  FSEventStreamRelease(EventStream);214}215 216llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create(217    StringRef Path,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);222 223  if (Path.empty())224    llvm::report_fatal_error(225        "DirectoryWatcher::create can not accept an empty Path.");226 227  auto EventStream = createFSEventStream(Path, Receiver, Queue);228  assert(EventStream && "EventStream expected to be non-null");229 230  std::unique_ptr<DirectoryWatcher> Result =231      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 made234  // for the block.235  const std::string CopiedPath = Path.str();236 237  auto InitWork = ^{238    // We need to start watching the directory before we start scanning in order239    // to not miss any event. By dispatching this on the same serial Queue as240    // the FSEvents will be handled we manage to start watching BEFORE the241    // inital scan and handling events ONLY AFTER the scan finishes.242    FSEventStreamSetDispatchQueue(EventStream, Queue);243    FSEventStreamStart(EventStream);244    Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true);245  };246 247  if (WaitForInitialSync) {248    dispatch_sync(Queue, InitWork);249  } else {250    dispatch_async(Queue, InitWork);251  }252 253  return Result;254}255 256#else // TARGET_OS_OSX257 258llvm::Expected<std::unique_ptr<DirectoryWatcher>>259clang::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_OSX269