| #include <poll.h> |
| #include <stdbool.h> |
| #include <termios.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <sys/uio.h> |
| #include <signal.h> |
| #include <pty.h> |
| #include <utmp.h> |
| |
| #include "kvm/read-write.h" |
| #include "kvm/term.h" |
| #include "kvm/util.h" |
| #include "kvm/kvm.h" |
| #include "kvm/kvm-cpu.h" |
| |
| #define TERM_FD_IN 0 |
| #define TERM_FD_OUT 1 |
| |
| static struct termios orig_term; |
| |
| static int term_fds[TERM_MAX_DEVS][2]; |
| |
| static pthread_t term_poll_thread; |
| |
| /* ctrl-a is used for escape */ |
| #define term_escape_char 0x01 |
| |
| int term_getc(struct kvm *kvm, int term) |
| { |
| static bool term_got_escape = false; |
| unsigned char c; |
| |
| if (read_in_full(term_fds[term][TERM_FD_IN], &c, 1) < 0) |
| return -1; |
| |
| if (term_got_escape) { |
| term_got_escape = false; |
| if (c == 'x') |
| kvm__reboot(kvm); |
| if (c == term_escape_char) |
| return c; |
| } |
| |
| if (c == term_escape_char) { |
| term_got_escape = true; |
| return -1; |
| } |
| |
| return c; |
| } |
| |
| int term_putc(char *addr, int cnt, int term) |
| { |
| static char buf[128]; |
| static int len = 0; |
| |
| for (int off = 0; off < cnt;) { |
| // Use at most the remaining space in buf. |
| int rem = sizeof(buf) - len; |
| |
| // Use at most `cnt` bytes. |
| if (cnt <= rem) { |
| rem = cnt; |
| } |
| |
| // Use at most characters leading up to a new-line. |
| char *brk = memchr(addr + off, '\n', rem - off); |
| if (brk != NULL) { |
| int brk_len = brk - (addr + off) + 1; |
| if (brk_len < rem) { |
| rem = brk_len; |
| } |
| } |
| |
| // Copy bytes to buffer. |
| memcpy(buf + len, addr + off, rem); |
| len += rem; |
| off += rem; |
| |
| // Print if buffer full or new-line encountered. |
| if (len == sizeof(buf)) { |
| fprintf(stderr, "%.*s", len, buf); |
| len = 0; |
| } else if (len > 0 && buf[len - 1] == '\n') { |
| fprintf(stderr, "%.*s", len - 1, buf); |
| len = 0; |
| } |
| } |
| |
| return cnt; |
| } |
| |
| int term_getc_iov(struct kvm *kvm, struct iovec *iov, int iovcnt, int term) |
| { |
| int c; |
| |
| c = term_getc(kvm, term); |
| |
| if (c < 0) |
| return 0; |
| |
| *((char *)iov[TERM_FD_IN].iov_base) = (char)c; |
| |
| return sizeof(char); |
| } |
| |
| int term_putc_iov(struct iovec *iov, int iovcnt, int term) |
| { |
| // static char buffer[1024]; |
| // static size_t used = 0; |
| |
| // for (int i = 0; i < iovcnt; ++i) { |
| // struct iovec *v = iov[i]; |
| // size_t transfer = sizeof(buffer) - used; |
| |
| // if (v->iov_len < transfer) { |
| // transfer = v->iov_len; |
| // } |
| // memcpy(buffer + used, v->iov_base, transfer); |
| // used += transfer; |
| |
| |
| // } |
| |
| int total = 0; |
| for (int i = 0; i < iovcnt; ++i) { |
| fprintf(stderr, "%.*s\n", (int)iov[i].iov_len, iov[i].iov_base); |
| total += iov[i].iov_len; |
| } |
| return total; |
| } |
| |
| bool term_readable(int term) |
| { |
| struct pollfd pollfd = (struct pollfd) { |
| .fd = term_fds[term][TERM_FD_IN], |
| .events = POLLIN, |
| .revents = 0, |
| }; |
| int err; |
| |
| err = poll(&pollfd, 1, 0); |
| return (err > 0 && (pollfd.revents & POLLIN)); |
| } |
| |
| static void *term_poll_thread_loop(void *param) |
| { |
| struct pollfd fds[TERM_MAX_DEVS]; |
| struct kvm *kvm = (struct kvm *) param; |
| int i; |
| |
| kvm__set_thread_name("term-poll"); |
| |
| for (i = 0; i < TERM_MAX_DEVS; i++) { |
| fds[i].fd = term_fds[i][TERM_FD_IN]; |
| fds[i].events = POLLIN; |
| fds[i].revents = 0; |
| } |
| |
| while (1) { |
| /* Poll with infinite timeout */ |
| if(poll(fds, TERM_MAX_DEVS, -1) < 1) |
| break; |
| kvm__arch_read_term(kvm); |
| } |
| |
| die("term_poll_thread_loop: error polling device fds %d\n", errno); |
| return NULL; |
| } |
| |
| static void term_cleanup(void) |
| { |
| int i; |
| |
| for (i = 0; i < TERM_MAX_DEVS; i++) |
| tcsetattr(term_fds[i][TERM_FD_IN], TCSANOW, &orig_term); |
| } |
| |
| static void term_sig_cleanup(int sig) |
| { |
| term_cleanup(); |
| signal(sig, SIG_DFL); |
| raise(sig); |
| } |
| |
| static void term_set_tty(int term) |
| { |
| struct termios orig_term; |
| int master, slave; |
| char new_pty[PATH_MAX]; |
| |
| if (tcgetattr(STDIN_FILENO, &orig_term) < 0) |
| die("unable to save initial standard input settings"); |
| |
| orig_term.c_lflag &= ~(ICANON | ECHO | ISIG); |
| |
| if (openpty(&master, &slave, new_pty, &orig_term, NULL) < 0) |
| return; |
| |
| close(slave); |
| |
| pr_info("Assigned terminal %d to pty %s\n", term, new_pty); |
| |
| term_fds[term][TERM_FD_IN] = term_fds[term][TERM_FD_OUT] = master; |
| } |
| |
| int tty_parser(const struct option *opt, const char *arg, int unset) |
| { |
| int tty = atoi(arg); |
| |
| term_set_tty(tty); |
| |
| return 0; |
| } |
| |
| static int term_init(struct kvm *kvm) |
| { |
| struct termios term; |
| int i, r; |
| |
| for (i = 0; i < TERM_MAX_DEVS; i++) |
| if (term_fds[i][TERM_FD_IN] == 0) { |
| term_fds[i][TERM_FD_IN] = STDIN_FILENO; |
| term_fds[i][TERM_FD_OUT] = STDOUT_FILENO; |
| } |
| |
| if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) |
| return 0; |
| |
| r = tcgetattr(STDIN_FILENO, &orig_term); |
| if (r < 0) { |
| pr_warning("unable to save initial standard input settings"); |
| return r; |
| } |
| |
| |
| term = orig_term; |
| term.c_iflag &= ~(ICRNL); |
| term.c_lflag &= ~(ICANON | ECHO | ISIG); |
| tcsetattr(STDIN_FILENO, TCSANOW, &term); |
| |
| |
| /* Use our own blocking thread to read stdin, don't require a tick */ |
| if(pthread_create(&term_poll_thread, NULL, term_poll_thread_loop,kvm)) |
| die("Unable to create console input poll thread\n"); |
| |
| signal(SIGTERM, term_sig_cleanup); |
| atexit(term_cleanup); |
| |
| return 0; |
| } |
| dev_init(term_init); |
| |
| static int term_exit(struct kvm *kvm) |
| { |
| return 0; |
| } |
| dev_exit(term_exit); |