aboutsummaryrefslogtreecommitdiff
path: root/extras/ezio
diff options
context:
space:
mode:
authorBen Pfaff <blp@nicira.com>2009-07-08 13:19:16 -0700
committerBen Pfaff <blp@nicira.com>2009-07-08 13:19:16 -0700
commit064af42167bf4fc9aaea2702d80ce08074b889c0 (patch)
treeefd15a6dc2402eeec273bb34db3b2445687589e5 /extras/ezio
Import from old repository commit 61ef2b42a9c4ba8e1600f15bb0236765edc2ad45.v0.90.0
Diffstat (limited to 'extras/ezio')
-rw-r--r--extras/ezio/automake.mk49
-rw-r--r--extras/ezio/byteq.c216
-rw-r--r--extras/ezio/byteq.h57
-rw-r--r--extras/ezio/ezio-term.c1060
-rw-r--r--extras/ezio/ezio.c243
-rw-r--r--extras/ezio/ezio.h96
-rw-r--r--extras/ezio/ezio3.ti21
-rw-r--r--extras/ezio/ovs-switchui.c3026
-rw-r--r--extras/ezio/terminal.c833
-rw-r--r--extras/ezio/terminal.h41
-rw-r--r--extras/ezio/tty.c404
-rw-r--r--extras/ezio/tty.h39
-rw-r--r--extras/ezio/vt-dummy.c40
-rw-r--r--extras/ezio/vt-linux.c139
-rw-r--r--extras/ezio/vt.h33
15 files changed, 6297 insertions, 0 deletions
diff --git a/extras/ezio/automake.mk b/extras/ezio/automake.mk
new file mode 100644
index 00000000..2aeaa644
--- /dev/null
+++ b/extras/ezio/automake.mk
@@ -0,0 +1,49 @@
+# Copyright (C) 2008, 2009 Nicira Networks, Inc.
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved. This file is offered as-is,
+# without warranty of any kind.
+
+EXTRA_DIST += extras/ezio/ezio3.ti
+install-data-hook:
+ @echo tic -x $(srcdir)/extras/ezio/ezio3.ti
+ @if ! tic -x $(srcdir)/extras/ezio/ezio3.ti; then \
+ echo "-----------------------------------------------------------"; \
+ echo "Failed to install ezio3 terminfo file. The ezio-term"; \
+ echo "program will not work until it has been installed."; \
+ echo "Probably, you need to install the 'tic' program from"; \
+ echo "ncurses, e.g. using a command like:"; \
+ echo " apt-get install ncurses-bin"; \
+ echo "and then re-run \"make install\""; \
+ echo "-----------------------------------------------------------"; \
+ exit 1; \
+ fi
+
+bin_PROGRAMS += extras/ezio/ezio-term
+extras_ezio_ezio_term_SOURCES = \
+ extras/ezio/byteq.c \
+ extras/ezio/byteq.h \
+ extras/ezio/ezio-term.c \
+ extras/ezio/ezio.c \
+ extras/ezio/ezio.h \
+ extras/ezio/terminal.c \
+ extras/ezio/terminal.h \
+ extras/ezio/tty.c \
+ extras/ezio/tty.h \
+ extras/ezio/vt.h
+if HAVE_LINUX_VT_H
+extras_ezio_ezio_term_SOURCES += extras/ezio/vt-linux.c
+else
+extras_ezio_ezio_term_SOURCES += extras/ezio/vt-dummy.c
+endif
+extras_ezio_ezio_term_LDADD = lib/libopenvswitch.a $(NCURSES_LIBS)
+
+bin_PROGRAMS += extras/ezio/ovs-switchui
+extras_ezio_ovs_switchui_SOURCES = extras/ezio/ovs-switchui.c
+extras_ezio_ovs_switchui_LDADD = \
+ lib/libopenvswitch.a \
+ $(NCURSES_LIBS) \
+ $(PCRE_LIBS) \
+ $(SSL_LIBS) \
+ -lm
diff --git a/extras/ezio/byteq.c b/extras/ezio/byteq.c
new file mode 100644
index 00000000..31d48aad
--- /dev/null
+++ b/extras/ezio/byteq.c
@@ -0,0 +1,216 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#include <config.h>
+#include "extras/ezio/byteq.h"
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include "util.h"
+
+/* The queue size must be a power of 2. */
+BUILD_ASSERT_DECL(!(BYTEQ_SIZE & (BYTEQ_SIZE - 1)));
+
+static uint8_t *head(struct byteq *);
+static int headroom(const struct byteq *);
+static void advance_head(struct byteq *, unsigned int n);
+static int tailroom(const struct byteq *);
+static const uint8_t *tail(const struct byteq *);
+static void advance_tail(struct byteq *, unsigned int n);
+
+/* Initializes 'q' as empty. */
+void
+byteq_init(struct byteq *q)
+{
+ q->head = q->tail = 0;
+}
+
+/* Returns the number of bytes current queued in 'q'. */
+int
+byteq_used(const struct byteq *q)
+{
+ return q->head - q->tail;
+}
+
+/* Returns the number of bytes that can be added to 'q' without overflow. */
+int
+byteq_avail(const struct byteq *q)
+{
+ return BYTEQ_SIZE - byteq_used(q);
+}
+
+/* Returns true if no bytes are queued in 'q',
+ * false if at least one byte is queued. */
+bool
+byteq_is_empty(const struct byteq *q)
+{
+ return !byteq_used(q);
+}
+
+/* Returns true if 'q' has no room to queue additional bytes,
+ * false if 'q' has room for at least one more byte. */
+bool
+byteq_is_full(const struct byteq *q)
+{
+ return !byteq_avail(q);
+}
+
+/* Adds 'c' at the head of 'q', which must not be full. */
+void
+byteq_put(struct byteq *q, uint8_t c)
+{
+ assert(!byteq_is_full(q));
+ *head(q) = c;
+ q->head++;
+}
+
+/* Adds the 'n' bytes in 'p' at the head of 'q', which must have at least 'n'
+ * bytes of free space. */
+void
+byteq_putn(struct byteq *q, const void *p_, size_t n)
+{
+ const uint8_t *p = p_;
+ assert(byteq_avail(q) >= n);
+ while (n > 0) {
+ size_t chunk = MIN(n, headroom(q));
+ memcpy(head(q), p, chunk);
+ advance_head(q, chunk);
+ p += chunk;
+ n -= chunk;
+ }
+}
+
+/* Appends null-terminated string 's' to the head of 'q', which must have
+ * enough space. The null terminator is not added to 'q'. */
+void
+byteq_put_string(struct byteq *q, const char *s)
+{
+ byteq_putn(q, s, strlen(s));
+}
+
+/* Removes a byte from the tail of 'q' and returns it. 'q' must not be
+ * empty. */
+uint8_t
+byteq_get(struct byteq *q)
+{
+ uint8_t c;
+ assert(!byteq_is_empty(q));
+ c = *tail(q);
+ q->tail++;
+ return c;
+}
+
+/* Writes as much of 'q' as possible to 'fd'. Returns 0 if 'q' is fully
+ * drained by the write, otherwise a positive errno value (e.g. EAGAIN if a
+ * socket or tty buffer filled up). */
+int
+byteq_write(struct byteq *q, int fd)
+{
+ while (!byteq_is_empty(q)) {
+ ssize_t n = write(fd, tail(q), tailroom(q));
+ if (n > 0) {
+ advance_tail(q, n);
+ } else {
+ assert(n < 0);
+ return errno;
+ }
+ }
+ return 0;
+}
+
+/* Reads as much possible from 'fd' into 'q'. Returns 0 if 'q' is completely
+ * filled up by the read, EOF if end-of-file was reached before 'q' was filled,
+ * and otherwise a positive errno value (e.g. EAGAIN if a socket or tty buffer
+ * was drained). */
+int
+byteq_read(struct byteq *q, int fd)
+{
+ while (!byteq_is_full(q)) {
+ ssize_t n = read(fd, head(q), headroom(q));
+ if (n > 0) {
+ advance_head(q, n);
+ } else {
+ return !n ? EOF : errno;
+ }
+ }
+ return 0;
+}
+
+/* Returns the number of contiguous bytes of in-use space starting at the tail
+ * of 'q'. */
+static int
+tailroom(const struct byteq *q)
+{
+ int used = byteq_used(q);
+ int tail_to_end = BYTEQ_SIZE - (q->tail & (BYTEQ_SIZE - 1));
+ return MIN(used, tail_to_end);
+}
+
+/* Returns the first in-use byte of 'q', the point at which data is removed
+ * from 'q'. */
+static const uint8_t *
+tail(const struct byteq *q)
+{
+ return &q->buffer[q->tail & (BYTEQ_SIZE - 1)];
+}
+
+/* Removes 'n' bytes from the tail of 'q', which must have at least 'n' bytes
+ * of tailroom. */
+static void
+advance_tail(struct byteq *q, unsigned int n)
+{
+ assert(tailroom(q) >= n);
+ q->tail += n;
+}
+
+/* Returns the byte after the last in-use byte of 'q', the point at which new
+ * data will be added to 'q'. */
+static uint8_t *
+head(struct byteq *q)
+{
+ return &q->buffer[q->head & (BYTEQ_SIZE - 1)];
+}
+
+/* Returns the number of contiguous bytes of free space starting at the head
+ * of 'q'. */
+static int
+headroom(const struct byteq *q)
+{
+ int avail = byteq_avail(q);
+ int head_to_end = BYTEQ_SIZE - (q->head & (BYTEQ_SIZE - 1));
+ return MIN(avail, head_to_end);
+}
+
+/* Adds to 'q' the 'n' bytes after the last currently in-use byte of 'q'. 'q'
+ * must have at least 'n' bytes of headroom. */
+static void
+advance_head(struct byteq *q, unsigned int n)
+{
+ assert(headroom(q) >= n);
+ q->head += n;
+}
diff --git a/extras/ezio/byteq.h b/extras/ezio/byteq.h
new file mode 100644
index 00000000..4397f6aa
--- /dev/null
+++ b/extras/ezio/byteq.h
@@ -0,0 +1,57 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#ifndef BYTEQ_H
+#define BYTEQ_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/* Maximum number of bytes in a byteq. */
+#define BYTEQ_SIZE 512
+
+/* General-purpose circular queue of bytes. */
+struct byteq {
+ uint8_t buffer[BYTEQ_SIZE]; /* Circular queue. */
+ unsigned int head; /* Head of queue. */
+ unsigned int tail; /* Chases the head. */
+};
+
+void byteq_init(struct byteq *);
+int byteq_used(const struct byteq *);
+int byteq_avail(const struct byteq *);
+bool byteq_is_empty(const struct byteq *);
+bool byteq_is_full(const struct byteq *);
+void byteq_put(struct byteq *, uint8_t c);
+void byteq_putn(struct byteq *, const void *, size_t n);
+void byteq_put_string(struct byteq *, const char *);
+uint8_t byteq_get(struct byteq *);
+int byteq_write(struct byteq *, int fd);
+int byteq_read(struct byteq *, int fd);
+
+#endif /* byteq.h */
diff --git a/extras/ezio/ezio-term.c b/extras/ezio/ezio-term.c
new file mode 100644
index 00000000..c2177add
--- /dev/null
+++ b/extras/ezio/ezio-term.c
@@ -0,0 +1,1060 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+
+#include <config.h>
+#include <assert.h>
+#include <curses.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <term.h>
+#include <unistd.h>
+#include "command-line.h"
+#include "extras/ezio/byteq.h"
+#include "extras/ezio/tty.h"
+#include "extras/ezio/vt.h"
+#include "daemon.h"
+#include "ezio.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "terminal.h"
+#include "timeval.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ezio_term
+#include "vlog.h"
+
+/* EZIO button status. */
+enum btn_status {
+ BTN_UP = 1 << 0,
+ BTN_DOWN = 1 << 1,
+ BTN_ENTER = 1 << 2,
+ BTN_ESC = 1 << 3
+};
+
+/* -e, --ezio: EZIO3 serial device file. */
+static char *ezio_dev = "/dev/ttyS1";
+
+/* -i, --input: Terminal from which to accept additional keyboard input. */
+static char *input_dev = NULL;
+
+struct inputdev;
+static int inputdev_open(const char *name, struct inputdev **);
+static void inputdev_close(struct inputdev *);
+static int inputdev_run(struct inputdev *, struct byteq *);
+static void inputdev_update(struct inputdev *, const struct ezio *);
+static void inputdev_wait(struct inputdev *);
+
+static struct scanner *scanner_create(void);
+static void scanner_destroy(struct scanner *);
+static void scanner_run(struct scanner *, struct ezio *);
+static void scanner_wait(struct scanner *);
+static void scanner_left(struct scanner *, struct ezio *);
+static void scanner_right(struct scanner *, struct ezio *);
+
+static struct updater *updater_create(void);
+static void updater_destroy(struct updater *);
+static int updater_run(struct updater *, const struct ezio *shadow,
+ int ezio_fd);
+static void updater_wait(struct updater *, int ezio_fd);
+enum btn_status updater_get_buttons(struct updater *);
+bool updater_has_buttons(const struct updater *);
+
+static void handle_buttons(struct updater *, struct scanner *,
+ struct byteq *, struct ezio *);
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+ struct terminal *terminal;
+ struct updater *updater;
+ struct scanner *scanner;
+ struct inputdev *inputdev;
+ struct byteq inputq;
+ struct ezio ezio;
+ int ezio_fd, pty_fd, dummy_fd;
+ int retval;
+ int i;
+
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ signal(SIGPIPE, SIG_IGN);
+
+ argc -= optind;
+ argv += optind;
+
+ /* Make sure that the ezio3 terminfo entry is available. */
+ dummy_fd = open("/dev/null", O_RDWR);
+ if (dummy_fd >= 0) {
+ if (setupterm("ezio3", dummy_fd, &retval) == ERR) {
+ if (retval == 0) {
+ ovs_fatal(0, "Missing terminfo entry for ezio3. "
+ "Did you run \"make install\"?");
+ } else {
+ ovs_fatal(0, "Missing terminfo database. Is ncurses "
+ "properly installed?");
+ }
+ }
+ del_curterm(cur_term);
+ close(dummy_fd);
+ } else {
+ ovs_error(errno, "failed to open /dev/null");
+ }
+
+ /* Lock serial port. */
+ retval = tty_lock(ezio_dev);
+ if (retval) {
+ ovs_fatal(retval, "%s: lock failed", ezio_dev);
+ }
+
+ /* Open EZIO and configure as 2400 bps, N-8-1, in raw mode. */
+ ezio_fd = open(ezio_dev, O_RDWR | O_NOCTTY);
+ if (ezio_fd < 0) {
+ ovs_fatal(errno, "%s: open", ezio_dev);
+ }
+ retval = tty_set_raw_mode(ezio_fd, B2400);
+ if (retval) {
+ ovs_fatal(retval, "%s: failed to configure tty parameters", ezio_dev);
+ }
+
+ /* Open keyboard device for input. */
+ if (input_dev) {
+ retval = inputdev_open(input_dev, &inputdev);
+ if (retval) {
+ ovs_fatal(retval, "%s: failed to open input device", input_dev);
+ }
+ } else {
+ inputdev = NULL;
+ }
+
+ /* Open pty master. */
+ pty_fd = tty_open_master_pty();
+ if (pty_fd < 0) {
+ ovs_fatal(-pty_fd, "failed to open master pty");
+ }
+ tty_set_window_size(pty_fd, 2, 40);
+
+ /* Start child process. */
+ if (argc < 1) {
+ char *child_argv[2];
+
+ child_argv[0] = getenv("SHELL");
+ if (!child_argv[0]) {
+ child_argv[0] = "/bin/sh";
+ }
+ child_argv[1] = NULL;
+ retval = tty_fork_child(pty_fd, child_argv);
+ } else {
+ retval = tty_fork_child(pty_fd, argv);
+ }
+ if (retval) {
+ ovs_fatal(retval, "failed to fork child process");
+ }
+
+ die_if_already_running();
+ daemonize();
+
+ terminal = terminal_create();
+ updater = updater_create();
+ scanner = scanner_create();
+ ezio_init(&ezio);
+ for (i = 0; i < 8; i++) {
+ ezio_set_default_icon(&ezio, i);
+ }
+ byteq_init(&inputq);
+ for (;;) {
+ /* Get button presses and keyboard input into inputq, then push the
+ * inputq to the pty. */
+ handle_buttons(updater, scanner, &inputq, &ezio);
+ if (inputdev) {
+ retval = inputdev_run(inputdev, &inputq);
+ if (retval) {
+ VLOG_ERR("error reading from input device: %s",
+ strerror(retval));
+ inputdev_close(inputdev);
+ inputdev = NULL;
+ }
+ }
+ retval = byteq_write(&inputq, pty_fd);
+ if (retval && retval != EAGAIN) {
+ VLOG_ERR("error passing through input: %s",
+ retval == EOF ? "end of file" : strerror(retval));
+ }
+
+ /* Process data from pty in terminal emulator. */
+ retval = terminal_run(terminal, &ezio, pty_fd);
+ if (retval) {
+ VLOG_ERR("error reading from terminal: %s",
+ retval == EOF ? "end of file" : strerror(retval));
+ break;
+ }
+
+ /* Scroll left and right through text. */
+ scanner_run(scanner, &ezio);
+
+ /* Update the display to match what should be shown. */
+ retval = updater_run(updater, &ezio, ezio_fd);
+ if (retval) {
+ VLOG_ERR("error writing to ezio: %s",
+ retval == EOF ? "end of file" : strerror(retval));
+ break;
+ }
+ if (inputdev) {
+ inputdev_update(inputdev, &ezio);
+ }
+
+ /* Wait for something to happen. */
+ terminal_wait(terminal, pty_fd);
+ scanner_wait(scanner);
+ if (updater_has_buttons(updater)) {
+ poll_immediate_wake();
+ }
+ updater_wait(updater, ezio_fd);
+ if (!byteq_is_empty(&inputq)) {
+ poll_fd_wait(pty_fd, POLLOUT);
+ }
+ if (inputdev) {
+ inputdev_wait(inputdev);
+ }
+ poll_block();
+ }
+ terminal_destroy(terminal);
+ updater_destroy(updater);
+ scanner_destroy(scanner);
+
+ return 0;
+}
+
+static void
+send_keys(struct byteq *q, const char *s)
+{
+ size_t n = strlen(s);
+ if (byteq_avail(q) >= n) {
+ byteq_putn(q, s, n);
+ }
+}
+
+static void
+handle_buttons(struct updater *up, struct scanner *s,
+ struct byteq *q, struct ezio *ezio)
+{
+ while (updater_has_buttons(up)) {
+ int btns = updater_get_buttons(up);
+ switch (btns) {
+ case BTN_UP:
+ send_keys(q, "\x1b\x5b\x41"); /* Up arrow. */
+ break;
+
+ case BTN_UP | BTN_ESC:
+ send_keys(q, "\x1b[5~"); /* Page up. */
+ break;
+
+ case BTN_DOWN:
+ send_keys(q, "\x1b\x5b\x42"); /* Down arrow. */
+ break;
+
+ case BTN_DOWN | BTN_ESC:
+ send_keys(q, "\x1b[6~"); /* Page down. */
+ break;
+
+ case BTN_ENTER:
+ send_keys(q, "\r");
+ break;
+
+ case BTN_ESC:
+ send_keys(q, "\x7f");
+ break;
+
+ case BTN_UP | BTN_DOWN:
+ scanner_left(s, ezio);
+ break;
+
+ case BTN_ESC | BTN_ENTER:
+ scanner_right(s, ezio);
+ break;
+
+ case BTN_UP | BTN_DOWN | BTN_ENTER | BTN_ESC:
+ send_keys(q, "\x04"); /* End of file. */
+ break;
+
+ case BTN_UP | BTN_ENTER | BTN_ESC:
+ send_keys(q, "y");
+ break;
+
+ case BTN_DOWN | BTN_ENTER | BTN_ESC:
+ send_keys(q, "n");
+ break;
+ }
+ }
+}
+
+/* EZIO screen updater. */
+
+/* EZIO command codes. */
+#define EZIO_CMD 0xfe /* Command prefix byte. */
+#define EZIO_CLEAR 0x01 /* Clear screen. */
+#define EZIO_HOME 0x02 /* Move to (0, 0). */
+#define EZIO_READ 0x06 /* Poll keyboard. */
+
+#define EZIO_ENTRY_MODE 0x04 /* Set entry mode: */
+#define EZIO_LTOR_MODE 0x02 /* ...left-to-right (vs. r-to-l). */
+#define EZIO_SHIFT_MODE 0x01 /* ...scroll with output (vs. don't). */
+
+#define EZIO_DISPLAY_MODE 0x08 /* Set display mode: */
+#define EZIO_ENABLE_DISPLAY 0x04 /* ...turn on display (vs. blank). */
+#define EZIO_SHOW_CURSOR 0x02 /* ...show cursor (vs. hide). */
+#define EZIO_BLOCK_CURSOR 0x01 /* ...block cursor (vs. underline). */
+
+#define EZIO_INIT 0x28 /* Initialize EZIO. */
+
+#define EZIO_MOVE_CURSOR 0x80 /* Set cursor position. */
+#define EZIO_COL_SHIFT 0 /* Shift count for column (0-based). */
+#define EZIO_ROW_SHIFT 6 /* Shift count for row (0-based). */
+
+#define EZIO_DEFINE_ICON 0x40 /* Define icon. */
+#define EZIO_ICON_SHIFT 3 /* Shift count for icon number (0-7). */
+
+#define EZIO_SCROLL_LEFT 0x18 /* Scroll display left 1 position. */
+#define EZIO_SCROLL_RIGHT 0x1c /* Scroll display right 1 position. */
+#define EZIO_CURSOR_LEFT 0x10 /* Move cursor left 1 position. */
+#define EZIO_CURSOR_RIGHT 0x14 /* Move cursor right 1 position. */
+
+/* Rate limiting: the EZIO runs at 2400 bps, which is 240 bytes per second.
+ * Kernel tty buffers, on the other hand, tend to be at least 4 kB. That
+ * means that, if we keep the kernel buffer filled, then the queued data will
+ * be 4,096 kB / 240 bytes/s ~= 17 seconds ahead of what is actually
+ * displayed. This is not a happy situation. So we rate-limit with a token
+ * bucket.
+ *
+ * The parameters below work out as: (6 tokens/ms * 1000 ms) / (25
+ * tokens/byte) = 240 bytes/s. */
+#define UP_TOKENS_PER_MS 6 /* Tokens acquired per millisecond. */
+#define UP_BUCKET_SIZE (6 * 100) /* Capacity of the token bukect. */
+#define UP_TOKENS_PER_BYTE 25 /* Tokens required to output a byte. */
+
+struct updater {
+ /* Current state of EZIO device. */
+ struct ezio visible;
+
+ /* Output state. */
+ struct byteq obuf; /* Output being sent to serial port. */
+ int tokens; /* Token bucket content. */
+ long long int last_fill; /* Last time we increased 'tokens'.*/
+ bool up_to_date; /* Does visible state match shadow state? */
+
+ /* Input state. */
+ struct byteq ibuf; /* Queued button pushes. */
+ long long int last_poll; /* Last time we sent a button poll request. */
+ enum btn_status last_status; /* Last received button status. */
+ long long int last_change; /* Time when status most recently changed. */
+ int repeat_count; /* Autorepeat count. */
+ bool releasing; /* Waiting for button release? */
+};
+
+static void send_command(struct updater *, uint8_t command);
+static void recv_button_state(struct updater *, enum btn_status status);
+static int range(int value, int min, int max);
+static void send_command(struct updater *, uint8_t command);
+static void set_cursor_position(struct updater *, int x, int y);
+static bool icons_differ(const struct ezio *, const struct ezio *, int *idx);
+static void update_char(struct updater *, const struct ezio *, int x, int y);
+static void update_cursor_status(struct updater *, const struct ezio *);
+
+/* Creates and returns a new updater. */
+static struct updater *
+updater_create(void)
+{
+ struct updater *up = xmalloc(sizeof *up);
+ ezio_init(&up->visible);
+ byteq_init(&up->obuf);
+ up->tokens = UP_BUCKET_SIZE;
+ up->last_fill = time_msec();
+ byteq_init(&up->ibuf);
+ up->last_poll = LLONG_MIN;
+ up->last_status = 0;
+ up->last_change = time_msec();
+ up->releasing = false;
+ send_command(up, EZIO_INIT);
+ send_command(up, EZIO_INIT);
+ send_command(up, EZIO_CLEAR);
+ send_command(up, EZIO_HOME);
+ return up;
+}
+
+/* Destroys updater 'up. */
+static void
+updater_destroy(struct updater *up)
+{
+ free(up);
+}
+
+/* Sends EZIO commands over file descriptor 'ezio_fd' to the EZIO represented
+ * by updater 'up', to make the EZIO display the contents of 'shadow'.
+ * Rate-limiting can cause the update to be only partial, but the next call to
+ * updater_run() will resume the update.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+static int
+updater_run(struct updater *up, const struct ezio *shadow, int ezio_fd)
+{
+ uint8_t c;
+ while (read(ezio_fd, &c, 1) > 0) {
+ if ((c & 0xf0) == 0xb0) {
+ recv_button_state(up, ~c & 0x0f);
+ }
+ }
+
+ up->up_to_date = false;
+ for (;;) {
+ struct ezio *visible = &up->visible;
+ int idx, x, y;
+ int retval;
+
+ /* Flush the buffer out to the EZIO device. */
+ retval = byteq_write(&up->obuf, ezio_fd);
+ if (retval == EAGAIN) {
+ return 0;
+ } else if (retval) {
+ VLOG_WARN("error writing ezio: %s", strerror(retval));
+ return retval;
+ }
+
+ /* Make sure we have some tokens before we write anything more. */
+ if (up->tokens <= 0) {
+ long long int now = time_msec();
+ if (now > up->last_fill) {
+ up->tokens += (now - up->last_fill) * UP_TOKENS_PER_MS;
+ up->last_fill = now;
+ if (up->tokens > UP_BUCKET_SIZE) {
+ up->tokens = UP_BUCKET_SIZE;
+ }
+ }
+ if (up->tokens <= 0) {
+ /* Still out of tokens. */
+ return 0;
+ }
+ }
+
+ /* Consider what else we might want to send. */
+ if (time_msec() >= up->last_poll + 100) {
+ /* Send a button-read command. */
+ send_command(up, EZIO_READ);
+ up->last_poll = time_msec();
+ } else if (visible->show_cursor && !shadow->show_cursor) {
+ /* Turn off the cursor. */
+ update_cursor_status(up, shadow);
+ } else if (icons_differ(shadow, visible, &idx)) {
+ /* Update the icons. */
+ send_command(up, EZIO_DEFINE_ICON + (idx << EZIO_ICON_SHIFT));
+ byteq_putn(&up->obuf, &shadow->icons[idx][0], 8);
+ set_cursor_position(up, shadow->x, shadow->y);
+ memcpy(visible->icons[idx], shadow->icons[idx], 8);
+ } else if (visible->x_ofs != shadow->x_ofs) {
+ /* Scroll to the correct horizontal position. */
+ if (visible->x_ofs < shadow->x_ofs) {
+ send_command(up, EZIO_SCROLL_LEFT);
+ visible->x_ofs++;
+ } else {
+ send_command(up, EZIO_SCROLL_RIGHT);
+ visible->x_ofs--;
+ }
+ } else if (ezio_chars_differ(shadow, visible, shadow->x_ofs,
+ shadow->x_ofs + 16, &x, &y)) {
+ /* Update the visible region. */
+ update_char(up, shadow, x, y);
+ } else if (ezio_chars_differ(shadow, visible, 0, 40, &x, &y)) {
+ /* Update the off-screen region. */
+ update_char(up, shadow, x, y);
+ } else if ((visible->x != shadow->x || visible->y != shadow->y)
+ && shadow->show_cursor) {
+ /* Update the cursor position. (This has to follow updating the
+ * display content, because updating display content changes the
+ * cursor position.) */
+ set_cursor_position(up, shadow->x, shadow->y);
+ } else if (visible->show_cursor != shadow->show_cursor
+ || visible->blink_cursor != shadow->blink_cursor) {
+ /* Update the cursor type. */
+ update_cursor_status(up, shadow);
+ } else {
+ /* We're fully up-to-date. */
+ up->up_to_date = true;
+ return 0;
+ }
+ up->tokens -= UP_TOKENS_PER_BYTE * byteq_used(&up->obuf);
+ }
+}
+
+/* Calls poll-loop functions that will cause poll_block() to wake up when
+ * updater_run() has work to do. */
+static void
+updater_wait(struct updater *up, int ezio_fd)
+{
+ if (!byteq_is_empty(&up->obuf)) {
+ poll_fd_wait(ezio_fd, POLLOUT);
+ } else if (up->tokens <= 0) {
+ poll_timer_wait((-up->tokens / UP_TOKENS_PER_MS) + 1);
+ } else if (!up->up_to_date) {
+ poll_immediate_wake();
+ }
+
+ if (!up->last_status && time_msec() - up->last_change > 100) {
+ /* No button presses in a while. Sleep longer. */
+ poll_timer_wait(100);
+ } else {
+ poll_timer_wait(50);
+ }
+}
+
+/* Returns a button or buttons that were pushed. Must not be called if
+ * updater_has_buttons() would return false. One or more BTN_* flags will be
+ * set in the return value. */
+enum btn_status
+updater_get_buttons(struct updater *up)
+{
+ return byteq_get(&up->ibuf);
+}
+
+/* Any buttons pushed? */
+bool
+updater_has_buttons(const struct updater *up)
+{
+ return !byteq_is_empty(&up->ibuf);
+}
+
+/* Adds 'btns' to the queue of pushed buttons */
+static void
+buttons_pushed(struct updater *up, enum btn_status btns)
+{
+ if (!byteq_is_full(&up->ibuf)) {
+ byteq_put(&up->ibuf, btns);
+ }
+}
+
+/* Updates the buttons-pushed queue based on the current button 'status'. */
+static void
+recv_button_state(struct updater *up, enum btn_status status)
+{
+ /* Calculate milliseconds since button status last changed. */
+ long long int stable_msec;
+ if (status != up->last_status) {
+ up->last_change = time_msec();
+ stable_msec = 0;
+ } else {
+ stable_msec = time_msec() - up->last_change;
+ }
+
+ if (up->releasing) {
+ if (!status) {
+ up->releasing = false;
+ }
+ } else if (up->last_status) {
+ if (!(status & up->last_status)) {
+ /* Button(s) were pushed and released. */
+ if (!up->repeat_count) {
+ buttons_pushed(up, up->last_status);
+ }
+ } else if (stable_msec >= 150 && !up->repeat_count) {
+ /* Buttons have been stable for a while, so push them once. */
+ buttons_pushed(up, status);
+ up->repeat_count++;
+ } else if (stable_msec >= 1000) {
+ /* Autorepeat 10/second after 1 second hold time. */
+ int n = (stable_msec - 1000) / 100 + 1;
+ while (up->repeat_count < n) {
+ buttons_pushed(up, status);
+ up->repeat_count++;
+ }
+ } else if ((status & up->last_status) == up->last_status) {
+ /* More buttons pushed than at last poll. */
+ } else {
+ /* Some, but not all, buttons were released. Ignore the buttons
+ * until all are released. */
+ up->releasing = true;
+ }
+ }
+ if (!status) {
+ up->repeat_count = 0;
+ }
+ up->last_status = status;
+}
+
+static int
+range(int value, int min, int max)
+{
+ return value < min ? min : value > max ? max : value;
+}
+
+static void
+send_command(struct updater *up, uint8_t command)
+{
+ byteq_put(&up->obuf, EZIO_CMD);
+ byteq_put(&up->obuf, command);
+}
+
+/* Moves the cursor to 0-based position (x, y). Updates 'up->visible' to
+ * reflect the change. */
+static void
+set_cursor_position(struct updater *up, int x, int y)
+{
+ int command = EZIO_MOVE_CURSOR;
+ command |= range(x, 0, 39) << EZIO_COL_SHIFT;
+ command |= range(y, 0, 1) << EZIO_ROW_SHIFT;
+ send_command(up, command);
+ up->visible.x = x;
+ up->visible.y = y;
+}
+
+/* If any of the icons differ from 'a' to 'b', returns true and sets '*idx' to
+ * the index of the first icon that differs. Otherwise, returns false. */
+static bool
+icons_differ(const struct ezio *a, const struct ezio *b, int *idx)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(a->icons); i++) {
+ if (memcmp(&a->icons[i], &b->icons[i], sizeof a->icons[i])) {
+ *idx = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Queues commands in 'up''s output buffer to update the character at 0-based
+ * position (x,y) to match the character that 'shadow' has there. Updates
+ * 'up->visible' to reflect the change. */
+static void
+update_char(struct updater *up, const struct ezio *shadow, int x, int y)
+{
+ if (x != up->visible.x || y != up->visible.y) {
+ set_cursor_position(up, x, y);
+ }
+ byteq_put(&up->obuf, shadow->chars[y][x]);
+ up->visible.chars[y][x] = shadow->chars[y][x];
+ up->visible.x++;
+}
+
+/* Queues commands in 'up''s output buffer to change the EZIO's cursor shape to
+ * match that in 'shadow'. Updates 'up->visible' to reflect the change. */
+static void
+update_cursor_status(struct updater *up, const struct ezio *shadow)
+{
+ uint8_t command = EZIO_DISPLAY_MODE | EZIO_ENABLE_DISPLAY;
+ if (shadow->show_cursor) {
+ command |= EZIO_SHOW_CURSOR;
+ if (shadow->blink_cursor) {
+ command |= EZIO_BLOCK_CURSOR;
+ }
+ }
+ send_command(up, command);
+ up->visible.show_cursor = shadow->show_cursor;
+ up->visible.blink_cursor = shadow->blink_cursor;
+}
+
+/* An input device, such as a tty. */
+
+struct inputdev {
+ /* Input. */
+ int fd; /* File descriptor. */
+
+ /* State for mirroring the EZIO display to the device. */
+ bool is_tty; /* We only attempt to mirror to ttys. */
+ struct byteq outq; /* Output queue. */
+ struct ezio visible; /* Data that we have displayed. */
+};
+
+/* Opens 'name' as a input device. If successful, returns 0 and stores a
+ * pointer to the input device in '*devp'. On failure, returns a positive
+ * errno value. */
+static int
+inputdev_open(const char *name, struct inputdev **devp)
+{
+ struct inputdev *dev;
+ int retval;
+ int fd;
+
+ *devp = NULL;
+ if (!strcmp(name, "vt")) {
+ fd = vt_open(O_RDWR | O_NOCTTY);
+ if (fd < 0) {
+ return -fd;
+ }
+ } else if (!strcmp(name, "-")) {
+ fd = dup(STDIN_FILENO);
+ if (fd < 0) {
+ return errno;
+ }
+ } else {
+ fd = open(name, O_RDWR | O_NOCTTY);
+ if (fd < 0) {
+ return errno;
+ }
+ }
+
+ retval = tty_set_raw_mode(fd, B0);
+ if (retval) {
+ close(fd);
+ VLOG_WARN("%s: failed to configure tty parameters: %s",
+ name, strerror(retval));
+ return retval;
+ }
+
+ dev = xmalloc(sizeof *dev);
+ dev->fd = fd;
+ dev->is_tty = isatty(fd);
+ byteq_init(&dev->outq);
+ ezio_init(&dev->visible);
+ *devp = dev;
+ return 0;
+}
+
+/* Closes and destroys input device 'dev'. */
+static void
+inputdev_close(struct inputdev *dev)
+{
+ if (dev) {
+ close(dev->fd);
+ free(dev);
+ }
+}
+
+/* Reads input from 'dev' into 'q'. Returns 0 if successful, otherwise a
+ * positive errno value. */
+static int
+inputdev_run(struct inputdev *dev, struct byteq *q)
+{
+ int retval = byteq_read(q, dev->fd);
+ return retval == EAGAIN ? 0 : retval;
+}
+
+/* Dumps data from 'dev''s output queue to the underlying file descriptor,
+ * updating the tty screen display. */
+static void
+flush_inputdev(struct inputdev *dev)
+{
+ int retval = byteq_write(&dev->outq, dev->fd);
+ if (retval && retval != EAGAIN) {
+ VLOG_WARN("error writing input device, "
+ "disabling further output");
+ dev->is_tty = false;
+ }
+}
+
+/* Updates the tty screen display on 'dev' to match 'e'. */
+static void
+inputdev_update(struct inputdev *dev, const struct ezio *e)
+{
+ struct byteq *q = &dev->outq;
+ int x, y;
+
+ if (!dev->is_tty) {
+ return;
+ }
+
+ flush_inputdev(dev);
+ if (!byteq_is_empty(q)) {
+ return;
+ }
+
+ if (!ezio_chars_differ(e, &dev->visible, 0, 40, &x, &y)
+ && e->x == dev->visible.x
+ && e->y == dev->visible.y
+ && e->x_ofs == dev->visible.x_ofs
+ && e->show_cursor == dev->visible.show_cursor) {
+ return;
+ }
+ dev->visible = *e;
+
+ byteq_put_string(q, "\033[H\033[2J"); /* Clear screen. */
+ for (y = 0; y < 4; y++) {
+ byteq_put(q, "+||+"[y]);
+ for (x = 0; x < 40; x++) {
+ int c;
+ if (x == e->x_ofs) {
+ byteq_put(q, '[');
+ }
+ c = y == 0 || y == 3 ? '-' : e->chars[y - 1][x];
+ if (c == 6) {
+ c = '\\';
+ } else if (c == 7) {
+ c = '~';
+ } else if (c < 0x20 || c > 0x7d) {
+ c = '?';
+ }
+ byteq_put(q, c);
+ if (x == e->x_ofs + 15) {
+ byteq_put(q, ']');
+ }
+ }
+ byteq_put(q, "+||+"[y]);
+ byteq_put(q, '\r');
+ byteq_put(q, '\n');
+ }
+ if (e->show_cursor) {
+ int x = range(e->x, 0, 39) + 2 + (e->x >= e->x_ofs) + (e->x > e->x_ofs + 15);
+ int y = range(e->y, 0, 1) + 2;
+ char cup[16];
+ sprintf(cup, "\033[%d;%dH", y, x); /* Position cursor. */
+ byteq_put_string(q, cup);
+ }
+ flush_inputdev(dev);
+}
+
+/* Calls poll-loop functions that will cause poll_block() to wake up when
+ * inputdev_run() has work to do. */
+static void
+inputdev_wait(struct inputdev *dev)
+{
+ int flags = POLLIN;
+ if (dev->is_tty && !byteq_is_empty(&dev->outq)) {
+ flags |= POLLOUT;
+ }
+ poll_fd_wait(dev->fd, flags);
+}
+
+/* Scrolls the display left and right automatically to display all the
+ * content. */
+
+enum scanner_state {
+ SCANNER_LEFT, /* Moving left. */
+ SCANNER_RIGHT /* Moving right. */
+};
+
+struct scanner {
+ enum scanner_state state; /* Current state. */
+ int wait; /* No. of cycles to pause before continuing. */
+ long long int last_move; /* Last time the state machine ran. */
+};
+
+static void find_min_max(struct ezio *, int *min, int *max);
+
+static struct scanner *
+scanner_create(void)
+{
+ struct scanner *s = xmalloc(sizeof *s);
+ s->state = SCANNER_RIGHT;
+ s->wait = 0;
+ s->last_move = LLONG_MIN;
+ return s;
+}
+
+static void
+scanner_destroy(struct scanner *s)
+{
+ free(s);
+}
+
+static void
+scanner_run(struct scanner *s, struct ezio *ezio)
+{
+ long long int now = time_msec();
+ if (now >= s->last_move + 750) {
+ s->last_move = now;
+ if (s->wait) {
+ s->wait--;
+ } else {
+ int min, max;
+
+ find_min_max(ezio, &min, &max);
+ if (max - min + 1 <= 16) {
+ ezio->x_ofs = min;
+ return;
+ }
+
+ switch (s->state) {
+ case SCANNER_RIGHT:
+ if (ezio->x_ofs + 15 < max) {
+ ezio->x_ofs++;
+ } else {
+ s->state = SCANNER_LEFT;
+ s->wait = 1;
+ }
+ break;
+
+ case SCANNER_LEFT:
+ if (ezio->x_ofs > min) {
+ ezio->x_ofs--;
+ } else {
+ s->state = SCANNER_RIGHT;
+ s->wait = 1;
+ }
+ break;
+ }
+ }
+ }
+}
+
+static void
+scanner_wait(struct scanner *s)
+{
+ long long int now = time_msec();
+ long long int expires = s->last_move + 750;
+ if (now >= expires) {
+ poll_immediate_wake();
+ } else {
+ poll_timer_wait(expires - now);
+ }
+
+}
+
+static void
+scanner_left(struct scanner *s, struct ezio *ezio)
+{
+ s->wait = 7;
+ if (ezio->x_ofs > 0) {
+ ezio->x_ofs--;
+ }
+}
+
+static void
+scanner_right(struct scanner *s, struct ezio *ezio)
+{
+ s->wait = 7;
+ if (ezio->x_ofs < 40 - 16) {
+ ezio->x_ofs++;
+ }
+}
+
+static void
+find_min_max(struct ezio *ezio, int *min, int *max)
+{
+ int x;
+
+ *min = 0;
+ for (x = 0; x < 40; x++) {
+ if (ezio->chars[0][x] != ' ' || ezio->chars[1][x] != ' ') {
+ *min = x;
+ break;
+ }
+ }
+
+ *max = 15;
+ for (x = 39; x >= 0; x--) {
+ if (ezio->chars[0][x] != ' ' || ezio->chars[1][x] != ' ') {
+ *max = x;
+ break;
+ }
+ }
+
+ if (ezio->show_cursor) {
+ if (ezio->x < *min) {
+ *min = ezio->x;
+ }
+ if (ezio->x > *max) {
+ *max = ezio->x;
+ }
+ }
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ enum {
+ OPT_DUMMY = UCHAR_MAX + 1,
+ VLOG_OPTION_ENUMS
+ };
+ static struct option long_options[] = {
+ {"ezio3", required_argument, 0, 'e'},
+ {"input", required_argument, 0, 'i'},
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ DAEMON_LONG_OPTIONS,
+ VLOG_LONG_OPTIONS,
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'e':
+ ezio_dev = optarg;
+ break;
+
+ case 'i':
+ input_dev = optarg ? optarg : "-";
+ break;
+
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(0, 0);
+ exit(EXIT_SUCCESS);
+
+ DAEMON_OPTION_HANDLERS
+ VLOG_OPTION_HANDLERS
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: EZIO3 terminal front-end\n"
+ "Provides a front-end to a 16x2 EZIO3 LCD display that makes\n"
+ "it look more like a conventional terminal\n"
+ "usage: %s [OPTIONS] [-- COMMAND [ARG...]]\n"
+ "where COMMAND is a command to run with stdin, stdout, and\n"
+ "stderr directed to the EZIO3 display.\n"
+ "\nSettings (defaults in parentheses):\n"
+ " -e, --ezio=TTY set EZIO3 serial device (/dev/ttyS1)\n"
+ " -i, --input=TERMINAL also read input from TERMINAL;\n"
+ " specify - for stdin, or vt to allocate\n"
+ " and switch to a free virtual terminal\n"
+ "\nOther options:\n"
+ " -v, --verbose=MODULE:FACILITY:LEVEL configure logging levels\n"
+ " -v, --verbose set maximum verbosity level\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n",
+ program_name, program_name);
+ exit(EXIT_SUCCESS);
+}
diff --git a/extras/ezio/ezio.c b/extras/ezio/ezio.c
new file mode 100644
index 00000000..6024766e
--- /dev/null
+++ b/extras/ezio/ezio.c
@@ -0,0 +1,243 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#include <config.h>
+#include "ezio.h"
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#include "util.h"
+
+static void remove_elements(uint8_t *p, size_t n_elems, size_t elem_size,
+ int pos, int n_del);
+static void insert_elements(uint8_t *p, size_t n_elems, size_t elem_size,
+ int pos, int n_insert);
+static int range(int value, int min, int max);
+
+void
+ezio_init(struct ezio *e)
+{
+ memset(e->icons, 0, sizeof e->icons);
+ ezio_clear(e);
+ e->x_ofs = 0;
+ e->show_cursor = true;
+ e->blink_cursor = false;
+}
+
+void
+ezio_set_icon(struct ezio *e, int idx,
+ int row0, int row1, int row2, int row3,
+ int row4, int row5, int row6, int row7)
+{
+ e->icons[idx][0] = row0;
+ e->icons[idx][1] = row1;
+ e->icons[idx][2] = row2;
+ e->icons[idx][3] = row3;
+ e->icons[idx][4] = row4;
+ e->icons[idx][5] = row5;
+ e->icons[idx][6] = row6;
+ e->icons[idx][7] = row7;
+}
+
+void
+ezio_set_default_icon(struct ezio *e, int idx)
+{
+ uint8_t *icon;
+
+ assert(idx >= 0 && idx < 8);
+ icon = e->icons[idx];
+ if (idx == 6) {
+ ezio_set_icon(e, idx,
+ e_____,
+ eX____,
+ e_X___,
+ e__X__,
+ e___X_,
+ e____X,
+ e_____,
+ e_____);
+ } else if (idx == 7) {
+ ezio_set_icon(e, idx,
+ e_____,
+ e_____,
+ e_X___,
+ eX_X_X,
+ eX_X_X,
+ e___X_,
+ e_____,
+ e_____);
+ } else {
+ ezio_set_icon(e, idx,
+ e_____,
+ e_____,
+ e_____,
+ e_____,
+ e_____,
+ e_____,
+ e_____,
+ e_____);
+ }
+}
+
+void
+ezio_clear(struct ezio *e)
+{
+ memset(e->chars, ' ', sizeof e->chars);
+ e->x = e->y = 0;
+}
+
+void
+ezio_put_char(struct ezio *e, int x, int y, uint8_t c)
+{
+ assert(x >= 0 && x <= 39);
+ assert(y >= 0 && y <= 1);
+ e->chars[y][x] = c != 0xfe ? c : 0xff;
+}
+
+void
+ezio_line_feed(struct ezio *e)
+{
+ if (++e->y >= 2) {
+ e->y = 1;
+ ezio_scroll_up(e, 1);
+ }
+}
+
+void
+ezio_newline(struct ezio *e)
+{
+ e->x = 0;
+ ezio_line_feed(e);
+}
+
+void
+ezio_delete_char(struct ezio *e, int x, int y, int n)
+{
+ remove_elements(&e->chars[y][0], 40, 1, x, n);
+}
+
+void
+ezio_delete_line(struct ezio *e, int y, int n)
+{
+ remove_elements(e->chars[0], 2, 40, y, n);
+}
+
+void
+ezio_insert_char(struct ezio *e, int x, int y, int n)
+{
+ insert_elements(&e->chars[y][0], 40, 1, x, n);
+}
+
+void
+ezio_insert_line(struct ezio *e, int y, int n)
+{
+ insert_elements(&e->chars[0][0], 2, 40, y, n);
+}
+
+void
+ezio_scroll_left(struct ezio *e, int n)
+{
+ int y;
+ for (y = 0; y < 2; y++) {
+ ezio_delete_char(e, 0, y, n);
+ }
+}
+
+void
+ezio_scroll_right(struct ezio *e, int n)
+{
+ int y;
+
+ for (y = 0; y < 2; y++) {
+ ezio_insert_char(e, 0, y, n);
+ }
+}
+
+void
+ezio_scroll_up(struct ezio *e, int n)
+{
+ ezio_delete_line(e, 0, n);
+}
+
+void
+ezio_scroll_down(struct ezio *e, int n)
+{
+ ezio_insert_line(e, 0, n);
+}
+
+bool
+ezio_chars_differ(const struct ezio *a, const struct ezio *b, int x0, int x1,
+ int *xp, int *yp)
+{
+ int x, y;
+
+ x0 = range(x0, 0, 39);
+ x1 = range(x1, 1, 40);
+ for (y = 0; y < 2; y++) {
+ for (x = x0; x < x1; x++) {
+ if (a->chars[y][x] != b->chars[y][x]) {
+ *xp = x;
+ *yp = y;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static void
+remove_elements(uint8_t *p, size_t n_elems, size_t elem_size,
+ int pos, int n_del)
+{
+ if (pos >= 0 && pos < n_elems) {
+ n_del = MIN(n_del, n_elems - pos);
+ memmove(p + elem_size * pos,
+ p + elem_size * (pos + n_del),
+ elem_size * (n_elems - pos - n_del));
+ memset(p + elem_size * (n_elems - n_del), ' ', n_del * elem_size);
+ }
+}
+
+static void
+insert_elements(uint8_t *p, size_t n_elems, size_t elem_size,
+ int pos, int n_insert)
+{
+ if (pos >= 0 && pos < n_elems) {
+ n_insert = MIN(n_insert, n_elems - pos);
+ memmove(p + elem_size * (pos + n_insert),
+ p + elem_size * pos,
+ elem_size * (n_elems - pos - n_insert));
+ memset(p + elem_size * pos, ' ', n_insert * elem_size);
+ }
+}
+
+static int
+range(int value, int min, int max)
+{
+ return value < min ? min : value > max ? max : value;
+}
+
diff --git a/extras/ezio/ezio.h b/extras/ezio/ezio.h
new file mode 100644
index 00000000..1308ec30
--- /dev/null
+++ b/extras/ezio/ezio.h
@@ -0,0 +1,96 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#ifndef EZIO_H
+#define EZIO_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+
+/* Constants for visual representation of a row in an EZIO icon. */
+#define e_____ 0x00
+#define e____X 0x01
+#define e___X_ 0x02
+#define e___XX 0x03
+#define e__X__ 0x04
+#define e__X_X 0x05
+#define e__XX_ 0x06
+#define e__XXX 0x07
+#define e_X___ 0x08
+#define e_X__X 0x09
+#define e_X_X_ 0x0a
+#define e_X_XX 0x0b
+#define e_XX__ 0x0c
+#define e_XX_X 0x0d
+#define e_XXX_ 0x0e
+#define e_XXXX 0x0f
+#define eX____ 0x10
+#define eX___X 0x11
+#define eX__X_ 0x12
+#define eX__XX 0x13
+#define eX_X__ 0x14
+#define eX_X_X 0x15
+#define eX_XX_ 0x16
+#define eX_XXX 0x17
+#define eXX___ 0x18
+#define eXX__X 0x19
+#define eXX_X_ 0x1a
+#define eXX_XX 0x1b
+#define eXXX__ 0x1c
+#define eXXX_X 0x1d
+#define eXXXX_ 0x1e
+#define eXXXXX 0x1f
+
+struct ezio {
+ uint8_t icons[8][8];
+ uint8_t chars[2][40];
+ int x, y, x_ofs;
+ bool show_cursor;
+ bool blink_cursor;
+};
+
+void ezio_init(struct ezio *);
+void ezio_set_icon(struct ezio *, int idx,
+ int row0, int row1, int row2, int row3,
+ int row4, int row5, int row6, int row7);
+void ezio_set_default_icon(struct ezio *, int idx);
+void ezio_clear(struct ezio *);
+void ezio_put_char(struct ezio *, int x, int y, uint8_t c);
+void ezio_line_feed(struct ezio *);
+void ezio_newline(struct ezio *);
+void ezio_delete_char(struct ezio *, int x, int y, int n);
+void ezio_delete_line(struct ezio *, int y, int n);
+void ezio_insert_char(struct ezio *, int x, int y, int n);
+void ezio_insert_line(struct ezio *, int y, int n);
+void ezio_scroll_left(struct ezio *, int n);
+void ezio_scroll_right(struct ezio *, int n);
+void ezio_scroll_up(struct ezio *, int n);
+void ezio_scroll_down(struct ezio *, int n);
+bool ezio_chars_differ(const struct ezio *, const struct ezio *,
+ int x0, int x1, int *xp, int *yp);
+
+#endif /* ezio.h */
diff --git a/extras/ezio/ezio3.ti b/extras/ezio/ezio3.ti
new file mode 100644
index 00000000..0bbcb398
--- /dev/null
+++ b/extras/ezio/ezio3.ti
@@ -0,0 +1,21 @@
+# Copyright (C) 2008, 2009 Nicira Networks, Inc.
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty. This file is offered
+# as-is, without warranty of any kind.
+
+ezio3|16x2 EZIO3 LCD display,
+ cols#40, lines#2, it#8, am, xenl, npc,
+ bel=, clear=\E[H\E[J, cr=^M,
+ cub=\E[%p1%dD, cub1=^H, cud=\E[%p1%dB, cud1=^J,
+ cuf=\E[%p1%dC, cuf1=\E[C$<2>,
+ cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA,
+ cuu1=\E[A, ed=\E[J, el=\E[K, el1=\E[1K,
+ home=\E[H, ht=^I, ind=^J, kbs=^H,
+ kcub1=\E[D, kcud1=\E[B, kcuf1=\E[C, kcuu1=\E[A,
+ civis=\E[1r, cnorm=\E[2r, cvvis=\E[3r,
+ ri=\EM, rs2=\Ec, rmacs=^O, smacs=^N,
+ dico=\E[%p1%d;%p2%d;%p3%d;%p4%d;%p5%d;%p6%d;%p7%d;%p8%d;%p9%dp,
+ cico=\E[%p1%dq,
+ acsc=}\355\,\177+\176~\245f\337{\367,
+
diff --git a/extras/ezio/ovs-switchui.c b/extras/ezio/ovs-switchui.c
new file mode 100644
index 00000000..6fbf2523
--- /dev/null
+++ b/extras/ezio/ovs-switchui.c
@@ -0,0 +1,3026 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#include <config.h>
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <curses.h>
+#include <errno.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <math.h>
+#include <pcre.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <term.h>
+#include <unistd.h>
+#include "command-line.h"
+#include "daemon.h"
+#include "dynamic-string.h"
+#include "ezio.h"
+#include "fatal-signal.h"
+#include "netdev.h"
+#include "ofpbuf.h"
+#include "openflow/nicira-ext.h"
+#include "openflow/openflow.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "random.h"
+#include "rconn.h"
+#include "socket-util.h"
+#include "svec.h"
+#include "timeval.h"
+#include "util.h"
+#include "vconn.h"
+#include "xtoxll.h"
+
+#define THIS_MODULE VLM_switchui
+#include "vlog.h"
+
+static void parse_options(int argc, char *argv[]);
+static void usage(void);
+
+static void initialize_terminal(void);
+static void restore_terminal(void *aux);
+
+enum priority {
+ P_STATUS = 5,
+ P_PROGRESS = 10,
+ P_WARNING = 15,
+ P_ERROR = 20,
+ P_FATAL = 25
+};
+
+struct message;
+static void emit(struct message **, enum priority, const char *, ...)
+ PRINTF_FORMAT(3, 4);
+static void emit_function(struct message **, enum priority,
+ void (*function)(void *aux), void *aux);
+static int shown(struct message **);
+static void clear_messages(void);
+static bool empty_message(const struct message *);
+static struct message *best_message(void);
+static struct message *next_message(struct message *);
+static struct message *prev_message(struct message *);
+static void put_message(const struct message *);
+static void message_shown(struct message *);
+static void age_messages(void);
+
+struct pair {
+ char *name;
+ char *value;
+};
+
+struct dict {
+ struct pair *pairs;
+ size_t n, max;
+};
+
+static void dict_init(struct dict *);
+static void dict_add(struct dict *, const char *name, const char *value);
+static void dict_add_nocopy(struct dict *, char *name, char *value);
+static void dict_delete(struct dict *, const char *name);
+static void dict_parse(struct dict *, const char *data, size_t nbytes);
+static void dict_free(struct dict *);
+static bool dict_lookup(const struct dict *,
+ const char *name, const char **value);
+static int dict_get_int(const struct dict *, const char *name, int def);
+static bool dict_get_bool(const struct dict *, const char *name, bool def);
+static const char *dict_get_string(const struct dict *,
+ const char *name, const char *def);
+static uint32_t dict_get_ip(const struct dict *, const char *name);
+
+static void addf(const char *format, ...) PRINTF_FORMAT(1, 2);
+
+static void fetch_status(struct rconn *, struct dict *, long long int timeout);
+static bool parse_reply(void *, struct dict *, uint32_t xid);
+static void compose_messages(const struct dict *, struct rconn *rconn);
+
+static void show_flows(struct rconn *);
+static void show_dpid_ip(struct rconn *, const struct dict *);
+static void show_secchan_state(const struct dict *);
+static void show_fail_open_state(const struct dict *);
+static void show_discovery_state(const struct dict *);
+static void show_remote_state(const struct dict *);
+static void show_data_rates(struct rconn *, const struct dict *);
+
+static void init_reboot_notifier(void);
+static bool show_reboot_state(void);
+
+static void show_string(const char *string);
+static void block_until(long long timeout);
+static void menu(const struct dict *);
+static void drain_keyboard_buffer(void);
+
+static const char *progress(void);
+
+int
+main(int argc, char *argv[])
+{
+ struct rconn *rconn;
+ struct message *msg;
+ int countdown = 5;
+ bool user_selected;
+ bool debug_mode;
+
+ /* Tracking keystroke repeat counts. */
+ int last_key = 0;
+ long long int last_key_time = 0;
+ int repeat_count = 0;
+
+ set_program_name(argv[0]);
+ time_init();
+ vlog_init();
+ parse_options(argc, argv);
+ signal(SIGPIPE, SIG_IGN);
+ vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_EMER);
+ init_reboot_notifier();
+
+ argc -= optind;
+ argv += optind;
+ if (argc != 1) {
+ ovs_fatal(0, "exactly one non-option argument required; "
+ "use --help for help");
+ }
+
+ rconn = rconn_new(argv[0], 5, 5);
+
+ die_if_already_running();
+ daemonize();
+
+ initialize_terminal();
+ fatal_signal_add_hook(restore_terminal, NULL, true);
+
+ msg = NULL;
+ countdown = 0;
+ user_selected = false;
+ debug_mode = false;
+ for (;;) {
+ struct dict dict;
+ long long timeout = time_msec() + 1000;
+
+ clear_messages();
+
+ dict_init(&dict);
+ fetch_status(rconn, &dict, timeout);
+ dict_add(&dict, "debug", debug_mode ? "true" : "false");
+ compose_messages(&dict, rconn);
+
+ if (countdown) {
+ if (!empty_message(msg)) {
+ countdown--;
+ } else {
+ msg = user_selected ? next_message(msg) : best_message();
+ countdown = 5;
+ }
+ } else {
+ msg = best_message();
+ countdown = 5;
+ user_selected = false;
+ }
+ if (!user_selected) {
+ message_shown(msg);
+ }
+
+ do {
+ for (;;) {
+ int c = getch();
+ if (c == ERR) {
+ break;
+ }
+
+ if (c != last_key || time_msec() > last_key_time + 250) {
+ repeat_count = 0;
+ }
+ last_key = c;
+ last_key_time = time_msec();
+ repeat_count++;
+
+ if (c == KEY_DOWN || c == KEY_UP) {
+ msg = (c == KEY_DOWN ? next_message(msg)
+ : prev_message(msg));
+ countdown = 5;
+ user_selected = true;
+ } else if (c == '\r' || c == '\n') {
+ countdown = 60;
+ user_selected = true;
+ if (repeat_count >= 20) {
+ debug_mode = !debug_mode;
+ show_string(debug_mode
+ ? "Debug Mode\nEnabled"
+ : "Debug Mode\nDisabled");
+ }
+ } else if (c == '\b' || c == '\x7f' ||
+ c == '\x1b' || c == KEY_BACKSPACE || c == KEY_DC) {
+ menu(&dict);
+ drain_keyboard_buffer();
+ break;
+ }
+ }
+
+ erase();
+ curs_set(0);
+ move(0, 0);
+ put_message(msg);
+ refresh();
+
+ poll_fd_wait(STDIN_FILENO, POLLIN);
+ poll_timer_wait(timeout - time_msec());
+ poll_block();
+ } while (time_msec() < timeout);
+ age_messages();
+ dict_free(&dict);
+ }
+
+ return 0;
+}
+
+static void
+compose_messages(const struct dict *dict, struct rconn *rconn)
+{
+ if (!show_reboot_state()) {
+ show_flows(rconn);
+ show_dpid_ip(rconn, dict);
+ show_secchan_state(dict);
+ show_fail_open_state(dict);
+ show_discovery_state(dict);
+ show_remote_state(dict);
+ show_data_rates(rconn, dict);
+ }
+}
+
+struct put_flows_data {
+ struct rconn *rconn;
+ uint32_t xid;
+ uint32_t flow_count;
+ bool got_reply;
+};
+
+static void
+parse_flow_reply(void *data, struct put_flows_data *pfd)
+{
+ struct ofp_header *oh;
+ struct ofp_stats_reply *rpy;
+ struct ofp_aggregate_stats_reply *asr;
+ const size_t min_size = sizeof *rpy + sizeof *asr;
+
+ oh = data;
+ if (ntohs(oh->length) < min_size) {
+ VLOG_WARN("reply is too short (%"PRIu16")", ntohs(oh->length));
+ return;
+ }
+ if (oh->xid != pfd->xid) {
+ VLOG_WARN("xid 0x%08"PRIx32" != expected 0x%08"PRIx32,
+ oh->xid, pfd->xid);
+ return;
+ }
+ if (oh->type != OFPT_STATS_REPLY) {
+ VLOG_WARN("reply is wrong type %"PRIu8, oh->type);
+ return;
+ }
+
+ rpy = data;
+ if (rpy->type != htons(OFPST_AGGREGATE)) {
+ VLOG_WARN("reply has wrong stat type ID %08"PRIx16, rpy->type);
+ return;
+ }
+
+ asr = (struct ofp_aggregate_stats_reply *) rpy->body;
+ pfd->flow_count = ntohl(asr->flow_count);
+ pfd->got_reply = true;
+}
+
+static bool
+have_icons(void)
+{
+ const char *dico = tigetstr("dico");
+ return dico && dico != (const char *) -1;
+}
+
+static void
+set_icon(int num, int r0, int r1, int r2, int r3, int r4, int r5, int r6,
+ int r7)
+{
+ if (have_icons()) {
+ putp(tparm(tigetstr("dico"), num, r0, r1, r2, r3, r4, r5, r6, r7));
+ }
+}
+
+static void
+set_repeated_icon(int num, int row)
+{
+ set_icon(num, row, row, row, row, row, row, row, row);
+}
+
+#if 0
+static void
+set_brick_icon(int num, int n_solid)
+{
+ const static int rows[6] = {_____, X____, XX___, XXX__, XXXX_, XXXXX};
+ set_repeated_icon(num, rows[n_solid < 0 ? 0
+ : n_solid > 5 ? 5
+ : n_solid]);
+}
+#endif
+
+static int
+icon_char(int num, int alternate)
+{
+ return have_icons() ? 0x80 | num | A_ALTCHARSET : alternate;
+}
+
+static void
+put_icon(int num, char alternate)
+{
+ addch(icon_char(num, alternate));
+}
+
+#if 0
+static void
+bar_graph(int n_chars, int n_pixels)
+{
+ int i;
+
+ if (n_pixels < 0) {
+ n_pixels = 0;
+ } else if (n_pixels > n_chars * 5) {
+ n_pixels = n_chars * 5;
+ }
+
+ if (n_pixels > 5) {
+ set_brick_icon(0, 5);
+ for (i = 0; i < n_pixels / 5; i++) {
+ put_icon(0, "#");
+ }
+ }
+ if (n_pixels % 5) {
+ set_brick_icon(1, n_pixels % 5);
+ put_icon(1, "#");
+ }
+}
+#endif
+
+static void
+put_flows(void *pfd_)
+{
+ struct put_flows_data *pfd = pfd_;
+ static struct rconn_packet_counter *counter;
+ char host[64];
+
+ if (!counter) {
+ counter = rconn_packet_counter_create();
+ }
+
+ if (!pfd->xid) {
+ struct ofp_stats_request *rq;
+ struct ofp_aggregate_stats_request *asr;
+ struct ofpbuf *b;
+
+ pfd->xid = random_uint32();
+ rq = make_openflow_xid(sizeof *rq, OFPT_STATS_REQUEST,
+ pfd->xid, &b);
+ rq->type = htons(OFPST_AGGREGATE);
+ rq->flags = htons(0);
+ asr = ofpbuf_put_uninit(b, sizeof *asr);
+ memset(asr, 0, sizeof *asr);
+ asr->match.wildcards = htonl(OFPFW_ALL);
+ asr->table_id = 0xff;
+ asr->out_port = htons(OFPP_NONE);
+ update_openflow_length(b);
+ rconn_send_with_limit(pfd->rconn, b, counter, 10);
+ }
+
+ if (!pfd->got_reply) {
+ int i;
+
+ rconn_run(pfd->rconn);
+ for (i = 0; i < 50; i++) {
+ struct ofpbuf *b;
+
+ b = rconn_recv(pfd->rconn);
+ if (!b) {
+ break;
+ }
+
+ parse_flow_reply(b->data, pfd);
+ ofpbuf_delete(b);
+ if (pfd->got_reply) {
+ break;
+ }
+ }
+ }
+
+ gethostname(host, sizeof host);
+ host[sizeof host - 1] = '\0';
+ if (strlen(host) + 6 <= 16) {
+ addf("Host: %s\n", host);
+ } else {
+ addf("%s\n", host);
+ }
+ if (pfd->got_reply) {
+ addf("Flows: %"PRIu32, pfd->flow_count);
+ }
+
+ if (!pfd->got_reply) {
+ rconn_run_wait(pfd->rconn);
+ rconn_recv_wait(pfd->rconn);
+ }
+}
+
+static void
+show_flows(struct rconn *rconn)
+{
+ static struct message *m;
+ static struct put_flows_data pfd;
+
+ memset(&pfd, 0, sizeof pfd);
+ pfd.rconn = rconn;
+ emit_function(&m, P_STATUS, put_flows, &pfd);
+
+}
+
+struct put_dpid_ip_data {
+ struct rconn *rconn;
+ uint32_t xid;
+ uint64_t dpid;
+ char ip[16];
+ bool got_reply;
+};
+
+static void
+parse_dp_reply(void *data, struct put_dpid_ip_data *pdid)
+{
+ struct ofp_switch_features *osf;
+ struct ofp_header *oh;
+
+ oh = data;
+ if (ntohs(oh->length) < sizeof *osf) {
+ VLOG_WARN("reply is too short (%"PRIu16")", ntohs(oh->length));
+ return;
+ }
+ if (oh->xid != pdid->xid) {
+ VLOG_WARN("xid 0x%08"PRIx32" != expected 0x%08"PRIx32,
+ oh->xid, pdid->xid);
+ return;
+ }
+ if (oh->type != OFPT_FEATURES_REPLY) {
+ VLOG_WARN("reply is wrong type %"PRIu8, oh->type);
+ return;
+ }
+
+ osf = data;
+ pdid->dpid = ntohll(osf->datapath_id);
+ pdid->got_reply = true;
+}
+
+static void
+put_dpid_id(void *pdid_)
+{
+ struct put_dpid_ip_data *pdid = pdid_;
+ static struct rconn_packet_counter *counter;
+
+ if (!counter) {
+ counter = rconn_packet_counter_create();
+ }
+
+ if (!pdid->xid) {
+ struct ofp_header *oh;
+ struct ofpbuf *b;
+
+ pdid->xid = random_uint32();
+ oh = make_openflow_xid(sizeof *oh, OFPT_FEATURES_REQUEST,
+ pdid->xid, &b);
+ rconn_send_with_limit(pdid->rconn, b, counter, 10);
+ }
+
+ if (!pdid->got_reply) {
+ int i;
+
+ rconn_run(pdid->rconn);
+ for (i = 0; i < 50; i++) {
+ struct ofpbuf *b;
+
+ b = rconn_recv(pdid->rconn);
+ if (!b) {
+ break;
+ }
+
+ parse_dp_reply(b->data, pdid);
+ ofpbuf_delete(b);
+ if (pdid->got_reply) {
+ break;
+ }
+ }
+ }
+
+ addf("DP: ");
+ if (pdid->got_reply) {
+ addf("%012"PRIx64, pdid->dpid);
+ }
+ addf("\nIP: %s", pdid->ip);
+
+ if (!pdid->got_reply) {
+ rconn_run_wait(pdid->rconn);
+ rconn_recv_wait(pdid->rconn);
+ }
+}
+
+static void
+show_dpid_ip(struct rconn *rconn, const struct dict *dict)
+{
+ static struct message *m;
+ static struct put_dpid_ip_data pdid;
+ const char *is_connected, *local_ip;
+
+ dict_lookup(dict, "local.is-connected", &is_connected);
+ dict_lookup(dict, "in-band.local-ip", &local_ip);
+ if (!is_connected && !local_ip) {
+ /* If we're not connected to the datapath and don't have a local IP,
+ * then we won't have anything useful to show anyhow. */
+ return;
+ }
+
+ memset(&pdid, 0, sizeof pdid);
+ pdid.rconn = rconn;
+ ovs_strlcpy(pdid.ip, local_ip ? local_ip : "", sizeof pdid.ip);
+ emit_function(&m, P_STATUS, put_dpid_id, &pdid);
+}
+
+static size_t
+dict_find(const struct dict *dict, const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < dict->n; i++) {
+ const struct pair *p = &dict->pairs[i];
+ if (!strcmp(p->name, name)) {
+ return i;
+ }
+ }
+
+ return SIZE_MAX;
+}
+
+static bool
+dict_lookup(const struct dict *dict, const char *name, const char **value)
+{
+ size_t idx = dict_find(dict, name);
+ if (idx != SIZE_MAX) {
+ *value = dict->pairs[idx].value;
+ return true;
+ } else {
+ *value = NULL;
+ return false;
+ }
+}
+
+static const char *
+dict_get(const struct dict *dict, const char *name)
+{
+ const char *value;
+ return dict_lookup(dict, name, &value) ? value : NULL;
+}
+
+static int
+dict_get_int(const struct dict *dict, const char *name, int def)
+{
+ const char *value;
+ return dict_lookup(dict, name, &value) ? atoi(value) : def;
+}
+
+static bool
+dict_get_bool(const struct dict *dict, const char *name, bool def)
+{
+ const char *value;
+ if (dict_lookup(dict, name, &value)) {
+ if (!strcmp(value, "true")) {
+ return true;
+ }
+ if (!strcmp(value, "false")) {
+ return false;
+ }
+ }
+ return def;
+}
+
+static const char *
+dict_get_string(const struct dict *dict, const char *name, const char *def)
+{
+ const char *value;
+ return dict_lookup(dict, name, &value) ? value : def;
+}
+
+static uint32_t
+dict_get_ip(const struct dict *dict, const char *name)
+{
+ struct in_addr in;
+ return (inet_aton(dict_get_string(dict, name, ""), &in) ? in.s_addr
+ : htonl(0));
+}
+
+static void
+addf(const char *format, ...)
+{
+ char buf[128];
+ va_list args;
+
+ va_start(args, format);
+ vsnprintf(buf, sizeof buf, format, args);
+ va_end(args);
+
+ addstr(buf);
+}
+
+static void
+show_secchan_state(const struct dict *dict)
+{
+ static struct message *msg;
+ const char *is_connected;
+
+ if (!dict_lookup(dict, "remote.is-connected", &is_connected)) {
+ /* Secchan not running or not responding. */
+ emit(&msg, P_ERROR, "Switch disabled");
+ }
+}
+
+static const char *
+discovery_state_label(const char *name)
+{
+ static struct dict *states;
+ if (!states) {
+ states = xmalloc(sizeof *states);
+ dict_init(states);
+ dict_add(states, "INIT", "Init");
+ dict_add(states, "INIT_REBOOT", "Init");
+ dict_add(states, "REBOOTING", "Init");
+ dict_add(states, "SELECTING", "Searching");
+ dict_add(states, "REQUESTING", "Requesting");
+ dict_add(states, "BOUND", "Got");
+ dict_add(states, "RENEWING", "Renewing");
+ dict_add(states, "REBINDING", "Rebinding");
+ dict_add(states, "RELEASED", "Released");
+ }
+ return dict_get_string(states, name, "Error");
+}
+
+static void
+show_discovery_state(const struct dict *dict)
+{
+ static struct message *m_bound, *m_other;
+ struct message **m;
+ const char *state, *ip;
+ enum priority priority;
+ int state_elapsed;
+
+ state = dict_get_string(dict, "discovery.state", NULL);
+ if (!state) {
+ return;
+ }
+ ip = dict_get_string(dict, "discovery.ip", NULL);
+ state_elapsed = dict_get_int(dict, "discovery.state-elapsed", 0);
+
+ if (!strcmp(state, "BOUND")) {
+ m = &m_bound;
+ priority = P_STATUS;
+ } else {
+ m = &m_other;
+ priority = P_PROGRESS;
+ }
+ emit(m, priority, "Discovery %s\n%s",
+ progress(), discovery_state_label(state));
+ if (ip) {
+ emit(m, priority, " %s", ip);
+ }
+}
+
+static void
+human_time(int seconds, char *buf, size_t size)
+{
+ const char *sign = "";
+ if (seconds < 0) {
+ sign = "-";
+ seconds = seconds == INT_MIN ? INT_MAX : -seconds;
+ }
+
+ if (seconds <= 60) {
+ snprintf(buf, size, "%s%d s", sign, seconds);
+ } else if (seconds <= 60 * 60) {
+ snprintf(buf, size, "%s%d min", sign, seconds / 60);
+ } else if (seconds <= 60 * 60 * 24 * 2) {
+ snprintf(buf, size, "%s%d h", sign, seconds / 60 / 60);
+ } else {
+ snprintf(buf, size, "%s%d days", sign, seconds / 60 / 60 / 24);
+ }
+}
+
+static void
+show_fail_open_state(const struct dict *dict)
+{
+ static struct message *m;
+ int cur_duration, trigger_duration;
+
+ if (!dict_get_bool(dict, "fail-open.triggered", false)) {
+ return;
+ }
+ trigger_duration = dict_get_int(dict, "fail-open.trigger-duration", 0);
+ cur_duration = dict_get_int(dict, "fail-open.current-duration", 0);
+ if (shown(&m) < 5) {
+ emit(&m, P_WARNING, "Failed open %s\nafter %d secs",
+ progress(), trigger_duration);
+ } else {
+ char buf[16];
+ human_time(cur_duration - trigger_duration, buf, sizeof buf);
+ emit(&m, P_WARNING, "In fail open for\n%s now %s", buf, progress());
+ }
+}
+
+static const char *
+progress(void)
+{
+ return "..." + (3 - (unsigned int) time_now() % 4);
+}
+
+static void
+show_remote_state(const struct dict *dict)
+{
+ bool debug_mode = dict_get_bool(dict, "debug", false);
+ const char *state, *is_connected;
+
+ state = dict_get_string(dict, "remote.state", NULL);
+ if (!state) {
+ return;
+ }
+ is_connected = dict_get_string(dict, "remote.is-connected", "false");
+ if (!strcmp(is_connected, "true")) {
+ if (debug_mode) {
+ static struct message *m_connected;
+ char buf[16];
+ human_time(dict_get_int(dict, "remote.last-connection", 0),
+ buf, sizeof buf);
+ emit(&m_connected, P_STATUS,
+ "Connected for\nlast %s %s", buf, progress());
+ }
+
+ if (!strcmp(state, "IDLE")) {
+ static struct message *m_idle;
+ emit(&m_idle, P_PROGRESS, "Sent idle probe");
+ }
+
+ if (debug_mode) {
+ const char *name = dict_get_string(dict, "remote.name", NULL);
+ if (name) {
+ static struct message *m_name;
+ emit(&m_name, P_STATUS, "Connected to\n%s", name);
+ }
+ }
+ } else {
+ int elapsed, backoff;
+ const char *name, *error;
+
+ elapsed = dict_get_int(dict, "remote.state-elapsed", 0);
+ backoff = dict_get_int(dict, "remote.backoff", 0);
+ name = dict_get_string(dict, "remote.name", "unknown");
+ state = dict_get_string(dict, "remote.state", "VOID");
+ error = dict_get_string(dict, "remote.last-connect-error", NULL);
+ if (!strcmp(state, "VOID")) {
+ static struct message *m;
+ emit(&m, P_PROGRESS, "Controller not\nfound");
+ } else if (!strcmp(state, "BACKOFF")) {
+ static struct message *m[3];
+ char buf[16];
+
+ if (error) {
+ emit(&m[0], P_PROGRESS, "Connect failed:\n%s", error);
+ }
+ emit(&m[2], P_STATUS, "Last connected\n%s ago", buf);
+ emit(&m[1], P_PROGRESS,
+ "Disconnected\nReconnect in %d", backoff - elapsed);
+ human_time(dict_get_int(dict, "remote.last-connection", 0),
+ buf, sizeof buf);
+ } else if (!strcmp(state, "CONNECTING")) {
+ static struct message *m;
+ emit(&m, P_PROGRESS, "Connecting %s\n%s", progress(), name);
+ }
+ }
+}
+
+static void
+fetch_status(struct rconn *rconn, struct dict *dict, long long timeout)
+{
+ static struct rconn_packet_counter *counter;
+ static uint32_t xid;
+ struct nicira_header *rq;
+ struct ofpbuf *b;
+ int retval;
+
+ if (!counter) {
+ counter = rconn_packet_counter_create();
+ }
+ if (!xid) {
+ xid = random_uint32();
+ }
+
+ rq = make_openflow_xid(sizeof *rq, OFPT_VENDOR, ++xid, &b);
+ rq->vendor = htonl(NX_VENDOR_ID);
+ rq->subtype = htonl(NXT_STATUS_REQUEST);
+ retval = rconn_send_with_limit(rconn, b, counter, 10);
+ if (retval) {
+ /* continue into the loop so that we pause for a while */
+ }
+
+ while (time_msec() < timeout) {
+ int i;
+
+ rconn_run(rconn);
+
+ for (i = 0; i < 50; i++) {
+ struct ofpbuf *b;
+ bool got_reply;
+
+ b = rconn_recv(rconn);
+ if (!b) {
+ break;
+ }
+
+ got_reply = parse_reply(b->data, dict, xid);
+ ofpbuf_delete(b);
+ if (got_reply) {
+ return;
+ }
+ }
+
+ rconn_run_wait(rconn);
+ rconn_recv_wait(rconn);
+ poll_timer_wait(timeout - time_msec());
+ poll_block();
+ }
+}
+
+static bool
+parse_reply(void *data, struct dict *dict, uint32_t xid)
+{
+ struct ofp_header *oh;
+ struct nicira_header *rpy;
+
+ oh = data;
+ if (ntohs(oh->length) < sizeof *rpy) {
+ VLOG_WARN("reply is too short (%"PRIu16")", ntohs(oh->length));
+ return false;
+ }
+ if (oh->xid != xid) {
+ VLOG_WARN("xid 0x%08"PRIx32" != expected 0x%08"PRIx32, oh->xid, xid);
+ return false;
+ }
+ if (oh->type != OFPT_VENDOR) {
+ VLOG_WARN("reply is wrong type %"PRIu8, oh->type);
+ return false;
+ }
+
+ rpy = data;
+ if (rpy->vendor != htonl(NX_VENDOR_ID)) {
+ VLOG_WARN("reply has wrong vendor ID %08"PRIx32, rpy->vendor);
+ return false;
+ }
+ if (rpy->subtype != htonl(NXT_STATUS_REPLY)) {
+ VLOG_WARN("reply has wrong subtype %08"PRIx32, rpy->subtype);
+ return false;
+ }
+
+ dict_parse(dict, (const char *) (rpy + 1),
+ ntohs(oh->length) - sizeof *rpy);
+ return true;
+}
+
+static void
+dict_parse(struct dict *dict, const char *data, size_t nbytes)
+{
+ char *save_ptr = NULL;
+ char *copy, *name;
+
+ copy = xmemdup0(data, nbytes);
+ for (name = strtok_r(copy, "=", &save_ptr); name;
+ name = strtok_r(NULL, "=", &save_ptr))
+ {
+ char *value = strtok_r(NULL, "\n", &save_ptr);
+ if (!value) {
+ break;
+ }
+ dict_add(dict, name, value);
+ }
+ free(copy);
+}
+
+static void
+dict_init(struct dict *dict)
+{
+ dict->n = 0;
+ dict->max = 16;
+ dict->pairs = xmalloc(sizeof *dict->pairs * dict->max);
+}
+
+static void
+dict_add(struct dict *dict, const char *name, const char *value)
+{
+ dict_add_nocopy(dict, xstrdup(name), xstrdup(value));
+}
+
+static void
+dict_add_nocopy(struct dict *dict, char *name, char *value)
+{
+ struct pair *p;
+
+ if (dict->n >= dict->max) {
+ dict->max *= 2;
+ dict->pairs = xrealloc(dict->pairs, sizeof *dict->pairs * dict->max);
+ }
+ p = &dict->pairs[dict->n++];
+ p->name = name;
+ p->value = value;
+}
+
+static void
+dict_delete(struct dict *dict, const char *name)
+{
+ size_t idx;
+ while ((idx = dict_find(dict, name)) != SIZE_MAX) {
+ struct pair *pair = &dict->pairs[idx];
+ free(pair->name);
+ free(pair->value);
+ dict->pairs[idx] = dict->pairs[--dict->n];
+ }
+}
+
+static void
+dict_free(struct dict *dict)
+{
+ if (dict) {
+ size_t i;
+
+ for (i = 0; i < dict->n; i++) {
+ free(dict->pairs[i].name);
+ free(dict->pairs[i].value);
+ }
+ free(dict->pairs);
+ }
+}
+
+static void
+initialize_terminal(void)
+{
+ initscr();
+ cbreak();
+ noecho();
+ nonl();
+ intrflush(stdscr, FALSE);
+ keypad(stdscr, TRUE);
+ nodelay(stdscr, TRUE);
+ typeahead(-1);
+ scrollok(stdscr, TRUE);
+}
+
+static void
+restore_terminal(void *aux UNUSED)
+{
+ endwin();
+}
+
+struct byte_count {
+ long long int when;
+ uint64_t tx_bytes;
+};
+
+struct show_rates_data {
+ struct rconn *rconn;
+ uint32_t xid;
+ struct byte_count prev, now;
+ bool got_reply;
+};
+
+static void
+parse_port_reply(void *data, struct show_rates_data *rates)
+{
+ struct ofp_header *oh;
+ struct ofp_stats_reply *rpy;
+ struct ofp_port_stats *ops;
+ size_t n_ports;
+ size_t i;
+
+ oh = data;
+ if (ntohs(oh->length) < sizeof *rpy) {
+ VLOG_WARN("reply is too short (%"PRIu16")", ntohs(oh->length));
+ return;
+ }
+ if (oh->xid != rates->xid) {
+ VLOG_WARN("xid 0x%08"PRIx32" != expected 0x%08"PRIx32,
+ oh->xid, rates->xid);
+ return;
+ }
+ if (oh->type != OFPT_STATS_REPLY) {
+ VLOG_WARN("reply is wrong type %"PRIu8, oh->type);
+ return;
+ }
+
+ rpy = data;
+ if (rpy->type != htons(OFPST_PORT)) {
+ VLOG_WARN("reply has wrong stat type ID %08"PRIx16, rpy->type);
+ return;
+ }
+
+ n_ports = ((ntohs(oh->length) - offsetof(struct ofp_stats_reply, body))
+ / sizeof *ops);
+ ops = (struct ofp_port_stats *) rpy->body;
+ rates->prev = rates->now;
+ rates->now.when = time_msec();
+ rates->now.tx_bytes = UINT64_MAX;
+ for (i = 0; i < n_ports; i++, ops++) {
+ if (ops->tx_bytes != htonll(UINT64_MAX)) {
+ if (rates->now.tx_bytes == UINT64_MAX) {
+ rates->now.tx_bytes = 0;
+ }
+ rates->now.tx_bytes += ntohll(ops->tx_bytes);
+ }
+ }
+ rates->got_reply = true;
+}
+
+static void
+dump_graph(const bool graph[80])
+{
+ signed char icons[32];
+ int n_icons = 3;
+ int i;
+
+ memset(icons, -1, sizeof icons);
+ for (i = 0; i < 16; i++) {
+ uint8_t row;
+ int j;
+
+ row = 0;
+ for (j = 0; j < 5; j++) {
+ row = (row << 1) | graph[i * 5 + j];
+ }
+ if (!row) {
+ addch(' ');
+ continue;
+ }
+
+ if (icons[row] < 0) {
+ if (n_icons >= 8) {
+ addch('X');
+ continue;
+ }
+ set_repeated_icon(n_icons, row);
+ icons[row] = n_icons++;
+ }
+ put_icon(icons[row], row == 0x1f ? '#' : ' ');
+ }
+}
+
+static void
+do_show_data_rates(void *rates_)
+{
+ struct show_rates_data *rates = rates_;
+ static struct rconn_packet_counter *counter;
+ bool graph[80];
+
+ if (!counter) {
+ counter = rconn_packet_counter_create();
+ }
+ if (!rates->xid) {
+ struct ofp_stats_request *rq;
+ struct ofpbuf *b;
+
+ rates->xid = random_uint32();
+ rq = make_openflow_xid(sizeof *rq, OFPT_STATS_REQUEST,
+ rates->xid, &b);
+ rq->type = htons(OFPST_PORT);
+ rq->flags = htons(0);
+ rconn_send_with_limit(rates->rconn, b, counter, 10);
+ }
+
+ if (!rates->got_reply) {
+ int i;
+
+ rconn_run(rates->rconn);
+ for (i = 0; i < 50; i++) {
+ struct ofpbuf *b;
+
+ b = rconn_recv(rates->rconn);
+ if (!b) {
+ break;
+ }
+
+ parse_port_reply(b->data, rates);
+ ofpbuf_delete(b);
+ if (rates->got_reply) {
+ break;
+ }
+ }
+ }
+
+ set_icon(0,
+ e_____,
+ e_____,
+ e_____,
+ e__X__,
+ e__X__,
+ e__X_X,
+ e__XX_,
+ e__X_X);
+ set_icon(1,
+ e_____,
+ e_____,
+ e_____,
+ eX___X,
+ eXX_XX,
+ eX_X_X,
+ eX___X,
+ eX___X);
+ set_icon(2,
+ e_____,
+ e_____,
+ e_____,
+ e_XXX_,
+ eX____,
+ eX_XXX,
+ eX___X,
+ e_XXX_);
+
+ memset(graph, 0, sizeof graph);
+ graph[24] = 1;
+ graph[48] = 1;
+ graph[72] = 1;
+
+ addstr("TX: ");
+ put_icon(0, 'k');
+ addstr(" ");
+ put_icon(1, 'M');
+ addstr(" ");
+ put_icon(2, 'G');
+ addch('\n');
+
+ if (rates->now.tx_bytes != UINT64_MAX
+ && rates->prev.tx_bytes != UINT64_MAX
+ && rates->now.when - rates->prev.when > 500
+ && time_msec() - rates->now.when < 2000)
+ {
+ uint64_t bits = (rates->now.tx_bytes - rates->prev.tx_bytes) * 8;
+ uint64_t msecs = rates->now.when - rates->prev.when;
+ double bps = (double) bits * 1000.0 / msecs;
+ int pixels = bps > 0 ? log(bps) / log(10.0) * 8 + .5 : 0;
+ if (pixels < 0) {
+ pixels = 0;
+ } else if (pixels > 80) {
+ pixels = 80;
+ }
+ memset(graph, 1, pixels);
+ }
+
+ dump_graph(graph);
+
+ if (!rates->got_reply) {
+ rconn_run_wait(rates->rconn);
+ rconn_recv_wait(rates->rconn);
+ }
+}
+
+static void
+show_data_rates(struct rconn *rconn, const struct dict *dict)
+{
+ static struct message *m;
+ static struct show_rates_data rates;
+ const char *is_connected, *local_ip;
+ static bool inited = false;
+
+ dict_lookup(dict, "local.is-connected", &is_connected);
+ dict_lookup(dict, "in-band.local-ip", &local_ip);
+ if (!is_connected && !local_ip) {
+ /* If we're not connected to the datapath and don't have a local IP,
+ * then we won't have anything useful to show anyhow. */
+ return;
+ }
+
+ rates.rconn = rconn;
+ rates.xid = 0;
+ rates.got_reply = false;
+ if (!inited) {
+ rates.now.tx_bytes = UINT64_MAX;
+ rates.prev.tx_bytes = UINT64_MAX;
+ inited = true;
+ }
+ emit_function(&m, P_STATUS, do_show_data_rates, &rates);
+}
+
+struct message {
+ /* Content. */
+ void (*function)(void *aux);
+ void *aux;
+ char string[128];
+
+ size_t index;
+ enum priority priority;
+ int age;
+ int shown;
+};
+
+static struct message **messages;
+static size_t n_messages, allocated_messages;
+
+static struct message *
+allocate_message(struct message **msgp)
+{
+ if (!*msgp) {
+ /* Allocate and initialize message. */
+ *msgp = xcalloc(1, sizeof **msgp);
+ (*msgp)->index = n_messages;
+
+ /* Add to list of messages. */
+ if (n_messages >= allocated_messages) {
+ allocated_messages = 2 * allocated_messages + 1;
+ messages = xrealloc(messages,
+ sizeof *messages * allocated_messages);
+ }
+ messages[n_messages++] = *msgp;
+ }
+ return *msgp;
+}
+
+static void
+emit(struct message **msgp, enum priority priority, const char *format, ...)
+{
+ struct message *msg = allocate_message(msgp);
+ va_list args;
+ size_t length;
+
+ msg->priority = priority;
+
+ va_start(args, format);
+ length = strlen(msg->string);
+ vsnprintf(msg->string + length, sizeof msg->string - length, format, args);
+ va_end(args);
+}
+
+static void
+emit_function(struct message **msgp, enum priority priority,
+ void (*function)(void *aux), void *aux)
+{
+ struct message *msg = allocate_message(msgp);
+ msg->priority = priority;
+ msg->function = function;
+ msg->aux = aux;
+}
+
+static int
+shown(struct message **msgp)
+{
+ struct message *msg = allocate_message(msgp);
+ return msg->shown;
+}
+
+static void
+clear_messages(void)
+{
+ size_t i;
+
+ for (i = 0; i < n_messages; i++) {
+ struct message *msg = messages[i];
+ msg->string[0] = '\0';
+ msg->function = NULL;
+ }
+}
+
+static struct message *
+best_message(void)
+{
+ struct message *best_msg;
+ int best_score;
+ size_t i;
+
+ best_score = INT_MIN;
+ best_msg = NULL;
+ for (i = 0; i < n_messages; i++) {
+ struct message *msg = messages[i];
+ int score;
+
+ if (empty_message(msg)) {
+ continue;
+ }
+
+ score = msg->priority;
+ if (!msg->shown) {
+ score += msg->age;
+ } else {
+ score -= msg->shown;
+ }
+ if (score > best_score) {
+ best_score = score;
+ best_msg = msg;
+ }
+ }
+ return best_msg;
+}
+
+static void
+message_shown(struct message *msg)
+{
+ if (msg && msg->shown++ > 3600) {
+ msg->shown = 0;
+ }
+}
+
+static bool
+empty_message(const struct message *msg)
+{
+ return !msg || (!msg->string[0] && !msg->function);
+}
+
+static struct message *get_message(size_t index)
+{
+ assert(index <= n_messages || index == SIZE_MAX);
+ return (index < n_messages ? messages[index]
+ : index == SIZE_MAX ? messages[n_messages - 1]
+ : messages[0]);
+}
+
+static struct message *
+next_message(struct message *msg)
+{
+ struct message *p;
+
+ for (p = get_message(msg->index + 1); p != msg;
+ p = get_message(p->index + 1)) {
+ if (!empty_message(p)) {
+ break;
+ }
+ }
+ return p;
+}
+
+static struct message *
+prev_message(struct message *msg)
+{
+ struct message *p;
+
+ for (p = get_message(msg->index - 1); p != msg;
+ p = get_message(p->index - 1)) {
+ if (!empty_message(p)) {
+ break;
+ }
+ }
+ return p;
+}
+
+static void
+put_message(const struct message *m)
+{
+ if (m->string[0]) {
+ addstr(m->string);
+ } else if (m->function) {
+ m->function(m->aux);
+ }
+}
+
+static void
+age_messages(void)
+{
+ size_t i;
+ int load;
+
+ load = 0;
+ for (i = 0; i < n_messages; i++) {
+ struct message *msg = messages[i];
+ if (!empty_message(msg)) {
+ load++;
+ }
+ }
+
+ for (i = 0; i < n_messages; i++) {
+ struct message *msg = messages[i];
+ if (empty_message(msg)) {
+ msg->age = msg->shown = 0;
+ } else {
+ if (msg->age && msg->age % 60 == 0) {
+ msg->shown -= MAX(0, 5 - (load + 6) / 12);
+ if (msg->shown < 0) {
+ msg->shown = 0;
+ }
+ }
+ if (msg->age++ > 3600) {
+ msg->age = 0;
+ }
+ }
+ }
+}
+
+/* Set by SIGUSR1 handler. */
+static volatile sig_atomic_t sigusr1_triggered;
+
+/* The time after which we stop indicating that the switch is rebooting.
+ * (This is just in case the reboot fails.) */
+static time_t reboot_deadline = TIME_MIN;
+
+static void sigusr1_handler(int);
+
+static void
+init_reboot_notifier(void)
+{
+ signal(SIGUSR1, sigusr1_handler);
+}
+
+static void
+sigusr1_handler(int signr UNUSED)
+{
+ sigusr1_triggered = true;
+}
+
+static bool
+show_reboot_state(void)
+{
+ if (sigusr1_triggered) {
+ reboot_deadline = time_now() + 30;
+ sigusr1_triggered = false;
+ }
+ if (time_now() < reboot_deadline) {
+ static struct message *msg;
+ emit(&msg, P_FATAL, "Rebooting");
+ return true;
+ }
+ return false;
+}
+
+struct menu_item {
+ char *text;
+ void (*f)(const struct dict *);
+ int id;
+ bool enabled;
+ int toggle;
+};
+
+struct menu {
+ struct menu_item **items;
+ size_t n_items, allocated_items;
+};
+
+static void menu_init(struct menu *);
+static void menu_free(struct menu *);
+static struct menu_item *menu_add_item(struct menu *, const char *text, ...)
+ PRINTF_FORMAT(2, 3);
+static int menu_show(const struct menu *, int start, bool select);
+
+static void cmd_shell(const struct dict *);
+static void cmd_show_version(const struct dict *);
+static void cmd_configure(const struct dict *);
+static void cmd_setup_pki(const struct dict *);
+static void cmd_browse_status(const struct dict *);
+static void cmd_show_motto(const struct dict *);
+
+static void
+menu_init(struct menu *menu)
+{
+ memset(menu, 0, sizeof *menu);
+}
+
+static void
+menu_free(struct menu *menu)
+{
+ size_t i;
+
+ for (i = 0; i < menu->n_items; i++) {
+ struct menu_item *item = menu->items[i];
+ free(item->text);
+ free(item);
+ }
+ free(menu->items);
+}
+
+static struct menu_item *
+menu_add_item(struct menu *menu, const char *text, ...)
+{
+ struct menu_item *item;
+ va_list args;
+
+ if (menu->n_items >= menu->allocated_items) {
+ menu->allocated_items = 2 * menu->allocated_items + 1;
+ menu->items = xrealloc(menu->items,
+ sizeof *menu->items * menu->allocated_items);
+ }
+ item = menu->items[menu->n_items++] = xmalloc(sizeof *item);
+ va_start(args, text);
+ item->text = xvasprintf(text, args);
+ va_end(args);
+ item->f = NULL;
+ item->id = -1;
+ item->enabled = true;
+ item->toggle = -1;
+ return item;
+}
+
+static void
+menu(const struct dict *dict)
+{
+ bool debug_mode = dict_get_bool(dict, "debug", false);
+ struct menu menu;
+ int choice;
+
+ menu_init(&menu);
+ menu_add_item(&menu, "Exit");
+ menu_add_item(&menu, "Show Version")->f = cmd_show_version;
+ menu_add_item(&menu, "Configure")->f = cmd_configure;
+ menu_add_item(&menu, "Setup PKI")->f = cmd_setup_pki;
+ if (debug_mode) {
+ menu_add_item(&menu, "Browse Status")->f = cmd_browse_status;
+ menu_add_item(&menu, "Shell")->f = cmd_shell;
+ menu_add_item(&menu, "Show Motto")->f = cmd_show_motto;
+ }
+
+ choice = menu_show(&menu, 0, true);
+ if (choice >= 0) {
+ void (*f)(const struct dict *) = menu.items[choice]->f;
+ if (f) {
+ (f)(dict);
+ }
+ }
+
+ menu_free(&menu);
+}
+
+static int
+menu_show(const struct menu *menu, int start, bool select)
+{
+ long long int adjust = LLONG_MAX;
+ int min = 0, max = MAX(menu->n_items - 2, 0);
+ int pos, selection;
+ set_icon(0,
+ eXX___,
+ eXXX__,
+ eXXXX_,
+ eXXXXX,
+ eXXXX_,
+ eXXX__,
+ eXX___,
+ e_____);
+ set_icon(1,
+ eXXXXX,
+ eX___X,
+ eX___X,
+ eX___X,
+ eX___X,
+ eX___X,
+ eXXXXX,
+ e_____);
+ set_icon(2,
+ eXXXXX,
+ eX___X,
+ eXX_XX,
+ eX_X_X,
+ eXX_XX,
+ eX___X,
+ eXXXXX,
+ e_____);
+ if (menu->n_items) {
+ pos = MIN(menu->n_items - 1, MAX(0, start));
+ selection = pos;
+ } else {
+ pos = 0;
+ selection = -1;
+ }
+ for (;;) {
+ int key;
+
+ while ((key = getch()) != ERR) {
+ switch (key) {
+ case KEY_UP:
+ if (select && selection > 0) {
+ selection--;
+ if (selection >= pos) {
+ break;
+ }
+ }
+ if (pos >= min) {
+ pos--;
+ }
+ break;
+
+ case KEY_DOWN:
+ if (select && selection < menu->n_items - 1) {
+ selection++;
+ if (selection <= pos + 1) {
+ break;
+ }
+ }
+ if (pos <= max) {
+ pos++;
+ }
+ break;
+
+ case '\r': case '\n':
+ if (select && selection >= 0 && selection < menu->n_items) {
+ struct menu_item *item = menu->items[selection];
+ if (!item->enabled) {
+ show_string("Item disabled");
+ break;
+ } else if (item->toggle >= 0) {
+ item->toggle = !item->toggle;
+ break;
+ }
+ }
+ return selection;
+
+ case '\b': case '\x7f': case '\x1b':
+ case KEY_BACKSPACE: case KEY_DC:
+ return -1;
+ }
+ adjust = time_msec() + 1000;
+ }
+ if (time_msec() >= adjust && menu->n_items > 1) {
+ if (pos < min) {
+ pos = min;
+ } else if (pos > max) {
+ pos = max;
+ }
+ }
+
+ erase();
+ curs_set(0);
+ move(0, 0);
+ if (!menu->n_items) {
+ addstr("[Empty]");
+ } else {
+ int idx;
+ for (idx = pos; idx < pos + 2; idx++) {
+ size_t width = 40;
+
+ if (select) {
+ width--;
+ if (selection == idx) {
+ put_icon(0, '>');
+ } else {
+ addch(' ');
+ }
+ }
+
+ if (idx < 0) {
+ addstr("[Top]");
+ } else if (idx >= menu->n_items) {
+ addstr("[Bottom]");
+ } else {
+ const struct menu_item *item = menu->items[idx];
+ size_t length = strlen(item->text);
+ if (!item->enabled) {
+ width -= 2;
+ addch('(');
+ }
+ if (item->toggle >= 0) {
+ if (have_icons()) {
+ addch(icon_char(item->toggle ? 2 : 1, 0));
+ width--;
+ } else {
+ addstr(item->toggle ? "[X]" : "[ ]");
+ width -= 3;
+ }
+ }
+ addnstr(item->text, MIN(width, length));
+ if (!item->enabled) {
+ addch(')');
+ }
+ }
+ if (idx == pos) {
+ addch('\n');
+ }
+ }
+ }
+ refresh();
+
+ if (pos < min || pos > max) {
+ poll_timer_wait(adjust - time_msec());
+ }
+ poll_fd_wait(STDIN_FILENO, POLLIN);
+ poll_block();
+ }
+}
+
+static int
+menu_show2(const struct menu *menu, int start, bool select)
+{
+ int pos;
+ if (menu->n_items) {
+ pos = MIN(menu->n_items - 1, MAX(0, start));
+ } else {
+ pos = -1;
+ }
+ set_icon(0,
+ e__X__,
+ e_XXX_,
+ eXXXXX,
+ e__X__,
+ e__X__,
+ e__X__,
+ e__X__,
+ e__X__);
+ set_icon(1,
+ e__X__,
+ e__X__,
+ e__X__,
+ e__X__,
+ e__X__,
+ eXXXXX,
+ e_XXX_,
+ e__X__);
+ for (;;) {
+ int key;
+
+ while ((key = getch()) != ERR) {
+ switch (key) {
+ case KEY_UP:
+ if (pos > 0) {
+ pos--;
+ }
+ break;
+
+ case KEY_DOWN:
+ if (menu->n_items > 0 && pos < menu->n_items - 1) {
+ pos++;
+ }
+ break;
+
+ case '\r': case '\n':
+ if (select && !menu->items[pos]->enabled) {
+ show_string("Item disabled");
+ break;
+ }
+ return pos;
+
+ case '\b': case '\x7f': case '\x1b':
+ case KEY_BACKSPACE: case KEY_DC:
+ return -1;
+ }
+ }
+
+ erase();
+ curs_set(0);
+ move(0, 0);
+ if (pos == -1) {
+ addstr("[Empty]");
+ } else {
+ const struct menu_item *item = menu->items[pos];
+ const char *line1 = item->text;
+ size_t len1 = strcspn(line1, "\n");
+ const char *line2 = line1[len1] ? &line1[len1 + 1] : "";
+ size_t len2 = strcspn(line2, "\n");
+ size_t width = 39 - 2 * !item->enabled;
+
+ /* First line. */
+ addch(pos > 0 ? icon_char(0, '^') : ' ');
+ if (!item->enabled && len1) {
+ addch('(');
+ }
+ addnstr(line1, MIN(len1, width));
+ if (!item->enabled && len1) {
+ addch(')');
+ }
+ addch('\n');
+
+ /* Second line. */
+ addch(pos < menu->n_items - 1 ? icon_char(1, 'V') : ' ');
+ if (!item->enabled && len2) {
+ addch('(');
+ }
+ addnstr(line2, MIN(len2, width));
+ if (!item->enabled && len2) {
+ addch(')');
+ }
+ }
+ refresh();
+
+ poll_fd_wait(STDIN_FILENO, POLLIN);
+ poll_block();
+ }
+}
+
+static bool
+yesno(const char *title, bool def)
+{
+ bool answer = def;
+
+ set_icon(0,
+ eXX___,
+ eXXX__,
+ eXXXX_,
+ eXXXXX,
+ eXXXX_,
+ eXXX__,
+ eXX___,
+ e_____);
+
+ for (;;) {
+ int key;
+
+ while ((key = getch()) != ERR) {
+ switch (key) {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ answer = !answer;
+ break;
+
+ case 'y': case 'Y':
+ answer = true;
+ break;
+
+ case 'n': case 'N':
+ answer = false;
+ break;
+
+ case '\r': case '\n':
+ return answer;
+ }
+ }
+
+ erase();
+ curs_set(0);
+ move(0, 0);
+ addstr(title);
+
+ move(0, 12);
+ addch(answer ? icon_char(0, '>') : ' ');
+ addstr("Yes");
+
+ move(1, 12);
+ addch(!answer ? icon_char(0, '>') : ' ');
+ addstr("No");
+
+ refresh();
+
+ poll_fd_wait(STDIN_FILENO, POLLIN);
+ poll_block();
+ }
+}
+
+static void
+cmd_show_version(const struct dict *dict UNUSED)
+{
+ show_string(VERSION BUILDNR);
+}
+
+static void
+cmd_browse_status(const struct dict *dict)
+{
+ struct menu menu;
+ size_t i;
+
+ menu_init(&menu);
+ for (i = 0; i < dict->n; i++) {
+ const struct pair *p = &dict->pairs[i];
+ menu_add_item(&menu, "%s = %s", p->name, p->value);
+ }
+ menu_show(&menu, 0, false);
+ menu_free(&menu);
+}
+
+static void
+cmd_shell(const struct dict *dict UNUSED)
+{
+ const char *home;
+
+ erase();
+ refresh();
+ endwin();
+
+ printf("Type ^D to exit\n");
+ fflush(stdout);
+
+ putenv("PS1=#");
+ putenv("PS2=>");
+ putenv("PS3=?");
+ putenv("PS4=+");
+ home = getenv("HOME");
+ if (home) {
+ chdir(home);
+ }
+ system("/bin/sh");
+ initialize_terminal();
+}
+
+static void
+cmd_show_motto(const struct dict *dict UNUSED)
+{
+ show_string("\"Just Add Ice\"");
+}
+
+static void
+show_string(const char *string)
+{
+ VLOG_INFO("%s", string);
+ erase();
+ curs_set(0);
+ move(0, 0);
+ addstr(string);
+ refresh();
+ block_until(time_msec() + 5000);
+}
+
+static void
+block_until(long long timeout)
+{
+ while (timeout > time_msec()) {
+ poll_timer_wait(timeout - time_msec());
+ poll_block();
+ }
+ drain_keyboard_buffer();
+}
+
+static void
+drain_keyboard_buffer(void)
+{
+ while (getch() != ERR) {
+ continue;
+ }
+}
+
+static int
+read_vars(const char *cmd, struct dict *dict)
+{
+ struct ds ds;
+ FILE *stream;
+ int status;
+
+ stream = popen(cmd, "r");
+ if (!stream) {
+ VLOG_ERR("popen(\"%s\") failed: %s", cmd, strerror(errno));
+ return errno;
+ }
+
+ dict_init(dict);
+ ds_init(&ds);
+ while (!ds_get_line(&ds, stream)) {
+ const char *s = ds_cstr(&ds);
+ const char *equals = strchr(s, '=');
+ if (equals) {
+ dict_add_nocopy(dict,
+ xmemdup0(s, equals - s), xstrdup(equals + 1));
+ }
+ }
+ status = pclose(stream);
+ if (status) {
+ char *msg = process_status_msg(status);
+ VLOG_ERR("pclose(\"%s\") reported subprocess failure: %s",
+ cmd, msg);
+ free(msg);
+ dict_free(dict);
+ return ECHILD;
+ }
+ return 0;
+}
+
+static bool
+run_and_report_failure(char **argv, const char *title)
+{
+ int null_fds[3] = {0, 1, 2};
+ int status;
+ int retval;
+ char *s;
+
+ s = process_escape_args(argv);
+ VLOG_INFO("starting subprocess: %s", s);
+ free(s);
+
+ retval = process_run(argv, NULL, 0, null_fds, 3, &status);
+ if (retval) {
+ char *s = xasprintf("%s:\n%s", title, strerror(retval));
+ show_string(s);
+ free(s);
+ return false;
+ } else if (status) {
+ char *msg = process_status_msg(status);
+ char *s = xasprintf("%s:\n%s", title, msg);
+ show_string(s);
+ free(msg);
+ free(s);
+ return false;
+ } else {
+ VLOG_INFO("subprocess exited with status 0");
+ return true;
+ }
+}
+
+static int
+do_load_config(const char *file_name, struct dict *dict)
+{
+ struct dict auto_vars;
+ int retval;
+ char *cmd;
+ size_t i;
+
+ /* Get the list of the variables that the shell sets automatically. */
+ retval = read_vars("set -a && env", &auto_vars);
+ if (retval) {
+ return retval;
+ }
+
+ /* Get the variables from 'file_name'. */
+ cmd = xasprintf("set -a && . '%s' && env", file_name);
+ retval = read_vars(cmd, dict);
+ free(cmd);
+ if (retval) {
+ dict_free(&auto_vars);
+ return retval;
+ }
+
+ /* Subtract. */
+ for (i = 0; i < auto_vars.n; i++) {
+ dict_delete(dict, auto_vars.pairs[i].name);
+ }
+ dict_free(&auto_vars);
+ return 0;
+}
+
+static bool
+load_config(struct dict *dict)
+{
+ static const char default_file[] = "/etc/default/openflow-switch";
+ int retval = do_load_config(default_file, dict);
+ if (!retval) {
+ return true;
+ } else {
+ char *s = xasprintf("Cfg load failed:\n%s", strerror(retval));
+ show_string(s);
+ free(s);
+ return false;
+ }
+}
+
+static bool
+save_config(const struct svec *settings)
+{
+ struct svec argv;
+ size_t i;
+ bool ok;
+
+ VLOG_INFO("Saving configuration:");
+ for (i = 0; i < settings->n; i++) {
+ VLOG_INFO("%s", settings->names[i]);
+ }
+
+ svec_init(&argv);
+ svec_add(&argv, "/usr/share/openvswitch/commands/reconfigure");
+ svec_append(&argv, settings);
+ svec_terminate(&argv);
+ ok = run_and_report_failure(argv.names, "Save failed");
+ if (ok) {
+ long long int timeout = time_msec() + 5000;
+
+ erase();
+ curs_set(0);
+ move(0, 0);
+ addstr("Saved.\nRestarting...");
+ refresh();
+
+ svec_clear(&argv);
+ svec_add(&argv, "/bin/sh");
+ svec_add(&argv, "-c");
+ svec_add(&argv,
+ "/etc/init.d/openflow-switch restart >/dev/null 2>&1");
+ svec_terminate(&argv);
+
+ ok = run_and_report_failure(argv.names, "Restart failed");
+ if (ok) {
+ block_until(timeout);
+ }
+ }
+ svec_destroy(&argv);
+
+ if (ok) {
+ VLOG_INFO("Save completed successfully");
+ } else {
+ VLOG_WARN("Save failed");
+ }
+ return ok;
+}
+
+static int
+match(pcre *re, const char *string, int length)
+{
+ int ovec[999];
+ int retval;
+
+ retval = pcre_exec(re, NULL, string, length, 0, PCRE_PARTIAL,
+ ovec, ARRAY_SIZE(ovec));
+ if (retval >= 0) {
+ if (ovec[0] >= 0 && ovec[1] >= length) {
+ /* 're' matched all of 'string'. */
+ return 0;
+ } else {
+ /* 're' matched the initial part of 'string' but not all of it. */
+ return PCRE_ERROR_NOMATCH;
+ }
+ } else {
+ return retval;
+ }
+}
+
+static void
+figure_choices(pcre *re, struct ds *s, int pos, struct ds *choices)
+{
+ struct ds tmp;
+ int retval;
+ char c;
+
+ ds_clear(choices);
+
+ /* See whether the current string is a complete match. */
+ if (!match(re, s->string, pos)) {
+ ds_put_char(choices, '\n');
+ }
+
+ /* Then try all the other possibilities. */
+ ds_init(&tmp);
+ ds_put_buffer(&tmp, s->string, pos);
+ for (c = 0x20; c < 0x7f; c++) {
+ ds_put_char(&tmp, c);
+ retval = match(re, tmp.string, pos + 1);
+ if (retval == PCRE_ERROR_PARTIAL || !retval) {
+ ds_put_char(choices, c);
+ }
+ tmp.length--;
+ }
+ ds_destroy(&tmp);
+
+ if (!choices->length) {
+ ds_put_char(choices, '\n');
+ }
+}
+
+static void
+figure_completion(pcre *re, struct ds *s)
+{
+ for (;;) {
+ int found = -1;
+ int c;
+
+ /* See whether the current string is a complete match. */
+ if (!match(re, s->string, s->length)) {
+ return;
+ }
+ for (c = 0x20; c < 0x7f; c++) {
+ int retval;
+
+ ds_put_char(s, c);
+ retval = match(re, s->string, s->length);
+ s->length--;
+
+ if (retval == PCRE_ERROR_PARTIAL || !retval) {
+ if (found != -1) {
+ return;
+ }
+ found = c;
+ }
+ }
+ if (found == -1) {
+ return;
+ }
+ ds_put_char(s, found);
+ }
+}
+
+#define OCTET_RE "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"
+#define IP_RE "("OCTET_RE"\\."OCTET_RE"\\."OCTET_RE"\\."OCTET_RE")"
+#define PORT_RE \
+ "([0-9]|" \
+ "[1-9][0-9]|" \
+ "[1-9][0-9][0-9]|" \
+ "[1-9][0-9][0-9][0-9]|" \
+ "[1-5][0-9][0-9][0-9][0-9]|" \
+ "6[1-4][0-9][0-9][0-9]|" \
+ "65[1-4][0-9][0-9]|" \
+ "655[1-2][0-9]|" \
+ "6553[1-5])"
+#define XOCTET_RE "[0-9A-F][0-9A-F]"
+#define MAC_RE \
+ XOCTET_RE":"XOCTET_RE":"XOCTET_RE":"\
+ XOCTET_RE":"XOCTET_RE":"XOCTET_RE
+#define NUM100_TO_99999_RE \
+ "([1-9][0-9][0-9]|" \
+ "[1-9][0-9][0-9][0-9]|" \
+ "[1-9][0-9][0-9][0-9][0-9])"
+#define NUM5_TO_99999_RE \
+ "([5-9]|" \
+ "[1-9][0-9]|" \
+ "[1-9][0-9][0-9]|" \
+ "[1-9][0-9][0-9][0-9]|" \
+ "[1-9][0-9][0-9][0-9][0-9])"
+#define NUM1_TO_99999_RE \
+ "([1-9]|" \
+ "[1-9][0-9]|" \
+ "[1-9][0-9][0-9]|" \
+ "[1-9][0-9][0-9][0-9]|" \
+ "[1-9][0-9][0-9][0-9][0-9])"
+
+static char *
+prompt(const char *prompt, const char *initial, const char *pattern)
+{
+ struct ds ds;
+ int pos, chidx;
+ struct ds choices;
+ const char *error;
+ int erroffset;
+ pcre *re;
+ int retval;
+ int okpartial;
+ char *p;
+
+ set_icon(0,
+ e____X,
+ e____X,
+ e__X_X,
+ e_X__X,
+ eXXXXX,
+ e_X___,
+ e__X__,
+ e_____);
+
+ re = pcre_compile(pattern, PCRE_ANCHORED, &error, &erroffset, NULL);
+ if (!re) {
+ VLOG_ERR("PCRE error for pattern \"%s\" at offset %d: %s",
+ pattern, erroffset, error);
+ return xstrdup(initial);
+ }
+
+ retval = pcre_fullinfo(re, NULL, PCRE_INFO_OKPARTIAL, &okpartial);
+ assert(!retval);
+ assert(okpartial);
+
+ pos = 0;
+ ds_init(&ds);
+ ds_put_cstr(&ds, initial);
+ ds_init(&choices);
+ figure_choices(re, &ds, pos, &choices);
+ p = memchr(choices.string, initial[0], choices.length);
+ chidx = p ? p - choices.string : 0;
+ for (;;) {
+ int c, key;
+
+ while ((key = getch()) != ERR) {
+ switch (key) {
+ case KEY_UP:
+ if (choices.length > 1) {
+ if (++chidx >= choices.length) {
+ chidx = 0;
+ }
+ ds.string[pos] = choices.string[chidx];
+ ds_truncate(&ds, pos + 1);
+ figure_completion(re, &ds);
+ }
+ break;
+
+ case KEY_DOWN:
+ if (choices.length > 1) {
+ if (--chidx < 0) {
+ chidx = choices.length - 1;
+ }
+ ds.string[pos] = choices.string[chidx];
+ ds_truncate(&ds, pos + 1);
+ figure_completion(re, &ds);
+ }
+ break;
+
+ case '\r': case '\n':
+ if (choices.string[chidx] == '\n') {
+ ds_truncate(&ds, pos);
+ return ds_cstr(&ds);
+ } else {
+ if (pos >= ds.length) {
+ pos++;
+ ds_put_char(&ds, choices.string[chidx]);
+ figure_choices(re, &ds, pos, &choices);
+ chidx = 0;
+ figure_completion(re, &ds);
+ } else {
+ pos = ds.length;
+ figure_choices(re, &ds, pos, &choices);
+ chidx = 0;
+ figure_completion(re, &ds);
+ }
+ }
+ break;
+
+ case '\f':
+ ds_truncate(&ds, pos + 1);
+ figure_choices(re, &ds, pos, &choices);
+ chidx = 0;
+ break;
+
+ case '\b': case '\x7f': case '\x1b':
+ case KEY_BACKSPACE: case KEY_DC:
+ if (pos) {
+ pos--;
+ } else {
+ return xstrdup(initial);
+ }
+ figure_choices(re, &ds, pos, &choices);
+ chidx = 0;
+ if (pos < ds.length) {
+ p = memchr(choices.string, ds.string[pos],
+ choices.length);
+ if (p) {
+ chidx = p - choices.string;
+ }
+ }
+ break;
+
+ default:
+ if (key >= 0x20 && key < 0x7f) {
+ /* Check whether 'key' is valid and toggle case if
+ * necessary. */
+ if (!memchr(choices.string, key, choices.length)) {
+ if (memchr(choices.string, toupper(key),
+ choices.length)) {
+ key = toupper(key);
+ } else if (memchr(choices.string, tolower(key),
+ choices.length)) {
+ key = tolower(key);
+ } else {
+ break;
+ }
+ }
+
+ /* Insert 'key' and advance the position. */
+ if (pos >= ds.length) {
+ ds_put_char(&ds, key);
+ } else {
+ ds.string[pos] = key;
+ }
+ pos++;
+
+ if (choices.string[chidx] != key) {
+ ds_truncate(&ds, pos);
+ }
+ figure_choices(re, &ds, pos, &choices);
+ chidx = 0;
+ if (pos < ds.length) {
+ p = memchr(choices.string, ds.string[pos],
+ choices.length);
+ if (p) {
+ chidx = p - choices.string;
+ }
+ }
+ figure_completion(re, &ds);
+ }
+ }
+ }
+
+ erase();
+ curs_set(1);
+ move(0, 0);
+ addnstr(prompt, MIN(40, strlen(prompt)));
+
+ c = choices.string[chidx];
+ move(1, 0);
+ addstr(ds_cstr(&ds));
+ move(1, pos);
+ if (c == '\n') {
+ put_icon(0, '$');
+ } else {
+ addch(c);
+ }
+ move(1, pos);
+ refresh();
+
+ poll_fd_wait(STDIN_FILENO, POLLIN);
+ poll_block();
+ }
+}
+
+static void
+prompt_ip(const char *title, uint32_t *ip)
+{
+ char *in = xasprintf(IP_FMT, IP_ARGS(ip));
+ char *out = prompt(title, in, "^"IP_RE"$");
+ *ip = inet_addr(out);
+ free(in);
+ free(out);
+}
+
+static void
+abbreviate_netdevs(const struct svec *netdevs, struct ds *abbrev)
+{
+ size_t i;
+
+ ds_init(abbrev);
+ for (i = 0; i < netdevs->n; ) {
+ size_t i_len = strlen(netdevs->names[i]);
+ size_t j;
+
+ for (j = i + 1; j < netdevs->n; j++) {
+ size_t j_len = strlen(netdevs->names[j]);
+ if (!i_len || !j_len || i_len != j_len
+ || memcmp(netdevs->names[i], netdevs->names[j], i_len - 1)) {
+ break;
+ }
+ }
+
+ if (abbrev->length) {
+ ds_put_char(abbrev, ' ');
+ }
+ if (j - i == 1) {
+ ds_put_cstr(abbrev, netdevs->names[i]);
+ } else {
+ size_t k;
+
+ ds_put_buffer(abbrev, netdevs->names[i], i_len - 1);
+ ds_put_char(abbrev, '[');
+ for (k = i; k < j; k++) {
+ ds_put_char(abbrev, netdevs->names[k][i_len - 1]);
+ }
+ ds_put_char(abbrev, ']');
+ }
+ i = j;
+ }
+}
+
+static void
+choose_netdevs(struct svec *choices)
+{
+ struct svec netdevs;
+ struct menu menu;
+ size_t i;
+
+ netdev_enumerate(&netdevs);
+ svec_sort(&netdevs);
+
+ menu_init(&menu);
+ menu_add_item(&menu, "Exit");
+ for (i = 0; i < netdevs.n; i++) {
+ const char *name = netdevs.names[i];
+ struct menu_item *item;
+ struct netdev *netdev;
+ int retval;
+
+ if (!strncmp(name, "wmaster", strlen("wmaster"))
+ || !strncmp(name, "of", strlen("of"))
+ || !strcmp(name, "lo")) {
+ continue;
+ }
+
+ retval = netdev_open(name, NETDEV_ETH_TYPE_NONE, &netdev);
+ if (!retval) {
+ bool exclude = netdev_get_in4(netdev, NULL);
+ netdev_close(netdev);
+ if (exclude) {
+ continue;
+ }
+ }
+
+ item = menu_add_item(&menu, "%s", name);
+ item->toggle = svec_contains(choices, name);
+ }
+ if (menu.n_items > 1) {
+ menu_show(&menu, 0, true);
+ } else {
+ show_string("No available\nbridge ports");
+ }
+
+ svec_clear(choices);
+ for (i = 0; i < menu.n_items; i++) {
+ struct menu_item *item = menu.items[i];
+ if (item->toggle > 0) {
+ svec_add(choices, item->text);
+ }
+ }
+
+ menu_free(&menu);
+}
+
+static bool
+is_datapath_id_in_dmi(void)
+{
+ FILE *dmidecode;
+ char line[256];
+ bool is_in_dmi;
+
+ dmidecode = popen("dmidecode -s system-uuid", "r");
+ if (!dmidecode) {
+ return false;
+ }
+ is_in_dmi = fgets(line, sizeof line, dmidecode) && strstr(line, "-002320");
+ fclose(dmidecode);
+ return is_in_dmi;
+}
+
+struct switch_config {
+ struct svec netdevs;
+ enum { DISCOVERY, IN_BAND } mode;
+ uint32_t switch_ip;
+ uint32_t switch_mask;
+ uint32_t switch_gw;
+ enum { FAIL_DROP, FAIL_SWITCH } disconnected;
+ bool stp;
+ int rate_limit;
+ int inactivity_probe;
+ int max_backoff;
+ char *controller_vconn;
+ char *datapath_id;
+};
+
+static const char *
+disconnected_string(int value)
+{
+#define FAIL_SWITCH_STRING "Switch packets"
+#define FAIL_DROP_STRING "Drop packets"
+ return value == FAIL_SWITCH ? FAIL_SWITCH_STRING : FAIL_DROP_STRING;
+}
+
+static void
+cmd_configure(const struct dict *dict UNUSED)
+{
+ bool debug_mode = dict_get_bool(dict, "debug", false);
+ struct dict config_dict;
+ struct switch_config config;
+ int start;
+
+ if (!load_config(&config_dict)) {
+ return;
+ }
+ svec_init(&config.netdevs);
+ svec_parse_words(&config.netdevs,
+ dict_get_string(&config_dict, "NETDEVS", ""));
+ config.mode = (!strcmp(dict_get_string(&config_dict, "MODE", "discovery"),
+ "in-band") ? IN_BAND : DISCOVERY);
+ config.switch_ip = dict_get_ip(&config_dict, "SWITCH_IP");
+ config.switch_mask = dict_get_ip(&config_dict, "SWITCH_NETMASK");
+ config.switch_gw = dict_get_ip(&config_dict, "SWITCH_GATEWAY");
+ config.controller_vconn = xstrdup(dict_get_string(&config_dict,
+ "CONTROLLER", ""));
+ config.disconnected = (!strcmp(dict_get_string(&config_dict,
+ "DISCONNECTED_MODE", ""),
+ "switch")
+ ? FAIL_SWITCH : FAIL_DROP);
+ config.stp = !strcmp(dict_get_string(&config_dict, "stp", ""), "yes");
+ config.rate_limit = dict_get_int(&config_dict, "RATE_LIMIT", -1);
+ config.inactivity_probe = dict_get_int(&config_dict, "INACTIVITY_PROBE",
+ -1);
+ config.max_backoff = dict_get_int(&config_dict, "MAX_BACKOFF", -1);
+ if (is_datapath_id_in_dmi()) {
+ config.datapath_id = xstrdup("DMI");
+ } else {
+ const char *dpid = dict_get(&config_dict, "DATAPATH_ID");
+ if (dpid) {
+ struct ds ds = DS_EMPTY_INITIALIZER;
+ const char *cp;
+ for (cp = dpid; *cp != '\0'; cp++) {
+ if (*cp != ':') {
+ ds_put_char(&ds, toupper((unsigned char) *cp));
+ }
+ }
+ config.datapath_id = ds_cstr(&ds);
+ } else {
+ config.datapath_id = xstrdup("Random");
+ }
+ }
+ dict_free(&config_dict);
+
+ start = 0;
+ while (start != -1) {
+ enum {
+ MENU_EXIT,
+ MENU_NETDEVS,
+ MENU_MODE,
+ MENU_IP,
+ MENU_NETMASK,
+ MENU_GATEWAY,
+ MENU_CONTROLLER,
+ MENU_DISCONNECTED_MODE,
+ MENU_DATAPATH_ID,
+ MENU_STP,
+ MENU_RATE_LIMIT,
+ MENU_INACTIVITY_PROBE,
+ MENU_MAX_BACKOFF,
+ };
+
+ struct ds ports;
+ struct menu_item *item;
+ struct menu menu;
+ char *in, *out;
+ uint32_t ip;
+
+ menu_init(&menu);
+
+ /* Exit. */
+ item = menu_add_item(&menu, "Exit");
+ item->id = MENU_EXIT;
+
+ /* Bridge Ports. */
+ abbreviate_netdevs(&config.netdevs, &ports);
+ item = menu_add_item(&menu, "Bridge Ports:\n%s", ds_cstr(&ports));
+ item->id = MENU_NETDEVS;
+ ds_destroy(&ports);
+
+ /* Mode. */
+ item = menu_add_item(&menu, "Mode:\n%s",
+ (config.mode == DISCOVERY
+ ? "Discovery" : "In-Band"));
+ item->id = MENU_MODE;
+
+ /* IP address. */
+ if (config.switch_ip == htonl(0)) {
+ item = menu_add_item(&menu, "Switch IP Addr:\nDHCP");
+ } else {
+ item = menu_add_item(&menu, "Switch IP Addr:\n"IP_FMT,
+ IP_ARGS(&config.switch_ip));
+ }
+ item->id = MENU_IP;
+ item->enabled = config.mode == IN_BAND;
+
+ /* Netmask. */
+ item = menu_add_item(&menu, "Switch Netmask:\n"IP_FMT,
+ IP_ARGS(&config.switch_mask));
+ item->id = MENU_NETMASK;
+ item->enabled = config.mode == IN_BAND && config.switch_ip != htonl(0);
+
+ /* Gateway. */
+ item = menu_add_item(&menu, "Switch Gateway:\n"IP_FMT,
+ IP_ARGS(&config.switch_gw));
+ item->id = MENU_GATEWAY;
+ item->enabled = config.mode == IN_BAND && config.switch_ip != htonl(0);
+
+ /* Controller. */
+ item = menu_add_item(&menu, "Controller:\n%s",
+ config.controller_vconn);
+ item->id = MENU_CONTROLLER;
+ item->enabled = config.mode == IN_BAND;
+
+ /* Disconnected mode. */
+ item = menu_add_item(&menu, "If disconnected:\n%s\n",
+ disconnected_string(config.disconnected));
+ item->id = MENU_DISCONNECTED_MODE;
+
+ /* Datapath ID. */
+ item = menu_add_item(&menu, "Datapath ID:\n%s", config.datapath_id);
+ item->id = MENU_DATAPATH_ID;
+ item->enabled = strcmp(config.datapath_id, "DMI");
+
+ /* Spanning tree protocol. */
+ if (debug_mode) {
+ item = menu_add_item(&menu, "802.1D-1998 STP:\n%s",
+ config.stp ? "Enabled" : "Disabled");
+ item->id = MENU_STP;
+ }
+
+ /* Rate-limiting. */
+ if (debug_mode) {
+ if (config.rate_limit < 0) {
+ item = menu_add_item(&menu, "Ctlr rate limit:\nDisabled");
+ } else {
+ item = menu_add_item(&menu, "Ctlr rate limit:\n%d/s",
+ config.rate_limit);
+ }
+ item->id = MENU_RATE_LIMIT;
+ }
+
+ /* Inactivity probe. */
+ if (debug_mode) {
+ if (config.inactivity_probe < 0) {
+ item = menu_add_item(&menu, "Activity probe:\nDefault");
+ } else {
+ item = menu_add_item(&menu, "Activity probe:\n%d s",
+ config.inactivity_probe);
+ }
+ item->id = MENU_INACTIVITY_PROBE;
+ }
+
+ /* Max backoff. */
+ if (debug_mode) {
+ if (config.max_backoff < 0) {
+ item = menu_add_item(&menu, "Max backoff:\nDefault");
+ } else {
+ item = menu_add_item(&menu, "Max backoff:\n%d s",
+ config.max_backoff);
+ }
+ item->id = MENU_MAX_BACKOFF;
+ }
+
+ start = menu_show2(&menu, start, true);
+ menu_free(&menu);
+
+ in = out = NULL;
+ switch (start) {
+ case MENU_EXIT:
+ start = -1;
+ break;
+
+ case MENU_NETDEVS:
+ choose_netdevs(&config.netdevs);
+ break;
+
+ case MENU_MODE:
+ out = prompt("Mode:",
+ config.mode == DISCOVERY ? "Discovery" : "In-Band",
+ "^(Discovery|In-Band)$");
+ config.mode = !strcmp(out, "Discovery") ? DISCOVERY : IN_BAND;
+ free(out);
+ break;
+
+ case MENU_IP:
+ in = (config.switch_ip == htonl(0) ? xstrdup("DHCP")
+ : xasprintf(IP_FMT, IP_ARGS(&config.switch_ip)));
+ out = prompt("Switch IP:", in, "^(DHCP|"IP_RE")$");
+ ip = strcmp(out, "DHCP") ? inet_addr(out) : htonl(0);
+ free(in);
+ free(out);
+ if (ip != config.switch_ip) {
+ config.switch_ip = ip;
+ if (ip != htonl(0)) {
+ uint32_t mask = guess_netmask(ip);
+ if (mask) {
+ config.switch_mask = mask;
+ config.switch_gw = (ip & mask) | htonl(1);
+ }
+ }
+ }
+ break;
+
+ case MENU_NETMASK:
+ prompt_ip("Switch Netmask:", &config.switch_mask);
+ break;
+
+ case MENU_GATEWAY:
+ prompt_ip("Switch Gateway:", &config.switch_gw);
+ break;
+
+ case MENU_CONTROLLER:
+ out = prompt("Controller:", config.controller_vconn,
+ "^(tcp|ssl):"IP_RE"(:"PORT_RE")?$");
+ free(config.controller_vconn);
+ config.controller_vconn = out;
+ break;
+
+ case MENU_DISCONNECTED_MODE:
+ out = prompt("If disconnected",
+ disconnected_string(config.disconnected),
+ "^("FAIL_DROP_STRING"|"FAIL_SWITCH_STRING")$");
+ config.disconnected = (!strcmp(out, FAIL_DROP_STRING)
+ ? FAIL_DROP : FAIL_SWITCH);
+ free(out);
+ break;
+
+ case MENU_DATAPATH_ID:
+ out = prompt("Datapath ID:", config.datapath_id,
+ "^Random|"MAC_RE"$");
+ free(config.datapath_id);
+ config.datapath_id = out;
+ break;
+
+ case MENU_STP:
+ out = prompt("802.1D-1998 STP:",
+ config.stp ? "Enabled" : "Disabled",
+ "^(Enabled|Disabled)$");
+ config.stp = !strcmp(out, "Enabled");
+ free(out);
+ break;
+
+ case MENU_RATE_LIMIT:
+ in = (config.rate_limit < 0
+ ? xstrdup("Disabled")
+ : xasprintf("%d/s", config.rate_limit));
+ out = prompt("Ctlr rate limit:", in,
+ "^(Disabled|("NUM100_TO_99999_RE")/s)$");
+ free(in);
+ config.rate_limit = isdigit(out[0]) ? atoi(out) : -1;
+ free(out);
+ break;
+
+ case MENU_INACTIVITY_PROBE:
+ in = (config.inactivity_probe < 0
+ ? xstrdup("Default")
+ : xasprintf("%d s", config.inactivity_probe));
+ out = prompt("Activity probe:", in,
+ "^(Default|("NUM5_TO_99999_RE") s)$");
+ free(in);
+ config.inactivity_probe = isdigit(out[0]) ? atoi(out) : -1;
+ free(out);
+ break;
+
+ case MENU_MAX_BACKOFF:
+ in = (config.max_backoff < 0
+ ? xstrdup("Default")
+ : xasprintf("%d s", config.max_backoff));
+ out = prompt("Max backoff:", in,
+ "^(Default|("NUM1_TO_99999_RE") s)$");
+ free(in);
+ config.max_backoff = isdigit(out[0]) ? atoi(out) : -1;
+ free(out);
+ break;
+ }
+ }
+
+ if (yesno("Save\nChanges?", false)) {
+ struct svec set;
+ char *netdevs;
+
+ svec_init(&set);
+ netdevs = svec_join(&config.netdevs, " ", "");
+ svec_add_nocopy(&set, xasprintf("NETDEVS=%s", netdevs));
+ free(netdevs);
+ svec_add(&set,
+ config.mode == IN_BAND ? "MODE=in-band" : "MODE=discovery");
+ if (config.mode == IN_BAND) {
+ if (config.switch_ip == htonl(0)) {
+ svec_add(&set, "SWITCH_IP=dhcp");
+ } else {
+ svec_add_nocopy(&set, xasprintf("SWITCH_IP="IP_FMT,
+ IP_ARGS(&config.switch_ip)));
+ svec_add_nocopy(&set,
+ xasprintf("SWITCH_NETMASK="IP_FMT,
+ IP_ARGS(&config.switch_mask)));
+ svec_add_nocopy(&set, xasprintf("SWITCH_GATEWAY="IP_FMT,
+ IP_ARGS(&config.switch_gw)));
+ svec_add_nocopy(&set, xasprintf("CONTROLLER=%s",
+ config.controller_vconn));
+ }
+ }
+ svec_add(&set, (config.disconnected == FAIL_DROP
+ ? "DISCONNECTED_MODE=drop"
+ : "DISCONNECTED_MODE=switch"));
+ svec_add_nocopy(&set, xasprintf("STP=%s", config.stp ? "yes" : "no"));
+ if (config.rate_limit < 0) {
+ svec_add(&set, "RATE_LIMIT=");
+ } else {
+ svec_add_nocopy(&set,
+ xasprintf("RATE_LIMIT=%d", config.rate_limit));
+ }
+ if (config.inactivity_probe < 0) {
+ svec_add(&set, "INACTIVITY_PROBE=");
+ } else {
+ svec_add_nocopy(&set, xasprintf("INACTIVITY_PROBE=%d",
+ config.inactivity_probe));
+ }
+ if (config.max_backoff < 0) {
+ svec_add(&set, "MAX_BACKOFF=");
+ } else {
+ svec_add_nocopy(&set, xasprintf("MAX_BACKOFF=%d",
+ config.max_backoff));
+ }
+ save_config(&set);
+ svec_destroy(&set);
+ }
+
+ svec_destroy(&config.netdevs);
+ free(config.controller_vconn);
+ free(config.datapath_id);
+}
+
+static void
+cmd_setup_pki(const struct dict *dict UNUSED)
+{
+ static const char def_privkey_file[]
+ = "/etc/openflow-switch/of0-privkey.pem";
+ static const char def_cert_file[] = "/etc/openflow-switch/of0-cert.pem";
+ static const char def_cacert_file[] = "/etc/openflow-switch/cacert.pem";
+ struct dict config_dict;
+ const char *privkey_file, *cert_file, *cacert_file;
+ bool bootstrap;
+ struct stat s;
+ struct svec set;
+ bool has_keys;
+
+ if (!load_config(&config_dict)) {
+ return;
+ }
+ privkey_file = dict_get_string(&config_dict, "PRIVKEY", def_privkey_file);
+ cert_file = dict_get_string(&config_dict, "CERT", def_cert_file);
+ cacert_file = dict_get_string(&config_dict, "CACERT", def_cacert_file);
+ bootstrap = !strcmp(dict_get_string(&config_dict, "CACERT_MODE", "secure"),
+ "bootstrap");
+
+ has_keys = !stat(privkey_file, &s) && !stat(cert_file, &s);
+ if (!has_keys
+ ? yesno("Generate\nkeys?", true)
+ : yesno("Generate\nnew keys?", false)) {
+ struct svec argv;
+ bool ok;
+
+ privkey_file = def_privkey_file;
+ cert_file = def_cert_file;
+
+ svec_init(&argv);
+ svec_parse_words(&argv, "sh -c 'cd /etc/openflow-switch "
+ "&& ovs-pki --force req of0"
+ "&& ovs-pki --force self-sign of0'");
+ svec_terminate(&argv);
+ ok = run_and_report_failure(argv.names, "Key gen failed");
+ svec_destroy(&argv);
+ if (!ok) {
+ return;
+ }
+ has_keys = true;
+ }
+ if (!has_keys) {
+ return;
+ }
+
+ if (stat(cacert_file, &s) && errno == ENOENT) {
+ bootstrap = yesno("Bootstrap\nCA cert?", bootstrap);
+ } else if (yesno("Replace\nCA cert?", false)) {
+ unlink(cacert_file);
+ bootstrap = true;
+ }
+
+ svec_init(&set);
+ svec_add_nocopy(&set, xasprintf("PRIVKEY=%s", privkey_file));
+ svec_add_nocopy(&set, xasprintf("CERT=%s", cert_file));
+ svec_add_nocopy(&set, xasprintf("CACERT=%s", cacert_file));
+ svec_add_nocopy(&set, xasprintf("CACERT_MODE=%s",
+ bootstrap ? "bootstrap" : "secure"));
+ save_config(&set);
+ svec_destroy(&set);
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ enum {
+ OPT_DUMMY = UCHAR_MAX + 1,
+ VLOG_OPTION_ENUMS
+ };
+ static struct option long_options[] = {
+ {"verbose", optional_argument, 0, 'v'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ DAEMON_LONG_OPTIONS,
+ VLOG_LONG_OPTIONS,
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ usage();
+
+ case 'V':
+ OVS_PRINT_VERSION(OFP_VERSION, OFP_VERSION);
+ exit(EXIT_SUCCESS);
+
+ VLOG_OPTION_HANDLERS
+ DAEMON_OPTION_HANDLERS
+
+ case '?':
+ exit(EXIT_FAILURE);
+
+ default:
+ abort();
+ }
+ }
+ free(short_options);
+}
+
+static void
+usage(void)
+{
+ printf("%s: OpenFlow switch monitoring user interface\n"
+ "usage: %s [OPTIONS] SWITCH\n"
+ "where SWITCH is an active OpenFlow connection method.\n",
+ program_name, program_name);
+ vconn_usage(true, false, false);
+ printf("\nOptions:\n"
+ " -v, --verbose=MODULE:FACILITY:LEVEL configure logging levels\n"
+ " -v, --verbose set maximum verbosity level\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n");
+ exit(EXIT_SUCCESS);
+}
diff --git a/extras/ezio/terminal.c b/extras/ezio/terminal.c
new file mode 100644
index 00000000..eacf0af0
--- /dev/null
+++ b/extras/ezio/terminal.c
@@ -0,0 +1,833 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#include <config.h>
+#include "terminal.h"
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "dynamic-string.h"
+#include "ezio.h"
+#include "poll-loop.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_terminal
+#include "vlog.h"
+
+/* UTF-8 decoding. */
+static struct utf8_reader *utf8_reader_create(void);
+static void utf8_reader_destroy(struct utf8_reader *);
+static int utf8_reader_read(struct utf8_reader *, uint8_t c);
+
+/* ANSI escape sequence decoding. */
+struct ansi_sequence {
+ int n_args;
+#define ANSI_MAX_ARGS 16
+ int args[ANSI_MAX_ARGS];
+ int function;
+};
+
+static struct ansi_decoder *ansi_decoder_create(void);
+static void ansi_decoder_destroy(struct ansi_decoder *);
+static int ansi_decoder_put(struct ansi_decoder *, uint8_t c);
+static const struct ansi_sequence *ansi_decoder_get(struct ansi_decoder *);
+
+/* Terminal emulation. */
+struct terminal {
+ struct ansi_decoder *ansi;
+ struct utf8_reader *utf8;
+ enum { EZIO, UTF8 } encoding;
+};
+
+static void recv_byte(struct terminal *term, struct ezio *ezio, uint8_t c);
+
+struct terminal *
+terminal_create(void)
+{
+ struct terminal *term = xmalloc(sizeof *term);
+ term->ansi = ansi_decoder_create();
+ term->utf8 = utf8_reader_create();
+ term->encoding = UTF8;
+ return term;
+}
+
+void
+terminal_destroy(struct terminal *term)
+{
+ if (term) {
+ utf8_reader_destroy(term->utf8);
+ ansi_decoder_destroy(term->ansi);
+ free(term);
+ }
+}
+
+int
+terminal_run(struct terminal *term, struct ezio *ezio, int input_fd)
+{
+ char input[512];
+ int n;
+
+ n = read(input_fd, input, sizeof input);
+ if (n > 0) {
+ int i;
+
+ for (i = 0; i < n; i++) {
+ recv_byte(term, ezio, input[i]);
+ }
+ return 0;
+ } else {
+ return !n ? EOF : errno == EAGAIN ? 0 : errno;
+ }
+}
+
+void
+terminal_wait(struct terminal *term UNUSED, int input_fd)
+{
+ poll_fd_wait(input_fd, POLLIN);
+}
+
+static void recv_ansi_sequence(const struct ansi_sequence *, struct ezio *);
+static void recv_control(uint8_t c, struct ezio *);
+static void recv_character(uint8_t byte, struct ezio *);
+static int unicode_to_ezio(uint16_t unicode);
+static int default_arg(int value, int default_value);
+static int range(int value, int min, int max);
+static void clear_elements(uint8_t *p, size_t size, int pos, int clear_type);
+static void define_icon(struct ezio *e, const int *args);
+static void clear_icon(struct ezio *e, int icon_nr);
+static void set_cursor(struct ezio *e, int visibility);
+
+static void
+recv_byte(struct terminal *term, struct ezio *ezio, uint8_t c)
+{
+ int retval;
+
+ /* Decode and interpret ANSI escape sequences. */
+ retval = ansi_decoder_put(term->ansi, c);
+ if (retval <= 0) {
+ if (retval < 0) {
+ recv_ansi_sequence(ansi_decoder_get(term->ansi), ezio);
+ return;
+ }
+ return;
+ }
+
+ /* Encoding selection. */
+ if (c == 0x0e) {
+ /* Shift Out. */
+ term->encoding = EZIO;
+ return;
+ } else if (c == 0x0f) {
+ /* Shift In. */
+ term->encoding = UTF8;
+ return;
+ }
+
+ if (term->encoding == UTF8) {
+ int unicode, ezchar;
+
+ /* Convert UTF-8 input to Unicode code point. */
+ unicode = utf8_reader_read(term->utf8, c);
+ if (unicode < 0) {
+ return;
+ }
+
+ /* Convert Unicode code point to EZIO encoding. */
+ ezchar = unicode_to_ezio(unicode);
+ if (ezchar >= 0) {
+ if (ezchar & 0xff00) {
+ recv_character(ezchar >> 8, ezio);
+ }
+ recv_character(ezchar, ezio);
+ } else if (unicode < 0x100) {
+ recv_control(unicode, ezio);
+ } else {
+ /* Unsupported Unicode code point. */
+ return;
+ }
+ } else {
+ if (c >= 0x80 && c < 0x87) {
+ c &= 0x07;
+ }
+ if (c != 0xfe) {
+ recv_character(c, ezio);
+ }
+ }
+}
+
+static void
+log_ansi_sequence(const struct ansi_sequence *seq, struct ezio *e)
+{
+ struct sequence {
+ int function;
+ const char *name;
+ };
+ static const struct sequence sequences[] = {
+ {0x5a, "CBT: Cursor Backward Tabulation"},
+ {0x47, "CHA: Cursor Character Absolute"},
+ {0x49, "CHT: Cursor Forward Tabulation"},
+ {0x45, "CNL: Cursor Next Line"},
+ {0x46, "CPL: Cursor Preceding Line"},
+ {0x44, "CUB: Cursor Left"},
+ {0x42, "CUD: Cursor Down"},
+ {0x43, "CUF: Cursor Right"},
+ {0x48, "CUP: Cursor Position"},
+ {0x41, "CUU: Cursor Up"},
+ {0x50, "DCH: Delete Character"},
+ {0x4d, "DL: Delete Line"},
+ {0x58, "ECH: Erase Character"},
+ {0x4a, "ED: Erase in Page"},
+ {0x4b, "EL: Erase in Line"},
+ {0x40, "ICH: Insert Character"},
+ {0x4c, "IL: Insert Line"},
+ {0x4500, "NEL: Next Line"},
+ {0x4d00, "RI: Reverse Line Feed"},
+ {0x6300, "RIS: Reset to Initial State"},
+ {0x54, "SD: Scroll Down"},
+ {0x240, "SL: Scroll Left"},
+ {0x241, "SR: Scroll Right"},
+ {0x53, "SU: Scroll Up"},
+ {0x70, "DICO: Define Icon"},
+ {0x71, "CICO: Clear Icon"},
+ {0x72, "Set cursor visibility"},
+ };
+ const struct sequence *s;
+ struct ds ds;
+ int i;
+
+ ds_init(&ds);
+ for (s = sequences; s < &sequences[ARRAY_SIZE(sequences)]; s++) {
+ if (s->function == seq->function) {
+ ds_put_cstr(&ds, s->name);
+ goto found;
+ }
+ }
+ ds_put_format(&ds, "0x%02x", s->function);
+ if (s->function < 0x100) {
+ ds_put_format(&ds, "(%02d/%02d)", s->function / 16, s->function % 16);
+ }
+
+found:
+ for (i = 0; i < seq->n_args; i++) {
+ ds_put_format(&ds, ", %d", seq->args[i]);
+ }
+ VLOG_DBG("%s (cursor:%d,%d)", ds_cstr(&ds), e->x, e->y);
+ ds_destroy(&ds);
+}
+
+static void
+recv_ansi_sequence(const struct ansi_sequence *seq, struct ezio *e)
+{
+#define ARG1(DEFAULT) default_arg(seq->args[0], DEFAULT)
+#define ARG2(DEFAULT) default_arg(seq->args[1], DEFAULT)
+ if (VLOG_IS_DBG_ENABLED()) {
+ log_ansi_sequence(seq, e);
+ }
+ switch (seq->function) {
+ case 0x5a: /* CBT: Cursor Backward Tabulation. */
+ e->x = 8 * (e->x / 8 - ARG1(1));
+ break;
+ case 0x47: /* CHA: Cursor Character Absolute. */
+ e->x = ARG1(1) - 1;
+ break;
+ case 0x49: /* CHT: Cursor Forward Tabulation. */
+ e->x = 8 * (e->x / 8 + ARG1(1));
+ break;
+ case 0x45: /* CNL: Cursor Next Line. */
+ e->x = 0;
+ e->y += ARG1(1);
+ break;
+ case 0x46: /* CPL: Cursor Preceding Line. */
+ e->x = 0;
+ e->y -= ARG1(1);
+ break;
+ case 0x44: /* CUB: Cursor Left. */
+ e->x -= ARG1(1);
+ break;
+ case 0x42: /* CUD: Cursor Down. */
+ e->y += ARG1(1);
+ break;
+ case 0x43: /* CUF: Cursor Right. */
+ e->x += ARG1(1);
+ break;
+ case 0x48: /* CUP: Cursor Position. */
+ e->y = ARG1(1) - 1;
+ e->x = ARG2(1) - 1;
+ break;
+ case 0x41: /* CUU: Cursor Up. */
+ e->y -= ARG1(1);
+ break;
+ case 0x50: /* DCH: Delete Character. */
+ ezio_delete_char(e, e->x, e->y, ARG1(1));
+ break;
+ case 0x4d: /* DL: Delete Line. */
+ ezio_delete_line(e, e->y, ARG1(1));
+ break;
+ case 0x58: /* ECH: Erase Character. */
+ memset(&e->chars[e->y][e->x], ' ', MIN(ARG1(1), 40 - e->x));
+ break;
+ case 0x4a: /* ED: Erase in Page. */
+ clear_elements(&e->chars[0][0], 2 * 40, e->x + 40 * e->y, ARG1(0));
+ break;
+ case 0x4b: /* EL: Erase in Line. */
+ clear_elements(&e->chars[e->y][0], 40, e->x, ARG1(0));
+ break;
+ case 0x40: /* ICH: Insert Character. */
+ ezio_insert_char(e, e->x, e->y, ARG1(1));
+ break;
+ case 0x4c: /* IL: Insert Line. */
+ ezio_insert_line(e, e->y, ARG1(1));
+ break;
+ case 0x4500: /* NEL: Next Line. */
+ e->x = 0;
+ e->y++;
+ break;
+ case 0x4d00: /* RI: Reverse Line Feed. */
+ e->y--;
+ break;
+ case 0x6300: /* RIS: Reset to Initial State. */
+ ezio_init(e);
+ break;
+ case 0x54: /* SD: Scroll Down. */
+ ezio_scroll_down(e, ARG1(1));
+ break;
+ case 0x240: /* SL: Scroll Left. */
+ ezio_scroll_left(e, ARG1(1));
+ break;
+ case 0x241: /* SR: Scroll Right. */
+ ezio_scroll_right(e, ARG1(1));
+ break;
+ case 0x53: /* SU: Scroll Up. */
+ ezio_scroll_up(e, ARG1(1));
+ break;
+
+ /* Private sequences. */
+ case 0x70: /* DICO: Define Icon. */
+ define_icon(e, seq->args);
+ break;
+ case 0x71: /* CICO: Clear Icon. */
+ clear_icon(e, ARG1(0));
+ break;
+ case 0x72: /* Set cursor visibility. */
+ set_cursor(e, ARG1(1));
+ break;
+ }
+ e->x = range(e->x, 0, 40);
+ e->y = range(e->y, 0, 1);
+ VLOG_DBG("cursor:%d,%d", e->x, e->y);
+}
+
+static void
+recv_control(uint8_t c, struct ezio *e)
+{
+ switch (c) {
+ case '\b':
+ if (e->x > 0) {
+ --e->x;
+ }
+ break;
+
+ case '\t':
+ e->x = ROUND_UP(e->x + 1, 8);
+ if (e->x > 40) {
+ ezio_newline(e);
+ }
+ break;
+
+ case '\n':
+ ezio_line_feed(e);
+ break;
+
+ case '\f':
+ ezio_clear(e);
+ break;
+
+ case '\r':
+ e->x = 0;
+ break;
+
+ default:
+ VLOG_DBG("Unhandled control character 0x%02"PRIx8, c);
+ }
+}
+
+static void
+recv_character(uint8_t byte, struct ezio *e)
+{
+ if (e->x >= 40) {
+ ezio_newline(e);
+ }
+ ezio_put_char(e, e->x++, e->y, byte);
+}
+
+static int
+default_arg(int value, int default_value)
+{
+ return value >= 0 ? value : default_value;
+}
+
+static int
+range(int value, int min, int max)
+{
+ return value < min ? min : value > max ? max : value;
+}
+
+static void
+clear_elements(uint8_t *p, size_t size, int pos, int clear_type)
+{
+ switch (clear_type) {
+ case 0:
+ /* Clear from 'pos' to end. */
+ memset(p + pos, ' ', size - pos);
+ break;
+ case 1:
+ /* Clear from beginning to 'pos'. */
+ memset(p, ' ', pos + 1);
+ break;
+ case 2:
+ /* Clear all. */
+ memset(p, ' ', size);
+ break;
+ }
+}
+
+static void
+define_icon(struct ezio *e, const int *args)
+{
+ int icon_nr;
+ int row;
+
+ icon_nr = args[0];
+ if (icon_nr < 0 || icon_nr > 7) {
+ return;
+ }
+
+ for (row = 0; row < 8; row++) {
+ e->icons[icon_nr][row] = default_arg(args[row + 1], 0) & 0x1f;
+ }
+}
+
+static void
+clear_icon(struct ezio *e, int icon_nr)
+{
+ if (icon_nr >= 0 && icon_nr <= 7) {
+ ezio_set_default_icon(e, icon_nr);
+ }
+}
+
+static void
+set_cursor(struct ezio *e, int visibility)
+{
+ switch (visibility) {
+ case 1:
+ e->show_cursor = e->blink_cursor = false;
+ break;
+ case 2:
+ e->show_cursor = true;
+ e->blink_cursor = false;
+ break;
+ case 3:
+ e->show_cursor = e->blink_cursor = true;
+ break;
+ }
+}
+
+static int
+unicode_to_ezio(uint16_t unicode)
+{
+ switch (unicode) {
+ /* Most ASCII characters map one-to-one. */
+ case 0x0020 ... 0x005b:
+ case 0x005d ... 0x007d:
+ return unicode;
+
+ /* A few ASCII characters have to be simulated with icons. */
+ case 0x005c: return 0x06; /* BACKSLASH */
+ case 0x007e: return 0x07; /* TILDE */
+
+ /* EZIO extended characters equivalents in Unicode - Japanese. */
+ case 0x00a5: return '\\'; /* YEN SIGN */
+ case 0x3002: return 0xa1; /* IDEOGRAPHIC FULL STOP */
+ case 0x300c: return 0xa2; /* LEFT CORNER BRACKET */
+ case 0x300d: return 0xa3; /* RIGHT CORNER BRACKET */
+ case 0x3001: return 0xa4; /* IDEOGRAPHIC COMMA */
+ case 0x30fb: return 0xa5; /* KATAKANA MIDDLE DOT */
+ case 0x30f2: return 0xa6; /* KATAKANA LETTER WO */
+ case 0x30a1: return 0xa7; /* KATAKANA LETTER SMALL A */
+ case 0x30a3: return 0xa8; /* KATAKANA LETTER SMALL I */
+ case 0x30a5: return 0xa9; /* KATAKANA LETTER SMALL U */
+ case 0x30a7: return 0xaa; /* KATAKANA LETTER SMALL E */
+ case 0x30a9: return 0xab; /* KATAKANA LETTER SMALL O */
+ case 0x30e3: return 0xac; /* KATAKANA LETTER SMALL YA */
+ case 0x30e5: return 0xad; /* KATAKANA LETTER SMALL YU */
+ case 0x30e7: return 0xae; /* KATAKANA LETTER SMALL YO */
+ case 0x30c3: return 0xaf; /* KATAKANA LETTER SMALL TU = SMALL TSU */
+ case 0x30fc: return 0xb0; /* KATAKANA-HIRAGANA PROLONGED SOUND MARK */
+ case 0x30a2: return 0xb1; /* KATAKANA LETTER A */
+ case 0x30a4: return 0xb2; /* KATAKANA LETTER I */
+ case 0x30a6: return 0xb3; /* KATAKANA LETTER U */
+ case 0x30a8: return 0xb4; /* KATAKANA LETTER E */
+ case 0x30aa: return 0xb5; /* KATAKANA LETTER O */
+ case 0x30ab: return 0xb6; /* KATAKANA LETTER KA */
+ case 0x30ac: return 0xb6de; /* KATAKANA LETTER GA */
+ case 0x30ad: return 0xb7; /* KATAKANA LETTER KI */
+ case 0x30ae: return 0xb7de; /* KATAKANA LETTER GI */
+ case 0x30af: return 0xb8; /* KATAKANA LETTER KU */
+ case 0x30b0: return 0xb8de; /* KATAKANA LETTER GU */
+ case 0x30b1: return 0xb9; /* KATAKANA LETTER KE */
+ case 0x30b2: return 0xb9de; /* KATAKANA LETTER GE */
+ case 0x30b3: return 0xba; /* KATAKANA LETTER KO */
+ case 0x30b4: return 0xbade; /* KATAKANA LETTER GO */
+ case 0x30b5: return 0xbb; /* KATAKANA LETTER SA */
+ case 0x30b6: return 0xbbde; /* KATAKANA LETTER ZA */
+ case 0x30b7: return 0xbc; /* KATAKANA LETTER SI = SHI */
+ case 0x30b8: return 0xbcde; /* KATAKANA LETTER ZI = JI */
+ case 0x30b9: return 0xbd; /* KATAKANA LETTER SU */
+ case 0x30ba: return 0xbdde; /* KATAKANA LETTER ZU */
+ case 0x30bb: return 0xbe; /* KATAKANA LETTER SE */
+ case 0x30bc: return 0xbede; /* KATAKANA LETTER ZE */
+ case 0x30bd: return 0xbf; /* KATAKANA LETTER SO */
+ case 0x30be: return 0xbfde; /* KATAKANA LETTER ZO */
+ case 0x30bf: return 0xc0; /* KATAKANA LETTER TA */
+ case 0x30c0: return 0xc0de; /* KATAKANA LETTER DA */
+ case 0x30c1: return 0xc1; /* KATAKANA LETTER TI = CHI */
+ case 0x30c2: return 0xc1de; /* KATAKANA LETTER DI = JI */
+ case 0x30c4: return 0xc2; /* KATAKANA LETTER TU = TSU */
+ case 0x30c5: return 0xc2de; /* KATAKANA LETTER DU = ZU */
+ case 0x30c6: return 0xc3; /* KATAKANA LETTER TE */
+ case 0x30c7: return 0xc3de; /* KATAKANA LETTER DE */
+ case 0x30c8: return 0xc4; /* KATAKANA LETTER TO */
+ case 0x30c9: return 0xc4de; /* KATAKANA LETTER DO */
+ case 0x30ca: return 0xc5; /* KATAKANA LETTER NA */
+ case 0x30cb: return 0xc6; /* KATAKANA LETTER NI */
+ case 0x30cc: return 0xc7; /* KATAKANA LETTER NU */
+ case 0x30cd: return 0xc8; /* KATAKANA LETTER NE */
+ case 0x30ce: return 0xc9; /* KATAKANA LETTER NO */
+ case 0x30cf: return 0xca; /* KATAKANA LETTER HA */
+ case 0x30d0: return 0xcade; /* KATAKANA LETTER BA */
+ case 0x30d1: return 0xcadf; /* KATAKANA LETTER PA */
+ case 0x30d2: return 0xcb; /* KATAKANA LETTER HI */
+ case 0x30d3: return 0xcbde; /* KATAKANA LETTER BI */
+ case 0x30d4: return 0xcbdf; /* KATAKANA LETTER PI */
+ case 0x30d5: return 0xcc; /* KATAKANA LETTER HU = FU */
+ case 0x30d6: return 0xccde; /* KATAKANA LETTER BU */
+ case 0x30d7: return 0xccdf; /* KATAKANA LETTER PU */
+ case 0x30d8: return 0xcd; /* KATAKANA LETTER HE */
+ case 0x30d9: return 0xcdde; /* KATAKANA LETTER BE */
+ case 0x30da: return 0xcddf; /* KATAKANA LETTER PE */
+ case 0x30db: return 0xce; /* KATAKANA LETTER HO */
+ case 0x30dc: return 0xcede; /* KATAKANA LETTER BO */
+ case 0x30dd: return 0xcedf; /* KATAKANA LETTER PO */
+ case 0x30de: return 0xcf; /* KATAKANA LETTER MA */
+ case 0x30df: return 0xd0; /* KATAKANA LETTER MI */
+ case 0x30e0: return 0xd1; /* KATAKANA LETTER MU */
+ case 0x30e1: return 0xd2; /* KATAKANA LETTER ME */
+ case 0x30e2: return 0xd3; /* KATAKANA LETTER MO */
+ case 0x30e4: return 0xd4; /* KATAKANA LETTER YA */
+ case 0x30e6: return 0xd5; /* KATAKANA LETTER YU */
+ case 0x30e8: return 0xd6; /* KATAKANA LETTER YO */
+ case 0x30e9: return 0xd7; /* KATAKANA LETTER RA */
+ case 0x30ea: return 0xd8; /* KATAKANA LETTER RI */
+ case 0x30eb: return 0xd9; /* KATAKANA LETTER RU */
+ case 0x30ec: return 0xda; /* KATAKANA LETTER RE */
+ case 0x30ed: return 0xdb; /* KATAKANA LETTER RO */
+ case 0x30ef: return 0xdc; /* KATAKANA LETTER WA */
+ case 0x30f3: return 0xdd; /* KATAKANA LETTER N */
+ case 0x30f4: return 0xb3de; /* KATAKANA LETTER VU */
+ case 0x30f7: return 0xdcde; /* KATAKANA LETTER VA */
+ case 0x3099: return 0xde; /* COMBINING KATAKANA-HIRAGANA VOICED SOUND
+ * MARK */
+ case 0x309a: return 0xdf; /* COMBINING KATAKANA-HIRAGANA SEMI-VOICED
+ * SOUND MARK */
+ case 0x309b: return 0xde; /* KATAKANA-HIRAGANA VOICED SOUND MARK */
+ case 0x309c: return 0xdf; /* KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
+
+ /* EZIO extended characters equivalents in Unicode - other. */
+ case 0x2192: return 0x7e; /* RIGHTWARDS ARROW */
+ case 0x2190: return 0x7f; /* LEFTWARDS ARROW */
+ case 0x03b1: return 0xe0; /* GREEK SMALL LETTER ALPHA */
+ case 0x00e4: return 0xe1; /* LATIN SMALL LETTER A WITH DIAERESIS */
+ case 0x03b2: return 0xe2; /* GREEK SMALL LETTER BETA */
+ case 0x03b5: return 0xe3; /* GREEK SMALL LETTER EPSILON */
+ case 0x03bc: return 0xe4; /* GREEK SMALL LETTER MU */
+ case 0x03c6: return 0xe5; /* GREEK SMALL LETTER PHI */
+ case 0x03c1: return 0xe6; /* GREEK SMALL LETTER RHO */
+ /* 0xe7 is 'g'. */
+ case 0x221a: return 0xe8; /* SQUARE ROOT = radical sign */
+ /* 0xe9 is an unrecognizable symbol. */
+ /* 0xea is 'j'. */
+ /* 0xeb is an unrecognizable symbol.*/
+ case 0x00a2: return 0xec; /* CENT SIGN */
+ case 0x00a3: return 0xed; /* POUND SIGN */
+ case 0x00f1: return 0xee; /* LATIN SMALL LETTER N WITH TILDE */
+ case 0x00f6: return 0xef; /* LATIN SMALL LETTER O WITH DIAERESIS */
+ /* 0xf0 is 'p'. */
+ /* 0xf1 is 'q'. */
+ case 0x03b8: return 0xf2; /* GREEK SMALL LETTER THETA */
+ case 0x221e: return 0xf3; /* INFINITY */
+ case 0x03a9: return 0xf4; /* GREEK CAPITAL LETTER OMEGA */
+ case 0x00fc: return 0xf5; /* LATIN SMALL LETTER U WITH DIAERESIS */
+ case 0x03a3: return 0xf6; /* GREEK CAPITAL LETTER SIGMA */
+ case 0x03c0: return 0xf7; /* GREEK SMALL LETTER PI */
+ /* 0xf8 is x-macron (the sample mean). */
+ /* 0xf9 is 'y'. */
+ case 0x5343: return 0xfa; /* thousand */
+ case 0x4e07: return 0xfb; /* ten thousand */
+ case 0x5186: return 0xfc; /* yen */
+ case 0x00f7: return 0xfd; /* DIVISION SIGN */
+ case 0x2588: return 0xff; /* FULL BLOCK = solid */
+
+ /* EZIO icons (from the Unicode Private Use corporate subarea). */
+ case 0xf8f8: return 0x00;
+ case 0xf8f9: return 0x01;
+ case 0xf8fa: return 0x02;
+ case 0xf8fb: return 0x03;
+ case 0xf8fc: return 0x04;
+ case 0xf8fd: return 0x05;
+ case 0xf8fe: return 0x06;
+ case 0xf8ff: return 0x07;
+
+ /* No mappings for anything else. */
+ default: return -1;
+ }
+}
+
+/* UTF-8 decoder. */
+
+#define UTF_STATES \
+ UTF_STATE(UTF8_INIT, 0x00, 0xf4, UTF8_INIT) \
+ UTF_STATE(UTF8_3, 0x80, 0xbf, UTF8_2) \
+ UTF_STATE(UTF8_2, 0x80, 0xbf, UTF8_1) \
+ UTF_STATE(UTF8_1, 0x80, 0xbf, UTF8_INIT) \
+ UTF_STATE(UTF8_E0, 0xa0, 0xbf, UTF8_1) \
+ UTF_STATE(UTF8_ED, 0x80, 0x9f, UTF8_1) \
+ UTF_STATE(UTF8_F0, 0x90, 0xbf, UTF8_INIT) \
+ UTF_STATE(UTF8_F4, 0x80, 0x8f, UTF8_INIT)
+
+enum state {
+#define UTF_STATE(NAME, MIN, MAX, NEXT) NAME,
+ UTF_STATES
+#undef UTF_STATE
+};
+
+struct state_info {
+ uint8_t min, max;
+ enum state next;
+};
+
+static const struct state_info states[] = {
+#define UTF_STATE(NAME, MIN, MAX, NEXT) {MIN, MAX, NEXT},
+ UTF_STATES
+#undef UTF_STATE
+};
+
+struct utf8_reader {
+ int cp;
+ enum state state;
+};
+
+struct utf8_reader *
+utf8_reader_create(void)
+{
+ struct utf8_reader *r = xmalloc(sizeof *r);
+ r->state = UTF8_INIT;
+ return r;
+}
+
+void
+utf8_reader_destroy(struct utf8_reader *r)
+{
+ free(r);
+}
+
+int
+utf8_reader_read(struct utf8_reader *r, uint8_t c)
+{
+ const struct state_info *s = &states[r->state];
+ if (c >= s->min && c <= s->max) {
+ if (r->state == UTF8_INIT) {
+ if (c < 0x80) {
+ return c;
+ } else if (c >= 0xc2 && c <= 0xdf) {
+ r->cp = c & 0x1f;
+ r->state = UTF8_1;
+ return -1;
+ } else if (c >= 0xe0 && c <= 0xef) {
+ r->cp = c & 0x0f;
+ r->state = c == 0xe0 ? UTF8_E0 : c == 0xed ? UTF8_ED : UTF8_2;
+ return -1;
+ } else if (c >= 0xf0 && c <= 0xf4) {
+ r->cp = c & 0x07;
+ r->state = c == 0xf0 ? UTF8_F0 : c == 0xf4 ? UTF8_F4 : UTF8_3;
+ return -1;
+ }
+ } else {
+ r->cp = (r->cp << 6) | (c & 0x3f);
+ r->state = s->next;
+ return r->state == UTF8_INIT ? r->cp : -1;
+ }
+ }
+
+ /* Invalid UTF-8 sequence. Return the Unicode general substitute
+ * REPLACEMENT CHARACTER. */
+ r->state = UTF8_INIT;
+ return 0xfffd;
+}
+
+/* ANSI control sequence decoder. */
+
+/* States are named for what we are looking for in that state. */
+enum ansi_state {
+ ANSI_ESC, /* Looking for ESC. */
+ ANSI_CSI, /* Looking for [ (to complete CSI). */
+ ANSI_PARAMETER, /* Looking for parameter. */
+ ANSI_INTERMEDIATE, /* Looking for intermediate byte. */
+ ANSI_FINAL, /* Looking for final byte. */
+ ANSI_COMPLETE /* Got an entire escape sequence. */
+};
+
+struct ansi_decoder {
+ enum ansi_state state;
+ struct ansi_sequence seq;
+ int c;
+};
+
+struct ansi_decoder *
+ansi_decoder_create(void)
+{
+ struct ansi_decoder *d = xmalloc(sizeof *d);
+ d->state = ANSI_ESC;
+ return d;
+}
+
+void
+ansi_decoder_destroy(struct ansi_decoder *d)
+{
+ free(d);
+}
+
+int
+ansi_decoder_put(struct ansi_decoder *d, uint8_t c)
+{
+ if (c == 27) {
+ /* Escape always starts a new escape sequence, aborting an incomplete
+ * one if necessary. */
+ if (d->state != ANSI_ESC) {
+ VLOG_DBG("Unexpected escape inside escape sequence");
+ }
+ d->state = ANSI_CSI;
+ return 0;
+ }
+
+ switch (d->state) {
+ case ANSI_ESC:
+ return 1;
+
+ case ANSI_CSI:
+ if (c == '[') {
+ d->state = ANSI_PARAMETER;
+ d->seq.n_args = 0;
+ d->seq.function = 0;
+ } else if (c >= 0x40 && c <= 0x5f) {
+ d->state = ANSI_COMPLETE;
+ d->seq.n_args = 0;
+ d->seq.function = 0;
+ d->seq.function = c << 8;
+ return -1;
+ } else {
+ d->state = ANSI_ESC;
+ }
+ break;
+
+ case ANSI_PARAMETER:
+ if (c >= '0' && c <= '9') {
+ int *arg;
+ if (d->seq.n_args == 0) {
+ d->seq.args[d->seq.n_args++] = 0;
+ } else if (d->seq.n_args > ANSI_MAX_ARGS) {
+ break;
+ }
+ arg = &d->seq.args[d->seq.n_args - 1];
+ if (*arg == -1) {
+ *arg = 0;
+ }
+ *arg = *arg * 10 + (c - '0');
+ break;
+ } else if (c == ';') {
+ if (d->seq.n_args < ANSI_MAX_ARGS) {
+ d->seq.args[d->seq.n_args] = -1;
+ }
+ d->seq.n_args++;
+ break;
+ }
+ d->state = ANSI_INTERMEDIATE;
+ /* Fall through. */
+
+ case ANSI_INTERMEDIATE:
+ if (c >= 0x20 && c <= 0x2f) {
+ d->seq.function = d->seq.function * 16 + (c - 0x20);
+ break;
+ }
+ d->state = ANSI_FINAL;
+ /* Fall through. */
+
+ case ANSI_FINAL:
+ if (c >= 0x40 && c <= 0x7e) {
+ d->seq.function = d->seq.function * 256 + c;
+ d->state = ANSI_COMPLETE;
+ return -1;
+ } else {
+ /* Invalid sequence. */
+ d->state = ANSI_ESC;
+ }
+ break;
+
+ case ANSI_COMPLETE:
+ NOT_REACHED();
+ }
+ return 0;
+}
+
+const struct ansi_sequence *
+ansi_decoder_get(struct ansi_decoder *d)
+{
+ assert(d->state == ANSI_COMPLETE);
+ d->state = ANSI_ESC;
+ if (d->seq.n_args < ANSI_MAX_ARGS) {
+ int i;
+ for (i = d->seq.n_args; i < ANSI_MAX_ARGS; i++) {
+ d->seq.args[i] = -1;
+ }
+ } else {
+ d->seq.n_args = ANSI_MAX_ARGS;
+ }
+ return &d->seq;
+}
diff --git a/extras/ezio/terminal.h b/extras/ezio/terminal.h
new file mode 100644
index 00000000..1ae5c479
--- /dev/null
+++ b/extras/ezio/terminal.h
@@ -0,0 +1,41 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#ifndef TERMINAL_H
+#define TERMINAL_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+
+struct ezio;
+
+struct terminal *terminal_create(void);
+void terminal_destroy(struct terminal *);
+int terminal_run(struct terminal *, struct ezio *, int input_fd);
+void terminal_wait(struct terminal *, int input_fd);
+
+#endif /* terminal.h */
diff --git a/extras/ezio/tty.c b/extras/ezio/tty.c
new file mode 100644
index 00000000..ce788f28
--- /dev/null
+++ b/extras/ezio/tty.c
@@ -0,0 +1,404 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#include <config.h>
+#include "extras/ezio/tty.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stropts.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "fatal-signal.h"
+#include "socket-util.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_tty
+#include "vlog.h"
+
+/* Get major() and minor() macros. */
+#if MAJOR_IN_MKDEV
+# include <sys/mkdev.h>
+#elif MAJOR_IN_SYSMACROS
+# include <sys/sysmacros.h>
+#else
+# include <sys/types.h>
+# ifndef major
+# define major(dev) (((dev) >> 8) & 0xff)
+# define minor(dev) ((dev) & 0xff)
+# endif
+#endif
+
+static int
+fcntl_lock(int fd)
+{
+ struct flock l;
+ memset(&l, 0, sizeof l);
+ l.l_type = F_WRLCK;
+ l.l_whence = SEEK_SET;
+ l.l_start = 0;
+ l.l_len = 0;
+ return fcntl(fd, F_SETLK, &l) == -1 ? errno : 0;
+}
+
+static int
+remove_lockfile(const char *name)
+{
+ char buffer[BUFSIZ];
+ ssize_t n;
+ pid_t pid;
+ int fd;
+
+ /* Remove existing lockfile. */
+ fd = open(name, O_RDWR);
+ if (fd < 0) {
+ if (errno == ENOENT) {
+ return 0;
+ } else {
+ VLOG_ERR("%s: open: %s", name, strerror(errno));
+ return errno;
+ }
+ }
+
+ /* Read lockfile. */
+ n = read(fd, buffer, sizeof buffer - 1);
+ if (n < 0) {
+ int error = errno;
+ VLOG_ERR("%s: read: %s", name, strerror(error));
+ close(fd);
+ return error;
+ }
+ buffer[n] = '\0';
+ if (n == 4 && memchr(buffer, '\0', n)) {
+ int32_t x;
+ memcpy(&x, buffer, sizeof x);
+ pid = x;
+ } else if (n >= 0) {
+ pid = strtol(buffer, NULL, 10);
+ }
+ if (pid <= 0) {
+ close(fd);
+ VLOG_WARN("%s: format not recognized, treating as locked.", name);
+ return EACCES;
+ }
+
+ /* Is lockfile fresh? */
+ if (strstr(buffer, "fcntl")) {
+ int retval = fcntl_lock(fd);
+ if (retval) {
+ close(fd);
+ VLOG_ERR("%s: device is locked (via fcntl): %s",
+ name, strerror(retval));
+ return retval;
+ } else {
+ VLOG_WARN("%s: removing stale lockfile (checked via fcntl)", name);
+ }
+ } else {
+ if (!(kill(pid, 0) < 0 && errno == ESRCH)) {
+ close(fd);
+ VLOG_ERR("%s: device is locked (without fcntl)", name);
+ return EACCES;
+ } else {
+ VLOG_WARN("%s: removing stale lockfile (without fcntl)", name);
+ }
+ }
+ close(fd);
+
+ /* Remove stale lockfile. */
+ if (unlink(name)) {
+ VLOG_ERR("%s: unlink: %s", name, strerror(errno));
+ return errno;
+ }
+ return 0;
+}
+
+static int
+create_lockfile(const char *name)
+{
+ const char *username;
+ char buffer[BUFSIZ];
+ struct passwd *pwd;
+ mode_t old_umask;
+ uid_t uid;
+ int fd;
+
+ /* Create file. */
+ old_umask = umask(022);
+ fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (fd < 0) {
+ int error = errno;
+ VLOG_ERR("%s: create: %s", name, strerror(error));
+ umask(old_umask);
+ return error;
+ }
+ umask(old_umask);
+
+ /* Lock file. */
+ if (fcntl_lock(fd)) {
+ int error = errno;
+ close(fd);
+ VLOG_ERR("%s: cannot lock: %s", name, strerror(error));
+ return error;
+ }
+
+ /* Write to file. */
+ uid = getuid();
+ pwd = getpwuid(uid);
+ username = pwd ? pwd->pw_name : "unknown";
+ snprintf(buffer, sizeof buffer, "%10ld %s %.20s fcntl\n",
+ (long int) getpid(), program_name, username);
+ if (write(fd, buffer, strlen(buffer)) != strlen(buffer)) {
+ int error = errno;
+ VLOG_ERR("%s: write: %s", name, strerror(error));
+ close(fd);
+ unlink(name);
+ return error;
+ }
+
+ /* We intentionally do not close 'fd', to avoid releasing the fcntl lock.
+ * The asssumption here is that we never unlock a tty. */
+ fatal_signal_add_file_to_unlink(name);
+
+ return 0;
+}
+
+static int
+do_lock(char *name)
+{
+ int retval = remove_lockfile(name);
+ if (!retval) {
+ retval = create_lockfile(name);
+ }
+ free(name);
+ return retval;
+}
+
+int
+tty_lock(const char *dev_name)
+{
+ struct stat s;
+ char *name;
+ int retval;
+
+ /* Check that the lockfile directory exists. */
+ if (stat(TTY_LOCK_DIR, &s)) {
+ VLOG_ERR("%s: stat: %s", TTY_LOCK_DIR, strerror(errno));
+ return errno;
+ }
+
+ /* First lock by device number. */
+ if (stat(dev_name, &s)) {
+ VLOG_ERR("%s: stat: %s", dev_name, strerror(errno));
+ return errno;
+ }
+ retval = do_lock(xasprintf("%s/LK.%03d.%03d.%03d", TTY_LOCK_DIR,
+ major(s.st_dev),
+ major(s.st_rdev), minor(s.st_rdev)));
+ if (retval) {
+ return retval;
+ }
+
+ /* Then lock by device name. */
+ if (!strncmp(dev_name, "/dev/", 5)) {
+ char *cp;
+
+ name = xasprintf("%s/%s", TTY_LOCK_DIR, dev_name + 5);
+ for (cp = name + strlen(dev_name) + 1; *cp; cp++) {
+ if (*cp == '/') {
+ *cp = '_';
+ }
+ }
+ } else {
+ char *slash = strrchr(dev_name, '/');
+ name = xasprintf ("%s/%s", TTY_LOCK_DIR, slash ? slash + 1 : dev_name);
+ }
+ return do_lock(name);
+}
+
+struct saved_termios {
+ int fd;
+ struct termios tios;
+};
+
+static void
+restore_termios(void *s_)
+{
+ struct saved_termios *s = s_;
+ tcsetattr(s->fd, TCSAFLUSH, &s->tios);
+}
+
+int
+tty_set_raw_mode(int fd, speed_t speed)
+{
+ if (isatty(fd)) {
+ struct termios tios;
+ struct saved_termios *s;
+
+ if (tcgetattr(fd, &tios) < 0) {
+ return errno;
+ }
+
+ s = xmalloc(sizeof *s);
+ s->fd = dup(fd);
+ if (s->fd < 0) {
+ int error = errno;
+ VLOG_WARN("dup failed: %s", strerror(error));
+ free(s);
+ return errno;
+ }
+ s->tios = tios;
+ fatal_signal_add_hook(restore_termios, s, true);
+
+ tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
+ | INLCR | IGNCR | ICRNL | IXON);
+ tios.c_oflag &= ~OPOST;
+ tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+ tios.c_cflag &= ~(CSIZE | PARENB);
+ tios.c_cflag |= CS8;
+ if (speed != B0) {
+ cfsetispeed(&tios, speed);
+ cfsetospeed(&tios, speed);
+ }
+ if (tcsetattr(fd, TCSAFLUSH, &tios) < 0) {
+ return errno;
+ }
+ }
+ return set_nonblocking(fd);
+}
+
+int
+tty_open_master_pty(void)
+{
+ int retval;
+ int fd;
+
+ fd = posix_openpt(O_RDWR | O_NOCTTY);
+ if (fd < 0) {
+ int error = errno;
+ VLOG_WARN("posix_openpt failed: %s", strerror(error));
+ close(fd);
+ return -error;
+ }
+
+ if (grantpt(fd) < 0) {
+ int error = errno;
+ VLOG_WARN("grantpt failed: %s", strerror(error));
+ close(fd);
+ return -error;
+ }
+
+ if (unlockpt(fd) < 0) {
+ int error = errno;
+ VLOG_WARN("unlockpt failed: %s", strerror(error));
+ close(fd);
+ return -error;
+ }
+
+ retval = set_nonblocking(fd);
+ if (retval) {
+ VLOG_WARN("set_nonblocking failed: %s", strerror(retval));
+ close(fd);
+ return retval;
+ }
+
+ return fd;
+}
+
+int
+tty_fork_child(int master_fd, char *argv[])
+{
+ int retval = fork();
+ if (!retval) {
+ char *slave_name;
+ int slave_fd;
+ int fd;
+
+ /* Running in child process. */
+ fatal_signal_fork();
+
+ /* Open pty slave as controlling terminal. */
+ setsid();
+ slave_name = ptsname(master_fd);
+ if (slave_name == NULL) {
+ ovs_fatal(errno, "ptsname");
+ }
+ slave_fd = open(slave_name, O_RDWR);
+ if (isastream(slave_fd)
+ && (ioctl(slave_fd, I_PUSH, "ptem") < 0
+ || ioctl(slave_fd, I_PUSH, "ldterm") < 0)) {
+ ovs_fatal(errno, "STREAMS ioctl");
+ }
+
+ /* Make pty slave stdin, stdout. */
+ if (dup2(slave_fd, STDIN_FILENO) < 0
+ || dup2(slave_fd, STDOUT_FILENO) < 0
+ || dup2(slave_fd, STDERR_FILENO) < 0) {
+ ovs_fatal(errno, "dup2");
+ }
+
+ /* Close other file descriptors. */
+ for (fd = 3; fd < 20; fd++) {
+ close(fd);
+ }
+
+ /* Set terminal type. */
+ setenv("TERM", "ezio3", true);
+
+ /* Invoke subprocess. */
+ execvp(argv[0], argv);
+ ovs_fatal(errno, "execvp");
+ } else if (retval > 0) {
+ /* Running in parent process. */
+ return 0;
+ } else {
+ /* Fork failed. */
+ VLOG_WARN("fork failed: %s", strerror(errno));
+ return errno;
+ }
+}
+
+int
+tty_set_window_size(int fd UNUSED, int rows UNUSED, int columns UNUSED)
+{
+#ifdef TIOCGWINSZ
+ struct winsize win;
+ win.ws_row = rows;
+ win.ws_col = columns;
+ win.ws_xpixel = 0;
+ win.ws_ypixel = 0;
+ if (ioctl(fd, TIOCSWINSZ, &win) == -1) {
+ return errno;
+ }
+#else
+#error
+#endif
+ return 0;
+}
diff --git a/extras/ezio/tty.h b/extras/ezio/tty.h
new file mode 100644
index 00000000..7500df55
--- /dev/null
+++ b/extras/ezio/tty.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#ifndef TTY_H
+#define TTY_H 1
+
+#include <termios.h>
+
+int tty_lock(const char *dev_name);
+int tty_set_raw_mode(int fd, speed_t);
+int tty_open_master_pty(void);
+int tty_fork_child(int master_fd, char *argv[]);
+int tty_set_window_size(int fd, int n_rows, int n_columns);
+
+#endif /* tty.h */
diff --git a/extras/ezio/vt-dummy.c b/extras/ezio/vt-dummy.c
new file mode 100644
index 00000000..f36d3114
--- /dev/null
+++ b/extras/ezio/vt-dummy.c
@@ -0,0 +1,40 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#include <config.h>
+#include "extras/ezio/vt.h"
+#include <errno.h>
+
+#define THIS_MODULE VLM_vt
+#include "vlog.h"
+
+int
+vt_open(int open_flags)
+{
+ VLOG_ERR("no virtual terminal support on this platform");
+ return -ENOSYS;
+}
diff --git a/extras/ezio/vt-linux.c b/extras/ezio/vt-linux.c
new file mode 100644
index 00000000..f502c9e1
--- /dev/null
+++ b/extras/ezio/vt-linux.c
@@ -0,0 +1,139 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#include <config.h>
+#include "extras/ezio/vt.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/kd.h>
+#include <linux/vt.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include "util.h"
+
+#define THIS_MODULE VLM_vt
+#include "vlog.h"
+
+static bool get_console_fd(int *fd);
+
+int
+vt_open(int open_flags)
+{
+ int console_fd, vt_fd;
+ char name[16];
+ int vt;
+
+ if (!get_console_fd(&console_fd)) {
+ return -EACCES;
+ }
+
+ /* Deallocate all unused virtual terminals, so that we don't proliferate an
+ * excess of empty ones over multiple runs. */
+ if (ioctl(console_fd, VT_DISALLOCATE, 0) < 0) {
+ VLOG_WARN("failed to deallocate empty virtual terminals: %s",
+ strerror(errno));
+ }
+
+ /* Find a unused virtual terminal. */
+ if (ioctl(console_fd, VT_OPENQRY, &vt) < 0) {
+ int error = errno;
+ VLOG_ERR("failed to find a free virtual terminal: %s",
+ strerror(error));
+ close(console_fd);
+ return -error;
+ }
+
+ /* Open virtual terminal. */
+ sprintf(name, "/dev/tty%d", vt);
+ vt_fd = open(name, open_flags);
+ if (vt_fd < 0) {
+ int error = errno;
+ VLOG_ERR("failed to open %s: %s", name, strerror(error));
+ close(console_fd);
+ return -error;
+ }
+
+ /* Activate virtual terminal. */
+ if (ioctl(console_fd, VT_ACTIVATE, vt) < 0
+ || ioctl(console_fd, VT_WAITACTIVE, vt) < 0) {
+ int error = errno;
+ VLOG_ERR("could not activate virtual terminal %d: %s",
+ vt, strerror(error));
+ close(console_fd);
+ close(vt_fd);
+ return -error;
+ }
+
+ /* Success. */
+ VLOG_DBG("allocated virtual terminal %d (%s)", vt, name);
+ close(console_fd);
+ return vt_fd;
+}
+
+static bool
+is_console(int fd)
+{
+ uint8_t type = 0;
+ return !ioctl(fd, KDGKBTYPE, &type) && (type == KB_101 || type == KB_84);
+}
+
+static bool
+open_console(const char *name, int *fdp)
+{
+ *fdp = open(name, O_RDWR | O_NOCTTY);
+ if (*fdp >= 0) {
+ if (is_console(*fdp)) {
+ return true;
+ }
+ close(*fdp);
+ }
+ return false;
+}
+
+static bool
+get_console_fd(int *fdp)
+{
+ int fd;
+
+ if (open_console("/dev/tty", fdp)
+ || open_console("/dev/tty0", fdp)
+ || open_console("/dev/console", fdp)) {
+ return true;
+ }
+ for (fd = 0; fd < 3; fd++) {
+ if (is_console(fd)) {
+ *fdp = dup(fd);
+ if (*fdp >= 0) {
+ return true;
+ }
+ }
+ }
+ VLOG_ERR("unable to obtain a file descriptor for the console");
+ return false;
+}
diff --git a/extras/ezio/vt.h b/extras/ezio/vt.h
new file mode 100644
index 00000000..2aafe941
--- /dev/null
+++ b/extras/ezio/vt.h
@@ -0,0 +1,33 @@
+/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables. You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL". If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version.
+ *
+ */
+
+#ifndef VT_H
+#define VT_H 1
+
+int vt_open(int open_flags);
+
+#endif /* vt.h */