_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 {
$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;
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;
*
* [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
* 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
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;
}
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;