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