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 | |