ac43d70fe7bc5286ddb8dfde218ef3ae9c815a3eb7e93023b49e95e2f0a0260a347c50c617a602384153294b8230ad5a280b1941beddb7b3321303f2a1ad40 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. #include <CoreServices/CoreServices.h>
  2. #include <sys/stat.h>
  3. #include <string>
  4. #include <fstream>
  5. #include <unordered_set>
  6. #include "../Event.hh"
  7. #include "../Backend.hh"
  8. #include "./FSEventsBackend.hh"
  9. #include "../Watcher.hh"
  10. #define CONVERT_TIME(ts) ((uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec)
  11. #define IGNORED_FLAGS (kFSEventStreamEventFlagItemIsHardlink | kFSEventStreamEventFlagItemIsLastHardlink | kFSEventStreamEventFlagItemIsSymlink | kFSEventStreamEventFlagItemIsDir | kFSEventStreamEventFlagItemIsFile)
  12. void stopStream(FSEventStreamRef stream, CFRunLoopRef runLoop) {
  13. FSEventStreamStop(stream);
  14. FSEventStreamUnscheduleFromRunLoop(stream, runLoop, kCFRunLoopDefaultMode);
  15. FSEventStreamInvalidate(stream);
  16. FSEventStreamRelease(stream);
  17. }
  18. // macOS has a case insensitive file system by default. In order to detect
  19. // file renames that only affect case, we need to get the canonical path
  20. // and compare it with the input path to determine if a file was created or deleted.
  21. bool pathExists(char *path) {
  22. int fd = open(path, O_RDONLY | O_SYMLINK);
  23. if (fd == -1) {
  24. return false;
  25. }
  26. char buf[PATH_MAX];
  27. if (fcntl(fd, F_GETPATH, buf) == -1) {
  28. close(fd);
  29. return false;
  30. }
  31. bool res = strncmp(path, buf, PATH_MAX) == 0;
  32. close(fd);
  33. return res;
  34. }
  35. class State: public WatcherState {
  36. public:
  37. FSEventStreamRef stream;
  38. std::shared_ptr<DirTree> tree;
  39. uint64_t since;
  40. };
  41. void FSEventsCallback(
  42. ConstFSEventStreamRef streamRef,
  43. void *clientCallBackInfo,
  44. size_t numEvents,
  45. void *eventPaths,
  46. const FSEventStreamEventFlags eventFlags[],
  47. const FSEventStreamEventId eventIds[]
  48. ) {
  49. char **paths = (char **)eventPaths;
  50. std::shared_ptr<Watcher>& watcher = *static_cast<std::shared_ptr<Watcher> *>(clientCallBackInfo);
  51. EventList& list = watcher->mEvents;
  52. if (watcher->state == nullptr) {
  53. return;
  54. }
  55. auto stateGuard = watcher->state;
  56. auto* state = static_cast<State*>(stateGuard.get());
  57. uint64_t since = state->since;
  58. bool deletedRoot = false;
  59. for (size_t i = 0; i < numEvents; ++i) {
  60. bool isCreated = (eventFlags[i] & kFSEventStreamEventFlagItemCreated) == kFSEventStreamEventFlagItemCreated;
  61. bool isRemoved = (eventFlags[i] & kFSEventStreamEventFlagItemRemoved) == kFSEventStreamEventFlagItemRemoved;
  62. bool isModified = (eventFlags[i] & kFSEventStreamEventFlagItemModified) == kFSEventStreamEventFlagItemModified ||
  63. (eventFlags[i] & kFSEventStreamEventFlagItemInodeMetaMod) == kFSEventStreamEventFlagItemInodeMetaMod ||
  64. (eventFlags[i] & kFSEventStreamEventFlagItemFinderInfoMod) == kFSEventStreamEventFlagItemFinderInfoMod ||
  65. (eventFlags[i] & kFSEventStreamEventFlagItemChangeOwner) == kFSEventStreamEventFlagItemChangeOwner ||
  66. (eventFlags[i] & kFSEventStreamEventFlagItemXattrMod) == kFSEventStreamEventFlagItemXattrMod;
  67. bool isRenamed = (eventFlags[i] & kFSEventStreamEventFlagItemRenamed) == kFSEventStreamEventFlagItemRenamed;
  68. bool isDone = (eventFlags[i] & kFSEventStreamEventFlagHistoryDone) == kFSEventStreamEventFlagHistoryDone;
  69. bool isDir = (eventFlags[i] & kFSEventStreamEventFlagItemIsDir) == kFSEventStreamEventFlagItemIsDir;
  70. if (eventFlags[i] & kFSEventStreamEventFlagMustScanSubDirs) {
  71. if (eventFlags[i] & kFSEventStreamEventFlagUserDropped) {
  72. list.error("Events were dropped by the FSEvents client. File system must be re-scanned.");
  73. } else if (eventFlags[i] & kFSEventStreamEventFlagKernelDropped) {
  74. list.error("Events were dropped by the kernel. File system must be re-scanned.");
  75. } else {
  76. list.error("Too many events. File system must be re-scanned.");
  77. }
  78. }
  79. if (isDone) {
  80. watcher->notify();
  81. break;
  82. }
  83. auto ignoredFlags = IGNORED_FLAGS;
  84. if (__builtin_available(macOS 10.13, *)) {
  85. ignoredFlags |= kFSEventStreamEventFlagItemCloned;
  86. }
  87. // If we don't care about any of the flags that are set, ignore this event.
  88. if ((eventFlags[i] & ~ignoredFlags) == 0) {
  89. continue;
  90. }
  91. // FSEvents exclusion paths only apply to files, not directories.
  92. if (watcher->isIgnored(paths[i])) {
  93. continue;
  94. }
  95. // Handle unambiguous events first
  96. if (isCreated && !(isRemoved || isModified || isRenamed)) {
  97. state->tree->add(paths[i], 0, isDir);
  98. list.create(paths[i]);
  99. } else if (isRemoved && !(isCreated || isModified || isRenamed)) {
  100. state->tree->remove(paths[i]);
  101. list.remove(paths[i]);
  102. if (paths[i] == watcher->mDir) {
  103. deletedRoot = true;
  104. }
  105. } else if (isModified && !(isCreated || isRemoved || isRenamed)) {
  106. struct stat file;
  107. if (stat(paths[i], &file)) {
  108. continue;
  109. }
  110. // Ignore if mtime is the same as the last event.
  111. // This prevents duplicate events from being emitted.
  112. // If tv_nsec is zero, the file system probably only has second-level
  113. // granularity so allow the even through in that case.
  114. uint64_t mtime = CONVERT_TIME(file.st_mtimespec);
  115. DirEntry *entry = state->tree->find(paths[i]);
  116. if (entry && mtime == entry->mtime && file.st_mtimespec.tv_nsec != 0) {
  117. continue;
  118. }
  119. if (entry) {
  120. // Update mtime.
  121. entry->mtime = mtime;
  122. } else {
  123. // Add to tree if this path has not been discovered yet.
  124. state->tree->add(paths[i], mtime, S_ISDIR(file.st_mode));
  125. }
  126. list.update(paths[i]);
  127. } else {
  128. // If multiple flags were set, then we need to call `stat` to determine if the file really exists.
  129. // This helps disambiguate creates, updates, and deletes.
  130. struct stat file;
  131. if (stat(paths[i], &file) || !pathExists(paths[i])) {
  132. // File does not exist, so we have to assume it was removed. This is not exact since the
  133. // flags set by fsevents get coalesced together (e.g. created & deleted), so there is no way to
  134. // know whether the create and delete both happened since our snapshot (in which case
  135. // we'd rather ignore this event completely). This will result in some extra delete events
  136. // being emitted for files we don't know about, but that is the best we can do.
  137. state->tree->remove(paths[i]);
  138. list.remove(paths[i]);
  139. if (paths[i] == watcher->mDir) {
  140. deletedRoot = true;
  141. }
  142. continue;
  143. }
  144. // If the file was modified, and existed before, then this is an update, otherwise a create.
  145. uint64_t ctime = CONVERT_TIME(file.st_birthtimespec);
  146. uint64_t mtime = CONVERT_TIME(file.st_mtimespec);
  147. DirEntry *entry = !since ? state->tree->find(paths[i]) : NULL;
  148. if (entry && entry->mtime == mtime && file.st_mtimespec.tv_nsec != 0) {
  149. continue;
  150. }
  151. // Some mounted file systems report a creation time of 0/unix epoch which we special case.
  152. if (isModified && (entry || (ctime <= since && ctime != 0))) {
  153. state->tree->update(paths[i], mtime);
  154. list.update(paths[i]);
  155. } else {
  156. state->tree->add(paths[i], mtime, S_ISDIR(file.st_mode));
  157. list.create(paths[i]);
  158. }
  159. }
  160. }
  161. if (!since) {
  162. watcher->notify();
  163. }
  164. // Stop watching if the root directory was deleted.
  165. if (deletedRoot) {
  166. stopStream((FSEventStreamRef)streamRef, CFRunLoopGetCurrent());
  167. watcher->state = nullptr;
  168. }
  169. }
  170. void checkWatcher(WatcherRef watcher) {
  171. struct stat file;
  172. if (stat(watcher->mDir.c_str(), &file)) {
  173. throw WatcherError(strerror(errno), watcher);
  174. }
  175. if (!S_ISDIR(file.st_mode)) {
  176. throw WatcherError(strerror(ENOTDIR), watcher);
  177. }
  178. }
  179. void FSEventsBackend::startStream(WatcherRef watcher, FSEventStreamEventId id) {
  180. checkWatcher(watcher);
  181. CFAbsoluteTime latency = 0.001;
  182. CFStringRef fileWatchPath = CFStringCreateWithCString(
  183. NULL,
  184. watcher->mDir.c_str(),
  185. kCFStringEncodingUTF8
  186. );
  187. CFArrayRef pathsToWatch = CFArrayCreate(
  188. NULL,
  189. (const void **)&fileWatchPath,
  190. 1,
  191. NULL
  192. );
  193. // Make a watcher reference we can pass into the callback. This ensures bumped ref-count.
  194. std::shared_ptr<Watcher>* callbackWatcher = new std::shared_ptr<Watcher> (watcher);
  195. FSEventStreamContext callbackInfo {0, static_cast<void*> (callbackWatcher), nullptr, nullptr, nullptr};
  196. FSEventStreamRef stream = FSEventStreamCreate(
  197. NULL,
  198. &FSEventsCallback,
  199. &callbackInfo,
  200. pathsToWatch,
  201. id,
  202. latency,
  203. kFSEventStreamCreateFlagFileEvents
  204. );
  205. CFMutableArrayRef exclusions = CFArrayCreateMutable(NULL, watcher->mIgnorePaths.size(), NULL);
  206. for (auto it = watcher->mIgnorePaths.begin(); it != watcher->mIgnorePaths.end(); it++) {
  207. CFStringRef path = CFStringCreateWithCString(
  208. NULL,
  209. it->c_str(),
  210. kCFStringEncodingUTF8
  211. );
  212. CFArrayAppendValue(exclusions, (const void *)path);
  213. }
  214. FSEventStreamSetExclusionPaths(stream, exclusions);
  215. FSEventStreamScheduleWithRunLoop(stream, mRunLoop, kCFRunLoopDefaultMode);
  216. bool started = FSEventStreamStart(stream);
  217. CFRelease(pathsToWatch);
  218. CFRelease(fileWatchPath);
  219. if (!started) {
  220. FSEventStreamRelease(stream);
  221. throw WatcherError("Error starting FSEvents stream", watcher);
  222. }
  223. auto stateGuard = watcher->state;
  224. State* s = static_cast<State*>(stateGuard.get());
  225. s->tree = std::make_shared<DirTree>(watcher->mDir);
  226. s->stream = stream;
  227. }
  228. void FSEventsBackend::start() {
  229. mRunLoop = CFRunLoopGetCurrent();
  230. CFRetain(mRunLoop);
  231. // Unlock once run loop has started.
  232. CFRunLoopPerformBlock(mRunLoop, kCFRunLoopDefaultMode, ^ {
  233. notifyStarted();
  234. });
  235. CFRunLoopWakeUp(mRunLoop);
  236. CFRunLoopRun();
  237. }
  238. FSEventsBackend::~FSEventsBackend() {
  239. std::unique_lock<std::mutex> lock(mMutex);
  240. CFRunLoopStop(mRunLoop);
  241. CFRelease(mRunLoop);
  242. }
  243. void FSEventsBackend::writeSnapshot(WatcherRef watcher, std::string *snapshotPath) {
  244. std::unique_lock<std::mutex> lock(mMutex);
  245. checkWatcher(watcher);
  246. FSEventStreamEventId id = FSEventsGetCurrentEventId();
  247. std::ofstream ofs(*snapshotPath);
  248. ofs << id;
  249. ofs << "\n";
  250. struct timespec now;
  251. clock_gettime(CLOCK_REALTIME, &now);
  252. ofs << CONVERT_TIME(now);
  253. }
  254. void FSEventsBackend::getEventsSince(WatcherRef watcher, std::string *snapshotPath) {
  255. std::unique_lock<std::mutex> lock(mMutex);
  256. std::ifstream ifs(*snapshotPath);
  257. if (ifs.fail()) {
  258. return;
  259. }
  260. FSEventStreamEventId id;
  261. uint64_t since;
  262. ifs >> id;
  263. ifs >> since;
  264. auto s = std::make_shared<State>();
  265. s->since = since;
  266. watcher->state = s;
  267. startStream(watcher, id);
  268. watcher->wait();
  269. stopStream(s->stream, mRunLoop);
  270. watcher->state = nullptr;
  271. }
  272. // This function is called by Backend::watch which takes a lock on mMutex
  273. void FSEventsBackend::subscribe(WatcherRef watcher) {
  274. auto s = std::make_shared<State>();
  275. s->since = 0;
  276. watcher->state = s;
  277. startStream(watcher, kFSEventStreamEventIdSinceNow);
  278. }
  279. // This function is called by Backend::unwatch which takes a lock on mMutex
  280. void FSEventsBackend::unsubscribe(WatcherRef watcher) {
  281. auto stateGuard = watcher->state;
  282. State* s = static_cast<State*>(stateGuard.get());
  283. if (s != nullptr) {
  284. stopStream(s->stream, mRunLoop);
  285. watcher->state = nullptr;
  286. }
  287. }