blob: ddb492ab2ff36c9c1a762a8e91770ad98bc9f734 [file] [log] [blame]
//===- 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;
}