summaryrefslogtreecommitdiff
path: root/usr/src/cmd/bhyveconsole/bhyveconsole.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/bhyveconsole/bhyveconsole.c')
-rw-r--r--usr/src/cmd/bhyveconsole/bhyveconsole.c360
1 files changed, 360 insertions, 0 deletions
diff --git a/usr/src/cmd/bhyveconsole/bhyveconsole.c b/usr/src/cmd/bhyveconsole/bhyveconsole.c
new file mode 100644
index 0000000000..7f237a72f6
--- /dev/null
+++ b/usr/src/cmd/bhyveconsole/bhyveconsole.c
@@ -0,0 +1,360 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2013 Pluribus Networks Inc.
+ */
+
+#include <sys/param.h>
+#include <sys/signal.h>
+#include <sys/socket.h>
+#include <sys/termios.h>
+#include <assert.h>
+#include <errno.h>
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <bhyve.h>
+
+static int masterfd;
+static struct termios save_termios;
+static int save_fd;
+
+static int nocmdchar = 0;
+static char cmdchar = '~';
+
+static const char *pname;
+
+#define BCONS_BUFSIZ 8192
+
+static void
+usage(void)
+{
+ (void) fprintf(stderr, "usage: %s vmname\n", pname);
+ exit(2);
+}
+
+static void
+bcons_error(const char *fmt, ...)
+{
+ va_list alist;
+
+ (void) fprintf(stderr, "%s: ", pname);
+ va_start(alist, fmt);
+ (void) vfprintf(stderr, fmt, alist);
+ va_end(alist);
+ (void) fprintf(stderr, "\n");
+}
+
+static void
+bcons_perror(const char *str)
+{
+ const char *estr;
+
+ if ((estr = strerror(errno)) != NULL)
+ (void) fprintf(stderr, "%s: %s: %s\n", pname, str, estr);
+ else
+ (void) fprintf(stderr, "%s: %s: errno %d\n", pname, str, errno);
+}
+
+/*
+ * Create the unix domain socket and call bhyve; handshake
+ * with it to determine whether it will allow us to connect.
+ */
+static int
+get_console(const char *vmname)
+{
+ int sockfd = -1;
+ struct sockaddr_un servaddr;
+ char clientid[MAXPATHLEN];
+ char handshake[MAXPATHLEN], c;
+ int msglen;
+ int i = 0, err = 0;
+
+ if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+ bcons_perror("could not create socket");
+ return (-1);
+ }
+
+ bzero(&servaddr, sizeof (servaddr));
+ servaddr.sun_family = AF_UNIX;
+ (void) snprintf(servaddr.sun_path, sizeof (servaddr.sun_path),
+ BHYVE_CONS_SOCKPATH, vmname);
+
+ if (connect(sockfd, (struct sockaddr *)&servaddr,
+ sizeof (servaddr)) == -1) {
+ bcons_perror("Could not connect to console server");
+ goto bad;
+ }
+ masterfd = sockfd;
+
+ msglen = snprintf(clientid, sizeof (clientid), "IDENT %lu\n",
+ getpid());
+ assert(msglen > 0 && msglen < sizeof (clientid));
+
+ if (write(masterfd, clientid, msglen) != msglen) {
+ bcons_error("protocol error");
+ goto bad;
+ }
+
+ /*
+ * Take care not to accumulate more than our fill, and leave room for
+ * the NUL at the end.
+ */
+ while ((err = read(masterfd, &c, 1)) == 1) {
+ if (i >= (sizeof (handshake) - 1))
+ break;
+ if (c == '\n')
+ break;
+ handshake[i] = c;
+ i++;
+ }
+ handshake[i] = '\0';
+
+ /*
+ * If something went wrong during the handshake we bail; perhaps
+ * the server died off.
+ */
+ if (err == -1) {
+ bcons_perror("Could not connect to console server");
+ goto bad;
+ }
+
+ if (strncmp(handshake, "OK", sizeof (handshake)) == 0)
+ return (0);
+
+ bcons_error("Console is already in use by process ID %s.",
+ handshake);
+bad:
+ (void) close(sockfd);
+ masterfd = -1;
+ return (-1);
+}
+
+/*
+ * Place terminal into raw mode.
+ */
+static int
+set_tty_rawmode(int fd)
+{
+ struct termios term;
+ if (tcgetattr(fd, &term) < 0) {
+ bcons_perror("failed to get user terminal settings");
+ return (-1);
+ }
+
+ /* Stash for later, so we can revert back to previous mode */
+ save_termios = term;
+ save_fd = fd;
+
+ /* disable 8->7 bit strip, start/stop, enable any char to restart */
+ term.c_iflag &= ~(ISTRIP|IXON|IXANY);
+ /* disable NL->CR, CR->NL, ignore CR, UPPER->lower */
+ term.c_iflag &= ~(INLCR|ICRNL|IGNCR|IUCLC);
+ /* disable output post-processing */
+ term.c_oflag &= ~OPOST;
+ /* disable canonical mode, signal chars, echo & extended functions */
+ term.c_lflag &= ~(ICANON|ISIG|ECHO|IEXTEN);
+
+ term.c_cc[VMIN] = 1; /* byte-at-a-time */
+ term.c_cc[VTIME] = 0;
+
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &term)) {
+ bcons_perror("failed to set user terminal to raw mode");
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * reset terminal settings for global environment
+ */
+static void
+reset_tty(void)
+{
+ (void) tcsetattr(save_fd, TCSADRAIN, &save_termios);
+}
+
+/*
+ * process_user_input watches the input stream for the escape sequence for
+ * 'quit' (by default, tilde-period). Because we might be fed just one
+ * keystroke at a time, state associated with the user input (are we at the
+ * beginning of the line? are we locally echoing the next character?) is
+ * maintained by beginning_of_line and local_echo across calls to the routine.
+ *
+ * This routine returns -1 when the 'quit' escape sequence has been issued,
+ * or an error is encountered and 0 otherwise.
+ */
+static int
+process_user_input(int out_fd, int in_fd)
+{
+ static boolean_t beginning_of_line = B_TRUE;
+ static boolean_t local_echo = B_FALSE;
+ char ibuf[BCONS_BUFSIZ];
+ int nbytes;
+ char *buf = ibuf;
+ char c;
+
+ nbytes = read(in_fd, ibuf, sizeof (ibuf));
+ if (nbytes == -1 && errno != EINTR)
+ return (-1);
+
+ if (nbytes == -1) /* The read was interrupted. */
+ return (0);
+
+ for (c = *buf; nbytes > 0; c = *buf, --nbytes) {
+ buf++;
+ if (beginning_of_line && !nocmdchar) {
+ beginning_of_line = B_FALSE;
+ if (c == cmdchar) {
+ local_echo = B_TRUE;
+ continue;
+ }
+ } else if (local_echo) {
+ local_echo = B_FALSE;
+ if (c == '.') {
+ (void) write(STDOUT_FILENO, &cmdchar, 1);
+ (void) write(STDOUT_FILENO, &c, 1);
+ return (-1);
+ }
+ }
+
+ (void) write(out_fd, &c, 1);
+
+ beginning_of_line = (c == '\r' || c == '\n');
+ }
+
+ return (0);
+}
+
+static int
+process_output(int in_fd, int out_fd)
+{
+ int wrote = 0;
+ int cc;
+ char ibuf[BCONS_BUFSIZ];
+
+ cc = read(in_fd, ibuf, sizeof (ibuf));
+ if (cc == -1 && errno != EINTR)
+ return (-1);
+ if (cc == 0) /* EOF */
+ return (-1);
+ if (cc == -1) /* The read was interrupted. */
+ return (0);
+
+ do {
+ int len;
+
+ len = write(out_fd, ibuf + wrote, cc - wrote);
+ if (len == -1 && errno != EINTR)
+ return (-1);
+ if (len != -1)
+ wrote += len;
+ } while (wrote < cc);
+
+ return (0);
+}
+
+/*
+ * This is the main I/O loop.
+ */
+static void
+doio(void)
+{
+ struct pollfd pollfds[2];
+ int res;
+
+ /* read from vm and write to stdout */
+ pollfds[0].fd = masterfd;
+ pollfds[0].events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI;
+
+ /* read from stdin and write to vm */
+ pollfds[1].fd = STDIN_FILENO;
+ pollfds[1].events = pollfds[0].events;
+
+ for (;;) {
+ pollfds[0].revents = pollfds[1].revents = 0;
+
+ res = poll(pollfds,
+ sizeof (pollfds) / sizeof (struct pollfd), -1);
+
+ if (res == -1 && errno != EINTR) {
+ bcons_perror("poll failed");
+ /* we are hosed, close connection */
+ break;
+ }
+
+ /* event from master side stdout */
+ if (pollfds[0].revents) {
+ if (pollfds[0].revents &
+ (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
+ if (process_output(masterfd, STDOUT_FILENO)
+ != 0)
+ break;
+ } else {
+ break;
+ }
+ }
+
+ /* event from user stdin side */
+ if (pollfds[1].revents) {
+ if (pollfds[1].revents &
+ (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) {
+ if (process_user_input(masterfd, STDIN_FILENO)
+ != 0)
+ break;
+ } else {
+ break;
+ }
+ }
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ char *vmname;
+
+ pname = basename(argv[0]);
+
+ if (argc == 2) {
+ vmname = argv[1];
+ } else {
+ usage();
+ }
+
+ /*
+ * Make contact with bhyve
+ */
+ if (get_console(vmname) == -1)
+ return (1);
+
+ (void) printf("[Connected to vm '%s' console]\n", vmname);
+
+ if (set_tty_rawmode(STDIN_FILENO) == -1) {
+ reset_tty();
+ bcons_perror("failed to set stdin pty to raw mode");
+ return (1);
+ }
+
+ /*
+ * Run the I/O loop until we get disconnected.
+ */
+ doio();
+ reset_tty();
+ (void) printf("\n[Connection to vm '%s' console closed]\n", vmname);
+
+ return (0);
+}