From: Ian Jackson Date: Fri, 15 Jul 2022 21:13:29 +0000 (+0100) Subject: prefork-interp: wip X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=chiark-utils.git;a=commitdiff_plain;h=1dcefda0059438029ce1cdf23e19c9fde0789640 prefork-interp: wip Signed-off-by: Ian Jackson --- diff --git a/perl/Prefork.pm b/perl/Prefork.pm index 162d5e4..fa986c1 100644 --- a/perl/Prefork.pm +++ b/perl/Prefork.pm @@ -18,8 +18,83 @@ sub server_quit ($) { _exit(0); } +# Returns in the executor process sub become_monitor () { - + my $child = fork // fail("fork executor: $!");or + if (!$child) { + #---- executor ---- + open ::STDIN , "<& $call_fds[0]" or fail("dup for fd0"); + open ::STDOUT, ">& $call_fds[1]" or fail("dup for fd1"); + open ::STDERR, ">& $call_fds[2]" or fail("dup for fd2"); + POSIX::close($_) foreach @call_fds; + close CALL; + return; + } + + #---- monitor [2] ---- + my $got = waitpid $child, 0 // fail("wait for executor: $!"); + $got == $child or fail("wait for esecutor gave $got, expected $child"); + + protocol_write(pack "L", $?); + _exit(0); +} + +sub protocol_write ($) { + my ($d) = @_; + return if (print CALL $d and flush CALL); + _exit(0) if $!==EPIPE || $!==ECONNRESET; + fail("protocol write: $!"); +} + +sub eintr_retry ($) { + my ($f) = @_; + for (;;) { + my $r = $f->(); + return $r if defined $r; + next if $!==EINTR; + return $r; + } +} + +sub protocol_read_fail ($) { + my ($what) = @_; + _exit(0) if $!==ECONNRESET; + fail("recv $what: $!"); +} + +sub protocol_exchange () { + protocol_write('\n'); + + @call_fds = map { + my $r; + for (;;) { + $! = 0; + $r = IO::FDPass::recv(fileno(CALL)); + last if $r >= 0; + _exit(0) if $!==0; + protocol_read_fail("fd $_"); + } + $r; + } 0..2; + + my $len; + $r = read(CALL, $len, 4) // protocol_read_fail("message length"); + $r == 4 or _exit(0); + + $len = unpack "L", $len; + my $data; + $r = read(CALL, $data, $len) // protocol_read_fail("message data ($len)"); + $r == $len or _exit(0); + + @ARGV = split /\0/, $data; + @ARGV >= 2 or fail("message data has too few strings"); + length(pop(@ARGV)) and fail("message data missing trailing nul"); + %ENV = (); + while (my $s = shift @ARGV) { + last if !length $s; + $s =~ m/=/ or fail("message data env var missing equals"); + $ENV{$`} = $'; + } } sub initialisation_complete { @@ -61,7 +136,7 @@ sub initialisation_complete { $child = fork // croak "second fork failed: $!"; if (!$child) { # we are the child, i.e. the one fa-monitor - become_monitor(); + return become_monitor(); } our %children; @@ -111,7 +186,10 @@ sub initialisation_complete { if (accept(CALL, LISTEN)) { $child = fork // fail("fork for accepted call failed: $!"); if (!$child) { - become_monitor(); + #---- monitor [1] ---- + close LISTEN; + protocol_exchange(); + return become_monitor(); } close(CALL); $errcount = 0; diff --git a/prefork-interp.c b/prefork-interp.c index f37c8fd..07e9fb0 100644 --- a/prefork-interp.c +++ b/prefork-interp.c @@ -53,7 +53,7 @@ * * [server (script)] accepts, forks monitor * - * monitor [fd[012]: null] + * monitor [1] [fd[012]: null] * other fds: syslog, call(server-end) * sends ack byte * receives args, env, fds @@ -62,7 +62,7 @@ * executor sorts out fds: * fd0, fd1, fd2: from-outer-caller * close fds: call(server-end) - * implicitly closed fds: syslog + * retained fds: syslog * * sets cmdline, env * runs main part of script @@ -128,9 +128,122 @@ static void propagate_exit_status(int status, const char *what) { die("setup failed with weird wait status %d 0x%x", status, status); } +static void die_data_overflow __attribute((noreturn)) { + die("cannot handle data with length >2^32"); +} + +static void prepare_data(size_t *len, char **buf, + const void *data, size_t dl) { + if (len) { + if (dl >= SIZE_MAX - *len) + die_data_overlow(); + *len += dl; + } + if (buf) { + memcpy(*buf, data, dl); + *buf += dl; + } +} + +static void prepare_length(size_t *len, char **buf, size_t dl) { + if (dl > UINT32_MAX) die_data_overflow(); + uint32_t dl = htonl(dl); + prepare_data(len, buf, &dl, sizeof(dl)); +} + +static void prepare_string(size_t *len, char **buf, const char *string) { + size_t sl = strlen(s); + prepare_data(len, buf, s, sl+1); +} + +static void prepare_message(size_t *len, char **buf, + const char *const *argv) { + const char *s; + + const char *const *p = environ; + while ((s = *p++)) { + if (strchr(s, '=')) + prepare_string(len, buf, s); + } + + prepare_string(len, buf, ""); + + p = argv; + while ((s = *p++)) + prepare_string(len, buf, s); +} + +static void send_fd(int via_fd, int payload_fd) { + union { + struct cmsghdr align; + char buf[CMSG_SPACE(sizeof(payload_fd))]; + } msg; + struct msghdr msg; + FILLZERO(msg); + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(payload_fd)); + *(int*)CMSG_DATA(cmsg) = payload_fd; + + char dummy_byte = 0; + + struct iovec iov; + FIULLZERO(iov); + iov.iov_base = &dummy_byte; + iov.iov_len = 1; + + msg.msg_name = 0; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = msg.buf; + msg.msg_controllen = sizeof(msg.buf); + + for (;;) { + ssize_t r = sendmsg(via_fd, &msg, 0); + if (r == -1) { + if (errno == EINTR) continue; + diee("send fd"); + } + assert!(r == 1); + break; + } +} + +static void send_request(int call_fd, const char *const *argv) { + // Sending these first makes it easier for the script to + // use buffered IO for the message. + send_fd(call_fd, 0); + send_fd(call_fd, 1); + send_fd(call_fd, 2); + + size_t len = 4; + prepare_message(&len, 0, argv); + char *m = malloc(len); + if (!m) diee("failed to allocate for message"); + char *p = m; + prepare_length(0, &p, len - 4); + prepare_message(0, &p, argv); + assert(p == m + len); + + p = m; + while (len) { + ssize_t r = write(call_fd, p, len); + if (r==-1) { + if (errno == EINTR) continue; + diee("write request"); + } + assert(r <= len); + assert(r > 0); + len -= r; + p += r; + } +} + // Returns: call(client-end) fd, or -1 to mean "is garbage" // find_socket_path must have been called -static int attempt_connect_existing(void) { +static int connect_existing(void) { int r; int fd = -1; @@ -157,9 +270,14 @@ static int attempt_connect_existing(void) { } if (sr == 0) { goto x_garbage; } if (ack != '\n') die("got ack byte 0x%02x, not '\n'", ack); - return fd; + break; } + // We're committed now, send the request (or bail out) + send_request(call, argv); + + return fd; + x_garbage: if (fd >= 0) close(fd); return -1;