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 <gladkov.alexey@gmail.com>
This commit is contained in:
Alexey Gladkov 2021-11-30 16:03:08 +01:00
parent b5598d5dd1
commit a9b345a24f
2 changed files with 129 additions and 4 deletions

View File

@ -37,6 +37,7 @@
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/queue.h>
#if defined(__linux__) || defined(__CYGWIN__)
# include <pty.h>
#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);

105
server.c
View File

@ -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);
}