brintos

brintos / llvm-project-archived public Read only

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