From 185eb4bf0dc5d911cbcddff259783b49c3e34e03 Mon Sep 17 00:00:00 2001 From: "Leon M. Busch-George" Date: Sun, 1 Mar 2026 11:39:20 +0100 Subject: [PATCH] support generic scrollbuffer implementations --- abduco.1 | 4 ++++ abduco.c | 18 ++++++++++++++++-- server.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/abduco.1 b/abduco.1 index c122308..81e8307 100644 --- a/abduco.1 +++ b/abduco.1 @@ -102,6 +102,10 @@ which is specified as ^\\ i.e. Ctrl is represented as a caret .It Fl f Force creation of session when there is an already terminated session of the same name, after showing its exit status. +.It Fl s Ar scrollback_fd_socket +A unix socket to send the writing end of a pipe through. This can be used for receiving the scrollback +buffer from when a new client attaches. Wait for the accepted socket to be closed before resuming +regular pass-through behavior. .It Fl l Attach with the lowest priority, meaning this client will be the last to control the size. .It Fl p diff --git a/abduco.c b/abduco.c index b56e6f4..417b246 100644 --- a/abduco.c +++ b/abduco.c @@ -116,6 +116,7 @@ typedef struct { const char *session_name; char host[255]; bool read_pty; + char scrollback_sock[PATH_MAX]; } Server; static Server server = { .running = true, .exit_status = -1, .host = "@localhost" }; @@ -223,7 +224,7 @@ static void die(const char *s) { } static void usage(void) { - fprintf(stderr, "usage: abduco [-a|-A|-c|-n] [-p] [-r] [-q] [-l] [-f] [-e detachkey] name command\n"); + fprintf(stderr, "usage: abduco [-a|-A|-c|-n] [-p] [-r] [-q] [-l] [-f] [-e detachkey] [-s scrollback-sock] name command\n"); exit(EXIT_FAILURE); } @@ -604,9 +605,10 @@ int main(int argc, char *argv[]) { } server.name = basename(argv[0]); + server.scrollback_sock[0] = '\0'; gethostname(server.host+1, sizeof(server.host) - 1); - while ((opt = getopt(argc, argv, "aAclne:fpqrv")) != -1) { + while ((opt = getopt(argc, argv, "aAclne:fpqrs:v")) != -1) { switch (opt) { case 'a': case 'A': @@ -633,6 +635,18 @@ int main(int argc, char *argv[]) { case 'r': client.flags |= CLIENT_READONLY; break; + case 's': + if (!realpath(".", server.scrollback_sock)) { + fprintf(stderr, "can't determine current directory: %s\n", strerror(errno)); + return 2; + } + if (strlen(server.scrollback_sock) + strlen(optarg) + 2 > PATH_MAX) { + fprintf(stderr, "scrollback_sock path too long\n"); + return 2; + } + strcat(server.scrollback_sock, "/"); + strcat(server.scrollback_sock, optarg); + break; case 'l': client.flags |= CLIENT_LOWPRIORITY; break; diff --git a/server.c b/server.c index e57773a..449f1af 100644 --- a/server.c +++ b/server.c @@ -136,6 +136,62 @@ static void server_sigterm_handler(int sig) { exit(EXIT_FAILURE); /* invoke atexit handler */ } +static void server_print_scrollback(const char *scrollback_sock, Client *c) { + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + debug("failed to open unix socket: %s\n", strerror(errno)); + return; + } + + struct sockaddr_un addr = { .sun_family = AF_UNIX }; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", scrollback_sock); + if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + debug("failed to connect to %s: %s\n", scrollback_sock, strerror(errno)); + goto cleanup; + } + + char buf[1]; + + struct iovec iov = { .iov_base = &buf, .iov_len = sizeof(buf) }; + char cmsg_buf[CMSG_SPACE(sizeof(int))]; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsg_buf, + .msg_controllen = sizeof(cmsg_buf) + }; + + int scrlb_fd = -1; + + if (recvmsg(sock, &msg, 0) == -1) + goto cleanup; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg->cmsg_level != SOL_SOCKET + || cmsg->cmsg_type != SCM_RIGHTS + || cmsg->cmsg_len != CMSG_LEN(sizeof(int))) + goto cleanup; + + memcpy(&scrlb_fd, CMSG_DATA(cmsg), sizeof(int)); + + Packet pkt = { .type = MSG_CONTENT }; + + ssize_t rlen; + while ((rlen = read(scrlb_fd, pkt.u.msg, sizeof(pkt.u.msg))) > 0) { + pkt.len = rlen; + server_send_packet(c, &pkt); + } + + // no further data is handled by the scrollback buffer until the unix + // socket is closed. this is the time to attach the client to receive + // any future data. since this code is blocking the server loop, we're + // done. + close(scrlb_fd); + +cleanup: + close(sock); +} + static Client *server_accept_client(void) { int newfd = accept(server.socket, NULL, NULL); if (newfd == -1 || server_set_socket_non_blocking(newfd) == -1) @@ -157,6 +213,8 @@ static Client *server_accept_client(void) { .u.l = getpid(), }; server_send_packet(c, &pkt); + if (server.scrollback_sock[0]) + server_print_scrollback(server.scrollback_sock, c); return c; error: