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