Path: blob/main/contrib/llvm-project/clang/lib/DirectoryWatcher/linux/DirectoryWatcher-linux.cpp
35291 views
//===- DirectoryWatcher-linux.cpp - Linux-platform directory watching -----===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "DirectoryScanner.h"9#include "clang/DirectoryWatcher/DirectoryWatcher.h"1011#include "llvm/ADT/STLExtras.h"12#include "llvm/ADT/ScopeExit.h"13#include "llvm/Support/AlignOf.h"14#include "llvm/Support/Errno.h"15#include "llvm/Support/Error.h"16#include "llvm/Support/Path.h"17#include <atomic>18#include <condition_variable>19#include <mutex>20#include <queue>21#include <string>22#include <thread>23#include <vector>2425#include <fcntl.h>26#include <limits.h>27#include <optional>28#include <sys/epoll.h>29#include <sys/inotify.h>30#include <unistd.h>3132namespace {3334using namespace llvm;35using namespace clang;3637/// Pipe for inter-thread synchronization - for epoll-ing on multiple38/// conditions. It is meant for uni-directional 1:1 signalling - specifically:39/// no multiple consumers, no data passing. Thread waiting for signal should40/// poll the FDRead. Signalling thread should call signal() which writes single41/// character to FDRead.42struct SemaphorePipe {43// Expects two file-descriptors opened as a pipe in the canonical POSIX44// order: pipefd[0] refers to the read end of the pipe. pipefd[1] refers to45// the write end of the pipe.46SemaphorePipe(int pipefd[2])47: FDRead(pipefd[0]), FDWrite(pipefd[1]), OwnsFDs(true) {}48SemaphorePipe(const SemaphorePipe &) = delete;49void operator=(const SemaphorePipe &) = delete;50SemaphorePipe(SemaphorePipe &&other)51: FDRead(other.FDRead), FDWrite(other.FDWrite),52OwnsFDs(other.OwnsFDs) // Someone could have moved from the other53// instance before.54{55other.OwnsFDs = false;56};5758void signal() {59#ifndef NDEBUG60ssize_t Result =61#endif62llvm::sys::RetryAfterSignal(-1, write, FDWrite, "A", 1);63assert(Result != -1);64}65~SemaphorePipe() {66if (OwnsFDs) {67close(FDWrite);68close(FDRead);69}70}71const int FDRead;72const int FDWrite;73bool OwnsFDs;7475static std::optional<SemaphorePipe> create() {76int InotifyPollingStopperFDs[2];77if (pipe2(InotifyPollingStopperFDs, O_CLOEXEC) == -1)78return std::nullopt;79return SemaphorePipe(InotifyPollingStopperFDs);80}81};8283/// Mutex-protected queue of Events.84class EventQueue {85std::mutex Mtx;86std::condition_variable NonEmpty;87std::queue<DirectoryWatcher::Event> Events;8889public:90void push_back(const DirectoryWatcher::Event::EventKind K,91StringRef Filename) {92{93std::unique_lock<std::mutex> L(Mtx);94Events.emplace(K, Filename);95}96NonEmpty.notify_one();97}9899// Blocks on caller thread and uses codition_variable to wait until there's an100// event to return.101DirectoryWatcher::Event pop_front_blocking() {102std::unique_lock<std::mutex> L(Mtx);103while (true) {104// Since we might have missed all the prior notifications on NonEmpty we105// have to check the queue first (under lock).106if (!Events.empty()) {107DirectoryWatcher::Event Front = Events.front();108Events.pop();109return Front;110}111NonEmpty.wait(L, [this]() { return !Events.empty(); });112}113}114};115116class DirectoryWatcherLinux : public clang::DirectoryWatcher {117public:118DirectoryWatcherLinux(119llvm::StringRef WatchedDirPath,120std::function<void(llvm::ArrayRef<Event>, bool)> Receiver,121bool WaitForInitialSync, int InotifyFD, int InotifyWD,122SemaphorePipe &&InotifyPollingStopSignal);123124~DirectoryWatcherLinux() override {125StopWork();126InotifyPollingThread.join();127EventsReceivingThread.join();128inotify_rm_watch(InotifyFD, InotifyWD);129llvm::sys::RetryAfterSignal(-1, close, InotifyFD);130}131132private:133const std::string WatchedDirPath;134// inotify file descriptor135int InotifyFD = -1;136// inotify watch descriptor137int InotifyWD = -1;138139EventQueue Queue;140141// Make sure lifetime of Receiver fully contains lifetime of142// EventsReceivingThread.143std::function<void(llvm::ArrayRef<Event>, bool)> Receiver;144145// Consumes inotify events and pushes directory watcher events to the Queue.146void InotifyPollingLoop();147std::thread InotifyPollingThread;148// Using pipe so we can epoll two file descriptors at once - inotify and149// stopping condition.150SemaphorePipe InotifyPollingStopSignal;151152// Does the initial scan of the directory - directly calling Receiver,153// bypassing the Queue. Both InitialScan and EventReceivingLoop use Receiver154// which isn't necessarily thread-safe.155void InitialScan();156157// Processing events from the Queue.158// In case client doesn't want to do the initial scan synchronously159// (WaitForInitialSync=false in ctor) we do the initial scan at the beginning160// of this thread.161std::thread EventsReceivingThread;162// Push event of WatcherGotInvalidated kind to the Queue to stop the loop.163// Both InitialScan and EventReceivingLoop use Receiver which isn't164// necessarily thread-safe.165void EventReceivingLoop();166167// Stops all the async work. Reentrant.168void StopWork() {169Queue.push_back(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,170"");171InotifyPollingStopSignal.signal();172}173};174175void DirectoryWatcherLinux::InotifyPollingLoop() {176// We want to be able to read ~30 events at once even in the worst case177// (obscenely long filenames).178constexpr size_t EventBufferLength =17930 * (sizeof(struct inotify_event) + NAME_MAX + 1);180// http://man7.org/linux/man-pages/man7/inotify.7.html181// Some systems cannot read integer variables if they are not182// properly aligned. On other systems, incorrect alignment may183// decrease performance. Hence, the buffer used for reading from184// the inotify file descriptor should have the same alignment as185// struct inotify_event.186187struct Buffer {188alignas(struct inotify_event) char buffer[EventBufferLength];189};190auto ManagedBuffer = std::make_unique<Buffer>();191char *const Buf = ManagedBuffer->buffer;192193const int EpollFD = epoll_create1(EPOLL_CLOEXEC);194if (EpollFD == -1) {195StopWork();196return;197}198auto EpollFDGuard = llvm::make_scope_exit([EpollFD]() { close(EpollFD); });199200struct epoll_event EventSpec;201EventSpec.events = EPOLLIN;202EventSpec.data.fd = InotifyFD;203if (epoll_ctl(EpollFD, EPOLL_CTL_ADD, InotifyFD, &EventSpec) == -1) {204StopWork();205return;206}207208EventSpec.data.fd = InotifyPollingStopSignal.FDRead;209if (epoll_ctl(EpollFD, EPOLL_CTL_ADD, InotifyPollingStopSignal.FDRead,210&EventSpec) == -1) {211StopWork();212return;213}214215std::array<struct epoll_event, 2> EpollEventBuffer;216217while (true) {218const int EpollWaitResult = llvm::sys::RetryAfterSignal(219-1, epoll_wait, EpollFD, EpollEventBuffer.data(),220EpollEventBuffer.size(), /*timeout=*/-1 /*== infinity*/);221if (EpollWaitResult == -1) {222StopWork();223return;224}225226// Multiple epoll_events can be received for a single file descriptor per227// epoll_wait call.228for (int i = 0; i < EpollWaitResult; ++i) {229if (EpollEventBuffer[i].data.fd == InotifyPollingStopSignal.FDRead) {230StopWork();231return;232}233}234235// epoll_wait() always return either error or >0 events. Since there was no236// event for stopping, it must be an inotify event ready for reading.237ssize_t NumRead = llvm::sys::RetryAfterSignal(-1, read, InotifyFD, Buf,238EventBufferLength);239for (char *P = Buf; P < Buf + NumRead;) {240if (P + sizeof(struct inotify_event) > Buf + NumRead) {241StopWork();242llvm_unreachable("an incomplete inotify_event was read");243return;244}245246struct inotify_event *Event = reinterpret_cast<struct inotify_event *>(P);247P += sizeof(struct inotify_event) + Event->len;248249if (Event->mask & (IN_CREATE | IN_MODIFY | IN_MOVED_TO | IN_DELETE) &&250Event->len <= 0) {251StopWork();252llvm_unreachable("expected a filename from inotify");253return;254}255256if (Event->mask & (IN_CREATE | IN_MOVED_TO | IN_MODIFY)) {257Queue.push_back(DirectoryWatcher::Event::EventKind::Modified,258Event->name);259} else if (Event->mask & (IN_DELETE | IN_MOVED_FROM)) {260Queue.push_back(DirectoryWatcher::Event::EventKind::Removed,261Event->name);262} else if (Event->mask & (IN_DELETE_SELF | IN_MOVE_SELF)) {263Queue.push_back(DirectoryWatcher::Event::EventKind::WatchedDirRemoved,264"");265StopWork();266return;267} else if (Event->mask & IN_IGNORED) {268StopWork();269return;270} else {271StopWork();272llvm_unreachable("Unknown event type.");273return;274}275}276}277}278279void DirectoryWatcherLinux::InitialScan() {280this->Receiver(getAsFileEvents(scanDirectory(WatchedDirPath)),281/*IsInitial=*/true);282}283284void DirectoryWatcherLinux::EventReceivingLoop() {285while (true) {286DirectoryWatcher::Event Event = this->Queue.pop_front_blocking();287this->Receiver(Event, false);288if (Event.Kind ==289DirectoryWatcher::Event::EventKind::WatcherGotInvalidated) {290StopWork();291return;292}293}294}295296DirectoryWatcherLinux::DirectoryWatcherLinux(297StringRef WatchedDirPath,298std::function<void(llvm::ArrayRef<Event>, bool)> Receiver,299bool WaitForInitialSync, int InotifyFD, int InotifyWD,300SemaphorePipe &&InotifyPollingStopSignal)301: WatchedDirPath(WatchedDirPath), InotifyFD(InotifyFD),302InotifyWD(InotifyWD), Receiver(Receiver),303InotifyPollingStopSignal(std::move(InotifyPollingStopSignal)) {304305InotifyPollingThread = std::thread([this]() { InotifyPollingLoop(); });306// We have no guarantees about thread safety of the Receiver which is being307// used in both InitialScan and EventReceivingLoop. We shouldn't run these308// only synchronously.309if (WaitForInitialSync) {310InitialScan();311EventsReceivingThread = std::thread([this]() { EventReceivingLoop(); });312} else {313EventsReceivingThread = std::thread([this]() {314// FIXME: We might want to terminate an async initial scan early in case315// of a failure in EventsReceivingThread.316InitialScan();317EventReceivingLoop();318});319}320}321322} // namespace323324llvm::Expected<std::unique_ptr<DirectoryWatcher>> clang::DirectoryWatcher::create(325StringRef Path,326std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver,327bool WaitForInitialSync) {328if (Path.empty())329llvm::report_fatal_error(330"DirectoryWatcher::create can not accept an empty Path.");331332const int InotifyFD = inotify_init1(IN_CLOEXEC);333if (InotifyFD == -1)334return llvm::make_error<llvm::StringError>(335llvm::errnoAsErrorCode(), std::string(": inotify_init1()"));336337const int InotifyWD = inotify_add_watch(338InotifyFD, Path.str().c_str(),339IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY |340IN_MOVED_FROM | IN_MOVE_SELF | IN_MOVED_TO | IN_ONLYDIR | IN_IGNORED341#ifdef IN_EXCL_UNLINK342| IN_EXCL_UNLINK343#endif344);345if (InotifyWD == -1)346return llvm::make_error<llvm::StringError>(347llvm::errnoAsErrorCode(), std::string(": inotify_add_watch()"));348349auto InotifyPollingStopper = SemaphorePipe::create();350351if (!InotifyPollingStopper)352return llvm::make_error<llvm::StringError>(353llvm::errnoAsErrorCode(), std::string(": SemaphorePipe::create()"));354355return std::make_unique<DirectoryWatcherLinux>(356Path, Receiver, WaitForInitialSync, InotifyFD, InotifyWD,357std::move(*InotifyPollingStopper));358}359360361