From 248ad987d3a605243b67be3761b6cbe0bfbb50ae Mon Sep 17 00:00:00 2001 From: Scott Mcdermott Date: Mon, 19 Aug 2024 04:56:54 -0700 Subject: [PATCH] communications: make control socket writes recover from interruption Both send_message() and receive_message() use writes: the former to send to the latter, and then a reply is sent back from the latter to the former. Both of them expected a single write to work unconditionally without being able to tolerate any transient issues. This commit factors out the writes into a common function called send_unix() which serves as the companion to the recently added recv_unix(). The send call is modified to handle both partial writes and interrupted writes. Especially the latter is not that unlikely: it can happen when we're in the middle of writing a message and then we get interrupted by a SIGCHLD. This is not a difficult thing to happen during the ordinary course of operations because the native 'exec' is asynchronous. It was observed to happen during testing. Partial writes might also be possible if some of the buffer was sent and then got interrupted by a signal, but this probably can't happen at the small BUFSZ we use. Nonetheless it will make the code more robust to support the possibility of partial writes. Any error besides EINTR is considered a fatal error for send_message() because this is only called by a "client" invocation, ie "sdorfehs -c". The "server" (ie receve_message()) only issues a warning and then aborts processing the signal. Otherwise, it would die. This matches the existing use of err() and warn() that was used after a mismatch in written and wrote buffer sizes in the respective writers, prior to this patch. --- communications.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/communications.c b/communications.c index 9bb61ff..e13b116 100644 --- a/communications.c +++ b/communications.c @@ -159,6 +159,28 @@ recv_unix(int fd, char **callerbuf) return len; } +static ssize_t +send_unix(int fd, char *p, ssize_t sz) +{ + ssize_t ret, off = 0; + + WARNX_DEBUG("entered send_unix with sz %zd\n", sz); + + while (sz > 0) { + if (((ret = write(fd, p + off, sz)) != sz) && ret == -1) { + if (errno == EINTR) + continue; + warn("send_unix: bad write"); + break; + } + sz -= ret; + off += ret; + } + + WARNX_DEBUG("leaving send_unix, off %zd, errno %d\n", off, errno); + return off; +} + int send_command(int interactive, char *cmd) { @@ -196,8 +218,8 @@ send_command(int interactive, char *cmd) err(1, "failed to connect to control socket at %s", rp_glob_screen.control_socket_path); - if (write(fd, wcmd, len) != len) - err(1, "short write to control socket"); + if (send_unix(fd, wcmd, len) != len) + err(1, "%s: aborting after bad write", __func__); free(wcmd); @@ -275,8 +297,8 @@ receive_command(void) PRINT_DEBUG(("writing back %d to command client: %s", len, result + 1)); - if (write(cl, result, len) != len) - warn("%s: short write", __func__); + if (send_unix(cl, result, len) != len) + warnx("%s: proceeding after bad write", __func__); PRINT_DEBUG(("receive_command: write finished, closing\n")); done: