source: imaps-frontend/node_modules/@parcel/watcher/src/macos/FSEventsBackend.cc@ 0c6b92a

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

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