1 //===- DirectoryWatcher-windows.cpp - Windows-platform directory watching -===//
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
7 //===----------------------------------------------------------------------===//
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>
24 using DirectoryWatcherCallback
=
25 std::function
<void(llvm::ArrayRef
<clang::DirectoryWatcher::Event
>, bool)>;
28 using namespace clang
;
30 class DirectoryWatcherWindows
: public clang::DirectoryWatcher
{
31 OVERLAPPED Overlapped
;
33 std::vector
<DWORD
> Notifications
;
35 std::thread WatcherThread
;
36 std::thread HandlerThread
;
37 std::function
<void(ArrayRef
<DirectoryWatcher::Event
>, bool)> Callback
;
38 SmallString
<MAX_PATH
> Path
;
42 bool WatcherActive
= false;
43 std::condition_variable Ready
;
47 std::queue
<DirectoryWatcher::Event
> Q
;
48 std::condition_variable CV
;
51 void emplace(DirectoryWatcher::Event::EventKind Kind
, StringRef Path
) {
53 std::unique_lock
<std::mutex
> L(M
);
54 Q
.emplace(Kind
, Path
);
59 DirectoryWatcher::Event
pop_front() {
60 std::unique_lock
<std::mutex
> L(M
);
63 DirectoryWatcher::Event E
= Q
.front();
67 CV
.wait(L
, [this]() { return !Q
.empty(); });
73 DirectoryWatcherWindows(HANDLE DirectoryHandle
, bool WaitForInitialSync
,
74 DirectoryWatcherCallback Receiver
);
76 ~DirectoryWatcherWindows() override
;
79 void WatcherThreadProc(HANDLE DirectoryHandle
);
80 void NotifierThreadProc(bool WaitForInitialSync
);
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.
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);
94 WCHAR
*Data
= Buffer
.get();
95 if (Size
>= 4 && ::memcmp(Data
, L
"\\\\?\\", 8) == 0) {
99 llvm::sys::windows::UTF16ToUTF8(Data
, Size
, Path
);
102 size_t EntrySize
= sizeof(FILE_NOTIFY_INFORMATION
) + MAX_PATH
* sizeof(WCHAR
);
103 Notifications
.resize((4 * EntrySize
) / sizeof(DWORD
));
105 memset(&Overlapped
, 0, sizeof(Overlapped
));
107 CreateEventW(NULL
, /*bManualReset=*/FALSE
, /*bInitialState=*/FALSE
, NULL
);
108 assert(Overlapped
.hEvent
&& "unable to create event");
111 CreateEventW(NULL
, /*bManualReset=*/TRUE
, /*bInitialState=*/FALSE
, NULL
);
113 WatcherThread
= std::thread([this, DirectoryHandle
]() {
114 this->WatcherThreadProc(DirectoryHandle
);
117 if (WaitForInitialSync
)
120 HandlerThread
= std::thread([this, WaitForInitialSync
]() {
121 this->NotifierThreadProc(WaitForInitialSync
);
125 DirectoryWatcherWindows::~DirectoryWatcherWindows() {
126 // Signal the Watcher to exit.
128 HandlerThread
.join();
129 WatcherThread
.join();
130 CloseHandle(Terminate
);
131 CloseHandle(Overlapped
.hEvent
);
134 void DirectoryWatcherWindows::InitialScan() {
135 std::unique_lock
<std::mutex
> lock(Mutex
);
136 Ready
.wait(lock
, [this] { return this->WatcherActive
; });
138 Callback(getAsFileEvents(scanDirectory(Path
.data())), /*IsInitial=*/true);
141 void DirectoryWatcherWindows::WatcherThreadProc(HANDLE DirectoryHandle
) {
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
;
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
,
162 if (!WatcherActive
) {
163 std::unique_lock
<std::mutex
> lock(Mutex
);
164 WatcherActive
= true;
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
,
174 (void)CloseHandle(DirectoryHandle
);
176 case WAIT_TIMEOUT
: // Spurious wakeup?
178 case WAIT_OBJECT_0
+ 1: // Directory change
182 if (!GetOverlappedResult(DirectoryHandle
, &Overlapped
, &BytesTransferred
,
184 Q
.emplace(DirectoryWatcher::Event::EventKind::WatchedDirRemoved
,
186 Q
.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated
,
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
,
199 for (FILE_NOTIFY_INFORMATION
*I
=
200 (FILE_NOTIFY_INFORMATION
*)Notifications
.data();
202 I
= I
->NextEntryOffset
203 ? (FILE_NOTIFY_INFORMATION
*)((CHAR
*)I
+ I
->NextEntryOffset
)
205 DirectoryWatcher::Event::EventKind Kind
=
206 DirectoryWatcher::Event::EventKind::WatcherGotInvalidated
;
208 case FILE_ACTION_ADDED
:
209 case FILE_ACTION_MODIFIED
:
210 case FILE_ACTION_RENAMED_NEW_NAME
:
211 Kind
= DirectoryWatcher::Event::EventKind::Modified
;
213 case FILE_ACTION_REMOVED
:
214 case FILE_ACTION_RENAMED_OLD_NAME
:
215 Kind
= DirectoryWatcher::Event::EventKind::Removed
;
219 SmallString
<MAX_PATH
> filename
;
220 sys::windows::UTF16ToUTF8(I
->FileName
, I
->FileNameLength
/ sizeof(WCHAR
),
222 Q
.emplace(Kind
, filename
);
226 (void)CloseHandle(DirectoryHandle
);
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
)
236 DirectoryWatcher::Event E
= Q
.pop_front();
237 Callback(E
, /*IsInitial=*/false);
238 if (E
.Kind
== DirectoryWatcher::Event::EventKind::WatcherGotInvalidated
)
243 auto error(DWORD ErrorCode
) {
244 DWORD Flags
= FORMAT_MESSAGE_ALLOCATE_BUFFER
245 | FORMAT_MESSAGE_FROM_SYSTEM
246 | FORMAT_MESSAGE_IGNORE_INSERTS
;
249 if (!FormatMessageA(Flags
, NULL
, ErrorCode
,
250 MAKELANGID(LANG_NEUTRAL
, SUBLANG_DEFAULT
), (LPSTR
)&Buffer
,
252 return make_error
<llvm::StringError
>("error " + utostr(ErrorCode
),
253 inconvertibleErrorCode());
255 std::string Message
{Buffer
};
257 return make_error
<llvm::StringError
>(Message
, inconvertibleErrorCode());
262 llvm::Expected
<std::unique_ptr
<DirectoryWatcher
>>
263 clang::DirectoryWatcher::create(StringRef Path
,
264 DirectoryWatcherCallback Receiver
,
265 bool WaitForInitialSync
) {
267 llvm::report_fatal_error(
268 "DirectoryWatcher::create can not accept an empty Path.");
270 if (!sys::fs::is_directory(Path
))
271 llvm::report_fatal_error(
272 "DirectoryWatcher::create can not accept a filepath.");
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());
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
;
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());
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
);