2 #include "fsm-darwin-gcc.h"
4 #include <CoreFoundation/CoreFoundation.h>
5 #include <CoreServices/CoreServices.h>
7 #ifndef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER
9 * This enum value was added in 10.13 to:
11 * /Applications/Xcode.app/Contents/Developer/Platforms/ \
12 * MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/ \
13 * Library/Frameworks/CoreServices.framework/Frameworks/ \
14 * FSEvents.framework/Versions/Current/Headers/FSEvents.h
16 * If we're compiling against an older SDK, this symbol won't be
17 * present. Silently define it here so that we don't have to ifdef
18 * the logging or masking below. This should be harmless since older
19 * versions of macOS won't ever emit this FS event anyway.
21 #define kFSEventStreamEventFlagItemCloned 0x00400000
26 #include "fsmonitor.h"
27 #include "fsm-listen.h"
28 #include "fsmonitor--daemon.h"
30 struct fsm_listen_data
32 CFStringRef cfsr_worktree_path
;
33 CFStringRef cfsr_gitdir_path
;
35 CFArrayRef cfar_paths_to_watch
;
36 int nr_paths_watching
;
38 FSEventStreamRef stream
;
48 unsigned int stream_scheduled
:1;
49 unsigned int stream_started
:1;
52 static void log_flags_set(const char *path
, const FSEventStreamEventFlags flag
)
54 struct strbuf msg
= STRBUF_INIT
;
56 if (flag
& kFSEventStreamEventFlagMustScanSubDirs
)
57 strbuf_addstr(&msg
, "MustScanSubDirs|");
58 if (flag
& kFSEventStreamEventFlagUserDropped
)
59 strbuf_addstr(&msg
, "UserDropped|");
60 if (flag
& kFSEventStreamEventFlagKernelDropped
)
61 strbuf_addstr(&msg
, "KernelDropped|");
62 if (flag
& kFSEventStreamEventFlagEventIdsWrapped
)
63 strbuf_addstr(&msg
, "EventIdsWrapped|");
64 if (flag
& kFSEventStreamEventFlagHistoryDone
)
65 strbuf_addstr(&msg
, "HistoryDone|");
66 if (flag
& kFSEventStreamEventFlagRootChanged
)
67 strbuf_addstr(&msg
, "RootChanged|");
68 if (flag
& kFSEventStreamEventFlagMount
)
69 strbuf_addstr(&msg
, "Mount|");
70 if (flag
& kFSEventStreamEventFlagUnmount
)
71 strbuf_addstr(&msg
, "Unmount|");
72 if (flag
& kFSEventStreamEventFlagItemChangeOwner
)
73 strbuf_addstr(&msg
, "ItemChangeOwner|");
74 if (flag
& kFSEventStreamEventFlagItemCreated
)
75 strbuf_addstr(&msg
, "ItemCreated|");
76 if (flag
& kFSEventStreamEventFlagItemFinderInfoMod
)
77 strbuf_addstr(&msg
, "ItemFinderInfoMod|");
78 if (flag
& kFSEventStreamEventFlagItemInodeMetaMod
)
79 strbuf_addstr(&msg
, "ItemInodeMetaMod|");
80 if (flag
& kFSEventStreamEventFlagItemIsDir
)
81 strbuf_addstr(&msg
, "ItemIsDir|");
82 if (flag
& kFSEventStreamEventFlagItemIsFile
)
83 strbuf_addstr(&msg
, "ItemIsFile|");
84 if (flag
& kFSEventStreamEventFlagItemIsHardlink
)
85 strbuf_addstr(&msg
, "ItemIsHardlink|");
86 if (flag
& kFSEventStreamEventFlagItemIsLastHardlink
)
87 strbuf_addstr(&msg
, "ItemIsLastHardlink|");
88 if (flag
& kFSEventStreamEventFlagItemIsSymlink
)
89 strbuf_addstr(&msg
, "ItemIsSymlink|");
90 if (flag
& kFSEventStreamEventFlagItemModified
)
91 strbuf_addstr(&msg
, "ItemModified|");
92 if (flag
& kFSEventStreamEventFlagItemRemoved
)
93 strbuf_addstr(&msg
, "ItemRemoved|");
94 if (flag
& kFSEventStreamEventFlagItemRenamed
)
95 strbuf_addstr(&msg
, "ItemRenamed|");
96 if (flag
& kFSEventStreamEventFlagItemXattrMod
)
97 strbuf_addstr(&msg
, "ItemXattrMod|");
98 if (flag
& kFSEventStreamEventFlagOwnEvent
)
99 strbuf_addstr(&msg
, "OwnEvent|");
100 if (flag
& kFSEventStreamEventFlagItemCloned
)
101 strbuf_addstr(&msg
, "ItemCloned|");
103 trace_printf_key(&trace_fsmonitor
, "fsevent: '%s', flags=0x%x %s",
104 path
, flag
, msg
.buf
);
106 strbuf_release(&msg
);
109 static int ef_is_root_delete(const FSEventStreamEventFlags ef
)
111 return (ef
& kFSEventStreamEventFlagItemIsDir
&&
112 ef
& kFSEventStreamEventFlagItemRemoved
);
115 static int ef_is_root_renamed(const FSEventStreamEventFlags ef
)
117 return (ef
& kFSEventStreamEventFlagItemIsDir
&&
118 ef
& kFSEventStreamEventFlagItemRenamed
);
121 static int ef_is_dropped(const FSEventStreamEventFlags ef
)
123 return (ef
& kFSEventStreamEventFlagMustScanSubDirs
||
124 ef
& kFSEventStreamEventFlagKernelDropped
||
125 ef
& kFSEventStreamEventFlagUserDropped
);
129 * If an `xattr` change is the only reason we received this event,
130 * then silently ignore it. Git doesn't care about xattr's. We
131 * have to be careful here because the kernel can combine multiple
132 * events for a single path. And because events always have certain
133 * bits set, such as `ItemIsFile` or `ItemIsDir`.
135 * Return 1 if we should ignore it.
137 static int ef_ignore_xattr(const FSEventStreamEventFlags ef
)
139 static const FSEventStreamEventFlags mask
=
140 kFSEventStreamEventFlagItemChangeOwner
|
141 kFSEventStreamEventFlagItemCreated
|
142 kFSEventStreamEventFlagItemFinderInfoMod
|
143 kFSEventStreamEventFlagItemInodeMetaMod
|
144 kFSEventStreamEventFlagItemModified
|
145 kFSEventStreamEventFlagItemRemoved
|
146 kFSEventStreamEventFlagItemRenamed
|
147 kFSEventStreamEventFlagItemXattrMod
|
148 kFSEventStreamEventFlagItemCloned
;
150 return ((ef
& mask
) == kFSEventStreamEventFlagItemXattrMod
);
153 static void fsevent_callback(ConstFSEventStreamRef streamRef
,
155 size_t num_of_events
,
157 const FSEventStreamEventFlags event_flags
[],
158 const FSEventStreamEventId event_ids
[])
160 struct fsmonitor_daemon_state
*state
= ctx
;
161 struct fsm_listen_data
*data
= state
->listen_data
;
162 char **paths
= (char **)event_paths
;
163 struct fsmonitor_batch
*batch
= NULL
;
164 struct string_list cookie_list
= STRING_LIST_INIT_DUP
;
168 struct strbuf tmp
= STRBUF_INIT
;
171 * Build a list of all filesystem changes into a private/local
172 * list and without holding any locks.
174 for (k
= 0; k
< num_of_events
; k
++) {
176 * On Mac, we receive an array of absolute paths.
181 * If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.
182 * Please don't log them to Trace2.
184 * trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k);
188 * If event[k] is marked as dropped, we assume that we have
189 * lost sync with the filesystem and should flush our cached
192 * [1] Abort/wake any client threads waiting for a cookie and
193 * flush the cached state data (the current token), and
194 * create a new token.
196 * [2] Discard the batch that we were locally building (since
197 * they are conceptually relative to the just flushed
200 if (ef_is_dropped(event_flags
[k
])) {
201 if (trace_pass_fl(&trace_fsmonitor
))
202 log_flags_set(path_k
, event_flags
[k
]);
204 fsmonitor_force_resync(state
);
205 fsmonitor_batch__free_list(batch
);
206 string_list_clear(&cookie_list
, 0);
209 * We assume that any events that we received
210 * in this callback after this dropped event
211 * may still be valid, so we continue rather
212 * than break. (And just in case there is a
213 * delete of ".git" hiding in there.)
218 if (ef_ignore_xattr(event_flags
[k
])) {
219 trace_printf_key(&trace_fsmonitor
,
220 "ignore-xattr: '%s', flags=0x%x",
221 path_k
, event_flags
[k
]);
225 switch (fsmonitor_classify_path_absolute(state
, path_k
)) {
227 case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX
:
228 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX
:
229 /* special case cookie files within .git or gitdir */
231 /* Use just the filename of the cookie file. */
232 slash
= find_last_dir_sep(path_k
);
233 string_list_append(&cookie_list
,
234 slash
? slash
+ 1 : path_k
);
237 case IS_INSIDE_DOT_GIT
:
238 case IS_INSIDE_GITDIR
:
239 /* ignore all other paths inside of .git or gitdir */
245 * If .git directory is deleted or renamed away,
248 if (ef_is_root_delete(event_flags
[k
])) {
249 trace_printf_key(&trace_fsmonitor
,
250 "event: gitdir removed");
253 if (ef_is_root_renamed(event_flags
[k
])) {
254 trace_printf_key(&trace_fsmonitor
,
255 "event: gitdir renamed");
260 case IS_WORKDIR_PATH
:
261 /* try to queue normal pathnames */
263 if (trace_pass_fl(&trace_fsmonitor
))
264 log_flags_set(path_k
, event_flags
[k
]);
267 * Because of the implicit "binning" (the
268 * kernel calls us at a given frequency) and
269 * de-duping (the kernel is free to combine
270 * multiple events for a given pathname), an
271 * individual fsevent could be marked as both
272 * a file and directory. Add it to the queue
273 * with both spellings so that the client will
274 * know how much to invalidate/refresh.
277 if (event_flags
[k
] & kFSEventStreamEventFlagItemIsFile
) {
278 const char *rel
= path_k
+
279 state
->path_worktree_watch
.len
+ 1;
282 batch
= fsmonitor_batch__new();
283 fsmonitor_batch__add_path(batch
, rel
);
286 if (event_flags
[k
] & kFSEventStreamEventFlagItemIsDir
) {
287 const char *rel
= path_k
+
288 state
->path_worktree_watch
.len
+ 1;
291 strbuf_addstr(&tmp
, rel
);
292 strbuf_addch(&tmp
, '/');
295 batch
= fsmonitor_batch__new();
296 fsmonitor_batch__add_path(batch
, tmp
.buf
);
301 case IS_OUTSIDE_CONE
:
303 trace_printf_key(&trace_fsmonitor
,
304 "ignoring '%s'", path_k
);
309 fsmonitor_publish(state
, batch
, &cookie_list
);
310 string_list_clear(&cookie_list
, 0);
311 strbuf_release(&tmp
);
315 fsmonitor_batch__free_list(batch
);
316 string_list_clear(&cookie_list
, 0);
318 data
->shutdown_style
= FORCE_SHUTDOWN
;
319 CFRunLoopStop(data
->rl
);
320 strbuf_release(&tmp
);
325 * In the call to `FSEventStreamCreate()` to setup our watch, the
326 * `latency` argument determines the frequency of calls to our callback
327 * with new FS events. Too slow and events get dropped; too fast and
328 * we burn CPU unnecessarily. Since it is rather obscure, I don't
329 * think this needs to be a config setting. I've done extensive
330 * testing on my systems and chosen the value below. It gives good
331 * results and I've not seen any dropped events.
333 * With a latency of 0.1, I was seeing lots of dropped events during
334 * the "touch 100000" files test within t/perf/p7519, but with a
335 * latency of 0.001 I did not see any dropped events. So I'm going
336 * to assume that this is the "correct" value.
338 * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
341 int fsm_listen__ctor(struct fsmonitor_daemon_state
*state
)
343 FSEventStreamCreateFlags flags
= kFSEventStreamCreateFlagNoDefer
|
344 kFSEventStreamCreateFlagWatchRoot
|
345 kFSEventStreamCreateFlagFileEvents
;
346 FSEventStreamContext ctx
= {
353 struct fsm_listen_data
*data
;
354 const void *dir_array
[2];
356 CALLOC_ARRAY(data
, 1);
357 state
->listen_data
= data
;
359 data
->cfsr_worktree_path
= CFStringCreateWithCString(
360 NULL
, state
->path_worktree_watch
.buf
, kCFStringEncodingUTF8
);
361 dir_array
[data
->nr_paths_watching
++] = data
->cfsr_worktree_path
;
363 if (state
->nr_paths_watching
> 1) {
364 data
->cfsr_gitdir_path
= CFStringCreateWithCString(
365 NULL
, state
->path_gitdir_watch
.buf
,
366 kCFStringEncodingUTF8
);
367 dir_array
[data
->nr_paths_watching
++] = data
->cfsr_gitdir_path
;
370 data
->cfar_paths_to_watch
= CFArrayCreate(NULL
, dir_array
,
371 data
->nr_paths_watching
,
373 data
->stream
= FSEventStreamCreate(NULL
, fsevent_callback
, &ctx
,
374 data
->cfar_paths_to_watch
,
375 kFSEventStreamEventIdSinceNow
,
377 if (data
->stream
== NULL
)
381 * `data->rl` needs to be set inside the listener thread.
387 error(_("Unable to create FSEventStream."));
389 FREE_AND_NULL(state
->listen_data
);
393 void fsm_listen__dtor(struct fsmonitor_daemon_state
*state
)
395 struct fsm_listen_data
*data
;
397 if (!state
|| !state
->listen_data
)
400 data
= state
->listen_data
;
403 if (data
->stream_started
)
404 FSEventStreamStop(data
->stream
);
405 if (data
->stream_scheduled
)
406 FSEventStreamInvalidate(data
->stream
);
407 FSEventStreamRelease(data
->stream
);
410 FREE_AND_NULL(state
->listen_data
);
413 void fsm_listen__stop_async(struct fsmonitor_daemon_state
*state
)
415 struct fsm_listen_data
*data
;
417 data
= state
->listen_data
;
418 data
->shutdown_style
= SHUTDOWN_EVENT
;
420 CFRunLoopStop(data
->rl
);
423 void fsm_listen__loop(struct fsmonitor_daemon_state
*state
)
425 struct fsm_listen_data
*data
;
427 data
= state
->listen_data
;
429 data
->rl
= CFRunLoopGetCurrent();
431 FSEventStreamScheduleWithRunLoop(data
->stream
, data
->rl
, kCFRunLoopDefaultMode
);
432 data
->stream_scheduled
= 1;
434 if (!FSEventStreamStart(data
->stream
)) {
435 error(_("Failed to start the FSEventStream"));
436 goto force_error_stop_without_loop
;
438 data
->stream_started
= 1;
442 switch (data
->shutdown_style
) {
443 case FORCE_ERROR_STOP
:
444 state
->listen_error_code
= -1;
447 ipc_server_stop_async(state
->ipc_server_data
);
455 force_error_stop_without_loop
:
456 state
->listen_error_code
= -1;
457 ipc_server_stop_async(state
->ipc_server_data
);