WindowsBackend.cc 7.9 KB

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