297 lines · cpp
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-exception6//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 22namespace {23 24using DirectoryWatcherCallback =25 std::function<void(llvm::ArrayRef<clang::DirectoryWatcher::Event>, bool)>;26 27using namespace llvm;28using namespace clang;29 30class 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 72public: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 83DirectoryWatcherWindows::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 directory88 // 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 125DirectoryWatcherWindows::~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 134void 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 141void DirectoryWatcherWindows::WatcherThreadProc(HANDLE DirectoryHandle) {142 while (true) {143 // We do not guarantee subdirectories, but macOS already provides144 // subdirectories, might as well as ...145 BOOL WatchSubtree = TRUE;146 DWORD NotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME147 | FILE_NOTIFY_CHANGE_DIR_NAME148 | FILE_NOTIFY_CHANGE_SIZE149 | FILE_NOTIFY_CHANGE_LAST_WRITE150 | 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 Request171 case WAIT_FAILED: // Failure172 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 change179 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 lost192 // 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->NextEntryOffset203 ? (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 229void DirectoryWatcherWindows::NotifierThreadProc(bool WaitForInitialSync) {230 // If we did not wait for the initial sync, then we should perform the231 // 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 243auto error(DWORD ErrorCode) {244 DWORD Flags = FORMAT_MESSAGE_ALLOCATE_BUFFER245 | FORMAT_MESSAGE_FROM_SYSTEM246 | 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} // namespace261 262llvm::Expected<std::unique_ptr<DirectoryWatcher>>263clang::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 handles292 // 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