blob: 005af60e738967cfe1a8711d15a5e85059a9f8a3 [file] [log] [blame]
/**
* Console server process for OpenBMC
*
* Copyright © 2016 IBM Corporation <jk@ozlabs.org>
*/
#define _GNU_SOURCE
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <termios.h>
#include <string.h>
#include <getopt.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/poll.h>
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
static const char esc_str[] = { '\r', '~', '.' };
struct console_ctx {
const char *tty_kname;
char *tty_sysfs_devnode;
char *tty_dev;
int tty_fd;
int console_fd_in;
int console_fd_out;
bool console_is_tty;
struct termios orig_termios;
int esc_str_pos;
};
static void usage(const char *progname)
{
fprintf(stderr,
"usage: %s [options]\n"
"\n"
"Options:\n"
" --device <TTY> Use serial device TTY (eg, ttyS0)\n"
"",
progname);
}
/* populates tty_dev and tty_sysfs_devnode, using the tty kernel name */
static int tty_find_device(struct console_ctx *ctx)
{
char *tty_class_device_link;
char *tty_device_tty_dir;
char *tty_device_reldir;
int rc;
rc = -1;
tty_class_device_link = NULL;
tty_device_tty_dir = NULL;
tty_device_reldir = NULL;
rc = asprintf(&tty_class_device_link,
"/sys/class/tty/%s", ctx->tty_kname);
if (rc < 0)
return -1;
tty_device_tty_dir = realpath(tty_class_device_link, NULL);
if (rc < 0) {
warn("Can't query sysfs for device %s", ctx->tty_kname);
goto out_free;
}
rc = asprintf(&tty_device_reldir, "%s/../../", tty_device_tty_dir);
if (rc < 0)
goto out_free;
ctx->tty_sysfs_devnode = realpath(tty_device_reldir, NULL);
if (!ctx->tty_sysfs_devnode)
warn("Can't find parent device for %s", ctx->tty_kname);
/* todo: lookup from major/minor info in sysfs, in case udev has
* renamed us */
rc = asprintf(&ctx->tty_dev, "/dev/%s", ctx->tty_kname);
if (rc < 0)
goto out_free;
rc = 0;
out_free:
free(tty_class_device_link);
free(tty_device_tty_dir);
free(tty_device_reldir);
return rc;
}
/**
* Open and initialise the serial device
*/
static int tty_init_io(struct console_ctx *ctx)
{
ctx->tty_fd = open(ctx->tty_dev, O_RDWR);
if (ctx->tty_fd <= 0) {
warn("Can't open tty %s", ctx->tty_dev);
return -1;
}
/* Disable character delay. We may want to later enable this when
* we detect larger amounts of data
*/
fcntl(ctx->tty_fd, F_SETFL, FNDELAY);
return 0;
}
/*
* Setup our console channel for IO: use stdin/stdout, and if we're on a TTY,
* put it in canonical mode
*/
static int console_init_io(struct console_ctx *ctx)
{
struct termios termios;
int rc;
ctx->console_fd_in = STDIN_FILENO;
ctx->console_fd_out = STDOUT_FILENO;
ctx->console_is_tty = isatty(ctx->console_fd_in);
if (!ctx->console_is_tty)
return 0;
rc = tcgetattr(ctx->console_fd_in, &termios);
if (rc) {
warn("Can't get terminal attributes for console");
return -1;
}
memcpy(&ctx->orig_termios, &termios, sizeof(ctx->orig_termios));
cfmakeraw(&termios);
rc = tcsetattr(ctx->console_fd_in, TCSANOW, &termios);
if (rc) {
warn("Can't set terminal attributes for console");
return -1;
}
return 0;
}
static int console_process_input(struct console_ctx *ctx,
uint8_t *buf, size_t len)
{
unsigned long i;
uint8_t e;
e = esc_str[ctx->esc_str_pos];
for (i = 0; i < len; i++) {
if (buf[i] == e) {
ctx->esc_str_pos++;
if (ctx->esc_str_pos == ARRAY_SIZE(esc_str))
return 1;
e = esc_str[ctx->esc_str_pos];
} else {
ctx->esc_str_pos = 0;
}
}
return 0;
}
static void console_restore_termios(struct console_ctx *ctx)
{
if (ctx->console_is_tty)
tcsetattr(ctx->console_fd_in, TCSANOW, &ctx->orig_termios);
}
static int write_buf_to_fd(int fd, uint8_t *buf, size_t len)
{
size_t pos;
ssize_t rc;
for (pos = 0; pos < len; pos += rc) {
rc = write(fd, buf + pos, len - pos);
if (rc <= 0) {
warn("Write error");
return -1;
}
}
return 0;
}
int run_console(struct console_ctx *ctx)
{
struct pollfd pollfds[2];
int rc, len;
pollfds[0].fd = ctx->tty_fd;
pollfds[0].events = POLLIN;
pollfds[1].fd = ctx->console_fd_in;
pollfds[1].events = POLLIN;
for (;;) {
uint8_t buf[4096];
rc = poll(pollfds, 2, -1);
if (rc < 0) {
warn("poll error");
return -1;
}
if (pollfds[0].revents) {
rc = read(ctx->tty_fd, buf, sizeof(buf));
if (rc <= 0) {
warn("Error reading from tty device");
return -1;
}
rc = write_buf_to_fd(ctx->console_fd_out, buf, rc);
if (rc < 0)
return -1;
}
if (pollfds[1].revents) {
rc = read(ctx->console_fd_in, buf, sizeof(buf));
if (rc == 0)
return 0;
if (rc <= 0) {
warn("Error reading from console");
return -1;
}
len = rc;
rc = console_process_input(ctx, buf, len);
if (rc) {
rc = 0;
return 0;
}
rc = write_buf_to_fd(ctx->tty_fd, buf, len);
if (rc < 0)
return -1;
}
}
}
static const struct option options[] = {
{ "device", required_argument, 0, 'd'},
{ },
};
int main(int argc, char **argv)
{
struct console_ctx *ctx;
int rc;
ctx = malloc(sizeof(struct console_ctx));
memset(ctx, 0, sizeof(*ctx));
for (;;) {
int c, idx;
c = getopt_long(argc, argv, "d", options, &idx);
if (c == -1)
break;
switch (c) {
case 'd':
ctx->tty_kname = optarg;
break;
case 'h':
case '?':
usage(argv[0]);
break;
}
}
if (!ctx->tty_kname) {
fprintf(stderr,
"Error: No TTY device specified (use --device)\n");
return EXIT_FAILURE;
}
rc = tty_find_device(ctx);
if (rc)
return EXIT_FAILURE;
return EXIT_SUCCESS;
rc = tty_init_io(ctx);
if (rc)
return EXIT_FAILURE;
rc = console_init_io(ctx);
if (rc)
return EXIT_FAILURE;
rc = run_console(ctx);
console_restore_termios(ctx);
free(ctx->tty_sysfs_devnode);
free(ctx->tty_dev);
free(ctx);
return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}