head	1.1;
branch	1.1.1;
access;
symbols
	netbsd-11-0-RC6:1.1.1.2
	netbsd-11-0-RC5:1.1.1.2
	netbsd-11-0-RC4:1.1.1.2
	netbsd-11-0-RC3:1.1.1.2
	netbsd-11-0-RC2:1.1.1.2
	netbsd-11-0-RC1:1.1.1.2
	perseant-exfatfs-base-20250801:1.1.1.2
	netbsd-11:1.1.1.2.0.8
	netbsd-11-base:1.1.1.2
	netbsd-10-1-RELEASE:1.1.1.2
	perseant-exfatfs-base-20240630:1.1.1.2
	perseant-exfatfs:1.1.1.2.0.6
	perseant-exfatfs-base:1.1.1.2
	netbsd-10-0-RELEASE:1.1.1.2
	netbsd-10-0-RC6:1.1.1.2
	netbsd-10-0-RC5:1.1.1.2
	netbsd-10-0-RC4:1.1.1.2
	netbsd-10-0-RC3:1.1.1.2
	netbsd-10-0-RC2:1.1.1.2
	netbsd-10-0-RC1:1.1.1.2
	netbsd-10:1.1.1.2.0.4
	netbsd-10-base:1.1.1.2
	cjep_sun2x-base1:1.1.1.2
	cjep_sun2x:1.1.1.2.0.2
	cjep_sun2x-base:1.1.1.2
	cjep_staticlib_x-base1:1.1.1.2
	LLVM-249b40b558955afe5ac2b549edcf2d7f859c8cc9:1.1.1.2
	cjep_staticlib_x:1.1.1.1.0.4
	cjep_staticlib_x-base:1.1.1.1
	phil-wifi-20200421:1.1.1.1
	phil-wifi-20200411:1.1.1.1
	is-mlppp:1.1.1.1.0.2
	is-mlppp-base:1.1.1.1
	phil-wifi-20200406:1.1.1.1
	phil-wifi-20191119:1.1.1.1
	LLVM-01f3a59fb3e2542fce74c768718f594d0debd0da:1.1.1.1
	LLVM:1.1.1;
locks; strict;
comment	@// @;


1.1
date	2019.11.08.14.30.01;	author joerg;	state Exp;
branches
	1.1.1.1;
next	;
commitid	lIv69tfiG0KHw3KB;

1.1.1.1
date	2019.11.08.14.30.01;	author joerg;	state Exp;
branches
	1.1.1.1.4.1;
next	1.1.1.2;
commitid	lIv69tfiG0KHw3KB;

1.1.1.2
date	2021.05.30.01.25.36;	author joerg;	state Exp;
branches;
next	;
commitid	uhgdinROdC6tU6VC;

1.1.1.1.4.1
date	2021.05.31.22.07.13;	author cjep;	state Exp;
branches;
next	;
commitid	eWz9SBW0XqKjJlVC;


desc
@@


1.1
log
@Initial revision
@
text
@//===- 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>

using namespace llvm;
using namespace clang;

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(
      FSEventStreamRef EventStream,
      std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)>
          Receiver,
      llvm::StringRef WatchedDirPath)
      : EventStream(EventStream), Receiver(Receiver),
        WatchedDirPath(WatchedDirPath) {}

  ~DirectoryWatcherMac() override {
    stopFSEventStream(EventStream);
    EventStream = nullptr;
    // Now it's safe to use Receiver as the only other concurrent use would have
    // been in EventStream processing.
    Receiver(DirectoryWatcher::Event(
                 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, ""),
             false);
  }

private:
  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;
    }

    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>(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;

  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);
    // We need to decrement the ref count for Queue as initialize() will return
    // and FSEvents has incremented it. Since we have to wait for FSEvents to
    // take ownership it's the easiest to do it here rather than main thread.
    dispatch_release(Queue);
    Receiver(getAsFileEvents(scanDirectory(CopiedPath)), /*IsInitial=*/true);
  };

  if (WaitForInitialSync) {
    dispatch_sync(Queue, InitWork);
  } else {
    dispatch_async(Queue, InitWork);
  }

  return Result;
}
@


1.1.1.1
log
@Import 01f3a59fb3e2542fce74c768718f594d0debd0da from the LLVM mono-repo:
clang (without test/, unittests/, www/)
llvm (without test/, unittests/)
@
text
@@


1.1.1.1.4.1
log
@sync with head
@
text
@a16 1
#include <TargetConditionals.h>
a20 2
#if TARGET_OS_OSX

d46 1
a46 1
      dispatch_queue_t Queue, FSEventStreamRef EventStream,
d50 1
a50 1
      : Queue(Queue), EventStream(EventStream), Receiver(Receiver),
d54 7
a60 14
    // 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);
a63 1
  dispatch_queue_t Queue;
d176 1
a176 1
        RealPath = Path.str();
d220 1
a220 1
      std::make_unique<DirectoryWatcherMac>(Queue, EventStream, Receiver, Path);
d224 1
a224 1
  const std::string CopiedPath = Path.str();
d233 4
a247 14

#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
@


1.1.1.2
log
@Import clang 249b40b558955afe5ac2b549edcf2d7f859c8cc9.
@
text
@a16 1
#include <TargetConditionals.h>
a20 2
#if TARGET_OS_OSX

d46 1
a46 1
      dispatch_queue_t Queue, FSEventStreamRef EventStream,
d50 1
a50 1
      : Queue(Queue), EventStream(EventStream), Receiver(Receiver),
d54 7
a60 14
    // 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);
a63 1
  dispatch_queue_t Queue;
d176 1
a176 1
        RealPath = Path.str();
d220 1
a220 1
      std::make_unique<DirectoryWatcherMac>(Queue, EventStream, Receiver, Path);
d224 1
a224 1
  const std::string CopiedPath = Path.str();
d233 4
a247 14

#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
@

