3 * It is possible to #include CoreFoundation/CoreFoundation.h when compiling
4 * with clang, but not with GCC as of time of writing.
6 * See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93082 for details.
8 typedef unsigned int FSEventStreamCreateFlags;
9 #define kFSEventStreamEventFlagNone 0x00000000
10 #define kFSEventStreamEventFlagMustScanSubDirs 0x00000001
11 #define kFSEventStreamEventFlagUserDropped 0x00000002
12 #define kFSEventStreamEventFlagKernelDropped 0x00000004
13 #define kFSEventStreamEventFlagEventIdsWrapped 0x00000008
14 #define kFSEventStreamEventFlagHistoryDone 0x00000010
15 #define kFSEventStreamEventFlagRootChanged 0x00000020
16 #define kFSEventStreamEventFlagMount 0x00000040
17 #define kFSEventStreamEventFlagUnmount 0x00000080
18 #define kFSEventStreamEventFlagItemCreated 0x00000100
19 #define kFSEventStreamEventFlagItemRemoved 0x00000200
20 #define kFSEventStreamEventFlagItemInodeMetaMod 0x00000400
21 #define kFSEventStreamEventFlagItemRenamed 0x00000800
22 #define kFSEventStreamEventFlagItemModified 0x00001000
23 #define kFSEventStreamEventFlagItemFinderInfoMod 0x00002000
24 #define kFSEventStreamEventFlagItemChangeOwner 0x00004000
25 #define kFSEventStreamEventFlagItemXattrMod 0x00008000
26 #define kFSEventStreamEventFlagItemIsFile 0x00010000
27 #define kFSEventStreamEventFlagItemIsDir 0x00020000
28 #define kFSEventStreamEventFlagItemIsSymlink 0x00040000
29 #define kFSEventStreamEventFlagOwnEvent 0x00080000
30 #define kFSEventStreamEventFlagItemIsHardlink 0x00100000
31 #define kFSEventStreamEventFlagItemIsLastHardlink 0x00200000
32 #define kFSEventStreamEventFlagItemCloned 0x00400000
34 typedef struct __FSEventStream *FSEventStreamRef;
35 typedef const FSEventStreamRef ConstFSEventStreamRef;
37 typedef unsigned int CFStringEncoding;
38 #define kCFStringEncodingUTF8 0x08000100
40 typedef const struct __CFString *CFStringRef;
41 typedef const struct __CFArray *CFArrayRef;
42 typedef const struct __CFRunLoop *CFRunLoopRef;
44 struct FSEventStreamContext {
46 void *cb_data, *retain, *release, *copy_description;
49 typedef struct FSEventStreamContext FSEventStreamContext;
50 typedef unsigned int FSEventStreamEventFlags;
51 #define kFSEventStreamCreateFlagNoDefer 0x02
52 #define kFSEventStreamCreateFlagWatchRoot 0x04
53 #define kFSEventStreamCreateFlagFileEvents 0x10
55 typedef unsigned long long FSEventStreamEventId;
56 #define kFSEventStreamEventIdSinceNow 0xFFFFFFFFFFFFFFFFULL
58 typedef void (*FSEventStreamCallback)(ConstFSEventStreamRef streamRef,
60 __SIZE_TYPE__ num_of_events,
62 const FSEventStreamEventFlags event_flags[],
63 const FSEventStreamEventId event_ids[]);
64 typedef double CFTimeInterval;
65 FSEventStreamRef FSEventStreamCreate(void *allocator,
66 FSEventStreamCallback callback,
67 FSEventStreamContext *context,
68 CFArrayRef paths_to_watch,
69 FSEventStreamEventId since_when,
70 CFTimeInterval latency,
71 FSEventStreamCreateFlags flags);
72 CFStringRef CFStringCreateWithCString(void *allocator, const char *string,
73 CFStringEncoding encoding);
74 CFArrayRef CFArrayCreate(void *allocator, const void **items, long long count,
76 void CFRunLoopRun(void);
77 void CFRunLoopStop(CFRunLoopRef run_loop);
78 CFRunLoopRef CFRunLoopGetCurrent(void);
79 extern CFStringRef kCFRunLoopDefaultMode;
80 void FSEventStreamScheduleWithRunLoop(FSEventStreamRef stream,
81 CFRunLoopRef run_loop,
82 CFStringRef run_loop_mode);
83 unsigned char FSEventStreamStart(FSEventStreamRef stream);
84 void FSEventStreamStop(FSEventStreamRef stream);
85 void FSEventStreamInvalidate(FSEventStreamRef stream);
86 void FSEventStreamRelease(FSEventStreamRef stream);
89 * Let Apple's headers declare `isalnum()` first, before
90 * Git's headers override it via a constant
93 #include <CoreFoundation/CoreFoundation.h>
94 #include <CoreServices/CoreServices.h>
98 #include "fsmonitor.h"
99 #include "fsmonitor-fs-listen.h"
100 #include "fsmonitor--daemon.h"
102 struct fsmonitor_daemon_backend_data
104 CFStringRef cfsr_worktree_path;
105 CFStringRef cfsr_gitdir_path;
107 CFArrayRef cfar_paths_to_watch;
108 int nr_paths_watching;
110 FSEventStreamRef stream;
114 enum shutdown_style {
120 unsigned int stream_scheduled:1;
121 unsigned int stream_started:1;
124 static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)
126 struct strbuf msg = STRBUF_INIT;
128 if (flag & kFSEventStreamEventFlagMustScanSubDirs)
129 strbuf_addstr(&msg, "MustScanSubDirs|");
130 if (flag & kFSEventStreamEventFlagUserDropped)
131 strbuf_addstr(&msg, "UserDropped|");
132 if (flag & kFSEventStreamEventFlagKernelDropped)
133 strbuf_addstr(&msg, "KernelDropped|");
134 if (flag & kFSEventStreamEventFlagEventIdsWrapped)
135 strbuf_addstr(&msg, "EventIdsWrapped|");
136 if (flag & kFSEventStreamEventFlagHistoryDone)
137 strbuf_addstr(&msg, "HistoryDone|");
138 if (flag & kFSEventStreamEventFlagRootChanged)
139 strbuf_addstr(&msg, "RootChanged|");
140 if (flag & kFSEventStreamEventFlagMount)
141 strbuf_addstr(&msg, "Mount|");
142 if (flag & kFSEventStreamEventFlagUnmount)
143 strbuf_addstr(&msg, "Unmount|");
144 if (flag & kFSEventStreamEventFlagItemChangeOwner)
145 strbuf_addstr(&msg, "ItemChangeOwner|");
146 if (flag & kFSEventStreamEventFlagItemCreated)
147 strbuf_addstr(&msg, "ItemCreated|");
148 if (flag & kFSEventStreamEventFlagItemFinderInfoMod)
149 strbuf_addstr(&msg, "ItemFinderInfoMod|");
150 if (flag & kFSEventStreamEventFlagItemInodeMetaMod)
151 strbuf_addstr(&msg, "ItemInodeMetaMod|");
152 if (flag & kFSEventStreamEventFlagItemIsDir)
153 strbuf_addstr(&msg, "ItemIsDir|");
154 if (flag & kFSEventStreamEventFlagItemIsFile)
155 strbuf_addstr(&msg, "ItemIsFile|");
156 if (flag & kFSEventStreamEventFlagItemIsHardlink)
157 strbuf_addstr(&msg, "ItemIsHardlink|");
158 if (flag & kFSEventStreamEventFlagItemIsLastHardlink)
159 strbuf_addstr(&msg, "ItemIsLastHardlink|");
160 if (flag & kFSEventStreamEventFlagItemIsSymlink)
161 strbuf_addstr(&msg, "ItemIsSymlink|");
162 if (flag & kFSEventStreamEventFlagItemModified)
163 strbuf_addstr(&msg, "ItemModified|");
164 if (flag & kFSEventStreamEventFlagItemRemoved)
165 strbuf_addstr(&msg, "ItemRemoved|");
166 if (flag & kFSEventStreamEventFlagItemRenamed)
167 strbuf_addstr(&msg, "ItemRenamed|");
168 if (flag & kFSEventStreamEventFlagItemXattrMod)
169 strbuf_addstr(&msg, "ItemXattrMod|");
170 if (flag & kFSEventStreamEventFlagOwnEvent)
171 strbuf_addstr(&msg, "OwnEvent|");
172 if (flag & kFSEventStreamEventFlagItemCloned)
173 strbuf_addstr(&msg, "ItemCloned|");
175 trace_printf_key(&trace_fsmonitor, "fsevent: '%s', flags=%u %s",
176 path, flag, msg.buf);
178 strbuf_release(&msg);
181 static int ef_is_root_delete(const FSEventStreamEventFlags ef)
183 return (ef & kFSEventStreamEventFlagItemIsDir &&
184 ef & kFSEventStreamEventFlagItemRemoved);
187 static int ef_is_root_renamed(const FSEventStreamEventFlags ef)
189 return (ef & kFSEventStreamEventFlagItemIsDir &&
190 ef & kFSEventStreamEventFlagItemRenamed);
193 static int ef_is_dropped(const FSEventStreamEventFlags ef)
195 return (ef & kFSEventStreamEventFlagKernelDropped ||
196 ef & kFSEventStreamEventFlagUserDropped);
199 static void fsevent_callback(ConstFSEventStreamRef streamRef,
201 size_t num_of_events,
203 const FSEventStreamEventFlags event_flags[],
204 const FSEventStreamEventId event_ids[])
206 struct fsmonitor_daemon_state *state = ctx;
207 struct fsmonitor_daemon_backend_data *data = state->backend_data;
208 char **paths = (char **)event_paths;
209 struct fsmonitor_batch *batch = NULL;
210 struct string_list cookie_list = STRING_LIST_INIT_DUP;
214 struct strbuf tmp = STRBUF_INIT;
217 * Build a list of all filesystem changes into a private/local
218 * list and without holding any locks.
220 for (k = 0; k < num_of_events; k++) {
222 * On Mac, we receive an array of absolute paths.
227 * If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.
228 * Please don't log them to Trace2.
230 * trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k);
234 * If event[k] is marked as dropped, we assume that we have
235 * lost sync with the filesystem and should flush our cached
238 * [1] Abort/wake any client threads waiting for a cookie and
239 * flush the cached state data (the current token), and
240 * create a new token.
242 * [2] Discard the batch that we were locally building (since
243 * they are conceptually relative to the just flushed
246 if (ef_is_dropped(event_flags[k])) {
248 * see also kFSEventStreamEventFlagMustScanSubDirs
250 trace_printf_key(&trace_fsmonitor, "event: dropped");
252 fsmonitor_force_resync(state);
253 fsmonitor_batch__pop(batch);
254 string_list_clear(&cookie_list, 0);
257 * We assume that any events that we received
258 * in this callback after this dropped event
259 * may still be valid, so we continue rather
260 * than break. (And just in case there is a
261 * delete of ".git" hiding in there.)
266 switch (fsmonitor_classify_path_absolute(state, path_k)) {
268 case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
269 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
270 /* special case cookie files within .git or gitdir */
272 /* Use just the filename of the cookie file. */
273 slash = find_last_dir_sep(path_k);
274 string_list_append(&cookie_list,
275 slash ? slash + 1 : path_k);
278 case IS_INSIDE_DOT_GIT:
279 case IS_INSIDE_GITDIR:
280 /* ignore all other paths inside of .git or gitdir */
286 * If .git directory is deleted or renamed away,
289 if (ef_is_root_delete(event_flags[k])) {
290 trace_printf_key(&trace_fsmonitor,
291 "event: gitdir removed");
294 if (ef_is_root_renamed(event_flags[k])) {
295 trace_printf_key(&trace_fsmonitor,
296 "event: gitdir renamed");
301 case IS_WORKDIR_PATH:
302 /* try to queue normal pathnames */
304 if (trace_pass_fl(&trace_fsmonitor))
305 log_flags_set(path_k, event_flags[k]);
308 * Because of the implicit "binning" (the
309 * kernel calls us at a given frequency) and
310 * de-duping (the kernel is free to combine
311 * multiple events for a given pathname), an
312 * individual fsevent could be marked as both
313 * a file and directory. Add it to the queue
314 * with both spellings so that the client will
315 * know how much to invalidate/refresh.
318 if (event_flags[k] & kFSEventStreamEventFlagItemIsFile) {
319 const char *rel = path_k +
320 state->path_worktree_watch.len + 1;
323 batch = fsmonitor_batch__new();
324 fsmonitor_batch__add_path(batch, rel);
327 if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) {
328 const char *rel = path_k +
329 state->path_worktree_watch.len + 1;
332 strbuf_addstr(&tmp, rel);
333 strbuf_addch(&tmp, '/');
336 batch = fsmonitor_batch__new();
337 fsmonitor_batch__add_path(batch, tmp.buf);
342 case IS_OUTSIDE_CONE:
344 trace_printf_key(&trace_fsmonitor,
345 "ignoring '%s'", path_k);
350 fsmonitor_publish(state, batch, &cookie_list);
351 string_list_clear(&cookie_list, 0);
352 strbuf_release(&tmp);
356 fsmonitor_batch__pop(batch);
357 string_list_clear(&cookie_list, 0);
359 data->shutdown_style = FORCE_SHUTDOWN;
360 CFRunLoopStop(data->rl);
361 strbuf_release(&tmp);
366 * NEEDSWORK: Investigate the proper value for the `latency` argument
367 * in the call to `FSEventStreamCreate()`. I'm not sure that this
368 * needs to be a config setting or just something that we tune after
371 * With a latency of 0.1, I was seeing lots of dropped events during
372 * the "touch 100000" files test within t/perf/p7519, but with a
373 * latency of 0.001 I did not see any dropped events. So the
374 * "correct" value may be somewhere in between.
376 * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
379 int fsmonitor_fs_listen__ctor(struct fsmonitor_daemon_state *state)
381 FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagNoDefer |
382 kFSEventStreamCreateFlagWatchRoot |
383 kFSEventStreamCreateFlagFileEvents;
384 FSEventStreamContext ctx = {
391 struct fsmonitor_daemon_backend_data *data;
392 const void *dir_array[2];
394 CALLOC_ARRAY(data, 1);
395 state->backend_data = data;
397 data->cfsr_worktree_path = CFStringCreateWithCString(
398 NULL, state->path_worktree_watch.buf, kCFStringEncodingUTF8);
399 dir_array[data->nr_paths_watching++] = data->cfsr_worktree_path;
401 if (state->nr_paths_watching > 1) {
402 data->cfsr_gitdir_path = CFStringCreateWithCString(
403 NULL, state->path_gitdir_watch.buf,
404 kCFStringEncodingUTF8);
405 dir_array[data->nr_paths_watching++] = data->cfsr_gitdir_path;
408 data->cfar_paths_to_watch = CFArrayCreate(NULL, dir_array,
409 data->nr_paths_watching,
411 data->stream = FSEventStreamCreate(NULL, fsevent_callback, &ctx,
412 data->cfar_paths_to_watch,
413 kFSEventStreamEventIdSinceNow,
415 if (data->stream == NULL)
419 * `data->rl` needs to be set inside the listener thread.
425 error("Unable to create FSEventStream.");
427 FREE_AND_NULL(state->backend_data);
431 void fsmonitor_fs_listen__dtor(struct fsmonitor_daemon_state *state)
433 struct fsmonitor_daemon_backend_data *data;
435 if (!state || !state->backend_data)
438 data = state->backend_data;
441 if (data->stream_started)
442 FSEventStreamStop(data->stream);
443 if (data->stream_scheduled)
444 FSEventStreamInvalidate(data->stream);
445 FSEventStreamRelease(data->stream);
448 FREE_AND_NULL(state->backend_data);
451 void fsmonitor_fs_listen__stop_async(struct fsmonitor_daemon_state *state)
453 struct fsmonitor_daemon_backend_data *data;
455 data = state->backend_data;
456 data->shutdown_style = SHUTDOWN_EVENT;
458 CFRunLoopStop(data->rl);
461 void fsmonitor_fs_listen__loop(struct fsmonitor_daemon_state *state)
463 struct fsmonitor_daemon_backend_data *data;
465 data = state->backend_data;
467 data->rl = CFRunLoopGetCurrent();
469 FSEventStreamScheduleWithRunLoop(data->stream, data->rl, kCFRunLoopDefaultMode);
470 data->stream_scheduled = 1;
472 if (!FSEventStreamStart(data->stream)) {
473 error("Failed to start the FSEventStream");
474 goto force_error_stop_without_loop;
476 data->stream_started = 1;
480 switch (data->shutdown_style) {
481 case FORCE_ERROR_STOP:
482 state->error_code = -1;
485 ipc_server_stop_async(state->ipc_server_data);
493 force_error_stop_without_loop:
494 state->error_code = -1;
495 ipc_server_stop_async(state->ipc_server_data);