diff --git a/abduco.c b/abduco.c index b56e6f4..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,14 +103,24 @@ 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; - Packet pty_output; int pty; 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; @@ -118,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, @@ -223,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); } @@ -606,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': @@ -636,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); }