Skip to content

Commit f7a8c02

Browse files
committed
fsmonitor--daemon: implement 'start' command
Implement 'git fsmonitor--daemon start' command. This command tries to start a daemon in the background. It creates a background process to run the daemon. The updated daemon does not actually do anything yet because the platform backends are still just stubs. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent 7215c5a commit f7a8c02

File tree

1 file changed

+208
-0
lines changed

1 file changed

+208
-0
lines changed

builtin/fsmonitor--daemon.c

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "khash.h"
1010

1111
static const char * const builtin_fsmonitor__daemon_usage[] = {
12+
N_("git fsmonitor--daemon start [<options>]"),
1213
N_("git fsmonitor--daemon run [<options>]"),
1314
N_("git fsmonitor--daemon stop"),
1415
N_("git fsmonitor--daemon status"),
@@ -22,6 +23,9 @@ static const char * const builtin_fsmonitor__daemon_usage[] = {
2223
#define FSMONITOR__IPC_THREADS "fsmonitor.ipcthreads"
2324
static int fsmonitor__ipc_threads = 8;
2425

26+
#define FSMONITOR__START_TIMEOUT "fsmonitor.starttimeout"
27+
static int fsmonitor__start_timeout_sec = 60;
28+
2529
static int fsmonitor_config(const char *var, const char *value, void *cb)
2630
{
2731
if (!strcmp(var, FSMONITOR__IPC_THREADS)) {
@@ -33,6 +37,15 @@ static int fsmonitor_config(const char *var, const char *value, void *cb)
3337
return 0;
3438
}
3539

40+
if (!strcmp(var, FSMONITOR__START_TIMEOUT)) {
41+
int i = git_config_int(var, value);
42+
if (i < 0)
43+
return error(_("value of '%s' out of range: %d"),
44+
FSMONITOR__START_TIMEOUT, i);
45+
fsmonitor__start_timeout_sec = i;
46+
return 0;
47+
}
48+
3649
return git_default_config(var, value, cb);
3750
}
3851

@@ -256,6 +269,194 @@ static int try_to_run_foreground_daemon(void)
256269
return !!fsmonitor_run_daemon();
257270
}
258271

272+
#ifdef GIT_WINDOWS_NATIVE
273+
/*
274+
* Create a background process to run the daemon. It should be completely
275+
* disassociated from the terminal.
276+
*
277+
* Conceptually like `daemonize()` but different because Windows does not
278+
* have `fork(2)`. Spawn a normal Windows child process but without the
279+
* limitations of `start_command()` and `finish_command()`.
280+
*
281+
* The child process execs the "git fsmonitor--daemon run" command.
282+
*
283+
* The current process returns so that the caller can wait for the child
284+
* to startup before exiting.
285+
*/
286+
static int spawn_fsmonitor(pid_t *pid)
287+
{
288+
char git_exe[MAX_PATH];
289+
struct strvec args = STRVEC_INIT;
290+
int in, out;
291+
292+
GetModuleFileNameA(NULL, git_exe, MAX_PATH);
293+
294+
in = open("/dev/null", O_RDONLY);
295+
out = open("/dev/null", O_WRONLY);
296+
297+
strvec_push(&args, git_exe);
298+
strvec_push(&args, "fsmonitor--daemon");
299+
strvec_push(&args, "run");
300+
strvec_pushf(&args, "--ipc-threads=%d", fsmonitor__ipc_threads);
301+
302+
*pid = mingw_spawnvpe(args.v[0], args.v, NULL, NULL, in, out, out);
303+
close(in);
304+
close(out);
305+
306+
strvec_clear(&args);
307+
308+
if (*pid < 0)
309+
return error(_("could not spawn fsmonitor--daemon in the background"));
310+
311+
return 0;
312+
}
313+
#else
314+
/*
315+
* Create a background process to run the daemon. It should be completely
316+
* disassociated from the terminal.
317+
*
318+
* This is adapted from `daemonize()`. Use `fork()` to directly
319+
* create and run the daemon in the child process.
320+
*
321+
* The fork-child can just call the run code; it does not need to exec
322+
* it.
323+
*
324+
* The fork-parent returns the child PID so that we can wait for the
325+
* child to startup before exiting.
326+
*/
327+
static int spawn_fsmonitor(pid_t *pid)
328+
{
329+
*pid = fork();
330+
331+
switch (*pid) {
332+
case 0:
333+
if (setsid() == -1)
334+
error_errno(_("setsid failed"));
335+
close(0);
336+
close(1);
337+
close(2);
338+
sanitize_stdfds();
339+
340+
return !!fsmonitor_run_daemon();
341+
342+
case -1:
343+
return error_errno(_("could not spawn fsmonitor--daemon in the background"));
344+
345+
default:
346+
return 0;
347+
}
348+
}
349+
#endif
350+
351+
/*
352+
* This is adapted from `wait_or_whine()`. Watch the child process and
353+
* let it get started and begin listening for requests on the socket
354+
* before reporting our success.
355+
*/
356+
static int wait_for_startup(pid_t pid_child)
357+
{
358+
int status;
359+
pid_t pid_seen;
360+
enum ipc_active_state s;
361+
time_t time_limit, now;
362+
363+
time(&time_limit);
364+
time_limit += fsmonitor__start_timeout_sec;
365+
366+
for (;;) {
367+
pid_seen = waitpid(pid_child, &status, WNOHANG);
368+
369+
if (pid_seen == -1)
370+
return error_errno(_("waitpid failed"));
371+
else if (pid_seen == 0) {
372+
/*
373+
* The child is still running (this should be
374+
* the normal case). Try to connect to it on
375+
* the socket and see if it is ready for
376+
* business.
377+
*
378+
* If there is another daemon already running,
379+
* our child will fail to start (possibly
380+
* after a timeout on the lock), but we don't
381+
* care (who responds) if the socket is live.
382+
*/
383+
s = fsmonitor_ipc__get_state();
384+
if (s == IPC_STATE__LISTENING)
385+
return 0;
386+
387+
time(&now);
388+
if (now > time_limit)
389+
return error(_("fsmonitor--daemon not online yet"));
390+
} else if (pid_seen == pid_child) {
391+
/*
392+
* The new child daemon process shutdown while
393+
* it was starting up, so it is not listening
394+
* on the socket.
395+
*
396+
* Try to ping the socket in the odd chance
397+
* that another daemon started (or was already
398+
* running) while our child was starting.
399+
*
400+
* Again, we don't care who services the socket.
401+
*/
402+
s = fsmonitor_ipc__get_state();
403+
if (s == IPC_STATE__LISTENING)
404+
return 0;
405+
406+
/*
407+
* We don't care about the WEXITSTATUS() nor
408+
* any of the WIF*(status) values because
409+
* `cmd_fsmonitor__daemon()` does the `!!result`
410+
* trick on all function return values.
411+
*
412+
* So it is sufficient to just report the
413+
* early shutdown as an error.
414+
*/
415+
return error(_("fsmonitor--daemon failed to start"));
416+
} else
417+
return error(_("waitpid is confused"));
418+
}
419+
}
420+
421+
static int try_to_start_background_daemon(void)
422+
{
423+
pid_t pid_child;
424+
int ret;
425+
426+
/*
427+
* Before we try to create a background daemon process, see
428+
* if a daemon process is already listening. This makes it
429+
* easier for us to report an already-listening error to the
430+
* console, since our spawn/daemon can only report the success
431+
* of creating the background process (and not whether it
432+
* immediately exited).
433+
*/
434+
if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING)
435+
die("fsmonitor--daemon is already running '%s'",
436+
the_repository->worktree);
437+
438+
printf(_("starting fsmonitor-daemon in '%s'\n"),
439+
the_repository->worktree);
440+
fflush(stdout);
441+
442+
/*
443+
* Run the actual daemon in a background process.
444+
*/
445+
ret = spawn_fsmonitor(&pid_child);
446+
if (pid_child <= 0)
447+
return ret;
448+
449+
/*
450+
* Wait (with timeout) for the background child process get
451+
* started and begin listening on the socket/pipe. This makes
452+
* the "start" command more synchronous and more reliable in
453+
* tests.
454+
*/
455+
ret = wait_for_startup(pid_child);
456+
457+
return ret;
458+
}
459+
259460
int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
260461
{
261462
const char *subcmd;
@@ -264,6 +465,10 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
264465
OPT_INTEGER(0, "ipc-threads",
265466
&fsmonitor__ipc_threads,
266467
N_("use <n> ipc worker threads")),
468+
OPT_INTEGER(0, "start-timeout",
469+
&fsmonitor__start_timeout_sec,
470+
N_("Max seconds to wait for background daemon startup")),
471+
267472
OPT_END()
268473
};
269474

@@ -285,6 +490,9 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
285490
die(_("invalid 'ipc-threads' value (%d)"),
286491
fsmonitor__ipc_threads);
287492

493+
if (!strcmp(subcmd, "start"))
494+
return !!try_to_start_background_daemon();
495+
288496
if (!strcmp(subcmd, "run"))
289497
return !!try_to_run_foreground_daemon();
290498

0 commit comments

Comments
 (0)