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