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