2 #include <dispatch/dispatch.h>
3 #include "fsm-darwin-gcc.h"
5 #include <CoreFoundation/CoreFoundation.h>
6 #include <CoreServices/CoreServices.h>
8 #ifndef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER
10 * This enum value was added in 10.13 to:
12 * /Applications/Xcode.app/Contents/Developer/Platforms/ \
13 * MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/ \
14 * Library/Frameworks/CoreServices.framework/Frameworks/ \
15 * FSEvents.framework/Versions/Current/Headers/FSEvents.h
17 * If we're compiling against an older SDK, this symbol won't be
18 * present. Silently define it here so that we don't have to ifdef
19 * the logging or masking below. This should be harmless since older
20 * versions of macOS won't ever emit this FS event anyway.
22 #define kFSEventStreamEventFlagItemCloned 0x00400000
26 #include "git-compat-util.h"
27 #include "fsmonitor-ll.h"
28 #include "fsm-listen.h"
29 #include "fsmonitor--daemon.h"
30 #include "fsmonitor-path-utils.h"
32 #include "simple-ipc.h"
33 #include "string-list.h"
36 struct fsm_listen_data
38 CFStringRef cfsr_worktree_path
;
39 CFStringRef cfsr_gitdir_path
;
41 CFArrayRef cfar_paths_to_watch
;
42 int nr_paths_watching
;
44 FSEventStreamRef stream
;
47 pthread_cond_t dq_finished
;
48 pthread_mutex_t dq_lock
;
56 unsigned int stream_scheduled
:1;
57 unsigned int stream_started
:1;
60 static void log_flags_set(const char *path
, const FSEventStreamEventFlags flag
)
62 struct strbuf msg
= STRBUF_INIT
;
64 if (flag
& kFSEventStreamEventFlagMustScanSubDirs
)
65 strbuf_addstr(&msg
, "MustScanSubDirs|");
66 if (flag
& kFSEventStreamEventFlagUserDropped
)
67 strbuf_addstr(&msg
, "UserDropped|");
68 if (flag
& kFSEventStreamEventFlagKernelDropped
)
69 strbuf_addstr(&msg
, "KernelDropped|");
70 if (flag
& kFSEventStreamEventFlagEventIdsWrapped
)
71 strbuf_addstr(&msg
, "EventIdsWrapped|");
72 if (flag
& kFSEventStreamEventFlagHistoryDone
)
73 strbuf_addstr(&msg
, "HistoryDone|");
74 if (flag
& kFSEventStreamEventFlagRootChanged
)
75 strbuf_addstr(&msg
, "RootChanged|");
76 if (flag
& kFSEventStreamEventFlagMount
)
77 strbuf_addstr(&msg
, "Mount|");
78 if (flag
& kFSEventStreamEventFlagUnmount
)
79 strbuf_addstr(&msg
, "Unmount|");
80 if (flag
& kFSEventStreamEventFlagItemChangeOwner
)
81 strbuf_addstr(&msg
, "ItemChangeOwner|");
82 if (flag
& kFSEventStreamEventFlagItemCreated
)
83 strbuf_addstr(&msg
, "ItemCreated|");
84 if (flag
& kFSEventStreamEventFlagItemFinderInfoMod
)
85 strbuf_addstr(&msg
, "ItemFinderInfoMod|");
86 if (flag
& kFSEventStreamEventFlagItemInodeMetaMod
)
87 strbuf_addstr(&msg
, "ItemInodeMetaMod|");
88 if (flag
& kFSEventStreamEventFlagItemIsDir
)
89 strbuf_addstr(&msg
, "ItemIsDir|");
90 if (flag
& kFSEventStreamEventFlagItemIsFile
)
91 strbuf_addstr(&msg
, "ItemIsFile|");
92 if (flag
& kFSEventStreamEventFlagItemIsHardlink
)
93 strbuf_addstr(&msg
, "ItemIsHardlink|");
94 if (flag
& kFSEventStreamEventFlagItemIsLastHardlink
)
95 strbuf_addstr(&msg
, "ItemIsLastHardlink|");
96 if (flag
& kFSEventStreamEventFlagItemIsSymlink
)
97 strbuf_addstr(&msg
, "ItemIsSymlink|");
98 if (flag
& kFSEventStreamEventFlagItemModified
)
99 strbuf_addstr(&msg
, "ItemModified|");
100 if (flag
& kFSEventStreamEventFlagItemRemoved
)
101 strbuf_addstr(&msg
, "ItemRemoved|");
102 if (flag
& kFSEventStreamEventFlagItemRenamed
)
103 strbuf_addstr(&msg
, "ItemRenamed|");
104 if (flag
& kFSEventStreamEventFlagItemXattrMod
)
105 strbuf_addstr(&msg
, "ItemXattrMod|");
106 if (flag
& kFSEventStreamEventFlagOwnEvent
)
107 strbuf_addstr(&msg
, "OwnEvent|");
108 if (flag
& kFSEventStreamEventFlagItemCloned
)
109 strbuf_addstr(&msg
, "ItemCloned|");
111 trace_printf_key(&trace_fsmonitor
, "fsevent: '%s', flags=0x%x %s",
112 path
, flag
, msg
.buf
);
114 strbuf_release(&msg
);
117 static int ef_is_root_changed(const FSEventStreamEventFlags ef
)
119 return (ef
& kFSEventStreamEventFlagRootChanged
);
122 static int ef_is_root_delete(const FSEventStreamEventFlags ef
)
124 return (ef
& kFSEventStreamEventFlagItemIsDir
&&
125 ef
& kFSEventStreamEventFlagItemRemoved
);
128 static int ef_is_root_renamed(const FSEventStreamEventFlags ef
)
130 return (ef
& kFSEventStreamEventFlagItemIsDir
&&
131 ef
& kFSEventStreamEventFlagItemRenamed
);
134 static int ef_is_dropped(const FSEventStreamEventFlags ef
)
136 return (ef
& kFSEventStreamEventFlagMustScanSubDirs
||
137 ef
& kFSEventStreamEventFlagKernelDropped
||
138 ef
& kFSEventStreamEventFlagUserDropped
);
142 * If an `xattr` change is the only reason we received this event,
143 * then silently ignore it. Git doesn't care about xattr's. We
144 * have to be careful here because the kernel can combine multiple
145 * events for a single path. And because events always have certain
146 * bits set, such as `ItemIsFile` or `ItemIsDir`.
148 * Return 1 if we should ignore it.
150 static int ef_ignore_xattr(const FSEventStreamEventFlags ef
)
152 static const FSEventStreamEventFlags mask
=
153 kFSEventStreamEventFlagItemChangeOwner
|
154 kFSEventStreamEventFlagItemCreated
|
155 kFSEventStreamEventFlagItemFinderInfoMod
|
156 kFSEventStreamEventFlagItemInodeMetaMod
|
157 kFSEventStreamEventFlagItemModified
|
158 kFSEventStreamEventFlagItemRemoved
|
159 kFSEventStreamEventFlagItemRenamed
|
160 kFSEventStreamEventFlagItemXattrMod
|
161 kFSEventStreamEventFlagItemCloned
;
163 return ((ef
& mask
) == kFSEventStreamEventFlagItemXattrMod
);
167 * On MacOS we have to adjust for Unicode composition insensitivity
168 * (where NFC and NFD spellings are not respected). The different
169 * spellings are essentially aliases regardless of how the path is
170 * actually stored on the disk.
172 * This is related to "core.precomposeUnicode" (which wants to try
173 * to hide NFD completely and treat everything as NFC). Here, we
174 * don't know what the value the client has (or will have) for this
175 * config setting when they make a query, so assume the worst and
176 * emit both when the OS gives us an NFD path.
178 static void my_add_path(struct fsmonitor_batch
*batch
, const char *path
)
182 /* add the NFC or NFD path as received from the OS */
183 fsmonitor_batch__add_path(batch
, path
);
185 /* if NFD, also add the corresponding NFC spelling */
186 composed
= (char *)precompose_string_if_needed(path
);
187 if (!composed
|| composed
== path
)
190 fsmonitor_batch__add_path(batch
, composed
);
195 static void fsevent_callback(ConstFSEventStreamRef streamRef UNUSED
,
197 size_t num_of_events
,
199 const FSEventStreamEventFlags event_flags
[],
200 const FSEventStreamEventId event_ids
[] UNUSED
)
202 struct fsmonitor_daemon_state
*state
= ctx
;
203 struct fsm_listen_data
*data
= state
->listen_data
;
204 char **paths
= (char **)event_paths
;
205 struct fsmonitor_batch
*batch
= NULL
;
206 struct string_list cookie_list
= STRING_LIST_INIT_DUP
;
209 char *resolved
= NULL
;
210 struct strbuf tmp
= STRBUF_INIT
;
214 * Build a list of all filesystem changes into a private/local
215 * list and without holding any locks.
217 for (k
= 0; k
< num_of_events
; k
++) {
219 * On Mac, we receive an array of absolute paths.
222 resolved
= fsmonitor__resolve_alias(paths
[k
], &state
->alias
);
229 * If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.
230 * Please don't log them to Trace2.
232 * trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k);
236 * If event[k] is marked as dropped, we assume that we have
237 * lost sync with the filesystem and should flush our cached
240 * [1] Abort/wake any client threads waiting for a cookie and
241 * flush the cached state data (the current token), and
242 * create a new token.
244 * [2] Discard the batch that we were locally building (since
245 * they are conceptually relative to the just flushed
248 if (ef_is_dropped(event_flags
[k
])) {
249 if (trace_pass_fl(&trace_fsmonitor
))
250 log_flags_set(path_k
, event_flags
[k
]);
252 fsmonitor_force_resync(state
);
253 fsmonitor_batch__free_list(batch
);
254 string_list_clear(&cookie_list
, 0);
258 * We assume that any events that we received
259 * in this callback after this dropped event
260 * may still be valid, so we continue rather
261 * than break. (And just in case there is a
262 * delete of ".git" hiding in there.)
267 if (ef_is_root_changed(event_flags
[k
])) {
269 * The spelling of the pathname of the root directory
270 * has changed. This includes the name of the root
271 * directory itself or of any parent directory in the
274 * (There may be other conditions that throw this,
275 * but I couldn't find any information on it.)
277 * Force a shutdown now and avoid things getting
278 * out of sync. The Unix domain socket is inside
279 * the .git directory and a spelling change will make
280 * it hard for clients to rendezvous with us.
282 trace_printf_key(&trace_fsmonitor
,
283 "event: root changed");
287 if (ef_ignore_xattr(event_flags
[k
])) {
288 trace_printf_key(&trace_fsmonitor
,
289 "ignore-xattr: '%s', flags=0x%x",
290 path_k
, event_flags
[k
]);
294 switch (fsmonitor_classify_path_absolute(state
, path_k
)) {
296 case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX
:
297 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX
:
298 /* special case cookie files within .git or gitdir */
300 /* Use just the filename of the cookie file. */
301 slash
= find_last_dir_sep(path_k
);
302 string_list_append(&cookie_list
,
303 slash
? slash
+ 1 : path_k
);
306 case IS_INSIDE_DOT_GIT
:
307 case IS_INSIDE_GITDIR
:
308 /* ignore all other paths inside of .git or gitdir */
314 * If .git directory is deleted or renamed away,
317 if (ef_is_root_delete(event_flags
[k
])) {
318 trace_printf_key(&trace_fsmonitor
,
319 "event: gitdir removed");
322 if (ef_is_root_renamed(event_flags
[k
])) {
323 trace_printf_key(&trace_fsmonitor
,
324 "event: gitdir renamed");
329 case IS_WORKDIR_PATH
:
330 /* try to queue normal pathnames */
332 if (trace_pass_fl(&trace_fsmonitor
))
333 log_flags_set(path_k
, event_flags
[k
]);
336 * Because of the implicit "binning" (the
337 * kernel calls us at a given frequency) and
338 * de-duping (the kernel is free to combine
339 * multiple events for a given pathname), an
340 * individual fsevent could be marked as both
341 * a file and directory. Add it to the queue
342 * with both spellings so that the client will
343 * know how much to invalidate/refresh.
346 if (event_flags
[k
] & (kFSEventStreamEventFlagItemIsFile
| kFSEventStreamEventFlagItemIsSymlink
)) {
347 const char *rel
= path_k
+
348 state
->path_worktree_watch
.len
+ 1;
351 batch
= fsmonitor_batch__new();
352 my_add_path(batch
, rel
);
355 if (event_flags
[k
] & kFSEventStreamEventFlagItemIsDir
) {
356 const char *rel
= path_k
+
357 state
->path_worktree_watch
.len
+ 1;
360 strbuf_addstr(&tmp
, rel
);
361 strbuf_addch(&tmp
, '/');
364 batch
= fsmonitor_batch__new();
365 my_add_path(batch
, tmp
.buf
);
370 case IS_OUTSIDE_CONE
:
372 trace_printf_key(&trace_fsmonitor
,
373 "ignoring '%s'", path_k
);
379 fsmonitor_publish(state
, batch
, &cookie_list
);
380 string_list_clear(&cookie_list
, 0);
381 strbuf_release(&tmp
);
386 fsmonitor_batch__free_list(batch
);
387 string_list_clear(&cookie_list
, 0);
389 pthread_mutex_lock(&data
->dq_lock
);
390 data
->shutdown_style
= FORCE_SHUTDOWN
;
391 pthread_cond_broadcast(&data
->dq_finished
);
392 pthread_mutex_unlock(&data
->dq_lock
);
394 strbuf_release(&tmp
);
399 * In the call to `FSEventStreamCreate()` to setup our watch, the
400 * `latency` argument determines the frequency of calls to our callback
401 * with new FS events. Too slow and events get dropped; too fast and
402 * we burn CPU unnecessarily. Since it is rather obscure, I don't
403 * think this needs to be a config setting. I've done extensive
404 * testing on my systems and chosen the value below. It gives good
405 * results and I've not seen any dropped events.
407 * With a latency of 0.1, I was seeing lots of dropped events during
408 * the "touch 100000" files test within t/perf/p7519, but with a
409 * latency of 0.001 I did not see any dropped events. So I'm going
410 * to assume that this is the "correct" value.
412 * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
415 int fsm_listen__ctor(struct fsmonitor_daemon_state
*state
)
417 FSEventStreamCreateFlags flags
= kFSEventStreamCreateFlagNoDefer
|
418 kFSEventStreamCreateFlagWatchRoot
|
419 kFSEventStreamCreateFlagFileEvents
;
420 FSEventStreamContext ctx
= {
427 struct fsm_listen_data
*data
;
428 const void *dir_array
[2];
430 CALLOC_ARRAY(data
, 1);
431 state
->listen_data
= data
;
433 data
->cfsr_worktree_path
= CFStringCreateWithCString(
434 NULL
, state
->path_worktree_watch
.buf
, kCFStringEncodingUTF8
);
435 dir_array
[data
->nr_paths_watching
++] = data
->cfsr_worktree_path
;
437 if (state
->nr_paths_watching
> 1) {
438 data
->cfsr_gitdir_path
= CFStringCreateWithCString(
439 NULL
, state
->path_gitdir_watch
.buf
,
440 kCFStringEncodingUTF8
);
441 dir_array
[data
->nr_paths_watching
++] = data
->cfsr_gitdir_path
;
444 data
->cfar_paths_to_watch
= CFArrayCreate(NULL
, dir_array
,
445 data
->nr_paths_watching
,
447 data
->stream
= FSEventStreamCreate(NULL
, fsevent_callback
, &ctx
,
448 data
->cfar_paths_to_watch
,
449 kFSEventStreamEventIdSinceNow
,
457 error(_("Unable to create FSEventStream."));
459 FREE_AND_NULL(state
->listen_data
);
463 void fsm_listen__dtor(struct fsmonitor_daemon_state
*state
)
465 struct fsm_listen_data
*data
;
467 if (!state
|| !state
->listen_data
)
470 data
= state
->listen_data
;
473 if (data
->stream_started
)
474 FSEventStreamStop(data
->stream
);
475 if (data
->stream_scheduled
)
476 FSEventStreamInvalidate(data
->stream
);
477 FSEventStreamRelease(data
->stream
);
481 dispatch_release(data
->dq
);
482 pthread_cond_destroy(&data
->dq_finished
);
483 pthread_mutex_destroy(&data
->dq_lock
);
485 FREE_AND_NULL(state
->listen_data
);
488 void fsm_listen__stop_async(struct fsmonitor_daemon_state
*state
)
490 struct fsm_listen_data
*data
;
492 data
= state
->listen_data
;
494 pthread_mutex_lock(&data
->dq_lock
);
495 data
->shutdown_style
= SHUTDOWN_EVENT
;
496 pthread_cond_broadcast(&data
->dq_finished
);
497 pthread_mutex_unlock(&data
->dq_lock
);
500 void fsm_listen__loop(struct fsmonitor_daemon_state
*state
)
502 struct fsm_listen_data
*data
;
504 data
= state
->listen_data
;
506 pthread_mutex_init(&data
->dq_lock
, NULL
);
507 pthread_cond_init(&data
->dq_finished
, NULL
);
508 data
->dq
= dispatch_queue_create("FSMonitor", NULL
);
510 FSEventStreamSetDispatchQueue(data
->stream
, data
->dq
);
511 data
->stream_scheduled
= 1;
513 if (!FSEventStreamStart(data
->stream
)) {
514 error(_("Failed to start the FSEventStream"));
515 goto force_error_stop_without_loop
;
517 data
->stream_started
= 1;
519 pthread_mutex_lock(&data
->dq_lock
);
520 pthread_cond_wait(&data
->dq_finished
, &data
->dq_lock
);
521 pthread_mutex_unlock(&data
->dq_lock
);
523 switch (data
->shutdown_style
) {
524 case FORCE_ERROR_STOP
:
525 state
->listen_error_code
= -1;
528 ipc_server_stop_async(state
->ipc_server_data
);
536 force_error_stop_without_loop
:
537 state
->listen_error_code
= -1;
538 ipc_server_stop_async(state
->ipc_server_data
);