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 | }
|
---|