Skip to content
This repository was archived by the owner on Mar 28, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
196 changes: 196 additions & 0 deletions lib/DirectoryWatcher/DirectoryWatcher-linux.inc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
//===- 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.
//
//===----------------------------------------------------------------------===//

#include "llvm/Support/Errno.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Mutex.h"
#include <thread>
#include <unistd.h>
#include <sys/inotify.h>

namespace {

struct INotifyEvent {
DirectoryWatcher::EventKind K;
std::string Filename;
Optional<sys::fs::file_status> Status;
};

class EventQueue {
DirectoryWatcher::EventReceiver Receiver;
sys::Mutex Mtx;
bool gotInitialScan = false;
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};
}

public:
explicit EventQueue(DirectoryWatcher::EventReceiver receiver)
: Receiver(receiver) {}

void onDirectoryEvents(ArrayRef<INotifyEvent> evts) {
sys::ScopedLock L(Mtx);

if (!gotInitialScan) {
PendingEvents.insert(PendingEvents.end(), evts.begin(), evts.end());
return;
}

SmallVector<DirectoryWatcher::Event, 8> dirEvents;
for (const auto &evt : evts) {
dirEvents.push_back(toDirEvent(evt));
}
Receiver(dirEvents, /*isInitial=*/false);
}

void onInitialScan(std::shared_ptr<DirectoryScan> dirScan) {
sys::ScopedLock L(Mtx);

std::vector<DirectoryWatcher::Event> events = dirScan->getAsFileEvents();
Receiver(events, /*isInitial=*/true);

events.clear();
for (const auto &evt : PendingEvents) {
if (evt.K == DirectoryWatcher::EventKind::Added &&
dirScan->FileIDSet.count(evt.Status->getUniqueID())) {
// Already reported this event at the initial directory scan.
continue;
}
events.push_back(toDirEvent(evt));
}
if (!events.empty()) {
Receiver(events, /*isInitial=*/false);
}

gotInitialScan = true;
PendingEvents.clear();
}
};
} // namespace

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

private:
int inotifyFD = -1;

void stopListening();
};

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

while (1) {
ssize_t numRead = read(inotifyFD, buf, EVT_BUF_LEN);
if (numRead == -1) {
return; // watcher is stopped.
}

SmallVector<INotifyEvent, 8> iEvents;
for (char *p = buf; p < buf + numRead;) {
struct inotify_event *ievt = (struct inotify_event *)p;
p += sizeof(struct inotify_event) + ievt->len;

if (ievt->mask & IN_DELETE_SELF) {
INotifyEvent iEvt{DirectoryWatcher::EventKind::DirectoryDeleted,
pathToWatch, None};
iEvents.push_back(iEvt);
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;
}

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);
}

if (!iEvents.empty())
evtQueue->onDirectoryEvents(iEvents);
}
}

bool DirectoryWatcher::Implementation::initialize(StringRef Path,
EventReceiver Receiver,
bool waitInitialSync,
std::string &errorMsg) {
auto error = [&](StringRef msg) -> bool {
errorMsg = msg;
errorMsg += ": ";
errorMsg += llvm::sys::StrError();
return true;
};

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);
if (wd == -1)
return error("inotify_add_watch failed");

std::thread watchThread(
std::bind(runWatcher, pathToWatch, inotifyFD, evtQueue));
watchThread.detach();

auto initialScan = std::make_shared<DirectoryScan>();
auto runScan = [pathToWatch, initialScan, evtQueue]() {
initialScan->scanDirectory(pathToWatch);
evtQueue->onInitialScan(std::move(initialScan));
};

if (waitInitialSync) {
runScan();
} else {
std::thread scanThread(runScan);
scanThread.detach();
}

return false;
}

void DirectoryWatcher::Implementation::stopListening() {
if (inotifyFD == -1)
return;
close(inotifyFD);
inotifyFD = -1;
}
Loading