/** * Copyright © 2016 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include "console-server.h" #include "config.h" struct tty_handler { struct handler handler; struct console *console; struct ringbuffer_consumer *rbc; struct poller *poller; int fd; int fd_flags; bool blocked; }; static struct tty_handler *to_tty_handler(struct handler *handler) { return container_of(handler, struct tty_handler, handler); } static void tty_set_fd_blocking(struct tty_handler *th, bool fd_blocking) { int flags; flags = th->fd_flags & ~O_NONBLOCK; if (!fd_blocking) { flags |= O_NONBLOCK; } if (flags != th->fd_flags) { fcntl(th->fd, F_SETFL, flags); th->fd_flags = flags; } } /* * A "blocked" handler indicates that the last write returned EAGAIN * (==EWOULDBLOCK), so we know not to continue writing (for non-forced output), * as it'll just return EAGAIN again. * * Once we detect this, we watch for POLLOUT in the poller events. A * POLLOUT indicates that the fd is no longer blocking, so we clear * blocked mode and can continue writing. */ static void tty_set_blocked(struct tty_handler *th, bool blocked) { int events; if (blocked == th->blocked) { return; } th->blocked = blocked; events = POLLIN; if (th->blocked) { events |= POLLOUT; } console_poller_set_events(th->console, th->poller, events); } static int tty_drain_queue(struct tty_handler *th, size_t force_len) { size_t len; size_t total_len; ssize_t wlen; uint8_t *buf; /* if we're forcing data, we need to clear non-blocking mode */ if (force_len) { tty_set_fd_blocking(th, true); /* no point writing, we'll just see -EAGAIN */ } else if (th->blocked) { return 0; } total_len = 0; for (;;) { len = ringbuffer_dequeue_peek(th->rbc, total_len, &buf); if (!len) { break; } /* write as little as possible while blocking */ if (force_len && force_len < total_len + len) { len = force_len - total_len; } wlen = write(th->fd, buf, len); if (wlen < 0) { if (errno == EINTR) { continue; } if ((errno == EAGAIN || errno == EWOULDBLOCK) && !force_len) { tty_set_blocked(th, true); break; } warn("failed writing to local tty; disabling"); return -1; } total_len += wlen; if (force_len && total_len >= force_len) { break; } } ringbuffer_dequeue_commit(th->rbc, total_len); if (force_len) { tty_set_fd_blocking(th, false); } return 0; } static enum ringbuffer_poll_ret tty_ringbuffer_poll(void *arg, size_t force_len) { struct tty_handler *th = arg; int rc; rc = tty_drain_queue(th, force_len); if (rc) { console_poller_unregister(th->console, th->poller); return RINGBUFFER_POLL_REMOVE; } return RINGBUFFER_POLL_OK; } static enum poller_ret tty_poll(struct handler *handler, int events, void __attribute__((unused)) * data) { struct tty_handler *th = to_tty_handler(handler); uint8_t buf[4096]; ssize_t len; int rc; if (events & POLLIN) { len = read(th->fd, buf, sizeof(buf)); if (len <= 0) { goto err; } console_data_out(th->console, buf, len); } if (events & POLLOUT) { tty_set_blocked(th, false); rc = tty_drain_queue(th, 0); if (rc) { goto err; } } return POLLER_OK; err: th->poller = NULL; close(th->fd); ringbuffer_consumer_unregister(th->rbc); return POLLER_REMOVE; } static int set_terminal_baud(struct tty_handler *th, const char *tty_name, speed_t speed) { struct termios term_options; if (tcgetattr(th->fd, &term_options) < 0) { warn("Can't get config for %s", tty_name); return -1; } if (cfsetspeed(&term_options, speed) < 0) { warn("Couldn't set speeds for %s", tty_name); return -1; } if (tcsetattr(th->fd, TCSAFLUSH, &term_options) < 0) { warn("Couldn't commit terminal options for %s", tty_name); return -1; } return 0; } static int make_terminal_raw(struct tty_handler *th, const char *tty_name) { struct termios term_options; if (tcgetattr(th->fd, &term_options) < 0) { warn("Can't get config for %s", tty_name); return -1; } /* Disable various input and output processing including character * translation, line edit (canonical) mode, flow control, and special signal * generating characters. */ cfmakeraw(&term_options); if (tcsetattr(th->fd, TCSAFLUSH, &term_options) < 0) { warn("Couldn't commit terminal options for %s", tty_name); return -1; } printf("Set %s for raw byte handling\n", tty_name); return 0; } static struct handler *tty_init(const struct handler_type *type __attribute__((unused)), struct console *console, struct config *config __attribute__((unused))) { struct tty_handler *th; speed_t desired_speed; const char *tty_name; const char *tty_baud; char *tty_path; int rc; tty_name = config_get_value(config, "local-tty"); if (!tty_name) { return NULL; } rc = asprintf(&tty_path, "/dev/%s", tty_name); if (!rc) { return NULL; } th = malloc(sizeof(*th)); if (!th) { return NULL; } th->fd = open(tty_path, O_RDWR | O_NONBLOCK); if (th->fd < 0) { warn("Can't open %s; disabling local tty", tty_name); free(tty_path); free(th); return NULL; } free(tty_path); th->fd_flags = fcntl(th->fd, F_GETFL, 0); tty_baud = config_get_value(config, "local-tty-baud"); if (tty_baud != NULL) { rc = config_parse_baud(&desired_speed, tty_baud); if (rc) { fprintf(stderr, "%s is not a valid baud rate\n", tty_baud); } else { rc = set_terminal_baud(th, tty_name, desired_speed); if (rc) { fprintf(stderr, "Couldn't set baud rate for %s to %s\n", tty_name, tty_baud); } } } if (make_terminal_raw(th, tty_name) != 0) { fprintf(stderr, "Couldn't make %s a raw terminal\n", tty_name); } th->poller = console_poller_register(console, &th->handler, tty_poll, NULL, th->fd, POLLIN, NULL); th->console = console; th->rbc = console_ringbuffer_consumer_register(console, tty_ringbuffer_poll, th); return &th->handler; } static void tty_fini(struct handler *handler) { struct tty_handler *th = to_tty_handler(handler); if (th->poller) { console_poller_unregister(th->console, th->poller); } close(th->fd); free(th); } static int tty_baudrate(struct handler *handler, speed_t baudrate) { const char *tty_name = "local-tty"; struct tty_handler *th = to_tty_handler(handler); if (baudrate == 0) { return -1; } if (set_terminal_baud(th, tty_name, baudrate) != 0) { fprintf(stderr, "Couldn't set baud rate for %s to %d\n", tty_name, baudrate); return -1; } return 0; } static const struct handler_type tty_handler = { .name = "tty", .init = tty_init, .fini = tty_fini, .baudrate = tty_baudrate, }; console_handler_register(&tty_handler);