From b5598d5dd18c1ba57ae097f66d0939c5e10ccd6c Mon Sep 17 00:00:00 2001 From: Alexey Gladkov Date: Tue, 30 Nov 2021 12:35:27 +0100 Subject: [PATCH 1/2] Remove unused variable Fixes: a99e46f ("Simplify I/O handling") Signed-off-by: Alexey Gladkov --- abduco.c | 1 - 1 file changed, 1 deletion(-) diff --git a/abduco.c b/abduco.c index b56e6f4..1336e53 100644 --- a/abduco.c +++ b/abduco.c @@ -105,7 +105,6 @@ struct Client { typedef struct { Client *clients; int socket; - Packet pty_output; int pty; int exit_status; struct termios term; From a9b345a24f2b49ed2b6c7f2f61a8514fae2f31be Mon Sep 17 00:00:00 2001 From: Alexey Gladkov Date: Tue, 30 Nov 2021 16:03:08 +0100 Subject: [PATCH 2/2] Add screen buffer to server When a client attaches to an existing session, the session command output is not available to him. The problem is not noticeable when the program (top, man, etc.) in the session is able to redraw its output. But this does not work with non-interactive utilities. The output of commands in bash will not be available to the client connecting to the session. To solve this, we add a buffer that contains the last few lines of the session output. When a new client attaches, the contents of this buffer are sent to him. Signed-off-by: Alexey Gladkov --- abduco.c | 28 +++++++++++++-- server.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 4 deletions(-) diff --git a/abduco.c b/abduco.c index 1336e53..c224129 100644 --- a/abduco.c +++ b/abduco.c @@ -37,6 +37,7 @@ #include #include #include +#include #if defined(__linux__) || defined(__CYGWIN__) # include #elif defined(__FreeBSD__) || defined(__DragonFly__) @@ -102,6 +103,15 @@ struct Client { Client *next; }; +struct entry { + char *data; + int len; + bool complete; + TAILQ_ENTRY(entry) entries; +}; + +TAILQ_HEAD(screenhead, entry); + typedef struct { Client *clients; int socket; @@ -109,6 +119,8 @@ typedef struct { int exit_status; struct termios term; struct winsize winsize; + struct screenhead screen; + int screen_rows; pid_t pid; volatile sig_atomic_t running; const char *name; @@ -117,10 +129,11 @@ typedef struct { bool read_pty; } Server; -static Server server = { .running = true, .exit_status = -1, .host = "@localhost" }; +static Server server = { .running = true, .exit_status = -1, .host = "@localhost", .screen_rows = 0 }; static Client client; static struct termios orig_term, cur_term; static bool has_term, alternate_buffer, quiet, passthrough; +static int screen_max_rows = 120; static struct sockaddr_un sockaddr = { .sun_family = AF_UNIX, @@ -222,7 +235,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] [-L num] name command\n"); exit(EXIT_FAILURE); } @@ -605,7 +618,7 @@ int main(int argc, char *argv[]) { server.name = basename(argv[0]); gethostname(server.host+1, sizeof(server.host) - 1); - while ((opt = getopt(argc, argv, "aAclne:fpqrv")) != -1) { + while ((opt = getopt(argc, argv, "aAclne:fpqrvL:")) != -1) { switch (opt) { case 'a': case 'A': @@ -635,6 +648,15 @@ int main(int argc, char *argv[]) { case 'l': client.flags |= CLIENT_LOWPRIORITY; break; + case 'L': + if (!optarg) + usage(); + screen_max_rows = atoi(optarg); + if (screen_max_rows < 0) { + fputs("ERROR: a negative value for the number of rows is meaningless.\n", stderr); + usage(); + } + break; case 'v': puts("abduco-"VERSION" © 2013-2018 Marc André Tanner"); exit(EXIT_SUCCESS); diff --git a/server.c b/server.c index e57773a..a661ab1 100644 --- a/server.c +++ b/server.c @@ -178,6 +178,93 @@ static void server_atexit_handler(void) { unlink(sockaddr.sun_path); } +static void server_send_screen_buffer(Client *c) { + struct entry *np; + + TAILQ_FOREACH_REVERSE(np, &server.screen, screenhead, entries) { + Packet pkt = { + .type = MSG_CONTENT, + .len = np->len, + }; + strncpy(pkt.u.msg, np->data, np->len); + server_send_packet(c, &pkt); + } +} + +static void server_preserve_screen_data(Packet *pkt) { + char *str, *end; + uint32_t len; + struct entry *scrline = NULL; + + if (screen_max_rows == 0 || pkt->len <= 0 || pkt->type != MSG_CONTENT) + return; + + str = pkt->u.msg; + len = pkt->len; + end = str + len; + + while (str != end) { + char *data; + uint32_t i, dlen; + + bool newline = false; + char *token = end; + + for (i = 0; i < len; i++) { + if (str[i] == '\n') { + token = str + i + 1; + newline = true; + break; + } + } + + if ((dlen = token - str) <= 0) + break; + + scrline = TAILQ_FIRST(&server.screen); + + if (scrline && !scrline->complete) { + data = realloc(scrline->data, scrline->len + dlen); + if (!data) + die("unable to extend string in the screen buffer"); + + memcpy(data + scrline->len, str, dlen); + + scrline->complete = newline; + scrline->data = data; + scrline->len += dlen; + } else { + data = malloc(dlen); + if (!data) + die("unable to allocate memory for new line in the screen buffer"); + + memcpy(data, str, dlen); + + scrline = malloc(sizeof(*scrline)); + if (!scrline) + die("unable to allocate memory for screen buffer element"); + + scrline->complete = newline; + scrline->data = data; + scrline->len = dlen; + + TAILQ_INSERT_HEAD(&server.screen, scrline, entries); + server.screen_rows++; + + if (server.screen_rows > screen_max_rows) { + scrline = TAILQ_LAST(&server.screen, screenhead); + TAILQ_REMOVE(&server.screen, scrline, entries); + free(scrline->data); + free(scrline); + server.screen_rows--; + } + } + + str = token; + len -= dlen; + } +} + static void server_mainloop(void) { atexit(server_atexit_handler); fd_set new_readfds, new_writefds; @@ -187,6 +274,8 @@ static void server_mainloop(void) { int new_fdmax = server.socket; bool exit_packet_delivered = false; + TAILQ_INIT(&server.screen); + if (server.read_pty) FD_SET_MAX(server.pty, &new_readfds, new_fdmax); @@ -213,8 +302,11 @@ static void server_mainloop(void) { if (FD_ISSET(server.socket, &readfds)) server_accept_client(); - if (FD_ISSET(server.pty, &readfds)) + if (FD_ISSET(server.pty, &readfds)) { pty_data = server_read_pty(&server_packet); + if (pty_data) + server_preserve_screen_data(&server_packet); + } for (Client **prev_next = &server.clients, *c = server.clients; c;) { if (FD_ISSET(c->socket, &readfds) && server_recv_packet(c, &client_packet)) { @@ -226,6 +318,7 @@ static void server_mainloop(void) { c->flags = client_packet.u.i; if (c->flags & CLIENT_LOWPRIORITY) server_sink_client(); + server_send_screen_buffer(c); break; case MSG_RESIZE: c->state = STATE_ATTACHED; @@ -291,5 +384,15 @@ static void server_mainloop(void) { FD_SET_MAX(server.pty, &new_readfds, new_fdmax); } + struct entry *n1, *n2; + + n1 = TAILQ_FIRST(&server.screen); + while (n1 != NULL) { + n2 = TAILQ_NEXT(n1, entries); + free(n1->data); + free(n1); + n1 = n2; + } + exit(EXIT_SUCCESS); }