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:
emplace(DirectoryWatcher::Event::EventKind Kind,StringRef Path)51 void emplace(DirectoryWatcher::Event::EventKind Kind, StringRef Path) {
52 {
53 std::unique_lock<std::mutex> L(M);
54 Q.emplace(Kind, Path);
55 }
56 CV.notify_one();
57 }
58
pop_front()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(L, [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
DirectoryWatcherWindows(HANDLE DirectoryHandle,bool WaitForInitialSync,DirectoryWatcherCallback Receiver)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
~DirectoryWatcherWindows()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
InitialScan()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
WatcherThreadProc(HANDLE DirectoryHandle)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
NotifierThreadProc(bool WaitForInitialSync)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
error(DWORD ErrorCode)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>>
create(StringRef Path,DirectoryWatcherCallback Receiver,bool WaitForInitialSync)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