|
6 | 6 | //
|
7 | 7 | //===----------------------------------------------------------------------===//
|
8 | 8 |
|
9 |
| -// TODO: This is not yet an implementation, but it will make it so Windows |
10 |
| -// builds don't fail. |
11 |
| - |
12 | 9 | #include "DirectoryScanner.h"
|
13 | 10 | #include "clang/DirectoryWatcher/DirectoryWatcher.h"
|
14 |
| - |
15 | 11 | #include "llvm/ADT/STLExtras.h"
|
16 |
| -#include "llvm/ADT/ScopeExit.h" |
17 |
| -#include "llvm/Support/AlignOf.h" |
18 |
| -#include "llvm/Support/Errno.h" |
19 |
| -#include "llvm/Support/Mutex.h" |
| 12 | +#include "llvm/Support/ConvertUTF.h" |
20 | 13 | #include "llvm/Support/Path.h"
|
21 |
| -#include <atomic> |
| 14 | +#include "llvm/Support/Windows/WindowsSupport.h" |
22 | 15 | #include <condition_variable>
|
23 | 16 | #include <mutex>
|
24 | 17 | #include <queue>
|
|
28 | 21 |
|
29 | 22 | namespace {
|
30 | 23 |
|
| 24 | +using DirectoryWatcherCallback = |
| 25 | + std::function<void(llvm::ArrayRef<clang::DirectoryWatcher::Event>, bool)>; |
| 26 | + |
31 | 27 | using namespace llvm;
|
32 | 28 | using namespace clang;
|
33 | 29 |
|
34 | 30 | class DirectoryWatcherWindows : public clang::DirectoryWatcher {
|
| 31 | + OVERLAPPED Overlapped; |
| 32 | + |
| 33 | + std::vector<DWORD> Notifications; |
| 34 | + |
| 35 | + std::thread WatcherThread; |
| 36 | + std::thread HandlerThread; |
| 37 | + std::function<void(ArrayRef<DirectoryWatcher::Event>, bool)> Callback; |
| 38 | + SmallString<MAX_PATH> Path; |
| 39 | + HANDLE Terminate; |
| 40 | + |
| 41 | + class EventQueue { |
| 42 | + std::mutex M; |
| 43 | + std::queue<DirectoryWatcher::Event> Q; |
| 44 | + std::condition_variable CV; |
| 45 | + |
| 46 | + public: |
| 47 | + void emplace(DirectoryWatcher::Event::EventKind Kind, StringRef Path) { |
| 48 | + { |
| 49 | + std::unique_lock<std::mutex> L(M); |
| 50 | + Q.emplace(Kind, Path); |
| 51 | + } |
| 52 | + CV.notify_one(); |
| 53 | + } |
| 54 | + |
| 55 | + DirectoryWatcher::Event pop_front() { |
| 56 | + std::unique_lock<std::mutex> L(M); |
| 57 | + while (true) { |
| 58 | + if (!Q.empty()) { |
| 59 | + DirectoryWatcher::Event E = Q.front(); |
| 60 | + Q.pop(); |
| 61 | + return E; |
| 62 | + } |
| 63 | + CV.wait(L, [this]() { return !Q.empty(); }); |
| 64 | + } |
| 65 | + } |
| 66 | + } Q; |
| 67 | + |
35 | 68 | public:
|
36 |
| - ~DirectoryWatcherWindows() override { } |
37 |
| - void InitialScan() { } |
38 |
| - void EventReceivingLoop() { } |
39 |
| - void StopWork() { } |
| 69 | + DirectoryWatcherWindows(HANDLE DirectoryHandle, bool WaitForInitialSync, |
| 70 | + DirectoryWatcherCallback Receiver); |
| 71 | + |
| 72 | + ~DirectoryWatcherWindows() override; |
| 73 | + |
| 74 | + void InitialScan(); |
| 75 | + void WatcherThreadProc(HANDLE DirectoryHandle); |
| 76 | + void NotifierThreadProc(bool WaitForInitialSync); |
40 | 77 | };
|
| 78 | + |
| 79 | +DirectoryWatcherWindows::DirectoryWatcherWindows( |
| 80 | + HANDLE DirectoryHandle, bool WaitForInitialSync, |
| 81 | + DirectoryWatcherCallback Receiver) |
| 82 | + : Callback(Receiver), Terminate(INVALID_HANDLE_VALUE) { |
| 83 | + // Pre-compute the real location as we will be handing over the directory |
| 84 | + // handle to the watcher and performing synchronous operations. |
| 85 | + { |
| 86 | + DWORD Length = GetFinalPathNameByHandleW(DirectoryHandle, NULL, 0, 0); |
| 87 | + |
| 88 | + std::vector<WCHAR> Buffer; |
| 89 | + Buffer.resize(Length); |
| 90 | + |
| 91 | + Length = GetFinalPathNameByHandleW(DirectoryHandle, Buffer.data(), |
| 92 | + Buffer.size(), 0); |
| 93 | + Buffer.resize(Length); |
| 94 | + |
| 95 | + llvm::sys::windows::UTF16ToUTF8(Buffer.data(), Buffer.size(), Path); |
| 96 | + } |
| 97 | + |
| 98 | + Notifications.resize(4 * (sizeof(FILE_NOTIFY_INFORMATION) + |
| 99 | + MAX_PATH * sizeof(WCHAR))); |
| 100 | + |
| 101 | + memset(&Overlapped, 0, sizeof(Overlapped)); |
| 102 | + Overlapped.hEvent = |
| 103 | + CreateEventW(NULL, /*bManualReset=*/TRUE, /*bInitialState=*/FALSE, NULL); |
| 104 | + assert(Overlapped.hEvent && "unable to create event"); |
| 105 | + |
| 106 | + Terminate = CreateEventW(NULL, /*bManualReset=*/TRUE, |
| 107 | + /*bInitialState=*/FALSE, NULL); |
| 108 | + |
| 109 | + WatcherThread = std::thread([this, DirectoryHandle]() { |
| 110 | + this->WatcherThreadProc(DirectoryHandle); |
| 111 | + }); |
| 112 | + |
| 113 | + if (WaitForInitialSync) |
| 114 | + InitialScan(); |
| 115 | + |
| 116 | + HandlerThread = std::thread([this, WaitForInitialSync]() { |
| 117 | + this->NotifierThreadProc(WaitForInitialSync); |
| 118 | + }); |
| 119 | +} |
| 120 | + |
| 121 | +DirectoryWatcherWindows::~DirectoryWatcherWindows() { |
| 122 | + // Signal the Watcher to exit. |
| 123 | + SetEvent(Terminate); |
| 124 | + HandlerThread.join(); |
| 125 | + WatcherThread.join(); |
| 126 | + CloseHandle(Terminate); |
| 127 | + CloseHandle(Overlapped.hEvent); |
| 128 | +} |
| 129 | + |
| 130 | +void DirectoryWatcherWindows::InitialScan() { |
| 131 | + Callback(getAsFileEvents(scanDirectory(Path.data())), /*IsInitial=*/true); |
| 132 | +} |
| 133 | + |
| 134 | +void DirectoryWatcherWindows::WatcherThreadProc(HANDLE DirectoryHandle) { |
| 135 | + while (true) { |
| 136 | + // We do not guarantee subdirectories, but macOS already provides |
| 137 | + // subdirectories, might as well as ... |
| 138 | + BOOL WatchSubtree = TRUE; |
| 139 | + DWORD NotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME |
| 140 | + | FILE_NOTIFY_CHANGE_DIR_NAME |
| 141 | + | FILE_NOTIFY_CHANGE_SIZE |
| 142 | + | FILE_NOTIFY_CHANGE_LAST_ACCESS |
| 143 | + | FILE_NOTIFY_CHANGE_LAST_WRITE |
| 144 | + | FILE_NOTIFY_CHANGE_CREATION; |
| 145 | + |
| 146 | + DWORD BytesTransferred; |
| 147 | + if (!ReadDirectoryChangesW(DirectoryHandle, Notifications.data(), |
| 148 | + Notifications.size(), WatchSubtree, |
| 149 | + NotifyFilter, &BytesTransferred, &Overlapped, |
| 150 | + NULL)) { |
| 151 | + Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
| 152 | + ""); |
| 153 | + break; |
| 154 | + } |
| 155 | + |
| 156 | + HANDLE Handles[2] = { Terminate, Overlapped.hEvent }; |
| 157 | + switch (WaitForMultipleObjects(2, Handles, FALSE, INFINITE)) { |
| 158 | + case WAIT_OBJECT_0: // Terminate Request |
| 159 | + case WAIT_FAILED: // Failure |
| 160 | + Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
| 161 | + ""); |
| 162 | + (void)CloseHandle(DirectoryHandle); |
| 163 | + return; |
| 164 | + case WAIT_TIMEOUT: // Spurious wakeup? |
| 165 | + continue; |
| 166 | + case WAIT_OBJECT_0 + 1: // Directory change |
| 167 | + break; |
| 168 | + } |
| 169 | + |
| 170 | + if (!GetOverlappedResult(DirectoryHandle, &Overlapped, &BytesTransferred, |
| 171 | + FALSE)) { |
| 172 | + Q.emplace(DirectoryWatcher::Event::EventKind::WatchedDirRemoved, |
| 173 | + ""); |
| 174 | + Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
| 175 | + ""); |
| 176 | + break; |
| 177 | + } |
| 178 | + |
| 179 | + // There was a buffer underrun on the kernel side. We may have lost |
| 180 | + // events, please re-synchronize. |
| 181 | + if (BytesTransferred == 0) { |
| 182 | + Q.emplace(DirectoryWatcher::Event::EventKind::WatcherGotInvalidated, |
| 183 | + ""); |
| 184 | + break; |
| 185 | + } |
| 186 | + |
| 187 | + for (FILE_NOTIFY_INFORMATION *I = |
| 188 | + (FILE_NOTIFY_INFORMATION *)Notifications.data(); |
| 189 | + I; |
| 190 | + I = I->NextEntryOffset |
| 191 | + ? (FILE_NOTIFY_INFORMATION *)((CHAR *)I + I->NextEntryOffset) |
| 192 | + : NULL) { |
| 193 | + DirectoryWatcher::Event::EventKind Kind = |
| 194 | + DirectoryWatcher::Event::EventKind::WatcherGotInvalidated; |
| 195 | + switch (I->Action) { |
| 196 | + case FILE_ACTION_MODIFIED: |
| 197 | + Kind = DirectoryWatcher::Event::EventKind::Modified; |
| 198 | + break; |
| 199 | + case FILE_ACTION_ADDED: |
| 200 | + Kind = DirectoryWatcher::Event::EventKind::Modified; |
| 201 | + break; |
| 202 | + case FILE_ACTION_REMOVED: |
| 203 | + Kind = DirectoryWatcher::Event::EventKind::Removed; |
| 204 | + break; |
| 205 | + case FILE_ACTION_RENAMED_OLD_NAME: |
| 206 | + Kind = DirectoryWatcher::Event::EventKind::Removed; |
| 207 | + break; |
| 208 | + case FILE_ACTION_RENAMED_NEW_NAME: |
| 209 | + Kind = DirectoryWatcher::Event::EventKind::Modified; |
| 210 | + break; |
| 211 | + } |
| 212 | + |
| 213 | + SmallString<MAX_PATH> filename; |
| 214 | + sys::windows::UTF16ToUTF8(I->FileName, I->FileNameLength / 2, |
| 215 | + filename); |
| 216 | + Q.emplace(Kind, filename); |
| 217 | + } |
| 218 | + } |
| 219 | + |
| 220 | + (void)CloseHandle(DirectoryHandle); |
| 221 | +} |
| 222 | + |
| 223 | +void DirectoryWatcherWindows::NotifierThreadProc(bool WaitForInitialSync) { |
| 224 | + // If we did not wait for the initial sync, then we should perform the |
| 225 | + // scan when we enter the thread. |
| 226 | + if (!WaitForInitialSync) |
| 227 | + this->InitialScan(); |
| 228 | + |
| 229 | + while (true) { |
| 230 | + DirectoryWatcher::Event E = Q.pop_front(); |
| 231 | + Callback(E, /*IsInitial=*/false); |
| 232 | + if (E.Kind == DirectoryWatcher::Event::EventKind::WatcherGotInvalidated) |
| 233 | + break; |
| 234 | + } |
| 235 | +} |
| 236 | + |
| 237 | +auto error(DWORD ErrorCode) { |
| 238 | + DWORD Flags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
| 239 | + | FORMAT_MESSAGE_FROM_SYSTEM |
| 240 | + | FORMAT_MESSAGE_IGNORE_INSERTS; |
| 241 | + |
| 242 | + LPSTR Buffer; |
| 243 | + if (!FormatMessageA(Flags, NULL, ErrorCode, |
| 244 | + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&Buffer, |
| 245 | + 0, NULL)) { |
| 246 | + return make_error<llvm::StringError>("error " + utostr(ErrorCode), |
| 247 | + inconvertibleErrorCode()); |
| 248 | + } |
| 249 | + std::string Message{Buffer}; |
| 250 | + LocalFree(Buffer); |
| 251 | + return make_error<llvm::StringError>(Message, inconvertibleErrorCode()); |
| 252 | +} |
| 253 | + |
41 | 254 | } // namespace
|
42 | 255 |
|
43 | 256 | llvm::Expected<std::unique_ptr<DirectoryWatcher>>
|
44 |
| -clang::DirectoryWatcher::create( |
45 |
| - StringRef Path, |
46 |
| - std::function<void(llvm::ArrayRef<DirectoryWatcher::Event>, bool)> Receiver, |
47 |
| - bool WaitForInitialSync) { |
48 |
| - return llvm::Expected<std::unique_ptr<DirectoryWatcher>>( |
49 |
| - llvm::errorCodeToError(std::make_error_code(std::errc::not_supported))); |
| 257 | +clang::DirectoryWatcher::create(StringRef Path, |
| 258 | + DirectoryWatcherCallback Receiver, |
| 259 | + bool WaitForInitialSync) { |
| 260 | + if (Path.empty()) |
| 261 | + llvm::report_fatal_error( |
| 262 | + "DirectoryWatcher::create can not accept an empty Path."); |
| 263 | + |
| 264 | + if (!sys::fs::is_directory(Path)) |
| 265 | + llvm::report_fatal_error( |
| 266 | + "DirectoryWatcher::create can not accept a filepath."); |
| 267 | + |
| 268 | + SmallVector<wchar_t, MAX_PATH> WidePath; |
| 269 | + if (sys::windows::UTF8ToUTF16(Path, WidePath)) |
| 270 | + return llvm::make_error<llvm::StringError>( |
| 271 | + "unable to convert path to UTF-16", llvm::inconvertibleErrorCode()); |
| 272 | + |
| 273 | + DWORD DesiredAccess = FILE_LIST_DIRECTORY; |
| 274 | + DWORD ShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; |
| 275 | + DWORD CreationDisposition = OPEN_EXISTING; |
| 276 | + DWORD FlagsAndAttributes = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED; |
| 277 | + |
| 278 | + HANDLE DirectoryHandle = |
| 279 | + CreateFileW(WidePath.data(), DesiredAccess, ShareMode, |
| 280 | + /*lpSecurityAttributes=*/NULL, CreationDisposition, |
| 281 | + FlagsAndAttributes, NULL); |
| 282 | + if (DirectoryHandle == INVALID_HANDLE_VALUE) |
| 283 | + return error(GetLastError()); |
| 284 | + |
| 285 | + // NOTE: We use the watcher instance as a RAII object to discard the handles |
| 286 | + // for the directory and the IOCP in case of an error. Hence, this is early |
| 287 | + // allocated, with the state being written directly to the watcher. |
| 288 | + return std::make_unique<DirectoryWatcherWindows>( |
| 289 | + DirectoryHandle, WaitForInitialSync, Receiver); |
50 | 290 | }
|
0 commit comments