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

Commit 9a77335

Browse files
authored
Merge pull request #219 from akyrtzi/dir-watch-linux
[DirectoryWatcher] Add a linux-specific implementation using inotify
2 parents cbbd496 + 5db0bd1 commit 9a77335

File tree

7 files changed

+783
-227
lines changed

7 files changed

+783
-227
lines changed
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
//===- DirectoryWatcher-linux.inc.h - Linux-platform directory listening --===//
2+
//
3+
// The LLVM Compiler Infrastructure
4+
//
5+
// This file is distributed under the University of Illinois Open Source
6+
// License. See LICENSE.TXT for details.
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
#include "llvm/Support/Errno.h"
11+
#include "llvm/Support/Path.h"
12+
#include "llvm/Support/Mutex.h"
13+
#include <thread>
14+
#include <unistd.h>
15+
#include <sys/inotify.h>
16+
17+
namespace {
18+
19+
struct INotifyEvent {
20+
DirectoryWatcher::EventKind K;
21+
std::string Filename;
22+
Optional<sys::fs::file_status> Status;
23+
};
24+
25+
class EventQueue {
26+
DirectoryWatcher::EventReceiver Receiver;
27+
sys::Mutex Mtx;
28+
bool gotInitialScan = false;
29+
std::vector<INotifyEvent> PendingEvents;
30+
31+
DirectoryWatcher::Event toDirEvent(const INotifyEvent &evt) {
32+
llvm::sys::TimePoint<> modTime{};
33+
if (evt.Status.hasValue()) modTime = evt.Status->getLastModificationTime();
34+
return DirectoryWatcher::Event{evt.K, evt.Filename, modTime};
35+
}
36+
37+
public:
38+
explicit EventQueue(DirectoryWatcher::EventReceiver receiver)
39+
: Receiver(receiver) {}
40+
41+
void onDirectoryEvents(ArrayRef<INotifyEvent> evts) {
42+
sys::ScopedLock L(Mtx);
43+
44+
if (!gotInitialScan) {
45+
PendingEvents.insert(PendingEvents.end(), evts.begin(), evts.end());
46+
return;
47+
}
48+
49+
SmallVector<DirectoryWatcher::Event, 8> dirEvents;
50+
for (const auto &evt : evts) {
51+
dirEvents.push_back(toDirEvent(evt));
52+
}
53+
Receiver(dirEvents, /*isInitial=*/false);
54+
}
55+
56+
void onInitialScan(std::shared_ptr<DirectoryScan> dirScan) {
57+
sys::ScopedLock L(Mtx);
58+
59+
std::vector<DirectoryWatcher::Event> events = dirScan->getAsFileEvents();
60+
Receiver(events, /*isInitial=*/true);
61+
62+
events.clear();
63+
for (const auto &evt : PendingEvents) {
64+
if (evt.K == DirectoryWatcher::EventKind::Added &&
65+
dirScan->FileIDSet.count(evt.Status->getUniqueID())) {
66+
// Already reported this event at the initial directory scan.
67+
continue;
68+
}
69+
events.push_back(toDirEvent(evt));
70+
}
71+
if (!events.empty()) {
72+
Receiver(events, /*isInitial=*/false);
73+
}
74+
75+
gotInitialScan = true;
76+
PendingEvents.clear();
77+
}
78+
};
79+
} // namespace
80+
81+
struct DirectoryWatcher::Implementation {
82+
bool initialize(StringRef Path, EventReceiver Receiver,
83+
bool waitInitialSync, std::string &Error);
84+
~Implementation() {
85+
stopListening();
86+
};
87+
88+
private:
89+
int inotifyFD = -1;
90+
91+
void stopListening();
92+
};
93+
94+
static void runWatcher(std::string pathToWatch, int inotifyFD,
95+
std::shared_ptr<EventQueue> evtQueue) {
96+
#define EVT_BUF_LEN (30 * (sizeof(struct inotify_event) + NAME_MAX + 1))
97+
char buf[EVT_BUF_LEN] __attribute__ ((aligned(8)));
98+
99+
while (1) {
100+
ssize_t numRead = read(inotifyFD, buf, EVT_BUF_LEN);
101+
if (numRead == -1) {
102+
return; // watcher is stopped.
103+
}
104+
105+
SmallVector<INotifyEvent, 8> iEvents;
106+
for (char *p = buf; p < buf + numRead;) {
107+
struct inotify_event *ievt = (struct inotify_event *)p;
108+
p += sizeof(struct inotify_event) + ievt->len;
109+
110+
if (ievt->mask & IN_DELETE_SELF) {
111+
INotifyEvent iEvt{DirectoryWatcher::EventKind::DirectoryDeleted,
112+
pathToWatch, None};
113+
iEvents.push_back(iEvt);
114+
break;
115+
}
116+
117+
DirectoryWatcher::EventKind K = DirectoryWatcher::EventKind::Added;
118+
if (ievt->mask & IN_MODIFY) {
119+
K = DirectoryWatcher::EventKind::Modified;
120+
}
121+
if (ievt->mask & IN_MOVED_TO) {
122+
K = DirectoryWatcher::EventKind::Added;
123+
}
124+
if (ievt->mask & IN_DELETE) {
125+
K = DirectoryWatcher::EventKind::Removed;
126+
}
127+
128+
assert(ievt->len > 0 && "expected a filename from inotify");
129+
SmallString<256> fullPath{pathToWatch};
130+
sys::path::append(fullPath, ievt->name);
131+
132+
Optional<sys::fs::file_status> statusOpt;
133+
if (K != DirectoryWatcher::EventKind::Removed) {
134+
statusOpt = getFileStatus(fullPath);
135+
if (!statusOpt.hasValue())
136+
K = DirectoryWatcher::EventKind::Removed;
137+
}
138+
INotifyEvent iEvt{K, fullPath.str(), statusOpt};
139+
iEvents.push_back(iEvt);
140+
}
141+
142+
if (!iEvents.empty())
143+
evtQueue->onDirectoryEvents(iEvents);
144+
}
145+
}
146+
147+
bool DirectoryWatcher::Implementation::initialize(StringRef Path,
148+
EventReceiver Receiver,
149+
bool waitInitialSync,
150+
std::string &errorMsg) {
151+
auto error = [&](StringRef msg) -> bool {
152+
errorMsg = msg;
153+
errorMsg += ": ";
154+
errorMsg += llvm::sys::StrError();
155+
return true;
156+
};
157+
158+
auto evtQueue = std::make_shared<EventQueue>(std::move(Receiver));
159+
160+
inotifyFD = inotify_init();
161+
if (inotifyFD == -1)
162+
return error("inotify_init failed");
163+
164+
std::string pathToWatch = Path;
165+
int wd = inotify_add_watch(
166+
inotifyFD, pathToWatch.c_str(),
167+
IN_MOVED_TO | IN_DELETE | IN_MODIFY | IN_DELETE_SELF | IN_ONLYDIR);
168+
if (wd == -1)
169+
return error("inotify_add_watch failed");
170+
171+
std::thread watchThread(
172+
std::bind(runWatcher, pathToWatch, inotifyFD, evtQueue));
173+
watchThread.detach();
174+
175+
auto initialScan = std::make_shared<DirectoryScan>();
176+
auto runScan = [pathToWatch, initialScan, evtQueue]() {
177+
initialScan->scanDirectory(pathToWatch);
178+
evtQueue->onInitialScan(std::move(initialScan));
179+
};
180+
181+
if (waitInitialSync) {
182+
runScan();
183+
} else {
184+
std::thread scanThread(runScan);
185+
scanThread.detach();
186+
}
187+
188+
return false;
189+
}
190+
191+
void DirectoryWatcher::Implementation::stopListening() {
192+
if (inotifyFD == -1)
193+
return;
194+
close(inotifyFD);
195+
inotifyFD = -1;
196+
}

0 commit comments

Comments
 (0)