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