Move EZIO utilities from vswitchext into openvswitch.
authorBen Pfaff <blp@nicira.com>
Wed, 13 May 2009 22:11:37 +0000 (15:11 -0700)
committerBen Pfaff <blp@nicira.com>
Wed, 13 May 2009 22:16:37 +0000 (15:16 -0700)
32 files changed:
COPYING
INSTALL
Makefile.am
boot.sh
configure.ac
debian/.gitignore
debian/control.in
debian/openvswitch-switchui.copyright [new file with mode: 0644]
debian/openvswitch-switchui.default [new file with mode: 0644]
debian/openvswitch-switchui.dirs [new file with mode: 0644]
debian/openvswitch-switchui.init [new file with mode: 0755]
debian/openvswitch-switchui.install [new file with mode: 0644]
debian/rules
extras/ezio/automake.mk [new file with mode: 0644]
extras/ezio/byteq.c [new file with mode: 0644]
extras/ezio/byteq.h [new file with mode: 0644]
extras/ezio/ezio-term.c [new file with mode: 0644]
extras/ezio/ezio.c [new file with mode: 0644]
extras/ezio/ezio.h [new file with mode: 0644]
extras/ezio/ezio3.ti [new file with mode: 0644]
extras/ezio/ovs-switchui.c [new file with mode: 0644]
extras/ezio/terminal.c [new file with mode: 0644]
extras/ezio/terminal.h [new file with mode: 0644]
extras/ezio/tty.c [new file with mode: 0644]
extras/ezio/tty.h [new file with mode: 0644]
extras/ezio/vt-dummy.c [new file with mode: 0644]
extras/ezio/vt-linux.c [new file with mode: 0644]
extras/ezio/vt.h [new file with mode: 0644]
lib/vlog-modules.def
m4/libopenvswitch.m4 [deleted file]
m4/openvswitch.m4 [new file with mode: 0644]
secchan/automake.mk

diff --git a/COPYING b/COPYING
index 2345fa31bac415d140fba0622baa4a2a692f294b..c70d12658374ea2d9aa8c241299c2b9a935a7d6a 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -36,8 +36,8 @@ following "OpenFlow license":
 Files under the datapath directory are licensed under the GNU General
 Public License, version 2.
 
-Files under the vswitchd directory are licensed under the GNU General
-Public License, version 3 or later.
+Files under the extras and vswitchd directories are licensed under the
+GNU General Public License, version 3 or later.
 
 Files under the xenserver directory are licensed on a file-by-file
 basis.  Some files are under an uncertain license that may not be
diff --git a/INSTALL b/INSTALL
index 34f898b7dc38cc2f90b2efa57d4b710438886887..d8332352e2538bc027c1ceecf178af949ac350f2 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -146,37 +146,40 @@ distribution in the ordinary way using "configure" and "make".
 
       % make
 
-   The following binaries will be built:
+   The following main binaries will be built:
 
       - Virtual switch daemon: vswitchd/vswitchd
 
       - Bridge compatibility daemon: vswitchd/brcompatd
 
-      - Secure channel executable: secchan/secchan.
-
-      - Controller executable: controller/controller.
-
       - Datapath administration utility: utilities/dpctl.
 
       - Runtime logging configuration utility: utilities/vlogconf.
 
-      - Miscellaneous utilities: utilities/ovs-discover,
-        utilities/ovs-kill.
+   Some less important binaries will be built also:
 
-      - Tests: various binaries in tests/.
+      - Controller executable: controller/controller.
 
-   If your distribution includes the OpenFlow extensions, the
-   following additional binaries will be built:
+      - Secure channel executable: secchan/secchan.
+
+      - Miscellaneous utilities: utilities/ovs-discover,
+        utilities/ovs-kill.
 
       - ANSI terminal support for EZIO 16x2 LCD panel:
-        ext/ezio/ezio-term.
+        extras/ezio/ezio-term (only if the proper libraries are
+        installed).
 
       - Switch monitoring UI for small text displays:
-        ext/ezio/ovs-switchui.
+        extras/ezio/ovs-switchui (only if the proper libraries are
+        installed).
+
+      - Tests: various binaries in tests/.
 
    If you passed --with-l26 to configure, "make" will also build the
    following kernel modules:
 
+      - datapath/linux-2.6/brcompat_mod.ko
+
       - datapath/linux-2.6/openflow_mod.ko
 
 3. Run "make install" to install the executables and manpages into the
index ce3b72c570cf72891ab75991e775f24a010af787..b6dc87c70bb7ac35121b30368efcd30ff346700d 100644 (file)
@@ -13,6 +13,8 @@ distcheck-hook:
 endif
 
 AM_CPPFLAGS = $(SSL_CFLAGS)
+AM_CPPFLAGS += $(NCURSES_CFLAGS)
+AM_CPPFLAGS += $(PCRE_CFLAGS)
 AM_CPPFLAGS += -I $(top_srcdir)/include
 AM_CPPFLAGS += -I $(top_srcdir)/lib
 if HAVE_EXT
@@ -69,4 +71,9 @@ include third-party/automake.mk
 include debian/automake.mk
 include vswitchd/automake.mk
 include xenserver/automake.mk
+if HAVE_CURSES
+if HAVE_PCRE
+include extras/ezio/automake.mk
+endif
+endif
 include ext.mk
diff --git a/boot.sh b/boot.sh
index 4598d5379793b06725a973954aba57d8a61cc618..33228e0ba68428f1a1847f1cae6438c3d968b2e4 100755 (executable)
--- a/boot.sh
+++ b/boot.sh
@@ -26,7 +26,6 @@ done
 if test "$have_ext" = yes; then
     echo 'Enabling vswitchext...'
     echo 'include ext/automake.mk' > ext.mk
-    echo 'm4_include([ext/configure.m4])' > ext.m4
     cat debian/control.in ext/debian/control.in > debian/control
     for d in $(cat ext/debian/LINKS); do
         test -e debian/$d || ln -s ../ext/debian/$d debian/$d
@@ -34,7 +33,6 @@ if test "$have_ext" = yes; then
 else
     echo 'Disabling vswitchext...'
     echo '# This file intentionally left blank.' > ext.mk
-    echo '# This file intentionally left blank.' > ext.m4
     cat debian/control.in > debian/control
 fi
 
index 67526c91f40d747618d509e94ec15a5b47196efd..e1ec0b7f0d9e691fc36e70b0a40913018ae27131 100644 (file)
@@ -51,10 +51,27 @@ if test "$PERL" = no; then
    AC_MSG_ERROR([Perl interpreter not found in $PATH or $PERL.])
 fi
 
-OVS_CHECK_LIBOPENVSWITCH
-OVS_CHECK_IF_PACKET
+AC_USE_SYSTEM_EXTENSIONS
+AC_C_BIGENDIAN
 AC_SYS_LARGEFILE
 
+OVS_CHECK_NDEBUG
+OVS_CHECK_NETLINK
+OVS_CHECK_OPENSSL
+OVS_CHECK_SNAT
+OVS_CHECK_FAULT_LIBS
+OVS_CHECK_SOCKET_LIBS
+OVS_CHECK_PKIDIR
+OVS_CHECK_RUNDIR
+OVS_CHECK_LOGDIR
+OVS_CHECK_MALLOC_HOOKS
+OVS_CHECK_VALGRIND
+OVS_CHECK_TTY_LOCK_DIR
+OVS_CHECK_CURSES
+OVS_CHECK_LINUX_VT_H
+OVS_CHECK_PCRE
+OVS_CHECK_IF_PACKET
+
 AC_CHECK_FUNCS([strsignal])
 
 AC_ARG_VAR(KARCH, [Kernel Architecture String])
@@ -80,7 +97,6 @@ OVS_ENABLE_OPTION([-Wmissing-field-initializers])
 OVS_ENABLE_OPTION([-Wno-override-init])
 
 OVS_ENABLE_EXT
-m4_include([ext.m4])
 
 AC_CONFIG_FILES([Makefile 
 datapath/Makefile 
index ece9bd0c55d228b3990113210b13c85b81238bf5..d2667a5afafe3819284a0b28a461a3f859c090d7 100644 (file)
 /openvswitch-switch
 /openvswitch-switch-config
 /openvswitch-switch.copyright
-/openvswitch-switchui
-/openvswitch-switchui.copyright
-/openvswitch-switchui.default
-/openvswitch-switchui.dirs
-/openvswitch-switchui.init
-/openvswitch-switchui.install
 /openvswitch-wdt
 /openvswitch-wdt.copyright
 /openvswitch-wdt.default
 /openvswitch-wdt.dirs
 /openvswitch-wdt.init
 /openvswitch-wdt.install
-/rules.ext
index a2750a6d0cb916dd9f8e1086fd30e219cf8c6402..fc773f532c1a77b61c273a91516dfe0095acb4d5 100644 (file)
@@ -49,6 +49,20 @@ Description: OpenVSwitch switch implementations
  OpenVSwitch is a software-based Ethernet switch targeted at virtual
  servers.
 
+Package: openvswitch-switchui
+Architecture: any
+Recommends: openvswitch-switch
+Depends: ${shlibs:Depends}, ${misc:Depends}, console-tools
+Description: Monitoring utility for OpenFlow switches
+ The ovs-switchui utility included in this package provides a
+ "front-panel display" to allow administrators to view the status of
+ an OpenFlow switch at a glance.
+ .
+ The ezio-term utility, also included, provides a VT100-compatible
+ terminal interface for EZIO3 (aka MTB-134) 16x2 LCD displays found on
+ server appliances made by Portwell.  It allows ovs-switchui to work
+ with such displays.
+
 Package: openvswitch-pki
 Architecture: all
 Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-common
diff --git a/debian/openvswitch-switchui.copyright b/debian/openvswitch-switchui.copyright
new file mode 100644 (file)
index 0000000..326fc49
--- /dev/null
@@ -0,0 +1,32 @@
+Upstream Authors: 
+
+    Nicira Networks, Inc.
+
+Copyright: 
+
+    Copyright (c) 2008, 2009 Nicira Networks, Inc.
+
+License:
+
+    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.
diff --git a/debian/openvswitch-switchui.default b/debian/openvswitch-switchui.default
new file mode 100644 (file)
index 0000000..6cdbf7a
--- /dev/null
@@ -0,0 +1,35 @@
+# This is a POSIX shell fragment                -*- sh -*-
+
+# To configure the switch monitor, modify the following.  Afterward,
+# the secure channel will come up automatically at boot time.  It can
+# be restarted immediately with
+#       /etc/init.d/openvswitch-switchui start
+
+# Defaults for initscript
+# sourced by /etc/init.d/openvswitch-switchui
+# installed at /etc/default/openvswitch-switchui by the maintainer scripts
+
+# SWITCH_VCONN: The vconn used to connect to the switch (secchan).
+# The secchan must be configured to listen to this vconn.  The default
+# here set is also listened to by default by the openvswitch-switch
+# package, so ordinarily there is no need to modify this.
+SWITCH_VCONN="unix:/var/run/secchan.mgmt"
+
+# EZIO3_DEVICE: To display the switch monitor on an EZIO3 (aka
+# MTB-134) 16x2 LCD displays found on server appliances made by
+# Portwell, set this to the EZIO3 serial device and uncomment it.
+#EZIO3_DEVICE="/dev/ttyS1"
+
+# OPENVT: When EZIO3_DEVICE is unset, this specifies the command under
+# which to run ovs-switchui.  The default value of "/usr/bin/openvt"
+# causes ovs-switchui to run on a new, otherwise empty virtual
+# console.
+#
+# The value must be a command name without arguments.  Use a wrapper
+# script to provide arguments if you need them.
+#
+# When EZIO3_DEVICE is set, this variable has no effect.
+OPENVT="/usr/bin/openvt"
+
+# DAEMON_OPTS: Additional options to pass to ovs-switchui.
+DAEMON_OPTS=""
diff --git a/debian/openvswitch-switchui.dirs b/debian/openvswitch-switchui.dirs
new file mode 100644 (file)
index 0000000..4dced02
--- /dev/null
@@ -0,0 +1,3 @@
+usr/bin
+usr/sbin
+usr/share/terminfo
diff --git a/debian/openvswitch-switchui.init b/debian/openvswitch-switchui.init
new file mode 100755 (executable)
index 0000000..1857bb5
--- /dev/null
@@ -0,0 +1,210 @@
+#!/bin/sh 
+#
+# Example init.d script with LSB support.
+#
+# Please read this init.d carefully and modify the sections to
+# adjust it to the program you want to run.
+#
+# Copyright (c) 2007, 2009 Javier Fernandez-Sanguino <jfs@debian.org>
+#
+# This is free software; you may redistribute it and/or modify
+# it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2,
+# or (at your option) any later version.
+#
+# This 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 with
+# the Debian operating system, in /usr/share/common-licenses/GPL;  if
+# not, write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA 02111-1307 USA
+#
+### BEGIN INIT INFO
+# Provides:          openvswitch-switchui
+# Required-Start:    $network $local_fs
+# Required-Stop:     
+# Should-Start:      $named $syslog openvswitch-switch
+# Should-Stop:       
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: OpenVSwitch switch monitor
+### END INIT INFO
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+
+DAEMON=/usr/bin/ovs-switchui
+NAME=openvswitch-switchui
+DESC="OpenVSwitch switch monitor"
+
+PIDFILE=/var/run/$NAME.pid 
+
+test -x $DAEMON || exit 0
+
+. /lib/lsb/init-functions
+
+# Default options, these can be overriden by the information
+# at /etc/default/$NAME
+DAEMON_OPTS=""          # Additional options given to the server 
+
+DODTIME=10              # Time to wait for the server to die, in seconds
+                        # If this value is set too low you might not
+                        # let some servers to die gracefully and
+                        # 'restart' will not work
+                        
+# Include defaults if available
+if [ -f /etc/default/$NAME ] ; then
+       . /etc/default/$NAME
+fi
+
+set -e
+
+running_pid() {
+# Check if a given process pid's cmdline matches a given name
+    pid=$1
+    name=$2
+    [ -z "$pid" ] && return 1 
+    [ ! -d /proc/$pid ] &&  return 1
+    return 0
+}
+
+running() {
+# Check if the process is running looking at /proc
+# (works for all users)
+
+    # No pidfile, probably no daemon present
+    [ ! -f "$PIDFILE" ] && return 1
+    pid=`cat $PIDFILE`
+    running_pid $pid $DAEMON || return 1
+    return 0
+}
+
+start_server() {
+# Start the process using the wrapper
+    if test -n "$EZIO3_DEVICE"; then
+        # Make ezio-term detach and create the pidfile.
+        WRAPPER="/usr/sbin/ezio-term"
+        WRAPPER_OPTS="--detach --pidfile=$PIDFILE --ezio=$EZIO3_DEVICE --input=vt"
+    else
+        # openvt will detach, so instead make ovs-switchui make the pidfile.
+        WRAPPER=$OPENVT
+        WRAPPER_OPTS=""
+        DAEMON_OPTS="--pidfile=$PIDFILE"
+    fi
+    start-stop-daemon --start --quiet --pidfile $PIDFILE \
+        --exec $WRAPPER -- $WRAPPER_OPTS -- $DAEMON $DAEMON_OPTS \
+        --log-file $SWITCH_VCONN
+
+    # Wait up to 3 seconds for the daemon to start.
+    for i in 1 2 3; do
+       if running; then
+           break
+       fi
+       sleep 1
+    done
+}
+
+stop_server() {
+    ovs-kill $PIDFILE
+}
+
+force_stop() {
+# Force the process to die killing it manually
+       [ ! -e "$PIDFILE" ] && return
+       if running ; then
+               kill -15 $pid
+       # Is it really dead?
+               sleep "$DIETIME"s
+               if running ; then
+                       kill -9 $pid
+                       sleep "$DIETIME"s
+                       if running ; then
+                               echo "Cannot kill $NAME (pid=$pid)!"
+                               exit 1
+                       fi
+               fi
+       fi
+       rm -f $PIDFILE
+}
+
+
+case "$1" in
+  start)
+       log_daemon_msg "Starting $DESC " "$NAME"
+        # Check if it's running first
+        if running ;  then
+            log_progress_msg "apparently already running"
+            log_end_msg 0
+            exit 0
+        fi
+        if start_server && running ;  then
+            # It's ok, the server started and is running
+            log_end_msg 0
+        else
+            # Either we could not start it or it is not running
+            # after we did
+            # NOTE: Some servers might die some time after they start,
+            # this code does not try to detect this and might give
+            # a false positive (use 'status' for that)
+            log_end_msg 1
+        fi
+       ;;
+  stop)
+        log_daemon_msg "Stopping $DESC" "$NAME"
+        if running ; then
+            # Only stop the server if we see it running
+            stop_server
+            log_end_msg $?
+        else
+            # If it's not running don't do anything
+            log_progress_msg "apparently not running"
+            log_end_msg 0
+            exit 0
+        fi
+        ;;
+  force-stop)
+        # First try to stop gracefully the program
+        $0 stop
+        if running; then
+            # If it's still running try to kill it more forcefully
+            log_daemon_msg "Stopping (force) $DESC" "$NAME"
+            force_stop
+            log_end_msg $?
+        fi
+       ;;
+  restart|force-reload)
+        log_daemon_msg "Restarting $DESC" "$NAME"
+        stop_server
+        # Wait some sensible amount, some server need this
+        [ -n "$DIETIME" ] && sleep $DIETIME
+        start_server
+        running
+        log_end_msg $?
+       ;;
+  status)
+
+        log_daemon_msg "Checking status of $DESC" "$NAME"
+        if running ;  then
+            log_progress_msg "running"
+            log_end_msg 0
+        else
+            log_progress_msg "apparently not running"
+            log_end_msg 1
+            exit 1
+        fi
+        ;;
+  # Use this if the daemon cannot reload
+  reload)
+        log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon"
+        log_warning_msg "cannot re-read the config file (use restart)."
+        ;;
+  *)
+       N=/etc/init.d/$NAME
+       echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2
+       exit 1
+       ;;
+esac
+
+exit 0
diff --git a/debian/openvswitch-switchui.install b/debian/openvswitch-switchui.install
new file mode 100644 (file)
index 0000000..f2872c8
--- /dev/null
@@ -0,0 +1,2 @@
+_debian/extras/ezio/ezio-term usr/sbin
+_debian/extras/ezio/ovs-switchui usr/bin
index 1eea4a266622b9d62300f12ff8ad0a45c06f3493..8475ee741c5f10dd5e1a5e7f361be4670e164ed4 100755 (executable)
@@ -25,8 +25,6 @@ MA_DIR ?= /usr/share/modass
 # load default rules
 -include $(MA_DIR)/include/common-rules.make
 
--include debian/rules.ext
-
 DATAPATH_CONFIGURE_OPTS = --enable-snat
 
 # Official build number.  Leave set to 0 if not an official build.
@@ -42,7 +40,6 @@ configure-stamp:
                ../configure --prefix=/usr --localstatedir=/var --enable-ssl \
                        --with-build-number=$(BUILD_NUMBER) \
                        $(DATAPATH_CONFIGURE_OPTS))
-       $(ext_configure)
        touch configure-stamp
 
 #Architecture 
@@ -51,13 +48,11 @@ build: build-arch build-indep
 build-arch: build-arch-stamp
 build-arch-stamp: configure-stamp 
        $(MAKE) -C _debian
-       $(ext_build_arch)
        touch $@
 
 build-indep: build-indep-stamp
 build-indep-stamp: configure-stamp 
        $(MAKE) -C _debian dist distdir=openvswitch
-       $(ext_build_indep)
        touch $@
 
 clean:
@@ -66,7 +61,6 @@ clean:
        rm -f build-arch-stamp build-indep-stamp configure-stamp
        rm -rf _debian
        [ ! -f Makefile ] || $(MAKE) distclean
-       $(ext_clean)
        dh_clean 
        debconf-updatepo
 
@@ -105,7 +99,6 @@ install-indep: build-indep
        cd debian/openvswitch-datapath-source/usr/src && tar -c modules | bzip2 -9 > openvswitch-datapath.tar.bz2 && rm -rf modules
        install -m644 debian/openvswitch-pki-server.apache2 debian/openvswitch-pki-server/etc/apache2/sites-available/openvswitch-pki
        install -m1777 -d debian/corekeeper/var/log/core
-       $(ext_install_indep)
 
 install-arch: build-arch
        dh_testdir
@@ -116,7 +109,10 @@ install-arch: build-arch
        cp debian/openvswitch-switch-config.overrides debian/openvswitch-switch-config/usr/share/lintian/overrides/openvswitch-switch-config
        cp debian/openvswitch-switch.template debian/openvswitch-switch/usr/share/openvswitch/switch/default.template
        dh_install -s
-       $(ext_install_arch)
+       env TERMINFO=debian/openvswitch-switchui/usr/share/terminfo tic -x extras/ezio/ezio3.ti
+       if test -e ext/utilities/dump-desc; then \
+               install -m755 ext/utilities/dump-desc debian/openvswitch-switch/usr/share/openvswitch; \
+       fi
 
 # Must not depend on anything. This is to be called by
 # binary-arch/binary-indep
diff --git a/extras/ezio/automake.mk b/extras/ezio/automake.mk
new file mode 100644 (file)
index 0000000..2aeaa64
--- /dev/null
@@ -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 (file)
index 0000000..31d48aa
--- /dev/null
@@ -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;
+}
+\f
+/* 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 (file)
index 0000000..4397f6a
--- /dev/null
@@ -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 (file)
index 0000000..2bda002
--- /dev/null
@@ -0,0 +1,1061 @@
+/* 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;
+        }
+    }
+}
+\f
+/* 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;
+}
+\f
+/* 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);
+}
+\f
+/* 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;
+        }
+    }
+}
+\f
+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':
+            printf("%s %s compiled "__DATE__" "__TIME__"\n",
+                   program_name, VERSION BUILDNR);
+            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 (file)
index 0000000..6024766
--- /dev/null
@@ -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 (file)
index 0000000..1308ec3
--- /dev/null
@@ -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 (file)
index 0000000..0bbcb39
--- /dev/null
@@ -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 (file)
index 0000000..0855abc
--- /dev/null
@@ -0,0 +1,3027 @@
+/* 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();
+}
+\f
+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);
+}
+\f
+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;
+            }
+        }
+    }
+}
+\f
+/* 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;
+}
+\f
+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;
+    }
+}
+\f
+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);
+}
+\f
+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':
+            printf("%s %s compiled "__DATE__" "__TIME__"\n",
+                   program_name, VERSION BUILDNR);
+            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 (file)
index 0000000..eacf0af
--- /dev/null
@@ -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 *);
+\f
+/* 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);
+}
+\f
+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;
+    }
+}
+\f
+/* 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;
+}
+\f
+/* 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 (file)
index 0000000..1ae5c47
--- /dev/null
@@ -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 (file)
index 0000000..ce788f2
--- /dev/null
@@ -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 (file)
index 0000000..7500df5
--- /dev/null
@@ -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 (file)
index 0000000..f36d311
--- /dev/null
@@ -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 (file)
index 0000000..f502c9e
--- /dev/null
@@ -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 (file)
index 0000000..2aafe94
--- /dev/null
@@ -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 */
index b5ac41349618817d59ddbe76ed1e9de4ba9db64c..e9221e0d402c56d6fbece5b9aa077d6e2f783e8a 100644 (file)
@@ -16,6 +16,7 @@ VLOG_MODULE(discovery)
 VLOG_MODULE(dpif)
 VLOG_MODULE(dpctl)
 VLOG_MODULE(executer)
+VLOG_MODULE(ezio_term)
 VLOG_MODULE(fail_open)
 VLOG_MODULE(fault)
 VLOG_MODULE(flow)
@@ -47,7 +48,9 @@ VLOG_MODULE(svec)
 VLOG_MODULE(switch)
 VLOG_MODULE(terminal)
 VLOG_MODULE(timeval)
+VLOG_MODULE(tty)
 VLOG_MODULE(socket_util)
+VLOG_MODULE(switchui)
 VLOG_MODULE(unixctl)
 VLOG_MODULE(vconn_tcp)
 VLOG_MODULE(vconn_ssl)
@@ -57,10 +60,7 @@ VLOG_MODULE(vconn)
 VLOG_MODULE(vlog)
 VLOG_MODULE(wcelim)
 VLOG_MODULE(vswitchd)
+VLOG_MODULE(vt)
 VLOG_MODULE(xenserver)
 
-#ifdef HAVE_EXT
-#include "ext/vlogext-modules.def"
-#endif
-
 #undef VLOG_MODULE
diff --git a/m4/libopenvswitch.m4 b/m4/libopenvswitch.m4
deleted file mode 100644 (file)
index 51d7560..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-# -*- autoconf -*-
-
-# Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
-# Junior University
-#
-# We are making the OpenFlow specification and associated documentation
-# (Software) available for public use and benefit with the expectation
-# that others will use, modify and enhance the Software and contribute
-# those enhancements back to the community. However, since we would
-# like to make the Software available for broadest use, with as few
-# restrictions as possible permission is hereby granted, free of
-# charge, to any person obtaining a copy of this Software to deal in
-# the Software under the copyrights without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
-# The name and trademarks of copyright holder(s) may NOT be used in
-# advertising or publicity pertaining to the Software or any
-# derivatives without specific, written prior permission.
-
-dnl Checks for --enable-ndebug and defines NDEBUG if it is specified.
-AC_DEFUN([OVS_CHECK_NDEBUG],
-  [AC_ARG_ENABLE(
-     [ndebug],
-     [AC_HELP_STRING([--enable-ndebug], 
-                     [Disable debugging features for max performance])],
-     [case "${enableval}" in
-        (yes) ndebug=true ;;
-        (no)  ndebug=false ;;
-        (*) AC_MSG_ERROR([bad value ${enableval} for --enable-ndebug]) ;;
-      esac],
-     [ndebug=false])
-   AM_CONDITIONAL([NDEBUG], [test x$ndebug = xtrue])])
-
-dnl Checks for Netlink support.
-AC_DEFUN([OVS_CHECK_NETLINK],
-  [AC_CHECK_HEADER([linux/netlink.h],
-                   [HAVE_NETLINK=yes],
-                   [HAVE_NETLINK=no],
-                   [#include <sys/socket.h>
-   #include <linux/types.h>
-   ])
-   AM_CONDITIONAL([HAVE_NETLINK], [test "$HAVE_NETLINK" = yes])
-   if test "$HAVE_NETLINK" = yes; then
-      AC_DEFINE([HAVE_NETLINK], [1],
-                [Define to 1 if Netlink protocol is available.])
-   fi])
-
-dnl Checks for OpenSSL, if --enable-ssl is passed in.
-AC_DEFUN([OVS_CHECK_OPENSSL],
-  [AC_ARG_ENABLE(
-     [ssl],
-     [AC_HELP_STRING([--enable-ssl], 
-                     [Enable ssl support (requires libssl)])],
-     [case "${enableval}" in
-        (yes) ssl=true ;;
-        (no)  ssl=false ;;
-        (*) AC_MSG_ERROR([bad value ${enableval} for --enable-ssl]) ;;
-      esac],
-     [ssl=false])
-
-   if test "$ssl" = true; then
-   dnl Make sure that pkg-config is installed.
-   m4_pattern_forbid([PKG_CHECK_MODULES])
-   PKG_CHECK_MODULES([SSL], [libssl], 
-     [HAVE_OPENSSL=yes],
-     [HAVE_OPENSSL=no
-      AC_MSG_WARN([Cannot find libssl:
-
-   $SSL_PKG_ERRORS
-
-   OpenFlow connections over SSL will not be supported.])])
-
-   fi
-   AM_CONDITIONAL([HAVE_OPENSSL], [test "$HAVE_OPENSSL" = yes])
-   if test "$HAVE_OPENSSL" = yes; then
-      AC_DEFINE([HAVE_OPENSSL], [1], [Define to 1 if OpenSSL is installed.])
-   fi])
-
-dnl Checks for --enable-snat and defines SUPPORT_SNAT if it is specified.
-AC_DEFUN([OVS_CHECK_SNAT],
-  [AC_ARG_ENABLE(
-     [snat],
-     [AC_HELP_STRING([--enable-snat], 
-                     [Enable support for source-NAT action])],
-     [case "${enableval}" in
-        (yes) snat=true ;;
-        (no)  snat=false ;;
-        (*) AC_MSG_ERROR([bad value ${enableval} for --enable-snat]) ;;
-      esac],
-     [snat=false])
-   AM_CONDITIONAL([SUPPORT_SNAT], [test x$snat = xtrue])
-   if test x$snat = xtrue; then
-      AC_DEFINE([SUPPORT_SNAT], [1], [Define to 1 if SNAT is desired.])
-      SUPPORT_SNAT=-DSUPPORT_SNAT
-      AC_SUBST([SUPPORT_SNAT])
-   fi])
-
-dnl Checks for libraries needed by lib/fault.c.
-AC_DEFUN([OVS_CHECK_FAULT_LIBS],
-  [AC_CHECK_LIB([dl], [dladdr], [FAULT_LIBS=-ldl])
-   AC_SUBST([FAULT_LIBS])])
-
-dnl Checks for libraries needed by lib/socket-util.c.
-AC_DEFUN([OVS_CHECK_SOCKET_LIBS],
-  [AC_CHECK_LIB([socket], [connect])
-   AC_SEARCH_LIBS([gethostbyname], [resolv], [RESOLVER_LIBS=-lresolv])])
-
-dnl Checks for the directory in which to store the PKI.
-AC_DEFUN([OVS_CHECK_PKIDIR],
-  [AC_ARG_WITH(
-     [pkidir], 
-     AC_HELP_STRING([--with-pkidir=DIR], 
-                    [PKI hierarchy directory [[DATADIR/openvswitch/pki]]]),
-     [PKIDIR=$withval],
-     [PKIDIR='${pkgdatadir}/pki'])
-   AC_SUBST([PKIDIR])])
-
-dnl Checks for the directory in which to store pidfiles.
-AC_DEFUN([OVS_CHECK_RUNDIR],
-  [AC_ARG_WITH(
-     [rundir], 
-     AC_HELP_STRING([--with-rundir=DIR], 
-                    [directory used for pidfiles [[LOCALSTATEDIR/run]]]),
-     [RUNDIR=$withval],
-     [RUNDIR='${localstatedir}/run'])
-   AC_SUBST([RUNDIR])])
-
-dnl Checks for the directory in which to store logs.
-AC_DEFUN([OVS_CHECK_LOGDIR],
-  [AC_ARG_WITH(
-     [logdir], 
-     AC_HELP_STRING([--with-logdir=DIR], 
-                    [directory used for logs [[LOCALSTATEDIR/log/PACKAGE]]]),
-     [LOGDIR=$withval],
-     [LOGDIR='${localstatedir}/log/${PACKAGE}'])
-   AC_SUBST([LOGDIR])])
-
-dnl Checks for __malloc_hook, etc., supported by glibc.
-AC_DEFUN([OVS_CHECK_MALLOC_HOOKS],
-  [AC_CACHE_CHECK(
-    [whether libc supports hooks for malloc and related functions],
-    [ovs_cv_malloc_hooks],
-    [AC_COMPILE_IFELSE(
-      [AC_LANG_PROGRAM(
-         [#include <malloc.h>
-         ], 
-         [(void) __malloc_hook;
-          (void) __realloc_hook;
-          (void) __free_hook;])],
-      [ovs_cv_malloc_hooks=yes],
-      [ovs_cv_malloc_hooks=no])])
-   if test $ovs_cv_malloc_hooks = yes; then
-     AC_DEFINE([HAVE_MALLOC_HOOKS], [1], 
-               [Define to 1 if you have __malloc_hook, __realloc_hook, and
-                __free_hook in <malloc.h>.])
-   fi])
-
-dnl Checks for valgrind/valgrind.h.
-AC_DEFUN([OVS_CHECK_VALGRIND], 
-  [AC_CHECK_HEADERS([valgrind/valgrind.h])])
-
-dnl Runs the checks required to include the headers in include/ and
-dnl link against lib/libopenvswitch.a.
-AC_DEFUN([OVS_CHECK_LIBOPENVSWITCH],
-  [AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
-   AC_REQUIRE([AC_C_BIGENDIAN])
-   AC_REQUIRE([OVS_CHECK_NDEBUG])
-   AC_REQUIRE([OVS_CHECK_NETLINK])
-   AC_REQUIRE([OVS_CHECK_OPENSSL])
-   AC_REQUIRE([OVS_CHECK_SNAT])
-   AC_REQUIRE([OVS_CHECK_FAULT_LIBS])
-   AC_REQUIRE([OVS_CHECK_SOCKET_LIBS])
-   AC_REQUIRE([OVS_CHECK_PKIDIR])
-   AC_REQUIRE([OVS_CHECK_RUNDIR])
-   AC_REQUIRE([OVS_CHECK_LOGDIR])
-   AC_REQUIRE([OVS_CHECK_MALLOC_HOOKS])
-   AC_REQUIRE([OVS_CHECK_VALGRIND])])
-
diff --git a/m4/openvswitch.m4 b/m4/openvswitch.m4
new file mode 100644 (file)
index 0000000..88ed2a1
--- /dev/null
@@ -0,0 +1,247 @@
+# -*- autoconf -*-
+
+# Copyright (c) 2008, 2009 The Board of Trustees of The Leland Stanford
+# Junior University
+#
+# We are making the OpenFlow specification and associated documentation
+# (Software) available for public use and benefit with the expectation
+# that others will use, modify and enhance the Software and contribute
+# those enhancements back to the community. However, since we would
+# like to make the Software available for broadest use, with as few
+# restrictions as possible permission is hereby granted, free of
+# charge, to any person obtaining a copy of this Software to deal in
+# the Software under the copyrights without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+# The name and trademarks of copyright holder(s) may NOT be used in
+# advertising or publicity pertaining to the Software or any
+# derivatives without specific, written prior permission.
+
+dnl Checks for --enable-ndebug and defines NDEBUG if it is specified.
+AC_DEFUN([OVS_CHECK_NDEBUG],
+  [AC_ARG_ENABLE(
+     [ndebug],
+     [AC_HELP_STRING([--enable-ndebug], 
+                     [Disable debugging features for max performance])],
+     [case "${enableval}" in
+        (yes) ndebug=true ;;
+        (no)  ndebug=false ;;
+        (*) AC_MSG_ERROR([bad value ${enableval} for --enable-ndebug]) ;;
+      esac],
+     [ndebug=false])
+   AM_CONDITIONAL([NDEBUG], [test x$ndebug = xtrue])])
+
+dnl Checks for Netlink support.
+AC_DEFUN([OVS_CHECK_NETLINK],
+  [AC_CHECK_HEADER([linux/netlink.h],
+                   [HAVE_NETLINK=yes],
+                   [HAVE_NETLINK=no],
+                   [#include <sys/socket.h>
+   #include <linux/types.h>
+   ])
+   AM_CONDITIONAL([HAVE_NETLINK], [test "$HAVE_NETLINK" = yes])
+   if test "$HAVE_NETLINK" = yes; then
+      AC_DEFINE([HAVE_NETLINK], [1],
+                [Define to 1 if Netlink protocol is available.])
+   fi])
+
+dnl Checks for OpenSSL, if --enable-ssl is passed in.
+AC_DEFUN([OVS_CHECK_OPENSSL],
+  [AC_ARG_ENABLE(
+     [ssl],
+     [AC_HELP_STRING([--enable-ssl], 
+                     [Enable ssl support (requires libssl)])],
+     [case "${enableval}" in
+        (yes) ssl=true ;;
+        (no)  ssl=false ;;
+        (*) AC_MSG_ERROR([bad value ${enableval} for --enable-ssl]) ;;
+      esac],
+     [ssl=false])
+
+   if test "$ssl" = true; then
+   dnl Make sure that pkg-config is installed.
+   m4_pattern_forbid([PKG_CHECK_MODULES])
+   PKG_CHECK_MODULES([SSL], [libssl], 
+     [HAVE_OPENSSL=yes],
+     [HAVE_OPENSSL=no
+      AC_MSG_WARN([Cannot find libssl:
+
+   $SSL_PKG_ERRORS
+
+   OpenFlow connections over SSL will not be supported.])])
+
+   fi
+   AM_CONDITIONAL([HAVE_OPENSSL], [test "$HAVE_OPENSSL" = yes])
+   if test "$HAVE_OPENSSL" = yes; then
+      AC_DEFINE([HAVE_OPENSSL], [1], [Define to 1 if OpenSSL is installed.])
+   fi])
+
+dnl Checks for --enable-snat and defines SUPPORT_SNAT if it is specified.
+AC_DEFUN([OVS_CHECK_SNAT],
+  [AC_ARG_ENABLE(
+     [snat],
+     [AC_HELP_STRING([--enable-snat], 
+                     [Enable support for source-NAT action])],
+     [case "${enableval}" in
+        (yes) snat=true ;;
+        (no)  snat=false ;;
+        (*) AC_MSG_ERROR([bad value ${enableval} for --enable-snat]) ;;
+      esac],
+     [snat=false])
+   AM_CONDITIONAL([SUPPORT_SNAT], [test x$snat = xtrue])
+   if test x$snat = xtrue; then
+      AC_DEFINE([SUPPORT_SNAT], [1], [Define to 1 if SNAT is desired.])
+      SUPPORT_SNAT=-DSUPPORT_SNAT
+      AC_SUBST([SUPPORT_SNAT])
+   fi])
+
+dnl Checks for libraries needed by lib/fault.c.
+AC_DEFUN([OVS_CHECK_FAULT_LIBS],
+  [AC_CHECK_LIB([dl], [dladdr], [FAULT_LIBS=-ldl])
+   AC_SUBST([FAULT_LIBS])])
+
+dnl Checks for libraries needed by lib/socket-util.c.
+AC_DEFUN([OVS_CHECK_SOCKET_LIBS],
+  [AC_CHECK_LIB([socket], [connect])
+   AC_SEARCH_LIBS([gethostbyname], [resolv], [RESOLVER_LIBS=-lresolv])])
+
+dnl Checks for the directory in which to store the PKI.
+AC_DEFUN([OVS_CHECK_PKIDIR],
+  [AC_ARG_WITH(
+     [pkidir], 
+     AC_HELP_STRING([--with-pkidir=DIR], 
+                    [PKI hierarchy directory [[DATADIR/openvswitch/pki]]]),
+     [PKIDIR=$withval],
+     [PKIDIR='${pkgdatadir}/pki'])
+   AC_SUBST([PKIDIR])])
+
+dnl Checks for the directory in which to store pidfiles.
+AC_DEFUN([OVS_CHECK_RUNDIR],
+  [AC_ARG_WITH(
+     [rundir], 
+     AC_HELP_STRING([--with-rundir=DIR], 
+                    [directory used for pidfiles [[LOCALSTATEDIR/run]]]),
+     [RUNDIR=$withval],
+     [RUNDIR='${localstatedir}/run'])
+   AC_SUBST([RUNDIR])])
+
+dnl Checks for the directory in which to store logs.
+AC_DEFUN([OVS_CHECK_LOGDIR],
+  [AC_ARG_WITH(
+     [logdir], 
+     AC_HELP_STRING([--with-logdir=DIR], 
+                    [directory used for logs [[LOCALSTATEDIR/log/PACKAGE]]]),
+     [LOGDIR=$withval],
+     [LOGDIR='${localstatedir}/log/${PACKAGE}'])
+   AC_SUBST([LOGDIR])])
+
+dnl Checks for __malloc_hook, etc., supported by glibc.
+AC_DEFUN([OVS_CHECK_MALLOC_HOOKS],
+  [AC_CACHE_CHECK(
+    [whether libc supports hooks for malloc and related functions],
+    [ovs_cv_malloc_hooks],
+    [AC_COMPILE_IFELSE(
+      [AC_LANG_PROGRAM(
+         [#include <malloc.h>
+         ], 
+         [(void) __malloc_hook;
+          (void) __realloc_hook;
+          (void) __free_hook;])],
+      [ovs_cv_malloc_hooks=yes],
+      [ovs_cv_malloc_hooks=no])])
+   if test $ovs_cv_malloc_hooks = yes; then
+     AC_DEFINE([HAVE_MALLOC_HOOKS], [1], 
+               [Define to 1 if you have __malloc_hook, __realloc_hook, and
+                __free_hook in <malloc.h>.])
+   fi])
+
+dnl Checks for valgrind/valgrind.h.
+AC_DEFUN([OVS_CHECK_VALGRIND], 
+  [AC_CHECK_HEADERS([valgrind/valgrind.h])])
+
+dnl Searches for a directory to put lockfiles for tty devices.
+dnl Defines C preprocessor variable TTY_LOCK_DIR to a quoted string
+dnl for that directory.
+AC_DEFUN([OVS_CHECK_TTY_LOCK_DIR],
+  [AC_CACHE_CHECK([directory used for serial device lockfiles],
+                  [ovs_cv_path_tty_locks],
+                 [# This list of candidate directories is from minicom.
+                  ovs_cv_path_tty_locks=none
+                   for dir in /etc/locks /var/lock /usr/spool/locks \
+                              /var/spool/locks /var/spool/lock \
+                              /usr/spool/uucp /var/spool/uucp /var/run; do
+                    if test -d $dir; then
+                      ovs_cv_path_tty_locks=$dir
+                      break
+                    fi
+                   done])
+   if test "$ovs_cv_path_tty_locks" = none; then
+     AC_MSG_ERROR([cannot find a directory for tty locks])
+   fi
+   AC_DEFINE_UNQUOTED([TTY_LOCK_DIR], "$ovs_cv_path_tty_locks",
+                      [Directory used for serial device lockfiles])])
+
+dnl The following check is adapted from GNU PSPP.
+dnl It searches for the ncurses library.  If it finds it, it sets
+dnl HAVE_CURSES to yes and sets NCURSES_LIBS and NCURSES_CFLAGS
+dnl appropriate.  Otherwise, it sets HAVE_CURSES to no. 
+AC_DEFUN([OVS_CHECK_CURSES],
+  [if test "$cross_compiling" != yes; then
+     AC_CHECK_PROGS([NCURSES_CONFIG], [ncurses5-config ncurses8-config])
+   fi
+   if test "$NCURSES_CONFIG" = ""; then
+     AC_SEARCH_LIBS([tgetent], [ncurses],
+         [AC_CHECK_HEADERS([term.h curses.h], 
+                           [HAVE_CURSES=yes],
+                           [HAVE_CURSES=no])])
+   else
+     save_cflags=$CFLAGS
+     CFLAGS="$CFLAGS $($NCURSES_CONFIG --cflags)"
+     AC_CHECK_HEADERS([term.h curses.h], 
+                      [HAVE_CURSES=yes],
+                      [HAVE_CURSES=no])
+     CFLAGS=$save_cflags
+     if test "$HAVE_CURSES" = yes; then
+       NCURSES_LIBS=$($NCURSES_CONFIG --libs)
+       NCURSES_CFLAGS=$($NCURSES_CONFIG --cflags)
+       AC_SUBST(NCURSES_CFLAGS)
+       AC_SUBST(NCURSES_LIBS)
+     fi
+   fi
+   AM_CONDITIONAL([HAVE_CURSES], [test "$HAVE_CURSES" = yes])])
+
+dnl Checks for linux/vt.h.
+AC_DEFUN([OVS_CHECK_LINUX_VT_H],
+  [AC_CHECK_HEADER([linux/vt.h],
+                   [HAVE_LINUX_VT_H=yes],
+                   [HAVE_LINUX_VT_H=no])
+   AM_CONDITIONAL([HAVE_LINUX_VT_H], [test "$HAVE_LINUX_VT_H" = yes])
+   if test "$HAVE_LINUX_VT_H" = yes; then
+      AC_DEFINE([HAVE_LINUX_VT_H], [1],
+                [Define to 1 if linux/vt.h is available.])
+   fi])
+
+dnl Checks for libpcre.
+AC_DEFUN([OVS_CHECK_PCRE],
+  [dnl Make sure that pkg-config is installed.
+   m4_pattern_forbid([PKG_CHECK_MODULES])
+   PKG_CHECK_MODULES([PCRE], [libpcre], [HAVE_PCRE=yes], [HAVE_PCRE=no])
+   AM_CONDITIONAL([HAVE_PCRE], [test "$HAVE_PCRE" = yes])
+   if test "$HAVE_PCRE" = yes; then
+      AC_DEFINE([HAVE_PCRE], [1], [Define to 1 if libpcre is installed.])
+   fi])
index cf24b353ec54ac525ed2d925b5f7d406621a62ad..d6bf1b0c50b9f368f5d4483a67d2b310741ec682 100644 (file)
@@ -1,3 +1,10 @@
+# Copyright (C) 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.
+
 bin_PROGRAMS += secchan/secchan
 man_MANS += secchan/secchan.8