//===- 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);
}