1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
|
//===- DirectoryWatcher-linux.cpp - Linux-platform directory watching -----===//
//
// 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 "DirectoryScanner.h"
#include "clang/DirectoryWatcher/DirectoryWatcher.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/AlignOf.h"
#include "llvm/Support/Errno.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/Path.h"
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <vector>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/inotify.h>
#include <unistd.h>
namespace {
using namespace llvm;
using namespace clang;
/// Pipe for inter-thread synchronization - for epoll-ing on multiple
/// conditions. It is meant for uni-directional 1:1 signalling - specifically:
/// no multiple consumers, no data passing. Thread waiting for signal should
/// poll the FDRead. Signalling thread should call signal() which writes single
/// character to FDRead.
struct SemaphorePipe {
// Expects two file-descriptors opened as a pipe in the canonical POSIX
// order: pipefd[0] refers to the read end of the pipe. pipefd[1] refers to
// the write end of the pipe.
SemaphorePipe(int pipefd[2])
: FDRead(pipefd[0]), FDWrite(pipefd[1]), OwnsFDs(true) {}
SemaphorePipe(const SemaphorePipe &) = delete;
void operator=(const SemaphorePipe &) = delete;
SemaphorePipe(SemaphorePipe &&other)
: FDRead(other.FDRead), FDWrite(other.FDWrite),
OwnsFDs(other.OwnsFDs) // Someone could have moved from the other
// instance before.
{
other.OwnsFDs = false;
};
void signal() {
#ifndef NDEBUG
ssize_t Result =
#endif
llvm::sys::RetryAfterSignal(-1, write, FDWrite, "A", 1);
assert(Result != -1);
}
~SemaphorePipe() {
if (OwnsFDs) {
close(FDWrite);
close(FDRead);
}
}
const int FDRead;
const int FDWrite;
bool OwnsFDs;
static llvm::Optional<SemaphorePipe> create() {
int InotifyPollingStopperFDs[2];
if (pipe2(InotifyPollingStopperFDs, O_CLOEXEC) == -1)
return llvm::None;
return SemaphorePipe(InotifyPollingStopperFDs);
}
};
/// Mutex-protected queue of Events.
class EventQueue {
std::mutex Mtx;
std::condition_variable NonEmpty;
std::queue<DirectoryWatcher::Event> Events;
public:
void push_back(const DirectoryWatcher::Event::EventKind K,
StringRef Filename) {
{
std::unique_lock<std::mutex> L(Mtx);
Events.emplace(K, Filename);
}
NonEmpty.notify_one();
}
// Blocks on caller thread and uses codition_variable to wait until there's an
// event to return.
DirectoryWatcher::Event pop_front_blocking() {
std::unique_lock<std::mutex> L(Mtx);
while (true) {
// Since we might have missed all the prior notifications on NonEmpty we
// have to check the queue first (under lock).
if (!Events.empty()) {
DirectoryWatcher::Event Front = Events.front();
Events.pop();
return Front;
}
NonEmpty.wait(L, [this]() { return !Events.empty(); });
}
}
};
class DirectoryWatcherLinux : public clang::DirectoryWatcher {
public:
DirectoryWatcherLinux(
llvm::StringRef WatchedDirPath,
std::function<void(llvm::ArrayRef<Event>, bool)> Receiver,
bool WaitForInitialSync, int InotifyFD, int InotifyWD,
SemaphorePipe &&InotifyPollingStopSignal);
~DirectoryWatcherLinux() override {
StopWork();
InotifyPollingThread.join();
EventsReceivingThread.join();
inotify_rm_watch(InotifyFD, InotifyWD);
llvm::sys::RetryAfterSignal(-1, close, InotifyFD);
}
private:
const std::string WatchedDirPath;
// inotify file descriptor
int InotifyFD = -1;
// inotify watch descriptor
int InotifyWD = -1;
EventQueue Queue;
// Make sure lifetime of Receiver fully contains lifetime of
// EventsReceivingThread.
std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;
// Consumes inotify events and pushes directory watcher events to the Queue.
void InotifyPollingLoop();
std::thread InotifyPollingThread;
// Using pipe so we can epoll two file descriptors at once - inotify and
// stopping condition.
SemaphorePipe InotifyPollingStopSignal;
// Does the initial scan of the directory - directly calling Receiver,
// bypassing the Queue. Both InitialScan and EventReceivingLoop use Receiver
// which isn't necessarily thread-safe.
void InitialScan();
// Processing events from the Queue.
// In case client doesn't want to do the initial scan synchronously
// (WaitForInitialSync=false in ctor) we do the initial scan at the beginning
// of this thread.
std::thread EventsReceivingThread;
// Push event of WatcherGotInvalidated kind to the Queue to stop the loop.
// Both InitialScan and EventReceivingLoop use Receiver which isn't
// necessarily thread-safe.
void EventReceivingLoop();
// Stops all the async work. Reentrant.
void StopWork() {
Queue.push_back(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,
"");
InotifyPollingStopSignal.signal();
}
};
void DirectoryWatcherLinux::InotifyPollingLoop() {
// We want to be able to read ~30 events at once even in the worst case
// (obscenely long filenames).
constexpr size_t EventBufferLength =
30 * (sizeof(struct inotify_event) + NAME_MAX + 1);
// http://man7.org/linux/man-pages/man7/inotify.7.html
// Some systems cannot read integer variables if they are not
// properly aligned. On other systems, incorrect alignment may
// decrease performance. Hence, the buffer used for reading from
// the inotify file descriptor should have the same alignment as
// struct inotify_event.
struct Buffer {
alignas(struct inotify_event) char buffer[EventBufferLength];
};
auto ManagedBuffer = std::make_unique<Buffer>();
char *const Buf = ManagedBuffer->buffer;
const int EpollFD = epoll_create1(EPOLL_CLOEXEC);
if (EpollFD == -1) {
StopWork();
return;
}
auto EpollFDGuard = llvm::make_scope_exit([EpollFD]() { close(EpollFD); });
struct epoll_event EventSpec;
EventSpec.events = EPOLLIN;
EventSpec.data.fd = InotifyFD;
if (epoll_ctl(EpollFD, EPOLL_CTL_ADD, InotifyFD, &EventSpec) == -1) {
StopWork();
return;
}
EventSpec.data.fd = InotifyPollingStopSignal.FDRead;
if (epoll_ctl(EpollFD, EPOLL_CTL_ADD, InotifyPollingStopSignal.FDRead,
&EventSpec) == -1) {
StopWork();
return;
}
std::array<struct epoll_event, 2> EpollEventBuffer;
while (true) {
const int EpollWaitResult = llvm::sys::RetryAfterSignal(
-1, epoll_wait, EpollFD, EpollEventBuffer.data(),
EpollEventBuffer.size(), /*timeout=*/-1 /*== infinity*/);
if (EpollWaitResult == -1) {
StopWork();
return;
}
// Multiple epoll_events can be received for a single file descriptor per
// epoll_wait call.
for (int i = 0; i < EpollWaitResult; ++i) {
if (EpollEventBuffer[i].data.fd == InotifyPollingStopSignal.FDRead) {
StopWork();
return;
}
}
// epoll_wait() always return either error or >0 events. Since there was no
// event for stopping, it must be an inotify event ready for reading.
ssize_t NumRead = llvm::sys::RetryAfterSignal(-1, read, InotifyFD, Buf,
EventBufferLength);
for (char *P = Buf; P < Buf + NumRead;) {
if (P + sizeof(struct inotify_event) > Buf + NumRead) {
StopWork();
llvm_unreachable("an incomplete inotify_event was read");
return;
}
struct inotify_event *Event = reinterpret_cast<struct inotify_event *>(P);
P += sizeof(struct inotify_event) + Event->len;
if (Event->mask & (IN_CREATE | IN_MODIFY | IN_MOVED_TO | IN_DELETE) &&
Event->len <= 0) {
StopWork();
llvm_unreachable("expected a filename from inotify");
return;
}
if (Event->mask & (IN_CREATE | IN_MOVED_TO | IN_MODIFY)) {
Queue.push_back(DirectoryWatcher::Event::EventKind::Modified,
Event->name);
} else if (Event->mask & (IN_DELETE | IN_MOVED_FROM)) {
Queue.push_back(DirectoryWatcher::Event::EventKind::Removed,
Event->name);
} else if (Event->mask & (IN_DELETE_SELF | IN_MOVE_SELF)) {
Queue.push_back(DirectoryWatcher::Event::EventKind::WatchedDirRemoved,
"");
StopWork();
return;
} else if (Event->mask & IN_IGNORED) {
StopWork();
return;
} else {
StopWork();
llvm_unreachable("Unknown event type.");
return;
}
}
}
}
void DirectoryWatcherLinux::InitialScan() {
this->Receiver(getAsFileEvents(scanDirectory(WatchedDirPath)),
/*IsInitial=*/true);
}
void DirectoryWatcherLinux::EventReceivingLoop() {
while (true) {
DirectoryWatcher::Event Event = this->Queue.pop_front_blocking();
this->Receiver(Event, false);
if (Event.Kind ==
DirectoryWatcher::Event::EventKind::WatcherGotInvalidated) {
StopWork();
return;
}
}
}
DirectoryWatcherLinux::DirectoryWatcherLinux(
StringRef WatchedDirPath,
std::function<void(llvm::ArrayRef<Event>, bool)> Receiver,
bool WaitForInitialSync, int InotifyFD, int InotifyWD,
SemaphorePipe &&InotifyPollingStopSignal)
: WatchedDirPath(WatchedDirPath), InotifyFD(InotifyFD),
InotifyWD(InotifyWD), Receiver(Receiver),
InotifyPollingStopSignal(std::move(InotifyPollingStopSignal)) {
InotifyPollingThread = std::thread([this]() { InotifyPollingLoop(); });
// We have no guarantees about thread safety of the Receiver which is being
// used in both InitialScan and EventReceivingLoop. We shouldn't run these
// only synchronously.
if (WaitForInitialSync) {
InitialScan();
EventsReceivingThread = std::thread([this]() { EventReceivingLoop(); });
} else {
EventsReceivingThread = std::thread([this]() {
// FIXME: We might want to terminate an async initial scan early in case
// of a failure in EventsReceivingThread.
InitialScan();
EventReceivingLoop();
});
}
}
} // namespace
llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create(
StringRef Path,
std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,
bool WaitForInitialSync) {
if (Path.empty())
llvm::report_fatal_error(
"DirectoryWatcher::create can not accept an empty Path.");
const int InotifyFD = inotify_init1(IN_CLOEXEC);
if (InotifyFD == -1)
return llvm::make_error<llvm::StringError>(
std::string("inotify_init1() error: ") + strerror(errno),
llvm::inconvertibleErrorCode());
const int InotifyWD = inotify_add_watch(
InotifyFD, Path.str().c_str(),
IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY |
IN_MOVED_FROM | IN_MOVE_SELF | IN_MOVED_TO | IN_ONLYDIR | IN_IGNORED
#ifdef IN_EXCL_UNLINK
| IN_EXCL_UNLINK
#endif
);
if (InotifyWD == -1)
return llvm::make_error<llvm::StringError>(
std::string("inotify_add_watch() error: ") + strerror(errno),
llvm::inconvertibleErrorCode());
auto InotifyPollingStopper = SemaphorePipe::create();
if (!InotifyPollingStopper)
return llvm::make_error<llvm::StringError>(
std::string("SemaphorePipe::create() error: ") + strerror(errno),
llvm::inconvertibleErrorCode());
return std::make_unique<DirectoryWatcherLinux>(
Path, Receiver, WaitForInitialSync, InotifyFD, InotifyWD,
std::move(*InotifyPollingStopper));
}
|