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