Skip to content
This repository was archived by the owner on Mar 28, 2020. It is now read-only.

Directory watcher #269

Open
wants to merge 17 commits into
base: upstream-with-swift
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions include/clang/DirectoryWatcher/DirectoryWatcher.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
//===- DirectoryWatcher.h - Listens for directory file changes --*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
// 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
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
/// \file
/// \brief Utility class for listening for file system changes in a directory.
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_DIRECTORYWATCHER_DIRECTORYWATCHER_H
Expand Down Expand Up @@ -41,7 +37,6 @@ class DirectoryWatcher {
struct Event {
EventKind Kind;
std::string Filename;
llvm::sys::TimePoint<> ModTime;
};

typedef std::function<void(ArrayRef<Event> Events, bool isInitial)> EventReceiver;
Expand All @@ -64,4 +59,4 @@ class DirectoryWatcher {

} // namespace clang

#endif
#endif // LLVM_CLANG_DIRECTORYWATCHER_DIRECTORYWATCHER_H
30 changes: 23 additions & 7 deletions lib/DirectoryWatcher/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,29 @@ add_clang_library(clangDirectoryWatcher
DirectoryWatcher.cpp
)

if(BUILD_SHARED_LIBS)
if(APPLE)
check_include_files("CoreServices/CoreServices.h" HAVE_CORESERVICES_H)
if(HAVE_CORESERVICES_H)
set(DIRECTORY_WATCHER_FLAGS "${DIRECTORY_WATCHER_FLAGS} -framework CoreServices")
endif()
if(APPLE)
check_include_files("CoreServices/CoreServices.h" HAVE_CORESERVICES)
if(HAVE_CORESERVICES)
set(DIRECTORY_WATCHER_FLAGS "${DIRECTORY_WATCHER_FLAGS} -framework CoreServices")
set_property(TARGET clangDirectoryWatcher APPEND_STRING PROPERTY
LINK_FLAGS ${DIRECTORY_WATCHER_FLAGS})
LINK_FLAGS ${DIRECTORY_WATCHER_FLAGS})
endif()
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
check_include_files("sys/inotify.h" HAVE_INOTIFY)
endif()

llvm_canonicalize_cmake_booleans(
HAVE_CORESERVICES)
llvm_canonicalize_cmake_booleans(
HAVE_INOTIFY)

configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/Config.inc.in
${CMAKE_CURRENT_BINARY_DIR}/Config.inc
)

if(BUILD_SHARED_LIBS AND APPLE AND HAVE_CORESERVICES)
set(DIRECTORY_WATCHER_FLAGS "${DIRECTORY_WATCHER_FLAGS} -framework CoreServices")
set_property(TARGET clangDirectoryWatcher APPEND_STRING PROPERTY
LINK_FLAGS ${DIRECTORY_WATCHER_FLAGS})
endif()
2 changes: 2 additions & 0 deletions lib/DirectoryWatcher/Config.inc.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#define HAVE_CORESERVICES @HAVE_CORESERVICES@
#define HAVE_INOTIFY @HAVE_INOTIFY@
74 changes: 32 additions & 42 deletions lib/DirectoryWatcher/DirectoryWatcher-linux.inc.h
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
//===- DirectoryWatcher-linux.inc.h - Linux-platform directory listening --===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
// 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 "llvm/Support/Errno.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Mutex.h"
#include "llvm/Support/Path.h"
#include <sys/inotify.h>
#include <thread>
#include <unistd.h>
#include <sys/inotify.h>

namespace {

Expand All @@ -29,9 +28,7 @@ class EventQueue {
std::vector<INotifyEvent> PendingEvents;

DirectoryWatcher::Event toDirEvent(const INotifyEvent &evt) {
llvm::sys::TimePoint<> modTime{};
if (evt.Status.hasValue()) modTime = evt.Status->getLastModificationTime();
return DirectoryWatcher::Event{evt.K, evt.Filename, modTime};
return DirectoryWatcher::Event{evt.K, evt.Filename};
}

public:
Expand Down Expand Up @@ -76,14 +73,12 @@ class EventQueue {
PendingEvents.clear();
}
};
} // namespace
} // namespace

struct DirectoryWatcher::Implementation {
bool initialize(StringRef Path, EventReceiver Receiver,
bool waitInitialSync, std::string &Error);
~Implementation() {
stopListening();
};
bool initialize(StringRef Path, EventReceiver Receiver, bool waitInitialSync,
std::string &Error);
~Implementation() { stopListening(); };

private:
int inotifyFD = -1;
Expand All @@ -93,18 +88,19 @@ struct DirectoryWatcher::Implementation {

static void runWatcher(std::string pathToWatch, int inotifyFD,
std::shared_ptr<EventQueue> evtQueue) {
#define EVT_BUF_LEN (30 * (sizeof(struct inotify_event) + NAME_MAX + 1))
char buf[EVT_BUF_LEN] __attribute__ ((aligned(8)));
constexpr size_t EventBufferLength =
30 * (sizeof(struct inotify_event) + NAME_MAX + 1);
char buf[EventBufferLength] __attribute__((aligned(8)));

while (1) {
ssize_t numRead = read(inotifyFD, buf, EVT_BUF_LEN);
if (numRead == -1) {
return; // watcher is stopped.
}
ssize_t numRead = llvm::sys::RetryAfterSignal(
-1, read, inotifyFD, reinterpret_cast<char *>(buf), EventBufferLength);

SmallVector<INotifyEvent, 8> iEvents;
for (char *p = buf; p < buf + numRead;) {
struct inotify_event *ievt = (struct inotify_event *)p;
assert(p + sizeof(struct inotify_event) <= buf + numRead &&
"a whole inotify_event was read");
struct inotify_event *ievt = reinterpret_cast<struct inotify_event *>(p);
p += sizeof(struct inotify_event) + ievt->len;

if (ievt->mask & IN_DELETE_SELF) {
Expand All @@ -114,27 +110,21 @@ static void runWatcher(std::string pathToWatch, int inotifyFD,
break;
}

DirectoryWatcher::EventKind K = DirectoryWatcher::EventKind::Added;
if (ievt->mask & IN_MODIFY) {
K = DirectoryWatcher::EventKind::Modified;
}
if (ievt->mask & IN_MOVED_TO) {
K = DirectoryWatcher::EventKind::Added;
}
if (ievt->mask & IN_DELETE) {
K = DirectoryWatcher::EventKind::Removed;
}
DirectoryWatcher::EventKind K = [&ievt]() {
if (ievt->mask & IN_MODIFY)
return DirectoryWatcher::EventKind::Modified;
if (ievt->mask & IN_MOVED_TO)
return DirectoryWatcher::EventKind::Added;
if (ievt->mask & IN_DELETE)
return DirectoryWatcher::EventKind::Removed;
llvm_unreachable("Unknown event type.");
}();

assert(ievt->len > 0 && "expected a filename from inotify");
SmallString<256> fullPath{pathToWatch};
sys::path::append(fullPath, ievt->name);

Optional<sys::fs::file_status> statusOpt;
if (K != DirectoryWatcher::EventKind::Removed) {
statusOpt = getFileStatus(fullPath);
if (!statusOpt.hasValue())
K = DirectoryWatcher::EventKind::Removed;
}
INotifyEvent iEvt{K, fullPath.str(), statusOpt};
iEvents.push_back(iEvt);
}
Expand All @@ -155,16 +145,16 @@ bool DirectoryWatcher::Implementation::initialize(StringRef Path,
return true;
};

auto evtQueue = std::make_shared<EventQueue>(std::move(Receiver));
auto evtQueue = std::make_shared<EventQueue>(std::move(Receiver));

inotifyFD = inotify_init();
if (inotifyFD == -1)
return error("inotify_init failed");

std::string pathToWatch = Path;
int wd = inotify_add_watch(
inotifyFD, pathToWatch.c_str(),
IN_MOVED_TO | IN_DELETE | IN_MODIFY | IN_DELETE_SELF | IN_ONLYDIR);
int wd = inotify_add_watch(inotifyFD, pathToWatch.c_str(),
IN_MOVED_TO | IN_DELETE | IN_MODIFY |
IN_DELETE_SELF | IN_ONLYDIR);
if (wd == -1)
return error("inotify_add_watch failed");

Expand All @@ -191,6 +181,6 @@ bool DirectoryWatcher::Implementation::initialize(StringRef Path,
void DirectoryWatcher::Implementation::stopListening() {
if (inotifyFD == -1)
return;
close(inotifyFD);
llvm::sys::RetryAfterSignal(-1, close, inotifyFD);
inotifyFD = -1;
}
95 changes: 45 additions & 50 deletions lib/DirectoryWatcher/DirectoryWatcher-mac.inc.h
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
//===- 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.
// 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 <CoreServices/CoreServices.h>

struct DirectoryWatcher::Implementation {
bool initialize(StringRef Path, EventReceiver Receiver,
bool waitInitialSync, std::string &Error);
~Implementation() {
stopFSEventStream();
};
bool initialize(StringRef Path, EventReceiver Receiver, bool waitInitialSync,
std::string &Error);
~Implementation() { stopFSEventStream(); };

private:
FSEventStreamRef EventStream = nullptr;

Expand All @@ -30,35 +28,34 @@ struct EventStreamContextData {
DirectoryWatcher::EventReceiver Receiver;
std::shared_ptr<DirectoryScan> InitialScan;

EventStreamContextData(std::string watchedPath, DirectoryWatcher::EventReceiver receiver,
EventStreamContextData(std::string watchedPath,
DirectoryWatcher::EventReceiver receiver,
std::shared_ptr<DirectoryScan> initialScanPtr)
: WatchedPath(std::move(watchedPath)),
Receiver(std::move(receiver)),
InitialScan(std::move(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);
delete static_cast<const EventStreamContextData *>(ctx);
}
};
}
} // namespace

static void eventStreamCallback(
ConstFSEventStreamRef stream,
void *clientCallBackInfo,
size_t numEvents,
void *eventPaths,
const FSEventStreamEventFlags eventFlags[],
const FSEventStreamEventId eventIds[]) {
auto *ctx = static_cast<EventStreamContextData*>(clientCallBackInfo);
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<>{} };
if ((flags & kFSEventStreamEventFlagItemRemoved) &&
path == ctx->WatchedPath) {
DirectoryWatcher::Event Evt{
DirectoryWatcher::EventKind::DirectoryDeleted, path};
Events.push_back(Evt);
break;
}
Expand Down Expand Up @@ -98,10 +95,7 @@ static void eventStreamCallback(
}
}

llvm::sys::TimePoint<> modTime{};
if (statusOpt.hasValue())
modTime = statusOpt->getLastModificationTime();
DirectoryWatcher::Event Evt{K, path, modTime};
DirectoryWatcher::Event Evt{K, path};
Events.push_back(Evt);
}

Expand All @@ -113,15 +107,17 @@ static void eventStreamCallback(
}
}

bool DirectoryWatcher::Implementation::setupFSEventStream(StringRef path,
EventReceiver receiver,
dispatch_queue_t queue,
std::shared_ptr<DirectoryScan> initialScanPtr) {
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);
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.
Expand All @@ -138,24 +134,19 @@ bool DirectoryWatcher::Implementation::setupFSEventStream(StringRef path,
realPath = path;
}

EventStreamContextData *ctxData =
new EventStreamContextData(std::move(realPath), std::move(receiver),
std::move(initialScanPtr));
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);
EventStream = FSEventStreamCreate(
nullptr, eventStreamCallback, &context, pathsToWatch,
kFSEventStreamEventIdSinceNow, latency,
kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagNoDefer);
CFRelease(pathsToWatch);
if (!EventStream) {
return true;
Expand All @@ -175,10 +166,13 @@ void DirectoryWatcher::Implementation::stopFSEventStream() {
}

bool DirectoryWatcher::Implementation::initialize(StringRef Path,
EventReceiver Receiver, bool waitInitialSync, std::string &Error) {
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_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);

Expand Down Expand Up @@ -206,7 +200,8 @@ bool DirectoryWatcher::Implementation::initialize(StringRef Path,
dispatch_release(queue);

if (fsErr) {
raw_string_ostream(Error) << "failed to setup FSEvents stream for path: " << Path;
raw_string_ostream(Error)
<< "failed to setup FSEvents stream for path: " << Path;
return true;
}

Expand Down
Loading