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