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