Path: blob/main/contrib/llvm-project/clang/lib/DirectoryWatcher/windows/DirectoryWatcher-windows.cpp
35292 views
//===- DirectoryWatcher-windows.cpp - Windows-platform directory watching -===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "DirectoryScanner.h"9#include "clang/DirectoryWatcher/DirectoryWatcher.h"10#include "llvm/ADT/STLExtras.h"11#include "llvm/Support/ConvertUTF.h"12#include "llvm/Support/Path.h"13#include "llvm/Support/Windows/WindowsSupport.h"14#include <condition_variable>15#include <mutex>16#include <queue>17#include <string>18#include <thread>19#include <vector>2021namespace {2223using DirectoryWatcherCallback =24std::function<void(llvm::ArrayRef<clang::DirectoryWatcher::Event>, bool)>;2526using namespace llvm;27using namespace clang;2829class DirectoryWatcherWindows : public clang::DirectoryWatcher {30OVERLAPPED Overlapped;3132std::vector<DWORD> Notifications;3334std::thread WatcherThread;35std::thread HandlerThread;36std::function<void(ArrayRef<DirectoryWatcher::Event>, bool)> Callback;37SmallString<MAX_PATH> Path;38HANDLE Terminate;3940std::mutex Mutex;41bool WatcherActive = false;42std::condition_variable Ready;4344class EventQueue {45std::mutex M;46std::queue<DirectoryWatcher::Event> Q;47std::condition_variable CV;4849public:50void emplace(DirectoryWatcher::Event::EventKind Kind, StringRef Path) {51{52std::unique_lock<std::mutex> L(M);53Q.emplace(Kind, Path);54}55CV.notify_one();56}5758DirectoryWatcher::Event pop_front() {59std::unique_lock<std::mutex> L(M);60while (true) {61if (!Q.empty()) {62DirectoryWatcher::Event E = Q.front();63Q.pop();64return E;65}66CV.wait(L, [this]() { return !Q.empty(); });67}68}69} Q;7071public:72DirectoryWatcherWindows(HANDLE DirectoryHandle, bool WaitForInitialSync,73DirectoryWatcherCallback Receiver);7475~DirectoryWatcherWindows() override;7677void InitialScan();78void WatcherThreadProc(HANDLE DirectoryHandle);79void NotifierThreadProc(bool WaitForInitialSync);80};8182DirectoryWatcherWindows::DirectoryWatcherWindows(83HANDLE DirectoryHandle, bool WaitForInitialSync,84DirectoryWatcherCallback Receiver)85: Callback(Receiver), Terminate(INVALID_HANDLE_VALUE) {86// Pre-compute the real location as we will be handing over the directory87// handle to the watcher and performing synchronous operations.88{89DWORD Size = GetFinalPathNameByHandleW(DirectoryHandle, NULL, 0, 0);90std::unique_ptr<WCHAR[]> Buffer{new WCHAR[Size + 1]};91Size = GetFinalPathNameByHandleW(DirectoryHandle, Buffer.get(), Size, 0);92Buffer[Size] = L'\0';93WCHAR *Data = Buffer.get();94if (Size >= 4 && ::memcmp(Data, L"\\\\?\\", 8) == 0) {95Data += 4;96Size -= 4;97}98llvm::sys::windows::UTF16ToUTF8(Data, Size, Path);99}100101size_t EntrySize = sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH * sizeof(WCHAR);102Notifications.resize((4 * EntrySize) / sizeof(DWORD));103104memset(&Overlapped, 0, sizeof(Overlapped));105Overlapped.hEvent =106CreateEventW(NULL, /*bManualReset=*/FALSE, /*bInitialState=*/FALSE, NULL);107assert(Overlapped.hEvent && "unable to create event");108109Terminate =110CreateEventW(NULL, /*bManualReset=*/TRUE, /*bInitialState=*/FALSE, NULL);111112WatcherThread = std::thread([this, DirectoryHandle]() {113this->WatcherThreadProc(DirectoryHandle);114});115116if (WaitForInitialSync)117InitialScan();118119HandlerThread = std::thread([this, WaitForInitialSync]() {120this->NotifierThreadProc(WaitForInitialSync);121});122}123124DirectoryWatcherWindows::~DirectoryWatcherWindows() {125// Signal the Watcher to exit.126SetEvent(Terminate);127HandlerThread.join();128WatcherThread.join();129CloseHandle(Terminate);130CloseHandle(Overlapped.hEvent);131}132133void DirectoryWatcherWindows::InitialScan() {134std::unique_lock<std::mutex> lock(Mutex);135Ready.wait(lock, [this] { return this->WatcherActive; });136137Callback(getAsFileEvents(scanDirectory(Path.data())), /*IsInitial=*/true);138}139140void DirectoryWatcherWindows::WatcherThreadProc(HANDLE DirectoryHandle) {141while (true) {142// We do not guarantee subdirectories, but macOS already provides143// subdirectories, might as well as ...144BOOL WatchSubtree = TRUE;145DWORD NotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME146| FILE_NOTIFY_CHANGE_DIR_NAME147| FILE_NOTIFY_CHANGE_SIZE148| FILE_NOTIFY_CHANGE_LAST_WRITE149| FILE_NOTIFY_CHANGE_CREATION;150151DWORD BytesTransferred;152if (!ReadDirectoryChangesW(DirectoryHandle, Notifications.data(),153Notifications.size() * sizeof(DWORD),154WatchSubtree, NotifyFilter, &BytesTransferred,155&Overlapped, NULL)) {156Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,157"");158break;159}160161if (!WatcherActive) {162std::unique_lock<std::mutex> lock(Mutex);163WatcherActive = true;164}165Ready.notify_one();166167HANDLE Handles[2] = { Terminate, Overlapped.hEvent };168switch (WaitForMultipleObjects(2, Handles, FALSE, INFINITE)) {169case WAIT_OBJECT_0: // Terminate Request170case WAIT_FAILED: // Failure171Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,172"");173(void)CloseHandle(DirectoryHandle);174return;175case WAIT_TIMEOUT: // Spurious wakeup?176continue;177case WAIT_OBJECT_0 + 1: // Directory change178break;179}180181if (!GetOverlappedResult(DirectoryHandle, &Overlapped, &BytesTransferred,182FALSE)) {183Q.emplace(DirectoryWatcher::Event::EventKind::WatchedDirRemoved,184"");185Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,186"");187break;188}189190// There was a buffer underrun on the kernel side. We may have lost191// events, please re-synchronize.192if (BytesTransferred == 0) {193Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated,194"");195break;196}197198for (FILE_NOTIFY_INFORMATION *I =199(FILE_NOTIFY_INFORMATION *)Notifications.data();200I;201I = I->NextEntryOffset202? (FILE_NOTIFY_INFORMATION *)((CHAR *)I + I->NextEntryOffset)203: NULL) {204DirectoryWatcher::Event::EventKind Kind =205DirectoryWatcher::Event::EventKind::WatcherGotInvalidated;206switch (I->Action) {207case FILE_ACTION_ADDED:208case FILE_ACTION_MODIFIED:209case FILE_ACTION_RENAMED_NEW_NAME:210Kind = DirectoryWatcher::Event::EventKind::Modified;211break;212case FILE_ACTION_REMOVED:213case FILE_ACTION_RENAMED_OLD_NAME:214Kind = DirectoryWatcher::Event::EventKind::Removed;215break;216}217218SmallString<MAX_PATH> filename;219sys::windows::UTF16ToUTF8(I->FileName, I->FileNameLength / sizeof(WCHAR),220filename);221Q.emplace(Kind, filename);222}223}224225(void)CloseHandle(DirectoryHandle);226}227228void DirectoryWatcherWindows::NotifierThreadProc(bool WaitForInitialSync) {229// If we did not wait for the initial sync, then we should perform the230// scan when we enter the thread.231if (!WaitForInitialSync)232this->InitialScan();233234while (true) {235DirectoryWatcher::Event E = Q.pop_front();236Callback(E, /*IsInitial=*/false);237if (E.Kind == DirectoryWatcher::Event::EventKind::WatcherGotInvalidated)238break;239}240}241242auto error(DWORD ErrorCode) {243DWORD Flags = FORMAT_MESSAGE_ALLOCATE_BUFFER244| FORMAT_MESSAGE_FROM_SYSTEM245| FORMAT_MESSAGE_IGNORE_INSERTS;246247LPSTR Buffer;248if (!FormatMessageA(Flags, NULL, ErrorCode,249MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&Buffer,2500, NULL)) {251return make_error<llvm::StringError>("error " + utostr(ErrorCode),252inconvertibleErrorCode());253}254std::string Message{Buffer};255LocalFree(Buffer);256return make_error<llvm::StringError>(Message, inconvertibleErrorCode());257}258259} // namespace260261llvm::Expected<std::unique_ptr<DirectoryWatcher>>262clang::DirectoryWatcher::create(StringRef Path,263DirectoryWatcherCallback Receiver,264bool WaitForInitialSync) {265if (Path.empty())266llvm::report_fatal_error(267"DirectoryWatcher::create can not accept an empty Path.");268269if (!sys::fs::is_directory(Path))270llvm::report_fatal_error(271"DirectoryWatcher::create can not accept a filepath.");272273SmallVector<wchar_t, MAX_PATH> WidePath;274if (sys::windows::UTF8ToUTF16(Path, WidePath))275return llvm::make_error<llvm::StringError>(276"unable to convert path to UTF-16", llvm::inconvertibleErrorCode());277278DWORD DesiredAccess = FILE_LIST_DIRECTORY;279DWORD ShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;280DWORD CreationDisposition = OPEN_EXISTING;281DWORD FlagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;282283HANDLE DirectoryHandle =284CreateFileW(WidePath.data(), DesiredAccess, ShareMode,285/*lpSecurityAttributes=*/NULL, CreationDisposition,286FlagsAndAttributes, NULL);287if (DirectoryHandle == INVALID_HANDLE_VALUE)288return error(GetLastError());289290// NOTE: We use the watcher instance as a RAII object to discard the handles291// for the directory in case of an error. Hence, this is early allocated,292// with the state being written directly to the watcher.293return std::make_unique<DirectoryWatcherWindows>(294DirectoryHandle, WaitForInitialSync, Receiver);295}296297298