From: Ian Jackson Date: Sat, 9 Jul 2022 14:00:07 +0000 (+0100) Subject: prefork-interp: wip X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;h=16ccc96d7b5e5182bdc74e7b6a1a8839ffdb295e;hp=ad6722d7ab7ad11a9d6c0e4e53369cbcbc08ed36;p=chiark-utils.git prefork-interp: wip Signed-off-by: Ian Jackson --- diff --git a/prefork-interp.c b/prefork-interp.c index 3b77f61..cdb7dd3 100644 --- a/prefork-interp.c +++ b/prefork-interp.c @@ -19,8 +19,8 @@ * * setup (pre-exec) fd0: null, * fd[12: fd2-from-outer-caller - * env fds: orig-fd[01], listener, - * env fds: call(server-end)(fake) + * env fds: listener, call(server-end)(fake) + * env fds: orig-fd[01] * close fd: lockfile * * setup (script) runs initialisation parts of the script @@ -82,17 +82,57 @@ struct sockaddr_un sun; #define ACK_BYTE '\n' +static struct sockaddr_unix socket_sun; + +static void propagate_exit_status(int status, const char *what) { + int r; + + if (WIFEXITED(status)) { + _exit(status); + } + + if (WIFSIGNALED(status)) { + int sig = WTERMSIG(status); + char *signame = strsignal(sig); + if (signame == 0) signame = "unknown signal"; + + if (! WCOREDUMP(status) && + (sig == SIGINT || + sig == SIGHUP || + sig == SIGPIPE || + sig == SIGKILL)) { + struct sigaction sa; + FILLZERO(sa); + sa.sa_handler = SIG_DFL; + r = sigaction(sig, &sa, 0); + if (r) diee("failed to reset signal handler while propagating %s", + signame); + + sigset_t sset; + sigemptyset(&sset); + sigaddset(&sset, sig); + r = sigprocmask(SA_UNBLOCK, sset, 0); + if (r) diee("failed to reset signal block while propagating %s", + signame); + + raise(sig); + die("unexpectedly kept running after raising (to propagate) %s", + signame); + } + + die("setup failed due to signal %d %s%s", sig, signame, + WCOREDUMP(status) ? " (core dumped)" : ""); + } + + die("setup failed with weird wait status %d 0x%x", status, status); +} + // Returns: call(client-end) fd, or -1 to mean "is garbage" // find_socket_path must have been called static int attempt_connect_existing(void) { int r; int fd = -1; - FILLZERO(sun); - sun.sun_family = AF_UNIX; - assert(strlen(socket_path) <= sizeof(sun.sun_path)); - strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path)); - bool isgarbage = check_garbage(); if (isgarbage) goto x_garbage; @@ -100,7 +140,7 @@ static int attempt_connect_existing(void) { if (fd==-1) diee("socket() for client"); salen_t salen = sizeof(sun); - r = connect(client, (const struct sockaddr*)sun, salen); + r = connect(client, (const struct sockaddr*)&socket_sun, salen); if (r==-1) { if (errno==ECONNREFUSED || errno==ENOENT) goto x_garbgae; diee("connect() %s", socket_path); @@ -124,16 +164,80 @@ static int attempt_connect_existing(void) { return -1; } +static void become_setup(int sfd, int fake_pair[2]) + __attribute__((noreturn)) +{ + close(fake_pair[0]); + int call_fd = fake_pair[1]; + + int fd0_save = dup(0); if (fd0_save < 0) diee("dup stdin"); + int fd1_save = dup(1); if (fd1_save < 0) diee("dup stdin"); + + int null_0 = open("/dev/null", O_RDONLY); if (null_0 < 0) diee("open null"); + if (dup2(null_0, 0)) diee("dup2 /dev/null onto stdin"); + if (dup2(2, 1) != 1) die("dup2 stderr onto stdout"); + + putenv(asprintf("PREFORK_INTERP_FDS=%d,%d,%d,%d", + sfd, call_fd, fd0_save, fd1_save)); + execv( +} + static int connect_or_spawn(void) { int fd = connect_existing(); if (fd >= 0) return fd; - let acquire_lock(); - fd = connect_existing(); + int lockfd = acquire_lock(); + fd = connect_existing(); + if (fd >= 0) { close(lockfd); return fd; } + + // We must start a fresh one, and we hold the lock + + r = unlink(socketpath); + if (r<0) diee("failed to remove stale socket %s", socketpath); + + int fake_pair[2]; + r = socketpair(AF_UNIX, SOCK_STREAM, 0, fake_pair); + if (r<0) diee("socketpair() for fake initial connection"); + + int sfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sfd<0) diee("socket() for new listener"); + + salen_t salen = sizeof(sun); + r= bind(sfd, (const struct sockaddr*)&socket_sun, saledn); + if (r<0) diee("bind() on new listener"); + + // We never want callers to get ECONNREFUSED!. + // There is a race here: from my RTFM they may get ECONNREFUSED + // if they tr between our bind() and listen(). But if they do, they'll + // acquire the lock (serialising with us) and retry, and then it will work. + r = listen(sfd, INT_MAX); + if (r<0) diee("listen() for new listener"); + + pid_t setup_pid = fork(); + if (setup_pid == (pid_t)-1) diee("fork for spawn setup"); + if (!setup_pid) become_setup(sfd, fake_pair); + close(fake_pair[1]); + close(sfd); + + int status; + pid_t got = waitpid(setup_pid, &status, 0); + if (got == (pid_t)-1) diee("waitpid setup [%ld]", (long)setup_pid); + if (got != setup_pid) diee("waitpid setup [%ld] gave [%ld]!", + (long)setup_pid, (long)got); + if (status != 0) propagate_exit_status(status); + + close(lockfd); + return fake_pair[0]; +} int main(int argc, const char *const *argv) { script = process_opts(argc, argv); find_socket_path(); - int fd = connect_or_spawn(); + FILLZERO(sun); + sun.sun_family = AF_UNIX; + assert(strlen(socket_path) <= sizeof(sun.sun_path)); + strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path)); + + int call_fd = connect_or_spawn(); }