blob: 826e9bea4fdaed1256e44c1a591205b35598a010 [file] [log] [blame]
//===- DirectoryWatcher-mac.inc.h - Mac-platform directory listening ------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <CoreServices/CoreServices.h>
struct DirectoryWatcher::Implementation {
bool initialize(StringRef Path, EventReceiver Receiver,
bool waitInitialSync, std::string &Error);
~Implementation() {
stopFSEventStream();
};
private:
FSEventStreamRef EventStream = nullptr;
bool setupFSEventStream(StringRef path, EventReceiver receiver,
dispatch_queue_t queue,
std::shared_ptr<DirectoryScan> initialScanPtr);
void stopFSEventStream();
};
namespace {
struct EventStreamContextData {
std::string WatchedPath;
DirectoryWatcher::EventReceiver Receiver;
std::shared_ptr<DirectoryScan> InitialScan;
EventStreamContextData(std::string watchedPath, DirectoryWatcher::EventReceiver receiver,
std::shared_ptr<DirectoryScan> initialScanPtr)
: WatchedPath(std::move(watchedPath)),
Receiver(std::move(receiver)),
InitialScan(std::move(initialScanPtr)) {
}
static void dispose(const void *ctx) {
delete static_cast<const EventStreamContextData*>(ctx);
}
};
}
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 & kFSEventStreamEventFlagItemIsFile)) {
if ((flags & kFSEventStreamEventFlagItemRemoved) && path == ctx->WatchedPath) {
DirectoryWatcher::Event Evt{DirectoryWatcher::EventKind::DirectoryDeleted, path, llvm::sys::TimePoint<>{} };
Events.push_back(Evt);
break;
}
continue;
}
DirectoryWatcher::EventKind K = DirectoryWatcher::EventKind::Modified;
bool hasAddedFlag = flags & (kFSEventStreamEventFlagItemCreated |
kFSEventStreamEventFlagItemRenamed);
bool hasRemovedFlag = flags & kFSEventStreamEventFlagItemRemoved;
Optional<sys::fs::file_status> statusOpt;
// NOTE: With low latency sometimes for a file that is moved inside the
// directory, or for a file that is removed from the directory, the flags
// have both 'renamed' and 'removed'. We use getting the file status as a
// way to distinguish between the two.
if (hasAddedFlag) {
statusOpt = getFileStatus(path);
if (statusOpt.hasValue()) {
K = DirectoryWatcher::EventKind::Added;
} else {
K = DirectoryWatcher::EventKind::Removed;
}
} else if (hasRemovedFlag) {
K = DirectoryWatcher::EventKind::Removed;
} else {
statusOpt = getFileStatus(path);
if (!statusOpt.hasValue()) {
K = DirectoryWatcher::EventKind::Removed;
}
}
if (ctx->InitialScan && K == DirectoryWatcher::EventKind::Added) {
// For the first time we get the events, check that we haven't already
// sent the 'added' event at the initial scan.
if (ctx->InitialScan->FileIDSet.count(statusOpt->getUniqueID())) {
// Already reported this event at the initial directory scan.
continue;
}
}
llvm::sys::TimePoint<> modTime{};
if (statusOpt.hasValue())
modTime = statusOpt->getLastModificationTime();
DirectoryWatcher::Event Evt{K, path, modTime};
Events.push_back(Evt);
}
// We won't need to check again later on.
ctx->InitialScan.reset();
if (!Events.empty()) {
ctx->Receiver(Events, /*isInitial=*/false);
}
}
bool DirectoryWatcher::Implementation::setupFSEventStream(StringRef path,
EventReceiver receiver,
dispatch_queue_t queue,
std::shared_ptr<DirectoryScan> initialScanPtr) {
if (path.empty())
return true;
CFMutableArrayRef pathsToWatch = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
CFStringRef cfPathStr = CFStringCreateWithBytes(nullptr, (const UInt8 *)path.data(), path.size(), kCFStringEncodingUTF8, false);
CFArrayAppendValue(pathsToWatch, cfPathStr);
CFRelease(cfPathStr);
CFAbsoluteTime latency = 0.0; // Latency in seconds.
std::string realPath;
{
SmallString<128> Storage;
StringRef P = llvm::Twine(path).toNullTerminatedStringRef(Storage);
char Buffer[PATH_MAX];
// Use ::realpath to get the real path name
if (::realpath(P.begin(), Buffer) != nullptr)
realPath = Buffer;
else
realPath = path;
}
EventStreamContextData *ctxData =
new EventStreamContextData(std::move(realPath), std::move(receiver),
std::move(initialScanPtr));
FSEventStreamContext context;
context.version = 0;
context.info = ctxData;
context.retain = nullptr;
context.release = EventStreamContextData::dispose;
context.copyDescription = nullptr;
EventStream = FSEventStreamCreate(nullptr,
eventStreamCallback,
&context,
pathsToWatch,
kFSEventStreamEventIdSinceNow,
latency,
kFSEventStreamCreateFlagFileEvents |
kFSEventStreamCreateFlagNoDefer);
CFRelease(pathsToWatch);
if (!EventStream) {
return true;
}
FSEventStreamSetDispatchQueue(EventStream, queue);
FSEventStreamStart(EventStream);
return false;
}
void DirectoryWatcher::Implementation::stopFSEventStream() {
if (!EventStream)
return;
FSEventStreamStop(EventStream);
FSEventStreamInvalidate(EventStream);
FSEventStreamRelease(EventStream);
EventStream = nullptr;
}
bool DirectoryWatcher::Implementation::initialize(StringRef Path,
EventReceiver Receiver, bool waitInitialSync, std::string &Error) {
auto initialScan = std::make_shared<DirectoryScan>();
dispatch_queue_t queue = dispatch_queue_create("DirectoryWatcher", DISPATCH_QUEUE_SERIAL);
dispatch_semaphore_t initScanSema = dispatch_semaphore_create(0);
dispatch_semaphore_t setupFSEventsSema = dispatch_semaphore_create(0);
std::string copiedPath = Path;
dispatch_retain(initScanSema);
dispatch_retain(setupFSEventsSema);
dispatch_async(queue, ^{
// Wait for the event stream to be setup before doing the initial scan,
// to make sure we won't miss any events.
dispatch_semaphore_wait(setupFSEventsSema, DISPATCH_TIME_FOREVER);
initialScan->scanDirectory(copiedPath);
Receiver(initialScan->getAsFileEvents(), /*isInitial=*/true);
dispatch_semaphore_signal(initScanSema);
dispatch_release(setupFSEventsSema);
dispatch_release(initScanSema);
});
bool fsErr = setupFSEventStream(Path, Receiver, queue, initialScan);
dispatch_semaphore_signal(setupFSEventsSema);
if (waitInitialSync) {
dispatch_semaphore_wait(initScanSema, DISPATCH_TIME_FOREVER);
}
dispatch_release(setupFSEventsSema);
dispatch_release(initScanSema);
dispatch_release(queue);
if (fsErr) {
raw_string_ostream(Error) << "failed to setup FSEvents stream for path: " << Path;
return true;
}
return false;
}