| 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(args&: Kind, args&: 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(lock&: L, p: [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 | |