Merge branch 'ab/progress-cleanup' into seen
[git] / compat / fsmonitor / fsmonitor-fs-listen-macos.c
1 #if defined(__GNUC__)
2 /*
3  * It is possible to #include CoreFoundation/CoreFoundation.h when compiling
4  * with clang, but not with GCC as of time of writing.
5  *
6  * See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93082 for details.
7  */
8 typedef unsigned int FSEventStreamCreateFlags;
9 #define kFSEventStreamEventFlagNone               0x00000000
10 #define kFSEventStreamEventFlagMustScanSubDirs    0x00000001
11 #define kFSEventStreamEventFlagUserDropped        0x00000002
12 #define kFSEventStreamEventFlagKernelDropped      0x00000004
13 #define kFSEventStreamEventFlagEventIdsWrapped    0x00000008
14 #define kFSEventStreamEventFlagHistoryDone        0x00000010
15 #define kFSEventStreamEventFlagRootChanged        0x00000020
16 #define kFSEventStreamEventFlagMount              0x00000040
17 #define kFSEventStreamEventFlagUnmount            0x00000080
18 #define kFSEventStreamEventFlagItemCreated        0x00000100
19 #define kFSEventStreamEventFlagItemRemoved        0x00000200
20 #define kFSEventStreamEventFlagItemInodeMetaMod   0x00000400
21 #define kFSEventStreamEventFlagItemRenamed        0x00000800
22 #define kFSEventStreamEventFlagItemModified       0x00001000
23 #define kFSEventStreamEventFlagItemFinderInfoMod  0x00002000
24 #define kFSEventStreamEventFlagItemChangeOwner    0x00004000
25 #define kFSEventStreamEventFlagItemXattrMod       0x00008000
26 #define kFSEventStreamEventFlagItemIsFile         0x00010000
27 #define kFSEventStreamEventFlagItemIsDir          0x00020000
28 #define kFSEventStreamEventFlagItemIsSymlink      0x00040000
29 #define kFSEventStreamEventFlagOwnEvent           0x00080000
30 #define kFSEventStreamEventFlagItemIsHardlink     0x00100000
31 #define kFSEventStreamEventFlagItemIsLastHardlink 0x00200000
32 #define kFSEventStreamEventFlagItemCloned         0x00400000
33
34 typedef struct __FSEventStream *FSEventStreamRef;
35 typedef const FSEventStreamRef ConstFSEventStreamRef;
36
37 typedef unsigned int CFStringEncoding;
38 #define kCFStringEncodingUTF8 0x08000100
39
40 typedef const struct __CFString *CFStringRef;
41 typedef const struct __CFArray *CFArrayRef;
42 typedef const struct __CFRunLoop *CFRunLoopRef;
43
44 struct FSEventStreamContext {
45     long long version;
46     void *cb_data, *retain, *release, *copy_description;
47 };
48
49 typedef struct FSEventStreamContext FSEventStreamContext;
50 typedef unsigned int FSEventStreamEventFlags;
51 #define kFSEventStreamCreateFlagNoDefer 0x02
52 #define kFSEventStreamCreateFlagWatchRoot 0x04
53 #define kFSEventStreamCreateFlagFileEvents 0x10
54
55 typedef unsigned long long FSEventStreamEventId;
56 #define kFSEventStreamEventIdSinceNow 0xFFFFFFFFFFFFFFFFULL
57
58 typedef void (*FSEventStreamCallback)(ConstFSEventStreamRef streamRef,
59                                       void *context,
60                                       __SIZE_TYPE__ num_of_events,
61                                       void *event_paths,
62                                       const FSEventStreamEventFlags event_flags[],
63                                       const FSEventStreamEventId event_ids[]);
64 typedef double CFTimeInterval;
65 FSEventStreamRef FSEventStreamCreate(void *allocator,
66                                      FSEventStreamCallback callback,
67                                      FSEventStreamContext *context,
68                                      CFArrayRef paths_to_watch,
69                                      FSEventStreamEventId since_when,
70                                      CFTimeInterval latency,
71                                      FSEventStreamCreateFlags flags);
72 CFStringRef CFStringCreateWithCString(void *allocator, const char *string,
73                                       CFStringEncoding encoding);
74 CFArrayRef CFArrayCreate(void *allocator, const void **items, long long count,
75                          void *callbacks);
76 void CFRunLoopRun(void);
77 void CFRunLoopStop(CFRunLoopRef run_loop);
78 CFRunLoopRef CFRunLoopGetCurrent(void);
79 extern CFStringRef kCFRunLoopDefaultMode;
80 void FSEventStreamScheduleWithRunLoop(FSEventStreamRef stream,
81                                       CFRunLoopRef run_loop,
82                                       CFStringRef run_loop_mode);
83 unsigned char FSEventStreamStart(FSEventStreamRef stream);
84 void FSEventStreamStop(FSEventStreamRef stream);
85 void FSEventStreamInvalidate(FSEventStreamRef stream);
86 void FSEventStreamRelease(FSEventStreamRef stream);
87 #else
88 /*
89  * Let Apple's headers declare `isalnum()` first, before
90  * Git's headers override it via a constant
91  */
92 #include <string.h>
93 #include <CoreFoundation/CoreFoundation.h>
94 #include <CoreServices/CoreServices.h>
95 #endif
96
97 #include "cache.h"
98 #include "fsmonitor.h"
99 #include "fsmonitor-fs-listen.h"
100 #include "fsmonitor--daemon.h"
101
102 struct fsmonitor_daemon_backend_data
103 {
104         CFStringRef cfsr_worktree_path;
105         CFStringRef cfsr_gitdir_path;
106
107         CFArrayRef cfar_paths_to_watch;
108         int nr_paths_watching;
109
110         FSEventStreamRef stream;
111
112         CFRunLoopRef rl;
113
114         enum shutdown_style {
115                 SHUTDOWN_EVENT = 0,
116                 FORCE_SHUTDOWN,
117                 FORCE_ERROR_STOP,
118         } shutdown_style;
119
120         unsigned int stream_scheduled:1;
121         unsigned int stream_started:1;
122 };
123
124 static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)
125 {
126         struct strbuf msg = STRBUF_INIT;
127
128         if (flag & kFSEventStreamEventFlagMustScanSubDirs)
129                 strbuf_addstr(&msg, "MustScanSubDirs|");
130         if (flag & kFSEventStreamEventFlagUserDropped)
131                 strbuf_addstr(&msg, "UserDropped|");
132         if (flag & kFSEventStreamEventFlagKernelDropped)
133                 strbuf_addstr(&msg, "KernelDropped|");
134         if (flag & kFSEventStreamEventFlagEventIdsWrapped)
135                 strbuf_addstr(&msg, "EventIdsWrapped|");
136         if (flag & kFSEventStreamEventFlagHistoryDone)
137                 strbuf_addstr(&msg, "HistoryDone|");
138         if (flag & kFSEventStreamEventFlagRootChanged)
139                 strbuf_addstr(&msg, "RootChanged|");
140         if (flag & kFSEventStreamEventFlagMount)
141                 strbuf_addstr(&msg, "Mount|");
142         if (flag & kFSEventStreamEventFlagUnmount)
143                 strbuf_addstr(&msg, "Unmount|");
144         if (flag & kFSEventStreamEventFlagItemChangeOwner)
145                 strbuf_addstr(&msg, "ItemChangeOwner|");
146         if (flag & kFSEventStreamEventFlagItemCreated)
147                 strbuf_addstr(&msg, "ItemCreated|");
148         if (flag & kFSEventStreamEventFlagItemFinderInfoMod)
149                 strbuf_addstr(&msg, "ItemFinderInfoMod|");
150         if (flag & kFSEventStreamEventFlagItemInodeMetaMod)
151                 strbuf_addstr(&msg, "ItemInodeMetaMod|");
152         if (flag & kFSEventStreamEventFlagItemIsDir)
153                 strbuf_addstr(&msg, "ItemIsDir|");
154         if (flag & kFSEventStreamEventFlagItemIsFile)
155                 strbuf_addstr(&msg, "ItemIsFile|");
156         if (flag & kFSEventStreamEventFlagItemIsHardlink)
157                 strbuf_addstr(&msg, "ItemIsHardlink|");
158         if (flag & kFSEventStreamEventFlagItemIsLastHardlink)
159                 strbuf_addstr(&msg, "ItemIsLastHardlink|");
160         if (flag & kFSEventStreamEventFlagItemIsSymlink)
161                 strbuf_addstr(&msg, "ItemIsSymlink|");
162         if (flag & kFSEventStreamEventFlagItemModified)
163                 strbuf_addstr(&msg, "ItemModified|");
164         if (flag & kFSEventStreamEventFlagItemRemoved)
165                 strbuf_addstr(&msg, "ItemRemoved|");
166         if (flag & kFSEventStreamEventFlagItemRenamed)
167                 strbuf_addstr(&msg, "ItemRenamed|");
168         if (flag & kFSEventStreamEventFlagItemXattrMod)
169                 strbuf_addstr(&msg, "ItemXattrMod|");
170         if (flag & kFSEventStreamEventFlagOwnEvent)
171                 strbuf_addstr(&msg, "OwnEvent|");
172         if (flag & kFSEventStreamEventFlagItemCloned)
173                 strbuf_addstr(&msg, "ItemCloned|");
174
175         trace_printf_key(&trace_fsmonitor, "fsevent: '%s', flags=%u %s",
176                          path, flag, msg.buf);
177
178         strbuf_release(&msg);
179 }
180
181 static int ef_is_root_delete(const FSEventStreamEventFlags ef)
182 {
183         return (ef & kFSEventStreamEventFlagItemIsDir &&
184                 ef & kFSEventStreamEventFlagItemRemoved);
185 }
186
187 static int ef_is_root_renamed(const FSEventStreamEventFlags ef)
188 {
189         return (ef & kFSEventStreamEventFlagItemIsDir &&
190                 ef & kFSEventStreamEventFlagItemRenamed);
191 }
192
193 static int ef_is_dropped(const FSEventStreamEventFlags ef)
194 {
195         return (ef & kFSEventStreamEventFlagKernelDropped ||
196                 ef & kFSEventStreamEventFlagUserDropped);
197 }
198
199 static void fsevent_callback(ConstFSEventStreamRef streamRef,
200                              void *ctx,
201                              size_t num_of_events,
202                              void *event_paths,
203                              const FSEventStreamEventFlags event_flags[],
204                              const FSEventStreamEventId event_ids[])
205 {
206         struct fsmonitor_daemon_state *state = ctx;
207         struct fsmonitor_daemon_backend_data *data = state->backend_data;
208         char **paths = (char **)event_paths;
209         struct fsmonitor_batch *batch = NULL;
210         struct string_list cookie_list = STRING_LIST_INIT_DUP;
211         const char *path_k;
212         const char *slash;
213         int k;
214         struct strbuf tmp = STRBUF_INIT;
215
216         /*
217          * Build a list of all filesystem changes into a private/local
218          * list and without holding any locks.
219          */
220         for (k = 0; k < num_of_events; k++) {
221                 /*
222                  * On Mac, we receive an array of absolute paths.
223                  */
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                         /*
248                          * see also kFSEventStreamEventFlagMustScanSubDirs
249                          */
250                         trace_printf_key(&trace_fsmonitor, "event: dropped");
251
252                         fsmonitor_force_resync(state);
253                         fsmonitor_batch__pop(batch);
254                         string_list_clear(&cookie_list, 0);
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                 switch (fsmonitor_classify_path_absolute(state, path_k)) {
267
268                 case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX:
269                 case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX:
270                         /* special case cookie files within .git or gitdir */
271
272                         /* Use just the filename of the cookie file. */
273                         slash = find_last_dir_sep(path_k);
274                         string_list_append(&cookie_list,
275                                            slash ? slash + 1 : path_k);
276                         break;
277
278                 case IS_INSIDE_DOT_GIT:
279                 case IS_INSIDE_GITDIR:
280                         /* ignore all other paths inside of .git or gitdir */
281                         break;
282
283                 case IS_DOT_GIT:
284                 case IS_GITDIR:
285                         /*
286                          * If .git directory is deleted or renamed away,
287                          * we have to quit.
288                          */
289                         if (ef_is_root_delete(event_flags[k])) {
290                                 trace_printf_key(&trace_fsmonitor,
291                                                  "event: gitdir removed");
292                                 goto force_shutdown;
293                         }
294                         if (ef_is_root_renamed(event_flags[k])) {
295                                 trace_printf_key(&trace_fsmonitor,
296                                                  "event: gitdir renamed");
297                                 goto force_shutdown;
298                         }
299                         break;
300
301                 case IS_WORKDIR_PATH:
302                         /* try to queue normal pathnames */
303
304                         if (trace_pass_fl(&trace_fsmonitor))
305                                 log_flags_set(path_k, event_flags[k]);
306
307                         /*
308                          * Because of the implicit "binning" (the
309                          * kernel calls us at a given frequency) and
310                          * de-duping (the kernel is free to combine
311                          * multiple events for a given pathname), an
312                          * individual fsevent could be marked as both
313                          * a file and directory.  Add it to the queue
314                          * with both spellings so that the client will
315                          * know how much to invalidate/refresh.
316                          */
317
318                         if (event_flags[k] & kFSEventStreamEventFlagItemIsFile) {
319                                 const char *rel = path_k +
320                                         state->path_worktree_watch.len + 1;
321
322                                 if (!batch)
323                                         batch = fsmonitor_batch__new();
324                                 fsmonitor_batch__add_path(batch, rel);
325                         }
326
327                         if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) {
328                                 const char *rel = path_k +
329                                         state->path_worktree_watch.len + 1;
330
331                                 strbuf_reset(&tmp);
332                                 strbuf_addstr(&tmp, rel);
333                                 strbuf_addch(&tmp, '/');
334
335                                 if (!batch)
336                                         batch = fsmonitor_batch__new();
337                                 fsmonitor_batch__add_path(batch, tmp.buf);
338                         }
339
340                         break;
341
342                 case IS_OUTSIDE_CONE:
343                 default:
344                         trace_printf_key(&trace_fsmonitor,
345                                          "ignoring '%s'", path_k);
346                         break;
347                 }
348         }
349
350         fsmonitor_publish(state, batch, &cookie_list);
351         string_list_clear(&cookie_list, 0);
352         strbuf_release(&tmp);
353         return;
354
355 force_shutdown:
356         fsmonitor_batch__pop(batch);
357         string_list_clear(&cookie_list, 0);
358
359         data->shutdown_style = FORCE_SHUTDOWN;
360         CFRunLoopStop(data->rl);
361         strbuf_release(&tmp);
362         return;
363 }
364
365 /*
366  * NEEDSWORK: Investigate the proper value for the `latency` argument
367  * in the call to `FSEventStreamCreate()`.  I'm not sure that this
368  * needs to be a config setting or just something that we tune after
369  * some testing.
370  *
371  * With a latency of 0.1, I was seeing lots of dropped events during
372  * the "touch 100000" files test within t/perf/p7519, but with a
373  * latency of 0.001 I did not see any dropped events.  So the
374  * "correct" value may be somewhere in between.
375  *
376  * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
377  */
378
379 int fsmonitor_fs_listen__ctor(struct fsmonitor_daemon_state *state)
380 {
381         FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagNoDefer |
382                 kFSEventStreamCreateFlagWatchRoot |
383                 kFSEventStreamCreateFlagFileEvents;
384         FSEventStreamContext ctx = {
385                 0,
386                 state,
387                 NULL,
388                 NULL,
389                 NULL
390         };
391         struct fsmonitor_daemon_backend_data *data;
392         const void *dir_array[2];
393
394         CALLOC_ARRAY(data, 1);
395         state->backend_data = data;
396
397         data->cfsr_worktree_path = CFStringCreateWithCString(
398                 NULL, state->path_worktree_watch.buf, kCFStringEncodingUTF8);
399         dir_array[data->nr_paths_watching++] = data->cfsr_worktree_path;
400
401         if (state->nr_paths_watching > 1) {
402                 data->cfsr_gitdir_path = CFStringCreateWithCString(
403                         NULL, state->path_gitdir_watch.buf,
404                         kCFStringEncodingUTF8);
405                 dir_array[data->nr_paths_watching++] = data->cfsr_gitdir_path;
406         }
407
408         data->cfar_paths_to_watch = CFArrayCreate(NULL, dir_array,
409                                                   data->nr_paths_watching,
410                                                   NULL);
411         data->stream = FSEventStreamCreate(NULL, fsevent_callback, &ctx,
412                                            data->cfar_paths_to_watch,
413                                            kFSEventStreamEventIdSinceNow,
414                                            0.001, flags);
415         if (data->stream == NULL)
416                 goto failed;
417
418         /*
419          * `data->rl` needs to be set inside the listener thread.
420          */
421
422         return 0;
423
424 failed:
425         error("Unable to create FSEventStream.");
426
427         FREE_AND_NULL(state->backend_data);
428         return -1;
429 }
430
431 void fsmonitor_fs_listen__dtor(struct fsmonitor_daemon_state *state)
432 {
433         struct fsmonitor_daemon_backend_data *data;
434
435         if (!state || !state->backend_data)
436                 return;
437
438         data = state->backend_data;
439
440         if (data->stream) {
441                 if (data->stream_started)
442                         FSEventStreamStop(data->stream);
443                 if (data->stream_scheduled)
444                         FSEventStreamInvalidate(data->stream);
445                 FSEventStreamRelease(data->stream);
446         }
447
448         FREE_AND_NULL(state->backend_data);
449 }
450
451 void fsmonitor_fs_listen__stop_async(struct fsmonitor_daemon_state *state)
452 {
453         struct fsmonitor_daemon_backend_data *data;
454
455         data = state->backend_data;
456         data->shutdown_style = SHUTDOWN_EVENT;
457
458         CFRunLoopStop(data->rl);
459 }
460
461 void fsmonitor_fs_listen__loop(struct fsmonitor_daemon_state *state)
462 {
463         struct fsmonitor_daemon_backend_data *data;
464
465         data = state->backend_data;
466
467         data->rl = CFRunLoopGetCurrent();
468
469         FSEventStreamScheduleWithRunLoop(data->stream, data->rl, kCFRunLoopDefaultMode);
470         data->stream_scheduled = 1;
471
472         if (!FSEventStreamStart(data->stream)) {
473                 error("Failed to start the FSEventStream");
474                 goto force_error_stop_without_loop;
475         }
476         data->stream_started = 1;
477
478         CFRunLoopRun();
479
480         switch (data->shutdown_style) {
481         case FORCE_ERROR_STOP:
482                 state->error_code = -1;
483                 /* fall thru */
484         case FORCE_SHUTDOWN:
485                 ipc_server_stop_async(state->ipc_server_data);
486                 /* fall thru */
487         case SHUTDOWN_EVENT:
488         default:
489                 break;
490         }
491         return;
492
493 force_error_stop_without_loop:
494         state->error_code = -1;
495         ipc_server_stop_async(state->ipc_server_data);
496         return;
497 }