xref: /freebsd-src/contrib/llvm-project/clang/lib/DirectoryWatcher/windows/DirectoryWatcher-windows.cpp (revision 349cc55c9796c4596a5b9904cd3281af295f878f)
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