| //===- DirectoryWatcher-windows.cpp - Windows-platform directory watching -===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "DirectoryScanner.h" |
| #include "clang/DirectoryWatcher/DirectoryWatcher.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/Support/ConvertUTF.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/Windows/WindowsSupport.h" |
| #include <condition_variable> |
| #include <mutex> |
| #include <queue> |
| #include <string> |
| #include <thread> |
| #include <vector> |
| |
| namespace { |
| |
| using DirectoryWatcherCallback = |
| std::function<void(llvm::ArrayRef<clang::DirectoryWatcher::Event>, bool)>; |
| |
| using namespace llvm; |
| using namespace clang; |
| |
| class DirectoryWatcherWindows : public clang::DirectoryWatcher { |
| OVERLAPPED Overlapped; |
| |
| std::vector<DWORD> Notifications; |
| |
| std::thread WatcherThread; |
| std::thread HandlerThread; |
| std::function<void(ArrayRef<DirectoryWatcher::Event>, bool)> Callback; |
| SmallString<MAX_PATH> Path; |
| HANDLE Terminate; |
| |
| std::mutex Mutex; |
| bool WatcherActive = false; |
| std::condition_variable Ready; |
| |
| class EventQueue { |
| std::mutex M; |
| std::queue<DirectoryWatcher::Event> Q; |
| std::condition_variable CV; |
| |
| public: |
| void emplace(DirectoryWatcher::Event::EventKind Kind, StringRef Path) { |
| { |
| std::unique_lock<std::mutex> L(M); |
| Q.emplace(Kind, Path); |
| } |
| CV.notify_one(); |
| } |
| |
| DirectoryWatcher::Event pop_front() { |
| std::unique_lock<std::mutex> L(M); |
| while (true) { |
| if (!Q.empty()) { |
| DirectoryWatcher::Event E = Q.front(); |
| Q.pop(); |
| return E; |
| } |
| CV.wait(L, [this]() { return !Q.empty(); }); |
| } |
| } |
| } Q; |
| |
| public: |
| DirectoryWatcherWindows(HANDLE DirectoryHandle, bool WaitForInitialSync, |
| DirectoryWatcherCallback Receiver); |
| |
| ~DirectoryWatcherWindows() override; |
| |
| void InitialScan(); |
| void WatcherThreadProc(HANDLE DirectoryHandle); |
| void NotifierThreadProc(bool WaitForInitialSync); |
| }; |
| |
| DirectoryWatcherWindows::DirectoryWatcherWindows( |
| HANDLE DirectoryHandle, bool WaitForInitialSync, |
| DirectoryWatcherCallback Receiver) |
| : Callback(Receiver), Terminate(INVALID_HANDLE_VALUE) { |
| // Pre-compute the real location as we will be handing over the directory |
| // handle to the watcher and performing synchronous operations. |
| { |
| DWORD Size = GetFinalPathNameByHandleW(DirectoryHandle, NULL, 0, 0); |
| std::unique_ptr<WCHAR[]> Buffer{new WCHAR[Size + 1]}; |
| Size = GetFinalPathNameByHandleW(DirectoryHandle, Buffer.get(), Size, 0); |
| Buffer[Size] = L'\0'; |
| WCHAR *Data = Buffer.get(); |
| if (Size >= 4 && ::memcmp(Data, L"\\\\?\\", 8) == 0) { |
| Data += 4; |
| Size -= 4; |
| } |
| llvm::sys::windows::UTF16ToUTF8(Data, Size, Path); |
| } |
| |
| size_t EntrySize = sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH * sizeof(WCHAR); |
| Notifications.resize((4 * EntrySize) / sizeof(DWORD)); |
| |
| memset(&Overlapped, 0, sizeof(Overlapped)); |
| Overlapped.hEvent = |
| CreateEventW(NULL, /*bManualReset=*/FALSE, /*bInitialState=*/FALSE, NULL); |
| assert(Overlapped.hEvent && "unable to create event"); |
| |
| Terminate = |
| CreateEventW(NULL, /*bManualReset=*/TRUE, /*bInitialState=*/FALSE, NULL); |
| |
| WatcherThread = std::thread([this, DirectoryHandle]() { |
| this->WatcherThreadProc(DirectoryHandle); |
| }); |
| |
| if (WaitForInitialSync) |
| InitialScan(); |
| |
| HandlerThread = std::thread([this, WaitForInitialSync]() { |
| this->NotifierThreadProc(WaitForInitialSync); |
| }); |
| } |
| |
| DirectoryWatcherWindows::~DirectoryWatcherWindows() { |
| // Signal the Watcher to exit. |
| SetEvent(Terminate); |
| HandlerThread.join(); |
| WatcherThread.join(); |
| CloseHandle(Terminate); |
| CloseHandle(Overlapped.hEvent); |
| } |
| |
| void DirectoryWatcherWindows::InitialScan() { |
| std::unique_lock<std::mutex> lock(Mutex); |
| Ready.wait(lock, [this] { return this->WatcherActive; }); |
| |
| Callback(getAsFileEvents(scanDirectory(Path.data())), /*IsInitial=*/true); |
| } |
| |
| void DirectoryWatcherWindows::WatcherThreadProc(HANDLE DirectoryHandle) { |
| while (true) { |
| // We do not guarantee subdirectories, but macOS already provides |
| // subdirectories, might as well as ... |
| BOOL WatchSubtree = TRUE; |
| DWORD NotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME |
| | FILE_NOTIFY_CHANGE_DIR_NAME |
| | FILE_NOTIFY_CHANGE_SIZE |
| | FILE_NOTIFY_CHANGE_LAST_WRITE |
| | FILE_NOTIFY_CHANGE_CREATION; |
| |
| DWORD BytesTransferred; |
| if (!ReadDirectoryChangesW(DirectoryHandle, Notifications.data(), |
| Notifications.size() * sizeof(DWORD), |
| WatchSubtree, NotifyFilter, &BytesTransferred, |
| &Overlapped, NULL)) { |
| Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
| ""); |
| break; |
| } |
| |
| if (!WatcherActive) { |
| std::unique_lock<std::mutex> lock(Mutex); |
| WatcherActive = true; |
| } |
| Ready.notify_one(); |
| |
| HANDLE Handles[2] = { Terminate, Overlapped.hEvent }; |
| switch (WaitForMultipleObjects(2, Handles, FALSE, INFINITE)) { |
| case WAIT_OBJECT_0: // Terminate Request |
| case WAIT_FAILED: // Failure |
| Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
| ""); |
| (void)CloseHandle(DirectoryHandle); |
| return; |
| case WAIT_TIMEOUT: // Spurious wakeup? |
| continue; |
| case WAIT_OBJECT_0 + 1: // Directory change |
| break; |
| } |
| |
| if (!GetOverlappedResult(DirectoryHandle, &Overlapped, &BytesTransferred, |
| FALSE)) { |
| Q.emplace(DirectoryWatcher::Event::EventKind::WatchedDirRemoved, |
| ""); |
| Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
| ""); |
| break; |
| } |
| |
| // There was a buffer underrun on the kernel side. We may have lost |
| // events, please re-synchronize. |
| if (BytesTransferred == 0) { |
| Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
| ""); |
| break; |
| } |
| |
| for (FILE_NOTIFY_INFORMATION *I = |
| (FILE_NOTIFY_INFORMATION *)Notifications.data(); |
| I; |
| I = I->NextEntryOffset |
| ? (FILE_NOTIFY_INFORMATION *)((CHAR *)I + I->NextEntryOffset) |
| : NULL) { |
| DirectoryWatcher::Event::EventKind Kind = |
| DirectoryWatcher::Event::EventKind::WatcherGotInvalidated; |
| switch (I->Action) { |
| case FILE_ACTION_ADDED: |
| case FILE_ACTION_MODIFIED: |
| case FILE_ACTION_RENAMED_NEW_NAME: |
| Kind = DirectoryWatcher::Event::EventKind::Modified; |
| break; |
| case FILE_ACTION_REMOVED: |
| case FILE_ACTION_RENAMED_OLD_NAME: |
| Kind = DirectoryWatcher::Event::EventKind::Removed; |
| break; |
| } |
| |
| SmallString<MAX_PATH> filename; |
| sys::windows::UTF16ToUTF8(I->FileName, I->FileNameLength / sizeof(WCHAR), |
| filename); |
| Q.emplace(Kind, filename); |
| } |
| } |
| |
| (void)CloseHandle(DirectoryHandle); |
| } |
| |
| void DirectoryWatcherWindows::NotifierThreadProc(bool WaitForInitialSync) { |
| // If we did not wait for the initial sync, then we should perform the |
| // scan when we enter the thread. |
| if (!WaitForInitialSync) |
| this->InitialScan(); |
| |
| while (true) { |
| DirectoryWatcher::Event E = Q.pop_front(); |
| Callback(E, /*IsInitial=*/false); |
| if (E.Kind == DirectoryWatcher::Event::EventKind::WatcherGotInvalidated) |
| break; |
| } |
| } |
| |
| auto error(DWORD ErrorCode) { |
| DWORD Flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
| | FORMAT_MESSAGE_FROM_SYSTEM |
| | FORMAT_MESSAGE_IGNORE_INSERTS; |
| |
| LPSTR Buffer; |
| if (!FormatMessageA(Flags, NULL, ErrorCode, |
| MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&Buffer, |
| 0, NULL)) { |
| return make_error<llvm::StringError>("error " + utostr(ErrorCode), |
| inconvertibleErrorCode()); |
| } |
| std::string Message{Buffer}; |
| LocalFree(Buffer); |
| return make_error<llvm::StringError>(Message, inconvertibleErrorCode()); |
| } |
| |
| } // namespace |
| |
| llvm::Expected<std::unique_ptr<DirectoryWatcher>> |
| clang::DirectoryWatcher::create(StringRef Path, |
| DirectoryWatcherCallback Receiver, |
| bool WaitForInitialSync) { |
| if (Path.empty()) |
| llvm::report_fatal_error( |
| "DirectoryWatcher::create can not accept an empty Path."); |
| |
| if (!sys::fs::is_directory(Path)) |
| llvm::report_fatal_error( |
| "DirectoryWatcher::create can not accept a filepath."); |
| |
| SmallVector<wchar_t, MAX_PATH> WidePath; |
| if (sys::windows::UTF8ToUTF16(Path, WidePath)) |
| return llvm::make_error<llvm::StringError>( |
| "unable to convert path to UTF-16", llvm::inconvertibleErrorCode()); |
| |
| DWORD DesiredAccess = FILE_LIST_DIRECTORY; |
| DWORD ShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; |
| DWORD CreationDisposition = OPEN_EXISTING; |
| DWORD FlagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED; |
| |
| HANDLE DirectoryHandle = |
| CreateFileW(WidePath.data(), DesiredAccess, ShareMode, |
| /*lpSecurityAttributes=*/NULL, CreationDisposition, |
| FlagsAndAttributes, NULL); |
| if (DirectoryHandle == INVALID_HANDLE_VALUE) |
| return error(GetLastError()); |
| |
| // NOTE: We use the watcher instance as a RAII object to discard the handles |
| // for the directory in case of an error. Hence, this is early allocated, |
| // with the state being written directly to the watcher. |
| return std::make_unique<DirectoryWatcherWindows>( |
| DirectoryHandle, WaitForInitialSync, Receiver); |
| } |