| 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-exception |
| 6 | // |
| 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 | |
| 28 | namespace { |
| 29 | |
| 30 | using namespace llvm; |
| 31 | using namespace clang; |
| 32 | |
| 33 | /// Pipe for inter-thread synchronization - for epoll-ing on multiple |
| 34 | /// conditions. It is meant for uni-directional 1:1 signalling - specifically: |
| 35 | /// no multiple consumers, no data passing. Thread waiting for signal should |
| 36 | /// poll the FDRead. Signalling thread should call signal() which writes single |
| 37 | /// character to FDRead. |
| 38 | struct SemaphorePipe { |
| 39 | // Expects two file-descriptors opened as a pipe in the canonical POSIX |
| 40 | // order: pipefd[0] refers to the read end of the pipe. pipefd[1] refers to |
| 41 | // 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 other |
| 49 | // instance before. |
| 50 | { |
| 51 | other.OwnsFDs = false; |
| 52 | }; |
| 53 | |
| 54 | void signal() { |
| 55 | #ifndef NDEBUG |
| 56 | ssize_t Result = |
| 57 | #endif |
| 58 | llvm::sys::RetryAfterSignal(Fail: -1, F&: write, As: FDWrite, As: "A" , As: 1); |
| 59 | assert(Result != -1); |
| 60 | } |
| 61 | ~SemaphorePipe() { |
| 62 | if (OwnsFDs) { |
| 63 | close(fd: FDWrite); |
| 64 | close(fd: 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(pipedes: InotifyPollingStopperFDs, O_CLOEXEC) == -1) |
| 74 | return std::nullopt; |
| 75 | return SemaphorePipe(InotifyPollingStopperFDs); |
| 76 | } |
| 77 | }; |
| 78 | |
| 79 | /// Mutex-protected queue of Events. |
| 80 | class EventQueue { |
| 81 | std::mutex Mtx; |
| 82 | std::condition_variable NonEmpty; |
| 83 | std::queue<DirectoryWatcher::Event> Events; |
| 84 | |
| 85 | public: |
| 86 | void push_back(const DirectoryWatcher::Event::EventKind K, |
| 87 | StringRef Filename) { |
| 88 | { |
| 89 | std::unique_lock<std::mutex> L(Mtx); |
| 90 | Events.emplace(args: K, args&: Filename); |
| 91 | } |
| 92 | NonEmpty.notify_one(); |
| 93 | } |
| 94 | |
| 95 | // Blocks on caller thread and uses codition_variable to wait until there's an |
| 96 | // 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 we |
| 101 | // 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(lock&: L, p: [this]() { return !Events.empty(); }); |
| 108 | } |
| 109 | } |
| 110 | }; |
| 111 | |
| 112 | class DirectoryWatcherLinux : public clang::DirectoryWatcher { |
| 113 | public: |
| 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(fd: InotifyFD, wd: InotifyWD); |
| 125 | llvm::sys::RetryAfterSignal(Fail: -1, F&: close, As: InotifyFD); |
| 126 | } |
| 127 | |
| 128 | private: |
| 129 | const std::string WatchedDirPath; |
| 130 | // inotify file descriptor |
| 131 | int InotifyFD = -1; |
| 132 | // inotify watch descriptor |
| 133 | int InotifyWD = -1; |
| 134 | |
| 135 | EventQueue Queue; |
| 136 | |
| 137 | // Make sure lifetime of Receiver fully contains lifetime of |
| 138 | // 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 and |
| 145 | // 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 Receiver |
| 150 | // 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 synchronously |
| 155 | // (WaitForInitialSync=false in ctor) we do the initial scan at the beginning |
| 156 | // 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't |
| 160 | // necessarily thread-safe. |
| 161 | void EventReceivingLoop(); |
| 162 | |
| 163 | // Stops all the async work. Reentrant. |
| 164 | void StopWork() { |
| 165 | Queue.push_back(K: DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
| 166 | Filename: "" ); |
| 167 | InotifyPollingStopSignal.signal(); |
| 168 | } |
| 169 | }; |
| 170 | |
| 171 | void DirectoryWatcherLinux::InotifyPollingLoop() { |
| 172 | // We want to be able to read ~30 events at once even in the worst case |
| 173 | // (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.html |
| 177 | // Some systems cannot read integer variables if they are not |
| 178 | // properly aligned. On other systems, incorrect alignment may |
| 179 | // decrease performance. Hence, the buffer used for reading from |
| 180 | // the inotify file descriptor should have the same alignment as |
| 181 | // 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(F: [EpollFD]() { close(fd: EpollFD); }); |
| 195 | |
| 196 | struct epoll_event EventSpec; |
| 197 | EventSpec.events = EPOLLIN; |
| 198 | EventSpec.data.fd = InotifyFD; |
| 199 | if (epoll_ctl(epfd: EpollFD, EPOLL_CTL_ADD, fd: InotifyFD, event: &EventSpec) == -1) { |
| 200 | StopWork(); |
| 201 | return; |
| 202 | } |
| 203 | |
| 204 | EventSpec.data.fd = InotifyPollingStopSignal.FDRead; |
| 205 | if (epoll_ctl(epfd: EpollFD, EPOLL_CTL_ADD, fd: InotifyPollingStopSignal.FDRead, |
| 206 | event: &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 | Fail: -1, F&: epoll_wait, As: EpollFD, As: EpollEventBuffer.data(), |
| 216 | As: EpollEventBuffer.size(), /*timeout=*/As: -1 /*== infinity*/); |
| 217 | if (EpollWaitResult == -1) { |
| 218 | StopWork(); |
| 219 | return; |
| 220 | } |
| 221 | |
| 222 | // Multiple epoll_events can be received for a single file descriptor per |
| 223 | // 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 no |
| 232 | // event for stopping, it must be an inotify event ready for reading. |
| 233 | ssize_t NumRead = llvm::sys::RetryAfterSignal(Fail: -1, F&: read, As: InotifyFD, As: Buf, |
| 234 | As: 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(K: DirectoryWatcher::Event::EventKind::Modified, |
| 254 | Filename: Event->name); |
| 255 | } else if (Event->mask & (IN_DELETE | IN_MOVED_FROM)) { |
| 256 | Queue.push_back(K: DirectoryWatcher::Event::EventKind::Removed, |
| 257 | Filename: Event->name); |
| 258 | } else if (Event->mask & (IN_DELETE_SELF | IN_MOVE_SELF)) { |
| 259 | Queue.push_back(K: DirectoryWatcher::Event::EventKind::WatchedDirRemoved, |
| 260 | Filename: "" ); |
| 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 | |
| 275 | void DirectoryWatcherLinux::InitialScan() { |
| 276 | this->Receiver(getAsFileEvents(Scan: scanDirectory(Path: WatchedDirPath)), |
| 277 | /*IsInitial=*/true); |
| 278 | } |
| 279 | |
| 280 | void 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 | |
| 292 | DirectoryWatcherLinux::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 being |
| 303 | // used in both InitialScan and EventReceivingLoop. We shouldn't run these |
| 304 | // 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 case |
| 311 | // of a failure in EventsReceivingThread. |
| 312 | InitialScan(); |
| 313 | EventReceivingLoop(); |
| 314 | }); |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | } // namespace |
| 319 | |
| 320 | llvm::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 | reason: "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 | Args: llvm::errnoAsErrorCode(), Args: std::string(": inotify_init1()" )); |
| 332 | |
| 333 | const int InotifyWD = inotify_add_watch( |
| 334 | fd: InotifyFD, name: 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_IGNORED |
| 337 | #ifdef IN_EXCL_UNLINK |
| 338 | | IN_EXCL_UNLINK |
| 339 | #endif |
| 340 | ); |
| 341 | if (InotifyWD == -1) |
| 342 | return llvm::make_error<llvm::StringError>( |
| 343 | Args: llvm::errnoAsErrorCode(), Args: std::string(": inotify_add_watch()" )); |
| 344 | |
| 345 | auto InotifyPollingStopper = SemaphorePipe::create(); |
| 346 | |
| 347 | if (!InotifyPollingStopper) |
| 348 | return llvm::make_error<llvm::StringError>( |
| 349 | Args: llvm::errnoAsErrorCode(), Args: std::string(": SemaphorePipe::create()" )); |
| 350 | |
| 351 | return std::make_unique<DirectoryWatcherLinux>( |
| 352 | args&: Path, args&: Receiver, args&: WaitForInitialSync, args: InotifyFD, args: InotifyWD, |
| 353 | args: std::move(*InotifyPollingStopper)); |
| 354 | } |
| 355 | |