/src/git/builtin/fsmonitor--daemon.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "builtin.h" |
2 | | #include "abspath.h" |
3 | | #include "config.h" |
4 | | #include "dir.h" |
5 | | #include "environment.h" |
6 | | #include "gettext.h" |
7 | | #include "parse-options.h" |
8 | | #include "fsmonitor-ll.h" |
9 | | #include "fsmonitor-ipc.h" |
10 | | #include "fsmonitor-settings.h" |
11 | | #include "compat/fsmonitor/fsm-health.h" |
12 | | #include "compat/fsmonitor/fsm-listen.h" |
13 | | #include "fsmonitor--daemon.h" |
14 | | #include "repository.h" |
15 | | #include "simple-ipc.h" |
16 | | #include "khash.h" |
17 | | #include "run-command.h" |
18 | | #include "trace.h" |
19 | | #include "trace2.h" |
20 | | |
21 | | static const char * const builtin_fsmonitor__daemon_usage[] = { |
22 | | N_("git fsmonitor--daemon start [<options>]"), |
23 | | N_("git fsmonitor--daemon run [<options>]"), |
24 | | "git fsmonitor--daemon stop", |
25 | | "git fsmonitor--daemon status", |
26 | | NULL |
27 | | }; |
28 | | |
29 | | #ifdef HAVE_FSMONITOR_DAEMON_BACKEND |
30 | | /* |
31 | | * Global state loaded from config. |
32 | | */ |
33 | | #define FSMONITOR__IPC_THREADS "fsmonitor.ipcthreads" |
34 | | static int fsmonitor__ipc_threads = 8; |
35 | | |
36 | | #define FSMONITOR__START_TIMEOUT "fsmonitor.starttimeout" |
37 | | static int fsmonitor__start_timeout_sec = 60; |
38 | | |
39 | | #define FSMONITOR__ANNOUNCE_STARTUP "fsmonitor.announcestartup" |
40 | | static int fsmonitor__announce_startup = 0; |
41 | | |
42 | | static int fsmonitor_config(const char *var, const char *value, |
43 | | const struct config_context *ctx, void *cb) |
44 | | { |
45 | | if (!strcmp(var, FSMONITOR__IPC_THREADS)) { |
46 | | int i = git_config_int(var, value, ctx->kvi); |
47 | | if (i < 1) |
48 | | return error(_("value of '%s' out of range: %d"), |
49 | | FSMONITOR__IPC_THREADS, i); |
50 | | fsmonitor__ipc_threads = i; |
51 | | return 0; |
52 | | } |
53 | | |
54 | | if (!strcmp(var, FSMONITOR__START_TIMEOUT)) { |
55 | | int i = git_config_int(var, value, ctx->kvi); |
56 | | if (i < 0) |
57 | | return error(_("value of '%s' out of range: %d"), |
58 | | FSMONITOR__START_TIMEOUT, i); |
59 | | fsmonitor__start_timeout_sec = i; |
60 | | return 0; |
61 | | } |
62 | | |
63 | | if (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) { |
64 | | int is_bool; |
65 | | int i = git_config_bool_or_int(var, value, ctx->kvi, &is_bool); |
66 | | if (i < 0) |
67 | | return error(_("value of '%s' not bool or int: %d"), |
68 | | var, i); |
69 | | fsmonitor__announce_startup = i; |
70 | | return 0; |
71 | | } |
72 | | |
73 | | return git_default_config(var, value, ctx, cb); |
74 | | } |
75 | | |
76 | | /* |
77 | | * Acting as a CLIENT. |
78 | | * |
79 | | * Send a "quit" command to the `git-fsmonitor--daemon` (if running) |
80 | | * and wait for it to shutdown. |
81 | | */ |
82 | | static int do_as_client__send_stop(void) |
83 | | { |
84 | | struct strbuf answer = STRBUF_INIT; |
85 | | int ret; |
86 | | |
87 | | ret = fsmonitor_ipc__send_command("quit", &answer); |
88 | | |
89 | | /* The quit command does not return any response data. */ |
90 | | strbuf_release(&answer); |
91 | | |
92 | | if (ret) |
93 | | return ret; |
94 | | |
95 | | trace2_region_enter("fsm_client", "polling-for-daemon-exit", NULL); |
96 | | while (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) |
97 | | sleep_millisec(50); |
98 | | trace2_region_leave("fsm_client", "polling-for-daemon-exit", NULL); |
99 | | |
100 | | return 0; |
101 | | } |
102 | | |
103 | | static int do_as_client__status(void) |
104 | | { |
105 | | enum ipc_active_state state = fsmonitor_ipc__get_state(); |
106 | | |
107 | | switch (state) { |
108 | | case IPC_STATE__LISTENING: |
109 | | printf(_("fsmonitor-daemon is watching '%s'\n"), |
110 | | the_repository->worktree); |
111 | | return 0; |
112 | | |
113 | | default: |
114 | | printf(_("fsmonitor-daemon is not watching '%s'\n"), |
115 | | the_repository->worktree); |
116 | | return 1; |
117 | | } |
118 | | } |
119 | | |
120 | | enum fsmonitor_cookie_item_result { |
121 | | FCIR_ERROR = -1, /* could not create cookie file ? */ |
122 | | FCIR_INIT, |
123 | | FCIR_SEEN, |
124 | | FCIR_ABORT, |
125 | | }; |
126 | | |
127 | | struct fsmonitor_cookie_item { |
128 | | struct hashmap_entry entry; |
129 | | char *name; |
130 | | enum fsmonitor_cookie_item_result result; |
131 | | }; |
132 | | |
133 | | static int cookies_cmp(const void *data UNUSED, |
134 | | const struct hashmap_entry *he1, |
135 | | const struct hashmap_entry *he2, const void *keydata) |
136 | | { |
137 | | const struct fsmonitor_cookie_item *a = |
138 | | container_of(he1, const struct fsmonitor_cookie_item, entry); |
139 | | const struct fsmonitor_cookie_item *b = |
140 | | container_of(he2, const struct fsmonitor_cookie_item, entry); |
141 | | |
142 | | return strcmp(a->name, keydata ? keydata : b->name); |
143 | | } |
144 | | |
145 | | static enum fsmonitor_cookie_item_result with_lock__wait_for_cookie( |
146 | | struct fsmonitor_daemon_state *state) |
147 | | { |
148 | | /* assert current thread holding state->main_lock */ |
149 | | |
150 | | int fd; |
151 | | struct fsmonitor_cookie_item *cookie; |
152 | | struct strbuf cookie_pathname = STRBUF_INIT; |
153 | | struct strbuf cookie_filename = STRBUF_INIT; |
154 | | enum fsmonitor_cookie_item_result result; |
155 | | int my_cookie_seq; |
156 | | |
157 | | CALLOC_ARRAY(cookie, 1); |
158 | | |
159 | | my_cookie_seq = state->cookie_seq++; |
160 | | |
161 | | strbuf_addf(&cookie_filename, "%i-%i", getpid(), my_cookie_seq); |
162 | | |
163 | | strbuf_addbuf(&cookie_pathname, &state->path_cookie_prefix); |
164 | | strbuf_addbuf(&cookie_pathname, &cookie_filename); |
165 | | |
166 | | cookie->name = strbuf_detach(&cookie_filename, NULL); |
167 | | cookie->result = FCIR_INIT; |
168 | | hashmap_entry_init(&cookie->entry, strhash(cookie->name)); |
169 | | |
170 | | hashmap_add(&state->cookies, &cookie->entry); |
171 | | |
172 | | trace_printf_key(&trace_fsmonitor, "cookie-wait: '%s' '%s'", |
173 | | cookie->name, cookie_pathname.buf); |
174 | | |
175 | | /* |
176 | | * Create the cookie file on disk and then wait for a notification |
177 | | * that the listener thread has seen it. |
178 | | */ |
179 | | fd = open(cookie_pathname.buf, O_WRONLY | O_CREAT | O_EXCL, 0600); |
180 | | if (fd < 0) { |
181 | | error_errno(_("could not create fsmonitor cookie '%s'"), |
182 | | cookie->name); |
183 | | |
184 | | cookie->result = FCIR_ERROR; |
185 | | goto done; |
186 | | } |
187 | | |
188 | | /* |
189 | | * Technically, close() and unlink() can fail, but we don't |
190 | | * care here. We only created the file to trigger a watch |
191 | | * event from the FS to know that when we're up to date. |
192 | | */ |
193 | | close(fd); |
194 | | unlink(cookie_pathname.buf); |
195 | | |
196 | | /* |
197 | | * Technically, this is an infinite wait (well, unless another |
198 | | * thread sends us an abort). I'd like to change this to |
199 | | * use `pthread_cond_timedwait()` and return an error/timeout |
200 | | * and let the caller do the trivial response thing, but we |
201 | | * don't have that routine in our thread-utils. |
202 | | * |
203 | | * After extensive beta testing I'm not really worried about |
204 | | * this. Also note that the above open() and unlink() calls |
205 | | * will cause at least two FS events on that path, so the odds |
206 | | * of getting stuck are pretty slim. |
207 | | */ |
208 | | while (cookie->result == FCIR_INIT) |
209 | | pthread_cond_wait(&state->cookies_cond, |
210 | | &state->main_lock); |
211 | | |
212 | | done: |
213 | | hashmap_remove(&state->cookies, &cookie->entry, NULL); |
214 | | |
215 | | result = cookie->result; |
216 | | |
217 | | free(cookie->name); |
218 | | free(cookie); |
219 | | strbuf_release(&cookie_pathname); |
220 | | |
221 | | return result; |
222 | | } |
223 | | |
224 | | /* |
225 | | * Mark these cookies as _SEEN and wake up the corresponding client threads. |
226 | | */ |
227 | | static void with_lock__mark_cookies_seen(struct fsmonitor_daemon_state *state, |
228 | | const struct string_list *cookie_names) |
229 | | { |
230 | | /* assert current thread holding state->main_lock */ |
231 | | |
232 | | int k; |
233 | | int nr_seen = 0; |
234 | | |
235 | | for (k = 0; k < cookie_names->nr; k++) { |
236 | | struct fsmonitor_cookie_item key; |
237 | | struct fsmonitor_cookie_item *cookie; |
238 | | |
239 | | key.name = cookie_names->items[k].string; |
240 | | hashmap_entry_init(&key.entry, strhash(key.name)); |
241 | | |
242 | | cookie = hashmap_get_entry(&state->cookies, &key, entry, NULL); |
243 | | if (cookie) { |
244 | | trace_printf_key(&trace_fsmonitor, "cookie-seen: '%s'", |
245 | | cookie->name); |
246 | | cookie->result = FCIR_SEEN; |
247 | | nr_seen++; |
248 | | } |
249 | | } |
250 | | |
251 | | if (nr_seen) |
252 | | pthread_cond_broadcast(&state->cookies_cond); |
253 | | } |
254 | | |
255 | | /* |
256 | | * Set _ABORT on all pending cookies and wake up all client threads. |
257 | | */ |
258 | | static void with_lock__abort_all_cookies(struct fsmonitor_daemon_state *state) |
259 | | { |
260 | | /* assert current thread holding state->main_lock */ |
261 | | |
262 | | struct hashmap_iter iter; |
263 | | struct fsmonitor_cookie_item *cookie; |
264 | | int nr_aborted = 0; |
265 | | |
266 | | hashmap_for_each_entry(&state->cookies, &iter, cookie, entry) { |
267 | | trace_printf_key(&trace_fsmonitor, "cookie-abort: '%s'", |
268 | | cookie->name); |
269 | | cookie->result = FCIR_ABORT; |
270 | | nr_aborted++; |
271 | | } |
272 | | |
273 | | if (nr_aborted) |
274 | | pthread_cond_broadcast(&state->cookies_cond); |
275 | | } |
276 | | |
277 | | /* |
278 | | * Requests to and from a FSMonitor Protocol V2 provider use an opaque |
279 | | * "token" as a virtual timestamp. Clients can request a summary of all |
280 | | * created/deleted/modified files relative to a token. In the response, |
281 | | * clients receive a new token for the next (relative) request. |
282 | | * |
283 | | * |
284 | | * Token Format |
285 | | * ============ |
286 | | * |
287 | | * The contents of the token are private and provider-specific. |
288 | | * |
289 | | * For the built-in fsmonitor--daemon, we define a token as follows: |
290 | | * |
291 | | * "builtin" ":" <token_id> ":" <sequence_nr> |
292 | | * |
293 | | * The "builtin" prefix is used as a namespace to avoid conflicts |
294 | | * with other providers (such as Watchman). |
295 | | * |
296 | | * The <token_id> is an arbitrary OPAQUE string, such as a GUID, |
297 | | * UUID, or {timestamp,pid}. It is used to group all filesystem |
298 | | * events that happened while the daemon was monitoring (and in-sync |
299 | | * with the filesystem). |
300 | | * |
301 | | * Unlike FSMonitor Protocol V1, it is not defined as a timestamp |
302 | | * and does not define less-than/greater-than relationships. |
303 | | * (There are too many race conditions to rely on file system |
304 | | * event timestamps.) |
305 | | * |
306 | | * The <sequence_nr> is a simple integer incremented whenever the |
307 | | * daemon needs to make its state public. For example, if 1000 file |
308 | | * system events come in, but no clients have requested the data, |
309 | | * the daemon can continue to accumulate file changes in the same |
310 | | * bin and does not need to advance the sequence number. However, |
311 | | * as soon as a client does arrive, the daemon needs to start a new |
312 | | * bin and increment the sequence number. |
313 | | * |
314 | | * The sequence number serves as the boundary between 2 sets |
315 | | * of bins -- the older ones that the client has already seen |
316 | | * and the newer ones that it hasn't. |
317 | | * |
318 | | * When a new <token_id> is created, the <sequence_nr> is reset to |
319 | | * zero. |
320 | | * |
321 | | * |
322 | | * About Token Ids |
323 | | * =============== |
324 | | * |
325 | | * A new token_id is created: |
326 | | * |
327 | | * [1] each time the daemon is started. |
328 | | * |
329 | | * [2] any time that the daemon must re-sync with the filesystem |
330 | | * (such as when the kernel drops or we miss events on a very |
331 | | * active volume). |
332 | | * |
333 | | * [3] in response to a client "flush" command (for dropped event |
334 | | * testing). |
335 | | * |
336 | | * When a new token_id is created, the daemon is free to discard all |
337 | | * cached filesystem events associated with any previous token_ids. |
338 | | * Events associated with a non-current token_id will never be sent |
339 | | * to a client. A token_id change implicitly means that the daemon |
340 | | * has gap in its event history. |
341 | | * |
342 | | * Therefore, clients that present a token with a stale (non-current) |
343 | | * token_id will always be given a trivial response. |
344 | | */ |
345 | | struct fsmonitor_token_data { |
346 | | struct strbuf token_id; |
347 | | struct fsmonitor_batch *batch_head; |
348 | | struct fsmonitor_batch *batch_tail; |
349 | | uint64_t client_ref_count; |
350 | | }; |
351 | | |
352 | | struct fsmonitor_batch { |
353 | | struct fsmonitor_batch *next; |
354 | | uint64_t batch_seq_nr; |
355 | | const char **interned_paths; |
356 | | size_t nr, alloc; |
357 | | time_t pinned_time; |
358 | | }; |
359 | | |
360 | | static struct fsmonitor_token_data *fsmonitor_new_token_data(void) |
361 | | { |
362 | | static int test_env_value = -1; |
363 | | static uint64_t flush_count = 0; |
364 | | struct fsmonitor_token_data *token; |
365 | | struct fsmonitor_batch *batch; |
366 | | |
367 | | CALLOC_ARRAY(token, 1); |
368 | | batch = fsmonitor_batch__new(); |
369 | | |
370 | | strbuf_init(&token->token_id, 0); |
371 | | token->batch_head = batch; |
372 | | token->batch_tail = batch; |
373 | | token->client_ref_count = 0; |
374 | | |
375 | | if (test_env_value < 0) |
376 | | test_env_value = git_env_bool("GIT_TEST_FSMONITOR_TOKEN", 0); |
377 | | |
378 | | if (!test_env_value) { |
379 | | struct timeval tv; |
380 | | struct tm tm; |
381 | | time_t secs; |
382 | | |
383 | | gettimeofday(&tv, NULL); |
384 | | secs = tv.tv_sec; |
385 | | gmtime_r(&secs, &tm); |
386 | | |
387 | | strbuf_addf(&token->token_id, |
388 | | "%"PRIu64".%d.%4d%02d%02dT%02d%02d%02d.%06ldZ", |
389 | | flush_count++, |
390 | | getpid(), |
391 | | tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, |
392 | | tm.tm_hour, tm.tm_min, tm.tm_sec, |
393 | | (long)tv.tv_usec); |
394 | | } else { |
395 | | strbuf_addf(&token->token_id, "test_%08x", test_env_value++); |
396 | | } |
397 | | |
398 | | /* |
399 | | * We created a new <token_id> and are starting a new series |
400 | | * of tokens with a zero <seq_nr>. |
401 | | * |
402 | | * Since clients cannot guess our new (non test) <token_id> |
403 | | * they will always receive a trivial response (because of the |
404 | | * mismatch on the <token_id>). The trivial response will |
405 | | * tell them our new <token_id> so that subsequent requests |
406 | | * will be relative to our new series. (And when sending that |
407 | | * response, we pin the current head of the batch list.) |
408 | | * |
409 | | * Even if the client correctly guesses the <token_id>, their |
410 | | * request of "builtin:<token_id>:0" asks for all changes MORE |
411 | | * RECENT than batch/bin 0. |
412 | | * |
413 | | * This implies that it is a waste to accumulate paths in the |
414 | | * initial batch/bin (because they will never be transmitted). |
415 | | * |
416 | | * So the daemon could be running for days and watching the |
417 | | * file system, but doesn't need to actually accumulate any |
418 | | * paths UNTIL we need to set a reference point for a later |
419 | | * relative request. |
420 | | * |
421 | | * However, it is very useful for testing to always have a |
422 | | * reference point set. Pin batch 0 to force early file system |
423 | | * events to accumulate. |
424 | | */ |
425 | | if (test_env_value) |
426 | | batch->pinned_time = time(NULL); |
427 | | |
428 | | return token; |
429 | | } |
430 | | |
431 | | struct fsmonitor_batch *fsmonitor_batch__new(void) |
432 | | { |
433 | | struct fsmonitor_batch *batch; |
434 | | |
435 | | CALLOC_ARRAY(batch, 1); |
436 | | |
437 | | return batch; |
438 | | } |
439 | | |
440 | | void fsmonitor_batch__free_list(struct fsmonitor_batch *batch) |
441 | | { |
442 | | while (batch) { |
443 | | struct fsmonitor_batch *next = batch->next; |
444 | | |
445 | | /* |
446 | | * The actual strings within the array of this batch |
447 | | * are interned, so we don't own them. We only own |
448 | | * the array. |
449 | | */ |
450 | | free(batch->interned_paths); |
451 | | free(batch); |
452 | | |
453 | | batch = next; |
454 | | } |
455 | | } |
456 | | |
457 | | void fsmonitor_batch__add_path(struct fsmonitor_batch *batch, |
458 | | const char *path) |
459 | | { |
460 | | const char *interned_path = strintern(path); |
461 | | |
462 | | trace_printf_key(&trace_fsmonitor, "event: %s", interned_path); |
463 | | |
464 | | ALLOC_GROW(batch->interned_paths, batch->nr + 1, batch->alloc); |
465 | | batch->interned_paths[batch->nr++] = interned_path; |
466 | | } |
467 | | |
468 | | static void fsmonitor_batch__combine(struct fsmonitor_batch *batch_dest, |
469 | | const struct fsmonitor_batch *batch_src) |
470 | | { |
471 | | size_t k; |
472 | | |
473 | | ALLOC_GROW(batch_dest->interned_paths, |
474 | | batch_dest->nr + batch_src->nr + 1, |
475 | | batch_dest->alloc); |
476 | | |
477 | | for (k = 0; k < batch_src->nr; k++) |
478 | | batch_dest->interned_paths[batch_dest->nr++] = |
479 | | batch_src->interned_paths[k]; |
480 | | } |
481 | | |
482 | | /* |
483 | | * To keep the batch list from growing unbounded in response to filesystem |
484 | | * activity, we try to truncate old batches from the end of the list as |
485 | | * they become irrelevant. |
486 | | * |
487 | | * We assume that the .git/index will be updated with the most recent token |
488 | | * any time the index is updated. And future commands will only ask for |
489 | | * recent changes *since* that new token. So as tokens advance into the |
490 | | * future, older batch items will never be requested/needed. So we can |
491 | | * truncate them without loss of functionality. |
492 | | * |
493 | | * However, multiple commands may be talking to the daemon concurrently |
494 | | * or perform a slow command, so a little "token skew" is possible. |
495 | | * Therefore, we want this to be a little bit lazy and have a generous |
496 | | * delay. |
497 | | * |
498 | | * The current reader thread walked backwards in time from `token->batch_head` |
499 | | * back to `batch_marker` somewhere in the middle of the batch list. |
500 | | * |
501 | | * Let's walk backwards in time from that marker an arbitrary delay |
502 | | * and truncate the list there. Note that these timestamps are completely |
503 | | * artificial (based on when we pinned the batch item) and not on any |
504 | | * filesystem activity. |
505 | | * |
506 | | * Return the obsolete portion of the list after we have removed it from |
507 | | * the official list so that the caller can free it after leaving the lock. |
508 | | */ |
509 | | #define MY_TIME_DELAY_SECONDS (5 * 60) /* seconds */ |
510 | | |
511 | | static struct fsmonitor_batch *with_lock__truncate_old_batches( |
512 | | struct fsmonitor_daemon_state *state, |
513 | | const struct fsmonitor_batch *batch_marker) |
514 | | { |
515 | | /* assert current thread holding state->main_lock */ |
516 | | |
517 | | const struct fsmonitor_batch *batch; |
518 | | struct fsmonitor_batch *remainder; |
519 | | |
520 | | if (!batch_marker) |
521 | | return NULL; |
522 | | |
523 | | trace_printf_key(&trace_fsmonitor, "Truncate: mark (%"PRIu64",%"PRIu64")", |
524 | | batch_marker->batch_seq_nr, |
525 | | (uint64_t)batch_marker->pinned_time); |
526 | | |
527 | | for (batch = batch_marker; batch; batch = batch->next) { |
528 | | time_t t; |
529 | | |
530 | | if (!batch->pinned_time) /* an overflow batch */ |
531 | | continue; |
532 | | |
533 | | t = batch->pinned_time + MY_TIME_DELAY_SECONDS; |
534 | | if (t > batch_marker->pinned_time) /* too close to marker */ |
535 | | continue; |
536 | | |
537 | | goto truncate_past_here; |
538 | | } |
539 | | |
540 | | return NULL; |
541 | | |
542 | | truncate_past_here: |
543 | | state->current_token_data->batch_tail = (struct fsmonitor_batch *)batch; |
544 | | |
545 | | remainder = ((struct fsmonitor_batch *)batch)->next; |
546 | | ((struct fsmonitor_batch *)batch)->next = NULL; |
547 | | |
548 | | return remainder; |
549 | | } |
550 | | |
551 | | static void fsmonitor_free_token_data(struct fsmonitor_token_data *token) |
552 | | { |
553 | | if (!token) |
554 | | return; |
555 | | |
556 | | assert(token->client_ref_count == 0); |
557 | | |
558 | | strbuf_release(&token->token_id); |
559 | | |
560 | | fsmonitor_batch__free_list(token->batch_head); |
561 | | |
562 | | free(token); |
563 | | } |
564 | | |
565 | | /* |
566 | | * Flush all of our cached data about the filesystem. Call this if we |
567 | | * lose sync with the filesystem and miss some notification events. |
568 | | * |
569 | | * [1] If we are missing events, then we no longer have a complete |
570 | | * history of the directory (relative to our current start token). |
571 | | * We should create a new token and start fresh (as if we just |
572 | | * booted up). |
573 | | * |
574 | | * [2] Some of those lost events may have been for cookie files. We |
575 | | * should assume the worst and abort them rather letting them starve. |
576 | | * |
577 | | * If there are no concurrent threads reading the current token data |
578 | | * series, we can free it now. Otherwise, let the last reader free |
579 | | * it. |
580 | | * |
581 | | * Either way, the old token data series is no longer associated with |
582 | | * our state data. |
583 | | */ |
584 | | static void with_lock__do_force_resync(struct fsmonitor_daemon_state *state) |
585 | | { |
586 | | /* assert current thread holding state->main_lock */ |
587 | | |
588 | | struct fsmonitor_token_data *free_me = NULL; |
589 | | struct fsmonitor_token_data *new_one = NULL; |
590 | | |
591 | | new_one = fsmonitor_new_token_data(); |
592 | | |
593 | | if (state->current_token_data->client_ref_count == 0) |
594 | | free_me = state->current_token_data; |
595 | | state->current_token_data = new_one; |
596 | | |
597 | | fsmonitor_free_token_data(free_me); |
598 | | |
599 | | with_lock__abort_all_cookies(state); |
600 | | } |
601 | | |
602 | | void fsmonitor_force_resync(struct fsmonitor_daemon_state *state) |
603 | | { |
604 | | pthread_mutex_lock(&state->main_lock); |
605 | | with_lock__do_force_resync(state); |
606 | | pthread_mutex_unlock(&state->main_lock); |
607 | | } |
608 | | |
609 | | /* |
610 | | * Format an opaque token string to send to the client. |
611 | | */ |
612 | | static void with_lock__format_response_token( |
613 | | struct strbuf *response_token, |
614 | | const struct strbuf *response_token_id, |
615 | | const struct fsmonitor_batch *batch) |
616 | | { |
617 | | /* assert current thread holding state->main_lock */ |
618 | | |
619 | | strbuf_reset(response_token); |
620 | | strbuf_addf(response_token, "builtin:%s:%"PRIu64, |
621 | | response_token_id->buf, batch->batch_seq_nr); |
622 | | } |
623 | | |
624 | | /* |
625 | | * Parse an opaque token from the client. |
626 | | * Returns -1 on error. |
627 | | */ |
628 | | static int fsmonitor_parse_client_token(const char *buf_token, |
629 | | struct strbuf *requested_token_id, |
630 | | uint64_t *seq_nr) |
631 | | { |
632 | | const char *p; |
633 | | char *p_end; |
634 | | |
635 | | strbuf_reset(requested_token_id); |
636 | | *seq_nr = 0; |
637 | | |
638 | | if (!skip_prefix(buf_token, "builtin:", &p)) |
639 | | return -1; |
640 | | |
641 | | while (*p && *p != ':') |
642 | | strbuf_addch(requested_token_id, *p++); |
643 | | if (!*p++) |
644 | | return -1; |
645 | | |
646 | | *seq_nr = (uint64_t)strtoumax(p, &p_end, 10); |
647 | | if (*p_end) |
648 | | return -1; |
649 | | |
650 | | return 0; |
651 | | } |
652 | | |
653 | | KHASH_INIT(str, const char *, int, 0, kh_str_hash_func, kh_str_hash_equal) |
654 | | |
655 | | static int do_handle_client(struct fsmonitor_daemon_state *state, |
656 | | const char *command, |
657 | | ipc_server_reply_cb *reply, |
658 | | struct ipc_server_reply_data *reply_data) |
659 | | { |
660 | | struct fsmonitor_token_data *token_data = NULL; |
661 | | struct strbuf response_token = STRBUF_INIT; |
662 | | struct strbuf requested_token_id = STRBUF_INIT; |
663 | | struct strbuf payload = STRBUF_INIT; |
664 | | uint64_t requested_oldest_seq_nr = 0; |
665 | | uint64_t total_response_len = 0; |
666 | | const char *p; |
667 | | const struct fsmonitor_batch *batch_head; |
668 | | const struct fsmonitor_batch *batch; |
669 | | struct fsmonitor_batch *remainder = NULL; |
670 | | intmax_t count = 0, duplicates = 0; |
671 | | kh_str_t *shown; |
672 | | int hash_ret; |
673 | | int do_trivial = 0; |
674 | | int do_flush = 0; |
675 | | int do_cookie = 0; |
676 | | enum fsmonitor_cookie_item_result cookie_result; |
677 | | |
678 | | /* |
679 | | * We expect `command` to be of the form: |
680 | | * |
681 | | * <command> := quit NUL |
682 | | * | flush NUL |
683 | | * | <V1-time-since-epoch-ns> NUL |
684 | | * | <V2-opaque-fsmonitor-token> NUL |
685 | | */ |
686 | | |
687 | | if (!strcmp(command, "quit")) { |
688 | | /* |
689 | | * A client has requested over the socket/pipe that the |
690 | | * daemon shutdown. |
691 | | * |
692 | | * Tell the IPC thread pool to shutdown (which completes |
693 | | * the await in the main thread (which can stop the |
694 | | * fsmonitor listener thread)). |
695 | | * |
696 | | * There is no reply to the client. |
697 | | */ |
698 | | return SIMPLE_IPC_QUIT; |
699 | | |
700 | | } else if (!strcmp(command, "flush")) { |
701 | | /* |
702 | | * Flush all of our cached data and generate a new token |
703 | | * just like if we lost sync with the filesystem. |
704 | | * |
705 | | * Then send a trivial response using the new token. |
706 | | */ |
707 | | do_flush = 1; |
708 | | do_trivial = 1; |
709 | | |
710 | | } else if (!skip_prefix(command, "builtin:", &p)) { |
711 | | /* assume V1 timestamp or garbage */ |
712 | | |
713 | | char *p_end; |
714 | | |
715 | | strtoumax(command, &p_end, 10); |
716 | | trace_printf_key(&trace_fsmonitor, |
717 | | ((*p_end) ? |
718 | | "fsmonitor: invalid command line '%s'" : |
719 | | "fsmonitor: unsupported V1 protocol '%s'"), |
720 | | command); |
721 | | do_trivial = 1; |
722 | | do_cookie = 1; |
723 | | |
724 | | } else { |
725 | | /* We have "builtin:*" */ |
726 | | if (fsmonitor_parse_client_token(command, &requested_token_id, |
727 | | &requested_oldest_seq_nr)) { |
728 | | trace_printf_key(&trace_fsmonitor, |
729 | | "fsmonitor: invalid V2 protocol token '%s'", |
730 | | command); |
731 | | do_trivial = 1; |
732 | | do_cookie = 1; |
733 | | |
734 | | } else { |
735 | | /* |
736 | | * We have a V2 valid token: |
737 | | * "builtin:<token_id>:<seq_nr>" |
738 | | */ |
739 | | do_cookie = 1; |
740 | | } |
741 | | } |
742 | | |
743 | | pthread_mutex_lock(&state->main_lock); |
744 | | |
745 | | if (!state->current_token_data) |
746 | | BUG("fsmonitor state does not have a current token"); |
747 | | |
748 | | /* |
749 | | * Write a cookie file inside the directory being watched in |
750 | | * an effort to flush out existing filesystem events that we |
751 | | * actually care about. Suspend this client thread until we |
752 | | * see the filesystem events for this cookie file. |
753 | | * |
754 | | * Creating the cookie lets us guarantee that our FS listener |
755 | | * thread has drained the kernel queue and we are caught up |
756 | | * with the kernel. |
757 | | * |
758 | | * If we cannot create the cookie (or otherwise guarantee that |
759 | | * we are caught up), we send a trivial response. We have to |
760 | | * assume that there might be some very, very recent activity |
761 | | * on the FS still in flight. |
762 | | */ |
763 | | if (do_cookie) { |
764 | | cookie_result = with_lock__wait_for_cookie(state); |
765 | | if (cookie_result != FCIR_SEEN) { |
766 | | error(_("fsmonitor: cookie_result '%d' != SEEN"), |
767 | | cookie_result); |
768 | | do_trivial = 1; |
769 | | } |
770 | | } |
771 | | |
772 | | if (do_flush) |
773 | | with_lock__do_force_resync(state); |
774 | | |
775 | | /* |
776 | | * We mark the current head of the batch list as "pinned" so |
777 | | * that the listener thread will treat this item as read-only |
778 | | * (and prevent any more paths from being added to it) from |
779 | | * now on. |
780 | | */ |
781 | | token_data = state->current_token_data; |
782 | | batch_head = token_data->batch_head; |
783 | | ((struct fsmonitor_batch *)batch_head)->pinned_time = time(NULL); |
784 | | |
785 | | /* |
786 | | * FSMonitor Protocol V2 requires that we send a response header |
787 | | * with a "new current token" and then all of the paths that changed |
788 | | * since the "requested token". We send the seq_nr of the just-pinned |
789 | | * head batch so that future requests from a client will be relative |
790 | | * to it. |
791 | | */ |
792 | | with_lock__format_response_token(&response_token, |
793 | | &token_data->token_id, batch_head); |
794 | | |
795 | | reply(reply_data, response_token.buf, response_token.len + 1); |
796 | | total_response_len += response_token.len + 1; |
797 | | |
798 | | trace2_data_string("fsmonitor", the_repository, "response/token", |
799 | | response_token.buf); |
800 | | trace_printf_key(&trace_fsmonitor, "response token: %s", |
801 | | response_token.buf); |
802 | | |
803 | | if (!do_trivial) { |
804 | | if (strcmp(requested_token_id.buf, token_data->token_id.buf)) { |
805 | | /* |
806 | | * The client last spoke to a different daemon |
807 | | * instance -OR- the daemon had to resync with |
808 | | * the filesystem (and lost events), so reject. |
809 | | */ |
810 | | trace2_data_string("fsmonitor", the_repository, |
811 | | "response/token", "different"); |
812 | | do_trivial = 1; |
813 | | |
814 | | } else if (requested_oldest_seq_nr < |
815 | | token_data->batch_tail->batch_seq_nr) { |
816 | | /* |
817 | | * The client wants older events than we have for |
818 | | * this token_id. This means that the end of our |
819 | | * batch list was truncated and we cannot give the |
820 | | * client a complete snapshot relative to their |
821 | | * request. |
822 | | */ |
823 | | trace_printf_key(&trace_fsmonitor, |
824 | | "client requested truncated data"); |
825 | | do_trivial = 1; |
826 | | } |
827 | | } |
828 | | |
829 | | if (do_trivial) { |
830 | | pthread_mutex_unlock(&state->main_lock); |
831 | | |
832 | | reply(reply_data, "/", 2); |
833 | | |
834 | | trace2_data_intmax("fsmonitor", the_repository, |
835 | | "response/trivial", 1); |
836 | | |
837 | | goto cleanup; |
838 | | } |
839 | | |
840 | | /* |
841 | | * We're going to hold onto a pointer to the current |
842 | | * token-data while we walk the list of batches of files. |
843 | | * During this time, we will NOT be under the lock. |
844 | | * So we ref-count it. |
845 | | * |
846 | | * This allows the listener thread to continue prepending |
847 | | * new batches of items to the token-data (which we'll ignore). |
848 | | * |
849 | | * AND it allows the listener thread to do a token-reset |
850 | | * (and install a new `current_token_data`). |
851 | | */ |
852 | | token_data->client_ref_count++; |
853 | | |
854 | | pthread_mutex_unlock(&state->main_lock); |
855 | | |
856 | | /* |
857 | | * The client request is relative to the token that they sent, |
858 | | * so walk the batch list backwards from the current head back |
859 | | * to the batch (sequence number) they named. |
860 | | * |
861 | | * We use khash to de-dup the list of pathnames. |
862 | | * |
863 | | * NEEDSWORK: each batch contains a list of interned strings, |
864 | | * so we only need to do pointer comparisons here to build the |
865 | | * hash table. Currently, we're still comparing the string |
866 | | * values. |
867 | | */ |
868 | | shown = kh_init_str(); |
869 | | for (batch = batch_head; |
870 | | batch && batch->batch_seq_nr > requested_oldest_seq_nr; |
871 | | batch = batch->next) { |
872 | | size_t k; |
873 | | |
874 | | for (k = 0; k < batch->nr; k++) { |
875 | | const char *s = batch->interned_paths[k]; |
876 | | size_t s_len; |
877 | | |
878 | | if (kh_get_str(shown, s) != kh_end(shown)) |
879 | | duplicates++; |
880 | | else { |
881 | | kh_put_str(shown, s, &hash_ret); |
882 | | |
883 | | trace_printf_key(&trace_fsmonitor, |
884 | | "send[%"PRIuMAX"]: %s", |
885 | | count, s); |
886 | | |
887 | | /* Each path gets written with a trailing NUL */ |
888 | | s_len = strlen(s) + 1; |
889 | | |
890 | | if (payload.len + s_len >= |
891 | | LARGE_PACKET_DATA_MAX) { |
892 | | reply(reply_data, payload.buf, |
893 | | payload.len); |
894 | | total_response_len += payload.len; |
895 | | strbuf_reset(&payload); |
896 | | } |
897 | | |
898 | | strbuf_add(&payload, s, s_len); |
899 | | count++; |
900 | | } |
901 | | } |
902 | | } |
903 | | |
904 | | if (payload.len) { |
905 | | reply(reply_data, payload.buf, payload.len); |
906 | | total_response_len += payload.len; |
907 | | } |
908 | | |
909 | | kh_release_str(shown); |
910 | | |
911 | | pthread_mutex_lock(&state->main_lock); |
912 | | |
913 | | if (token_data->client_ref_count > 0) |
914 | | token_data->client_ref_count--; |
915 | | |
916 | | if (token_data->client_ref_count == 0) { |
917 | | if (token_data != state->current_token_data) { |
918 | | /* |
919 | | * The listener thread did a token-reset while we were |
920 | | * walking the batch list. Therefore, this token is |
921 | | * stale and can be discarded completely. If we are |
922 | | * the last reader thread using this token, we own |
923 | | * that work. |
924 | | */ |
925 | | fsmonitor_free_token_data(token_data); |
926 | | } else if (batch) { |
927 | | /* |
928 | | * We are holding the lock and are the only |
929 | | * reader of the ref-counted portion of the |
930 | | * list, so we get the honor of seeing if the |
931 | | * list can be truncated to save memory. |
932 | | * |
933 | | * The main loop did not walk to the end of the |
934 | | * list, so this batch is the first item in the |
935 | | * batch-list that is older than the requested |
936 | | * end-point sequence number. See if the tail |
937 | | * end of the list is obsolete. |
938 | | */ |
939 | | remainder = with_lock__truncate_old_batches(state, |
940 | | batch); |
941 | | } |
942 | | } |
943 | | |
944 | | pthread_mutex_unlock(&state->main_lock); |
945 | | |
946 | | if (remainder) |
947 | | fsmonitor_batch__free_list(remainder); |
948 | | |
949 | | trace2_data_intmax("fsmonitor", the_repository, "response/length", total_response_len); |
950 | | trace2_data_intmax("fsmonitor", the_repository, "response/count/files", count); |
951 | | trace2_data_intmax("fsmonitor", the_repository, "response/count/duplicates", duplicates); |
952 | | |
953 | | cleanup: |
954 | | strbuf_release(&response_token); |
955 | | strbuf_release(&requested_token_id); |
956 | | strbuf_release(&payload); |
957 | | |
958 | | return 0; |
959 | | } |
960 | | |
961 | | static ipc_server_application_cb handle_client; |
962 | | |
963 | | static int handle_client(void *data, |
964 | | const char *command, size_t command_len, |
965 | | ipc_server_reply_cb *reply, |
966 | | struct ipc_server_reply_data *reply_data) |
967 | | { |
968 | | struct fsmonitor_daemon_state *state = data; |
969 | | int result; |
970 | | |
971 | | /* |
972 | | * The Simple IPC API now supports {char*, len} arguments, but |
973 | | * FSMonitor always uses proper null-terminated strings, so |
974 | | * we can ignore the command_len argument. (Trust, but verify.) |
975 | | */ |
976 | | if (command_len != strlen(command)) |
977 | | BUG("FSMonitor assumes text messages"); |
978 | | |
979 | | trace_printf_key(&trace_fsmonitor, "requested token: %s", command); |
980 | | |
981 | | trace2_region_enter("fsmonitor", "handle_client", the_repository); |
982 | | trace2_data_string("fsmonitor", the_repository, "request", command); |
983 | | |
984 | | result = do_handle_client(state, command, reply, reply_data); |
985 | | |
986 | | trace2_region_leave("fsmonitor", "handle_client", the_repository); |
987 | | |
988 | | return result; |
989 | | } |
990 | | |
991 | | #define FSMONITOR_DIR "fsmonitor--daemon" |
992 | | #define FSMONITOR_COOKIE_DIR "cookies" |
993 | | #define FSMONITOR_COOKIE_PREFIX (FSMONITOR_DIR "/" FSMONITOR_COOKIE_DIR "/") |
994 | | |
995 | | enum fsmonitor_path_type fsmonitor_classify_path_workdir_relative( |
996 | | const char *rel) |
997 | | { |
998 | | if (fspathncmp(rel, ".git", 4)) |
999 | | return IS_WORKDIR_PATH; |
1000 | | rel += 4; |
1001 | | |
1002 | | if (!*rel) |
1003 | | return IS_DOT_GIT; |
1004 | | if (*rel != '/') |
1005 | | return IS_WORKDIR_PATH; /* e.g. .gitignore */ |
1006 | | rel++; |
1007 | | |
1008 | | if (!fspathncmp(rel, FSMONITOR_COOKIE_PREFIX, |
1009 | | strlen(FSMONITOR_COOKIE_PREFIX))) |
1010 | | return IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX; |
1011 | | |
1012 | | return IS_INSIDE_DOT_GIT; |
1013 | | } |
1014 | | |
1015 | | enum fsmonitor_path_type fsmonitor_classify_path_gitdir_relative( |
1016 | | const char *rel) |
1017 | | { |
1018 | | if (!fspathncmp(rel, FSMONITOR_COOKIE_PREFIX, |
1019 | | strlen(FSMONITOR_COOKIE_PREFIX))) |
1020 | | return IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX; |
1021 | | |
1022 | | return IS_INSIDE_GITDIR; |
1023 | | } |
1024 | | |
1025 | | static enum fsmonitor_path_type try_classify_workdir_abs_path( |
1026 | | struct fsmonitor_daemon_state *state, |
1027 | | const char *path) |
1028 | | { |
1029 | | const char *rel; |
1030 | | |
1031 | | if (fspathncmp(path, state->path_worktree_watch.buf, |
1032 | | state->path_worktree_watch.len)) |
1033 | | return IS_OUTSIDE_CONE; |
1034 | | |
1035 | | rel = path + state->path_worktree_watch.len; |
1036 | | |
1037 | | if (!*rel) |
1038 | | return IS_WORKDIR_PATH; /* it is the root dir exactly */ |
1039 | | if (*rel != '/') |
1040 | | return IS_OUTSIDE_CONE; |
1041 | | rel++; |
1042 | | |
1043 | | return fsmonitor_classify_path_workdir_relative(rel); |
1044 | | } |
1045 | | |
1046 | | enum fsmonitor_path_type fsmonitor_classify_path_absolute( |
1047 | | struct fsmonitor_daemon_state *state, |
1048 | | const char *path) |
1049 | | { |
1050 | | const char *rel; |
1051 | | enum fsmonitor_path_type t; |
1052 | | |
1053 | | t = try_classify_workdir_abs_path(state, path); |
1054 | | if (state->nr_paths_watching == 1) |
1055 | | return t; |
1056 | | if (t != IS_OUTSIDE_CONE) |
1057 | | return t; |
1058 | | |
1059 | | if (fspathncmp(path, state->path_gitdir_watch.buf, |
1060 | | state->path_gitdir_watch.len)) |
1061 | | return IS_OUTSIDE_CONE; |
1062 | | |
1063 | | rel = path + state->path_gitdir_watch.len; |
1064 | | |
1065 | | if (!*rel) |
1066 | | return IS_GITDIR; /* it is the <gitdir> exactly */ |
1067 | | if (*rel != '/') |
1068 | | return IS_OUTSIDE_CONE; |
1069 | | rel++; |
1070 | | |
1071 | | return fsmonitor_classify_path_gitdir_relative(rel); |
1072 | | } |
1073 | | |
1074 | | /* |
1075 | | * We try to combine small batches at the front of the batch-list to avoid |
1076 | | * having a long list. This hopefully makes it a little easier when we want |
1077 | | * to truncate and maintain the list. However, we don't want the paths array |
1078 | | * to just keep growing and growing with realloc, so we insert an arbitrary |
1079 | | * limit. |
1080 | | */ |
1081 | | #define MY_COMBINE_LIMIT (1024) |
1082 | | |
1083 | | void fsmonitor_publish(struct fsmonitor_daemon_state *state, |
1084 | | struct fsmonitor_batch *batch, |
1085 | | const struct string_list *cookie_names) |
1086 | | { |
1087 | | if (!batch && !cookie_names->nr) |
1088 | | return; |
1089 | | |
1090 | | pthread_mutex_lock(&state->main_lock); |
1091 | | |
1092 | | if (batch) { |
1093 | | struct fsmonitor_batch *head; |
1094 | | |
1095 | | head = state->current_token_data->batch_head; |
1096 | | if (!head) { |
1097 | | BUG("token does not have batch"); |
1098 | | } else if (head->pinned_time) { |
1099 | | /* |
1100 | | * We cannot alter the current batch list |
1101 | | * because: |
1102 | | * |
1103 | | * [a] it is being transmitted to at least one |
1104 | | * client and the handle_client() thread has a |
1105 | | * ref-count, but not a lock on the batch list |
1106 | | * starting with this item. |
1107 | | * |
1108 | | * [b] it has been transmitted in the past to |
1109 | | * at least one client such that future |
1110 | | * requests are relative to this head batch. |
1111 | | * |
1112 | | * So, we can only prepend a new batch onto |
1113 | | * the front of the list. |
1114 | | */ |
1115 | | batch->batch_seq_nr = head->batch_seq_nr + 1; |
1116 | | batch->next = head; |
1117 | | state->current_token_data->batch_head = batch; |
1118 | | } else if (!head->batch_seq_nr) { |
1119 | | /* |
1120 | | * Batch 0 is unpinned. See the note in |
1121 | | * `fsmonitor_new_token_data()` about why we |
1122 | | * don't need to accumulate these paths. |
1123 | | */ |
1124 | | fsmonitor_batch__free_list(batch); |
1125 | | } else if (head->nr + batch->nr > MY_COMBINE_LIMIT) { |
1126 | | /* |
1127 | | * The head batch in the list has never been |
1128 | | * transmitted to a client, but folding the |
1129 | | * contents of the new batch onto it would |
1130 | | * exceed our arbitrary limit, so just prepend |
1131 | | * the new batch onto the list. |
1132 | | */ |
1133 | | batch->batch_seq_nr = head->batch_seq_nr + 1; |
1134 | | batch->next = head; |
1135 | | state->current_token_data->batch_head = batch; |
1136 | | } else { |
1137 | | /* |
1138 | | * We are free to add the paths in the given |
1139 | | * batch onto the end of the current head batch. |
1140 | | */ |
1141 | | fsmonitor_batch__combine(head, batch); |
1142 | | fsmonitor_batch__free_list(batch); |
1143 | | } |
1144 | | } |
1145 | | |
1146 | | if (cookie_names->nr) |
1147 | | with_lock__mark_cookies_seen(state, cookie_names); |
1148 | | |
1149 | | pthread_mutex_unlock(&state->main_lock); |
1150 | | } |
1151 | | |
1152 | | static void *fsm_health__thread_proc(void *_state) |
1153 | | { |
1154 | | struct fsmonitor_daemon_state *state = _state; |
1155 | | |
1156 | | trace2_thread_start("fsm-health"); |
1157 | | |
1158 | | fsm_health__loop(state); |
1159 | | |
1160 | | trace2_thread_exit(); |
1161 | | return NULL; |
1162 | | } |
1163 | | |
1164 | | static void *fsm_listen__thread_proc(void *_state) |
1165 | | { |
1166 | | struct fsmonitor_daemon_state *state = _state; |
1167 | | |
1168 | | trace2_thread_start("fsm-listen"); |
1169 | | |
1170 | | trace_printf_key(&trace_fsmonitor, "Watching: worktree '%s'", |
1171 | | state->path_worktree_watch.buf); |
1172 | | if (state->nr_paths_watching > 1) |
1173 | | trace_printf_key(&trace_fsmonitor, "Watching: gitdir '%s'", |
1174 | | state->path_gitdir_watch.buf); |
1175 | | |
1176 | | fsm_listen__loop(state); |
1177 | | |
1178 | | pthread_mutex_lock(&state->main_lock); |
1179 | | if (state->current_token_data && |
1180 | | state->current_token_data->client_ref_count == 0) |
1181 | | fsmonitor_free_token_data(state->current_token_data); |
1182 | | state->current_token_data = NULL; |
1183 | | pthread_mutex_unlock(&state->main_lock); |
1184 | | |
1185 | | trace2_thread_exit(); |
1186 | | return NULL; |
1187 | | } |
1188 | | |
1189 | | static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state) |
1190 | | { |
1191 | | struct ipc_server_opts ipc_opts = { |
1192 | | .nr_threads = fsmonitor__ipc_threads, |
1193 | | |
1194 | | /* |
1195 | | * We know that there are no other active threads yet, |
1196 | | * so we can let the IPC layer temporarily chdir() if |
1197 | | * it needs to when creating the server side of the |
1198 | | * Unix domain socket. |
1199 | | */ |
1200 | | .uds_disallow_chdir = 0 |
1201 | | }; |
1202 | | int health_started = 0; |
1203 | | int listener_started = 0; |
1204 | | int err = 0; |
1205 | | |
1206 | | /* |
1207 | | * Start the IPC thread pool before the we've started the file |
1208 | | * system event listener thread so that we have the IPC handle |
1209 | | * before we need it. |
1210 | | */ |
1211 | | if (ipc_server_run_async(&state->ipc_server_data, |
1212 | | state->path_ipc.buf, &ipc_opts, |
1213 | | handle_client, state)) |
1214 | | return error_errno( |
1215 | | _("could not start IPC thread pool on '%s'"), |
1216 | | state->path_ipc.buf); |
1217 | | |
1218 | | /* |
1219 | | * Start the fsmonitor listener thread to collect filesystem |
1220 | | * events. |
1221 | | */ |
1222 | | if (pthread_create(&state->listener_thread, NULL, |
1223 | | fsm_listen__thread_proc, state)) { |
1224 | | ipc_server_stop_async(state->ipc_server_data); |
1225 | | err = error(_("could not start fsmonitor listener thread")); |
1226 | | goto cleanup; |
1227 | | } |
1228 | | listener_started = 1; |
1229 | | |
1230 | | /* |
1231 | | * Start the health thread to watch over our process. |
1232 | | */ |
1233 | | if (pthread_create(&state->health_thread, NULL, |
1234 | | fsm_health__thread_proc, state)) { |
1235 | | ipc_server_stop_async(state->ipc_server_data); |
1236 | | err = error(_("could not start fsmonitor health thread")); |
1237 | | goto cleanup; |
1238 | | } |
1239 | | health_started = 1; |
1240 | | |
1241 | | /* |
1242 | | * The daemon is now fully functional in background threads. |
1243 | | * Our primary thread should now just wait while the threads |
1244 | | * do all the work. |
1245 | | */ |
1246 | | cleanup: |
1247 | | /* |
1248 | | * Wait for the IPC thread pool to shutdown (whether by client |
1249 | | * request, from filesystem activity, or an error). |
1250 | | */ |
1251 | | ipc_server_await(state->ipc_server_data); |
1252 | | |
1253 | | /* |
1254 | | * The fsmonitor listener thread may have received a shutdown |
1255 | | * event from the IPC thread pool, but it doesn't hurt to tell |
1256 | | * it again. And wait for it to shutdown. |
1257 | | */ |
1258 | | if (listener_started) { |
1259 | | fsm_listen__stop_async(state); |
1260 | | pthread_join(state->listener_thread, NULL); |
1261 | | } |
1262 | | |
1263 | | if (health_started) { |
1264 | | fsm_health__stop_async(state); |
1265 | | pthread_join(state->health_thread, NULL); |
1266 | | } |
1267 | | |
1268 | | if (err) |
1269 | | return err; |
1270 | | if (state->listen_error_code) |
1271 | | return state->listen_error_code; |
1272 | | if (state->health_error_code) |
1273 | | return state->health_error_code; |
1274 | | return 0; |
1275 | | } |
1276 | | |
1277 | | static int fsmonitor_run_daemon(void) |
1278 | | { |
1279 | | struct fsmonitor_daemon_state state; |
1280 | | const char *home; |
1281 | | int err; |
1282 | | |
1283 | | memset(&state, 0, sizeof(state)); |
1284 | | |
1285 | | hashmap_init(&state.cookies, cookies_cmp, NULL, 0); |
1286 | | pthread_mutex_init(&state.main_lock, NULL); |
1287 | | pthread_cond_init(&state.cookies_cond, NULL); |
1288 | | state.listen_error_code = 0; |
1289 | | state.health_error_code = 0; |
1290 | | state.current_token_data = fsmonitor_new_token_data(); |
1291 | | |
1292 | | /* Prepare to (recursively) watch the <worktree-root> directory. */ |
1293 | | strbuf_init(&state.path_worktree_watch, 0); |
1294 | | strbuf_addstr(&state.path_worktree_watch, absolute_path(get_git_work_tree())); |
1295 | | state.nr_paths_watching = 1; |
1296 | | |
1297 | | strbuf_init(&state.alias.alias, 0); |
1298 | | strbuf_init(&state.alias.points_to, 0); |
1299 | | if ((err = fsmonitor__get_alias(state.path_worktree_watch.buf, &state.alias))) |
1300 | | goto done; |
1301 | | |
1302 | | /* |
1303 | | * We create and delete cookie files somewhere inside the .git |
1304 | | * directory to help us keep sync with the file system. If |
1305 | | * ".git" is not a directory, then <gitdir> is not inside the |
1306 | | * cone of <worktree-root>, so set up a second watch to watch |
1307 | | * the <gitdir> so that we get events for the cookie files. |
1308 | | */ |
1309 | | strbuf_init(&state.path_gitdir_watch, 0); |
1310 | | strbuf_addbuf(&state.path_gitdir_watch, &state.path_worktree_watch); |
1311 | | strbuf_addstr(&state.path_gitdir_watch, "/.git"); |
1312 | | if (!is_directory(state.path_gitdir_watch.buf)) { |
1313 | | strbuf_reset(&state.path_gitdir_watch); |
1314 | | strbuf_addstr(&state.path_gitdir_watch, absolute_path(get_git_dir())); |
1315 | | state.nr_paths_watching = 2; |
1316 | | } |
1317 | | |
1318 | | /* |
1319 | | * We will write filesystem syncing cookie files into |
1320 | | * <gitdir>/<fsmonitor-dir>/<cookie-dir>/<pid>-<seq>. |
1321 | | * |
1322 | | * The extra layers of subdirectories here keep us from |
1323 | | * changing the mtime on ".git/" or ".git/foo/" when we create |
1324 | | * or delete cookie files. |
1325 | | * |
1326 | | * There have been problems with some IDEs that do a |
1327 | | * non-recursive watch of the ".git/" directory and run a |
1328 | | * series of commands any time something happens. |
1329 | | * |
1330 | | * For example, if we place our cookie files directly in |
1331 | | * ".git/" or ".git/foo/" then a `git status` (or similar |
1332 | | * command) from the IDE will cause a cookie file to be |
1333 | | * created in one of those dirs. This causes the mtime of |
1334 | | * those dirs to change. This triggers the IDE's watch |
1335 | | * notification. This triggers the IDE to run those commands |
1336 | | * again. And the process repeats and the machine never goes |
1337 | | * idle. |
1338 | | * |
1339 | | * Adding the extra layers of subdirectories prevents the |
1340 | | * mtime of ".git/" and ".git/foo" from changing when a |
1341 | | * cookie file is created. |
1342 | | */ |
1343 | | strbuf_init(&state.path_cookie_prefix, 0); |
1344 | | strbuf_addbuf(&state.path_cookie_prefix, &state.path_gitdir_watch); |
1345 | | |
1346 | | strbuf_addch(&state.path_cookie_prefix, '/'); |
1347 | | strbuf_addstr(&state.path_cookie_prefix, FSMONITOR_DIR); |
1348 | | mkdir(state.path_cookie_prefix.buf, 0777); |
1349 | | |
1350 | | strbuf_addch(&state.path_cookie_prefix, '/'); |
1351 | | strbuf_addstr(&state.path_cookie_prefix, FSMONITOR_COOKIE_DIR); |
1352 | | mkdir(state.path_cookie_prefix.buf, 0777); |
1353 | | |
1354 | | strbuf_addch(&state.path_cookie_prefix, '/'); |
1355 | | |
1356 | | /* |
1357 | | * We create a named-pipe or unix domain socket inside of the |
1358 | | * ".git" directory. (Well, on Windows, we base our named |
1359 | | * pipe in the NPFS on the absolute path of the git |
1360 | | * directory.) |
1361 | | */ |
1362 | | strbuf_init(&state.path_ipc, 0); |
1363 | | strbuf_addstr(&state.path_ipc, |
1364 | | absolute_path(fsmonitor_ipc__get_path(the_repository))); |
1365 | | |
1366 | | /* |
1367 | | * Confirm that we can create platform-specific resources for the |
1368 | | * filesystem listener before we bother starting all the threads. |
1369 | | */ |
1370 | | if (fsm_listen__ctor(&state)) { |
1371 | | err = error(_("could not initialize listener thread")); |
1372 | | goto done; |
1373 | | } |
1374 | | |
1375 | | if (fsm_health__ctor(&state)) { |
1376 | | err = error(_("could not initialize health thread")); |
1377 | | goto done; |
1378 | | } |
1379 | | |
1380 | | /* |
1381 | | * CD out of the worktree root directory. |
1382 | | * |
1383 | | * The common Git startup mechanism causes our CWD to be the |
1384 | | * root of the worktree. On Windows, this causes our process |
1385 | | * to hold a locked handle on the CWD. This prevents the |
1386 | | * worktree from being moved or deleted while the daemon is |
1387 | | * running. |
1388 | | * |
1389 | | * We assume that our FS and IPC listener threads have either |
1390 | | * opened all of the handles that they need or will do |
1391 | | * everything using absolute paths. |
1392 | | */ |
1393 | | home = getenv("HOME"); |
1394 | | if (home && *home && chdir(home)) |
1395 | | die_errno(_("could not cd home '%s'"), home); |
1396 | | |
1397 | | err = fsmonitor_run_daemon_1(&state); |
1398 | | |
1399 | | done: |
1400 | | pthread_cond_destroy(&state.cookies_cond); |
1401 | | pthread_mutex_destroy(&state.main_lock); |
1402 | | fsm_listen__dtor(&state); |
1403 | | fsm_health__dtor(&state); |
1404 | | |
1405 | | ipc_server_free(state.ipc_server_data); |
1406 | | |
1407 | | strbuf_release(&state.path_worktree_watch); |
1408 | | strbuf_release(&state.path_gitdir_watch); |
1409 | | strbuf_release(&state.path_cookie_prefix); |
1410 | | strbuf_release(&state.path_ipc); |
1411 | | strbuf_release(&state.alias.alias); |
1412 | | strbuf_release(&state.alias.points_to); |
1413 | | |
1414 | | return err; |
1415 | | } |
1416 | | |
1417 | | static int try_to_run_foreground_daemon(int detach_console MAYBE_UNUSED) |
1418 | | { |
1419 | | /* |
1420 | | * Technically, we don't need to probe for an existing daemon |
1421 | | * process, since we could just call `fsmonitor_run_daemon()` |
1422 | | * and let it fail if the pipe/socket is busy. |
1423 | | * |
1424 | | * However, this method gives us a nicer error message for a |
1425 | | * common error case. |
1426 | | */ |
1427 | | if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) |
1428 | | die(_("fsmonitor--daemon is already running '%s'"), |
1429 | | the_repository->worktree); |
1430 | | |
1431 | | if (fsmonitor__announce_startup) { |
1432 | | fprintf(stderr, _("running fsmonitor-daemon in '%s'\n"), |
1433 | | the_repository->worktree); |
1434 | | fflush(stderr); |
1435 | | } |
1436 | | |
1437 | | #ifdef GIT_WINDOWS_NATIVE |
1438 | | if (detach_console) |
1439 | | FreeConsole(); |
1440 | | #endif |
1441 | | |
1442 | | return !!fsmonitor_run_daemon(); |
1443 | | } |
1444 | | |
1445 | | static start_bg_wait_cb bg_wait_cb; |
1446 | | |
1447 | | static int bg_wait_cb(const struct child_process *cp UNUSED, |
1448 | | void *cb_data UNUSED) |
1449 | | { |
1450 | | enum ipc_active_state s = fsmonitor_ipc__get_state(); |
1451 | | |
1452 | | switch (s) { |
1453 | | case IPC_STATE__LISTENING: |
1454 | | /* child is "ready" */ |
1455 | | return 0; |
1456 | | |
1457 | | case IPC_STATE__NOT_LISTENING: |
1458 | | case IPC_STATE__PATH_NOT_FOUND: |
1459 | | /* give child more time */ |
1460 | | return 1; |
1461 | | |
1462 | | default: |
1463 | | case IPC_STATE__INVALID_PATH: |
1464 | | case IPC_STATE__OTHER_ERROR: |
1465 | | /* all the time in world won't help */ |
1466 | | return -1; |
1467 | | } |
1468 | | } |
1469 | | |
1470 | | static int try_to_start_background_daemon(void) |
1471 | | { |
1472 | | struct child_process cp = CHILD_PROCESS_INIT; |
1473 | | enum start_bg_result sbgr; |
1474 | | |
1475 | | /* |
1476 | | * Before we try to create a background daemon process, see |
1477 | | * if a daemon process is already listening. This makes it |
1478 | | * easier for us to report an already-listening error to the |
1479 | | * console, since our spawn/daemon can only report the success |
1480 | | * of creating the background process (and not whether it |
1481 | | * immediately exited). |
1482 | | */ |
1483 | | if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) |
1484 | | die(_("fsmonitor--daemon is already running '%s'"), |
1485 | | the_repository->worktree); |
1486 | | |
1487 | | if (fsmonitor__announce_startup) { |
1488 | | fprintf(stderr, _("starting fsmonitor-daemon in '%s'\n"), |
1489 | | the_repository->worktree); |
1490 | | fflush(stderr); |
1491 | | } |
1492 | | |
1493 | | cp.git_cmd = 1; |
1494 | | |
1495 | | strvec_push(&cp.args, "fsmonitor--daemon"); |
1496 | | strvec_push(&cp.args, "run"); |
1497 | | strvec_push(&cp.args, "--detach"); |
1498 | | strvec_pushf(&cp.args, "--ipc-threads=%d", fsmonitor__ipc_threads); |
1499 | | |
1500 | | cp.no_stdin = 1; |
1501 | | cp.no_stdout = 1; |
1502 | | cp.no_stderr = 1; |
1503 | | |
1504 | | sbgr = start_bg_command(&cp, bg_wait_cb, NULL, |
1505 | | fsmonitor__start_timeout_sec); |
1506 | | |
1507 | | switch (sbgr) { |
1508 | | case SBGR_READY: |
1509 | | return 0; |
1510 | | |
1511 | | default: |
1512 | | case SBGR_ERROR: |
1513 | | case SBGR_CB_ERROR: |
1514 | | return error(_("daemon failed to start")); |
1515 | | |
1516 | | case SBGR_TIMEOUT: |
1517 | | return error(_("daemon not online yet")); |
1518 | | |
1519 | | case SBGR_DIED: |
1520 | | return error(_("daemon terminated")); |
1521 | | } |
1522 | | } |
1523 | | |
1524 | | int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix) |
1525 | | { |
1526 | | const char *subcmd; |
1527 | | enum fsmonitor_reason reason; |
1528 | | int detach_console = 0; |
1529 | | |
1530 | | struct option options[] = { |
1531 | | OPT_BOOL(0, "detach", &detach_console, N_("detach from console")), |
1532 | | OPT_INTEGER(0, "ipc-threads", |
1533 | | &fsmonitor__ipc_threads, |
1534 | | N_("use <n> ipc worker threads")), |
1535 | | OPT_INTEGER(0, "start-timeout", |
1536 | | &fsmonitor__start_timeout_sec, |
1537 | | N_("max seconds to wait for background daemon startup")), |
1538 | | |
1539 | | OPT_END() |
1540 | | }; |
1541 | | |
1542 | | git_config(fsmonitor_config, NULL); |
1543 | | |
1544 | | argc = parse_options(argc, argv, prefix, options, |
1545 | | builtin_fsmonitor__daemon_usage, 0); |
1546 | | if (argc != 1) |
1547 | | usage_with_options(builtin_fsmonitor__daemon_usage, options); |
1548 | | subcmd = argv[0]; |
1549 | | |
1550 | | if (fsmonitor__ipc_threads < 1) |
1551 | | die(_("invalid 'ipc-threads' value (%d)"), |
1552 | | fsmonitor__ipc_threads); |
1553 | | |
1554 | | prepare_repo_settings(the_repository); |
1555 | | /* |
1556 | | * If the repo is fsmonitor-compatible, explicitly set IPC-mode |
1557 | | * (without bothering to load the `core.fsmonitor` config settings). |
1558 | | * |
1559 | | * If the repo is not compatible, the repo-settings will be set to |
1560 | | * incompatible rather than IPC, so we can use one of the __get |
1561 | | * routines to detect the discrepancy. |
1562 | | */ |
1563 | | fsm_settings__set_ipc(the_repository); |
1564 | | |
1565 | | reason = fsm_settings__get_reason(the_repository); |
1566 | | if (reason > FSMONITOR_REASON_OK) |
1567 | | die("%s", |
1568 | | fsm_settings__get_incompatible_msg(the_repository, |
1569 | | reason)); |
1570 | | |
1571 | | if (!strcmp(subcmd, "start")) |
1572 | | return !!try_to_start_background_daemon(); |
1573 | | |
1574 | | if (!strcmp(subcmd, "run")) |
1575 | | return !!try_to_run_foreground_daemon(detach_console); |
1576 | | |
1577 | | if (!strcmp(subcmd, "stop")) |
1578 | | return !!do_as_client__send_stop(); |
1579 | | |
1580 | | if (!strcmp(subcmd, "status")) |
1581 | | return !!do_as_client__status(); |
1582 | | |
1583 | | die(_("Unhandled subcommand '%s'"), subcmd); |
1584 | | } |
1585 | | |
1586 | | #else |
1587 | | int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix UNUSED) |
1588 | 0 | { |
1589 | 0 | struct option options[] = { |
1590 | 0 | OPT_END() |
1591 | 0 | }; |
1592 | |
|
1593 | 0 | if (argc == 2 && !strcmp(argv[1], "-h")) |
1594 | 0 | usage_with_options(builtin_fsmonitor__daemon_usage, options); |
1595 | | |
1596 | 0 | die(_("fsmonitor--daemon not supported on this platform")); |
1597 | 0 | } |
1598 | | #endif |