Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/clang/lib/DirectoryWatcher/windows/DirectoryWatcher-windows.cpp
35292 views
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(Kind, 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(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
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
298