blob: 984ae13ad3ee8251fc00a4b0e868c322b3a0e2ec [file] [log] [blame]
#include <err.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "console-server.h"
enum process_rc {
PROCESS_OK = 0,
PROCESS_ERR,
PROCESS_EXIT,
};
struct console_client {
int console_sd;
int fd_in;
int fd_out;
bool is_tty;
struct termios orig_termios;
int esc_str_pos;
bool newline;
};
static const uint8_t esc_str[] = { '~', '.' };
static enum process_rc process_tty(struct console_client *client)
{
uint8_t e, buf[4096];
long i;
ssize_t len;
int rc;
len = read(client->fd_in, buf, sizeof(buf));
if (len < 0)
return PROCESS_ERR;
if (len == 0)
return PROCESS_EXIT;
/* check escape sequence status */
for (i = 0; i < len; i++) {
/* the escape string is only valid after a newline */
if (buf[i] == '\r') {
client->newline = true;
continue;
}
if (!client->newline)
continue;
e = esc_str[client->esc_str_pos];
if (buf[i] == e) {
client->esc_str_pos++;
/* have we hit the end of the escape string? */
if (client->esc_str_pos == ARRAY_SIZE(esc_str)) {
/* flush out any data before the escape */
if (i > client->esc_str_pos)
write_buf_to_fd(client->console_sd,
buf,
i - client->esc_str_pos);
return PROCESS_EXIT;
}
} else {
/* if we're partially the way through the escape
* string, flush out the bytes we'd skipped */
if (client->esc_str_pos)
write_buf_to_fd(client->console_sd,
esc_str, client->esc_str_pos);
client->esc_str_pos = 0;
client->newline = false;
}
}
rc = write_buf_to_fd(client->console_sd, buf,
len - client->esc_str_pos);
if (rc < 0)
return PROCESS_ERR;
return PROCESS_OK;
}
static int process_console(struct console_client *client)
{
uint8_t buf[4096];
int len, rc;
len = read(client->console_sd, buf, sizeof(buf));
if (len < 0) {
warn("Can't read from server");
return PROCESS_ERR;
}
if (len == 0) {
fprintf(stderr, "Connection closed\n");
return PROCESS_EXIT;
}
rc = write_buf_to_fd(client->fd_out, buf, len);
return rc ? PROCESS_ERR : PROCESS_OK;
}
/*
* Setup our local file descriptors for IO: use stdin/stdout, and if we're on a
* TTY, put it in canonical mode
*/
static int client_tty_init(struct console_client *client)
{
struct termios termios;
int rc;
client->fd_in = STDIN_FILENO;
client->fd_out = STDOUT_FILENO;
client->is_tty = isatty(client->fd_in);
if (!client->is_tty)
return 0;
rc = tcgetattr(client->fd_in, &termios);
if (rc) {
warn("Can't get terminal attributes for console");
return -1;
}
memcpy(&client->orig_termios, &termios, sizeof(client->orig_termios));
cfmakeraw(&termios);
rc = tcsetattr(client->fd_in, TCSANOW, &termios);
if (rc) {
warn("Can't set terminal attributes for console");
return -1;
}
return 0;
}
static int client_init(struct console_client *client)
{
struct sockaddr_un addr;
int rc;
client->console_sd = socket(AF_UNIX, SOCK_STREAM, 0);
if (!client->console_sd) {
warn("Can't open socket");
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
memcpy(addr.sun_path, console_socket_path, console_socket_path_len);
rc = connect(client->console_sd, (struct sockaddr *)&addr,
sizeof(addr));
if (rc) {
warn("Can't connect to console server");
close(client->console_sd);
return -1;
}
return 0;
}
static void client_fini(struct console_client *client)
{
if (client->is_tty)
tcsetattr(client->fd_in, TCSANOW, &client->orig_termios);
close(client->console_sd);
}
int main(void)
{
struct console_client _client, *client;
struct pollfd pollfds[2];
enum process_rc prc;
int rc;
client = &_client;
memset(client, 0, sizeof(*client));
rc = client_init(client);
if (rc)
return EXIT_FAILURE;
rc = client_tty_init(client);
if (rc)
goto out_fini;
for (;;) {
pollfds[0].fd = client->fd_in;
pollfds[0].events = POLLIN;
pollfds[1].fd = client->console_sd;
pollfds[1].events = POLLIN;
rc = poll(pollfds, 2, -1);
if (rc < 0) {
warn("Poll failure");
break;
}
if (pollfds[0].revents)
prc = process_tty(client);
if (prc == PROCESS_OK && pollfds[1].revents)
prc = process_console(client);
rc = (prc == PROCESS_ERR) ? -1 : 0;
if (prc != PROCESS_OK)
break;
}
out_fini:
client_fini(client);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}