[0c6b92a] | 1 | #include <string>
|
---|
| 2 | #include <stack>
|
---|
| 3 | #include "../DirTree.hh"
|
---|
| 4 | #include "../shared/BruteForceBackend.hh"
|
---|
| 5 | #include "./WindowsBackend.hh"
|
---|
| 6 | #include "./win_utils.hh"
|
---|
| 7 |
|
---|
| 8 | #define DEFAULT_BUF_SIZE 1024 * 1024
|
---|
| 9 | #define NETWORK_BUF_SIZE 64 * 1024
|
---|
| 10 | #define CONVERT_TIME(ft) ULARGE_INTEGER{ft.dwLowDateTime, ft.dwHighDateTime}.QuadPart
|
---|
| 11 |
|
---|
| 12 | void BruteForceBackend::readTree(WatcherRef watcher, std::shared_ptr<DirTree> tree) {
|
---|
| 13 | std::stack<std::string> directories;
|
---|
| 14 |
|
---|
| 15 | directories.push(watcher->mDir);
|
---|
| 16 |
|
---|
| 17 | while (!directories.empty()) {
|
---|
| 18 | HANDLE hFind = INVALID_HANDLE_VALUE;
|
---|
| 19 |
|
---|
| 20 | std::string path = directories.top();
|
---|
| 21 | std::string spec = path + "\\*";
|
---|
| 22 | directories.pop();
|
---|
| 23 |
|
---|
| 24 | WIN32_FIND_DATA ffd;
|
---|
| 25 | hFind = FindFirstFile(spec.c_str(), &ffd);
|
---|
| 26 |
|
---|
| 27 | if (hFind == INVALID_HANDLE_VALUE) {
|
---|
| 28 | if (path == watcher->mDir) {
|
---|
| 29 | FindClose(hFind);
|
---|
| 30 | throw WatcherError("Error opening directory", watcher);
|
---|
| 31 | }
|
---|
| 32 |
|
---|
| 33 | tree->remove(path);
|
---|
| 34 | continue;
|
---|
| 35 | }
|
---|
| 36 |
|
---|
| 37 | do {
|
---|
| 38 | if (strcmp(ffd.cFileName, ".") != 0 && strcmp(ffd.cFileName, "..") != 0) {
|
---|
| 39 | std::string fullPath = path + "\\" + ffd.cFileName;
|
---|
| 40 | if (watcher->isIgnored(fullPath)) {
|
---|
| 41 | continue;
|
---|
| 42 | }
|
---|
| 43 |
|
---|
| 44 | tree->add(fullPath, CONVERT_TIME(ffd.ftLastWriteTime), ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
---|
| 45 | if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
---|
| 46 | directories.push(fullPath);
|
---|
| 47 | }
|
---|
| 48 | }
|
---|
| 49 | } while (FindNextFile(hFind, &ffd) != 0);
|
---|
| 50 |
|
---|
| 51 | FindClose(hFind);
|
---|
| 52 | }
|
---|
| 53 | }
|
---|
| 54 |
|
---|
| 55 | void WindowsBackend::start() {
|
---|
| 56 | mRunning = true;
|
---|
| 57 | notifyStarted();
|
---|
| 58 |
|
---|
| 59 | while (mRunning) {
|
---|
| 60 | SleepEx(INFINITE, true);
|
---|
| 61 | }
|
---|
| 62 | }
|
---|
| 63 |
|
---|
| 64 | WindowsBackend::~WindowsBackend() {
|
---|
| 65 | // Mark as stopped, and queue a noop function in the thread to break the loop
|
---|
| 66 | mRunning = false;
|
---|
| 67 | QueueUserAPC([](__in ULONG_PTR) {}, mThread.native_handle(), (ULONG_PTR)this);
|
---|
| 68 | }
|
---|
| 69 |
|
---|
| 70 | class Subscription: public WatcherState {
|
---|
| 71 | public:
|
---|
| 72 | Subscription(WindowsBackend *backend, WatcherRef watcher, std::shared_ptr<DirTree> tree) {
|
---|
| 73 | mRunning = true;
|
---|
| 74 | mBackend = backend;
|
---|
| 75 | mWatcher = watcher;
|
---|
| 76 | mTree = tree;
|
---|
| 77 | ZeroMemory(&mOverlapped, sizeof(OVERLAPPED));
|
---|
| 78 | mOverlapped.hEvent = this;
|
---|
| 79 | mReadBuffer.resize(DEFAULT_BUF_SIZE);
|
---|
| 80 | mWriteBuffer.resize(DEFAULT_BUF_SIZE);
|
---|
| 81 |
|
---|
| 82 | mDirectoryHandle = CreateFileW(
|
---|
| 83 | utf8ToUtf16(watcher->mDir).data(),
|
---|
| 84 | FILE_LIST_DIRECTORY,
|
---|
| 85 | FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
---|
| 86 | NULL,
|
---|
| 87 | OPEN_EXISTING,
|
---|
| 88 | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
---|
| 89 | NULL
|
---|
| 90 | );
|
---|
| 91 |
|
---|
| 92 | if (mDirectoryHandle == INVALID_HANDLE_VALUE) {
|
---|
| 93 | throw WatcherError("Invalid handle", mWatcher);
|
---|
| 94 | }
|
---|
| 95 |
|
---|
| 96 | // Ensure that the path is a directory
|
---|
| 97 | BY_HANDLE_FILE_INFORMATION info;
|
---|
| 98 | bool success = GetFileInformationByHandle(
|
---|
| 99 | mDirectoryHandle,
|
---|
| 100 | &info
|
---|
| 101 | );
|
---|
| 102 |
|
---|
| 103 | if (!success) {
|
---|
| 104 | throw WatcherError("Could not get file information", mWatcher);
|
---|
| 105 | }
|
---|
| 106 |
|
---|
| 107 | if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
---|
| 108 | throw WatcherError("Not a directory", mWatcher);
|
---|
| 109 | }
|
---|
| 110 | }
|
---|
| 111 |
|
---|
| 112 | virtual ~Subscription() {
|
---|
| 113 | stop();
|
---|
| 114 | }
|
---|
| 115 |
|
---|
| 116 | void run() {
|
---|
| 117 | try {
|
---|
| 118 | poll();
|
---|
| 119 | } catch (WatcherError &err) {
|
---|
| 120 | mBackend->handleWatcherError(err);
|
---|
| 121 | }
|
---|
| 122 | }
|
---|
| 123 |
|
---|
| 124 | void stop() {
|
---|
| 125 | if (mRunning) {
|
---|
| 126 | mRunning = false;
|
---|
| 127 | CancelIo(mDirectoryHandle);
|
---|
| 128 | CloseHandle(mDirectoryHandle);
|
---|
| 129 | }
|
---|
| 130 | }
|
---|
| 131 |
|
---|
| 132 | void poll() {
|
---|
| 133 | if (!mRunning) {
|
---|
| 134 | return;
|
---|
| 135 | }
|
---|
| 136 |
|
---|
| 137 | // Asynchronously wait for changes.
|
---|
| 138 | int success = ReadDirectoryChangesW(
|
---|
| 139 | mDirectoryHandle,
|
---|
| 140 | mWriteBuffer.data(),
|
---|
| 141 | static_cast<DWORD>(mWriteBuffer.size()),
|
---|
| 142 | TRUE, // recursive
|
---|
| 143 | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES
|
---|
| 144 | | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE,
|
---|
| 145 | NULL,
|
---|
| 146 | &mOverlapped,
|
---|
| 147 | [](DWORD errorCode, DWORD numBytes, LPOVERLAPPED overlapped) {
|
---|
| 148 | auto subscription = reinterpret_cast<Subscription *>(overlapped->hEvent);
|
---|
| 149 | try {
|
---|
| 150 | subscription->processEvents(errorCode);
|
---|
| 151 | } catch (WatcherError &err) {
|
---|
| 152 | subscription->mBackend->handleWatcherError(err);
|
---|
| 153 | }
|
---|
| 154 | }
|
---|
| 155 | );
|
---|
| 156 |
|
---|
| 157 | if (!success) {
|
---|
| 158 | throw WatcherError("Failed to read changes", mWatcher);
|
---|
| 159 | }
|
---|
| 160 | }
|
---|
| 161 |
|
---|
| 162 | void processEvents(DWORD errorCode) {
|
---|
| 163 | if (!mRunning) {
|
---|
| 164 | return;
|
---|
| 165 | }
|
---|
| 166 |
|
---|
| 167 | switch (errorCode) {
|
---|
| 168 | case ERROR_OPERATION_ABORTED:
|
---|
| 169 | return;
|
---|
| 170 | case ERROR_INVALID_PARAMETER:
|
---|
| 171 | // resize buffers to network size (64kb), and try again
|
---|
| 172 | mReadBuffer.resize(NETWORK_BUF_SIZE);
|
---|
| 173 | mWriteBuffer.resize(NETWORK_BUF_SIZE);
|
---|
| 174 | poll();
|
---|
| 175 | return;
|
---|
| 176 | case ERROR_NOTIFY_ENUM_DIR:
|
---|
| 177 | throw WatcherError("Buffer overflow. Some events may have been lost.", mWatcher);
|
---|
| 178 | case ERROR_ACCESS_DENIED: {
|
---|
| 179 | // This can happen if the watched directory is deleted. Check if that is the case,
|
---|
| 180 | // and if so emit a delete event. Otherwise, fall through to default error case.
|
---|
| 181 | DWORD attrs = GetFileAttributesW(utf8ToUtf16(mWatcher->mDir).data());
|
---|
| 182 | bool isDir = attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY);
|
---|
| 183 | if (!isDir) {
|
---|
| 184 | mWatcher->mEvents.remove(mWatcher->mDir);
|
---|
| 185 | mTree->remove(mWatcher->mDir);
|
---|
| 186 | mWatcher->notify();
|
---|
| 187 | stop();
|
---|
| 188 | return;
|
---|
| 189 | }
|
---|
| 190 | }
|
---|
| 191 | default:
|
---|
| 192 | if (errorCode != ERROR_SUCCESS) {
|
---|
| 193 | throw WatcherError("Unknown error", mWatcher);
|
---|
| 194 | }
|
---|
| 195 | }
|
---|
| 196 |
|
---|
| 197 | // Swap read and write buffers, and poll again
|
---|
| 198 | std::swap(mWriteBuffer, mReadBuffer);
|
---|
| 199 | poll();
|
---|
| 200 |
|
---|
| 201 | // Read change events
|
---|
| 202 | BYTE *base = mReadBuffer.data();
|
---|
| 203 | while (true) {
|
---|
| 204 | PFILE_NOTIFY_INFORMATION info = (PFILE_NOTIFY_INFORMATION)base;
|
---|
| 205 | processEvent(info);
|
---|
| 206 |
|
---|
| 207 | if (info->NextEntryOffset == 0) {
|
---|
| 208 | break;
|
---|
| 209 | }
|
---|
| 210 |
|
---|
| 211 | base += info->NextEntryOffset;
|
---|
| 212 | }
|
---|
| 213 |
|
---|
| 214 | mWatcher->notify();
|
---|
| 215 | }
|
---|
| 216 |
|
---|
| 217 | void processEvent(PFILE_NOTIFY_INFORMATION info) {
|
---|
| 218 | std::string path = mWatcher->mDir + "\\" + utf16ToUtf8(info->FileName, info->FileNameLength / sizeof(WCHAR));
|
---|
| 219 | if (mWatcher->isIgnored(path)) {
|
---|
| 220 | return;
|
---|
| 221 | }
|
---|
| 222 |
|
---|
| 223 | switch (info->Action) {
|
---|
| 224 | case FILE_ACTION_ADDED:
|
---|
| 225 | case FILE_ACTION_RENAMED_NEW_NAME: {
|
---|
| 226 | WIN32_FILE_ATTRIBUTE_DATA data;
|
---|
| 227 | if (GetFileAttributesExW(utf8ToUtf16(path).data(), GetFileExInfoStandard, &data)) {
|
---|
| 228 | mWatcher->mEvents.create(path);
|
---|
| 229 | mTree->add(path, CONVERT_TIME(data.ftLastWriteTime), data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
---|
| 230 | }
|
---|
| 231 | break;
|
---|
| 232 | }
|
---|
| 233 | case FILE_ACTION_MODIFIED: {
|
---|
| 234 | WIN32_FILE_ATTRIBUTE_DATA data;
|
---|
| 235 | if (GetFileAttributesExW(utf8ToUtf16(path).data(), GetFileExInfoStandard, &data)) {
|
---|
| 236 | mTree->update(path, CONVERT_TIME(data.ftLastWriteTime));
|
---|
| 237 | if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
---|
| 238 | mWatcher->mEvents.update(path);
|
---|
| 239 | }
|
---|
| 240 | }
|
---|
| 241 | break;
|
---|
| 242 | }
|
---|
| 243 | case FILE_ACTION_REMOVED:
|
---|
| 244 | case FILE_ACTION_RENAMED_OLD_NAME:
|
---|
| 245 | mWatcher->mEvents.remove(path);
|
---|
| 246 | mTree->remove(path);
|
---|
| 247 | break;
|
---|
| 248 | }
|
---|
| 249 | }
|
---|
| 250 |
|
---|
| 251 | private:
|
---|
| 252 | WindowsBackend *mBackend;
|
---|
| 253 | std::shared_ptr<Watcher> mWatcher;
|
---|
| 254 | std::shared_ptr<DirTree> mTree;
|
---|
| 255 | bool mRunning;
|
---|
| 256 | HANDLE mDirectoryHandle;
|
---|
| 257 | std::vector<BYTE> mReadBuffer;
|
---|
| 258 | std::vector<BYTE> mWriteBuffer;
|
---|
| 259 | OVERLAPPED mOverlapped;
|
---|
| 260 | };
|
---|
| 261 |
|
---|
| 262 | // This function is called by Backend::watch which takes a lock on mMutex
|
---|
| 263 | void WindowsBackend::subscribe(WatcherRef watcher) {
|
---|
| 264 | // Create a subscription for this watcher
|
---|
| 265 | auto sub = std::make_shared<Subscription>(this, watcher, getTree(watcher, false));
|
---|
| 266 | watcher->state = sub;
|
---|
| 267 |
|
---|
| 268 | // Queue polling for this subscription in the correct thread.
|
---|
| 269 | bool success = QueueUserAPC([](__in ULONG_PTR ptr) {
|
---|
| 270 | Subscription *sub = (Subscription *)ptr;
|
---|
| 271 | sub->run();
|
---|
| 272 | }, mThread.native_handle(), (ULONG_PTR)sub.get());
|
---|
| 273 |
|
---|
| 274 | if (!success) {
|
---|
| 275 | throw std::runtime_error("Unable to queue APC");
|
---|
| 276 | }
|
---|
| 277 | }
|
---|
| 278 |
|
---|
| 279 | // This function is called by Backend::unwatch which takes a lock on mMutex
|
---|
| 280 | void WindowsBackend::unsubscribe(WatcherRef watcher) {
|
---|
| 281 | watcher->state = nullptr;
|
---|
| 282 | }
|
---|