297 lines
9.6 KiB
C++
297 lines
9.6 KiB
C++
//===- 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);
|
|
}
|