From 29b9aea425198af94f4a406c863edd9d3c1e1cd4 Mon Sep 17 00:00:00 2001 From: benjamin-tlon <42358674+benjamin-tlon@users.noreply.github.com> Date: Mon, 11 Feb 2019 13:12:23 -0800 Subject: [PATCH] In daemon mode, fork into a background process. (#1190) In daemon mode, fork into a background process, but don't exit from the parent process until the child finishes booting. If the child crashes during boot, exit with an error code. The motivation for this change is to be able to be able to start a ship with `urb.py` and immediately be able to start interacting with it. --- .gitignore | 2 + include/vere/vere.h | 1 + vere/main.c | 110 ++++++++++++++++++++++++++++++++++++++++++-- vere/reck.c | 8 ++++ 4 files changed, 117 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 7c7e62b11..870040010 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ node_modules/ cscope.* build/ TAGS +/fakezod +/zod diff --git a/include/vere/vere.h b/include/vere/vere.h index ec3c9def9..18e6992a3 100644 --- a/include/vere/vere.h +++ b/include/vere/vere.h @@ -600,6 +600,7 @@ c3_i xit_i; // exit code for shutdown void* tls_u; // server SSL_CTX* u3_trac tra_u; // tracing information + void (*bot_f)(); // call when chis is up } u3_host; // host == computer == process /** New pier system. diff --git a/vere/main.c b/vere/main.c index 976ae4cc9..7b1c3a577 100644 --- a/vere/main.c +++ b/vere/main.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -22,7 +23,8 @@ /* Require unsigned char */ -STATIC_ASSERT(( 0 == CHAR_MIN && UCHAR_MAX == CHAR_MAX ), "unsigned char required"); +STATIC_ASSERT(( 0 == CHAR_MIN && UCHAR_MAX == CHAR_MAX ), + "unsigned char required"); /* _main_readw(): parse a word from a string. */ @@ -248,7 +250,8 @@ _main_getopt(c3_i argc, c3_c** argv) } } - c3_t imp_t = ( (0 != u3_Host.ops_u.who_c) && (4 == strlen(u3_Host.ops_u.who_c)) ); + c3_t imp_t = ((0 != u3_Host.ops_u.who_c) && + (4 == strlen(u3_Host.ops_u.who_c))); if ( u3_Host.ops_u.gen_c != 0 && u3_Host.ops_u.nuu == c3n ) { fprintf(stderr, "-G only makes sense when bootstrapping a new instance\n"); @@ -519,11 +522,16 @@ report(void) { printf("---------\nLibraries\n---------\n"); printf("gmp: %s\n", gmp_version); - printf("sigsegv: %d.%d\n", (libsigsegv_version >> 8) & 0xff, libsigsegv_version & 0xff); + printf("sigsegv: %d.%d\n", + (libsigsegv_version >> 8) & 0xff, + libsigsegv_version & 0xff); printf("openssl: %s\n", SSLeay_version(SSLEAY_VERSION)); printf("curses: %s\n", curses_version()); printf("libuv: %s\n", uv_version_string()); - printf("libh2o: %d.%d.%d\n", H2O_LIBRARY_VERSION_MAJOR, H2O_LIBRARY_VERSION_MINOR, H2O_LIBRARY_VERSION_PATCH); + printf("libh2o: %d.%d.%d\n", + H2O_LIBRARY_VERSION_MAJOR, + H2O_LIBRARY_VERSION_MINOR, + H2O_LIBRARY_VERSION_PATCH); } void @@ -535,6 +543,96 @@ _stop_exit(c3_i int_i) u3_pier_exit(); } +/* + This is set to the the write-end of a pipe when Urbit is started in + daemon mode. It's meant to be used as a signal to the parent process + that the child process has finished booting. +*/ +static c3_i _child_process_booted_signal_fd = -1; + +/* + This should be called whenever the ship has been booted enough to + handle commands from automation code. Specifically, once the Eyre's + `chis` interface is up and running. + + In daemon mode, this signals to the parent process that it can + exit. Otherwise, it does nothing. + + Once we've sent a signal with `write`, we close the file descriptor + and overwrite the global to make it impossible to accidentally do + this twice. +*/ +static void _on_boot_completed_cb() { + c3_c buf[2] = {0,0}; + + if ( -1 == _child_process_booted_signal_fd ) { + return; + } + + if ( 0 == write(_child_process_booted_signal_fd, buf, 1) ) { + c3_assert(!"_on_boot_completed_cb: Can't write to parent FD"); + } + + close(_child_process_booted_signal_fd); + _child_process_booted_signal_fd = -1; +} + +/* + In daemon mode, run the urbit as a background process, but don't + exit from the parent process until the ship is finished booting. + + We use a pipe to communicate between the child and the parent. The + parent waits for the child to write something to the pipe and + then exits. If the pipe is closed with nothing written to it, get + the exit status from the child process and also exit with that status. + + We want the child to write to the pipe once it's booted, so we put + `_on_boot_completed_cb` into `u3_Host.bot_f`, which is NULL in + non-daemon mode. That gets called once the `chis` service is + available. + + In both processes, we are good fork() citizens, and close all unused + file descriptors. Closing `pipefd[1]` in the parent process is + especially important, since the pipe needs to be closed if the child + process dies. When the pipe is closed, the read fails, and that's + how we know that something went wrong. + + There are some edge cases around `WEXITSTATUS` that are not handled + here, but I don't think it matters. +*/ +static void +_fork_into_background_process() +{ + c3_i pipefd[2]; + + if ( 0 != pipe(pipefd) ) { + c3_assert(!"Failed to create pipe"); + } + + pid_t childpid = fork(); + + if ( 0 == childpid ) { + close(pipefd[0]); + _child_process_booted_signal_fd = pipefd[1]; + u3_Host.bot_f = _on_boot_completed_cb; + return; + } + + close(pipefd[1]); + close(0); + close(1); + close(2); + + c3_c buf[2] = {0,0}; + if ( 1 == read(pipefd[0], buf, 1) ) { + exit(0); + } + + c3_i status; + wait(&status); + exit(WEXITSTATUS(status)); +} + c3_i main(c3_i argc, c3_c** argv) @@ -546,6 +644,10 @@ main(c3_i argc, return 1; } + if ( c3y == u3_Host.ops_u.dem ) { + _fork_into_background_process(); + } + if ( c3y == u3_Host.ops_u.rep ) { report(); return 0; diff --git a/vere/reck.c b/vere/reck.c index 6f45522fd..34a518bed 100644 --- a/vere/reck.c +++ b/vere/reck.c @@ -161,6 +161,14 @@ _reck_kick_http(u3_pier* pir_u, { u3_http_ef_form(u3k(p_fav)); + // The control server has now started. + // + // If we're in daemon mode, we need to inform the parent process + // that we've finished booting. + if (u3_Host.bot_f) { + u3_Host.bot_f(); + } + u3z(pox); u3z(fav); return c3y; }