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