]> git.ipfire.org Git - thirdparty/git.git/blob - compat/fsmonitor/fsm-listen-darwin.c
Merge branch 'jk/bundle-use-dash-for-stdfiles'
[thirdparty/git.git] / compat / fsmonitor / fsm-listen-darwin.c
1 #ifndef __clang__
2 #include <dispatch/dispatch.h>
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
26 #include "cache.h"
27 #include "fsmonitor.h"
28 #include "fsm-listen.h"
29 #include "fsmonitor--daemon.h"
30 #include "fsmonitor-path-utils.h"
31
32 struct fsm_listen_data
33 {
34 CFStringRef cfsr_worktree_path;
35 CFStringRef cfsr_gitdir_path;
36
37 CFArrayRef cfar_paths_to_watch;
38 int nr_paths_watching;
39
40 FSEventStreamRef stream;
41
42 dispatch_queue_t dq;
43 pthread_cond_t dq_finished;
44 pthread_mutex_t dq_lock;
45
46 enum shutdown_style {
47 SHUTDOWN_EVENT = 0,
48 FORCE_SHUTDOWN,
49 FORCE_ERROR_STOP,
50 } shutdown_style;
51
52 unsigned int stream_scheduled:1;
53 unsigned int stream_started:1;
54 };
55
56 static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)
57 {
58 struct strbuf msg = STRBUF_INIT;
59
60 if (flag & kFSEventStreamEventFlagMustScanSubDirs)
61 strbuf_addstr(&msg, "MustScanSubDirs|");
62 if (flag & kFSEventStreamEventFlagUserDropped)
63 strbuf_addstr(&msg, "UserDropped|");
64 if (flag & kFSEventStreamEventFlagKernelDropped)
65 strbuf_addstr(&msg, "KernelDropped|");
66 if (flag & kFSEventStreamEventFlagEventIdsWrapped)
67 strbuf_addstr(&msg, "EventIdsWrapped|");
68 if (flag & kFSEventStreamEventFlagHistoryDone)
69 strbuf_addstr(&msg, "HistoryDone|");
70 if (flag & kFSEventStreamEventFlagRootChanged)
71 strbuf_addstr(&msg, "RootChanged|");
72 if (flag & kFSEventStreamEventFlagMount)
73 strbuf_addstr(&msg, "Mount|");
74 if (flag & kFSEventStreamEventFlagUnmount)
75 strbuf_addstr(&msg, "Unmount|");
76 if (flag & kFSEventStreamEventFlagItemChangeOwner)
77 strbuf_addstr(&msg, "ItemChangeOwner|");
78 if (flag & kFSEventStreamEventFlagItemCreated)
79 strbuf_addstr(&msg, "ItemCreated|");
80 if (flag & kFSEventStreamEventFlagItemFinderInfoMod)
81 strbuf_addstr(&msg, "ItemFinderInfoMod|");
82 if (flag & kFSEventStreamEventFlagItemInodeMetaMod)
83 strbuf_addstr(&msg, "ItemInodeMetaMod|");
84 if (flag & kFSEventStreamEventFlagItemIsDir)
85 strbuf_addstr(&msg, "ItemIsDir|");
86 if (flag & kFSEventStreamEventFlagItemIsFile)
87 strbuf_addstr(&msg, "ItemIsFile|");
88 if (flag & kFSEventStreamEventFlagItemIsHardlink)
89 strbuf_addstr(&msg, "ItemIsHardlink|");
90 if (flag & kFSEventStreamEventFlagItemIsLastHardlink)
91 strbuf_addstr(&msg, "ItemIsLastHardlink|");
92 if (flag & kFSEventStreamEventFlagItemIsSymlink)
93 strbuf_addstr(&msg, "ItemIsSymlink|");
94 if (flag & kFSEventStreamEventFlagItemModified)
95 strbuf_addstr(&msg, "ItemModified|");
96 if (flag & kFSEventStreamEventFlagItemRemoved)
97 strbuf_addstr(&msg, "ItemRemoved|");
98 if (flag & kFSEventStreamEventFlagItemRenamed)
99 strbuf_addstr(&msg, "ItemRenamed|");
100 if (flag & kFSEventStreamEventFlagItemXattrMod)
101 strbuf_addstr(&msg, "ItemXattrMod|");
102 if (flag & kFSEventStreamEventFlagOwnEvent)
103 strbuf_addstr(&msg, "OwnEvent|");
104 if (flag & kFSEventStreamEventFlagItemCloned)
105 strbuf_addstr(&msg, "ItemCloned|");
106
107 trace_printf_key(&trace_fsmonitor, "fsevent: '%s', flags=0x%x %s",
108 path, flag, msg.buf);
109
110 strbuf_release(&msg);
111 }
112
113 static int ef_is_root_changed(const FSEventStreamEventFlags ef)
114 {
115 return (ef & kFSEventStreamEventFlagRootChanged);
116 }
117
118 static int ef_is_root_delete(const FSEventStreamEventFlags ef)
119 {
120 return (ef & kFSEventStreamEventFlagItemIsDir &&
121 ef & kFSEventStreamEventFlagItemRemoved);
122 }
123
124 static int ef_is_root_renamed(const FSEventStreamEventFlags ef)
125 {
126 return (ef & kFSEventStreamEventFlagItemIsDir &&
127 ef & kFSEventStreamEventFlagItemRenamed);
128 }
129
130 static int ef_is_dropped(const FSEventStreamEventFlags ef)
131 {
132 return (ef & kFSEventStreamEventFlagMustScanSubDirs ||
133 ef & kFSEventStreamEventFlagKernelDropped ||
134 ef & kFSEventStreamEventFlagUserDropped);
135 }
136
137 /*
138 * If an `xattr` change is the only reason we received this event,
139 * then silently ignore it. Git doesn't care about xattr's. We
140 * have to be careful here because the kernel can combine multiple
141 * events for a single path. And because events always have certain
142 * bits set, such as `ItemIsFile` or `ItemIsDir`.
143 *
144 * Return 1 if we should ignore it.
145 */
146 static int ef_ignore_xattr(const FSEventStreamEventFlags ef)
147 {
148 static const FSEventStreamEventFlags mask =
149 kFSEventStreamEventFlagItemChangeOwner |
150 kFSEventStreamEventFlagItemCreated |
151 kFSEventStreamEventFlagItemFinderInfoMod |
152 kFSEventStreamEventFlagItemInodeMetaMod |
153 kFSEventStreamEventFlagItemModified |
154 kFSEventStreamEventFlagItemRemoved |
155 kFSEventStreamEventFlagItemRenamed |
156 kFSEventStreamEventFlagItemXattrMod |
157 kFSEventStreamEventFlagItemCloned;
158
159 return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod);
160 }
161
162 /*
163 * On MacOS we have to adjust for Unicode composition insensitivity
164 * (where NFC and NFD spellings are not respected). The different
165 * spellings are essentially aliases regardless of how the path is
166 * actually stored on the disk.
167 *
168 * This is related to "core.precomposeUnicode" (which wants to try
169 * to hide NFD completely and treat everything as NFC). Here, we
170 * don't know what the value the client has (or will have) for this
171 * config setting when they make a query, so assume the worst and
172 * emit both when the OS gives us an NFD path.
173 */
174 static void my_add_path(struct fsmonitor_batch *batch, const char *path)
175 {
176 char *composed;
177
178 /* add the NFC or NFD path as received from the OS */
179 fsmonitor_batch__add_path(batch, path);
180
181 /* if NFD, also add the corresponding NFC spelling */
182 composed = (char *)precompose_string_if_needed(path);
183 if (!composed || composed == path)
184 return;
185
186 fsmonitor_batch__add_path(batch, composed);
187 free(composed);
188 }
189
190
191 static void fsevent_callback(ConstFSEventStreamRef streamRef,
192 void *ctx,
193 size_t num_of_events,
194 void *event_paths,
195 const FSEventStreamEventFlags event_flags[],
196 const FSEventStreamEventId event_ids[])
197 {
198 struct fsmonitor_daemon_state *state = ctx;
199 struct fsm_listen_data *data = state->listen_data;
200 char **paths = (char **)event_paths;
201 struct fsmonitor_batch *batch = NULL;
202 struct string_list cookie_list = STRING_LIST_INIT_DUP;
203 const char *path_k;
204 const char *slash;
205 char *resolved = NULL;
206 struct strbuf tmp = STRBUF_INIT;
207 int k;
208
209 /*
210 * Build a list of all filesystem changes into a private/local
211 * list and without holding any locks.
212 */
213 for (k = 0; k < num_of_events; k++) {
214 /*
215 * On Mac, we receive an array of absolute paths.
216 */
217 free(resolved);
218 resolved = fsmonitor__resolve_alias(paths[k], &state->alias);
219 if (resolved)
220 path_k = resolved;
221 else
222 path_k = paths[k];
223
224 /*
225 * If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR.
226 * Please don't log them to Trace2.
227 *
228 * trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k);
229 */
230
231 /*
232 * If event[k] is marked as dropped, we assume that we have
233 * lost sync with the filesystem and should flush our cached
234 * data. We need to:
235 *
236 * [1] Abort/wake any client threads waiting for a cookie and
237 * flush the cached state data (the current token), and
238 * create a new token.
239 *
240 * [2] Discard the batch that we were locally building (since
241 * they are conceptually relative to the just flushed
242 * token).
243 */
244 if (ef_is_dropped(event_flags[k])) {
245 if (trace_pass_fl(&trace_fsmonitor))
246 log_flags_set(path_k, event_flags[k]);
247
248 fsmonitor_force_resync(state);
249 fsmonitor_batch__free_list(batch);
250 string_list_clear(&cookie_list, 0);
251 batch = NULL;
252
253 /*
254 * We assume that any events that we received
255 * in this callback after this dropped event
256 * may still be valid, so we continue rather
257 * than break. (And just in case there is a
258 * delete of ".git" hiding in there.)
259 */
260 continue;
261 }
262
263 if (ef_is_root_changed(event_flags[k])) {
264 /*
265 * The spelling of the pathname of the root directory
266 * has changed. This includes the name of the root
267 * directory itself or of any parent directory in the
268 * path.
269 *
270 * (There may be other conditions that throw this,
271 * but I couldn't find any information on it.)
272 *
273 * Force a shutdown now and avoid things getting
274 * out of sync. The Unix domain socket is inside
275 * the .git directory and a spelling change will make
276 * it hard for clients to rendezvous with us.
277 */
278 trace_printf_key(&trace_fsmonitor,
279 "event: root changed");
280 goto force_shutdown;
281 }
282
283 if (ef_ignore_xattr(event_flags[k])) {
284 trace_printf_key(&trace_fsmonitor,
285 "ignore-xattr: '%s', flags=0x%x",
286 path_k, event_flags[k]);
287 continue;
288 }
289
290 switch (fsmonitor_classify_path_absolute(state, path_k)) {
291
292 case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
293 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
294 /* special case cookie files within .git or gitdir */
295
296 /* Use just the filename of the cookie file. */
297 slash = find_last_dir_sep(path_k);
298 string_list_append(&cookie_list,
299 slash ? slash + 1 : path_k);
300 break;
301
302 case IS_INSIDE_DOT_GIT:
303 case IS_INSIDE_GITDIR:
304 /* ignore all other paths inside of .git or gitdir */
305 break;
306
307 case IS_DOT_GIT:
308 case IS_GITDIR:
309 /*
310 * If .git directory is deleted or renamed away,
311 * we have to quit.
312 */
313 if (ef_is_root_delete(event_flags[k])) {
314 trace_printf_key(&trace_fsmonitor,
315 "event: gitdir removed");
316 goto force_shutdown;
317 }
318 if (ef_is_root_renamed(event_flags[k])) {
319 trace_printf_key(&trace_fsmonitor,
320 "event: gitdir renamed");
321 goto force_shutdown;
322 }
323 break;
324
325 case IS_WORKDIR_PATH:
326 /* try to queue normal pathnames */
327
328 if (trace_pass_fl(&trace_fsmonitor))
329 log_flags_set(path_k, event_flags[k]);
330
331 /*
332 * Because of the implicit "binning" (the
333 * kernel calls us at a given frequency) and
334 * de-duping (the kernel is free to combine
335 * multiple events for a given pathname), an
336 * individual fsevent could be marked as both
337 * a file and directory. Add it to the queue
338 * with both spellings so that the client will
339 * know how much to invalidate/refresh.
340 */
341
342 if (event_flags[k] & (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemIsSymlink)) {
343 const char *rel = path_k +
344 state->path_worktree_watch.len + 1;
345
346 if (!batch)
347 batch = fsmonitor_batch__new();
348 my_add_path(batch, rel);
349 }
350
351 if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) {
352 const char *rel = path_k +
353 state->path_worktree_watch.len + 1;
354
355 strbuf_reset(&tmp);
356 strbuf_addstr(&tmp, rel);
357 strbuf_addch(&tmp, '/');
358
359 if (!batch)
360 batch = fsmonitor_batch__new();
361 my_add_path(batch, tmp.buf);
362 }
363
364 break;
365
366 case IS_OUTSIDE_CONE:
367 default:
368 trace_printf_key(&trace_fsmonitor,
369 "ignoring '%s'", path_k);
370 break;
371 }
372 }
373
374 free(resolved);
375 fsmonitor_publish(state, batch, &cookie_list);
376 string_list_clear(&cookie_list, 0);
377 strbuf_release(&tmp);
378 return;
379
380 force_shutdown:
381 free(resolved);
382 fsmonitor_batch__free_list(batch);
383 string_list_clear(&cookie_list, 0);
384
385 pthread_mutex_lock(&data->dq_lock);
386 data->shutdown_style = FORCE_SHUTDOWN;
387 pthread_cond_broadcast(&data->dq_finished);
388 pthread_mutex_unlock(&data->dq_lock);
389
390 strbuf_release(&tmp);
391 return;
392 }
393
394 /*
395 * In the call to `FSEventStreamCreate()` to setup our watch, the
396 * `latency` argument determines the frequency of calls to our callback
397 * with new FS events. Too slow and events get dropped; too fast and
398 * we burn CPU unnecessarily. Since it is rather obscure, I don't
399 * think this needs to be a config setting. I've done extensive
400 * testing on my systems and chosen the value below. It gives good
401 * results and I've not seen any dropped events.
402 *
403 * With a latency of 0.1, I was seeing lots of dropped events during
404 * the "touch 100000" files test within t/perf/p7519, but with a
405 * latency of 0.001 I did not see any dropped events. So I'm going
406 * to assume that this is the "correct" value.
407 *
408 * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
409 */
410
411 int fsm_listen__ctor(struct fsmonitor_daemon_state *state)
412 {
413 FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagNoDefer |
414 kFSEventStreamCreateFlagWatchRoot |
415 kFSEventStreamCreateFlagFileEvents;
416 FSEventStreamContext ctx = {
417 0,
418 state,
419 NULL,
420 NULL,
421 NULL
422 };
423 struct fsm_listen_data *data;
424 const void *dir_array[2];
425
426 CALLOC_ARRAY(data, 1);
427 state->listen_data = data;
428
429 data->cfsr_worktree_path = CFStringCreateWithCString(
430 NULL, state->path_worktree_watch.buf, kCFStringEncodingUTF8);
431 dir_array[data->nr_paths_watching++] = data->cfsr_worktree_path;
432
433 if (state->nr_paths_watching > 1) {
434 data->cfsr_gitdir_path = CFStringCreateWithCString(
435 NULL, state->path_gitdir_watch.buf,
436 kCFStringEncodingUTF8);
437 dir_array[data->nr_paths_watching++] = data->cfsr_gitdir_path;
438 }
439
440 data->cfar_paths_to_watch = CFArrayCreate(NULL, dir_array,
441 data->nr_paths_watching,
442 NULL);
443 data->stream = FSEventStreamCreate(NULL, fsevent_callback, &ctx,
444 data->cfar_paths_to_watch,
445 kFSEventStreamEventIdSinceNow,
446 0.001, flags);
447 if (!data->stream)
448 goto failed;
449
450 return 0;
451
452 failed:
453 error(_("Unable to create FSEventStream."));
454
455 FREE_AND_NULL(state->listen_data);
456 return -1;
457 }
458
459 void fsm_listen__dtor(struct fsmonitor_daemon_state *state)
460 {
461 struct fsm_listen_data *data;
462
463 if (!state || !state->listen_data)
464 return;
465
466 data = state->listen_data;
467
468 if (data->stream) {
469 if (data->stream_started)
470 FSEventStreamStop(data->stream);
471 if (data->stream_scheduled)
472 FSEventStreamInvalidate(data->stream);
473 FSEventStreamRelease(data->stream);
474 }
475
476 if (data->dq)
477 dispatch_release(data->dq);
478 pthread_cond_destroy(&data->dq_finished);
479 pthread_mutex_destroy(&data->dq_lock);
480
481 FREE_AND_NULL(state->listen_data);
482 }
483
484 void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
485 {
486 struct fsm_listen_data *data;
487
488 data = state->listen_data;
489
490 pthread_mutex_lock(&data->dq_lock);
491 data->shutdown_style = SHUTDOWN_EVENT;
492 pthread_cond_broadcast(&data->dq_finished);
493 pthread_mutex_unlock(&data->dq_lock);
494 }
495
496 void fsm_listen__loop(struct fsmonitor_daemon_state *state)
497 {
498 struct fsm_listen_data *data;
499
500 data = state->listen_data;
501
502 pthread_mutex_init(&data->dq_lock, NULL);
503 pthread_cond_init(&data->dq_finished, NULL);
504 data->dq = dispatch_queue_create("FSMonitor", NULL);
505
506 FSEventStreamSetDispatchQueue(data->stream, data->dq);
507 data->stream_scheduled = 1;
508
509 if (!FSEventStreamStart(data->stream)) {
510 error(_("Failed to start the FSEventStream"));
511 goto force_error_stop_without_loop;
512 }
513 data->stream_started = 1;
514
515 pthread_mutex_lock(&data->dq_lock);
516 pthread_cond_wait(&data->dq_finished, &data->dq_lock);
517 pthread_mutex_unlock(&data->dq_lock);
518
519 switch (data->shutdown_style) {
520 case FORCE_ERROR_STOP:
521 state->listen_error_code = -1;
522 /* fall thru */
523 case FORCE_SHUTDOWN:
524 ipc_server_stop_async(state->ipc_server_data);
525 /* fall thru */
526 case SHUTDOWN_EVENT:
527 default:
528 break;
529 }
530 return;
531
532 force_error_stop_without_loop:
533 state->listen_error_code = -1;
534 ipc_server_stop_async(state->ipc_server_data);
535 return;
536 }