]> git.ipfire.org Git - thirdparty/git.git/blame - compat/fsmonitor/fsm-listen-darwin.c
Merge branch 'rs/parse-options-with-keep-unknown-abbrev-fix'
[thirdparty/git.git] / compat / fsmonitor / fsm-listen-darwin.c
CommitLineData
5ff01b1f 1#ifndef __clang__
b0226007 2#include <dispatch/dispatch.h>
5ff01b1f
JH
3#include "fsm-darwin-gcc.h"
4#else
5#include <CoreFoundation/CoreFoundation.h>
6#include <CoreServices/CoreServices.h>
7
8#ifndef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER
9/*
10 * This enum value was added in 10.13 to:
11 *
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
16 *
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.
21 */
22#define kFSEventStreamEventFlagItemCloned 0x00400000
23#endif
24#endif
25
e1c38214 26#include "git-compat-util.h"
68d68646 27#include "fsmonitor-ll.h"
f67df255 28#include "fsm-listen.h"
65723b30 29#include "fsmonitor--daemon.h"
12fd27df 30#include "fsmonitor-path-utils.h"
f394e093 31#include "gettext.h"
31d20faa 32#include "simple-ipc.h"
4c98cb8e 33#include "string-list.h"
68d68646 34#include "trace.h"
65723b30 35
207534e4 36struct fsm_listen_data
65723b30
JH
37{
38 CFStringRef cfsr_worktree_path;
39 CFStringRef cfsr_gitdir_path;
40
41 CFArrayRef cfar_paths_to_watch;
42 int nr_paths_watching;
43
44 FSEventStreamRef stream;
45
b0226007
JH
46 dispatch_queue_t dq;
47 pthread_cond_t dq_finished;
48 pthread_mutex_t dq_lock;
65723b30
JH
49
50 enum shutdown_style {
51 SHUTDOWN_EVENT = 0,
52 FORCE_SHUTDOWN,
53 FORCE_ERROR_STOP,
54 } shutdown_style;
55
56 unsigned int stream_scheduled:1;
57 unsigned int stream_started:1;
58};
59
60static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)
61{
62 struct strbuf msg = STRBUF_INIT;
63
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|");
110
8e8f4b81 111 trace_printf_key(&trace_fsmonitor, "fsevent: '%s', flags=0x%x %s",
65723b30
JH
112 path, flag, msg.buf);
113
114 strbuf_release(&msg);
115}
116
de7e0b58
JH
117static int ef_is_root_changed(const FSEventStreamEventFlags ef)
118{
119 return (ef & kFSEventStreamEventFlagRootChanged);
120}
121
65723b30
JH
122static int ef_is_root_delete(const FSEventStreamEventFlags ef)
123{
124 return (ef & kFSEventStreamEventFlagItemIsDir &&
125 ef & kFSEventStreamEventFlagItemRemoved);
126}
127
128static int ef_is_root_renamed(const FSEventStreamEventFlags ef)
129{
130 return (ef & kFSEventStreamEventFlagItemIsDir &&
131 ef & kFSEventStreamEventFlagItemRenamed);
132}
133
134static int ef_is_dropped(const FSEventStreamEventFlags ef)
135{
136 return (ef & kFSEventStreamEventFlagMustScanSubDirs ||
137 ef & kFSEventStreamEventFlagKernelDropped ||
138 ef & kFSEventStreamEventFlagUserDropped);
139}
140
8e8f4b81
JH
141/*
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`.
147 *
148 * Return 1 if we should ignore it.
149 */
150static int ef_ignore_xattr(const FSEventStreamEventFlags ef)
151{
152 static const FSEventStreamEventFlags mask =
153 kFSEventStreamEventFlagItemChangeOwner |
154 kFSEventStreamEventFlagItemCreated |
155 kFSEventStreamEventFlagItemFinderInfoMod |
156 kFSEventStreamEventFlagItemInodeMetaMod |
157 kFSEventStreamEventFlagItemModified |
158 kFSEventStreamEventFlagItemRemoved |
159 kFSEventStreamEventFlagItemRenamed |
160 kFSEventStreamEventFlagItemXattrMod |
161 kFSEventStreamEventFlagItemCloned;
162
163 return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod);
164}
165
d6d58ff8
JH
166/*
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.
171 *
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.
177 */
178static void my_add_path(struct fsmonitor_batch *batch, const char *path)
179{
180 char *composed;
181
182 /* add the NFC or NFD path as received from the OS */
183 fsmonitor_batch__add_path(batch, path);
184
185 /* if NFD, also add the corresponding NFC spelling */
186 composed = (char *)precompose_string_if_needed(path);
187 if (!composed || composed == path)
188 return;
189
190 fsmonitor_batch__add_path(batch, composed);
191 free(composed);
192}
193
194
997eb910 195static void fsevent_callback(ConstFSEventStreamRef streamRef UNUSED,
65723b30
JH
196 void *ctx,
197 size_t num_of_events,
198 void *event_paths,
199 const FSEventStreamEventFlags event_flags[],
997eb910 200 const FSEventStreamEventId event_ids[] UNUSED)
65723b30
JH
201{
202 struct fsmonitor_daemon_state *state = ctx;
207534e4 203 struct fsm_listen_data *data = state->listen_data;
65723b30
JH
204 char **paths = (char **)event_paths;
205 struct fsmonitor_batch *batch = NULL;
206 struct string_list cookie_list = STRING_LIST_INIT_DUP;
207 const char *path_k;
208 const char *slash;
12fd27df 209 char *resolved = NULL;
65723b30 210 struct strbuf tmp = STRBUF_INIT;
12fd27df 211 int k;
65723b30
JH
212
213 /*
214 * Build a list of all filesystem changes into a private/local
215 * list and without holding any locks.
216 */
217 for (k = 0; k < num_of_events; k++) {
218 /*
219 * On Mac, we receive an array of absolute paths.
220 */
12fd27df
ED
221 free(resolved);
222 resolved = fsmonitor__resolve_alias(paths[k], &state->alias);
223 if (resolved)
224 path_k = resolved;
225 else
226 path_k = paths[k];
65723b30
JH
227
228 /*
229 * If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.
230 * Please don't log them to Trace2.
231 *
232 * trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k);
233 */
234
235 /*
236 * If event[k] is marked as dropped, we assume that we have
237 * lost sync with the filesystem and should flush our cached
238 * data. We need to:
239 *
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.
243 *
244 * [2] Discard the batch that we were locally building (since
245 * they are conceptually relative to the just flushed
246 * token).
247 */
248 if (ef_is_dropped(event_flags[k])) {
249 if (trace_pass_fl(&trace_fsmonitor))
250 log_flags_set(path_k, event_flags[k]);
251
252 fsmonitor_force_resync(state);
253 fsmonitor_batch__free_list(batch);
254 string_list_clear(&cookie_list, 0);
12fd27df 255 batch = NULL;
65723b30
JH
256
257 /*
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.)
263 */
264 continue;
265 }
266
de7e0b58
JH
267 if (ef_is_root_changed(event_flags[k])) {
268 /*
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
272 * path.
273 *
274 * (There may be other conditions that throw this,
275 * but I couldn't find any information on it.)
276 *
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.
281 */
282 trace_printf_key(&trace_fsmonitor,
283 "event: root changed");
284 goto force_shutdown;
285 }
286
8e8f4b81
JH
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]);
291 continue;
292 }
293
65723b30
JH
294 switch (fsmonitor_classify_path_absolute(state, path_k)) {
295
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 */
299
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);
304 break;
305
306 case IS_INSIDE_DOT_GIT:
307 case IS_INSIDE_GITDIR:
308 /* ignore all other paths inside of .git or gitdir */
309 break;
310
311 case IS_DOT_GIT:
312 case IS_GITDIR:
313 /*
314 * If .git directory is deleted or renamed away,
315 * we have to quit.
316 */
317 if (ef_is_root_delete(event_flags[k])) {
318 trace_printf_key(&trace_fsmonitor,
319 "event: gitdir removed");
320 goto force_shutdown;
321 }
322 if (ef_is_root_renamed(event_flags[k])) {
323 trace_printf_key(&trace_fsmonitor,
324 "event: gitdir renamed");
325 goto force_shutdown;
326 }
327 break;
328
329 case IS_WORKDIR_PATH:
330 /* try to queue normal pathnames */
331
332 if (trace_pass_fl(&trace_fsmonitor))
333 log_flags_set(path_k, event_flags[k]);
334
335 /*
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.
344 */
345
ee0e7fc9 346 if (event_flags[k] & (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemIsSymlink)) {
65723b30
JH
347 const char *rel = path_k +
348 state->path_worktree_watch.len + 1;
349
350 if (!batch)
351 batch = fsmonitor_batch__new();
d6d58ff8 352 my_add_path(batch, rel);
65723b30
JH
353 }
354
355 if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) {
356 const char *rel = path_k +
357 state->path_worktree_watch.len + 1;
358
359 strbuf_reset(&tmp);
360 strbuf_addstr(&tmp, rel);
361 strbuf_addch(&tmp, '/');
362
363 if (!batch)
364 batch = fsmonitor_batch__new();
d6d58ff8 365 my_add_path(batch, tmp.buf);
65723b30
JH
366 }
367
368 break;
369
370 case IS_OUTSIDE_CONE:
371 default:
372 trace_printf_key(&trace_fsmonitor,
373 "ignoring '%s'", path_k);
374 break;
375 }
376 }
377
12fd27df 378 free(resolved);
65723b30
JH
379 fsmonitor_publish(state, batch, &cookie_list);
380 string_list_clear(&cookie_list, 0);
381 strbuf_release(&tmp);
382 return;
383
384force_shutdown:
12fd27df 385 free(resolved);
65723b30
JH
386 fsmonitor_batch__free_list(batch);
387 string_list_clear(&cookie_list, 0);
388
b0226007 389 pthread_mutex_lock(&data->dq_lock);
65723b30 390 data->shutdown_style = FORCE_SHUTDOWN;
b0226007
JH
391 pthread_cond_broadcast(&data->dq_finished);
392 pthread_mutex_unlock(&data->dq_lock);
393
65723b30
JH
394 strbuf_release(&tmp);
395 return;
396}
397
398/*
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.
406 *
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.
411 *
412 * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
413 */
f67df255
JH
414
415int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
416{
65723b30
JH
417 FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagNoDefer |
418 kFSEventStreamCreateFlagWatchRoot |
419 kFSEventStreamCreateFlagFileEvents;
420 FSEventStreamContext ctx = {
421 0,
422 state,
423 NULL,
424 NULL,
425 NULL
426 };
207534e4 427 struct fsm_listen_data *data;
65723b30
JH
428 const void *dir_array[2];
429
430 CALLOC_ARRAY(data, 1);
207534e4 431 state->listen_data = data;
65723b30
JH
432
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;
436
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;
442 }
443
444 data->cfar_paths_to_watch = CFArrayCreate(NULL, dir_array,
445 data->nr_paths_watching,
446 NULL);
447 data->stream = FSEventStreamCreate(NULL, fsevent_callback, &ctx,
448 data->cfar_paths_to_watch,
449 kFSEventStreamEventIdSinceNow,
450 0.001, flags);
e6bf70d1 451 if (!data->stream)
65723b30
JH
452 goto failed;
453
65723b30
JH
454 return 0;
455
456failed:
457 error(_("Unable to create FSEventStream."));
458
207534e4 459 FREE_AND_NULL(state->listen_data);
f67df255
JH
460 return -1;
461}
462
463void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
464{
207534e4 465 struct fsm_listen_data *data;
65723b30 466
207534e4 467 if (!state || !state->listen_data)
65723b30
JH
468 return;
469
207534e4 470 data = state->listen_data;
65723b30
JH
471
472 if (data->stream) {
473 if (data->stream_started)
474 FSEventStreamStop(data->stream);
475 if (data->stream_scheduled)
476 FSEventStreamInvalidate(data->stream);
477 FSEventStreamRelease(data->stream);
478 }
479
b0226007
JH
480 if (data->dq)
481 dispatch_release(data->dq);
482 pthread_cond_destroy(&data->dq_finished);
483 pthread_mutex_destroy(&data->dq_lock);
484
207534e4 485 FREE_AND_NULL(state->listen_data);
f67df255
JH
486}
487
488void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
489{
207534e4 490 struct fsm_listen_data *data;
65723b30 491
207534e4 492 data = state->listen_data;
65723b30 493
b0226007
JH
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);
f67df255
JH
498}
499
500void fsm_listen__loop(struct fsmonitor_daemon_state *state)
501{
207534e4 502 struct fsm_listen_data *data;
65723b30 503
207534e4 504 data = state->listen_data;
65723b30 505
b0226007
JH
506 pthread_mutex_init(&data->dq_lock, NULL);
507 pthread_cond_init(&data->dq_finished, NULL);
508 data->dq = dispatch_queue_create("FSMonitor", NULL);
65723b30 509
b0226007 510 FSEventStreamSetDispatchQueue(data->stream, data->dq);
65723b30
JH
511 data->stream_scheduled = 1;
512
513 if (!FSEventStreamStart(data->stream)) {
514 error(_("Failed to start the FSEventStream"));
515 goto force_error_stop_without_loop;
516 }
517 data->stream_started = 1;
518
b0226007
JH
519 pthread_mutex_lock(&data->dq_lock);
520 pthread_cond_wait(&data->dq_finished, &data->dq_lock);
521 pthread_mutex_unlock(&data->dq_lock);
65723b30
JH
522
523 switch (data->shutdown_style) {
524 case FORCE_ERROR_STOP:
207534e4 525 state->listen_error_code = -1;
65723b30
JH
526 /* fall thru */
527 case FORCE_SHUTDOWN:
528 ipc_server_stop_async(state->ipc_server_data);
529 /* fall thru */
530 case SHUTDOWN_EVENT:
531 default:
532 break;
533 }
534 return;
535
536force_error_stop_without_loop:
207534e4 537 state->listen_error_code = -1;
65723b30
JH
538 ipc_server_stop_async(state->ipc_server_data);
539 return;
f67df255 540}