clang  19.0.0git
DirectoryWatcher-windows.cpp
Go to the documentation of this file.
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"
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;
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) {
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 
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 
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 }
const char * Data
__DEVICE__ void * memset(void *__a, int __b, size_t __c)
#define NULL
Definition: __stddef_null.h:26
Provides notifications for file changes in a directory.
static llvm::Expected< std::unique_ptr< DirectoryWatcher > > create(llvm::StringRef Path, std::function< void(llvm::ArrayRef< DirectoryWatcher::Event > Events, bool IsInitial)> Receiver, bool WaitForInitialSync)
llvm fatal_error if
The JSON file list parser is used to communicate input to InstallAPI.
std::vector< std::string > scanDirectory(StringRef Path)
std::vector< DirectoryWatcher::Event > getAsFileEvents(const std::vector< std::string > &Scan)
Create event with EventKind::Added for every element in Scan.
Diagnostic wrappers for TextAPI types for error reporting.
Definition: Dominators.h:30