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