1 | //===- DirectoryWatcher-windows.cpp - Windows-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 | #include "llvm/ADT/STLExtras.h" |
12 | #include "llvm/Support/ConvertUTF.h" |
13 | #include "llvm/Support/Path.h" |
14 | #include "llvm/Support/Windows/WindowsSupport.h" |
15 | #include <condition_variable> |
16 | #include <mutex> |
17 | #include <queue> |
18 | #include <string> |
19 | #include <thread> |
20 | #include <vector> |
21 | |
22 | namespace { |
23 | |
24 | using DirectoryWatcherCallback = |
25 | std::function<void(llvm::ArrayRef<clang::DirectoryWatcher::Event>, bool)>; |
26 | |
27 | using namespace llvm; |
28 | using namespace clang; |
29 | |
30 | class DirectoryWatcherWindows : public clang::DirectoryWatcher { |
31 | OVERLAPPED Overlapped; |
32 | |
33 | std::vector<DWORD> Notifications; |
34 | |
35 | std::thread WatcherThread; |
36 | std::thread HandlerThread; |
37 | std::function<void(ArrayRef<DirectoryWatcher::Event>, bool)> Callback; |
38 | SmallString<MAX_PATH> Path; |
39 | HANDLE Terminate; |
40 | |
41 | std::mutex Mutex; |
42 | bool WatcherActive = false; |
43 | std::condition_variable Ready; |
44 | |
45 | class EventQueue { |
46 | std::mutex M; |
47 | std::queue<DirectoryWatcher::Event> Q; |
48 | std::condition_variable CV; |
49 | |
50 | public: |
51 | void emplace(DirectoryWatcher::Event::EventKind Kind, StringRef Path) { |
52 | { |
53 | std::unique_lock<std::mutex> L(M); |
54 | Q.emplace(args&: Kind, args&: Path); |
55 | } |
56 | CV.notify_one(); |
57 | } |
58 | |
59 | DirectoryWatcher::Event pop_front() { |
60 | std::unique_lock<std::mutex> L(M); |
61 | while (true) { |
62 | if (!Q.empty()) { |
63 | DirectoryWatcher::Event E = Q.front(); |
64 | Q.pop(); |
65 | return E; |
66 | } |
67 | CV.wait(lock&: L, p: [this]() { return !Q.empty(); }); |
68 | } |
69 | } |
70 | } Q; |
71 | |
72 | public: |
73 | DirectoryWatcherWindows(HANDLE DirectoryHandle, bool WaitForInitialSync, |
74 | DirectoryWatcherCallback Receiver); |
75 | |
76 | ~DirectoryWatcherWindows() override; |
77 | |
78 | void InitialScan(); |
79 | void WatcherThreadProc(HANDLE DirectoryHandle); |
80 | void NotifierThreadProc(bool WaitForInitialSync); |
81 | }; |
82 | |
83 | DirectoryWatcherWindows::DirectoryWatcherWindows( |
84 | HANDLE DirectoryHandle, bool WaitForInitialSync, |
85 | DirectoryWatcherCallback Receiver) |
86 | : Callback(Receiver), Terminate(INVALID_HANDLE_VALUE) { |
87 | // Pre-compute the real location as we will be handing over the directory |
88 | // handle to the watcher and performing synchronous operations. |
89 | { |
90 | DWORD Size = GetFinalPathNameByHandleW(DirectoryHandle, NULL, 0, 0); |
91 | std::unique_ptr<WCHAR[]> Buffer{new WCHAR[Size + 1]}; |
92 | Size = GetFinalPathNameByHandleW(DirectoryHandle, Buffer.get(), Size, 0); |
93 | Buffer[Size] = L'\0'; |
94 | WCHAR *Data = Buffer.get(); |
95 | if (Size >= 4 && ::memcmp(Data, L"\\\\?\\" , 8) == 0) { |
96 | Data += 4; |
97 | Size -= 4; |
98 | } |
99 | llvm::sys::windows::UTF16ToUTF8(Data, Size, Path); |
100 | } |
101 | |
102 | size_t EntrySize = sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH * sizeof(WCHAR); |
103 | Notifications.resize((4 * EntrySize) / sizeof(DWORD)); |
104 | |
105 | memset(&Overlapped, 0, sizeof(Overlapped)); |
106 | Overlapped.hEvent = |
107 | CreateEventW(NULL, /*bManualReset=*/FALSE, /*bInitialState=*/FALSE, NULL); |
108 | assert(Overlapped.hEvent && "unable to create event" ); |
109 | |
110 | Terminate = |
111 | CreateEventW(NULL, /*bManualReset=*/TRUE, /*bInitialState=*/FALSE, NULL); |
112 | |
113 | WatcherThread = std::thread([this, DirectoryHandle]() { |
114 | this->WatcherThreadProc(DirectoryHandle); |
115 | }); |
116 | |
117 | if (WaitForInitialSync) |
118 | InitialScan(); |
119 | |
120 | HandlerThread = std::thread([this, WaitForInitialSync]() { |
121 | this->NotifierThreadProc(WaitForInitialSync); |
122 | }); |
123 | } |
124 | |
125 | DirectoryWatcherWindows::~DirectoryWatcherWindows() { |
126 | // Signal the Watcher to exit. |
127 | SetEvent(Terminate); |
128 | HandlerThread.join(); |
129 | WatcherThread.join(); |
130 | CloseHandle(Terminate); |
131 | CloseHandle(Overlapped.hEvent); |
132 | } |
133 | |
134 | void DirectoryWatcherWindows::InitialScan() { |
135 | std::unique_lock<std::mutex> lock(Mutex); |
136 | Ready.wait(lock, [this] { return this->WatcherActive; }); |
137 | |
138 | Callback(getAsFileEvents(scanDirectory(Path.data())), /*IsInitial=*/true); |
139 | } |
140 | |
141 | void DirectoryWatcherWindows::WatcherThreadProc(HANDLE DirectoryHandle) { |
142 | while (true) { |
143 | // We do not guarantee subdirectories, but macOS already provides |
144 | // subdirectories, might as well as ... |
145 | BOOL WatchSubtree = TRUE; |
146 | DWORD NotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME |
147 | | FILE_NOTIFY_CHANGE_DIR_NAME |
148 | | FILE_NOTIFY_CHANGE_SIZE |
149 | | FILE_NOTIFY_CHANGE_LAST_WRITE |
150 | | FILE_NOTIFY_CHANGE_CREATION; |
151 | |
152 | DWORD BytesTransferred; |
153 | if (!ReadDirectoryChangesW(DirectoryHandle, Notifications.data(), |
154 | Notifications.size() * sizeof(DWORD), |
155 | WatchSubtree, NotifyFilter, &BytesTransferred, |
156 | &Overlapped, NULL)) { |
157 | Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
158 | "" ); |
159 | break; |
160 | } |
161 | |
162 | if (!WatcherActive) { |
163 | std::unique_lock<std::mutex> lock(Mutex); |
164 | WatcherActive = true; |
165 | } |
166 | Ready.notify_one(); |
167 | |
168 | HANDLE Handles[2] = { Terminate, Overlapped.hEvent }; |
169 | switch (WaitForMultipleObjects(2, Handles, FALSE, INFINITE)) { |
170 | case WAIT_OBJECT_0: // Terminate Request |
171 | case WAIT_FAILED: // Failure |
172 | Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
173 | "" ); |
174 | (void)CloseHandle(DirectoryHandle); |
175 | return; |
176 | case WAIT_TIMEOUT: // Spurious wakeup? |
177 | continue; |
178 | case WAIT_OBJECT_0 + 1: // Directory change |
179 | break; |
180 | } |
181 | |
182 | if (!GetOverlappedResult(DirectoryHandle, &Overlapped, &BytesTransferred, |
183 | FALSE)) { |
184 | Q.emplace(DirectoryWatcher::Event::EventKind::WatchedDirRemoved, |
185 | "" ); |
186 | Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
187 | "" ); |
188 | break; |
189 | } |
190 | |
191 | // There was a buffer underrun on the kernel side. We may have lost |
192 | // events, please re-synchronize. |
193 | if (BytesTransferred == 0) { |
194 | Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
195 | "" ); |
196 | break; |
197 | } |
198 | |
199 | for (FILE_NOTIFY_INFORMATION *I = |
200 | (FILE_NOTIFY_INFORMATION *)Notifications.data(); |
201 | I; |
202 | I = I->NextEntryOffset |
203 | ? (FILE_NOTIFY_INFORMATION *)((CHAR *)I + I->NextEntryOffset) |
204 | : NULL) { |
205 | DirectoryWatcher::Event::EventKind Kind = |
206 | DirectoryWatcher::Event::EventKind::WatcherGotInvalidated; |
207 | switch (I->Action) { |
208 | case FILE_ACTION_ADDED: |
209 | case FILE_ACTION_MODIFIED: |
210 | case FILE_ACTION_RENAMED_NEW_NAME: |
211 | Kind = DirectoryWatcher::Event::EventKind::Modified; |
212 | break; |
213 | case FILE_ACTION_REMOVED: |
214 | case FILE_ACTION_RENAMED_OLD_NAME: |
215 | Kind = DirectoryWatcher::Event::EventKind::Removed; |
216 | break; |
217 | } |
218 | |
219 | SmallString<MAX_PATH> filename; |
220 | sys::windows::UTF16ToUTF8(I->FileName, I->FileNameLength / sizeof(WCHAR), |
221 | filename); |
222 | Q.emplace(Kind, filename); |
223 | } |
224 | } |
225 | |
226 | (void)CloseHandle(DirectoryHandle); |
227 | } |
228 | |
229 | void DirectoryWatcherWindows::NotifierThreadProc(bool WaitForInitialSync) { |
230 | // If we did not wait for the initial sync, then we should perform the |
231 | // scan when we enter the thread. |
232 | if (!WaitForInitialSync) |
233 | this->InitialScan(); |
234 | |
235 | while (true) { |
236 | DirectoryWatcher::Event E = Q.pop_front(); |
237 | Callback(E, /*IsInitial=*/false); |
238 | if (E.Kind == DirectoryWatcher::Event::EventKind::WatcherGotInvalidated) |
239 | break; |
240 | } |
241 | } |
242 | |
243 | auto error(DWORD ErrorCode) { |
244 | DWORD Flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
245 | | FORMAT_MESSAGE_FROM_SYSTEM |
246 | | FORMAT_MESSAGE_IGNORE_INSERTS; |
247 | |
248 | LPSTR Buffer; |
249 | if (!FormatMessageA(Flags, NULL, ErrorCode, |
250 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&Buffer, |
251 | 0, NULL)) { |
252 | return make_error<llvm::StringError>("error " + utostr(ErrorCode), |
253 | inconvertibleErrorCode()); |
254 | } |
255 | std::string Message{Buffer}; |
256 | LocalFree(Buffer); |
257 | return make_error<llvm::StringError>(Message, inconvertibleErrorCode()); |
258 | } |
259 | |
260 | } // namespace |
261 | |
262 | llvm::Expected<std::unique_ptr<DirectoryWatcher>> |
263 | clang::DirectoryWatcher::create(StringRef Path, |
264 | DirectoryWatcherCallback Receiver, |
265 | bool WaitForInitialSync) { |
266 | if (Path.empty()) |
267 | llvm::report_fatal_error( |
268 | "DirectoryWatcher::create can not accept an empty Path." ); |
269 | |
270 | if (!sys::fs::is_directory(Path)) |
271 | llvm::report_fatal_error( |
272 | "DirectoryWatcher::create can not accept a filepath." ); |
273 | |
274 | SmallVector<wchar_t, MAX_PATH> WidePath; |
275 | if (sys::windows::UTF8ToUTF16(Path, WidePath)) |
276 | return llvm::make_error<llvm::StringError>( |
277 | "unable to convert path to UTF-16" , llvm::inconvertibleErrorCode()); |
278 | |
279 | DWORD DesiredAccess = FILE_LIST_DIRECTORY; |
280 | DWORD ShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; |
281 | DWORD CreationDisposition = OPEN_EXISTING; |
282 | DWORD FlagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED; |
283 | |
284 | HANDLE DirectoryHandle = |
285 | CreateFileW(WidePath.data(), DesiredAccess, ShareMode, |
286 | /*lpSecurityAttributes=*/NULL, CreationDisposition, |
287 | FlagsAndAttributes, NULL); |
288 | if (DirectoryHandle == INVALID_HANDLE_VALUE) |
289 | return error(GetLastError()); |
290 | |
291 | // NOTE: We use the watcher instance as a RAII object to discard the handles |
292 | // for the directory in case of an error. Hence, this is early allocated, |
293 | // with the state being written directly to the watcher. |
294 | return std::make_unique<DirectoryWatcherWindows>( |
295 | DirectoryHandle, WaitForInitialSync, Receiver); |
296 | } |
297 | |