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