X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=prefork-interp.c;h=07e9fb0dd94eb4ee32cbac996b92fc27aff938fc;hb=1dcefda0059438029ce1cdf23e19c9fde0789640;hp=f37c8fdcb367f1b39993976109b9874b338a2f68;hpb=9032eb556eb7839d225bad23fb36e968ec94b2f1;p=chiark-utils.git 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;