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