diff --git a/clang/lib/DirectoryWatcher/windows/DirectoryWatcher-windows.cpp b/clang/lib/DirectoryWatcher/windows/DirectoryWatcher-windows.cpp --- a/clang/lib/DirectoryWatcher/windows/DirectoryWatcher-windows.cpp +++ b/clang/lib/DirectoryWatcher/windows/DirectoryWatcher-windows.cpp @@ -6,17 +6,11 @@ // //===----------------------------------------------------------------------===// -// TODO: This is not yet an implementation, but it will make it so Windows -// builds don't fail. - #include "DirectoryScanner.h" #include "clang/DirectoryWatcher/DirectoryWatcher.h" #include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/ScopeExit.h" -#include "llvm/Support/AlignOf.h" -#include "llvm/Support/Errno.h" -#include "llvm/Support/Mutex.h" +#include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Path.h" #include #include @@ -24,27 +18,232 @@ #include #include #include -#include + +#include namespace { +using DirectoryWatcherCallback = + std::function, bool)>; + using namespace llvm; using namespace clang; class DirectoryWatcherWindows : public clang::DirectoryWatcher { + OVERLAPPED ovlIO; + + alignas(DWORD) + CHAR Buffer[4 * (sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH * sizeof(WCHAR))]; + + std::thread WatcherThread; + std::thread HandlerThread; + std::function, bool)> Callback; + SmallString Path; + + class EventQueue { + std::mutex M; + std::queue Q; + std::condition_variable CV; + + public: + void emplace(DirectoryWatcher::Event::EventKind Kind, StringRef Path) { + { + std::unique_lock L(M); + Q.emplace(Kind, Path); + } + CV.notify_one(); + } + + DirectoryWatcher::Event pop_front() { + std::unique_lock 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() override { } - void InitialScan() { } - void EventReceivingLoop() { } - void StopWork() { } + DirectoryWatcherWindows(HANDLE hDirectory, bool WaitForInitialSync, + DirectoryWatcherCallback Receiver); + + ~DirectoryWatcherWindows() override; + + void InitialScan(); }; + +DirectoryWatcherWindows::DirectoryWatcherWindows( + HANDLE hDirectory, bool WaitForInitialSync, + DirectoryWatcherCallback Receiver) + : Callback(Receiver) { + // Pre-compute the real location as we will be handing over the directory + // handle to the watcher and performing synchronous operations. + { + DWORD dwLength = GetFinalPathNameByHandleW(hDirectory, NULL, 0, 0); + std::unique_ptr buffer{new WCHAR[dwLength + 1]}; + (void)GetFinalPathNameByHandleW(hDirectory, buffer.get(), dwLength + 1, 0); + sys::windows::UTF16ToUTF8(buffer.get(), dwLength + 1, Path); + } + + memset(&ovlIO, 0, sizeof(ovlIO)); + ovlIO.hEvent = + CreateEvent(NULL, /*bManualReset=*/TRUE, /*bInitialState=*/FALSE, NULL); + assert(ovlIO.hEvent); + + WatcherThread = std::thread([this, hDirectory]() { + while (true) { + // We do not guarantee subdirectories, but macOS already provides + // subdirectories, might as well as ... + const BOOL bWatchSubtree = TRUE; + const DWORD dwNotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME + | FILE_NOTIFY_CHANGE_DIR_NAME + | FILE_NOTIFY_CHANGE_SIZE + | FILE_NOTIFY_CHANGE_LAST_ACCESS + | FILE_NOTIFY_CHANGE_LAST_WRITE + | FILE_NOTIFY_CHANGE_CREATION; + + DWORD BytesTransferred; + if (!ReadDirectoryChangesW(hDirectory, Buffer, sizeof(Buffer), + bWatchSubtree, dwNotifyFilter, + &BytesTransferred, &ovlIO, NULL)) { + Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, + ""); + break; + } + + if (!GetOverlappedResult(hDirectory, &ovlIO, &BytesTransferred, TRUE)) { + 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 *pFNI = (FILE_NOTIFY_INFORMATION *)Buffer; + pFNI; + pFNI = pFNI->NextEntryOffset + ? (FILE_NOTIFY_INFORMATION *)((CHAR *)pFNI + pFNI->NextEntryOffset) + : NULL) { + + DirectoryWatcher::Event::EventKind Kind = + DirectoryWatcher::Event::EventKind::WatcherGotInvalidated; + switch (pFNI->Action) { + case FILE_ACTION_MODIFIED: + Kind = DirectoryWatcher::Event::EventKind::Modified; + break; + case FILE_ACTION_ADDED: + Kind = DirectoryWatcher::Event::EventKind::Modified; + break; + case FILE_ACTION_REMOVED: + Kind = DirectoryWatcher::Event::EventKind::Removed; + break; + case FILE_ACTION_RENAMED_OLD_NAME: + Kind = DirectoryWatcher::Event::EventKind::Removed; + break; + case FILE_ACTION_RENAMED_NEW_NAME: + Kind = DirectoryWatcher::Event::EventKind::Modified; + break; + } + + SmallString filename; + sys::windows::UTF16ToUTF8(pFNI->FileName, pFNI->FileNameLength / 2, + filename); + Q.emplace(Kind, filename); + } + } + + (void)CloseHandle(hDirectory); + }); + + if (WaitForInitialSync) + InitialScan(); + HandlerThread = std::thread([this, 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; + } + }); +} + +DirectoryWatcherWindows::~DirectoryWatcherWindows() { + // Signal the Watcher to exit. + SetEvent(ovlIO.hEvent); + HandlerThread.join(); + WatcherThread.join(); +} + +void DirectoryWatcherWindows::InitialScan() { + Callback(getAsFileEvents(scanDirectory(Path.data())), /*IsInitial=*/true); +} + +auto error(DWORD dwError) { + const DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS; + + LPSTR buffer; + if (!FormatMessageA(dwFlags, NULL, dwError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, + 0, NULL)) { + return make_error("error " + utostr(dwError), + inconvertibleErrorCode()); + } + std::string message{buffer}; + LocalFree(buffer); + return make_error(message, inconvertibleErrorCode()); +} + } // namespace llvm::Expected> -clang::DirectoryWatcher::create( - StringRef Path, - std::function, bool)> Receiver, - bool WaitForInitialSync) { - return llvm::Expected>( - llvm::errorCodeToError(std::make_error_code(std::errc::not_supported))); +clang::DirectoryWatcher::create(StringRef Path, + DirectoryWatcherCallback Receiver, + bool WaitForInitialSync) { + if (Path.empty()) + llvm::report_fatal_error( + "DirectoryWatcher::create can not accept an empty Path."); + + // FIXME(compnerd) should we assert that the path is a directory? + SmallVector WidePath; + if (sys::windows::UTF8ToUTF16(Path, WidePath)) + return llvm::make_error("unable to convert path to UTF-16", + llvm::inconvertibleErrorCode()); + + const DWORD dwDesiredAccess = FILE_LIST_DIRECTORY; + const DWORD dwShareMode = + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; + const DWORD dwCreationDisposition = OPEN_EXISTING; + const DWORD dwFlagsAndAttributes = + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED; + + HANDLE hDirectory = + CreateFileW(WidePath.data(), dwDesiredAccess, dwShareMode, + /*lpSecurityAttributes=*/NULL, + dwCreationDisposition, dwFlagsAndAttributes, NULL); + if (hDirectory == INVALID_HANDLE_VALUE) + return error(GetLastError()); + + // NOTE: We use the watcher instance as a RAII object to discard the handles + // for the directory and the IOCP in case of an error. Hence, this is early + // allocated, with the state being written directly to the watcher. + return std::make_unique(hDirectory, + WaitForInitialSync, + Receiver); }