From: Ian Jackson Date: Fri, 19 Aug 2022 18:59:03 +0000 (+0100) Subject: prefork-interp: wip, socket watch, etc. X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;h=5cecdd540579e4d24f6d271acecac7fea64da5bd;hp=678036e697ee734e718d864b20c3a7649af1e9a4;p=chiark-utils.git prefork-interp: wip, socket watch, etc. Signed-off-by: Ian Jackson --- diff --git a/cprogs/Makefile b/cprogs/Makefile index b200b07..127c1fc 100644 --- a/cprogs/Makefile +++ b/cprogs/Makefile @@ -84,7 +84,7 @@ cgi-fcgi-interp: cgi-fcgi-interp.o prefork.o myopt.o common.o cgi-fcgi-interp: LDLIBS += -lnettle prefork-interp: prefork-interp.o prefork.o myopt.o common.o -prefork-interp: LDLIBS += -lnettle +prefork-interp: LDLIBS += -lnettle -luv $(SEDDERYDOCS): %.txt: %.c sed '/^$$/,$$d' <$^ >$@.new && mv -f $@.new $@ diff --git a/cprogs/prefork-interp.c b/cprogs/prefork-interp.c index 9eb5aa1..1125601 100644 --- a/cprogs/prefork-interp.c +++ b/cprogs/prefork-interp.c @@ -18,11 +18,11 @@ * client (C wrapper) connects to server * (including reading ack byte) * if fails or garbage - * === acquire lock === - * makes new listening socket - * makes first-instance socketpair - * makes watcher pipes + * === acquires lock === + * makes new listening socket + * makes watcher pipes * forks watcher and awaits + * makes first-instance socketpair * forks setup (script, sock fds indicated in env) * fd0, fd1, fd2: from-outer * other fd: call(client-end)(fake) @@ -33,7 +33,7 @@ * starts watch on socket path * sets stderr to line buffered * sets stdin to nonblocking - * daemonises + * daemonises (one fork, becomes session leader) * when socket stat changes, quit * * setup (pre-exec) fd0: null, @@ -51,6 +51,7 @@ * * server (pm) [1] [fd0: null], * [fd[12]: fd2-from-outer] + * setsid * right away, forks init monitor * [2] closes outer caller fds and call(fake) * [server (pm)] fd[012]: null @@ -97,6 +98,8 @@ #include +#include + #include "prefork.h" const char our_name[] = "prefork-interp"; @@ -115,6 +118,7 @@ void fusagemessage(FILE *f) { } static int laundering; +static struct stat initial_stab; const struct cmdinfo cmdinfos[]= { PREFORK_CMDINFOS @@ -360,8 +364,83 @@ static FILE *connect_existing(void) { return 0; } +static void watcher_cb_stdin(uv_poll_t *handle, int status, int events) { + char c; + int r; + + if ((errno = -status)) diee("watcher: poll stdin"); + for (;;) { + r= read(0, &c, 1); + if (r!=-1) _exit(0); + if (!(errno==EINTR || errno==EWOULDBLOCK || errno==EAGAIN)) + diee("watcher: read sentinel stdin"); + } +} + +static void watcher_cb_sockpath(uv_fs_event_t *handle, const char *filename, + int events, int status) { + int r; + struct stat now_stab; + + if ((errno = -status)) diee("watcher: poll stdin"); + for (;;) { + r= stat(socket_path, &now_stab); + if (r==-1) { + if (errno==ENOENT) _exit(0); + if (errno==EINTR) continue; + diee("stat socket: %s", socket_path); + } + if (!(now_stab.st_dev == initial_stab.st_dev && + now_stab.st_ino == initial_stab.st_ino)) + _exit(0); + } +} + +// On entry, stderr is still inherited, but 0 and 1 are the pipes +static __attribute__((noreturn)) +void become_watcher(void) { + uv_loop_t loop; + uv_poll_t uvhandle_stdin; + uv_fs_event_t uvhandle_sockpath; + int r; + + if (fcntl(0, F_SETFL, O_NONBLOCK)) diee("watcher set stdin nonblocking"); + + errno= -uv_loop_init(&loop); + if (errno) diee("watcher: uv_loop_init"); + + errno= -uv_poll_init(&loop, &uvhandle_stdin, 0); + if (errno) diee("watcher: uv_poll_init"); + errno= -uv_poll_start(&uvhandle_stdin, + UV_READABLE | UV_WRITABLE | UV_DISCONNECT, + watcher_cb_stdin); + if (errno) diee("watcher: uv_poll_start"); + + errno= -uv_fs_event_init(&loop, &uvhandle_sockpath); + if (errno) diee("watcher: uv_fs_event_init"); + + errno= -uv_fs_event_start(&uvhandle_sockpath, watcher_cb_sockpath, + socket_path, 0); + if (errno) diee("watcher: uv_fs_event_start"); + + // OK everything is set up, let us daemonise + if (dup2(1,2) != 2) diee("watcher: set daemonised stderr"); + r= setvbuf(stderr, 0, _IOLBF, BUFSIZ); + if (r) diee("watcher: setvbuf stderr"); + + pid_t child = fork(); + if (child == (pid_t)-1) diee("watcher: fork"); + if (child) _exit(0); + + if (setsid() == (pid_t)-1) diee("watcher: setsid"); + + r= uv_run(&loop, UV_RUN_DEFAULT); + die("uv_run returned (%d)", r); +} + static __attribute__((noreturn)) -void become_setup(int sfd, int fake_pair[2]) { +void become_setup(int sfd, int fake_pair[2], + int watcher_stdin, int watcher_stderr) { close(fake_pair[0]); int call_fd = fake_pair[1]; @@ -376,7 +455,7 @@ void become_setup(int sfd, int fake_pair[2]) { // next to "v1". Simple extension can be done by having the script // side say something about it in the ack xdata, which we currently ignore. putenv(m_asprintf("PREFORK_INTERP=v1 %d,%d %s", - sfd, call_fd, socket_path)); + sfd, call_fd, socket_path, watcher_stdin, watcher_stderr)); execvp(executor_argv[0], (char**)executor_argv); diee("execute %s", executor_argv[0]); @@ -405,6 +484,9 @@ static void connect_or_spawn(void) { r= bind(sfd, (const struct sockaddr*)&sockaddr_sun, salen); if (r<0) diee("bind() on new listener"); + r= stat(socket_path, &initial_stab); + if (r<0) diee("stat() fresh socket"); + // We never want callers to get ECONNREFUSED. But: // There is a race here: from my RTFM they may get ECONNREFUSED // if they try between our bind() and listen(). But if they do, they'll @@ -412,13 +494,43 @@ static void connect_or_spawn(void) { r = listen(sfd, INT_MAX); if (r<0) diee("listen() for new listener"); + // Fork watcher + + int watcher_stdin[2]; + int watcher_stderr[2]; + if (pipe(watcher_stdin) || pipe(watcher_stderr)) + diee("pipe() for socket inode watcher"); + + pid_t watcher = fork(); + if (watcher == (pid_t)-1) diee("fork for watcher"); + if (!watcher) { + close(sfd); + close(lockfd); + close(watcher_stdin[1]); + close(watcher_stderr[0]); + if (dup2(watcher_stdin[0], 0) != 0 || + dup2(watcher_stderr[1], 1) != 0) + diee("initial dup2() for watcher"); + close(watcher_stdin[0]); + close(watcher_stderr[1]); + become_watcher(); + } + + close(watcher_stdin[0]); + close(watcher_stderr[1]); + if (fcntl(watcher_stderr[0], F_SETFL, O_NONBLOCK)) + diee("parent set watcher stderr nonblocking"); + + // Fork setup + int fake_pair[2]; r = socketpair(AF_UNIX, SOCK_STREAM, 0, fake_pair); if (r<0) diee("socketpair() for fake initial connection"); pid_t setup_pid = fork(); if (setup_pid == (pid_t)-1) diee("fork for spawn setup"); - if (!setup_pid) become_setup(sfd, fake_pair); + if (!setup_pid) become_setup(sfd, fake_pair, + watcher_stdin[1], watcher_stderr[0]); close(fake_pair[1]); close(sfd); diff --git a/debian/control b/debian/control index f813f95..da52509 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: admin Priority: extra Maintainer: Ian Jackson Build-Depends: libx11-dev, libxmu-dev, nettle-dev, debhelper (>= 5), - libxdmcp-dev, libxau-dev, libice-dev, libsm-dev + libxdmcp-dev, libxau-dev, libice-dev, libsm-dev, libuv1-dev Standards-Version: 3.9.1 Package: chiark-backup diff --git a/perl/Prefork.pm b/perl/Prefork.pm index 2869caf..a9fc481 100644 --- a/perl/Prefork.pm +++ b/perl/Prefork.pm @@ -162,6 +162,9 @@ sub initialisation_complete { #---- setup (pm) [2], exits ---- _exit(0); } + setsid() > 0 or fail_log("setsid: $!"); + # The server will be a session leader, but it won't open ttys, + # so that is ok. #---- server(pm) [1] ---- @@ -177,7 +180,6 @@ sub initialisation_complete { # --- server(pm) [2] ---- $fail_log = 1; - setsid() > 0 or fail_log("setsid: $!"); open STDIN, "<&NULL" or fail_log("dup null onto stdin: $!"); open STDOUT, ">&NULL" or fail_log("dup null onto stdout: $!"); open STDERR, ">&NULL" or fail_log("dup null onto stderr: $!");