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