Merge branch 'master' into next
authorJustin Pettit <jpettit@nicira.com>
Fri, 5 Feb 2010 23:58:27 +0000 (15:58 -0800)
committerJustin Pettit <jpettit@nicira.com>
Sat, 6 Feb 2010 01:14:55 +0000 (17:14 -0800)
Conflicts:
COPYING
datapath/datapath.h
lib/automake.mk
lib/dpif-provider.h
lib/dpif.c
lib/hmap.h
lib/netdev-provider.h
lib/netdev.c
lib/stream-ssl.h
ofproto/executer.c
ofproto/ofproto.c
ofproto/ofproto.h
tests/automake.mk
utilities/ovs-ofctl.c
utilities/ovs-vsctl.in
vswitchd/ovs-vswitchd.conf.5.in
xenserver/etc_init.d_vswitch
xenserver/etc_xensource_scripts_vif
xenserver/opt_xensource_libexec_interface-reconfigure

364 files changed:
++WARNING++ [new file with mode: 0644]
.gitignore
COPYING
INSTALL.Linux
INSTALL.XenServer
Makefile.am
README-gcov
SubmittingPatches
acinclude.m4
build-aux/.gitignore [new file with mode: 0644]
build-aux/check-structs [new file with mode: 0755]
configure.ac
datapath/actions.c
datapath/datapath.c
datapath/datapath.h
datapath/dp_notify.c
datapath/linux-2.6/.gitignore
datapath/linux-2.6/Makefile.main.in
datapath/linux-2.6/Modules.mk
datapath/linux-2.6/compat-2.6/include/linux/if.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/if_ether.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/in.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/inetdevice.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/netdevice.h
datapath/linux-2.6/compat-2.6/include/linux/skbuff.h
datapath/linux-2.6/compat-2.6/include/linux/stddef.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/linux/types.h
datapath/linux-2.6/compat-2.6/include/net/dst.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/net/ipip.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/net/net_namespace.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/net/netlink.h
datapath/linux-2.6/compat-2.6/include/net/netns/generic.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/include/net/route.h [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/ip_gre.c [new file with mode: 0644]
datapath/linux-2.6/compat-2.6/net_namespace-ip_gre.c [new file with mode: 0644]
debian/automake.mk
debian/changelog
debian/commands/update [deleted file]
debian/control
debian/corekeeper.init
debian/openvswitch-common.install
debian/openvswitch-common.manpages
debian/openvswitch-controller.init
debian/openvswitch-datapath-source.install
debian/openvswitch-monitor.init
debian/openvswitch-switch.init
debian/openvswitch-switch.install
debian/openvswitch-switch.manpages
debian/openvswitch-switch.postinst
debian/openvswitch-switch.postrm
debian/openvswitch-switch.template
debian/openvswitch-switchui.init
debian/openvswitch-switchui.install
debian/openvswitch-wdt.init
debian/po/templates.pot
debian/reconfigure [moved from debian/commands/reconfigure with 100% similarity]
debian/rules
debian/rules.modules [new file with mode: 0755]
extras/ezio/automake.mk
extras/ezio/ezio-term.c
extras/ezio/ovs-switchui.c
extras/ezio/tty.c
include/openflow/.gitignore [new file with mode: 0644]
include/openflow/automake.mk
include/openflow/nicira-ext.h
include/openflow/openflow-mgmt.h [deleted file]
include/openvswitch/automake.mk
include/openvswitch/gre.h [new file with mode: 0644]
lib/.gitignore
lib/aes128.c [new file with mode: 0644]
lib/aes128.h [new file with mode: 0644]
lib/automake.mk
lib/bitmap.c
lib/bitmap.h
lib/byteq.c [moved from extras/ezio/byteq.c with 81% similarity]
lib/byteq.h [moved from extras/ezio/byteq.h with 83% similarity]
lib/cfg.c [deleted file]
lib/cfg.h [deleted file]
lib/classifier.c
lib/command-line.c
lib/command-line.h
lib/common-syn.man [new file with mode: 0644]
lib/compiler.h
lib/coverage.c
lib/daemon-syn.man [new file with mode: 0644]
lib/daemon.c
lib/daemon.h
lib/daemon.man
lib/dhcp-client.c
lib/dpif-linux.c
lib/dpif-netdev.c
lib/dpif-provider.h
lib/dpif.c
lib/dpif.h
lib/dpif.man
lib/dynamic-string.c
lib/dynamic-string.h
lib/fatal-signal.c
lib/fatal-signal.h
lib/fault.c [deleted file]
lib/hash.c
lib/hash.h
lib/hmap.c
lib/hmap.h
lib/json.c [new file with mode: 0644]
lib/json.h [new file with mode: 0644]
lib/jsonrpc.c [new file with mode: 0644]
lib/jsonrpc.h [new file with mode: 0644]
lib/learning-switch.c
lib/lockfile.c [new file with mode: 0644]
lib/lockfile.h [new file with mode: 0644]
lib/mac-learning.c
lib/mac-learning.h
lib/netdev-linux.c
lib/netdev-provider.h
lib/netdev.c
lib/netdev.h
lib/ovsdb-data.c [new file with mode: 0644]
lib/ovsdb-data.h [new file with mode: 0644]
lib/ovsdb-error.c [new file with mode: 0644]
lib/ovsdb-error.h [new file with mode: 0644]
lib/ovsdb-idl-provider.h [new file with mode: 0644]
lib/ovsdb-idl.c [new file with mode: 0644]
lib/ovsdb-idl.h [new file with mode: 0644]
lib/ovsdb-parser.c [new file with mode: 0644]
lib/ovsdb-parser.h [new file with mode: 0644]
lib/ovsdb-types.c [new file with mode: 0644]
lib/ovsdb-types.h [new file with mode: 0644]
lib/packets.c
lib/packets.h
lib/poll-loop.c
lib/poll-loop.h
lib/process.c
lib/queue.h
lib/rconn.c
lib/reconnect.c [new file with mode: 0644]
lib/reconnect.h [new file with mode: 0644]
lib/rtnetlink.c
lib/rtnetlink.h
lib/sha1.c
lib/sha1.h
lib/shash.c
lib/shash.h
lib/signals.c
lib/socket-util.c
lib/socket-util.h
lib/sort.c [new file with mode: 0644]
lib/sort.h [moved from vswitchd/ovs-vswitchd.h with 72% similarity]
lib/ssl-bootstrap-syn.man [new file with mode: 0644]
lib/ssl-bootstrap.man [new file with mode: 0644]
lib/ssl-peer-ca-cert.man [new file with mode: 0644]
lib/ssl-syn.man [new file with mode: 0644]
lib/ssl.man [new file with mode: 0644]
lib/stp.c
lib/stream-fd.c [new file with mode: 0644]
lib/stream-fd.h [moved from lib/vconn-stream.h with 57% similarity]
lib/stream-provider.h [new file with mode: 0644]
lib/stream-ssl.c [moved from lib/vconn-ssl.c with 81% similarity]
lib/stream-ssl.h [moved from lib/vconn-ssl.h with 65% similarity]
lib/stream-tcp.c [moved from lib/vconn-tcp.c with 61% similarity]
lib/stream-unix.c [moved from lib/vconn-unix.c with 74% similarity]
lib/stream.c [new file with mode: 0644]
lib/stream.h [new file with mode: 0644]
lib/svec.c
lib/svec.h
lib/timeval.c
lib/timeval.h
lib/unicode.c [new file with mode: 0644]
lib/unicode.h [new file with mode: 0644]
lib/unixctl.c
lib/unixctl.h
lib/util.c
lib/util.h
lib/uuid.c [new file with mode: 0644]
lib/uuid.h [new file with mode: 0644]
lib/vconn-active.man [new file with mode: 0644]
lib/vconn-passive.man [new file with mode: 0644]
lib/vconn-provider.h
lib/vconn-stream.c
lib/vconn.c
lib/vconn.h
lib/vlog-modules.def
lib/vlog-syn.man [new file with mode: 0644]
lib/vlog.c
m4/openvswitch.m4
ofproto/automake.mk
ofproto/commands/automake.mk [deleted file]
ofproto/commands/reboot [deleted file]
ofproto/discovery.c
ofproto/executer.c [deleted file]
ofproto/executer.h [deleted file]
ofproto/in-band.c
ofproto/netflow.c
ofproto/netflow.h
ofproto/ofproto-sflow.c
ofproto/ofproto.c
ofproto/ofproto.h
ofproto/pinsched.c
ofproto/pktbuf.c
ofproto/status.c
ovsdb/.gitignore [new file with mode: 0644]
ovsdb/SPECS [new file with mode: 0644]
ovsdb/automake.mk [new file with mode: 0644]
ovsdb/column.c [new file with mode: 0644]
ovsdb/column.h [new file with mode: 0644]
ovsdb/condition.c [new file with mode: 0644]
ovsdb/condition.h [new file with mode: 0644]
ovsdb/execution.c [new file with mode: 0644]
ovsdb/file.c [new file with mode: 0644]
ovsdb/file.h [moved from vswitchd/mgmt.h with 68% similarity]
ovsdb/jsonrpc-server.c [new file with mode: 0644]
ovsdb/jsonrpc-server.h [new file with mode: 0644]
ovsdb/log.c [new file with mode: 0644]
ovsdb/log.h [new file with mode: 0644]
ovsdb/mutation.c [new file with mode: 0644]
ovsdb/mutation.h [new file with mode: 0644]
ovsdb/ovsdb-client.1.in [new file with mode: 0644]
ovsdb/ovsdb-client.c [new file with mode: 0644]
ovsdb/ovsdb-idlc.1 [new file with mode: 0644]
ovsdb/ovsdb-idlc.in [new file with mode: 0755]
ovsdb/ovsdb-server.1.in [new file with mode: 0644]
ovsdb/ovsdb-server.c [new file with mode: 0644]
ovsdb/ovsdb-tool.1.in [new file with mode: 0644]
ovsdb/ovsdb-tool.c [new file with mode: 0644]
ovsdb/ovsdb.c [new file with mode: 0644]
ovsdb/ovsdb.h [new file with mode: 0644]
ovsdb/query.c [new file with mode: 0644]
ovsdb/query.h [new file with mode: 0644]
ovsdb/remote-active.man [new file with mode: 0644]
ovsdb/remote-passive.man [new file with mode: 0644]
ovsdb/row.c [new file with mode: 0644]
ovsdb/row.h [new file with mode: 0644]
ovsdb/simplejson/__init__.py [new file with mode: 0644]
ovsdb/simplejson/_speedups.c [new file with mode: 0644]
ovsdb/simplejson/decoder.py [new file with mode: 0644]
ovsdb/simplejson/encoder.py [new file with mode: 0644]
ovsdb/simplejson/scanner.py [new file with mode: 0644]
ovsdb/simplejson/tests/__init__.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_check_circular.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_decode.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_default.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_dump.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_encode_basestring_ascii.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_fail.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_float.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_indent.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_pass1.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_pass2.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_pass3.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_recursion.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_scanstring.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_separators.py [new file with mode: 0644]
ovsdb/simplejson/tests/test_unicode.py [new file with mode: 0644]
ovsdb/simplejson/tool.py [new file with mode: 0644]
ovsdb/table.c [new file with mode: 0644]
ovsdb/table.h [new file with mode: 0644]
ovsdb/transaction.c [new file with mode: 0644]
ovsdb/transaction.h [new file with mode: 0644]
ovsdb/trigger.c [new file with mode: 0644]
ovsdb/trigger.h [new file with mode: 0644]
tests/.gitignore
tests/aes128.at [new file with mode: 0644]
tests/atlocal.in
tests/automake.mk
tests/check-structs.at [new file with mode: 0644]
tests/daemon.at [new file with mode: 0644]
tests/dir_name.at [new file with mode: 0644]
tests/idltest.ann [new file with mode: 0644]
tests/idltest.ovsschema [new file with mode: 0644]
tests/json.at [new file with mode: 0644]
tests/jsonrpc.at [new file with mode: 0644]
tests/lcov-post.at [deleted file]
tests/lcov-pre.at [deleted file]
tests/lcov-wrapper.in [new file with mode: 0755]
tests/library.at
tests/lockfile.at [new file with mode: 0644]
tests/openssl.supp [new file with mode: 0644]
tests/ovs-vsctl.at
tests/ovsdb-column.at [new file with mode: 0644]
tests/ovsdb-condition.at [new file with mode: 0644]
tests/ovsdb-data.at [new file with mode: 0644]
tests/ovsdb-execution.at [new file with mode: 0644]
tests/ovsdb-file.at [new file with mode: 0644]
tests/ovsdb-idl.at [new file with mode: 0644]
tests/ovsdb-log.at [new file with mode: 0644]
tests/ovsdb-macros.at [new file with mode: 0644]
tests/ovsdb-monitor.at [new file with mode: 0644]
tests/ovsdb-mutation.at [new file with mode: 0644]
tests/ovsdb-query.at [new file with mode: 0644]
tests/ovsdb-row.at [new file with mode: 0644]
tests/ovsdb-server.at [new file with mode: 0644]
tests/ovsdb-table.at [new file with mode: 0644]
tests/ovsdb-transaction.at [new file with mode: 0644]
tests/ovsdb-trigger.at [new file with mode: 0644]
tests/ovsdb-types.at [new file with mode: 0644]
tests/ovsdb.at [new file with mode: 0644]
tests/reconnect.at [new file with mode: 0644]
tests/stp.at
tests/test-aes128.c [new file with mode: 0644]
tests/test-classifier.c
tests/test-dhcp-client.c
tests/test-dir_name.c [moved from lib/fault.h with 67% similarity]
tests/test-json.c [new file with mode: 0644]
tests/test-jsonrpc.c [new file with mode: 0644]
tests/test-lockfile.c [new file with mode: 0644]
tests/test-ovsdb.c [new file with mode: 0644]
tests/test-reconnect.c [new file with mode: 0644]
tests/test-stp.c
tests/test-timeval.c [new file with mode: 0644]
tests/test-uuid.c [new file with mode: 0644]
tests/test-vconn.c
tests/testpki-cacert.pem [new file with mode: 0644]
tests/testpki-cert.pem [new file with mode: 0644]
tests/testpki-cert2.pem [new file with mode: 0644]
tests/testpki-privkey.pem [new file with mode: 0644]
tests/testpki-privkey2.pem [new file with mode: 0644]
tests/testpki-req.pem [new file with mode: 0644]
tests/testpki-req2.pem [new file with mode: 0644]
tests/testsuite.at
tests/timeval.at [new file with mode: 0644]
tests/uuid.at [new file with mode: 0644]
tests/uuidfilt.pl [new file with mode: 0755]
tests/valgrind-wrapper.in [new file with mode: 0755]
tests/vconn.at [new file with mode: 0644]
utilities/.gitignore
utilities/automake.mk
utilities/ovs-appctl.c
utilities/ovs-cfg-mod.8.in [deleted file]
utilities/ovs-cfg-mod.c [deleted file]
utilities/ovs-controller.8.in
utilities/ovs-controller.c
utilities/ovs-discover.c
utilities/ovs-dpctl.c
utilities/ovs-ofctl.8.in
utilities/ovs-ofctl.c
utilities/ovs-openflowd.8.in
utilities/ovs-openflowd.c
utilities/ovs-vsctl.8.in
utilities/ovs-vsctl.c [new file with mode: 0644]
utilities/ovs-vsctl.in [deleted file]
vswitchd/.gitignore
vswitchd/automake.mk
vswitchd/bridge.c
vswitchd/bridge.h
vswitchd/mgmt.c [deleted file]
vswitchd/ovs-brcompatd.8.in
vswitchd/ovs-brcompatd.c
vswitchd/ovs-vswitchd.8.in
vswitchd/ovs-vswitchd.c
vswitchd/ovs-vswitchd.conf.5.in [deleted file]
vswitchd/proc-net-compat.c
vswitchd/vswitch-idl.ann [new file with mode: 0644]
vswitchd/vswitch.ovsschema [new file with mode: 0644]
xenserver/etc_init.d_vswitch
xenserver/etc_init.d_vswitch-xapi-update
xenserver/etc_logrotate.d_vswitch
xenserver/etc_xapi.d_plugins_vswitch-cfg-update
xenserver/etc_xensource_scripts_vif
xenserver/opt_xensource_libexec_InterfaceReconfigureVswitch.py
xenserver/usr_lib_xsconsole_plugins-base_XSFeatureVSwitch.py
xenserver/usr_sbin_brctl
xenserver/usr_share_vswitch_scripts_dump-vif-details
xenserver/usr_share_vswitch_scripts_sysconfig.template
xenserver/vswitch-xen.spec

diff --git a/++WARNING++ b/++WARNING++
new file mode 100644 (file)
index 0000000..d8b05ea
--- /dev/null
@@ -0,0 +1,5 @@
+This version of Open vSwitch is in the middle of a transition from
+using the ovs-vswitchd.conf configuration file to database-based
+configuration.  Many features are known to be broken, so please do not
+use this version of Open vSwitch if you expect it to be working
+stably.
index 3230266..4e1fccb 100644 (file)
@@ -15,6 +15,7 @@
 .*.cmd
 .*.swp
 .deps
+.dirstamp
 .libs
 .tmp_versions
 /Makefile
@@ -22,7 +23,6 @@
 /aclocal.m4
 /autom4te.cache
 /build-arch-stamp
-/build-aux
 /build-indep-stamp
 /compile
 /config.guess
diff --git a/COPYING b/COPYING
index 375efec..39dd45c 100644 (file)
--- a/COPYING
+++ b/COPYING
@@ -23,6 +23,30 @@ Files under the xenserver directory are licensed on a file-by-file
 basis.  Some files are under an uncertain license that may not be
 DFSG-compliant or GPL-compatible.  Refer to each file for details.
 
+The files under ovsdb/simplejson are covered by the following license:
+
+    Copyright (c) 2006 Bob Ippolito
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use, copy,
+    modify, merge, publish, distribute, sublicense, and/or sell copies
+    of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+    DEALINGS IN THE SOFTWARE.
+
 Files lib/sflow*.[ch] are licensed under the terms of the InMon sFlow
 licence that is available at:
         http://www.inmon.com/technology/sflowlicense.txt
index 7090ce6..c0344f2 100644 (file)
@@ -67,6 +67,8 @@ you will also need the following software:
 
     - pkg-config.  We test with version 0.22.
 
+    - Python 2.x, for x >= 4.
+
 Installation Requirements
 -------------------------
 
index 46250b2..5476633 100644 (file)
@@ -5,8 +5,8 @@ This document describes how to build and install Open vSwitch on a
 Citrix XenServer host.  If you want to install Open vSwitch on a
 generic Linux host, see INSTALL.Linux instead.
 
-These instructions have been tested with XenServer versions 5.5.0 and
-5.5.900.
+These instructions have been tested with XenServer versions 5.5.0,
+5.5.0-24648p (Update 1), 5.5.0-25727p (Update 2), and 5.5.900.
 
 Building Open vSwitch for XenServer
 -----------------------------------
index de51e10..bb8245e 100644 (file)
@@ -25,6 +25,7 @@ else
 AM_LDFLAGS = -export-dynamic
 endif
 
+BUILT_SOURCES =
 CLEANFILES =
 DISTCLEANFILES =
 EXTRA_DIST = INSTALL.bridge \
@@ -32,25 +33,29 @@ EXTRA_DIST = INSTALL.bridge \
        INSTALL.userspace \
        INSTALL.OpenFlow \
        INSTALL.SSL \
-       INSTALL.XenServer 
+       INSTALL.XenServer \
+       README-gcov \
+       ++WARNING++
 bin_PROGRAMS =
 sbin_PROGRAMS =
 bin_SCRIPTS =
-dist_commands_DATA =
 dist_man_MANS =
 dist_pkgdata_SCRIPTS =
 dist_sbin_SCRIPTS =
 man_MANS =
+noinst_DATA =
 noinst_HEADERS =
 noinst_LIBRARIES =
 noinst_PROGRAMS =
 noinst_SCRIPTS =
+OVSIDL_BUILT =
+SUFFIXES =
 
 EXTRA_DIST += soexpand.pl
 
 ro_c = echo '/* -*- mode: c; buffer-read-only: t -*- */'
 
-SUFFIXES = .in
+SUFFIXES += .in
 .in:
        $(PERL) $(srcdir)/soexpand.pl -I$(srcdir) < $< | \
            sed \
@@ -63,6 +68,7 @@ SUFFIXES = .in
                 -e 's,[@]localstatedir[@],$(localstatedir),g' \
                 -e 's,[@]pkgdatadir[@],$(pkgdatadir),g' \
                 -e 's,[@]sysconfdir[@],$(sysconfdir),g' \
+                -e 's,[@]abs_top_srcdir[@],$(abs_top_srcdir),g' \
             > $@.tmp
        @if head -n 1 $@.tmp | grep -q '#!'; then \
            echo chmod +x $@.tmp; \
@@ -78,5 +84,6 @@ include include/automake.mk
 include third-party/automake.mk
 include debian/automake.mk
 include vswitchd/automake.mk
+include ovsdb/automake.mk
 include xenserver/automake.mk
 include extras/ezio/automake.mk
index 665a1ec..01c49cc 100644 (file)
@@ -15,17 +15,10 @@ code-coverage related options:
   --enable-coverage
   --enable-coverage=yes
 
-    Build with gcov code coverage support, but do not assume that any
-    coverage-related tools are installed and do not add special
-    coverage support to the test suite.
+    Build with gcov code coverage support.
 
-  --enable-coverage=lcov
-
-    Build with gcov code coverage support, as above, but also add
-    support for coverage analysis to the test suite.  Running "make
-    check" will produce a directory "tests/coverage.html" in the build
-    directory with an analysis of the test suite's coverage.
-
-    This setting requires the lcov suite of utilities to be installed.
-    The "lcov" and "genhtml" programs from lcov must be in PATH.  lcov
-    is available at: http://ltp.sourceforge.net/coverage/lcov.php
+    If you enable coverage and you have the "lcov" and "genhtml"
+    programs in PATH, then you may run "make check-lcov" to produce a
+    directory "tests/coverage.html" in the build directory with an
+    analysis of the test suite's coverage.  lcov is available at
+    http://ltp.sourceforge.net/coverage/lcov.php
index 917cddb..280f11e 100644 (file)
@@ -193,7 +193,7 @@ index 32647ea..00cffbc 100644
      /* Get rid of deleted bridges and add new bridges. */
      svec_sort(&old_br);
 @@ -793,7 +780,7 @@ bridge_create(const char *name)
-     br = xcalloc(1, sizeof *br);
+     br = xzalloc(sizeof *br);
  
      error = dpif_create(name, &br->dpif);
 -    if (error == EEXIST) {
index a254c23..e38676f 100644 (file)
@@ -42,7 +42,14 @@ AC_DEFUN([OVS_CHECK_LINUX26], [
     AC_MSG_CHECKING([for Linux 2.6 source directory])
     KSRC26=$KBUILD26
     if test ! -e $KSRC26/include/linux/kernel.h; then
-      KSRC26=`(cd $KBUILD26 && pwd -P) | sed 's,-[[^-]]*$,-common,'`
+      case `echo "$KBUILD26" | sed 's,/*$,,'` in # (
+        */build)
+          KSRC26=`echo "$KBUILD26" | sed 's,/build/*$,/source,'`
+          ;; # (
+        *)
+          KSRC26=`(cd $KBUILD26 && pwd -P) | sed 's,-[[^-]]*$,-common,'`
+          ;;
+      esac
       if test ! -e $KSRC26/include/linux/kernel.h; then
         AC_MSG_ERROR([cannot find source directory])
       fi
@@ -111,6 +118,16 @@ AC_DEFUN([OVS_CHECK_VETH], [
   fi
 ])
 
+AC_DEFUN([OVS_CHECK_GRE], [
+  AC_MSG_CHECKING([whether to build gre module])
+  if test "$sublevel" -ge 18; then
+    AC_MSG_RESULT([yes])
+    AC_SUBST([BUILD_GRE], 1)
+  else
+    AC_MSG_RESULT([no])
+  fi
+])
+
 AC_DEFUN([OVS_CHECK_LOG2_H], [
   AC_MSG_CHECKING([for $KSRC26/include/linux/log2.h])
   if test -e $KSRC26/include/linux/log2.h; then
@@ -129,6 +146,8 @@ AC_DEFUN([OVS_CHECK_LINUX26_COMPAT], [
   rm -f datapath/linux-2.6/kcompat.h.new
   mkdir -p datapath/linux-2.6
   : > datapath/linux-2.6/kcompat.h.new
+  OVS_GREP_IFELSE([$KSRC26/include/linux/types.h], [bool],
+                  [OVS_DEFINE([HAVE_BOOL_TYPE])])
   OVS_GREP_IFELSE([$KSRC26/include/linux/skbuff.h], [skb_transport_header],
                   [OVS_DEFINE([HAVE_SKBUFF_HEADER_HELPERS])])
   OVS_GREP_IFELSE([$KSRC26/include/linux/skbuff.h], [raw],
@@ -144,6 +163,10 @@ AC_DEFUN([OVS_CHECK_LINUX26_COMPAT], [
                   [OVS_DEFINE([HAVE_CSUM_UNFOLD])])
   OVS_GREP_IFELSE([$KSRC26/include/linux/skbuff.h], [skb_cow],
                   [OVS_DEFINE([HAVE_SKB_COW])])
+  OVS_GREP_IFELSE([$KSRC26/include/net/netlink.h], [nla_get_be16],
+                  [OVS_DEFINE([HAVE_NLA_GET_BE16])])
+  OVS_GREP_IFELSE([$KSRC26/include/linux/in.h], [ipv4_is_multicast],
+                  [OVS_DEFINE([HAVE_IPV4_IS_MULTICAST])])
   # Check for the proto_data_valid member in struct sk_buff.  The [^@]
   # is necessary because some versions of this header remove the
   # member but retain the kerneldoc comment that describes it (which
@@ -153,6 +176,7 @@ AC_DEFUN([OVS_CHECK_LINUX26_COMPAT], [
                   [OVS_DEFINE([HAVE_PROTO_DATA_VALID])])
   OVS_CHECK_LOG2_H
   OVS_CHECK_VETH
+  OVS_CHECK_GRE
   if cmp -s datapath/linux-2.6/kcompat.h.new \
             datapath/linux-2.6/kcompat.h >/dev/null 2>&1; then
     rm datapath/linux-2.6/kcompat.h.new
diff --git a/build-aux/.gitignore b/build-aux/.gitignore
new file mode 100644 (file)
index 0000000..999eae2
--- /dev/null
@@ -0,0 +1,4 @@
+/compile
+/depcomp
+/install-sh
+/missing
diff --git a/build-aux/check-structs b/build-aux/check-structs
new file mode 100755 (executable)
index 0000000..545c80a
--- /dev/null
@@ -0,0 +1,267 @@
+#! /usr/bin/python
+
+import sys
+import re
+
+macros = {}
+
+anyWarnings = False
+
+types = {}
+types['char'] = {"size": 1, "alignment": 1}
+types['uint8_t'] = {"size": 1, "alignment": 1}
+types['uint16_t'] = {"size": 2, "alignment": 2}
+types['uint32_t'] = {"size": 4, "alignment": 4}
+types['uint64_t'] = {"size": 8, "alignment": 8}
+
+token = None
+line = ""
+idRe = "[a-zA-Z_][a-zA-Z_0-9]*"
+tokenRe = "#?" + idRe + "|[0-9]+|."
+inComment = False
+inDirective = False
+def getToken():
+    global token
+    global line
+    global inComment
+    global inDirective
+    while True:
+        line = line.lstrip()
+        if line != "":
+            if line.startswith("/*"):
+                inComment = True
+                line = line[2:]
+            elif inComment:
+                commentEnd = line.find("*/")
+                if commentEnd < 0:
+                    line = ""
+                else:
+                    inComment = False
+                    line = line[commentEnd + 2:]
+            else:
+                match = re.match(tokenRe, line)
+                token = match.group(0)
+                line = line[len(token):]
+                if token.startswith('#'):
+                    inDirective = True
+                elif token in macros and not inDirective:
+                    line = macros[token] + line
+                    continue
+                return True
+        elif inDirective:
+            token = "$"
+            inDirective = False
+            return True
+        else:
+            global lineNumber
+            line = inputFile.readline()
+            lineNumber += 1
+            while line.endswith("\\\n"):
+                line = line[:-2] + inputFile.readline()
+                lineNumber += 1
+            if line == "":
+                if token == None:
+                    fatal("unexpected end of input")
+                token = None
+                return False
+    
+def fatal(msg):
+    sys.stderr.write("%s:%d: error at \"%s\": %s\n" % (fileName, lineNumber, token, msg))
+    sys.exit(1)
+    
+def warn(msg):
+    global anyWarnings
+    anyWarnings = True
+    sys.stderr.write("%s:%d: warning: %s\n" % (fileName, lineNumber, msg))
+
+def skipDirective():
+    getToken()
+    while token != '$':
+        getToken()
+
+def isId(s):
+    return re.match(idRe + "$", s) != None
+
+def forceId():
+    if not isId(token):
+        fatal("identifier expected")
+
+def forceInteger():
+    if not re.match('[0-9]+$', token):
+        fatal("integer expected")
+
+def match(t):
+    if token == t:
+        getToken()
+        return True
+    else:
+        return False
+
+def forceMatch(t):
+    if not match(t):
+        fatal("%s expected" % t)
+
+def parseTaggedName():
+    assert token in ('struct', 'union')
+    name = token
+    getToken()
+    forceId()
+    name = "%s %s" % (name, token)
+    getToken()
+    return name
+
+def parseTypeName():
+    if token in ('struct', 'union'):
+        name = parseTaggedName()
+    elif isId(token):
+        name = token
+        getToken()
+    else:
+        fatal("type name expected")
+
+    if name in types:
+        return name
+    else:
+        fatal("unknown type \"%s\"" % name)
+
+def parseStruct():
+    isStruct = token == 'struct'
+    structName = parseTaggedName()
+    if token == ";":
+        return
+
+    ofs = size = 0
+    alignment = 4               # ARM has minimum 32-bit alignment
+    forceMatch('{')
+    while not match('}'):
+        typeName = parseTypeName()
+        typeSize = types[typeName]['size']
+        typeAlignment = types[typeName]['alignment']
+
+        forceId()
+        memberName = token
+        getToken()
+
+        if match('['):
+            if token == ']':
+                count = 0
+            else:
+                forceInteger()
+                count = int(token)
+                getToken()
+            forceMatch(']')
+        else:
+            count = 1
+
+        nBytes = typeSize * count
+        if isStruct:
+            if ofs % typeAlignment:
+                shortage = typeAlignment - (ofs % typeAlignment)
+                warn("%s member %s is %d bytes short of %d-byte alignment"
+                     % (structName, memberName, shortage, typeAlignment))
+                size += shortage
+                ofs += shortage
+            size += nBytes
+            ofs += nBytes
+        else:
+            if nBytes > size:
+                size = nBytes
+        if typeAlignment > alignment:
+            alignment = typeAlignment
+
+        forceMatch(';')
+    if size % alignment:
+        shortage = alignment - (size % alignment)
+        if (structName == "struct ofp_packet_in" and
+            shortage == 2 and
+            memberName == 'data' and
+            count == 0):
+            # This is intentional
+            pass
+        else:
+            warn("%s needs %d bytes of tail padding" % (structName, shortage))
+        size += shortage
+    types[structName] = {"size": size, "alignment": alignment}
+
+def checkStructs():
+    if len(sys.argv) < 2:
+        sys.stderr.write("at least one non-option argument required; "
+                         "use --help for help")
+        sys.exit(1)
+
+    if '--help' in sys.argv:
+        argv0 = sys.argv[0]
+        slash = argv0.rfind('/')
+        if slash:
+            argv0 = argv0[slash + 1:]
+        print '''\
+%(argv0)s, for checking struct and struct member alignment
+usage: %(argv0)s HEADER [HEADER]...
+
+This program reads the header files specified on the command line and
+verifies that all struct members are aligned on natural boundaries
+without any need for the compiler to add additional padding.  It also
+verifies that each struct's size is a multiple of 32 bits (because
+some ABIs for ARM require all structs to be a multiple of 32 bits), or
+64 bits if the struct has any 64-bit members, again without the
+compiler adding additional padding.  Finally, it checks struct size
+assertions using OFP_ASSERT.
+
+Header files are read in the order specified.  #include directives are
+not processed, so specify them in dependency order.
+
+This program is specialized for reading include/openflow/openflow.h
+and include/openflow/nicira-ext.h.  It will not work on arbitrary
+header files without extensions.''' % {"argv0": argv0}
+        sys.exit(0)
+
+    global fileName
+    for fileName in sys.argv[1:]:
+        global inputFile
+        global lineNumber
+        inputFile = open(fileName)
+        lineNumber = 0
+        while getToken():
+            if token in ("#ifdef", "#ifndef", "#include",
+                         "#endif", "#elif", "#else"):
+                skipDirective()
+            elif token == "#define":
+                getToken()
+                name = token
+                if line.startswith('('):
+                    skipDirective()
+                else:
+                    definition = ""
+                    getToken()
+                    while token != '$':
+                        definition += token
+                        getToken()
+                    macros[name] = definition
+            elif token == "enum":
+                while token != ';':
+                    getToken()
+            elif token in ('struct', 'union'):
+                parseStruct()
+            elif match('OFP_ASSERT') or match('BOOST_STATIC_ASSERT'):
+                forceMatch('(')
+                forceMatch('sizeof')
+                forceMatch('(')
+                typeName = parseTypeName()
+                forceMatch(')')
+                forceMatch('=')
+                forceMatch('=')
+                forceInteger()
+                size = int(token)
+                getToken()
+                forceMatch(')')
+                if types[typeName]['size'] != size:
+                    warn("%s is %d bytes long but declared as %d" % (
+                            typeName, types[typeName]['size'], size))
+            else:
+                fatal("parse error")
+        inputFile.close()
+    if anyWarnings:
+        sys.exit(1)
+
+if __name__ == '__main__':
+    checkStructs()
index e84a3cb..6a8a1ea 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2008, 2009 Nicira Networks
+# Copyright (c) 2008, 2009, 2010 Nicira Networks
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -38,6 +38,8 @@ AC_USE_SYSTEM_EXTENSIONS
 AC_C_BIGENDIAN
 AC_SYS_LARGEFILE
 
+AC_SEARCH_LIBS([pow], [m])
+
 OVS_CHECK_COVERAGE
 OVS_CHECK_NDEBUG
 OVS_CHECK_NETLINK
@@ -56,7 +58,6 @@ OVS_CHECK_MALLOC_HOOKS
 OVS_CHECK_VALGRIND
 OVS_CHECK_TTY_LOCK_DIR
 OVS_CHECK_SOCKET_LIBS
-OVS_CHECK_FAULT_LIBS
 
 AC_CHECK_FUNCS([strsignal])
 
@@ -88,4 +89,7 @@ datapath/linux-2.6/Makefile
 datapath/linux-2.6/Makefile.main
 tests/atlocal])
 
+dnl This makes sure that include/openflow gets created in the build directory.
+AC_CONFIG_COMMANDS([include/openflow/openflow.h.stamp])
+
 AC_OUTPUT
index 8b32de4..4fc0a4a 100644 (file)
@@ -213,10 +213,43 @@ static void update_csum(__sum16 *sum, struct sk_buff *skb,
                        __be32 from, __be32 to, int pseudohdr)
 {
        __be32 diff[] = { ~from, to };
-       if (skb->ip_summed != CHECKSUM_PARTIAL) {
+
+/* On older kernels, CHECKSUM_PARTIAL and CHECKSUM_COMPLETE are both defined
+ * as CHECKSUM_HW.  However, we can make some inferences so that we can update
+ * the checksums appropriately. */
+       enum {
+               CSUM_PARTIAL,   /* Partial checksum, skb->csum undefined. */
+               CSUM_PACKET,    /* In-packet checksum, skb->csum undefined. */
+               CSUM_COMPLETE,  /* In-packet checksum, skb->csum valid. */
+       } csum_type;
+
+       csum_type = CSUM_PACKET;
+#ifndef CHECKSUM_HW
+       /* Newer kernel, just map between kernel types and ours. */
+       if (skb->ip_summed == CHECKSUM_PARTIAL)
+               csum_type = CSUM_PARTIAL;
+       else if (skb->ip_summed == CHECKSUM_COMPLETE)
+               csum_type = CSUM_COMPLETE;
+#else
+       /* In theory this could be either CHECKSUM_PARTIAL or CHECKSUM_COMPLETE.
+        * However, we should only get CHECKSUM_PARTIAL packets from Xen, which
+        * uses some special fields to represent this (see below).  Since we
+        * can only make one type work, pick the one that actually happens in
+        * practice. */
+       if (skb->ip_summed == CHECKSUM_HW)
+               csum_type = CSUM_COMPLETE;
+#endif
+#if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID)
+       /* Xen has a special way of representing CHECKSUM_PARTIAL on older
+        * kernels. */
+       if (skb->proto_csum_blank)
+               csum_type = CSUM_PARTIAL;
+#endif
+
+       if (csum_type != CSUM_PARTIAL) {
                *sum = csum_fold(csum_partial((char *)diff, sizeof(diff),
                                ~csum_unfold(*sum)));
-               if (skb->ip_summed == CHECKSUM_COMPLETE && pseudohdr)
+               if (csum_type == CSUM_COMPLETE && pseudohdr)
                        skb->csum = ~csum_partial((char *)diff, sizeof(diff),
                                                ~skb->csum);
        } else if (pseudohdr)
@@ -276,7 +309,7 @@ set_tp_port(struct sk_buff *skb, struct odp_flow_key *key,
                u16 old = *f;
                u16 new = a->tp_port;
                update_csum((u16*)(skb_transport_header(skb) + check_ofs), 
-                               skb, old, new, 1);
+                               skb, old, new, 0);
                *f = new;
        }
        return skb;
@@ -302,6 +335,7 @@ int dp_xmit_skb(struct sk_buff *skb)
                return -E2BIG;
        }
 
+       forward_ip_summed(skb);
        dev_queue_xmit(skb);
 
        return len;
index ba363fb..6a3b9ec 100644 (file)
@@ -420,6 +420,7 @@ got_port_no:
        if (err)
                goto out_put;
 
+       set_dp_devs_mtu(dp, dev);
        dp_sysfs_add_if(dp->ports[port_no]);
 
        err = __put_user(port_no, &port.port);
@@ -576,9 +577,10 @@ static int dp_frame_hook(struct net_bridge_port *p, struct sk_buff **pskb)
 #endif
 
 #if defined(CONFIG_XEN) && defined(HAVE_PROTO_DATA_VALID)
-/* This code is copied verbatim from net/dev/core.c in Xen's
- * linux-2.6.18-92.1.10.el5.xs5.0.0.394.644.  We can't call those functions
- * directly because they aren't exported. */
+/* This code is based on a skb_checksum_setup from net/dev/core.c from a
+ * combination of Lenny's 2.6.26 Xen kernel and Xen's
+ * linux-2.6.18-92.1.10.el5.xs5.0.0.394.644.  We can't call this function
+ * directly because it isn't exported in all versions. */
 static int skb_pull_up_to(struct sk_buff *skb, void *ptr)
 {
        if (ptr < (void *)skb->tail)
@@ -593,37 +595,116 @@ static int skb_pull_up_to(struct sk_buff *skb, void *ptr)
 
 int vswitch_skb_checksum_setup(struct sk_buff *skb)
 {
-       if (skb->proto_csum_blank) {
-               if (skb->protocol != htons(ETH_P_IP))
-                       goto out;
-               if (!skb_pull_up_to(skb, skb->nh.iph + 1))
-                       goto out;
-               skb->h.raw = (unsigned char *)skb->nh.iph + 4*skb->nh.iph->ihl;
-               switch (skb->nh.iph->protocol) {
-               case IPPROTO_TCP:
-                       skb->csum = offsetof(struct tcphdr, check);
-                       break;
-               case IPPROTO_UDP:
-                       skb->csum = offsetof(struct udphdr, check);
-                       break;
-               default:
-                       if (net_ratelimit())
-                               printk(KERN_ERR "Attempting to checksum a non-"
-                                      "TCP/UDP packet, dropping a protocol"
-                                      " %d packet", skb->nh.iph->protocol);
-                       goto out;
-               }
-               if (!skb_pull_up_to(skb, skb->h.raw + skb->csum + 2))
-                       goto out;
-               skb->ip_summed = CHECKSUM_HW;
-               skb->proto_csum_blank = 0;
+       struct iphdr *iph;
+       unsigned char *th;
+       int err = -EPROTO;
+       __u16 csum_start, csum_offset;
+
+       if (!skb->proto_csum_blank)
+               return 0;
+
+       if (skb->protocol != htons(ETH_P_IP))
+               goto out;
+
+       if (!skb_pull_up_to(skb, skb_network_header(skb) + 1))
+               goto out;
+
+       iph = ip_hdr(skb);
+       th = skb_network_header(skb) + 4 * iph->ihl;
+
+       csum_start = th - skb->head;
+       switch (iph->protocol) {
+       case IPPROTO_TCP:
+               csum_offset = offsetof(struct tcphdr, check);
+               break;
+       case IPPROTO_UDP:
+               csum_offset = offsetof(struct udphdr, check);
+               break;
+       default:
+               if (net_ratelimit())
+                       printk(KERN_ERR "Attempting to checksum a non-"
+                              "TCP/UDP packet, dropping a protocol"
+                              " %d packet", iph->protocol);
+               goto out;
        }
-       return 0;
+
+       if (!skb_pull_up_to(skb, th + csum_offset + 2))
+               goto out;
+
+       skb->ip_summed = CHECKSUM_PARTIAL;
+       skb->proto_csum_blank = 0;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
+       skb->csum_start = csum_start;
+       skb->csum_offset = csum_offset;
+#else
+       skb_set_transport_header(skb, csum_start - skb_headroom(skb));
+       skb->csum = csum_offset;
+#endif
+
+       err = 0;
+
 out:
-       return -EPROTO;
+       return err;
 }
 #endif /* CONFIG_XEN && HAVE_PROTO_DATA_VALID */
 
+ /* Types of checksums that we can receive (these all refer to L4 checksums):
+ * 1. CHECKSUM_NONE: Device that did not compute checksum, contains full
+ *     (though not verified) checksum in packet but not in skb->csum.  Packets
+ *     from the bridge local port will also have this type.
+ * 2. CHECKSUM_COMPLETE (CHECKSUM_HW): Good device that computes checksums,
+ *     also the GRE module.  This is the same as CHECKSUM_NONE, except it has
+ *     a valid skb->csum.  Importantly, both contain a full checksum (not
+ *     verified) in the packet itself.  The only difference is that if the
+ *     packet gets to L4 processing on this machine (not in DomU) we won't
+ *     have to recompute the checksum to verify.  Most hardware devices do not
+ *     produce packets with this type, even if they support receive checksum
+ *     offloading (they produce type #5).
+ * 3. CHECKSUM_PARTIAL (CHECKSUM_HW): Packet without full checksum and needs to
+ *     be computed if it is sent off box.  Unfortunately on earlier kernels,
+ *     this case is impossible to distinguish from #2, despite having opposite
+ *     meanings.  Xen adds an extra field on earlier kernels (see #4) in order
+ *     to distinguish the different states.  The only real user of this type
+ *     with bridging is Xen (on later kernels).
+ * 4. CHECKSUM_UNNECESSARY (with proto_csum_blank true): This packet was
+ *     generated locally by a Xen DomU and has a partial checksum.  If it is
+ *     handled on this machine (Dom0 or DomU), then the checksum will not be
+ *     computed.  If it goes off box, the checksum in the packet needs to
+ *     completed.  Calling skb_checksum_setup converts this to CHECKSUM_HW
+ *     (CHECKSUM_PARTIAL) so that the checksum can be completed.  In later
+ *     kernels, this combination is replaced with CHECKSUM_PARTIAL.
+ * 5. CHECKSUM_UNNECESSARY (with proto_csum_blank false): Packet with a correct
+ *     full checksum or using a protocol without a checksum.  skb->csum is
+ *     undefined.  This is common from devices with receive checksum
+ *     offloading.  This is somewhat similar to CHECKSUM_NONE, except that
+ *     nobody will try to verify the checksum with CHECKSUM_UNNECESSARY.
+ *
+ * Note that on earlier kernels, CHECKSUM_COMPLETE and CHECKSUM_PARTIAL are
+ * both defined as CHECKSUM_HW.  Normally the meaning of CHECKSUM_HW is clear
+ * based on whether it is on the transmit or receive path.  After the datapath
+ * it will be intepreted as CHECKSUM_PARTIAL.  If the packet already has a
+ * checksum, we will panic.  Since we can receive packets with checksums, we
+ * assume that all CHECKSUM_HW packets have checksums and map them to
+ * CHECKSUM_NONE, which has a similar meaning (the it is only different if the
+ * packet is processed by the local IP stack, in which case it will need to
+ * be reverified).  If we receive a packet with CHECKSUM_HW that really means
+ * CHECKSUM_PARTIAL, it will be sent with the wrong checksum.  However, there
+ * shouldn't be any devices that do this with bridging.
+ *
+ * The bridge has similar behavior and this function closely resembles
+ * skb_forward_csum().  It is slightly different because we are only concerned
+ * with bridging and not other types of forwarding and can get away with
+ * slightly more optimal behavior.*/
+void
+forward_ip_summed(struct sk_buff *skb)
+{
+#ifdef CHECKSUM_HW
+       if (skb->ip_summed == CHECKSUM_HW)
+               skb->ip_summed = CHECKSUM_NONE;
+#endif
+}
+
 /* Append each packet in 'skb' list to 'queue'.  There will be only one packet
  * unless we broke up a GSO packet. */
 static int
@@ -720,6 +801,8 @@ dp_output_control(struct datapath *dp, struct sk_buff *skb, int queue_no,
        if (skb_queue_len(queue) >= DP_MAX_QUEUE_LEN)
                goto err_kfree_skb;
 
+       forward_ip_summed(skb);
+
        /* Break apart GSO packets into their component pieces.  Otherwise
         * userspace may try to stuff a 64kB packet into a 1500-byte MTU. */
        if (skb_is_gso(skb)) {
@@ -1235,6 +1318,29 @@ int dp_min_mtu(const struct datapath *dp)
        return mtu ? mtu : ETH_DATA_LEN;
 }
 
+/* Sets the MTU of all datapath devices to the minimum of the ports. 'dev'
+ * is the device whose MTU may have changed.  Must be called with RTNL lock
+ * and dp_mutex. */
+void set_dp_devs_mtu(const struct datapath *dp, struct net_device *dev)
+{
+       struct net_bridge_port *p;
+       int mtu;
+
+       ASSERT_RTNL();
+
+       if (is_dp_dev(dev))
+               return;
+
+       mtu = dp_min_mtu(dp);
+
+       list_for_each_entry_rcu (p, &dp->port_list, node) {
+               struct net_device *br_dev = p->dev;
+
+               if (is_dp_dev(br_dev))
+                       dev_set_mtu(br_dev, mtu);
+       }
+}
+
 static int
 put_port(const struct net_bridge_port *p, struct odp_port __user *uop)
 {
index 3b5a67b..6732b59 100644 (file)
@@ -201,6 +201,7 @@ void dp_process_received_packet(struct sk_buff *, struct net_bridge_port *);
 int dp_del_port(struct net_bridge_port *);
 int dp_output_control(struct datapath *, struct sk_buff *, int, u32 arg);
 int dp_min_mtu(const struct datapath *dp);
+void set_dp_devs_mtu(const struct datapath *dp, struct net_device *dev);
 
 struct datapath *get_dp(int dp_idx);
 
@@ -218,4 +219,6 @@ static inline int vswitch_skb_checksum_setup(struct sk_buff *skb)
 }
 #endif
 
+void forward_ip_summed(struct sk_buff *skb);
+
 #endif /* datapath.h */
index d5a2749..0278988 100644 (file)
@@ -45,6 +45,14 @@ static int dp_device_event(struct notifier_block *unused, unsigned long event,
                        mutex_unlock(&dp->mutex);
                }
                break;
+
+       case NETDEV_CHANGEMTU:
+               if (!is_dp_dev(dev)) {
+                       mutex_lock(&dp->mutex);
+                       set_dp_devs_mtu(dp, dev);
+                       mutex_unlock(&dp->mutex);
+               }
+               break;
        }
        return NOTIFY_DONE;
 }
index aa10e66..89f269e 100644 (file)
@@ -2,6 +2,9 @@
 /Makefile
 /Makefile.main
 /actions.c
+/brc_procfs.c
+/brc_sysfs_dp.c
+/brc_sysfs_if.c
 /brcompat.c
 /dp_sysfs_dp.c
 /dp_sysfs_if.c
 /flow.c
 /genetlink-brcompat.c
 /genetlink-openvswitch.c
+/ip_gre.c
 /kcompat.h
 /linux-2.6
 /modules.order
+/net_namespace-ip_gre.c
 /random32.c
 /table.c
 /tmp
index 967e2f7..fd231e6 100644 (file)
@@ -5,6 +5,7 @@ export top_srcdir = @abs_top_srcdir@
 export KSRC = @KBUILD26@
 export VERSION = @VERSION@
 export BUILD_VETH = @BUILD_VETH@
+export BUILD_GRE = @BUILD_GRE@
 
 include $(srcdir)/../Modules.mk
 include $(srcdir)/Modules.mk
index 7892583..8d2f969 100644 (file)
@@ -47,3 +47,26 @@ dist_modules += veth
 build_modules += $(if $(BUILD_VETH),veth)
 veth_sources = linux-2.6/compat-2.6/veth.c
 veth_headers = 
+
+dist_modules += ip_gre
+build_modules += $(if $(BUILD_GRE),ip_gre)
+ip_gre_sources = \
+       linux-2.6/compat-2.6/ip_gre.c \
+       linux-2.6/compat-2.6/net_namespace-ip_gre.c
+ip_gre_headers = \
+       linux-2.6/compat-2.6/compat26.h \
+       linux-2.6/compat-2.6/include/linux/if.h \
+       linux-2.6/compat-2.6/include/linux/in.h \
+       linux-2.6/compat-2.6/include/linux/inetdevice.h \
+       linux-2.6/compat-2.6/include/linux/if_ether.h \
+       linux-2.6/compat-2.6/include/linux/ip.h \
+       linux-2.6/compat-2.6/include/linux/ipv6.h \
+       linux-2.6/compat-2.6/include/linux/netdevice.h \
+       linux-2.6/compat-2.6/include/linux/skbuff.h \
+       linux-2.6/compat-2.6/include/linux/tcp.h \
+       linux-2.6/compat-2.6/include/linux/types.h \
+       linux-2.6/compat-2.6/include/net/dst.h \
+       linux-2.6/compat-2.6/include/net/ipip.h \
+       linux-2.6/compat-2.6/include/net/netns/generic.h \
+       linux-2.6/compat-2.6/include/net/net_namespace.h \
+       linux-2.6/compat-2.6/include/net/route.h
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/if.h b/datapath/linux-2.6/compat-2.6/include/linux/if.h
new file mode 100644 (file)
index 0000000..0aa9ee3
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __LINUX_IF_WRAPPER_H
+#define __LINUX_IF_WRAPPER_H 1
+
+#include_next <linux/if.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
+
+#define IFF_XMIT_DST_RELEASE 0
+
+#endif /* linux kernel < 2.6.31 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/if_ether.h b/datapath/linux-2.6/compat-2.6/include/linux/if_ether.h
new file mode 100644 (file)
index 0000000..b8390e2
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef __LINUX_IF_ETHER_WRAPPER_H
+#define __LINUX_IF_ETHER_WRAPPER_H 1
+
+#include_next <linux/if_ether.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+
+#define ETH_P_TEB      0x6558          /* Trans Ether Bridging         */
+
+#endif /* linux kernel < 2.6.28 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/in.h b/datapath/linux-2.6/compat-2.6/include/linux/in.h
new file mode 100644 (file)
index 0000000..fd5c3c6
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef __LINUX_IN_WRAPPER_H
+#define __LINUX_IN_WRAPPER_H 1
+
+#include_next <linux/in.h>
+
+#ifndef HAVE_IPV4_IS_MULTICAST
+
+static inline bool ipv4_is_multicast(__be32 addr)
+{
+       return (addr & htonl(0xf0000000)) == htonl(0xe0000000);
+}
+
+#endif /* !HAVE_IPV4_IS_MULTICAST */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/inetdevice.h b/datapath/linux-2.6/compat-2.6/include/linux/inetdevice.h
new file mode 100644 (file)
index 0000000..813a70a
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef __LINUX_INETDEVICE_WRAPPER_H
+#define __LINUX_INETDEVICE_WRAPPER_H 1
+
+#include_next <linux/inetdevice.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+
+#define inetdev_by_index(net, ifindex) \
+               inetdev_by_index((ifindex))
+
+#endif /* linux kernel < 2.6.25 */
+
+#endif
index b718283..c25f2bd 100644 (file)
@@ -5,6 +5,7 @@
 
 struct net;
 
+#include <linux/version.h>
 /* Before 2.6.21, struct net_device has a "struct class_device" member named
  * class_dev.  Beginning with 2.6.21, struct net_device instead has a "struct
  * device" member named dev.  Otherwise the usage of these members is pretty
@@ -23,14 +24,36 @@ struct net;
 static inline
 struct net *dev_net(const struct net_device *dev)
 {
+#ifdef CONFIG_NET_NS
+       return dev->nd_net;
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25)
+       return &init_net;
+#else
        return NULL;
+#endif
+}
+
+static inline
+void dev_net_set(struct net_device *dev, const struct net *net)
+{
+#ifdef CONFIG_NET_NS
+       dev->nd_dev = net;
+#endif
 }
 #endif /* linux kernel < 2.6.26 */
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+#define NETIF_F_NETNS_LOCAL 0
+#endif
+
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
 #define proc_net init_net.proc_net
 #endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)
+typedef int netdev_tx_t;
+#endif
+
 #ifndef for_each_netdev
 /* Linux before 2.6.22 didn't have for_each_netdev at all. */
 #define for_each_netdev(net, d) for (d = dev_base; d; d = d->next)
index dd9bfa3..d9f043a 100644 (file)
@@ -76,6 +76,29 @@ static inline int skb_cow_head(struct sk_buff *skb, unsigned int headroom)
 }
 #endif  /* !HAVE_SKB_COW */
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
+static inline int skb_clone_writable(struct sk_buff *skb, int len)
+{
+       return false;
+}
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
+static inline struct dst_entry *skb_dst(const struct sk_buff *skb)
+{
+       return (struct dst_entry *)skb->dst;
+}
+
+static inline void skb_dst_set(struct sk_buff *skb, struct dst_entry *dst)
+{
+       skb->dst = dst;
+}
+
+static inline struct rtable *skb_rtable(const struct sk_buff *skb)
+{
+       return (struct rtable *)skb->dst;
+}
+#endif
 
 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,17)
 /* Emulate Linux 2.6.17 and later behavior, in which kfree_skb silently ignores 
@@ -90,9 +113,7 @@ static inline void kfree_skb_maybe_null(struct sk_buff *skb)
 
 
 #ifndef CHECKSUM_PARTIAL
-/* Note that CHECKSUM_PARTIAL is not implemented, but this allows us to at
- * least test against it: see update_csum() in forward.c. */
-#define CHECKSUM_PARTIAL 3
+#define CHECKSUM_PARTIAL CHECKSUM_HW
 #endif
 #ifndef CHECKSUM_COMPLETE
 #define CHECKSUM_COMPLETE CHECKSUM_HW
@@ -101,6 +122,7 @@ static inline void kfree_skb_maybe_null(struct sk_buff *skb)
 #ifdef HAVE_MAC_RAW
 #define mac_header mac.raw
 #define network_header nh.raw
+#define transport_header h.raw
 #endif
 
 #ifndef HAVE_SKBUFF_HEADER_HELPERS
@@ -125,6 +147,11 @@ static inline unsigned char *skb_network_header(const struct sk_buff *skb)
        return skb->nh.raw;
 }
 
+static inline void skb_reset_network_header(struct sk_buff *skb)
+{
+       skb->nh.raw = skb->data;
+}
+
 static inline void skb_set_network_header(struct sk_buff *skb, const int offset)
 {
        skb->nh.raw = skb->data + offset;
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/stddef.h b/datapath/linux-2.6/compat-2.6/include/linux/stddef.h
new file mode 100644 (file)
index 0000000..9b68f71
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef __LINUX_STDDEF_WRAPPER_H
+#define __LINUX_STDDEF_WRAPPER_H 1
+
+#include_next <linux/stddef.h>
+
+#ifdef __KERNEL__
+
+#ifndef HAVE_BOOL_TYPE
+enum {
+       false   = 0,
+       true    = 1
+};
+#endif /* !HAVE_BOOL_TYPE */
+
+#endif /* __KERNEL__ */
+
+#endif
index c1f375e..d88baf7 100644 (file)
@@ -11,4 +11,8 @@ typedef __u32 __bitwise __wsum;
 
 #endif /* linux kernel < 2.6.20 */
 
+#ifndef HAVE_BOOL_TYPE
+typedef _Bool bool;
+#endif /* !HAVE_BOOL_TYPE */
+
 #endif
diff --git a/datapath/linux-2.6/compat-2.6/include/net/dst.h b/datapath/linux-2.6/compat-2.6/include/net/dst.h
new file mode 100644 (file)
index 0000000..edb0f75
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef __NET_DST_WRAPPER_H
+#define __NET_DST_WRAPPER_H 1
+
+#include_next <net/dst.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
+
+static inline void skb_dst_drop(struct sk_buff *skb)
+{
+       if (skb->dst)
+               dst_release(skb_dst(skb));
+       skb->dst = 0UL;
+}
+
+#endif /* linux kernel < 2.6.31 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/net/ipip.h b/datapath/linux-2.6/compat-2.6/include/net/ipip.h
new file mode 100644 (file)
index 0000000..2eca5f8
--- /dev/null
@@ -0,0 +1,93 @@
+#ifndef __NET_IPIP_WRAPPER_H
+#define __NET_IPIP_WRAPPER_H 1
+
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)
+
+#include <linux/if_tunnel.h>
+#include <net/ip.h>
+
+/* Keep error state on tunnel for 30 sec */
+#define IPTUNNEL_ERR_TIMEO     (30*HZ)
+
+struct ip_tunnel
+{
+       struct ip_tunnel        *next;
+       struct net_device       *dev;
+#ifndef HAVE_NETDEV_STATS
+       struct net_device_stats stat;
+#endif
+
+       int                     err_count;      /* Number of arrived ICMP errors */
+       unsigned long           err_time;       /* Time when the last ICMP error arrived */
+
+       /* These four fields used only by GRE */
+       __u32                   i_seqno;        /* The last seen seqno  */
+       __u32                   o_seqno;        /* The last output seqno */
+       int                     hlen;           /* Precalculated GRE header length */
+       int                     mlink;
+
+       struct ip_tunnel_parm   parms;
+
+       struct ip_tunnel_prl_entry      *prl;           /* potential router list */
+       unsigned int                    prl_count;      /* # of entries in PRL */
+};
+
+/* ISATAP: default interval between RS in secondy */
+#define IPTUNNEL_RS_DEFAULT_DELAY      (900)
+
+struct ip_tunnel_prl_entry
+{
+       struct ip_tunnel_prl_entry      *next;
+       __be32                          addr;
+       u16                             flags;
+       unsigned long                   rs_delay;
+       struct timer_list               rs_timer;
+       struct ip_tunnel                *tunnel;
+       spinlock_t                      lock;
+};
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+#define IPTUNNEL_XMIT() do {                                           \
+       int err;                                                        \
+       int pkt_len = skb->len - skb_transport_offset(skb);             \
+                                                                       \
+       skb->ip_summed = CHECKSUM_NONE;                                 \
+       iph->tot_len = htons(skb->len);                                 \
+       ip_select_ident(iph, &rt->u.dst, NULL);                         \
+       ip_send_check(iph);                                             \
+                                                                       \
+       err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev, dst_output);\
+       if (err == NET_XMIT_SUCCESS || err == NET_XMIT_CN) {            \
+               stats->tx_bytes += pkt_len;                             \
+               stats->tx_packets++;                                    \
+       } else {                                                        \
+               stats->tx_errors++;                                     \
+               stats->tx_aborted_errors++;                             \
+       }                                                               \
+} while (0)
+#else
+#define IPTUNNEL_XMIT() do {                                           \
+       int err;                                                        \
+       int pkt_len = skb->len;                                         \
+                                                                       \
+       skb->ip_summed = CHECKSUM_NONE;                                 \
+       ip_select_ident(iph, &rt->u.dst, NULL);                         \
+                                                                       \
+       err = ip_local_out(skb);                                        \
+       if (net_xmit_eval(err) == 0) {                                  \
+               stats->tx_bytes += pkt_len;                             \
+               stats->tx_packets++;                                    \
+       } else {                                                        \
+               stats->tx_errors++;                                     \
+               stats->tx_aborted_errors++;                             \
+       }                                                               \
+} while (0)
+#endif
+
+#else
+#include_next <net/ipip.h>
+#endif /* kernel < 2.6.32 */
+
+#endif /* net/ipip.h wrapper */
diff --git a/datapath/linux-2.6/compat-2.6/include/net/net_namespace.h b/datapath/linux-2.6/compat-2.6/include/net/net_namespace.h
new file mode 100644 (file)
index 0000000..9b66c91
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef __NET_NAMESPACE_WRAPPER_H
+#define __NET_NAMESPACE_WRAPPER_H 1
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,24)
+#include_next <net/net_namespace.h>
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+struct net;
+
+struct pernet_operations {
+       struct list_head list;
+       int (*init)(struct net *net);
+       void (*exit)(struct net *net);
+};
+#endif /* linux kernel < 2.6.24 */
+
+extern int register_pernet_gen_device(int *id, struct pernet_operations *);
+extern void unregister_pernet_gen_device(int id, struct pernet_operations *);
+
+#endif /* linux kernel < 2.6.26 */
+
+#endif
index e0d594d..2f979fe 100644 (file)
@@ -19,4 +19,22 @@ static inline int VERIFY_NUL_STRING(struct nlattr *attr)
 }
 #endif /* !HAVE_NLA_NUL_STRING */
 
+
+#ifndef NLA_PUT_BE16
+#define NLA_PUT_BE16(skb, attrtype, value) \
+        NLA_PUT_TYPE(skb, __be16, attrtype, value)
+#endif  /* !NLA_PUT_BE16 */
+
+
+#ifndef HAVE_NLA_GET_BE16
+/**
+ * nla_get_be16 - return payload of __be16 attribute
+ * @nla: __be16 netlink attribute
+ */
+static inline __be16 nla_get_be16(struct nlattr *nla)
+{
+        return *(__be16 *) nla_data(nla);
+}
+#endif  /* !HAVE_NLA_GET_BE16 */
+
 #endif /* net/netlink.h */
diff --git a/datapath/linux-2.6/compat-2.6/include/net/netns/generic.h b/datapath/linux-2.6/compat-2.6/include/net/netns/generic.h
new file mode 100644 (file)
index 0000000..f70bc87
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef __NET_NETNS_GENERIC_WRAPPER_H
+#define __NET_NETNS_GENERIC_WRAPPER_H 1
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+
+struct net;
+
+extern void *net_generic(struct net *net, int id);
+extern int net_assign_generic(struct net *net, int id, void *data);
+
+#else
+#include_next <net/netns/generic.h>
+#endif /* linux kernel < 2.6.26 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/include/net/route.h b/datapath/linux-2.6/compat-2.6/include/net/route.h
new file mode 100644 (file)
index 0000000..867f407
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef __NET_ROUTE_WRAPPER_H
+#define __NET_ROUTE_WRAPPER_H 1
+
+#include_next <net/route.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
+
+#define ip_route_output_key(net, rp, flp) \
+               ip_route_output_key((rp), (flp))
+
+#endif /* linux kernel < 2.6.25 */
+
+#endif
diff --git a/datapath/linux-2.6/compat-2.6/ip_gre.c b/datapath/linux-2.6/compat-2.6/ip_gre.c
new file mode 100644 (file)
index 0000000..05c6055
--- /dev/null
@@ -0,0 +1,1894 @@
+/* ip_gre driver port to Linux 2.6.18 and greater */
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,22)
+#define HAVE_NETDEV_STATS
+#endif
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
+#define HAVE_NETDEV_HEADER_OPS
+#endif
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+#define HAVE_NETDEV_NEEDED_HEADROOM
+#endif
+
+/*
+ *     Linux NET3:     GRE over IP protocol decoder.
+ *
+ *     Authors: Alexey Kuznetsov (kuznet@ms2.inr.ac.ru)
+ *
+ *     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
+ *     2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/capability.h>
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <asm/uaccess.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/if_arp.h>
+#include <linux/mroute.h>
+#include <linux/init.h>
+#include <linux/in6.h>
+#include <linux/inetdevice.h>
+#include <linux/igmp.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/etherdevice.h>
+#include <linux/if_ether.h>
+
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/protocol.h>
+#include <net/ipip.h>
+#include <net/arp.h>
+#include <net/checksum.h>
+#include <net/dsfield.h>
+#include <net/inet_ecn.h>
+#include <net/xfrm.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+
+#ifdef CONFIG_IPV6
+#include <net/ipv6.h>
+#include <net/ip6_fib.h>
+#include <net/ip6_route.h>
+#endif
+
+#include "compat.h"
+#include "openvswitch/gre.h"
+
+#ifndef GRE_IOCTL_ONLY
+#include <net/rtnetlink.h>
+#endif
+
+/*
+   Problems & solutions
+   --------------------
+
+   1. The most important issue is detecting local dead loops.
+   They would cause complete host lockup in transmit, which
+   would be "resolved" by stack overflow or, if queueing is enabled,
+   with infinite looping in net_bh.
+
+   We cannot track such dead loops during route installation,
+   it is infeasible task. The most general solutions would be
+   to keep skb->encapsulation counter (sort of local ttl),
+   and silently drop packet when it expires. It is the best
+   solution, but it supposes maintaing new variable in ALL
+   skb, even if no tunneling is used.
+
+   Current solution: HARD_TX_LOCK lock breaks dead loops.
+
+
+
+   2. Networking dead loops would not kill routers, but would really
+   kill network. IP hop limit plays role of "t->recursion" in this case,
+   if we copy it from packet being encapsulated to upper header.
+   It is very good solution, but it introduces two problems:
+
+   - Routing protocols, using packets with ttl=1 (OSPF, RIP2),
+     do not work over tunnels.
+   - traceroute does not work. I planned to relay ICMP from tunnel,
+     so that this problem would be solved and traceroute output
+     would even more informative. This idea appeared to be wrong:
+     only Linux complies to rfc1812 now (yes, guys, Linux is the only
+     true router now :-)), all routers (at least, in neighbourhood of mine)
+     return only 8 bytes of payload. It is the end.
+
+   Hence, if we want that OSPF worked or traceroute said something reasonable,
+   we should search for another solution.
+
+   One of them is to parse packet trying to detect inner encapsulation
+   made by our node. It is difficult or even impossible, especially,
+   taking into account fragmentation. TO be short, tt is not solution at all.
+
+   Current solution: The solution was UNEXPECTEDLY SIMPLE.
+   We force DF flag on tunnels with preconfigured hop limit,
+   that is ALL. :-) Well, it does not remove the problem completely,
+   but exponential growth of network traffic is changed to linear
+   (branches, that exceed pmtu are pruned) and tunnel mtu
+   fastly degrades to value <68, where looping stops.
+   Yes, it is not good if there exists a router in the loop,
+   which does not force DF, even when encapsulating packets have DF set.
+   But it is not our problem! Nobody could accuse us, we made
+   all that we could make. Even if it is your gated who injected
+   fatal route to network, even if it were you who configured
+   fatal static route: you are innocent. :-)
+
+
+
+   3. Really, ipv4/ipip.c, ipv4/ip_gre.c and ipv6/sit.c contain
+   practically identical code. It would be good to glue them
+   together, but it is not very evident, how to make them modular.
+   sit is integral part of IPv6, ipip and gre are naturally modular.
+   We could extract common parts (hash table, ioctl etc)
+   to a separate module (ip_tunnel.c).
+
+   Alexey Kuznetsov.
+ */
+
+#ifndef GRE_IOCTL_ONLY
+static struct rtnl_link_ops ipgre_link_ops __read_mostly;
+static struct rtnl_link_ops ipgre_tap_ops __read_mostly;
+#endif
+static int ipgre_tunnel_init(struct net_device *dev);
+static void ipgre_tunnel_setup(struct net_device *dev);
+static void ipgre_tap_setup(struct net_device *dev);
+static int ipgre_tunnel_bind_dev(struct net_device *dev);
+
+#define HASH_SIZE  16
+
+static int ipgre_net_id;
+struct ipgre_net {
+       struct ip_tunnel *tunnels[4][HASH_SIZE];
+
+       struct net_device *fb_tunnel_dev;
+};
+
+/* Tunnel hash table */
+
+/*
+   4 hash tables:
+
+   3: (remote,local)
+   2: (remote,*)
+   1: (*,local)
+   0: (*,*)
+
+   We require exact key match i.e. if a key is present in packet
+   it will match only tunnel with the same key; if it is not present,
+   it will match only keyless tunnel.
+
+   All keysless packets, if not matched configured keyless tunnels
+   will match fallback tunnel.
+ */
+
+#define HASH(addr) (((__force u32)addr^((__force u32)addr>>4))&0xF)
+
+#define tunnels_r_l    tunnels[3]
+#define tunnels_r      tunnels[2]
+#define tunnels_l      tunnels[1]
+#define tunnels_wc     tunnels[0]
+
+static DEFINE_RWLOCK(ipgre_lock);
+
+/* Given src, dst and key, find appropriate for input tunnel. */
+
+static struct ip_tunnel * ipgre_tunnel_lookup(struct net_device *dev,
+                                             __be32 remote, __be32 local,
+                                             __be32 key, __be16 gre_proto)
+{
+       struct net *net = dev_net(dev);
+       int link = dev->ifindex;
+       unsigned h0 = HASH(remote);
+       unsigned h1 = HASH(key);
+       struct ip_tunnel *t, *cand = NULL;
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       int dev_type = (gre_proto == htons(ETH_P_TEB)) ?
+                      ARPHRD_ETHER : ARPHRD_IPGRE;
+       int score, cand_score = 4;
+
+       for (t = ign->tunnels_r_l[h0^h1]; t; t = t->next) {
+               if (local != t->parms.iph.saddr ||
+                   remote != t->parms.iph.daddr ||
+                   key != t->parms.i_key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
+               }
+       }
+
+       for (t = ign->tunnels_r[h0^h1]; t; t = t->next) {
+               if (remote != t->parms.iph.daddr ||
+                   key != t->parms.i_key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
+               }
+       }
+
+       for (t = ign->tunnels_l[h1]; t; t = t->next) {
+               if ((local != t->parms.iph.saddr &&
+                    (local != t->parms.iph.daddr ||
+                     !ipv4_is_multicast(local))) ||
+                   key != t->parms.i_key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
+               }
+       }
+
+       for (t = ign->tunnels_wc[h1]; t; t = t->next) {
+               if (t->parms.i_key != key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
+               }
+       }
+
+       if (cand != NULL)
+               return cand;
+
+       if (ign->fb_tunnel_dev->flags & IFF_UP)
+               return netdev_priv(ign->fb_tunnel_dev);
+
+       return NULL;
+}
+
+static struct ip_tunnel **__ipgre_bucket(struct ipgre_net *ign,
+               struct ip_tunnel_parm *parms)
+{
+       __be32 remote = parms->iph.daddr;
+       __be32 local = parms->iph.saddr;
+       __be32 key = parms->i_key;
+       unsigned h = HASH(key);
+       int prio = 0;
+
+       if (local)
+               prio |= 1;
+       if (remote && !ipv4_is_multicast(remote)) {
+               prio |= 2;
+               h ^= HASH(remote);
+       }
+
+       return &ign->tunnels[prio][h];
+}
+
+static inline struct ip_tunnel **ipgre_bucket(struct ipgre_net *ign,
+               struct ip_tunnel *t)
+{
+       return __ipgre_bucket(ign, &t->parms);
+}
+
+static void ipgre_tunnel_link(struct ipgre_net *ign, struct ip_tunnel *t)
+{
+       struct ip_tunnel **tp = ipgre_bucket(ign, t);
+
+       t->next = *tp;
+       write_lock_bh(&ipgre_lock);
+       *tp = t;
+       write_unlock_bh(&ipgre_lock);
+}
+
+static void ipgre_tunnel_unlink(struct ipgre_net *ign, struct ip_tunnel *t)
+{
+       struct ip_tunnel **tp;
+
+       for (tp = ipgre_bucket(ign, t); *tp; tp = &(*tp)->next) {
+               if (t == *tp) {
+                       write_lock_bh(&ipgre_lock);
+                       *tp = t->next;
+                       write_unlock_bh(&ipgre_lock);
+                       break;
+               }
+       }
+}
+
+static struct ip_tunnel *ipgre_tunnel_find(struct net *net,
+                                          struct ip_tunnel_parm *parms,
+                                          int type)
+{
+       __be32 remote = parms->iph.daddr;
+       __be32 local = parms->iph.saddr;
+       __be32 key = parms->i_key;
+       int link = parms->link;
+       struct ip_tunnel *t, **tp;
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+
+       for (tp = __ipgre_bucket(ign, parms); (t = *tp) != NULL; tp = &t->next)
+               if (local == t->parms.iph.saddr &&
+                   remote == t->parms.iph.daddr &&
+                   key == t->parms.i_key &&
+                   link == t->parms.link &&
+                   type == t->dev->type)
+                       break;
+
+       return t;
+}
+
+static struct ip_tunnel * ipgre_tunnel_locate(struct net *net,
+               struct ip_tunnel_parm *parms, int gretap, int create)
+{
+       struct ip_tunnel *t, *nt;
+       struct net_device *dev;
+       char name[IFNAMSIZ];
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+
+       t = ipgre_tunnel_find(net, parms, gretap ? ARPHRD_ETHER : ARPHRD_IPGRE);
+       if (t || !create)
+               return t;
+
+       if (parms->name[0])
+               strlcpy(name, parms->name, IFNAMSIZ);
+       else
+               sprintf(name, "gre%%d");
+
+       dev = alloc_netdev(sizeof(*t), name, gretap ? ipgre_tap_setup
+                                                   : ipgre_tunnel_setup);
+       if (!dev)
+         return NULL;
+
+       dev_net_set(dev, net);
+
+       if (strchr(name, '%')) {
+               if (dev_alloc_name(dev, name) < 0)
+                       goto failed_free;
+       }
+
+       if (gretap)
+               random_ether_addr(dev->dev_addr);
+
+#ifndef GRE_IOCTL_ONLY
+       dev->rtnl_link_ops = gretap ? &ipgre_tap_ops : &ipgre_link_ops;
+#endif
+       nt = netdev_priv(dev);
+       nt->parms = *parms;
+
+       dev->mtu = ipgre_tunnel_bind_dev(dev);
+
+       if (register_netdevice(dev) < 0)
+               goto failed_free;
+
+       dev_hold(dev);
+       ipgre_tunnel_link(ign, nt);
+       return nt;
+
+failed_free:
+       free_netdev(dev);
+       return NULL;
+}
+
+static void ipgre_tunnel_uninit(struct net_device *dev)
+{
+       struct net *net = dev_net(dev);
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+
+       ipgre_tunnel_unlink(ign, netdev_priv(dev));
+       dev_put(dev);
+}
+
+
+static void ipgre_err(struct sk_buff *skb, u32 info)
+{
+
+/* All the routers (except for Linux) return only
+   8 bytes of packet payload. It means, that precise relaying of
+   ICMP in the real Internet is absolutely infeasible.
+
+   Moreover, Cisco "wise men" put GRE key to the third word
+   in GRE header. It makes impossible maintaining even soft state for keyed
+   GRE tunnels with enabled checksum. Tell them "thank you".
+
+   Well, I wonder, rfc1812 was written by Cisco employee,
+   what the hell these idiots break standrads established
+   by themself???
+ */
+
+       struct iphdr *iph = (struct iphdr *)skb->data;
+       __be16       *p = (__be16*)(skb->data+(iph->ihl<<2));
+       int grehlen = (iph->ihl<<2) + 4;
+       const int type = icmp_hdr(skb)->type;
+       const int code = icmp_hdr(skb)->code;
+       struct ip_tunnel *t;
+       __be16 flags;
+
+       flags = p[0];
+       if (flags&(GRE_CSUM|GRE_KEY|GRE_SEQ|GRE_ROUTING|GRE_VERSION)) {
+               if (flags&(GRE_VERSION|GRE_ROUTING))
+                       return;
+               if (flags&GRE_KEY) {
+                       grehlen += 4;
+                       if (flags&GRE_CSUM)
+                               grehlen += 4;
+               }
+       }
+
+       /* If only 8 bytes returned, keyed message will be dropped here */
+       if (skb_headlen(skb) < grehlen)
+               return;
+
+       switch (type) {
+       default:
+       case ICMP_PARAMETERPROB:
+               return;
+
+       case ICMP_DEST_UNREACH:
+               switch (code) {
+               case ICMP_SR_FAILED:
+               case ICMP_PORT_UNREACH:
+                       /* Impossible event. */
+                       return;
+               case ICMP_FRAG_NEEDED:
+                       /* Soft state for pmtu is maintained by IP core. */
+                       return;
+               default:
+                       /* All others are translated to HOST_UNREACH.
+                          rfc2003 contains "deep thoughts" about NET_UNREACH,
+                          I believe they are just ether pollution. --ANK
+                        */
+                       break;
+               }
+               break;
+       case ICMP_TIME_EXCEEDED:
+               if (code != ICMP_EXC_TTL)
+                       return;
+               break;
+       }
+
+       read_lock(&ipgre_lock);
+       t = ipgre_tunnel_lookup(skb->dev, iph->daddr, iph->saddr,
+                               flags & GRE_KEY ?
+                               *(((__be32 *)p) + (grehlen / 4) - 1) : 0,
+                               p[1]);
+       if (t == NULL || t->parms.iph.daddr == 0 ||
+           ipv4_is_multicast(t->parms.iph.daddr))
+               goto out;
+
+       if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
+               goto out;
+
+       if (time_before(jiffies, t->err_time + IPTUNNEL_ERR_TIMEO))
+               t->err_count++;
+       else
+               t->err_count = 1;
+       t->err_time = jiffies;
+out:
+       read_unlock(&ipgre_lock);
+       return;
+}
+
+static inline void ipgre_ecn_decapsulate(struct iphdr *iph, struct sk_buff *skb)
+{
+       if (INET_ECN_is_ce(iph->tos)) {
+               if (skb->protocol == htons(ETH_P_IP)) {
+                       IP_ECN_set_ce(ip_hdr(skb));
+               } else if (skb->protocol == htons(ETH_P_IPV6)) {
+                       IP6_ECN_set_ce(ipv6_hdr(skb));
+               }
+       }
+}
+
+static inline u8
+ipgre_ecn_encapsulate(u8 tos, struct iphdr *old_iph, struct sk_buff *skb)
+{
+       u8 inner = 0;
+       if (skb->protocol == htons(ETH_P_IP))
+               inner = old_iph->tos;
+       else if (skb->protocol == htons(ETH_P_IPV6))
+               inner = ipv6_get_dsfield((struct ipv6hdr *)old_iph);
+       return INET_ECN_encapsulate(tos, inner);
+}
+
+static int ipgre_rcv(struct sk_buff *skb)
+{
+       struct iphdr *iph;
+       u8     *h;
+       __be16    flags;
+       __sum16   csum = 0;
+       __be32 key = 0;
+       u32    seqno = 0;
+       struct ip_tunnel *tunnel;
+       int    offset = 4;
+       __be16 gre_proto;
+       unsigned int len;
+
+       if (!pskb_may_pull(skb, 16))
+               goto drop_nolock;
+
+       iph = ip_hdr(skb);
+       h = skb->data;
+       flags = *(__be16*)h;
+
+       if (flags&(GRE_CSUM|GRE_KEY|GRE_ROUTING|GRE_SEQ|GRE_VERSION)) {
+               /* - Version must be 0.
+                  - We do not support routing headers.
+                */
+               if (flags&(GRE_VERSION|GRE_ROUTING))
+                       goto drop_nolock;
+
+               if (flags&GRE_CSUM) {
+                       switch (skb->ip_summed) {
+                       case CHECKSUM_COMPLETE:
+                               csum = csum_fold(skb->csum);
+                               if (!csum)
+                                       break;
+                               /* fall through */
+                       case CHECKSUM_NONE:
+                               skb->csum = 0;
+                               csum = __skb_checksum_complete(skb);
+                               skb->ip_summed = CHECKSUM_COMPLETE;
+                       }
+                       offset += 4;
+               }
+               if (flags&GRE_KEY) {
+                       key = *(__be32*)(h + offset);
+                       offset += 4;
+               }
+               if (flags&GRE_SEQ) {
+                       seqno = ntohl(*(__be32*)(h + offset));
+                       offset += 4;
+               }
+       }
+
+       gre_proto = *(__be16 *)(h + 2);
+
+       read_lock(&ipgre_lock);
+       if ((tunnel = ipgre_tunnel_lookup(skb->dev,
+                                         iph->saddr, iph->daddr, key,
+                                         gre_proto))) {
+               struct net_device_stats *stats;
+#ifdef HAVE_NETDEV_STATS
+               stats = &tunnel->dev->stats;
+#else
+               stats = &tunnel->stat;
+#endif
+
+               secpath_reset(skb);
+
+               skb->protocol = gre_proto;
+               /* WCCP version 1 and 2 protocol decoding.
+                * - Change protocol to IP
+                * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header
+                */
+               if (flags == 0 && gre_proto == htons(ETH_P_WCCP)) {
+                       skb->protocol = htons(ETH_P_IP);
+                       if ((*(h + offset) & 0xF0) != 0x40)
+                               offset += 4;
+               }
+
+               skb->mac_header = skb->network_header;
+               __pskb_pull(skb, offset);
+               skb_postpull_rcsum(skb, skb_transport_header(skb), offset);
+               skb->pkt_type = PACKET_HOST;
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+               if (ipv4_is_multicast(iph->daddr)) {
+                       /* Looped back packet, drop it! */
+                       if (skb_rtable(skb)->fl.iif == 0)
+                               goto drop;
+                       stats->multicast++;
+                       skb->pkt_type = PACKET_BROADCAST;
+               }
+#endif
+
+               if (((flags&GRE_CSUM) && csum) ||
+                   (!(flags&GRE_CSUM) && tunnel->parms.i_flags&GRE_CSUM)) {
+                       stats->rx_crc_errors++;
+                       stats->rx_errors++;
+                       goto drop;
+               }
+               if (tunnel->parms.i_flags&GRE_SEQ) {
+                       if (!(flags&GRE_SEQ) ||
+                           (tunnel->i_seqno && (s32)(seqno - tunnel->i_seqno) < 0)) {
+                               stats->rx_fifo_errors++;
+                               stats->rx_errors++;
+                               goto drop;
+                       }
+                       tunnel->i_seqno = seqno + 1;
+               }
+
+               len = skb->len;
+
+               /* Warning: All skb pointers will be invalidated! */
+               if (tunnel->dev->type == ARPHRD_ETHER) {
+                       if (!pskb_may_pull(skb, ETH_HLEN)) {
+                               stats->rx_length_errors++;
+                               stats->rx_errors++;
+                               goto drop;
+                       }
+
+                       iph = ip_hdr(skb);
+                       skb->protocol = eth_type_trans(skb, tunnel->dev);
+                       skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);
+               }
+
+               stats->rx_packets++;
+               stats->rx_bytes += len;
+               skb->dev = tunnel->dev;
+               skb_dst_drop(skb);
+               nf_reset(skb);
+
+               skb_reset_network_header(skb);
+               ipgre_ecn_decapsulate(iph, skb);
+
+               netif_rx(skb);
+               read_unlock(&ipgre_lock);
+               return(0);
+       }
+       icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
+
+drop:
+       read_unlock(&ipgre_lock);
+drop_nolock:
+       kfree_skb(skb);
+       return(0);
+}
+
+static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+       struct ip_tunnel *tunnel = netdev_priv(dev);
+       struct net_device_stats *stats;
+       struct iphdr  *old_iph = ip_hdr(skb);
+       struct iphdr  *tiph;
+       u8     tos;
+       __be16 df;
+       struct rtable *rt;                      /* Route to the other host */
+       struct net_device *tdev;                /* Device to other host */
+       struct iphdr  *iph;                     /* Our new IP header */
+       unsigned int max_headroom;              /* The extra header space needed */
+       int    gre_hlen;
+       __be32 dst;
+       int    mtu;
+
+#ifdef HAVE_NETDEV_STATS
+       stats = &tunnel->dev->stats;
+#else
+       stats = &tunnel->stat;
+#endif
+
+       if (dev->type == ARPHRD_ETHER)
+               IPCB(skb)->flags = 0;
+
+#ifdef HAVE_NETDEV_HEADER_OPS
+       if (dev->header_ops && dev->type == ARPHRD_IPGRE) {
+#else
+       if (dev->hard_header && dev->type == ARPHRD_IPGRE) {
+#endif
+               gre_hlen = 0;
+               tiph = (struct iphdr *)skb->data;
+       } else {
+               gre_hlen = tunnel->hlen;
+               tiph = &tunnel->parms.iph;
+       }
+
+       if ((dst = tiph->daddr) == 0) {
+               /* NBMA tunnel */
+
+               if (skb_dst(skb) == NULL) {
+                       stats->tx_fifo_errors++;
+                       goto tx_error;
+               }
+
+               if (skb->protocol == htons(ETH_P_IP)) {
+                       rt = skb_rtable(skb);
+                       if ((dst = rt->rt_gateway) == 0)
+                               goto tx_error_icmp;
+               }
+#ifdef CONFIG_IPV6
+               else if (skb->protocol == htons(ETH_P_IPV6)) {
+                       struct in6_addr *addr6;
+                       int addr_type;
+                       struct neighbour *neigh = skb_dst(skb)->neighbour;
+
+                       if (neigh == NULL)
+                               goto tx_error;
+
+                       addr6 = (struct in6_addr *)&neigh->primary_key;
+                       addr_type = ipv6_addr_type(addr6);
+
+                       if (addr_type == IPV6_ADDR_ANY) {
+                               addr6 = &ipv6_hdr(skb)->daddr;
+                               addr_type = ipv6_addr_type(addr6);
+                       }
+
+                       if ((addr_type & IPV6_ADDR_COMPATv4) == 0)
+                               goto tx_error_icmp;
+
+                       dst = addr6->s6_addr32[3];
+               }
+#endif
+               else
+                       goto tx_error;
+       }
+
+       tos = tiph->tos;
+       if (tos == 1) {
+               tos = 0;
+               if (skb->protocol == htons(ETH_P_IP))
+                       tos = old_iph->tos;
+       }
+
+       {
+               struct flowi fl = { .oif = tunnel->parms.link,
+                                   .nl_u = { .ip4_u =
+                                             { .daddr = dst,
+                                               .saddr = tiph->saddr,
+                                               .tos = RT_TOS(tos) } },
+                                   .proto = IPPROTO_GRE };
+               if (ip_route_output_key(dev_net(dev), &rt, &fl)) {
+                       stats->tx_carrier_errors++;
+                       goto tx_error;
+               }
+       }
+       tdev = rt->u.dst.dev;
+
+       if (tdev == dev) {
+               ip_rt_put(rt);
+               stats->collisions++;
+               goto tx_error;
+       }
+
+       df = tiph->frag_off;
+       if (df)
+#ifdef HAVE_NETDEV_NEEDED_HEADROOM
+               mtu = dst_mtu(&rt->u.dst) - dev->hard_header_len - tunnel->hlen;
+#else
+               mtu = dst_mtu(&rt->u.dst) - tunnel->hlen;
+#endif
+       else
+               mtu = skb_dst(skb) ? dst_mtu(skb_dst(skb)) : dev->mtu;
+
+       if (skb_dst(skb))
+               skb_dst(skb)->ops->update_pmtu(skb_dst(skb), mtu);
+
+       /* XXX: Temporarily allow fragmentation since DF doesn't
+        * do the right thing with bridging. */
+/*
+       if (skb->protocol == htons(ETH_P_IP)) {
+               df |= (old_iph->frag_off&htons(IP_DF));
+
+               if ((old_iph->frag_off&htons(IP_DF)) &&
+                   mtu < ntohs(old_iph->tot_len)) {
+                       icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
+                       ip_rt_put(rt);
+                       goto tx_error;
+               }
+       }
+#ifdef CONFIG_IPV6
+       else if (skb->protocol == htons(ETH_P_IPV6)) {
+               struct rt6_info *rt6 = (struct rt6_info *)skb_dst(skb);
+
+               if (rt6 && mtu < dst_mtu(skb_dst(skb)) && mtu >= IPV6_MIN_MTU) {
+                       if ((tunnel->parms.iph.daddr &&
+                            !ipv4_is_multicast(tunnel->parms.iph.daddr)) ||
+                           rt6->rt6i_dst.plen == 128) {
+                               rt6->rt6i_flags |= RTF_MODIFIED;
+                               skb_dst(skb)->metrics[RTAX_MTU-1] = mtu;
+                       }
+               }
+
+               if (mtu >= IPV6_MIN_MTU && mtu < skb->len - tunnel->hlen + gre_hlen) {
+                       icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev);
+                       ip_rt_put(rt);
+                       goto tx_error;
+               }
+       }
+#endif
+*/
+       if (tunnel->err_count > 0) {
+               if (time_before(jiffies,
+                               tunnel->err_time + IPTUNNEL_ERR_TIMEO)) {
+                       tunnel->err_count--;
+
+                       dst_link_failure(skb);
+               } else
+                       tunnel->err_count = 0;
+       }
+
+       max_headroom = LL_RESERVED_SPACE(tdev) + gre_hlen;
+
+       if (skb_headroom(skb) < max_headroom || skb_shared(skb)||
+           (skb_cloned(skb) && !skb_clone_writable(skb, 0))) {
+               struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
+               if (!new_skb) {
+                       ip_rt_put(rt);
+                       stats->tx_dropped++;
+                       dev_kfree_skb(skb);
+                       return NETDEV_TX_OK;
+               }
+               if (skb->sk)
+                       skb_set_owner_w(new_skb, skb->sk);
+               dev_kfree_skb(skb);
+               skb = new_skb;
+               old_iph = ip_hdr(skb);
+       }
+
+       skb_reset_transport_header(skb);
+       skb_push(skb, gre_hlen);
+       skb_reset_network_header(skb);
+       memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
+       IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED |
+                             IPSKB_REROUTED);
+       skb_dst_drop(skb);
+       skb_dst_set(skb, &rt->u.dst);
+
+       /*
+        *      Push down and install the IPIP header.
+        */
+
+       iph                     =       ip_hdr(skb);
+       iph->version            =       4;
+       iph->ihl                =       sizeof(struct iphdr) >> 2;
+       iph->frag_off           =       df;
+       iph->protocol           =       IPPROTO_GRE;
+       iph->tos                =       ipgre_ecn_encapsulate(tos, old_iph, skb);
+       iph->daddr              =       rt->rt_dst;
+       iph->saddr              =       rt->rt_src;
+
+       if ((iph->ttl = tiph->ttl) == 0) {
+               if (skb->protocol == htons(ETH_P_IP))
+                       iph->ttl = old_iph->ttl;
+#ifdef CONFIG_IPV6
+               else if (skb->protocol == htons(ETH_P_IPV6))
+                       iph->ttl = ((struct ipv6hdr *)old_iph)->hop_limit;
+#endif
+               else
+                       iph->ttl = dst_metric(&rt->u.dst, RTAX_HOPLIMIT);
+       }
+
+       ((__be16 *)(iph + 1))[0] = tunnel->parms.o_flags;
+       ((__be16 *)(iph + 1))[1] = (dev->type == ARPHRD_ETHER) ?
+                                  htons(ETH_P_TEB) : skb->protocol;
+
+       if (tunnel->parms.o_flags&(GRE_KEY|GRE_CSUM|GRE_SEQ)) {
+               __be32 *ptr = (__be32*)(((u8*)iph) + tunnel->hlen - 4);
+
+               if (tunnel->parms.o_flags&GRE_SEQ) {
+                       ++tunnel->o_seqno;
+                       *ptr = htonl(tunnel->o_seqno);
+                       ptr--;
+               }
+               if (tunnel->parms.o_flags&GRE_KEY) {
+                       *ptr = tunnel->parms.o_key;
+                       ptr--;
+               }
+               if (tunnel->parms.o_flags&GRE_CSUM) {
+                       *ptr = 0;
+                       *(__sum16*)ptr = ip_compute_csum((void*)(iph+1), skb->len - sizeof(struct iphdr));
+               }
+       }
+
+       nf_reset(skb);
+
+       IPTUNNEL_XMIT();
+       return NETDEV_TX_OK;
+
+tx_error_icmp:
+       dst_link_failure(skb);
+
+tx_error:
+       stats->tx_errors++;
+       dev_kfree_skb(skb);
+       return NETDEV_TX_OK;
+}
+
+static int ipgre_tunnel_bind_dev(struct net_device *dev)
+{
+       struct net_device *tdev = NULL;
+       struct ip_tunnel *tunnel;
+       struct iphdr *iph;
+       int hlen = LL_MAX_HEADER;
+       int mtu = ETH_DATA_LEN;
+       int addend = sizeof(struct iphdr) + 4;
+
+       tunnel = netdev_priv(dev);
+       iph = &tunnel->parms.iph;
+
+       /* Guess output device to choose reasonable mtu and needed_headroom */
+
+       if (iph->daddr) {
+               struct flowi fl = { .oif = tunnel->parms.link,
+                                   .nl_u = { .ip4_u =
+                                             { .daddr = iph->daddr,
+                                               .saddr = iph->saddr,
+                                               .tos = RT_TOS(iph->tos) } },
+                                   .proto = IPPROTO_GRE };
+               struct rtable *rt;
+               if (!ip_route_output_key(dev_net(dev), &rt, &fl)) {
+                       tdev = rt->u.dst.dev;
+                       ip_rt_put(rt);
+               }
+
+               if (dev->type != ARPHRD_ETHER)
+                       dev->flags |= IFF_POINTOPOINT;
+       }
+
+       if (!tdev && tunnel->parms.link)
+               tdev = __dev_get_by_index(dev_net(dev), tunnel->parms.link);
+
+       if (tdev) {
+#ifdef HAVE_NETDEV_NEEDED_HEADROOM
+               hlen = tdev->hard_header_len + tdev->needed_headroom;
+#else
+               hlen = tdev->hard_header_len;
+#endif
+               mtu = tdev->mtu;
+       }
+       dev->iflink = tunnel->parms.link;
+
+       /* Precalculate GRE options length */
+       if (tunnel->parms.o_flags&(GRE_CSUM|GRE_KEY|GRE_SEQ)) {
+               if (tunnel->parms.o_flags&GRE_CSUM)
+                       addend += 4;
+               if (tunnel->parms.o_flags&GRE_KEY)
+                       addend += 4;
+               if (tunnel->parms.o_flags&GRE_SEQ)
+                       addend += 4;
+       }
+#ifdef HAVE_NETDEV_NEEDED_HEADROOM
+       dev->needed_headroom = hlen + addend;
+       mtu -= dev->hard_header_len + addend;
+#else
+       dev->hard_header_len = hlen + addend;
+       mtu -= addend;
+#endif
+       tunnel->hlen = addend;
+
+       if (mtu < 68)
+               mtu = 68;
+
+       /* XXX: Set MTU to the maximum possible value.  If we are bridged to a
+       * device with a larger MTU then packets will be dropped. */
+       mtu = 65482;
+
+       return mtu;
+}
+
+static int
+ipgre_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+       int err = 0;
+       struct ip_tunnel_parm p;
+       struct ip_tunnel *t;
+       struct net *net = dev_net(dev);
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       int add_tunnel, gretap;
+
+       switch (cmd) {
+       case SIOCGETTUNNEL:
+               t = NULL;
+               if (dev == ign->fb_tunnel_dev) {
+                       if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) {
+                               err = -EFAULT;
+                               break;
+                       }
+                       t = ipgre_tunnel_locate(net, &p, false, 0);
+               }
+               if (t == NULL)
+                       t = netdev_priv(dev);
+               memcpy(&p, &t->parms, sizeof(p));
+               if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
+                       err = -EFAULT;
+               break;
+
+       case SIOCADDTUNNEL:
+       case SIOCCHGTUNNEL:
+       case SIOCADDGRETAP:
+       case SIOCCHGGRETAP:
+               err = -EPERM;
+               if (!capable(CAP_NET_ADMIN))
+                       goto done;
+
+               err = -EFAULT;
+               if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
+                       goto done;
+
+               err = -EINVAL;
+               if (p.iph.version != 4 || p.iph.protocol != IPPROTO_GRE ||
+                   p.iph.ihl != 5 || (p.iph.frag_off&htons(~IP_DF)) ||
+                   ((p.i_flags|p.o_flags)&(GRE_VERSION|GRE_ROUTING)))
+                       goto done;
+
+               add_tunnel = (cmd == SIOCADDTUNNEL || cmd == SIOCADDGRETAP);
+               gretap = (cmd == SIOCADDGRETAP || cmd == SIOCCHGGRETAP);
+
+               if (p.iph.ttl)
+                       p.iph.frag_off |= htons(IP_DF);
+
+               if (!(p.i_flags&GRE_KEY))
+                       p.i_key = 0;
+               if (!(p.o_flags&GRE_KEY))
+                       p.o_key = 0;
+
+               t = ipgre_tunnel_locate(net, &p, gretap, add_tunnel);
+
+               if (dev != ign->fb_tunnel_dev && !add_tunnel) {
+                       if (t != NULL) {
+                               if (t->dev != dev) {
+                                       err = -EEXIST;
+                                       break;
+                               }
+                       } else {
+                               unsigned nflags = 0;
+
+                               t = netdev_priv(dev);
+
+                               if (ipv4_is_multicast(p.iph.daddr))
+                                       nflags = IFF_BROADCAST;
+                               else if (p.iph.daddr)
+                                       nflags = IFF_POINTOPOINT;
+
+                               if ((dev->flags^nflags)&(IFF_POINTOPOINT|IFF_BROADCAST)) {
+                                       err = -EINVAL;
+                                       break;
+                               }
+                               ipgre_tunnel_unlink(ign, t);
+                               t->parms.iph.saddr = p.iph.saddr;
+                               t->parms.iph.daddr = p.iph.daddr;
+                               t->parms.i_key = p.i_key;
+                               t->parms.o_key = p.o_key;
+                               memcpy(dev->dev_addr, &p.iph.saddr, 4);
+                               memcpy(dev->broadcast, &p.iph.daddr, 4);
+                               ipgre_tunnel_link(ign, t);
+                               netdev_state_change(dev);
+                       }
+               }
+
+               if (t) {
+                       err = 0;
+                       if (!add_tunnel) {
+                               t->parms.iph.ttl = p.iph.ttl;
+                               t->parms.iph.tos = p.iph.tos;
+                               t->parms.iph.frag_off = p.iph.frag_off;
+                               if (t->parms.link != p.link) {
+                                       t->parms.link = p.link;
+                                       dev->mtu = ipgre_tunnel_bind_dev(dev);
+                                       netdev_state_change(dev);
+                               }
+                       }
+                       if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof(p)))
+                               err = -EFAULT;
+               } else
+                       err = (add_tunnel ? -ENOBUFS : -ENOENT);
+               break;
+
+       case SIOCDELTUNNEL:
+               err = -EPERM;
+               if (!capable(CAP_NET_ADMIN))
+                       goto done;
+
+               if (dev == ign->fb_tunnel_dev) {
+                       err = -EFAULT;
+                       if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
+                               goto done;
+                       err = -ENOENT;
+                       if ((t = ipgre_tunnel_locate(net, &p, false, 0)) == NULL)
+                               goto done;
+                       err = -EPERM;
+                       if (t == netdev_priv(ign->fb_tunnel_dev))
+                               goto done;
+                       dev = t->dev;
+               }
+               unregister_netdevice(dev);
+               err = 0;
+               break;
+
+       default:
+               err = -EINVAL;
+       }
+
+done:
+       return err;
+}
+
+#ifndef HAVE_NETDEV_STATS
+static struct net_device_stats *ipgre_tunnel_get_stats(struct net_device *dev)
+{
+       return &(((struct ip_tunnel*)netdev_priv(dev))->stat);
+}
+#endif
+
+static int ipgre_tunnel_change_mtu(struct net_device *dev, int new_mtu)
+{
+       struct ip_tunnel *tunnel = netdev_priv(dev);
+       if (new_mtu < 68 ||
+#ifdef HAVE_NETDEV_NEEDED_HEADROOM
+       new_mtu > 0xFFF8 - dev->hard_header_len - tunnel->hlen)
+#else
+       new_mtu > 0xFFF8 - tunnel->hlen)
+#endif
+               return -EINVAL;
+       dev->mtu = new_mtu;
+       return 0;
+}
+
+/* Nice toy. Unfortunately, useless in real life :-)
+   It allows to construct virtual multiprotocol broadcast "LAN"
+   over the Internet, provided multicast routing is tuned.
+
+
+   I have no idea was this bicycle invented before me,
+   so that I had to set ARPHRD_IPGRE to a random value.
+   I have an impression, that Cisco could make something similar,
+   but this feature is apparently missing in IOS<=11.2(8).
+
+   I set up 10.66.66/24 and fec0:6666:6666::0/96 as virtual networks
+   with broadcast 224.66.66.66. If you have access to mbone, play with me :-)
+
+   ping -t 255 224.66.66.66
+
+   If nobody answers, mbone does not work.
+
+   ip tunnel add Universe mode gre remote 224.66.66.66 local <Your_real_addr> ttl 255
+   ip addr add 10.66.66.<somewhat>/24 dev Universe
+   ifconfig Universe up
+   ifconfig Universe add fe80::<Your_real_addr>/10
+   ifconfig Universe add fec0:6666:6666::<Your_real_addr>/96
+   ftp 10.66.66.66
+   ...
+   ftp fec0:6666:6666::193.233.7.65
+   ...
+
+ */
+
+#ifdef HAVE_NETDEV_HEADER_OPS
+static int ipgre_header(struct sk_buff *skb, struct net_device *dev,
+                       unsigned short type,
+                       const void *daddr, const void *saddr, unsigned len)
+#else
+static int ipgre_header(struct sk_buff *skb, struct net_device *dev, unsigned short type,
+                       void *daddr, void *saddr, unsigned len)
+#endif
+{
+       struct ip_tunnel *t = netdev_priv(dev);
+       struct iphdr *iph = (struct iphdr *)skb_push(skb, t->hlen);
+       __be16 *p = (__be16*)(iph+1);
+
+       memcpy(iph, &t->parms.iph, sizeof(struct iphdr));
+       p[0]            = t->parms.o_flags;
+       p[1]            = htons(type);
+
+       /*
+        *      Set the source hardware address.
+        */
+
+       if (saddr)
+               memcpy(&iph->saddr, saddr, 4);
+
+       if (daddr) {
+               memcpy(&iph->daddr, daddr, 4);
+               return t->hlen;
+       }
+       if (iph->daddr && !ipv4_is_multicast(iph->daddr))
+               return t->hlen;
+
+       return -t->hlen;
+}
+
+#ifdef HAVE_NETDEV_HEADER_OPS
+static int ipgre_header_parse(const struct sk_buff *skb, unsigned char *haddr)
+#else
+static int ipgre_header_parse(struct sk_buff *skb, unsigned char *haddr)
+#endif
+{
+       struct iphdr *iph = (struct iphdr *) skb_mac_header(skb);
+       memcpy(haddr, &iph->saddr, 4);
+       return 4;
+}
+
+#ifdef HAVE_NETDEV_HEADER_OPS
+static const struct header_ops ipgre_header_ops = {
+       .create = ipgre_header,
+       .parse  = ipgre_header_parse,
+};
+#endif
+
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+static int ipgre_open(struct net_device *dev)
+{
+       struct ip_tunnel *t = netdev_priv(dev);
+
+       if (ipv4_is_multicast(t->parms.iph.daddr)) {
+               struct flowi fl = { .oif = t->parms.link,
+                                   .nl_u = { .ip4_u =
+                                             { .daddr = t->parms.iph.daddr,
+                                               .saddr = t->parms.iph.saddr,
+                                               .tos = RT_TOS(t->parms.iph.tos) } },
+                                   .proto = IPPROTO_GRE };
+               struct rtable *rt;
+               if (ip_route_output_key(dev_net(dev), &rt, &fl))
+                       return -EADDRNOTAVAIL;
+               dev = rt->u.dst.dev;
+               ip_rt_put(rt);
+               if (__in_dev_get_rtnl(dev) == NULL)
+                       return -EADDRNOTAVAIL;
+               t->mlink = dev->ifindex;
+               ip_mc_inc_group(__in_dev_get_rtnl(dev), t->parms.iph.daddr);
+       }
+       return 0;
+}
+
+static int ipgre_close(struct net_device *dev)
+{
+       struct ip_tunnel *t = netdev_priv(dev);
+
+       if (ipv4_is_multicast(t->parms.iph.daddr) && t->mlink) {
+               struct in_device *in_dev;
+               in_dev = inetdev_by_index(dev_net(dev), t->mlink);
+               if (in_dev) {
+                       ip_mc_dec_group(in_dev, t->parms.iph.daddr);
+                       in_dev_put(in_dev);
+               }
+       }
+       return 0;
+}
+
+#endif
+
+static void ethtool_getinfo(struct net_device *dev,
+                           struct ethtool_drvinfo *info)
+{
+       strcpy(info->driver, "ip_gre");
+       strcpy(info->version, "Open vSwitch "VERSION BUILDNR);
+       strcpy(info->bus_info, dev->type == ARPHRD_ETHER ? "gretap" : "gre");
+}
+
+static struct ethtool_ops ethtool_ops = {
+       .get_drvinfo = ethtool_getinfo,
+};
+
+#ifdef HAVE_NET_DEVICE_OPS
+static const struct net_device_ops ipgre_netdev_ops = {
+       .ndo_init               = ipgre_tunnel_init,
+       .ndo_uninit             = ipgre_tunnel_uninit,
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+       .ndo_open               = ipgre_open,
+       .ndo_stop               = ipgre_close,
+#endif
+       .ndo_start_xmit         = ipgre_tunnel_xmit,
+       .ndo_do_ioctl           = ipgre_tunnel_ioctl,
+       .ndo_change_mtu         = ipgre_tunnel_change_mtu,
+};
+#endif
+
+static void ipgre_tunnel_setup(struct net_device *dev)
+{
+#ifdef HAVE_NET_DEVICE_OPS
+       dev->netdev_ops         = &ipgre_netdev_ops;
+#else
+       dev->init               = ipgre_tunnel_init;
+       dev->uninit             = ipgre_tunnel_uninit;
+       dev->hard_start_xmit    = ipgre_tunnel_xmit;
+#ifndef HAVE_NETDEV_STATS
+       dev->get_stats          = ipgre_tunnel_get_stats;
+#endif
+       dev->do_ioctl           = ipgre_tunnel_ioctl;
+       dev->change_mtu         = ipgre_tunnel_change_mtu;
+#endif /* HAVE_NET_DEVICE_OPS */
+       dev->destructor         = free_netdev;
+
+       dev->type               = ARPHRD_IPGRE;
+#ifdef HAVE_NETDEV_NEEDED_HEADROOM
+       dev->needed_headroom    = LL_MAX_HEADER + sizeof(struct iphdr) + 4;
+#else
+       dev->hard_header_len    = LL_MAX_HEADER + sizeof(struct iphdr) + 4;
+#endif
+       dev->mtu                = ETH_DATA_LEN - sizeof(struct iphdr) - 4;
+       dev->flags              = IFF_NOARP;
+       dev->iflink             = 0;
+       dev->addr_len           = 4;
+       dev->features           |= NETIF_F_NETNS_LOCAL;
+       dev->priv_flags         &= ~IFF_XMIT_DST_RELEASE;
+
+       SET_ETHTOOL_OPS(dev, &ethtool_ops);
+}
+
+static int ipgre_tunnel_init(struct net_device *dev)
+{
+       struct ip_tunnel *tunnel;
+       struct iphdr *iph;
+
+       tunnel = netdev_priv(dev);
+       iph = &tunnel->parms.iph;
+
+       tunnel->dev = dev;
+       strcpy(tunnel->parms.name, dev->name);
+
+       memcpy(dev->dev_addr, &tunnel->parms.iph.saddr, 4);
+       memcpy(dev->broadcast, &tunnel->parms.iph.daddr, 4);
+
+       if (iph->daddr) {
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+               if (ipv4_is_multicast(iph->daddr)) {
+                       if (!iph->saddr)
+                               return -EINVAL;
+                       dev->flags = IFF_BROADCAST;
+#ifdef HAVE_NETDEV_HEADER_OPS
+                       dev->header_ops = &ipgre_header_ops;
+#else
+                       dev->hard_header = ipgre_header;
+                       dev->hard_header_parse = ipgre_header_parse;
+#endif
+#ifndef HAVE_NET_DEVICE_OPS
+                       dev->open = ipgre_open;
+                       dev->stop = ipgre_close;
+#endif
+               }
+#endif
+       } else {
+#ifdef HAVE_NETDEV_HEADER_OPS
+               dev->header_ops = &ipgre_header_ops;
+#else
+               dev->hard_header = ipgre_header;
+               dev->hard_header_parse = ipgre_header_parse;
+#endif
+       }
+
+       return 0;
+}
+
+#ifdef HAVE_NET_DEVICE_OPS
+static void ipgre_fb_tunnel_init(struct net_device *dev)
+#else
+static int ipgre_fb_tunnel_init(struct net_device *dev)
+#endif
+{
+       struct ip_tunnel *tunnel = netdev_priv(dev);
+       struct iphdr *iph = &tunnel->parms.iph;
+       struct ipgre_net *ign = net_generic(dev_net(dev), ipgre_net_id);
+
+       tunnel->dev = dev;
+       strcpy(tunnel->parms.name, dev->name);
+
+       iph->version            = 4;
+       iph->protocol           = IPPROTO_GRE;
+       iph->ihl                = 5;
+       tunnel->hlen            = sizeof(struct iphdr) + 4;
+
+       dev_hold(dev);
+       ign->tunnels_wc[0]      = tunnel;
+
+#ifndef HAVE_NET_DEVICE_OPS
+       return 0;
+#endif
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)
+static struct net_protocol ipgre_protocol = {
+#else
+static const struct net_protocol ipgre_protocol = {
+#endif
+       .handler        =       ipgre_rcv,
+       .err_handler    =       ipgre_err,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)
+       .netns_ok       =       1,
+#endif
+};
+
+static void ipgre_destroy_tunnels(struct ipgre_net *ign)
+{
+       int prio;
+
+       for (prio = 0; prio < 4; prio++) {
+               int h;
+               for (h = 0; h < HASH_SIZE; h++) {
+                       struct ip_tunnel *t;
+                       while ((t = ign->tunnels[prio][h]) != NULL)
+                               unregister_netdevice(t->dev);
+               }
+       }
+}
+
+static int ipgre_init_net(struct net *net)
+{
+       int err;
+       struct ipgre_net *ign;
+
+       err = -ENOMEM;
+       ign = kzalloc(sizeof(struct ipgre_net), GFP_KERNEL);
+       if (ign == NULL)
+               goto err_alloc;
+
+       err = net_assign_generic(net, ipgre_net_id, ign);
+       if (err < 0)
+               goto err_assign;
+
+       ign->fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), GRE_IOCTL_DEVICE,
+                                          ipgre_tunnel_setup);
+       if (!ign->fb_tunnel_dev) {
+               err = -ENOMEM;
+               goto err_alloc_dev;
+       }
+       dev_net_set(ign->fb_tunnel_dev, net);
+
+#ifdef HAVE_NET_DEVICE_OPS
+       ipgre_fb_tunnel_init(ign->fb_tunnel_dev);
+#else
+       ign->fb_tunnel_dev->init = ipgre_fb_tunnel_init;
+#endif
+#ifndef GRE_IOCTL_ONLY
+       ign->fb_tunnel_dev->rtnl_link_ops = &ipgre_link_ops;
+#endif
+
+       if ((err = register_netdev(ign->fb_tunnel_dev)))
+               goto err_reg_dev;
+
+       return 0;
+
+err_reg_dev:
+       free_netdev(ign->fb_tunnel_dev);
+err_alloc_dev:
+       /* nothing */
+err_assign:
+       kfree(ign);
+err_alloc:
+       return err;
+}
+
+static void ipgre_exit_net(struct net *net)
+{
+       struct ipgre_net *ign;
+
+       ign = net_generic(net, ipgre_net_id);
+       rtnl_lock();
+       ipgre_destroy_tunnels(ign);
+       rtnl_unlock();
+       kfree(ign);
+}
+
+static struct pernet_operations ipgre_net_ops = {
+       .init = ipgre_init_net,
+       .exit = ipgre_exit_net,
+};
+
+static int ipgre_tap_init(struct net_device *dev)
+{
+       struct ip_tunnel *tunnel;
+
+       tunnel = netdev_priv(dev);
+
+       tunnel->dev = dev;
+       strcpy(tunnel->parms.name, dev->name);
+
+       ipgre_tunnel_bind_dev(dev);
+
+       return 0;
+}
+
+#ifdef HAVE_NET_DEVICE_OPS
+static const struct net_device_ops ipgre_tap_netdev_ops = {
+       .ndo_init               = ipgre_tap_init,
+       .ndo_uninit             = ipgre_tunnel_uninit,
+       .ndo_start_xmit         = ipgre_tunnel_xmit,
+       .ndo_set_mac_address    = eth_mac_addr,
+       .ndo_validate_addr      = eth_validate_addr,
+       .ndo_do_ioctl           = ipgre_tunnel_ioctl,
+       .ndo_change_mtu         = ipgre_tunnel_change_mtu,
+};
+#endif
+
+static void ipgre_tap_setup(struct net_device *dev)
+{
+       ether_setup(dev);
+
+#ifdef HAVE_NET_DEVICE_OPS
+       dev->netdev_ops         = &ipgre_tap_netdev_ops;
+#else
+       dev->init               = ipgre_tap_init;
+       dev->uninit             = ipgre_tunnel_uninit;
+       dev->hard_start_xmit    = ipgre_tunnel_xmit;
+#ifndef HAVE_NETDEV_STATS
+       dev->get_stats          = ipgre_tunnel_get_stats;
+#endif
+       dev->do_ioctl           = ipgre_tunnel_ioctl;
+       dev->change_mtu         = ipgre_tunnel_change_mtu;
+#endif /* HAVE_NET_DEVICE_OPS */
+       dev->destructor         = free_netdev;
+
+       dev->iflink             = 0;
+       dev->features           |= NETIF_F_NETNS_LOCAL;
+
+       SET_ETHTOOL_OPS(dev, &ethtool_ops);
+}
+
+#ifndef GRE_IOCTL_ONLY
+static int ipgre_tunnel_validate(struct nlattr *tb[], struct nlattr *data[])
+{
+       __be16 flags;
+
+       if (!data)
+               return 0;
+
+       flags = 0;
+       if (data[IFLA_GRE_IFLAGS])
+               flags |= nla_get_be16(data[IFLA_GRE_IFLAGS]);
+       if (data[IFLA_GRE_OFLAGS])
+               flags |= nla_get_be16(data[IFLA_GRE_OFLAGS]);
+       if (flags & (GRE_VERSION|GRE_ROUTING))
+               return -EINVAL;
+
+       return 0;
+}
+
+static int ipgre_tap_validate(struct nlattr *tb[], struct nlattr *data[])
+{
+       __be32 daddr;
+
+       if (tb[IFLA_ADDRESS]) {
+               if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN)
+                       return -EINVAL;
+               if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS])))
+                       return -EADDRNOTAVAIL;
+       }
+
+       if (!data)
+               goto out;
+
+       if (data[IFLA_GRE_REMOTE]) {
+               memcpy(&daddr, nla_data(data[IFLA_GRE_REMOTE]), 4);
+               if (!daddr)
+                       return -EINVAL;
+       }
+
+out:
+       return ipgre_tunnel_validate(tb, data);
+}
+
+static void ipgre_netlink_parms(struct nlattr *data[],
+                               struct ip_tunnel_parm *parms)
+{
+       memset(parms, 0, sizeof(*parms));
+
+       parms->iph.protocol = IPPROTO_GRE;
+
+       if (!data)
+               return;
+
+       if (data[IFLA_GRE_LINK])
+               parms->link = nla_get_u32(data[IFLA_GRE_LINK]);
+
+       if (data[IFLA_GRE_IFLAGS])
+               parms->i_flags = nla_get_be16(data[IFLA_GRE_IFLAGS]);
+
+       if (data[IFLA_GRE_OFLAGS])
+               parms->o_flags = nla_get_be16(data[IFLA_GRE_OFLAGS]);
+
+       if (data[IFLA_GRE_IKEY])
+               parms->i_key = nla_get_be32(data[IFLA_GRE_IKEY]);
+
+       if (data[IFLA_GRE_OKEY])
+               parms->o_key = nla_get_be32(data[IFLA_GRE_OKEY]);
+
+       if (data[IFLA_GRE_LOCAL])
+               parms->iph.saddr = nla_get_be32(data[IFLA_GRE_LOCAL]);
+
+       if (data[IFLA_GRE_REMOTE])
+               parms->iph.daddr = nla_get_be32(data[IFLA_GRE_REMOTE]);
+
+       if (data[IFLA_GRE_TTL])
+               parms->iph.ttl = nla_get_u8(data[IFLA_GRE_TTL]);
+
+       if (data[IFLA_GRE_TOS])
+               parms->iph.tos = nla_get_u8(data[IFLA_GRE_TOS]);
+
+       if (!data[IFLA_GRE_PMTUDISC] || nla_get_u8(data[IFLA_GRE_PMTUDISC]))
+               parms->iph.frag_off = htons(IP_DF);
+}
+
+static int ipgre_newlink(struct net_device *dev, struct nlattr *tb[],
+                        struct nlattr *data[])
+{
+       struct ip_tunnel *nt;
+       struct net *net = dev_net(dev);
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       int mtu;
+       int err;
+
+       nt = netdev_priv(dev);
+       ipgre_netlink_parms(data, &nt->parms);
+
+       if (ipgre_tunnel_find(net, &nt->parms, dev->type))
+               return -EEXIST;
+
+       if (dev->type == ARPHRD_ETHER && !tb[IFLA_ADDRESS])
+               random_ether_addr(dev->dev_addr);
+
+       mtu = ipgre_tunnel_bind_dev(dev);
+       if (!tb[IFLA_MTU])
+               dev->mtu = mtu;
+
+       err = register_netdevice(dev);
+       if (err)
+               goto out;
+
+       dev_hold(dev);
+       ipgre_tunnel_link(ign, nt);
+
+out:
+       return err;
+}
+
+static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
+                           struct nlattr *data[])
+{
+       struct ip_tunnel *t, *nt;
+       struct net *net = dev_net(dev);
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       struct ip_tunnel_parm p;
+       int mtu;
+
+       if (dev == ign->fb_tunnel_dev)
+               return -EINVAL;
+
+       nt = netdev_priv(dev);
+       ipgre_netlink_parms(data, &p);
+
+       t = ipgre_tunnel_locate(net, &p, false, 0);
+
+       if (t) {
+               if (t->dev != dev)
+                       return -EEXIST;
+       } else {
+               t = nt;
+
+               if (dev->type != ARPHRD_ETHER) {
+                       unsigned nflags = 0;
+
+                       if (ipv4_is_multicast(p.iph.daddr))
+                               nflags = IFF_BROADCAST;
+                       else if (p.iph.daddr)
+                               nflags = IFF_POINTOPOINT;
+
+                       if ((dev->flags ^ nflags) &
+                           (IFF_POINTOPOINT | IFF_BROADCAST))
+                               return -EINVAL;
+               }
+
+               ipgre_tunnel_unlink(ign, t);
+               t->parms.iph.saddr = p.iph.saddr;
+               t->parms.iph.daddr = p.iph.daddr;
+               t->parms.i_key = p.i_key;
+               if (dev->type != ARPHRD_ETHER) {
+                       memcpy(dev->dev_addr, &p.iph.saddr, 4);
+                       memcpy(dev->broadcast, &p.iph.daddr, 4);
+               }
+               ipgre_tunnel_link(ign, t);
+               netdev_state_change(dev);
+       }
+
+       t->parms.o_key = p.o_key;
+       t->parms.iph.ttl = p.iph.ttl;
+       t->parms.iph.tos = p.iph.tos;
+       t->parms.iph.frag_off = p.iph.frag_off;
+
+       if (t->parms.link != p.link) {
+               t->parms.link = p.link;
+               mtu = ipgre_tunnel_bind_dev(dev);
+               if (!tb[IFLA_MTU])
+                       dev->mtu = mtu;
+               netdev_state_change(dev);
+       }
+
+       return 0;
+}
+
+static size_t ipgre_get_size(const struct net_device *dev)
+{
+       return
+               /* IFLA_GRE_LINK */
+               nla_total_size(4) +
+               /* IFLA_GRE_IFLAGS */
+               nla_total_size(2) +
+               /* IFLA_GRE_OFLAGS */
+               nla_total_size(2) +
+               /* IFLA_GRE_IKEY */
+               nla_total_size(4) +
+               /* IFLA_GRE_OKEY */
+               nla_total_size(4) +
+               /* IFLA_GRE_LOCAL */
+               nla_total_size(4) +
+               /* IFLA_GRE_REMOTE */
+               nla_total_size(4) +
+               /* IFLA_GRE_TTL */
+               nla_total_size(1) +
+               /* IFLA_GRE_TOS */
+               nla_total_size(1) +
+               /* IFLA_GRE_PMTUDISC */
+               nla_total_size(1) +
+               0;
+}
+
+static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev)
+{
+       struct ip_tunnel *t = netdev_priv(dev);
+       struct ip_tunnel_parm *p = &t->parms;
+
+       NLA_PUT_U32(skb, IFLA_GRE_LINK, p->link);
+       NLA_PUT_BE16(skb, IFLA_GRE_IFLAGS, p->i_flags);
+       NLA_PUT_BE16(skb, IFLA_GRE_OFLAGS, p->o_flags);
+       NLA_PUT_BE32(skb, IFLA_GRE_IKEY, p->i_key);
+       NLA_PUT_BE32(skb, IFLA_GRE_OKEY, p->o_key);
+       NLA_PUT_BE32(skb, IFLA_GRE_LOCAL, p->iph.saddr);
+       NLA_PUT_BE32(skb, IFLA_GRE_REMOTE, p->iph.daddr);
+       NLA_PUT_U8(skb, IFLA_GRE_TTL, p->iph.ttl);
+       NLA_PUT_U8(skb, IFLA_GRE_TOS, p->iph.tos);
+       NLA_PUT_U8(skb, IFLA_GRE_PMTUDISC, !!(p->iph.frag_off & htons(IP_DF)));
+
+       return 0;
+
+nla_put_failure:
+       return -EMSGSIZE;
+}
+
+static const struct nla_policy ipgre_policy[IFLA_GRE_MAX + 1] = {
+       [IFLA_GRE_LINK]         = { .type = NLA_U32 },
+       [IFLA_GRE_IFLAGS]       = { .type = NLA_U16 },
+       [IFLA_GRE_OFLAGS]       = { .type = NLA_U16 },
+       [IFLA_GRE_IKEY]         = { .type = NLA_U32 },
+       [IFLA_GRE_OKEY]         = { .type = NLA_U32 },
+       [IFLA_GRE_LOCAL]        = { .len = FIELD_SIZEOF(struct iphdr, saddr) },
+       [IFLA_GRE_REMOTE]       = { .len = FIELD_SIZEOF(struct iphdr, daddr) },
+       [IFLA_GRE_TTL]          = { .type = NLA_U8 },
+       [IFLA_GRE_TOS]          = { .type = NLA_U8 },
+       [IFLA_GRE_PMTUDISC]     = { .type = NLA_U8 },
+};
+
+static struct rtnl_link_ops ipgre_link_ops __read_mostly = {
+       .kind           = "gre",
+       .maxtype        = IFLA_GRE_MAX,
+       .policy         = ipgre_policy,
+       .priv_size      = sizeof(struct ip_tunnel),
+       .setup          = ipgre_tunnel_setup,
+       .validate       = ipgre_tunnel_validate,
+       .newlink        = ipgre_newlink,
+       .changelink     = ipgre_changelink,
+       .get_size       = ipgre_get_size,
+       .fill_info      = ipgre_fill_info,
+};
+
+static struct rtnl_link_ops ipgre_tap_ops __read_mostly = {
+       .kind           = "gretap",
+       .maxtype        = IFLA_GRE_MAX,
+       .policy         = ipgre_policy,
+       .priv_size      = sizeof(struct ip_tunnel),
+       .setup          = ipgre_tap_setup,
+       .validate       = ipgre_tap_validate,
+       .newlink        = ipgre_newlink,
+       .changelink     = ipgre_changelink,
+       .get_size       = ipgre_get_size,
+       .fill_info      = ipgre_fill_info,
+};
+#endif
+
+/*
+ *     And now the modules code and kernel interface.
+ */
+
+static int __init ipgre_init(void)
+{
+       int err;
+
+       printk(KERN_INFO "GRE over IPv4 tunneling driver\n");
+
+       if (inet_add_protocol(&ipgre_protocol, IPPROTO_GRE) < 0) {
+               printk(KERN_INFO "ipgre init: can't add protocol\n");
+               return -EAGAIN;
+       }
+
+       err = register_pernet_gen_device(&ipgre_net_id, &ipgre_net_ops);
+       if (err < 0)
+               goto gen_device_failed;
+
+#ifndef GRE_IOCTL_ONLY
+       err = rtnl_link_register(&ipgre_link_ops);
+       if (err < 0)
+               goto rtnl_link_failed;
+
+       err = rtnl_link_register(&ipgre_tap_ops);
+       if (err < 0)
+               goto tap_ops_failed;
+#endif
+
+out:
+       return err;
+
+#ifndef GRE_IOCTL_ONLY
+tap_ops_failed:
+       rtnl_link_unregister(&ipgre_link_ops);
+rtnl_link_failed:
+       unregister_pernet_gen_device(ipgre_net_id, &ipgre_net_ops);
+#endif
+gen_device_failed:
+       inet_del_protocol(&ipgre_protocol, IPPROTO_GRE);
+       goto out;
+
+}
+
+static void __exit ipgre_fini(void)
+{
+#ifndef GRE_IOCTL_ONLY
+       rtnl_link_unregister(&ipgre_tap_ops);
+       rtnl_link_unregister(&ipgre_link_ops);
+#endif
+       unregister_pernet_gen_device(ipgre_net_id, &ipgre_net_ops);
+       if (inet_del_protocol(&ipgre_protocol, IPPROTO_GRE) < 0)
+               printk(KERN_INFO "ipgre close: can't remove protocol\n");
+}
+
+module_init(ipgre_init);
+module_exit(ipgre_fini);
+MODULE_DESCRIPTION("GRE over IPv4 tunneling driver");
+MODULE_LICENSE("GPL");
+#ifndef GRE_IOCTL_ONLY
+MODULE_ALIAS_RTNL_LINK("gre");
+MODULE_ALIAS_RTNL_LINK("gretap");
+#endif
+
diff --git a/datapath/linux-2.6/compat-2.6/net_namespace-ip_gre.c b/datapath/linux-2.6/compat-2.6/net_namespace-ip_gre.c
new file mode 100644 (file)
index 0000000..323b644
--- /dev/null
@@ -0,0 +1,49 @@
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+
+#include <linux/sched.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+
+/* This trivial implementation assumes that there is only a single pernet
+ * generic device registered and that the caller is well behaved.  It only
+ * weakly attempts to check that these conditions are true. */
+
+static bool device_registered;
+static void *ng_data;
+
+int register_pernet_gen_device(int *id, struct pernet_operations *ops)
+{
+       BUG_ON(device_registered);
+
+       *id = 1;
+       device_registered = true;
+
+       if (ops->init == NULL)
+               return 0;
+       return ops->init(NULL);
+}
+
+void unregister_pernet_gen_device(int id, struct pernet_operations *ops)
+{
+       device_registered = false;
+       if (ops->exit)
+               ops->exit(NULL);
+}
+
+int net_assign_generic(struct net *net, int id, void *data)
+{
+       BUG_ON(id != 1);
+
+       ng_data = data;
+       return 0;
+}
+
+void *net_generic(struct net *net, int id)
+{
+       BUG_ON(id != 1);
+
+       return ng_data;
+}
+
+#endif /* kernel < 2.6.26 */
index 813987e..1b64945 100644 (file)
@@ -1,7 +1,5 @@
 EXTRA_DIST += \
        debian/changelog \
-       debian/commands/reconfigure \
-       debian/commands/update \
        debian/compat \
        debian/control \
        debian/control.modules.in \
@@ -9,8 +7,6 @@ EXTRA_DIST += \
        debian/corekeeper.cron.daily \
        debian/corekeeper.init \
        debian/dirs \
-       debian/ovs-switch-setup \
-       debian/ovs-switch-setup.8 \
        debian/openvswitch-common.dirs \
        debian/openvswitch-common.install \
        debian/openvswitch-common.manpages \
@@ -26,6 +22,10 @@ EXTRA_DIST += \
        debian/openvswitch-datapath-source.copyright \
        debian/openvswitch-datapath-source.dirs \
        debian/openvswitch-datapath-source.install \
+       debian/openvswitch-monitor.default \
+       debian/openvswitch-monitor.dirs \
+       debian/openvswitch-monitor.init \
+       debian/openvswitch-monitor.install \
        debian/openvswitch-pki-server.apache2 \
        debian/openvswitch-pki-server.dirs \
        debian/openvswitch-pki-server.install \
@@ -45,6 +45,19 @@ EXTRA_DIST += \
        debian/openvswitch-switch.postinst \
        debian/openvswitch-switch.postrm \
        debian/openvswitch-switch.template \
+       debian/openvswitch-switchui.copyright \
+       debian/openvswitch-switchui.default \
+       debian/openvswitch-switchui.dirs \
+       debian/openvswitch-switchui.init \
+       debian/openvswitch-switchui.install \
+       debian/openvswitch-wdt.default \
+       debian/openvswitch-wdt.dirs \
+       debian/openvswitch-wdt.init \
+       debian/openvswitch-wdt.install \
+       debian/ovs-switch-setup \
+       debian/ovs-switch-setup.8 \
        debian/po/POTFILES.in \
        debian/po/templates.pot \
-       debian/rules
+       debian/reconfigure \
+       debian/rules \
+       debian/rules.modules
index 76ed626..eb1d40a 100644 (file)
@@ -1,5 +1,5 @@
-openvswitch (0.90.0) unstable; urgency=low
+openvswitch (0.90.6) unstable; urgency=low
 
   * Development version.
 
- -- Open vSwitch developers <dev@openvswitch.org>  Mon, 19 Nov 2007 14:57:52 -0800
+ -- Ben Pfaff <blp@nicira.com>  Tue, 15 Dec 2009 11:00:11 -0800
diff --git a/debian/commands/update b/debian/commands/update
deleted file mode 100755 (executable)
index 545e3c2..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-#! /bin/sh
-set -e
-apt-get update -qy
-apt-get upgrade -qy
index 9e0be5e..f5126e8 100644 (file)
@@ -7,7 +7,7 @@ Standards-Version: 3.7.3
 
 Package: openvswitch-datapath-source
 Architecture: all
-Depends: module-assistant, bzip2, debhelper (>= 5.0.37)
+Depends: module-assistant, bzip2, debhelper (>= 5.0.37), ${misc:Depends}
 Suggests: openvswitch-switch
 Description: Source code for Open vSwitch datapath Linux module
  This package provides the Open vSwitch datapath module source code
@@ -19,7 +19,7 @@ Description: Source code for Open vSwitch datapath Linux module
 
 Package: openvswitch-common
 Architecture: any
-Depends: ${shlibs:Depends}, openssl
+Depends: ${shlibs:Depends}, openssl, ${misc:Depends}
 Description: Open vSwitch common components
  openvswitch-common provides components required by both openvswitch-switch
  and openvswitch-controller.
@@ -29,7 +29,7 @@ Description: Open vSwitch common components
 Package: openvswitch-switch
 Architecture: any
 Suggests: openvswitch-datapath-module
-Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-common (= ${source:Version}), dhcp3-client, module-init-tools, dmidecode, procps, debianutils
+Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-common (= ${binary:Version}), module-init-tools, procps
 Description: Open vSwitch switch implementations
  openvswitch-switch provides the userspace components and utilities for
  the Open vSwitch kernel-based switch.  
@@ -38,7 +38,7 @@ Description: Open vSwitch switch implementations
 
 Package: openvswitch-switch-config
 Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-switch (= ${source:Version}), libwww-perl, libdigest-sha1-perl
+Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-switch (= ${binary:Version}), libwww-perl, libdigest-sha1-perl
 Description: Open vSwitch switch implementations
  openvswitch-switch-config provides a utility for interactively configuring
  the Open vSwitch switch provided in the openvswitch-switch package.
@@ -61,7 +61,7 @@ Description: Monitoring utility for OpenFlow switches
 
 Package: openvswitch-pki
 Architecture: all
-Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-common (= ${source:Version})
+Depends: ${shlibs:Depends}, ${misc:Depends}, openvswitch-common (= ${binary:Version})
 Description: Open vSwitch public key infrastructure
  openvswitch-pki provides PKI (public key infrastructure) support for
  Open vSwitch switches and controllers, reducing the risk of
@@ -71,7 +71,7 @@ Description: Open vSwitch public key infrastructure
 
 Package: openvswitch-pki-server
 Architecture: all
-Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, openvswitch-pki (= ${source:Version}), apache2
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, openvswitch-pki (= ${binary:Version}), apache2
 Description: Open vSwitch public key infrastructure (HTTP server support)
  openvswitch-pki-server provides HTTP access to the Open vSwitch PKI (public
  key infrastructure) maintained on the local machine by the
@@ -83,7 +83,7 @@ Description: Open vSwitch public key infrastructure (HTTP server support)
 
 Package: openvswitch-controller
 Architecture: any
-Depends: ${shlibs:Depends}, openvswitch-common (= ${source:Version}), openvswitch-pki (= ${source:Version})
+Depends: ${shlibs:Depends}, openvswitch-common (= ${binary:Version}), openvswitch-pki (= ${binary:Version}), ${misc:Depends}
 Description: Open vSwitch controller implementation
  The Open vSwitch controller enables OpenFlow switches that connect to it
  to act as MAC-learning Ethernet switches.
@@ -92,14 +92,15 @@ Description: Open vSwitch controller implementation
 
 Package: corekeeper
 Architecture: all
-Depends: tmpreaper
+Depends: tmpreaper, ${misc:Depends}
 Description: Core file centralizer and reaper
  The corekeeper package configures the system to dump all core files to
  /var/log/core.  It also deletes core files older than 7 days.
 
 Package: openvswitch-dbg
+Section: debug
 Architecture: any
-Depends: ${shlibs:Depends}
+Depends: ${shlibs:Depends}, ${misc:Depends}
 Description: Debug symbols for Open vSwitch packages
  This package contains the debug symbols for all the other openvswitch-*
  packages.  Install it to debug one of them or to examine a core dump
@@ -125,11 +126,11 @@ Description: Watchdog utility for Open vSwitch switches
 Package: nicira-switch
 Architecture: all
 Depends: 
- openvswitch-common (= ${source:Version}), 
- openvswitch-switch (= ${source:Version}), 
- openvswitch-switchui (= ${source:Version}), 
- openvswitch-datapath-module (= ${source:Version}),
- corekeeper, openvswitch-monitor, openvswitch-wdt
+ openvswitch-common (= ${binary:Version}), 
+ openvswitch-switch (= ${binary:Version}), 
+ openvswitch-switchui (= ${binary:Version}), 
+ openvswitch-datapath-module (= ${binary:Version}),
+ corekeeper, openvswitch-monitor, openvswitch-wdt, ${misc:Depends}
 Description: Metapackage for installing a Nicira Open vSwitch switch
  Installing this package will install everything needed for a Nicira
  Portwell-based Open vSwitch switch, including monitoring and the switch UI.
index d820b02..b7b2d4d 100755 (executable)
@@ -42,7 +42,7 @@ set -e
 
 case "$1" in
   start)
-       log_daemon_msg "Initializing core dump location..."
+        log_daemon_msg "Initializing core dump location..."
         if echo "/var/log/core/core.%e.%t.%p" > /proc/sys/kernel/core_pattern
         then
             log_progress_msg "success"
@@ -52,13 +52,13 @@ case "$1" in
             log_end_msg 1
             exit 1
         fi
-       ;;
+        ;;
   stop|restart|force-reload|status|reload)
         exit 0
         ;;
   *)
-       N=/etc/init.d/$NAME
-       echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
-       exit 1
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
+        exit 1
+        ;;
 esac
index 1967ccc..fab9916 100644 (file)
@@ -1,3 +1,7 @@
+_debian/ovsdb/ovsdb-client usr/bin
+_debian/ovsdb/ovsdb-tool usr/bin
 _debian/utilities/ovs-appctl usr/sbin
+_debian/utilities/ovs-ofctl usr/sbin
 _debian/utilities/ovs-parse-leaks usr/bin
 _debian/utilities/ovs-pki usr/sbin
+vswitchd/vswitch.ovsschema usr/share/openvswitch
index 99c48bd..5243247 100644 (file)
@@ -1,2 +1,5 @@
+_debian/ovsdb/ovsdb-client.1
+_debian/ovsdb/ovsdb-tool.1
 _debian/utilities/ovs-appctl.8
+_debian/utilities/ovs-ofctl.8
 _debian/utilities/ovs-pki.8
index ee9c44d..c5f8eb8 100755 (executable)
@@ -57,7 +57,7 @@ LOGFILE=$LOGDIR/$NAME.log  # Server logfile
 # Include defaults if available
 default=/etc/default/openvswitch-controller
 if [ -f $default ] ; then
-       . $default
+    . $default
 fi
 
 # Check that the user exists (if we set a user)
@@ -142,7 +142,7 @@ start_server() {
                         $SSL_OPTS
             errcode=$?
         fi
-       return $errcode
+        return $errcode
 }
 
 stop_server() {
@@ -158,7 +158,7 @@ stop_server() {
             errcode=$?
         fi
 
-       return $errcode
+        return $errcode
 }
 
 reload_server() {
@@ -171,27 +171,27 @@ reload_server() {
 
 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
+    [ ! -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"
+        log_daemon_msg "Starting $DESC " "$NAME"
         # Check if it's running first
         if running ;  then
             log_progress_msg "apparently already running"
@@ -209,7 +209,7 @@ case "$1" in
             # a false positive (use 'status' for that)
             log_end_msg 1
         fi
-       ;;
+        ;;
   stop)
         log_daemon_msg "Stopping $DESC" "$NAME"
         if running ; then
@@ -232,7 +232,7 @@ case "$1" in
             force_stop
             log_end_msg $?
         fi
-       ;;
+        ;;
   restart|force-reload)
         log_daemon_msg "Restarting $DESC" "$NAME"
         stop_server
@@ -241,7 +241,7 @@ case "$1" in
         start_server
         running
         log_end_msg $?
-       ;;
+        ;;
   status)
 
         log_daemon_msg "Checking status of $DESC" "$NAME"
@@ -260,10 +260,10 @@ case "$1" in
         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
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2
+        exit 1
+        ;;
 esac
 
 exit 0
index d1acc89..943245c 100644 (file)
@@ -2,5 +2,4 @@ debian/changelog usr/src/modules/openvswitch-datapath/debian
 debian/control usr/src/modules/openvswitch-datapath/debian
 debian/compat usr/src/modules/openvswitch-datapath/debian
 debian/*.modules.in usr/src/modules/openvswitch-datapath/debian
-debian/rules usr/src/modules/openvswitch-datapath/debian
 _debian/openvswitch.tar.gz usr/src/modules/openvswitch-datapath
index 8c7e1ad..d83dabc 100755 (executable)
@@ -56,7 +56,7 @@ DODTIME=10              # Time to wait for the daemon to die, in seconds
                         
 # Include defaults if available
 if [ -f /etc/default/$NAME ] ; then
-       . /etc/default/$NAME
+    . /etc/default/$NAME
 fi
 
 set -e
@@ -105,7 +105,7 @@ stop_daemon() {
 
 case "$1" in
   start)
-       log_daemon_msg "Starting $DESC " "$NAME"
+        log_daemon_msg "Starting $DESC " "$NAME"
         # Check if it's running first
         if running ;  then
             log_progress_msg "apparently already running"
@@ -123,7 +123,7 @@ case "$1" in
             # a false positive (use 'status' for that)
             log_end_msg 1
         fi
-       ;;
+        ;;
   stop)
         log_daemon_msg "Stopping $DESC" "$NAME"
         if running ; then
@@ -147,7 +147,7 @@ case "$1" in
         start_daemon
         running
         log_end_msg $?
-       ;;
+        ;;
   status)
         log_daemon_msg "Checking status of $DESC" "$NAME"
         if running ;  then
@@ -165,10 +165,10 @@ case "$1" in
         log_warning_msg "cannot re-read the config file (use restart)."
         ;;
   *)
-       N=/etc/init.d/$NAME
-       echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
-       exit 1
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
+        exit 1
+        ;;
 esac
 
 exit 0
index da4ec69..3a7f292 100755 (executable)
 ### END INIT INFO
 
 PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-DAEMON=/usr/sbin/ovs-openflowd
-NAME=ovs-openflowd
-DESC=ovs-openflowd
+ovs_vswitchd=/usr/sbin/ovs-vswitchd
+ovsdb_server=/usr/bin/ovsdb-server
 
-test -x $DAEMON || exit 0
+(test -x $ovsdb_server && test -x $ovs_vswitchd) || exit 0
 
-NICIRA_OUI="002320"
-
-LOGDIR=/var/log/openvswitch
-PIDFILE=/var/run/$NAME.pid
-DHCLIENT_PIDFILE=/var/run/dhclient.of0.pid
 DODTIME=1                   # 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 ovs-openflowd defaults if available
-unset NETDEVS
-unset MODE
-unset SWITCH_IP
-unset CONTROLLER
-unset PRIVKEY
-unset CERT
-unset CACERT
-unset CACERT_MODE
-unset MGMT_VCONNS
-unset COMMANDS
-unset DAEMON_OPTS
+unset OVSDB_SERVER_OPTS
+unset OVS_VSWITCHD_OPTS
 unset CORE_LIMIT
-unset DATAPATH_ID
+unset ENABLE_MONITOR
 default=/etc/default/openvswitch-switch
 if [ -f $default ] ; then
-       . $default
+    . $default
 fi
 
+: ${ENABLE_MONITOR:=y}
+
 set -e
 
+# running_pid pid name
+#
+# Check if 'pid' is a process named 'name'
 running_pid()
 {
-    # Check if a given process pid's cmdline matches a given name
-    pid=$1
-    name=$2
+    local pid=$1 name=$2
     [ -z "$pid" ] && return 1 
     [ ! -d /proc/$pid ] &&  return 1
     cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1`
@@ -75,36 +63,47 @@ running_pid()
     esac
 }
 
+# running name
+#
+# Checks for a running process named 'name' by looking for a pidfile
+# named /var/run/${name}.pid
 running()
 {
-# Check if the process is running looking at /proc
-# (works for all users)
+    local name=$1
+    local pidfile=/var/run/${name}.pid
 
     # No pidfile, probably no daemon present
-    [ ! -f "$PIDFILE" ] && return 1
+    [ ! -f "$pidfile" ] && return 1
+
     # Obtain the pid and check it against the binary name
-    pid=`cat $PIDFILE`
-    running_pid $pid $NAME || return 1
+    pid=`cat $pidfile`
+    running_pid $pid $name || return 1
     return 0
 }
 
+# force_stop name
+#
+# Checks for a running process named 'name', by looking for a pidfile
+# named /var/run/${name}.pid, and then kills it and waits for it to
+# die.
 force_stop() {
-# Forcefully kill the process
-    [ ! -f "$PIDFILE" ] && return
-    if running ; then
-        kill -15 $pid
-        # Is it really dead?
+    local name=$1
+    local pidfile=/var/run/${name}.pid
+
+    [ ! -f "$pidfile" ] && return
+    if running $name; then
+        kill $pid
         [ -n "$DODTIME" ] && sleep "$DODTIME"s
-        if running ; then
-            kill -9 $pid
+        if running $name; then
+            kill -KILL $pid
             [ -n "$DODTIME" ] && sleep "$DODTIME"s
-            if running ; then
-                echo "Cannot kill $NAME (pid=$pid)!"
+            if running $name; then
+                echo "Cannot kill $name (pid=$pid)!"
                 exit 1
             fi
         fi
     fi
-    rm -f $PIDFILE
+    rm -f $pidfile
     return 0
 }
 
@@ -129,300 +128,191 @@ check_op() {
     fi
 }
 
-configure_ssl() {
-    if (test "$CACERT_MODE" != secure && test "$CACERT_MODE" != bootstrap) \
-       || test ! -e "$PRIVKEY" || test ! -e "$CERT" \
-       || (test ! -e "$CACERT" && test "$CACERT_MODE" != bootstrap); then
-        if test "$CACERT_MODE" != secure && test "$CACERT_MODE" != bootstrap
-        then
-            echo "CACERT_MODE is not set to 'secure' or 'bootstrap'"
-        fi
-        if test ! -e "$PRIVKEY"; then
-            echo "$PRIVKEY: private key missing" >&2
-        fi
-        if test ! -e "$CERT"; then
-            echo "$CERT: certificate for private key missing" >&2
-        fi
-        if test ! -e "$CACERT" && test "$CACERT_MODE" != bootstrap; then
-            echo "$CACERT: CA certificate missing (and CA certificate bootstrapping not enabled)" >&2
-        fi
-        echo "Run ovs-switch-setup (in the openvswitch-switch-config package) or edit /etc/default/openvswitch-switch to configure" >&2
-        if test "$MODE" = discovery; then
-            echo "You may also delete or rename $PRIVKEY to disable SSL requirement" >&2
-        fi
-        exit 1
-    fi
+# is_module_loaded module
+#
+# Returns 0 if 'module' is loaded, 1 otherwise.
 
-    SSL_OPTS="--private-key=$PRIVKEY --certificate=$CERT"
-    if test ! -e "$CACERT" && test "$CACERT_MODE" = bootstrap; then
-        SSL_OPTS="$SSL_OPTS --bootstrap-ca-cert=$CACERT"
-    else
-        SSL_OPTS="$SSL_OPTS --ca-cert=$CACERT"
-    fi
+is_module_loaded() {
+    local module=$1
+    grep -q "^$module " /proc/modules
 }
 
-check_int_var() {
-    eval value=\$$1
-    if test -n "$value"; then
-        if expr "X$value" : 'X[0-9][0-9]*$' > /dev/null 2>&1; then
-            if test $value -lt $2; then
-                echo "warning: The $1 option may not be set to a value below $2, treating as $2" >&2
-                eval $1=$2
-            fi
+# load_module module
+#
+# Loads 'module' into the running kernel, if it is not already loaded.
+load_module() {
+    local module=$1
+    echo -n "Loading $module: "
+    if is_module_loaded $module; then
+        echo "already loaded, nothing to do."
+    elif modprobe $module; then
+        echo "success."
+    else
+        echo "ERROR."
+        echo "$module has probably not been built for this kernel."
+        if ! test -d /usr/share/doc/openvswitch-datapath-source; then
+            echo "Install the openvswitch-datapath-source package, then read"
+            echo "/usr/share/doc/openvswitch-datapath-source/README.Debian"
         else
-            echo "warning: The $1 option must be set to a number, ignoring" >&2
-            unset $1
+            echo "For instructions, read"
+            echo "/usr/share/doc/openvswitch-datapath-source/README.Debian"
         fi
+        exit 1
     fi
 }
 
-check_new_option() {
-    case $DAEMON_OPTS in
-        *$1*)
-            echo "warning: The $1 option in DAEMON_OPTS may now be set with the $2 variable in $default.  The setting in DAEMON_OPTS will override the $2 variable, which will prevent the switch UI from configuring $1." >&2
-            ;;
-    esac
-}
-
-case "$1" in
-    start)
-        if test -z "$NETDEVS"; then
-            echo "$default: No network devices configured, switch disabled" >&2
-            echo "Run ovs-switch-setup (in the openvswitch-switch-config package) or edit /etc/default/openvswitch-switch to configure" >&2
-            exit 0
-        fi
-        if test "$MODE" = discovery; then
-            unset CONTROLLER
-        elif test "$MODE" = in-band || test "$MODE" = out-of-band; then
-            if test -z "$CONTROLLER"; then
-                echo "$default: No controller configured and not configured for discovery, switch disabled" >&2
-                echo "Run ovs-switch-setup (in the openvswitch-switch-config package) or edit /etc/default/openvswitch-switch to configure" >&2
-                exit 0
-            fi
-        else
-            echo "$default: MODE must set to 'discovery', 'in-band', or 'out-of-band'" >&2
-            echo "Run ovs-switch-setup (in the openvswitch-switch-config package) or edit /etc/default/openvswitch-switch to configure" >&2
-            exit 1
-        fi
-        : ${PRIVKEY:=/etc/openvswitch-switch/of0-privkey.pem}
-        : ${CERT:=/etc/openvswitch-switch/of0-cert.pem}
-        : ${CACERT:=/etc/openvswitch-switch/cacert.pem}
-        case $CONTROLLER in
-            '')
-                # Discovery mode.
-                if test -e "$PRIVKEY"; then
-                    configure_ssl
-                fi
-                ;;
-            tcp:*)
-                ;;
-            ssl:*)
-                configure_ssl
-                ;;
-            *)
-                echo "$default: CONTROLLER must be in the form 'ssl:IP[:PORT]' or 'tcp:IP[:PORT]' when not in discovery mode" >&2
-                echo "Run ovs-switch-setup (in the openvswitch-switch-config package) or edit /etc/default/openvswitch-switch to configure" >&2
-                exit 1
-        esac
-        case $DISCONNECTED_MODE in
-            ''|switch|drop) ;; 
-            *) echo "$default: warning: DISCONNECTED_MODE is not 'switch' or 'drop'" >&2 ;;
-        esac
-
-        check_int_var RATE_LIMIT 100
-        check_int_var INACTIVITY_PROBE 5
-        check_int_var MAX_BACKOFF 1
-
-        check_new_option --fail DISCONNECTED_MODE
-        check_new_option --stp STP
-        check_new_option --rate-limit RATE_LIMIT
-        check_new_option --inactivity INACTIVITY_PROBE
-        check_new_option --max-backoff MAX_BACKOFF
-        case $DAEMON_OPTS in
-            *--rate-limit*)
-                echo "$default: --rate-limit may now be set with RATE_LIMIT" >&2
-        esac
-
-        echo -n "Loading openvswitch_mod: "
-        if grep -q '^openvswitch_mod$' /proc/modules; then
-            echo "already loaded, nothing to do."
-        elif modprobe openvswitch_mod; then
+# unload_module module
+#
+# Unloads 'module' from the running kernel, if it is loaded.
+unload_module() {
+    local module=$1
+    echo -n "Unloading $module: "
+    if is_module_loaded $module; then
+        if rmmod $module; then
             echo "success."
         else
             echo "ERROR."
-            echo "openvswitch_mod has probably not been built for this kernel."
-            if ! test -d /usr/share/doc/openvswitch-datapath-source; then
-                echo "Install the openvswitch-datapath-source package, then read"
-                echo "/usr/share/doc/openvswitch-datapath-source/README.Debian"
-            else
-                echo "For instructions, read"
-                echo "/usr/share/doc/openvswitch-datapath-source/README.Debian"
-            fi
             exit 1
         fi
+    else
+        echo "not loaded, nothing to do."
+    fi
+}
 
-        for netdev in $NETDEVS; do
-            check_op "Removing IP address from $netdev" ifconfig $netdev 0.0.0.0
+unload_modules() {
+    if is_module_loaded openvswitch_mod; then
+        for dp in $(ovs-dpctl dump-dps); do
+            echo -n "Deleting datapath $dp: "
+            if ovs-dpctl del-dp $dp; then
+                echo "success."
+            else
+                echo "ERROR."
+            fi
         done
+    fi
+    unload_module openvswitch_mod
+    unload_module ip_gre_mod
+}
 
-        must_succeed "Creating datapath" ovs-dpctl add-dp of0 $NETDEVS
+case "$1" in
+    start)
+        load_module openvswitch_mod
+        unload_module ip_gre
+        load_module ip_gre_mod
 
-        xx='[0-9abcdefABCDEF][0-9abcdefABCDEF]'
-        case $DATAPATH_ID in
-            '')
-                # Check if the DMI System UUID contains a Nicira mac address
-                # that should be used for this datapath.  The UUID is assumed 
-                # to be RFC 4122 compliant.
-                DMIDECODE=`which dmidecode`
-                if [ -n $DMIDECODE ]; then
-                    UUID_MAC=`$DMIDECODE -s system-uuid | cut -d'-' -f 5`
-                    case $UUID_MAC in
-                        $NICIRA_OUI*)
-                            ifconfig of0 down
-                            must_succeed "Setting of0 MAC address to $UUID_MAC" ifconfig of0 hw ether $UUID_MAC
-                            ifconfig of0 up
-                            ;;
-                    esac
-                fi  
-                ;;
-            $xx:$xx:$xx:$xx:$xx:$xx)
-                ifconfig of0 down
-                must_succeed "Setting of0 MAC address to $DATAPATH_ID" ifconfig of0 hw ether $DATAPATH_ID
-                ifconfig of0 up
-                ;;
-            *)
-                echo "DATAPATH_ID is not a valid MAC address in the form XX:XX:XX:XX:XX:XX, ignoring" >&2
-                ;;
-        esac
+        if test -n "$CORE_LIMIT"; then
+            check_op "Setting core limit to $CORE_LIMIT" ulimit -c "$CORE_LIMIT"
+        fi
 
-        if test "$MODE" = in-band; then
-            if test "$SWITCH_IP" = dhcp; then
-                must_succeed "Temporarily disabling of0" ifconfig of0 down
-            else
-                COMMAND="ifconfig of0 $SWITCH_IP"
-                if test -n "$SWITCH_NETMASK"; then
-                    COMMAND="$COMMAND netmask $SWITCH_NETMASK"
-                fi
-                must_succeed "Configuring of0: $COMMAND" $COMMAND
-                if test -n "$SWITCH_GATEWAY"; then
-                    # This can fail because the route already exists,
-                    # so we don't insist that it succeed.
-                    COMMAND="route add default gw $SWITCH_GATEWAY"
-                    check_op "Adding default route: $COMMAND" $COMMAND
-                fi
-            fi
-        else
-            must_succeed "Disabling of0" ifconfig of0 down
+        # Create an empty configuration database if it doesn't exist.
+        if test ! -e /etc/openvswitch-switch/conf; then
+            # Create configuration database.
+            ovsdb-tool -vANY:console:emer \
+                create /etc/openvswitch-switch/conf \
+                /usr/share/openvswitch/vswitch.ovsschema
         fi
 
-        if test -n "$CORE_LIMIT"; then
-            check_op "Setting core limit to $CORE_LIMIT" ulimit -c "$CORE_LIMIT"
+        if test "$ENABLE_MONITOR" = y; then
+            monitor_opt=--monitor
+        else
+            monitor_opt=
         fi
 
-        # Compose ovs-openflowd options.
+        # Start ovsdb-server.
         set --
         set -- "$@" --verbose=ANY:console:emer --verbose=ANY:syslog:err
         set -- "$@" --log-file
-        set -- "$@" --detach --pidfile=$PIDFILE
-        for vconn in $MGMT_VCONNS; do
-            set -- "$@" --listen="$vconn"
-        done
-        if test -n "$COMMANDS"; then
-            set -- "$@" --command-acl="$COMMANDS"
-        fi
-        case $STP in
-            yes) set -- "$@" --stp ;;
-            no) set -- "$@" --no-stp ;;
-        esac
-        case $DISCONNECTED_MODE in
-            switch) set -- "$@" --fail=open ;;
-            drop) set -- "$@" --fail=closed ;;
-        esac
-        if test -n "$RATE_LIMIT"; then
-            set -- "$@" --rate-limit=$RATE_LIMIT
-        fi
-        if test -n "$INACTIVITY_PROBE"; then
-            set -- "$@" --inactivity-probe=$INACTIVITY_PROBE
-        fi
-        if test -n "$MAX_BACKOFF"; then
-            set -- "$@" --max-backoff=$MAX_BACKOFF
-        fi
-        set -- "$@" $SSL_OPTS $DAEMON_OPTS
-        if test "$MODE" = out-of-band; then
-            set -- "$@" --out-of-band
-        fi
-        set -- "$@" of0 "$CONTROLLER"
-       echo -n "Starting $DESC: "
-       start-stop-daemon --start --quiet --pidfile $PIDFILE \
-           --exec $DAEMON -- "$@"
-        if running; then
-            echo "$NAME."
+        set -- "$@" --detach --pidfile $monitor_opt
+        set -- "$@" --remote punix:/var/run/ovsdb-server
+        set -- "$@" /etc/openvswitch-switch/conf
+        set -- "$@" $OVSDB_SERVER_OPTS
+        echo -n "Starting ovsdb-server: "
+        start-stop-daemon --start --quiet --pidfile /var/run/ovsdb-server.pid \
+            --exec $ovsdb_server -- "$@"
+        if running ovsdb-server; then
+            echo "ovsdb-server."
         else
             echo " ERROR."
         fi
 
-        if test "$MODE" = in-band && test "$SWITCH_IP" = dhcp; then
-            echo -n "Starting dhclient on of0: "
-           start-stop-daemon --start --quiet --pidfile $DHCLIENT_PIDFILE \
-               --exec /sbin/dhclient -- -q -pf $DHCLIENT_PIDFILE of0
-            if running; then
-                echo "dhclient."
-            else
-                echo " ERROR."
-            fi
+        ovs-vsctl --no-wait init
+
+        # Start ovs-vswitchd.
+        set --
+        set -- "$@" --verbose=ANY:console:emer --verbose=ANY:syslog:err
+        set -- "$@" --log-file
+        set -- "$@" --detach --pidfile $monitor_opt
+        set -- "$@" unix:/var/run/ovsdb-server
+        set -- "$@" $OVS_VSWITCHD_OPTS
+        echo -n "Starting ovs-vswitchd: "
+        start-stop-daemon --start --quiet --pidfile /var/run/ovs-vswitchd.pid \
+            --exec $ovs_vswitchd -- "$@"
+        if running ovs-vswitchd; then
+            echo "ovs-vswitchd."
+        else
+            echo " ERROR."
         fi
-       ;;
+        ;;
     stop)
-        if test -e /var/run/dhclient.of0.pid; then
-           echo -n "Stopping dhclient on of0: "
-           start-stop-daemon --stop --quiet --oknodo \
-                --pidfile $DHCLIENT_PIDFILE --exec /sbin/dhclient
-           echo "dhclient."
-        fi            
-
-       echo -n "Stopping $DESC: "
-       start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE \
-           --exec $DAEMON
-       echo "$NAME."
+        echo -n "Stopping ovs-vswitchd: "
+        start-stop-daemon --stop --quiet --oknodo \
+            --pidfile /var/run/ovs-vswitchd.pid \
+            --exec $ovs_vswitchd
+        echo "ovs-vswitchd."
 
-        check_op "Deleting datapath" ovs-dpctl del-dp of0
-        check_op "Unloading kernel module" modprobe -r openvswitch_mod
-       ;;
+        echo -n "Stopping ovsdb-server: "
+        start-stop-daemon --stop --quiet --oknodo \
+            --pidfile /var/run/ovsdb-server.pid \
+            --exec $ovsdb_server
+        echo "ovsdb-server."
+        ;;
     force-stop)
-       echo -n "Forcefully stopping $DESC: "
-        force_stop
-        if ! running; then
-            echo "$NAME."
+        echo -n "Forcefully stopping ovs-vswitchd: "
+        force_stop ovs-vswitchd
+        if ! running ovs-vswitchd; then
+            echo "ovs-vswitchd."
         else
             echo " ERROR."
         fi
-       ;;
+
+        echo -n "Forcefully stopping ovsdb-server: "
+        force_stop ovsdb-server
+        if ! running ovsdb-server; then
+            echo "ovsdb-server."
+        else
+            echo " ERROR."
+        fi
+        ;;
+    unload)
+        unload_modules
+        ;;
     reload)
         ;;
     force-reload)
-       start-stop-daemon --stop --test --quiet --pidfile \
-           $PIDFILE --exec $DAEMON \
-           && $0 restart \
-           || exit 0
-       ;;
+        # Nothing to do, since ovs-vswitchd automatically reloads
+        # whenever its configuration changes, and ovsdb-server doesn't
+        # have anything to reload.
+        ;;
     restart)
         $0 stop || true
         $0 start
-       ;;
+        ;;
     status)
-        echo -n "$NAME is "
-        if running ;  then
-            echo "running"
-        else
-            echo " not running."
-            exit 1
-        fi
+        for daemon in ovs-vswitchd ovsdb-server; do
+            echo -n "$daemon is "
+            if running $daemon;  then
+                echo "running"
+            else
+                echo " not running."
+                exit 1
+            fi
+        done
         ;;
     *)
-       N=/etc/init.d/$NAME
-       echo "Usage: $N {start|stop|restart|force-reload|status|force-stop}" >&2
-       exit 1
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|restart|force-reload|status|force-stop|unload}" >&2
+        exit 1
+        ;;
 esac
 
 exit 0
index a0cf9bf..7b988da 100644 (file)
@@ -1,7 +1,6 @@
-_debian/utilities/ovs-openflowd usr/sbin
-_debian/utilities/ovs-dpctl usr/sbin
+_debian/ovsdb/ovsdb-server usr/bin
 _debian/utilities/ovs-discover usr/sbin
+_debian/utilities/ovs-dpctl usr/sbin
 _debian/utilities/ovs-kill usr/sbin
-_debian/utilities/ovs-ofctl usr/sbin
-debian/openvswitch/usr/share/openvswitch/commands/* usr/share/openvswitch/commands
-debian/commands/* usr/share/openvswitch/commands
+_debian/utilities/ovs-vsctl usr/sbin
+_debian/vswitchd/ovs-vswitchd usr/sbin
index 821503a..a899114 100644 (file)
@@ -1,5 +1,6 @@
-_debian/utilities/ovs-openflowd.8
+_debian/ovsdb/ovsdb-server.1
 _debian/utilities/ovs-discover.8
 _debian/utilities/ovs-dpctl.8
 _debian/utilities/ovs-kill.8
-_debian/utilities/ovs-ofctl.8
+_debian/utilities/ovs-vsctl.8
+_debian/vswitchd/ovs-vswitchd.8
index 74b52ba..4be5a30 100755 (executable)
@@ -33,6 +33,17 @@ case "$1" in
                 fi
             done
        fi
+
+        if /etc/init.d/openvswitch-switch status >/dev/null 2>&1; then
+            running=true
+            /etc/init.d/openvswitch-switch stop
+        else
+            running=false
+        fi
+
+        if $running; then
+            /etc/init.d/openvswitch-switch start
+        fi
         ;;
 
     abort-upgrade|abort-remove|abort-deconfigure)
index 19e8ebe..e4a736a 100755 (executable)
@@ -21,16 +21,19 @@ set -e
 
 case "$1" in
     purge)
-       rm -f /etc/default/openvswitch-switch
-       ;;
+        rm -f /etc/openvswitch-switch/conf
+        rm -f /etc/openvswitch-switch/.conf.~lock~
+        rm -f /etc/default/openvswitch-switch
+        rm -f /var/log/openvswitch/*
+        ;;
 
     remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
-       ;;
+        ;;
 
     *)
         echo "postrm called with unknown argument \`$1'" >&2
         exit 1
-       ;;
+        ;;
 esac
 
 # dh_installdeb will replace this with shell code automatically
index 44f9210..e36fc68 100644 (file)
 # uncomment them.  Afterward, the switch will come up
 # automatically at boot time.  It can be started immediately with
 #       /etc/init.d/openvswitch-switch start
-# Alternatively, use the ovs-switch-setup program (from the
-# openvswitch-switch-config package) to do everything automatically.
 
-# NETDEVS: Which network devices should the OpenFlow switch include?
-#
-# List the network devices that should become part of the OpenFlow
-# switch, separated by spaces.  At least two devices must be selected
-# for this machine to be a useful switch.  Unselecting all network
-# devices will disable the OpenFlow switch entirely.
-# 
-# The network devices that you select should not be configured with IP
-# or IPv6 addresses, even if the switch contacts the controller over
-# one of the selected network devices.  This is because a running
-# Open vSwitch switch takes over network devices at a low level: they
-# become part of the switch and cannot be used for other purposes.
-#NETDEVS=""
-
-# MODE: The OpenFlow switch has three modes that determine how it
-# reaches the controller:
-#
-# * in-band with discovery: A single network is used for OpenFlow
-#   traffic and other data traffic; that is, the switch contacts the
-#   controller over one of the network devices selected as OpenFlow
-#   switch ports.  The switch automatically determines the location of
-#   the controller using a DHCP request with an OpenFlow-specific
-#   vendor option.  This is the most common case.
-# 
-# * in-band: As above, but the location of the controller is manually
-#   configured.
-# 
-# * out-of-band: OpenFlow traffic uses a network separate from the
-#   data traffic that it controls.  If this is the case, the control
-#   network must already be configured on a network device other than
-#   one of those selected as an Open vSwitch switch port in the previous
-#   question.
-#
-# Set MODE to 'discovery', 'in-band', or 'out-of-band' for these
-# respective cases.
-MODE=discovery
-
-# SWITCH_IP: In 'in-band' mode, the switch's IP address may be
-# configured statically or dynamically:
-# 
-# * For static configuration, specify the switch's IP address as a
-#   string.  In this case you may also set SWITCH_NETMASK and
-#   SWITCH_GATEWAY appropriately (see below).
-# 
-# * For dynamic configuration with DHCP (the most common case),
-#   specify "dhcp".  Configuration with DHCP will only work reliably
-#   if the network topology allows the switch to contact the DHCP
-#   server before it connects to the OpenFlow controller.
-#
-# This setting has no effect unless MODE is set to 'in-band'.
-SWITCH_IP=dhcp
-
-# SWITCH_NETMASK: IP netmask to use in 'in-band' mode when the switch
-# IP address is not 'dhcp'.
-#SWITCH_NETMASK=255.255.255.0
-
-# SWITCH_GATEWAY: IP gateway to use in 'in-band' mode when the switch
-# IP address is not 'dhcp'.
-#SWITCH_GATEWAY=192.168.1.1
-
-# CONTROLLER: Location of controller.
-# One of the following formats:
-#  tcp:IP[:PORT]         via TCP to PORT (default: 6633) at IP
-#  ssl:IP[:PORT]         via SSL to PORT (default: 6633) at IP
-# The default below assumes that the controller is running locally.
-# This setting has no effect when MODE is set to 'discovery'.
-#CONTROLLER="tcp:127.0.0.1"
-
-# PRIVKEY: Name of file containing switch's private key.
-# Required if SSL enabled.
-#PRIVKEY=/etc/openvswitch-switch/of0-privkey.pem
-
-# CERT: Name of file containing certificate for private key.
-# Required if SSL enabled.
-#CERT=/etc/openvswitch-switch/of0-cert.pem
-
-# CACERT: Name of file containing controller CA certificate.
-# Required if SSL enabled.
-#CACERT=/etc/openvswitch-switch/cacert.pem
+# OVSDB_SERVER_OPTS: Additional options to pass to ovsdb-server,
+# e.g. "--fail=open"
+OVSDB_SERVER_OPTS=
 
-# CACERT_MODE: Two modes are available:
-#
-# * secure: The controller CA certificate named in CACERT above must exist.
-#   (You must copy it manually from the PKI server or another trusted source.)
-#
-# * bootstrap: If the controller CA certificate named in CACERT above does
-#   not exist, the switch will obtain it from the controller the first time
-#   it connects and save a copy to the file named in CACERT.  This is insecure,
-#   in the same way that initial connections with ssh are insecure, but
-#   it is convenient.
-# 
-# Set CACERT_MODE to 'secure' or 'bootstrap' for these respective cases.
-#CACERT_MODE=secure
-
-# MGMT_VCONNS: List of vconns (space-separated) on which ovs-openflowd
-# should listen for management connections from ovs-ofctl, etc.
-# openvswitch-switchui by default connects to
-# unix:/var/run/ovs-openflowd.mgmt, so do not disable this if you want to
-# use openvswitch-switchui.
-MGMT_VCONNS="punix:/var/run/ovs-openflowd.mgmt"
-
-# COMMANDS: Access control list for the commands that can be executed
-# remotely over the OpenFlow protocol, as a comma-separated list of
-# shell glob patterns.  Negative patterns (beginning with !) act as a
-# blacklist.  To be executable, a command name must match one positive
-# pattern and not match any negative patterns.
-#COMMANDS="reboot,update"
-
-# DISCONNECTED_MODE: Switch behavior when attempts to connect to the
-# controller repeatedly fail, either 'switch', to act as an L2 switch
-# in this case, or 'drop', to drop all packets (except those necessary
-# to connect to the controller).  If unset, the default is 'drop'.
-#DISCONNECTED_MODE=switch
-
-# STP: Enable or disabled 802.1D-1998 Spanning Tree Protocol.  Set to
-# 'yes' to enable STP, 'no' to disable it.  If unset, ovs-openflowd's
-# current default is 'no' (but this may change in the future).
-#STP=no
-
-# RATE_LIMIT: Maximum number of received frames, that do not match any
-# existing switch flow, to forward up to the controller per second.
-# The valid range is 100 and up.  If unset, this rate will not be
-# limited.
-#RATE_LIMIT=1000
-
-# INACTIVITY_PROBE: The maximum number of seconds of inactivity on the
-# controller connection before ovs-openflowd sends an inactivity probe
-# message to the controller.  The valid range is 5 and up.  If unset,
-# ovs-openflowd defaults to 5 seconds.
-#INACTIVITY_PROBE=5
-
-# MAX_BACKOFF: The maximum time that ovs-openflowd will wait between
-# attempts to connect to the controller.  The valid range is 1 and up.
-# If unset, ovs-openflowd defaults to 8 seconds.
-#MAX_BACKOFF=8
-
-# DAEMON_OPTS: Additional options to pass to ovs-openflowd, e.g. "--fail=open"
-DAEMON_OPTS=""
+# OVS_VSWITCHD_OPTS: Additional options to pass to ovs-openflowd,
+# e.g. "--fail=open"
+OVS_VSWITCHD_OPTS=
 
 # CORE_LIMIT: Maximum size for core dumps.
 #
@@ -153,13 +20,6 @@ DAEMON_OPTS=""
 # core files regardless of size.
 #CORE_LIMIT=unlimited
 
-# DATAPATH_ID: Identifier for this switch.
-#
-# By default, the switch checks if the DMI System UUID contains a Nicira 
-# mac address to use as a datapath ID.  If not, then the switch generates 
-# a new, random datapath ID every time it starts up.  By setting this
-# value, the supplied datapath ID will always be used.
-#
-# Set DATAPATH_ID to a MAC address in the form XX:XX:XX:XX:XX:XX where each
-# X is a hexadecimal digit (0-9 or a-f).
-#DATAPATH_ID=XX:XX:XX:XX:XX:XX 
+# ENABLE_MONITOR: If 'y' then monitor daemon processes and restart them
+#    if they die due to an error signal.
+# ENABLE_MONITOR=y
index 7a02c5e..7ecb14a 100755 (executable)
@@ -56,7 +56,7 @@ DODTIME=10              # Time to wait for the server to die, in seconds
                         
 # Include defaults if available
 if [ -f /etc/default/$NAME ] ; then
-       . /etc/default/$NAME
+    . /etc/default/$NAME
 fi
 
 set -e
@@ -99,10 +99,10 @@ start_server() {
 
     # Wait up to 3 seconds for the daemon to start.
     for i in 1 2 3; do
-       if running; then
-           break
-       fi
-       sleep 1
+        if running; then
+        break
+    fi
+    sleep 1
     done
 }
 
@@ -112,27 +112,27 @@ stop_server() {
 
 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
+    [ ! -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"
+        log_daemon_msg "Starting $DESC " "$NAME"
         # Check if it's running first
         if running ;  then
             log_progress_msg "apparently already running"
@@ -150,7 +150,7 @@ case "$1" in
             # a false positive (use 'status' for that)
             log_end_msg 1
         fi
-       ;;
+        ;;
   stop)
         log_daemon_msg "Stopping $DESC" "$NAME"
         if running ; then
@@ -173,7 +173,7 @@ case "$1" in
             force_stop
             log_end_msg $?
         fi
-       ;;
+        ;;
   restart|force-reload)
         log_daemon_msg "Restarting $DESC" "$NAME"
         stop_server
@@ -182,7 +182,7 @@ case "$1" in
         start_server
         running
         log_end_msg $?
-       ;;
+        ;;
   status)
 
         log_daemon_msg "Checking status of $DESC" "$NAME"
@@ -201,10 +201,10 @@ case "$1" in
         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
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2
+        exit 1
+        ;;
 esac
 
 exit 0
index f2872c8..0cfb290 100644 (file)
@@ -1,2 +1,3 @@
 _debian/extras/ezio/ezio-term usr/sbin
 _debian/extras/ezio/ovs-switchui usr/bin
+debian/reconfigure usr/share/openvswitch-switchui
index b1c0ec5..e254115 100755 (executable)
@@ -56,7 +56,7 @@ DODTIME=10              # Time to wait for the daemon to die, in seconds
                         
 # Include defaults if available
 if [ -f /etc/default/$NAME ] ; then
-       . /etc/default/$NAME
+    . /etc/default/$NAME
 fi
 
 set -e
@@ -107,7 +107,7 @@ stop_daemon() {
 
 case "$1" in
   start)
-       log_daemon_msg "Starting $DESC " "$NAME"
+        log_daemon_msg "Starting $DESC " "$NAME"
         # Check if it's running first
         if running ;  then
             log_progress_msg "apparently already running"
@@ -125,7 +125,7 @@ case "$1" in
             # a false positive (use 'status' for that)
             log_end_msg 1
         fi
-       ;;
+        ;;
   stop)
         log_daemon_msg "Stopping $DESC" "$NAME"
         if running ; then
@@ -149,7 +149,7 @@ case "$1" in
         start_daemon
         running
         log_end_msg $?
-       ;;
+        ;;
   status)
         log_daemon_msg "Checking status of $DESC" "$NAME"
         if running ;  then
@@ -167,10 +167,10 @@ case "$1" in
         log_warning_msg "cannot re-read the config file (use restart)."
         ;;
   *)
-       N=/etc/init.d/$NAME
-       echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
-       exit 1
-       ;;
+        N=/etc/init.d/$NAME
+        echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
+        exit 1
+        ;;
 esac
 
 exit 0
index abccbc4..bf9953f 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: dev@openvswitch.org\n"
-"POT-Creation-Date: 2009-05-11 13:38-0700\n"
+"POT-Creation-Date: 2009-12-15 09:36-0800\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -188,8 +188,9 @@ msgstr ""
 #. Description
 #: ../openvswitch-switch-config.templates:6001
 msgid ""
-"Ensure that the OpenFlow DHCP server is properly configured.  See ovs-openflowd(8) "
-"for instructions on how to configure a DHCP server for controller discovery."
+"Ensure that the OpenFlow DHCP server is properly configured.  See ovs-"
+"openflowd(8) for instructions on how to configure a DHCP server for "
+"controller discovery."
 msgstr ""
 
 #. Type: boolean
@@ -293,9 +294,9 @@ msgstr ""
 #. Description
 #: ../openvswitch-switch-config.templates:11001
 msgid ""
-"The controller location must be specifed as \"ssl:IP[:PORT]\" to connect "
-"to the controller over SSL (recommended for security) or \"tcp:IP[:PORT]\" "
-"to connect over cleartext TCP."
+"The controller location must be specifed as \"ssl:IP[:PORT]\" to connect to "
+"the controller over SSL (recommended for security) or \"tcp:IP[:PORT]\" to "
+"connect over cleartext TCP."
 msgstr ""
 
 #. Type: string
index 4cf5b38..0d0abf1 100755 (executable)
 # Modified to make a template file for a multi-binary package with separated
 # build-arch and build-indep targets  by Bill Allombert 2001
 
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-# This has to be exported to make some magic below work.
-export DH_OPTIONS
-
-# prefix of the target package name
-PACKAGE=openvswitch-datapath-module
-# modifieable for experiments or debugging m-a
-MA_DIR ?= /usr/share/modass
-# load generic variable handling
--include $(MA_DIR)/include/generic.make
-# load default rules
--include $(MA_DIR)/include/common-rules.make
-
-DATAPATH_CONFIGURE_OPTS =
-
 # Official build number.  Leave set to 0 if not an official build.
 BUILD_NUMBER = 0
 
@@ -46,6 +29,7 @@ configure-stamp:
        dh_testdir
        test -e configure || ./boot.sh
        test -d _debian || mkdir _debian
+       echo $$CC
        cd _debian && ( \
                test -e Makefile || \
                ../configure --prefix=/usr --localstatedir=/var --enable-ssl \
@@ -78,31 +62,6 @@ clean:
        dh_clean 
        debconf-updatepo
 
-kdist_clean:
-       dh_clean
-       rm -rf openvswitch
-
-kdist_config: prep-deb-files
-
-binary-modules: DSTDIR = $(CURDIR)/debian/$(PKGNAME)/lib/modules/$(KVERS)
-binary-modules: prep-deb-files
-       dh_testdir
-       dh_testroot
-       dh_clean -k
-       tar xzf openvswitch.tar.gz
-       cd openvswitch && ./configure --with-l26=$(KSRC) $(DATAPATH_CONFIGURE_OPTS) --with-build-number=$(BUILD_NUMBER)
-       cd openvswitch && $(MAKE) -C datapath/linux-2.6
-       install -d -m755 $(DSTDIR)
-       install -m644 openvswitch/datapath/linux-2.6/*_mod.ko $(DSTDIR)/
-       dh_installdocs
-       dh_installchangelogs
-       dh_compress
-       dh_fixperms
-       dh_installdeb
-       dh_gencontrol
-       dh_md5sums
-       dh_builddeb --destdir=$(DEB_DESTDIR)
-
 install: install-indep install-arch
 install-indep: build-indep
        dh_testdir
@@ -110,6 +69,10 @@ install-indep: build-indep
        dh_clean -k -i 
        dh_installdirs -i
        dh_install -i
+       sed 's/^BUILD_NUMBER = .*/BUILD_NUMBER = $(BUILD_NUMBER)/' \
+               < debian/rules.modules \
+               > debian/openvswitch-datapath-source/usr/src/modules/openvswitch-datapath/debian/rules
+       chmod 755 debian/openvswitch-datapath-source/usr/src/modules/openvswitch-datapath/debian/rules
        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
diff --git a/debian/rules.modules b/debian/rules.modules
new file mode 100755 (executable)
index 0000000..4f39cee
--- /dev/null
@@ -0,0 +1,41 @@
+#! /usr/bin/make -f
+
+PACKAGE=openvswitch-datapath-module
+MA_DIR ?= /usr/share/modass
+-include $(MA_DIR)/include/generic.make
+-include $(MA_DIR)/include/common-rules.make
+
+DATAPATH_CONFIGURE_OPTS =
+
+# Official build number.  Leave set to 0 if not an official build.
+BUILD_NUMBER = 0
+
+kdist_clean:
+       dh_testdir      
+       dh_testroot
+       dh_clean
+       rm -rf openvswitch
+
+.PHONY: kdist_config
+kdist_config: prep-deb-files
+
+.PHONY: binary-modules
+binary-modules: DSTDIR = $(CURDIR)/debian/$(PKGNAME)/lib/modules/$(KVERS)
+binary-modules: prep-deb-files
+       dh_testdir
+       dh_testroot
+       dh_clean -k
+       tar xzf openvswitch.tar.gz
+       cd openvswitch && ./configure --with-l26=$(KSRC) $(DATAPATH_CONFIGURE_OPTS) --with-build-number=$(BUILD_NUMBER)
+       cd openvswitch && $(MAKE) -C datapath/linux-2.6
+       install -d -m755 $(DSTDIR)
+       install -m644 openvswitch/datapath/linux-2.6/*_mod.ko $(DSTDIR)/
+       dh_installdocs
+       dh_installchangelogs
+       dh_compress
+       dh_fixperms
+       dh_installdeb
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb --destdir=$(DEB_DESTDIR)
+
index 7c7db24..eae11cd 100644 (file)
@@ -25,8 +25,6 @@ install-data-hook:
 
 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 \
index 846ccfd..cedc5c9 100644 (file)
@@ -26,8 +26,8 @@
 #include <stdlib.h>
 #include <term.h>
 #include <unistd.h>
+#include "byteq.h"
 #include "command-line.h"
-#include "extras/ezio/byteq.h"
 #include "extras/ezio/tty.h"
 #include "extras/ezio/vt.h"
 #include "daemon.h"
@@ -96,6 +96,7 @@ main(int argc, char *argv[])
     int retval;
     int i;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
     time_init();
     vlog_init();
index cd1f352..4f0843f 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -144,6 +144,7 @@ main(int argc, char *argv[])
     long long int last_key_time = 0;
     int repeat_count = 0;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
     time_init();
     vlog_init();
@@ -165,7 +166,7 @@ main(int argc, char *argv[])
     daemonize();
 
     initialize_terminal();
-    fatal_signal_add_hook(restore_terminal, NULL, true);
+    fatal_signal_add_hook(restore_terminal, NULL, NULL, true);
 
     msg = NULL;
     countdown = 0;
@@ -1247,7 +1248,7 @@ allocate_message(struct message **msgp)
 {
     if (!*msgp) {
         /* Allocate and initialize message. */
-        *msgp = xcalloc(1, sizeof **msgp);
+        *msgp = xzalloc(sizeof **msgp);
         (*msgp)->index = n_messages;
 
         /* Add to list of messages. */
@@ -2079,7 +2080,7 @@ save_config(const struct svec *settings)
     }
 
     svec_init(&argv);
-    svec_add(&argv, "/usr/share/openvswitch/commands/reconfigure");
+    svec_add(&argv, "/usr/share/openvswitch-switchui/reconfigure");
     svec_append(&argv, settings);
     svec_terminate(&argv);
     ok = run_and_report_failure(argv.names, "Save failed");
@@ -2457,7 +2458,7 @@ abbreviate_netdevs(const struct svec *netdevs, struct ds *abbrev)
 static void
 choose_netdevs(struct svec *choices)
 {
-    struct svec netdevs;
+    struct svec netdevs = SVEC_EMPTY_INITIALIZER;
     struct menu menu;
     size_t i;
 
@@ -2478,7 +2479,7 @@ choose_netdevs(struct svec *choices)
             continue;
         }
 
-        retval = netdev_open(name, NETDEV_ETH_TYPE_NONE, &netdev);
+        retval = netdev_open_default(name, &netdev);
         if (!retval) {
             bool exclude = netdev_get_in4(netdev, NULL, NULL) == 0;
             netdev_close(netdev);
index 709b802..9b05480 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks, Inc.
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -263,7 +263,7 @@ tty_set_raw_mode(int fd, speed_t speed)
             return errno;
         }
         s->tios = tios;
-        fatal_signal_add_hook(restore_termios, s, true);
+        fatal_signal_add_hook(restore_termios, NULL, s, true);
 
         tios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
                           | INLCR | IGNCR | ICRNL | IXON);
diff --git a/include/openflow/.gitignore b/include/openflow/.gitignore
new file mode 100644 (file)
index 0000000..db736db
--- /dev/null
@@ -0,0 +1 @@
+/openflow.h.stamp
index d473155..b8dbc71 100644 (file)
@@ -1,4 +1,21 @@
 noinst_HEADERS += \
-       include/openflow/openflow-mgmt.h \
        include/openflow/nicira-ext.h \
        include/openflow/openflow.h
+
+if HAVE_PYTHON
+all-local: include/openflow/openflow.h.stamp
+include/openflow/openflow.h.stamp: \
+       include/openflow/openflow.h build-aux/check-structs
+       $(PYTHON) $(srcdir)/build-aux/check-structs $(srcdir)/include/openflow/openflow.h
+       touch $@
+DISTCLEANFILES += include/openflow/openflow.h.stamp
+
+all-local: include/openflow/nicira-ext.h.stamp
+include/openflow/nicira-ext.h.stamp: include/openflow/openflow.h include/openflow/nicira-ext.h build-aux/check-structs
+       $(PYTHON) $(srcdir)/build-aux/check-structs $(srcdir)/include/openflow/openflow.h $(srcdir)/include/openflow/nicira-ext.h
+       touch $@
+DISTCLEANFILES += include/openflow/nicira-ext.h.stamp
+endif
+
+EXTRA_DIST += build-aux/check-structs
+
index c4c6062..7232d57 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -37,30 +37,14 @@ enum nicira_type {
      * pairs in the form "key=value\n". */
     NXT_STATUS_REPLY,
 
-    /* Configure an action.  Most actions do not require configuration
-     * beyond that supplied in the actual action call. */
-    NXT_ACT_SET_CONFIG,
-
-    /* Get configuration of action. */
-    NXT_ACT_GET_CONFIG,
-
-    /* Remote command execution.  The request body is a sequence of strings
-     * delimited by null bytes.  The first string is a command name.
-     * Subsequent strings are command arguments. */
-    NXT_COMMAND_REQUEST,
-
-    /* Remote command execution reply, sent when the command's execution
-     * completes.  The reply body is struct nx_command_reply. */
-    NXT_COMMAND_REPLY,
-
     /* No longer used. */
+    NXT_ACT_SET_CONFIG__OBSOLETE,
+    NXT_ACT_GET_CONFIG__OBSOLETE,
+    NXT_COMMAND_REQUEST__OBSOLETE,
+    NXT_COMMAND_REPLY__OBSOLETE,
     NXT_FLOW_END_CONFIG__OBSOLETE,
-
-    /* No longer used. */
     NXT_FLOW_END__OBSOLETE,
-
-    /* Management protocol.  See "openflow-mgmt.h". */
-    NXT_MGMT,
+    NXT_MGMT__OBSOLETE,
 };
 
 struct nicira_header {
@@ -68,7 +52,7 @@ struct nicira_header {
     uint32_t vendor;            /* NX_VENDOR_ID. */
     uint32_t subtype;           /* One of NXT_* above. */
 };
-OFP_ASSERT(sizeof(struct nicira_header) == sizeof(struct ofp_vendor_header) + 4);
+OFP_ASSERT(sizeof(struct nicira_header) == 16);
 
 
 enum nx_action_subtype {
@@ -97,24 +81,4 @@ struct nx_action_header {
 };
 OFP_ASSERT(sizeof(struct nx_action_header) == 16);
 
-/* Status bits for NXT_COMMAND_REPLY. */
-enum {
-    NXT_STATUS_EXITED = 1 << 31,   /* Exited normally. */
-    NXT_STATUS_SIGNALED = 1 << 30, /* Exited due to signal. */
-    NXT_STATUS_UNKNOWN = 1 << 29,  /* Exited for unknown reason. */
-    NXT_STATUS_COREDUMP = 1 << 28, /* Exited with core dump. */
-    NXT_STATUS_ERROR = 1 << 27,    /* Command could not be executed. */
-    NXT_STATUS_STARTED = 1 << 26,  /* Command was started. */
-    NXT_STATUS_EXITSTATUS = 0xff,  /* Exit code mask if NXT_STATUS_EXITED. */
-    NXT_STATUS_TERMSIG = 0xff,     /* Signal number if NXT_STATUS_SIGNALED. */
-};
-
-/* NXT_COMMAND_REPLY. */
-struct nx_command_reply {
-    struct nicira_header nxh;
-    uint32_t status;            /* Status bits defined above. */
-    /* Followed by any number of bytes of process output. */
-};
-OFP_ASSERT(sizeof(struct nx_command_reply) == 20);
-
 #endif /* openflow/nicira-ext.h */
diff --git a/include/openflow/openflow-mgmt.h b/include/openflow/openflow-mgmt.h
deleted file mode 100644 (file)
index 04017d4..0000000
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (c) 2009 Nicira Networks.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef OPENFLOW_OPENFLOW_MGMT_H
-#define OPENFLOW_OPENFLOW_MGMT_H 1
-
-#include "openflow/nicira-ext.h"
-
-enum ofmp_type {
-    OFMPT_CAPABILITY_REQUEST,
-    OFMPT_CAPABILITY_REPLY,
-    OFMPT_RESOURCES_REQUEST,
-    OFMPT_RESOURCES_UPDATE,
-    OFMPT_CONFIG_REQUEST,
-    OFMPT_CONFIG_UPDATE,
-    OFMPT_CONFIG_UPDATE_ACK,
-    OFMPT_ERROR,
-    OFMPT_EXTENDED_DATA
-};
-
-/* Header on all OpenFlow management packets. */
-struct ofmp_header {
-    struct nicira_header header;
-    uint16_t type;           /* One of OFMPT_* above. */
-    uint8_t pad[2];
-};
-OFP_ASSERT(sizeof(struct ofmp_header) == sizeof(struct nicira_header) + 4);
-
-
-/* Generic TLV header. */
-struct ofmp_tlv {
-    uint16_t type;        /* Type of value (one of OFMPTLV_*). */
-    uint16_t len;         /* Length of TLV (includes this header). */
-    uint8_t data[0];      /* Value of data as defined by type and length. */
-};
-OFP_ASSERT(sizeof(struct ofmp_tlv) == 4);
-
-/* Universal TLV terminator.  Used to indicate end of TLV list. */
-struct ofmp_tlv_end {
-    uint16_t type;        /* Type is 0. */
-    uint16_t len;         /* Length is 4. */
-};
-OFP_ASSERT(sizeof(struct ofmp_tlv_end) == 4);
-
-
-/* Bitmask of capability description styles. */
-enum ofmp_capability_format {
-    OFMPCAF_SIMPLE  = 0 << 0,             /* "ovs-vswitchd.conf" style. */
-};
-
-/* Body of capbility request.
- *
- * OFMPT_CAPABILITY_REQUEST (controller -> switch) */
-struct ofmp_capability_request {
-    struct ofmp_header header;
-    uint32_t format;                      /* One of OFMPCAF_*. */
-};
-OFP_ASSERT(sizeof(struct ofmp_capability_request) == 24);
-
-/* Body of reply to capability request.  
- *
- * OFMPT_CAPABILITY_REPLY (switch -> controller). */
-struct ofmp_capability_reply {
-    struct ofmp_header header;
-    uint32_t format;                      /* One of OFMPCAF_*. */
-    uint64_t mgmt_id;                     /* Management ID. */
-    uint8_t data[0];
-};
-OFP_ASSERT(sizeof(struct ofmp_capability_reply) == 32);
-
-
-/* Resource TLV for datapath description. */
-struct ofmptsr_dp {
-    uint16_t type;                        /* OFMPTSR_DP. */
-    uint16_t len;                         /* 32. */
-    uint8_t pad[4];
-    uint64_t dp_id;                       /* Datapath ID. */
-    uint8_t name[OFP_MAX_PORT_NAME_LEN];  /* Null-terminated name. */
-};
-OFP_ASSERT(sizeof(struct ofmptsr_dp) == 32);
-
-/* UUIDs will be passed around as *non-terminated* strings in their
- * canonical form (e.g., 550e8400-e29b-41d4-a716-446655440000).
- */
-#define OFMP_UUID_LEN 36
-
-/* Resource TLV for XenServer UUIDs associated with this datapath. */
-struct ofmptsr_dp_uuid {
-    uint16_t type;                        /* OFMPTSR_DP_UUID. */
-    uint16_t len;                         /* Length. */
-    uint8_t pad[4];
-    uint64_t dp_id;                       /* Datapath ID. */
-    uint8_t uuid_list[0];                 /* List of UUIDs associated with 
-                                           * this datapath. */
-};
-OFP_ASSERT(sizeof(struct ofmptsr_dp_uuid) == 16);
-
-/* Resource TLV for XenServer UUID associated with this managment 
- * instance. 
- */
-struct ofmptsr_mgmt_uuid {
-    uint16_t type;                        /* OFMPTSR_MGMT_UUID. */
-    uint16_t len;                         /* 52. */
-    uint8_t pad[4];
-    uint64_t mgmt_id;                     /* Management ID. */
-    uint8_t uuid[OFMP_UUID_LEN];          /* System UUID. */
-    uint8_t pad2[4];                      /* Pad for 64-bit systems. */
-};
-OFP_ASSERT(sizeof(struct ofmptsr_mgmt_uuid) == 56);
-
-/* Resource TLV for details about this XenServer vif. */
-struct ofmptsr_vif {
-    uint16_t type;                        /* OFMPTSR_VIF. */
-    uint16_t len;                         /* 136. */
-    uint8_t name[OFP_MAX_PORT_NAME_LEN];  /* Null-terminated name. */
-    uint8_t vif_uuid[OFMP_UUID_LEN];      /* VIF UUID. */
-    uint8_t vm_uuid[OFMP_UUID_LEN];       /* VM UUID. */
-    uint8_t net_uuid[OFMP_UUID_LEN];      /* Network UUID. */
-    uint64_t vif_mac;                     /* Management ID. */
-};
-OFP_ASSERT(sizeof(struct ofmptsr_vif) == 136);
-
-/* TLV types for switch resource descriptions. */
-enum ofmp_switch_resources {
-    OFMPTSR_END = 0,                      /* Terminator. */
-    OFMPTSR_DP,                           /* Datapath. */
-    OFMPTSR_DP_UUID,                      /* Xen: datapath uuid's. */
-    OFMPTSR_MGMT_UUID,                    /* Xen: management uuid. */
-    OFMPTSR_VIF,                          /* Xen: vif details. */
-};
-
-/* Body of resources request.
- *
- * OFMPT_RESOURCES_REQUEST (controller -> switch) */
-struct ofmp_resources_request {
-    struct ofmp_header header;
-};
-
-/* Body of capbility update.  Sent in response to a resources request or
- * sent asynchronously when resources change on the switch. 
- *
- * OFMPT_RESOURCES_UPDATE (switch -> controller) */
-struct ofmp_resources_update {
-    struct ofmp_header header;
-    uint8_t data[0];
-};
-OFP_ASSERT(sizeof(struct ofmp_resources_update) == 20);
-
-
-/* Bitmask of capability description styles. */
-enum ofmp_config_format {
-    OFMPCOF_SIMPLE  = 0 << 0,           /* "ovs-vswitchd.conf" style. */
-};
-
-#define CONFIG_COOKIE_LEN 20
-
-/* Body of configuration request.
- *
- * OFMPT_CONFIG_REQUEST (controller -> switch) */
-struct ofmp_config_request {
-    struct ofmp_header header;
-    uint32_t format;                    /* One of OFMPCOF_*. */
-};
-OFP_ASSERT(sizeof(struct ofmp_config_request) == 24);
-
-/* Body of configuration update.  Sent in response to a configuration 
- * request from the controller.  May be sent asynchronously by either
- * the controller or switch to modify configuration or notify of
- * changes, respectively.  If sent by the controller, the switch must
- * respond with a OFMPT_CONFIG_UPDATE_ACK.
- *
- * OFMPT_CONFIG_UPDATE (switch <-> controller) */
-struct ofmp_config_update {
-    struct ofmp_header header;
-    uint32_t format;                    /* One of OFMPCOF_*. */
-    uint8_t cookie[CONFIG_COOKIE_LEN];  /* Cookie of config attempting to be
-                                         * replaced by this update. */
-    uint8_t data[0];
-};
-OFP_ASSERT(sizeof(struct ofmp_config_update) == 44);
-
-/* Bitmask of configuration update ack flags. */
-enum ofmp_config_update_ack_flags {
-    OFMPCUAF_SUCCESS = 1 << 0,          /* Config succeeded. */
-};
-
-/* Body of configuration update ack.  Sent in response to a configuration 
- * udpate request.
- *
- * OFMPT_CONFIG_UPDATE_ACK (switch -> controller) */
-struct ofmp_config_update_ack {
-    struct ofmp_header header;
-    uint32_t format;                    /* One of OFMPCOF_*. */
-    uint32_t flags;                     /* One of OFMPCUAF_*. */
-    uint8_t cookie[CONFIG_COOKIE_LEN];  /* Cookie of current configuration 
-                                         * being used in the switch. */
-};
-OFP_ASSERT(sizeof(struct ofmp_config_update_ack) == 48);
-
-/* Values for 'type' in ofmp_error_msg. */
-enum ofmp_error_type {
-    OFMPET_BAD_CONFIG                   /* Problem with configuration. */
-};
-
-/* ofmp_error_msg 'code' values for OFMPET_BAD_CONFIG.  'data' contains
- * at least the first 64 bytes of the failed request. */
-enum ofmp_bad_config_code {
-    OFMPBCC_BUSY,                       /* Config updating, try again. */
-    OFMPBCC_OLD_COOKIE,                 /* Config has changed. */
-};
-
-/* Body of error message.  May be sent by either the switch or the
- * controller to indicate some error condition.
- *
- * OFMPT_ERROR (switch <-> controller) */
-struct ofmp_error_msg {
-    struct ofmp_header header;
-
-    uint16_t type;            /* One of OFMPET_*. */
-    uint16_t code;            /* Code depending on 'type'. */
-    uint8_t data[0];          /* Variable-length data.  Interpreted based 
-                                 on the type and code. */
-};
-OFP_ASSERT(sizeof(struct ofmp_error_msg) == 24);
-
-/* Bitmask of extended data message flags. */
-enum ofmp_extended_data_flags {
-    OFMPEDF_MORE_DATA = 1 << 0,         /* More data follows. */
-};
-
-/* Body of extended data message.  May be sent by either the switch or the
- * controller to send messages that are greater than 65535 bytes in
- * length.  The OpenFlow transaction id (xid) must be the same for all
- * the individual OpenFlow messages that make up an extended message.
- *
- * OFMPT_EXTENDED_DATA (switch <-> controller) */
-struct ofmp_extended_data {
-    struct ofmp_header header;
-
-    uint16_t type;            /* Type code of the encapsulated message. */
-    uint8_t flags;            /* One of OFMPEDF_*. */
-    uint8_t pad;
-    uint8_t data[0];          /* Variable-length data. */
-};
-OFP_ASSERT(sizeof(struct ofmp_extended_data) == 24);
-
-#endif /* openflow/openflow-mgmt.h */
index 889a21f..92e0718 100644 (file)
@@ -1,4 +1,5 @@
 noinst_HEADERS += \
+       include/openvswitch/gre.h \
        include/openvswitch/brcompat-netlink.h \
        include/openvswitch/datapath-protocol.h
 
diff --git a/include/openvswitch/gre.h b/include/openvswitch/gre.h
new file mode 100644 (file)
index 0000000..2b24cf6
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * This file is offered under your choice of two licenses: Apache 2.0 or GNU
+ * GPL 2.0 or later.  The permission statements for each of these licenses is
+ * given below.  You may license your modifications to this file under either
+ * of these licenses or both.  If you wish to license your modifications under
+ * only one of these licenses, delete the permission text for the other
+ * license.
+ *
+ * ----------------------------------------------------------------------
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ * ----------------------------------------------------------------------
+ */
+
+#ifndef OPENVSWITCH_GRE_H
+#define OPENVSWITCH_GRE_H 1
+
+#include <linux/if_tunnel.h>
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
+#define GRE_IOCTL_ONLY
+#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+enum
+{
+       IFLA_GRE_UNSPEC,
+       IFLA_GRE_LINK,
+       IFLA_GRE_IFLAGS,
+       IFLA_GRE_OFLAGS,
+       IFLA_GRE_IKEY,
+       IFLA_GRE_OKEY,
+       IFLA_GRE_LOCAL,
+       IFLA_GRE_REMOTE,
+       IFLA_GRE_TTL,
+       IFLA_GRE_TOS,
+       IFLA_GRE_PMTUDISC,
+       __IFLA_GRE_MAX,
+};
+
+#define IFLA_GRE_MAX   (__IFLA_GRE_MAX - 1)
+#endif
+
+#define GRE_IOCTL_DEVICE "gre0"
+
+#define SIOCGETGRETAP  SIOCGETTUNNEL
+#define SIOCADDGRETAP  (SIOCDEVPRIVATE + 10)
+#define SIOCDELGRETAP  SIOCDELTUNNEL
+#define SIOCCHGGRETAP  (SIOCDEVPRIVATE + 11)
+
+#endif /* openvswitch/gre.h */
index 6a3f65c..d56deec 100644 (file)
@@ -1,4 +1,5 @@
 /Makefile
 /Makefile.in
 /dhparams.c
+/dirs.c
 /coverage-counters.c
diff --git a/lib/aes128.c b/lib/aes128.c
new file mode 100644 (file)
index 0000000..aa7f560
--- /dev/null
@@ -0,0 +1,846 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Based on rijndael.txt by Philip J. Erdelsky, downloaded from
+ * http://www.efgh.com/software/rijndael.htm on September 24, 2009.  The
+ * license information there is: "Public domain; no restrictions on use."
+ * The Apache license above applies only to Nicira's modifications to the
+ * original code.
+ */
+
+#include <config.h>
+
+#include "aes128.h"
+
+#include <assert.h>
+
+#include "util.h"
+
+static const uint32_t Te0[256] = {
+    0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU,
+    0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U,
+    0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU,
+    0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU,
+    0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U,
+    0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU,
+    0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU,
+    0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU,
+    0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU,
+    0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU,
+    0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U,
+    0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU,
+    0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU,
+    0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U,
+    0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU,
+    0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU,
+    0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU,
+    0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU,
+    0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU,
+    0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U,
+    0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU,
+    0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU,
+    0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU,
+    0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU,
+    0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U,
+    0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U,
+    0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U,
+    0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U,
+    0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU,
+    0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U,
+    0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U,
+    0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU,
+    0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU,
+    0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U,
+    0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U,
+    0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U,
+    0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU,
+    0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U,
+    0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU,
+    0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U,
+    0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU,
+    0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U,
+    0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U,
+    0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU,
+    0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U,
+    0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U,
+    0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U,
+    0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U,
+    0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U,
+    0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U,
+    0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U,
+    0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U,
+    0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU,
+    0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U,
+    0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U,
+    0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U,
+    0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U,
+    0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U,
+    0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U,
+    0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU,
+    0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U,
+    0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U,
+    0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U,
+    0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU,
+};
+
+static const uint32_t Te1[256] = {
+    0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU,
+    0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U,
+    0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU,
+    0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U,
+    0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU,
+    0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U,
+    0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU,
+    0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U,
+    0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U,
+    0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU,
+    0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U,
+    0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U,
+    0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U,
+    0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU,
+    0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U,
+    0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U,
+    0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU,
+    0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U,
+    0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U,
+    0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U,
+    0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU,
+    0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU,
+    0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U,
+    0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU,
+    0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU,
+    0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U,
+    0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU,
+    0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U,
+    0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU,
+    0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U,
+    0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U,
+    0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U,
+    0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU,
+    0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U,
+    0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU,
+    0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U,
+    0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU,
+    0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U,
+    0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U,
+    0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU,
+    0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU,
+    0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU,
+    0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U,
+    0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U,
+    0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU,
+    0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U,
+    0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU,
+    0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U,
+    0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU,
+    0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U,
+    0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU,
+    0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU,
+    0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U,
+    0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU,
+    0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U,
+    0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU,
+    0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U,
+    0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U,
+    0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U,
+    0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU,
+    0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU,
+    0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U,
+    0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU,
+    0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U,
+};
+
+static const uint32_t Te2[256] = {
+    0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU,
+    0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U,
+    0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU,
+    0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U,
+    0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU,
+    0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U,
+    0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU,
+    0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U,
+    0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U,
+    0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU,
+    0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U,
+    0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U,
+    0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U,
+    0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU,
+    0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U,
+    0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U,
+    0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU,
+    0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U,
+    0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U,
+    0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U,
+    0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU,
+    0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU,
+    0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U,
+    0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU,
+    0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU,
+    0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U,
+    0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU,
+    0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U,
+    0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU,
+    0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U,
+    0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U,
+    0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U,
+    0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU,
+    0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U,
+    0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU,
+    0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U,
+    0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU,
+    0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U,
+    0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U,
+    0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU,
+    0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU,
+    0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU,
+    0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U,
+    0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U,
+    0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU,
+    0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U,
+    0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU,
+    0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U,
+    0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU,
+    0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U,
+    0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU,
+    0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU,
+    0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U,
+    0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU,
+    0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U,
+    0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU,
+    0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U,
+    0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U,
+    0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U,
+    0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU,
+    0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU,
+    0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U,
+    0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU,
+    0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U,
+};
+
+static const uint32_t Te3[256] = {
+    0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U,
+    0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U,
+    0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U,
+    0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU,
+    0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU,
+    0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU,
+    0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U,
+    0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU,
+    0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU,
+    0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U,
+    0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U,
+    0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU,
+    0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU,
+    0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU,
+    0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU,
+    0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU,
+    0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U,
+    0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU,
+    0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU,
+    0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U,
+    0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U,
+    0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U,
+    0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U,
+    0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U,
+    0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU,
+    0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U,
+    0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU,
+    0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU,
+    0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U,
+    0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U,
+    0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U,
+    0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU,
+    0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U,
+    0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU,
+    0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU,
+    0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U,
+    0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U,
+    0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU,
+    0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U,
+    0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU,
+    0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U,
+    0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U,
+    0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U,
+    0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U,
+    0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU,
+    0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U,
+    0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU,
+    0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U,
+    0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU,
+    0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U,
+    0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU,
+    0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU,
+    0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU,
+    0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU,
+    0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U,
+    0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U,
+    0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U,
+    0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U,
+    0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U,
+    0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U,
+    0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU,
+    0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U,
+    0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU,
+    0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU,
+};
+
+static const uint32_t Te4[256] = {
+    0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU,
+    0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U,
+    0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU,
+    0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U,
+    0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU,
+    0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U,
+    0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU,
+    0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U,
+    0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U,
+    0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU,
+    0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U,
+    0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U,
+    0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U,
+    0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU,
+    0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U,
+    0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U,
+    0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU,
+    0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U,
+    0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U,
+    0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U,
+    0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU,
+    0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU,
+    0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U,
+    0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU,
+    0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU,
+    0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U,
+    0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU,
+    0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U,
+    0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU,
+    0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U,
+    0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U,
+    0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U,
+    0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU,
+    0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U,
+    0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU,
+    0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U,
+    0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU,
+    0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U,
+    0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U,
+    0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU,
+    0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU,
+    0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU,
+    0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U,
+    0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U,
+    0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU,
+    0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U,
+    0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU,
+    0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U,
+    0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU,
+    0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U,
+    0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU,
+    0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU,
+    0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U,
+    0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU,
+    0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U,
+    0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU,
+    0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U,
+    0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U,
+    0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U,
+    0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU,
+    0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU,
+    0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U,
+    0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU,
+    0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U,
+};
+
+static const uint32_t Td0[256] = {
+    0x51f4a750U, 0x7e416553U, 0x1a17a4c3U, 0x3a275e96U,
+    0x3bab6bcbU, 0x1f9d45f1U, 0xacfa58abU, 0x4be30393U,
+    0x2030fa55U, 0xad766df6U, 0x88cc7691U, 0xf5024c25U,
+    0x4fe5d7fcU, 0xc52acbd7U, 0x26354480U, 0xb562a38fU,
+    0xdeb15a49U, 0x25ba1b67U, 0x45ea0e98U, 0x5dfec0e1U,
+    0xc32f7502U, 0x814cf012U, 0x8d4697a3U, 0x6bd3f9c6U,
+    0x038f5fe7U, 0x15929c95U, 0xbf6d7aebU, 0x955259daU,
+    0xd4be832dU, 0x587421d3U, 0x49e06929U, 0x8ec9c844U,
+    0x75c2896aU, 0xf48e7978U, 0x99583e6bU, 0x27b971ddU,
+    0xbee14fb6U, 0xf088ad17U, 0xc920ac66U, 0x7dce3ab4U,
+    0x63df4a18U, 0xe51a3182U, 0x97513360U, 0x62537f45U,
+    0xb16477e0U, 0xbb6bae84U, 0xfe81a01cU, 0xf9082b94U,
+    0x70486858U, 0x8f45fd19U, 0x94de6c87U, 0x527bf8b7U,
+    0xab73d323U, 0x724b02e2U, 0xe31f8f57U, 0x6655ab2aU,
+    0xb2eb2807U, 0x2fb5c203U, 0x86c57b9aU, 0xd33708a5U,
+    0x302887f2U, 0x23bfa5b2U, 0x02036abaU, 0xed16825cU,
+    0x8acf1c2bU, 0xa779b492U, 0xf307f2f0U, 0x4e69e2a1U,
+    0x65daf4cdU, 0x0605bed5U, 0xd134621fU, 0xc4a6fe8aU,
+    0x342e539dU, 0xa2f355a0U, 0x058ae132U, 0xa4f6eb75U,
+    0x0b83ec39U, 0x4060efaaU, 0x5e719f06U, 0xbd6e1051U,
+    0x3e218af9U, 0x96dd063dU, 0xdd3e05aeU, 0x4de6bd46U,
+    0x91548db5U, 0x71c45d05U, 0x0406d46fU, 0x605015ffU,
+    0x1998fb24U, 0xd6bde997U, 0x894043ccU, 0x67d99e77U,
+    0xb0e842bdU, 0x07898b88U, 0xe7195b38U, 0x79c8eedbU,
+    0xa17c0a47U, 0x7c420fe9U, 0xf8841ec9U, 0x00000000U,
+    0x09808683U, 0x322bed48U, 0x1e1170acU, 0x6c5a724eU,
+    0xfd0efffbU, 0x0f853856U, 0x3daed51eU, 0x362d3927U,
+    0x0a0fd964U, 0x685ca621U, 0x9b5b54d1U, 0x24362e3aU,
+    0x0c0a67b1U, 0x9357e70fU, 0xb4ee96d2U, 0x1b9b919eU,
+    0x80c0c54fU, 0x61dc20a2U, 0x5a774b69U, 0x1c121a16U,
+    0xe293ba0aU, 0xc0a02ae5U, 0x3c22e043U, 0x121b171dU,
+    0x0e090d0bU, 0xf28bc7adU, 0x2db6a8b9U, 0x141ea9c8U,
+    0x57f11985U, 0xaf75074cU, 0xee99ddbbU, 0xa37f60fdU,
+    0xf701269fU, 0x5c72f5bcU, 0x44663bc5U, 0x5bfb7e34U,
+    0x8b432976U, 0xcb23c6dcU, 0xb6edfc68U, 0xb8e4f163U,
+    0xd731dccaU, 0x42638510U, 0x13972240U, 0x84c61120U,
+    0x854a247dU, 0xd2bb3df8U, 0xaef93211U, 0xc729a16dU,
+    0x1d9e2f4bU, 0xdcb230f3U, 0x0d8652ecU, 0x77c1e3d0U,
+    0x2bb3166cU, 0xa970b999U, 0x119448faU, 0x47e96422U,
+    0xa8fc8cc4U, 0xa0f03f1aU, 0x567d2cd8U, 0x223390efU,
+    0x87494ec7U, 0xd938d1c1U, 0x8ccaa2feU, 0x98d40b36U,
+    0xa6f581cfU, 0xa57ade28U, 0xdab78e26U, 0x3fadbfa4U,
+    0x2c3a9de4U, 0x5078920dU, 0x6a5fcc9bU, 0x547e4662U,
+    0xf68d13c2U, 0x90d8b8e8U, 0x2e39f75eU, 0x82c3aff5U,
+    0x9f5d80beU, 0x69d0937cU, 0x6fd52da9U, 0xcf2512b3U,
+    0xc8ac993bU, 0x10187da7U, 0xe89c636eU, 0xdb3bbb7bU,
+    0xcd267809U, 0x6e5918f4U, 0xec9ab701U, 0x834f9aa8U,
+    0xe6956e65U, 0xaaffe67eU, 0x21bccf08U, 0xef15e8e6U,
+    0xbae79bd9U, 0x4a6f36ceU, 0xea9f09d4U, 0x29b07cd6U,
+    0x31a4b2afU, 0x2a3f2331U, 0xc6a59430U, 0x35a266c0U,
+    0x744ebc37U, 0xfc82caa6U, 0xe090d0b0U, 0x33a7d815U,
+    0xf104984aU, 0x41ecdaf7U, 0x7fcd500eU, 0x1791f62fU,
+    0x764dd68dU, 0x43efb04dU, 0xccaa4d54U, 0xe49604dfU,
+    0x9ed1b5e3U, 0x4c6a881bU, 0xc12c1fb8U, 0x4665517fU,
+    0x9d5eea04U, 0x018c355dU, 0xfa877473U, 0xfb0b412eU,
+    0xb3671d5aU, 0x92dbd252U, 0xe9105633U, 0x6dd64713U,
+    0x9ad7618cU, 0x37a10c7aU, 0x59f8148eU, 0xeb133c89U,
+    0xcea927eeU, 0xb761c935U, 0xe11ce5edU, 0x7a47b13cU,
+    0x9cd2df59U, 0x55f2733fU, 0x1814ce79U, 0x73c737bfU,
+    0x53f7cdeaU, 0x5ffdaa5bU, 0xdf3d6f14U, 0x7844db86U,
+    0xcaaff381U, 0xb968c43eU, 0x3824342cU, 0xc2a3405fU,
+    0x161dc372U, 0xbce2250cU, 0x283c498bU, 0xff0d9541U,
+    0x39a80171U, 0x080cb3deU, 0xd8b4e49cU, 0x6456c190U,
+    0x7bcb8461U, 0xd532b670U, 0x486c5c74U, 0xd0b85742U,
+};
+
+static const uint32_t Td1[256] = {
+    0x5051f4a7U, 0x537e4165U, 0xc31a17a4U, 0x963a275eU,
+    0xcb3bab6bU, 0xf11f9d45U, 0xabacfa58U, 0x934be303U,
+    0x552030faU, 0xf6ad766dU, 0x9188cc76U, 0x25f5024cU,
+    0xfc4fe5d7U, 0xd7c52acbU, 0x80263544U, 0x8fb562a3U,
+    0x49deb15aU, 0x6725ba1bU, 0x9845ea0eU, 0xe15dfec0U,
+    0x02c32f75U, 0x12814cf0U, 0xa38d4697U, 0xc66bd3f9U,
+    0xe7038f5fU, 0x9515929cU, 0xebbf6d7aU, 0xda955259U,
+    0x2dd4be83U, 0xd3587421U, 0x2949e069U, 0x448ec9c8U,
+    0x6a75c289U, 0x78f48e79U, 0x6b99583eU, 0xdd27b971U,
+    0xb6bee14fU, 0x17f088adU, 0x66c920acU, 0xb47dce3aU,
+    0x1863df4aU, 0x82e51a31U, 0x60975133U, 0x4562537fU,
+    0xe0b16477U, 0x84bb6baeU, 0x1cfe81a0U, 0x94f9082bU,
+    0x58704868U, 0x198f45fdU, 0x8794de6cU, 0xb7527bf8U,
+    0x23ab73d3U, 0xe2724b02U, 0x57e31f8fU, 0x2a6655abU,
+    0x07b2eb28U, 0x032fb5c2U, 0x9a86c57bU, 0xa5d33708U,
+    0xf2302887U, 0xb223bfa5U, 0xba02036aU, 0x5ced1682U,
+    0x2b8acf1cU, 0x92a779b4U, 0xf0f307f2U, 0xa14e69e2U,
+    0xcd65daf4U, 0xd50605beU, 0x1fd13462U, 0x8ac4a6feU,
+    0x9d342e53U, 0xa0a2f355U, 0x32058ae1U, 0x75a4f6ebU,
+    0x390b83ecU, 0xaa4060efU, 0x065e719fU, 0x51bd6e10U,
+    0xf93e218aU, 0x3d96dd06U, 0xaedd3e05U, 0x464de6bdU,
+    0xb591548dU, 0x0571c45dU, 0x6f0406d4U, 0xff605015U,
+    0x241998fbU, 0x97d6bde9U, 0xcc894043U, 0x7767d99eU,
+    0xbdb0e842U, 0x8807898bU, 0x38e7195bU, 0xdb79c8eeU,
+    0x47a17c0aU, 0xe97c420fU, 0xc9f8841eU, 0x00000000U,
+    0x83098086U, 0x48322bedU, 0xac1e1170U, 0x4e6c5a72U,
+    0xfbfd0effU, 0x560f8538U, 0x1e3daed5U, 0x27362d39U,
+    0x640a0fd9U, 0x21685ca6U, 0xd19b5b54U, 0x3a24362eU,
+    0xb10c0a67U, 0x0f9357e7U, 0xd2b4ee96U, 0x9e1b9b91U,
+    0x4f80c0c5U, 0xa261dc20U, 0x695a774bU, 0x161c121aU,
+    0x0ae293baU, 0xe5c0a02aU, 0x433c22e0U, 0x1d121b17U,
+    0x0b0e090dU, 0xadf28bc7U, 0xb92db6a8U, 0xc8141ea9U,
+    0x8557f119U, 0x4caf7507U, 0xbbee99ddU, 0xfda37f60U,
+    0x9ff70126U, 0xbc5c72f5U, 0xc544663bU, 0x345bfb7eU,
+    0x768b4329U, 0xdccb23c6U, 0x68b6edfcU, 0x63b8e4f1U,
+    0xcad731dcU, 0x10426385U, 0x40139722U, 0x2084c611U,
+    0x7d854a24U, 0xf8d2bb3dU, 0x11aef932U, 0x6dc729a1U,
+    0x4b1d9e2fU, 0xf3dcb230U, 0xec0d8652U, 0xd077c1e3U,
+    0x6c2bb316U, 0x99a970b9U, 0xfa119448U, 0x2247e964U,
+    0xc4a8fc8cU, 0x1aa0f03fU, 0xd8567d2cU, 0xef223390U,
+    0xc787494eU, 0xc1d938d1U, 0xfe8ccaa2U, 0x3698d40bU,
+    0xcfa6f581U, 0x28a57adeU, 0x26dab78eU, 0xa43fadbfU,
+    0xe42c3a9dU, 0x0d507892U, 0x9b6a5fccU, 0x62547e46U,
+    0xc2f68d13U, 0xe890d8b8U, 0x5e2e39f7U, 0xf582c3afU,
+    0xbe9f5d80U, 0x7c69d093U, 0xa96fd52dU, 0xb3cf2512U,
+    0x3bc8ac99U, 0xa710187dU, 0x6ee89c63U, 0x7bdb3bbbU,
+    0x09cd2678U, 0xf46e5918U, 0x01ec9ab7U, 0xa8834f9aU,
+    0x65e6956eU, 0x7eaaffe6U, 0x0821bccfU, 0xe6ef15e8U,
+    0xd9bae79bU, 0xce4a6f36U, 0xd4ea9f09U, 0xd629b07cU,
+    0xaf31a4b2U, 0x312a3f23U, 0x30c6a594U, 0xc035a266U,
+    0x37744ebcU, 0xa6fc82caU, 0xb0e090d0U, 0x1533a7d8U,
+    0x4af10498U, 0xf741ecdaU, 0x0e7fcd50U, 0x2f1791f6U,
+    0x8d764dd6U, 0x4d43efb0U, 0x54ccaa4dU, 0xdfe49604U,
+    0xe39ed1b5U, 0x1b4c6a88U, 0xb8c12c1fU, 0x7f466551U,
+    0x049d5eeaU, 0x5d018c35U, 0x73fa8774U, 0x2efb0b41U,
+    0x5ab3671dU, 0x5292dbd2U, 0x33e91056U, 0x136dd647U,
+    0x8c9ad761U, 0x7a37a10cU, 0x8e59f814U, 0x89eb133cU,
+    0xeecea927U, 0x35b761c9U, 0xede11ce5U, 0x3c7a47b1U,
+    0x599cd2dfU, 0x3f55f273U, 0x791814ceU, 0xbf73c737U,
+    0xea53f7cdU, 0x5b5ffdaaU, 0x14df3d6fU, 0x867844dbU,
+    0x81caaff3U, 0x3eb968c4U, 0x2c382434U, 0x5fc2a340U,
+    0x72161dc3U, 0x0cbce225U, 0x8b283c49U, 0x41ff0d95U,
+    0x7139a801U, 0xde080cb3U, 0x9cd8b4e4U, 0x906456c1U,
+    0x617bcb84U, 0x70d532b6U, 0x74486c5cU, 0x42d0b857U,
+};
+
+static const uint32_t Td2[256] = {
+    0xa75051f4U, 0x65537e41U, 0xa4c31a17U, 0x5e963a27U,
+    0x6bcb3babU, 0x45f11f9dU, 0x58abacfaU, 0x03934be3U,
+    0xfa552030U, 0x6df6ad76U, 0x769188ccU, 0x4c25f502U,
+    0xd7fc4fe5U, 0xcbd7c52aU, 0x44802635U, 0xa38fb562U,
+    0x5a49deb1U, 0x1b6725baU, 0x0e9845eaU, 0xc0e15dfeU,
+    0x7502c32fU, 0xf012814cU, 0x97a38d46U, 0xf9c66bd3U,
+    0x5fe7038fU, 0x9c951592U, 0x7aebbf6dU, 0x59da9552U,
+    0x832dd4beU, 0x21d35874U, 0x692949e0U, 0xc8448ec9U,
+    0x896a75c2U, 0x7978f48eU, 0x3e6b9958U, 0x71dd27b9U,
+    0x4fb6bee1U, 0xad17f088U, 0xac66c920U, 0x3ab47dceU,
+    0x4a1863dfU, 0x3182e51aU, 0x33609751U, 0x7f456253U,
+    0x77e0b164U, 0xae84bb6bU, 0xa01cfe81U, 0x2b94f908U,
+    0x68587048U, 0xfd198f45U, 0x6c8794deU, 0xf8b7527bU,
+    0xd323ab73U, 0x02e2724bU, 0x8f57e31fU, 0xab2a6655U,
+    0x2807b2ebU, 0xc2032fb5U, 0x7b9a86c5U, 0x08a5d337U,
+    0x87f23028U, 0xa5b223bfU, 0x6aba0203U, 0x825ced16U,
+    0x1c2b8acfU, 0xb492a779U, 0xf2f0f307U, 0xe2a14e69U,
+    0xf4cd65daU, 0xbed50605U, 0x621fd134U, 0xfe8ac4a6U,
+    0x539d342eU, 0x55a0a2f3U, 0xe132058aU, 0xeb75a4f6U,
+    0xec390b83U, 0xefaa4060U, 0x9f065e71U, 0x1051bd6eU,
+    0x8af93e21U, 0x063d96ddU, 0x05aedd3eU, 0xbd464de6U,
+    0x8db59154U, 0x5d0571c4U, 0xd46f0406U, 0x15ff6050U,
+    0xfb241998U, 0xe997d6bdU, 0x43cc8940U, 0x9e7767d9U,
+    0x42bdb0e8U, 0x8b880789U, 0x5b38e719U, 0xeedb79c8U,
+    0x0a47a17cU, 0x0fe97c42U, 0x1ec9f884U, 0x00000000U,
+    0x86830980U, 0xed48322bU, 0x70ac1e11U, 0x724e6c5aU,
+    0xfffbfd0eU, 0x38560f85U, 0xd51e3daeU, 0x3927362dU,
+    0xd9640a0fU, 0xa621685cU, 0x54d19b5bU, 0x2e3a2436U,
+    0x67b10c0aU, 0xe70f9357U, 0x96d2b4eeU, 0x919e1b9bU,
+    0xc54f80c0U, 0x20a261dcU, 0x4b695a77U, 0x1a161c12U,
+    0xba0ae293U, 0x2ae5c0a0U, 0xe0433c22U, 0x171d121bU,
+    0x0d0b0e09U, 0xc7adf28bU, 0xa8b92db6U, 0xa9c8141eU,
+    0x198557f1U, 0x074caf75U, 0xddbbee99U, 0x60fda37fU,
+    0x269ff701U, 0xf5bc5c72U, 0x3bc54466U, 0x7e345bfbU,
+    0x29768b43U, 0xc6dccb23U, 0xfc68b6edU, 0xf163b8e4U,
+    0xdccad731U, 0x85104263U, 0x22401397U, 0x112084c6U,
+    0x247d854aU, 0x3df8d2bbU, 0x3211aef9U, 0xa16dc729U,
+    0x2f4b1d9eU, 0x30f3dcb2U, 0x52ec0d86U, 0xe3d077c1U,
+    0x166c2bb3U, 0xb999a970U, 0x48fa1194U, 0x642247e9U,
+    0x8cc4a8fcU, 0x3f1aa0f0U, 0x2cd8567dU, 0x90ef2233U,
+    0x4ec78749U, 0xd1c1d938U, 0xa2fe8ccaU, 0x0b3698d4U,
+    0x81cfa6f5U, 0xde28a57aU, 0x8e26dab7U, 0xbfa43fadU,
+    0x9de42c3aU, 0x920d5078U, 0xcc9b6a5fU, 0x4662547eU,
+    0x13c2f68dU, 0xb8e890d8U, 0xf75e2e39U, 0xaff582c3U,
+    0x80be9f5dU, 0x937c69d0U, 0x2da96fd5U, 0x12b3cf25U,
+    0x993bc8acU, 0x7da71018U, 0x636ee89cU, 0xbb7bdb3bU,
+    0x7809cd26U, 0x18f46e59U, 0xb701ec9aU, 0x9aa8834fU,
+    0x6e65e695U, 0xe67eaaffU, 0xcf0821bcU, 0xe8e6ef15U,
+    0x9bd9bae7U, 0x36ce4a6fU, 0x09d4ea9fU, 0x7cd629b0U,
+    0xb2af31a4U, 0x23312a3fU, 0x9430c6a5U, 0x66c035a2U,
+    0xbc37744eU, 0xcaa6fc82U, 0xd0b0e090U, 0xd81533a7U,
+    0x984af104U, 0xdaf741ecU, 0x500e7fcdU, 0xf62f1791U,
+    0xd68d764dU, 0xb04d43efU, 0x4d54ccaaU, 0x04dfe496U,
+    0xb5e39ed1U, 0x881b4c6aU, 0x1fb8c12cU, 0x517f4665U,
+    0xea049d5eU, 0x355d018cU, 0x7473fa87U, 0x412efb0bU,
+    0x1d5ab367U, 0xd25292dbU, 0x5633e910U, 0x47136dd6U,
+    0x618c9ad7U, 0x0c7a37a1U, 0x148e59f8U, 0x3c89eb13U,
+    0x27eecea9U, 0xc935b761U, 0xe5ede11cU, 0xb13c7a47U,
+    0xdf599cd2U, 0x733f55f2U, 0xce791814U, 0x37bf73c7U,
+    0xcdea53f7U, 0xaa5b5ffdU, 0x6f14df3dU, 0xdb867844U,
+    0xf381caafU, 0xc43eb968U, 0x342c3824U, 0x405fc2a3U,
+    0xc372161dU, 0x250cbce2U, 0x498b283cU, 0x9541ff0dU,
+    0x017139a8U, 0xb3de080cU, 0xe49cd8b4U, 0xc1906456U,
+    0x84617bcbU, 0xb670d532U, 0x5c74486cU, 0x5742d0b8U,
+};
+
+static const uint32_t Td3[256] = {
+    0xf4a75051U, 0x4165537eU, 0x17a4c31aU, 0x275e963aU,
+    0xab6bcb3bU, 0x9d45f11fU, 0xfa58abacU, 0xe303934bU,
+    0x30fa5520U, 0x766df6adU, 0xcc769188U, 0x024c25f5U,
+    0xe5d7fc4fU, 0x2acbd7c5U, 0x35448026U, 0x62a38fb5U,
+    0xb15a49deU, 0xba1b6725U, 0xea0e9845U, 0xfec0e15dU,
+    0x2f7502c3U, 0x4cf01281U, 0x4697a38dU, 0xd3f9c66bU,
+    0x8f5fe703U, 0x929c9515U, 0x6d7aebbfU, 0x5259da95U,
+    0xbe832dd4U, 0x7421d358U, 0xe0692949U, 0xc9c8448eU,
+    0xc2896a75U, 0x8e7978f4U, 0x583e6b99U, 0xb971dd27U,
+    0xe14fb6beU, 0x88ad17f0U, 0x20ac66c9U, 0xce3ab47dU,
+    0xdf4a1863U, 0x1a3182e5U, 0x51336097U, 0x537f4562U,
+    0x6477e0b1U, 0x6bae84bbU, 0x81a01cfeU, 0x082b94f9U,
+    0x48685870U, 0x45fd198fU, 0xde6c8794U, 0x7bf8b752U,
+    0x73d323abU, 0x4b02e272U, 0x1f8f57e3U, 0x55ab2a66U,
+    0xeb2807b2U, 0xb5c2032fU, 0xc57b9a86U, 0x3708a5d3U,
+    0x2887f230U, 0xbfa5b223U, 0x036aba02U, 0x16825cedU,
+    0xcf1c2b8aU, 0x79b492a7U, 0x07f2f0f3U, 0x69e2a14eU,
+    0xdaf4cd65U, 0x05bed506U, 0x34621fd1U, 0xa6fe8ac4U,
+    0x2e539d34U, 0xf355a0a2U, 0x8ae13205U, 0xf6eb75a4U,
+    0x83ec390bU, 0x60efaa40U, 0x719f065eU, 0x6e1051bdU,
+    0x218af93eU, 0xdd063d96U, 0x3e05aeddU, 0xe6bd464dU,
+    0x548db591U, 0xc45d0571U, 0x06d46f04U, 0x5015ff60U,
+    0x98fb2419U, 0xbde997d6U, 0x4043cc89U, 0xd99e7767U,
+    0xe842bdb0U, 0x898b8807U, 0x195b38e7U, 0xc8eedb79U,
+    0x7c0a47a1U, 0x420fe97cU, 0x841ec9f8U, 0x00000000U,
+    0x80868309U, 0x2bed4832U, 0x1170ac1eU, 0x5a724e6cU,
+    0x0efffbfdU, 0x8538560fU, 0xaed51e3dU, 0x2d392736U,
+    0x0fd9640aU, 0x5ca62168U, 0x5b54d19bU, 0x362e3a24U,
+    0x0a67b10cU, 0x57e70f93U, 0xee96d2b4U, 0x9b919e1bU,
+    0xc0c54f80U, 0xdc20a261U, 0x774b695aU, 0x121a161cU,
+    0x93ba0ae2U, 0xa02ae5c0U, 0x22e0433cU, 0x1b171d12U,
+    0x090d0b0eU, 0x8bc7adf2U, 0xb6a8b92dU, 0x1ea9c814U,
+    0xf1198557U, 0x75074cafU, 0x99ddbbeeU, 0x7f60fda3U,
+    0x01269ff7U, 0x72f5bc5cU, 0x663bc544U, 0xfb7e345bU,
+    0x4329768bU, 0x23c6dccbU, 0xedfc68b6U, 0xe4f163b8U,
+    0x31dccad7U, 0x63851042U, 0x97224013U, 0xc6112084U,
+    0x4a247d85U, 0xbb3df8d2U, 0xf93211aeU, 0x29a16dc7U,
+    0x9e2f4b1dU, 0xb230f3dcU, 0x8652ec0dU, 0xc1e3d077U,
+    0xb3166c2bU, 0x70b999a9U, 0x9448fa11U, 0xe9642247U,
+    0xfc8cc4a8U, 0xf03f1aa0U, 0x7d2cd856U, 0x3390ef22U,
+    0x494ec787U, 0x38d1c1d9U, 0xcaa2fe8cU, 0xd40b3698U,
+    0xf581cfa6U, 0x7ade28a5U, 0xb78e26daU, 0xadbfa43fU,
+    0x3a9de42cU, 0x78920d50U, 0x5fcc9b6aU, 0x7e466254U,
+    0x8d13c2f6U, 0xd8b8e890U, 0x39f75e2eU, 0xc3aff582U,
+    0x5d80be9fU, 0xd0937c69U, 0xd52da96fU, 0x2512b3cfU,
+    0xac993bc8U, 0x187da710U, 0x9c636ee8U, 0x3bbb7bdbU,
+    0x267809cdU, 0x5918f46eU, 0x9ab701ecU, 0x4f9aa883U,
+    0x956e65e6U, 0xffe67eaaU, 0xbccf0821U, 0x15e8e6efU,
+    0xe79bd9baU, 0x6f36ce4aU, 0x9f09d4eaU, 0xb07cd629U,
+    0xa4b2af31U, 0x3f23312aU, 0xa59430c6U, 0xa266c035U,
+    0x4ebc3774U, 0x82caa6fcU, 0x90d0b0e0U, 0xa7d81533U,
+    0x04984af1U, 0xecdaf741U, 0xcd500e7fU, 0x91f62f17U,
+    0x4dd68d76U, 0xefb04d43U, 0xaa4d54ccU, 0x9604dfe4U,
+    0xd1b5e39eU, 0x6a881b4cU, 0x2c1fb8c1U, 0x65517f46U,
+    0x5eea049dU, 0x8c355d01U, 0x877473faU, 0x0b412efbU,
+    0x671d5ab3U, 0xdbd25292U, 0x105633e9U, 0xd647136dU,
+    0xd7618c9aU, 0xa10c7a37U, 0xf8148e59U, 0x133c89ebU,
+    0xa927eeceU, 0x61c935b7U, 0x1ce5ede1U, 0x47b13c7aU,
+    0xd2df599cU, 0xf2733f55U, 0x14ce7918U, 0xc737bf73U,
+    0xf7cdea53U, 0xfdaa5b5fU, 0x3d6f14dfU, 0x44db8678U,
+    0xaff381caU, 0x68c43eb9U, 0x24342c38U, 0xa3405fc2U,
+    0x1dc37216U, 0xe2250cbcU, 0x3c498b28U, 0x0d9541ffU,
+    0xa8017139U, 0x0cb3de08U, 0xb4e49cd8U, 0x56c19064U,
+    0xcb84617bU, 0x32b670d5U, 0x6c5c7448U, 0xb85742d0U,
+};
+
+static const uint32_t Td4[256] = {
+    0x52525252U, 0x09090909U, 0x6a6a6a6aU, 0xd5d5d5d5U,
+    0x30303030U, 0x36363636U, 0xa5a5a5a5U, 0x38383838U,
+    0xbfbfbfbfU, 0x40404040U, 0xa3a3a3a3U, 0x9e9e9e9eU,
+    0x81818181U, 0xf3f3f3f3U, 0xd7d7d7d7U, 0xfbfbfbfbU,
+    0x7c7c7c7cU, 0xe3e3e3e3U, 0x39393939U, 0x82828282U,
+    0x9b9b9b9bU, 0x2f2f2f2fU, 0xffffffffU, 0x87878787U,
+    0x34343434U, 0x8e8e8e8eU, 0x43434343U, 0x44444444U,
+    0xc4c4c4c4U, 0xdedededeU, 0xe9e9e9e9U, 0xcbcbcbcbU,
+    0x54545454U, 0x7b7b7b7bU, 0x94949494U, 0x32323232U,
+    0xa6a6a6a6U, 0xc2c2c2c2U, 0x23232323U, 0x3d3d3d3dU,
+    0xeeeeeeeeU, 0x4c4c4c4cU, 0x95959595U, 0x0b0b0b0bU,
+    0x42424242U, 0xfafafafaU, 0xc3c3c3c3U, 0x4e4e4e4eU,
+    0x08080808U, 0x2e2e2e2eU, 0xa1a1a1a1U, 0x66666666U,
+    0x28282828U, 0xd9d9d9d9U, 0x24242424U, 0xb2b2b2b2U,
+    0x76767676U, 0x5b5b5b5bU, 0xa2a2a2a2U, 0x49494949U,
+    0x6d6d6d6dU, 0x8b8b8b8bU, 0xd1d1d1d1U, 0x25252525U,
+    0x72727272U, 0xf8f8f8f8U, 0xf6f6f6f6U, 0x64646464U,
+    0x86868686U, 0x68686868U, 0x98989898U, 0x16161616U,
+    0xd4d4d4d4U, 0xa4a4a4a4U, 0x5c5c5c5cU, 0xccccccccU,
+    0x5d5d5d5dU, 0x65656565U, 0xb6b6b6b6U, 0x92929292U,
+    0x6c6c6c6cU, 0x70707070U, 0x48484848U, 0x50505050U,
+    0xfdfdfdfdU, 0xededededU, 0xb9b9b9b9U, 0xdadadadaU,
+    0x5e5e5e5eU, 0x15151515U, 0x46464646U, 0x57575757U,
+    0xa7a7a7a7U, 0x8d8d8d8dU, 0x9d9d9d9dU, 0x84848484U,
+    0x90909090U, 0xd8d8d8d8U, 0xababababU, 0x00000000U,
+    0x8c8c8c8cU, 0xbcbcbcbcU, 0xd3d3d3d3U, 0x0a0a0a0aU,
+    0xf7f7f7f7U, 0xe4e4e4e4U, 0x58585858U, 0x05050505U,
+    0xb8b8b8b8U, 0xb3b3b3b3U, 0x45454545U, 0x06060606U,
+    0xd0d0d0d0U, 0x2c2c2c2cU, 0x1e1e1e1eU, 0x8f8f8f8fU,
+    0xcacacacaU, 0x3f3f3f3fU, 0x0f0f0f0fU, 0x02020202U,
+    0xc1c1c1c1U, 0xafafafafU, 0xbdbdbdbdU, 0x03030303U,
+    0x01010101U, 0x13131313U, 0x8a8a8a8aU, 0x6b6b6b6bU,
+    0x3a3a3a3aU, 0x91919191U, 0x11111111U, 0x41414141U,
+    0x4f4f4f4fU, 0x67676767U, 0xdcdcdcdcU, 0xeaeaeaeaU,
+    0x97979797U, 0xf2f2f2f2U, 0xcfcfcfcfU, 0xcecececeU,
+    0xf0f0f0f0U, 0xb4b4b4b4U, 0xe6e6e6e6U, 0x73737373U,
+    0x96969696U, 0xacacacacU, 0x74747474U, 0x22222222U,
+    0xe7e7e7e7U, 0xadadadadU, 0x35353535U, 0x85858585U,
+    0xe2e2e2e2U, 0xf9f9f9f9U, 0x37373737U, 0xe8e8e8e8U,
+    0x1c1c1c1cU, 0x75757575U, 0xdfdfdfdfU, 0x6e6e6e6eU,
+    0x47474747U, 0xf1f1f1f1U, 0x1a1a1a1aU, 0x71717171U,
+    0x1d1d1d1dU, 0x29292929U, 0xc5c5c5c5U, 0x89898989U,
+    0x6f6f6f6fU, 0xb7b7b7b7U, 0x62626262U, 0x0e0e0e0eU,
+    0xaaaaaaaaU, 0x18181818U, 0xbebebebeU, 0x1b1b1b1bU,
+    0xfcfcfcfcU, 0x56565656U, 0x3e3e3e3eU, 0x4b4b4b4bU,
+    0xc6c6c6c6U, 0xd2d2d2d2U, 0x79797979U, 0x20202020U,
+    0x9a9a9a9aU, 0xdbdbdbdbU, 0xc0c0c0c0U, 0xfefefefeU,
+    0x78787878U, 0xcdcdcdcdU, 0x5a5a5a5aU, 0xf4f4f4f4U,
+    0x1f1f1f1fU, 0xddddddddU, 0xa8a8a8a8U, 0x33333333U,
+    0x88888888U, 0x07070707U, 0xc7c7c7c7U, 0x31313131U,
+    0xb1b1b1b1U, 0x12121212U, 0x10101010U, 0x59595959U,
+    0x27272727U, 0x80808080U, 0xececececU, 0x5f5f5f5fU,
+    0x60606060U, 0x51515151U, 0x7f7f7f7fU, 0xa9a9a9a9U,
+    0x19191919U, 0xb5b5b5b5U, 0x4a4a4a4aU, 0x0d0d0d0dU,
+    0x2d2d2d2dU, 0xe5e5e5e5U, 0x7a7a7a7aU, 0x9f9f9f9fU,
+    0x93939393U, 0xc9c9c9c9U, 0x9c9c9c9cU, 0xefefefefU,
+    0xa0a0a0a0U, 0xe0e0e0e0U, 0x3b3b3b3bU, 0x4d4d4d4dU,
+    0xaeaeaeaeU, 0x2a2a2a2aU, 0xf5f5f5f5U, 0xb0b0b0b0U,
+    0xc8c8c8c8U, 0xebebebebU, 0xbbbbbbbbU, 0x3c3c3c3cU,
+    0x83838383U, 0x53535353U, 0x99999999U, 0x61616161U,
+    0x17171717U, 0x2b2b2b2bU, 0x04040404U, 0x7e7e7e7eU,
+    0xbabababaU, 0x77777777U, 0xd6d6d6d6U, 0x26262626U,
+    0xe1e1e1e1U, 0x69696969U, 0x14141414U, 0x63636363U,
+    0x55555555U, 0x21212121U, 0x0c0c0c0cU, 0x7d7d7d7dU,
+};
+
+static const uint32_t rcon[] = {
+    0x01000000, 0x02000000, 0x04000000, 0x08000000,
+    0x10000000, 0x20000000, 0x40000000, 0x80000000,
+    0x1B000000, 0x36000000,
+};
+
+static uint32_t
+get_u32(const uint8_t *p)
+{
+    uint32_t p0 = p[0];
+    uint32_t p1 = p[1];
+    uint32_t p2 = p[2];
+    uint32_t p3 = p[3];
+    return (p0 << 24) | (p1 << 16) | (p2 << 8) | p3;
+}
+
+static void
+put_u32(uint8_t *p, uint32_t x)
+{
+    p[0] = x >> 24;
+    p[1] = x >> 16;
+    p[2] = x >> 8;
+    p[3] = x;
+}
+
+/* Expands 128-bit 'key' into the encryption key 'schedule'. */
+void
+aes128_schedule(struct aes128 *aes, const uint8_t key[16])
+{
+    uint32_t *rk = aes->rk;
+    int i;
+
+    rk[0] = get_u32(key);
+    rk[1] = get_u32(key + 4);
+    rk[2] = get_u32(key + 8);
+    rk[3] = get_u32(key + 12);
+    for (i = 0; i < 10; i++, rk += 4) {
+        uint32_t temp = rk[3];
+        rk[4] = (rk[0]
+                 ^ (Te4[(temp >> 16) & 0xff] & 0xff000000)
+                 ^ (Te4[(temp >>  8) & 0xff] & 0x00ff0000)
+                 ^ (Te4[(temp      ) & 0xff] & 0x0000ff00)
+                 ^ (Te4[(temp >> 24)       ] & 0x000000ff)
+                 ^ rcon[i]);
+        rk[5] = rk[1] ^ rk[4];
+        rk[6] = rk[2] ^ rk[5];
+        rk[7] = rk[3] ^ rk[6];
+    }
+    assert(rk == &aes->rk[40]);
+}
+
+void
+aes128_encrypt(const struct aes128 *aes, const void *input_, void *output_)
+{
+    const uint8_t *input = input_;
+    uint8_t *output = output_;
+    const uint32_t *rk = aes->rk;
+    uint32_t s0, s1, s2, s3;
+    uint32_t t0, t1, t2, t3;
+    int r;
+
+    /* Map byte array block to cipher state and add initial round key. */
+    s0 = get_u32(input     ) ^ rk[0];
+    s1 = get_u32(input +  4) ^ rk[1];
+    s2 = get_u32(input +  8) ^ rk[2];
+    s3 = get_u32(input + 12) ^ rk[3];
+
+    /* 10 full rounds. */
+    r = 10 / 2;
+    for (;;) {
+        t0 = (Te0[(s0 >> 24)       ]
+              ^ Te1[(s1 >> 16) & 0xff]
+              ^ Te2[(s2 >>  8) & 0xff]
+              ^ Te3[(s3      ) & 0xff]
+              ^ rk[4]);
+        t1 = (Te0[(s1 >> 24)       ]
+              ^ Te1[(s2 >> 16) & 0xff]
+              ^ Te2[(s3 >>  8) & 0xff]
+              ^ Te3[(s0      ) & 0xff]
+              ^ rk[5]);
+        t2 = (Te0[(s2 >> 24)       ]
+              ^ Te1[(s3 >> 16) & 0xff]
+              ^ Te2[(s0 >>  8) & 0xff]
+              ^ Te3[(s1      ) & 0xff]
+              ^ rk[6]);
+        t3 = (Te0[(s3 >> 24)       ]
+              ^ Te1[(s0 >> 16) & 0xff]
+              ^ Te2[(s1 >>  8) & 0xff]
+              ^ Te3[(s2      ) & 0xff]
+              ^ rk[7]);
+
+        rk += 8;
+        if (--r == 0) {
+            break;
+        }
+
+        s0 = (Te0[(t0 >> 24)       ]
+              ^ Te1[(t1 >> 16) & 0xff]
+              ^ Te2[(t2 >>  8) & 0xff]
+              ^ Te3[(t3      ) & 0xff]
+              ^ rk[0]);
+        s1 = (Te0[(t1 >> 24)       ]
+              ^ Te1[(t2 >> 16) & 0xff]
+              ^ Te2[(t3 >>  8) & 0xff]
+              ^ Te3[(t0      ) & 0xff]
+              ^ rk[1]);
+        s2 = (Te0[(t2 >> 24)       ]
+              ^ Te1[(t3 >> 16) & 0xff]
+              ^ Te2[(t0 >>  8) & 0xff]
+              ^ Te3[(t1      ) & 0xff]
+              ^ rk[2]);
+        s3 = (Te0[(t3 >> 24)       ]
+              ^ Te1[(t0 >> 16) & 0xff]
+              ^ Te2[(t1 >>  8) & 0xff]
+              ^ Te3[(t2      ) & 0xff]
+              ^ rk[3]);
+    }
+
+    /* Apply last round and map cipher state to byte array block. */
+    s0 = ((Te4[(t0 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t1 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t2 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t3      ) & 0xff] & 0x000000ff)
+          ^ rk[0]);
+    put_u32(output     , s0);
+    s1 = ((Te4[(t1 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t2 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t3 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t0      ) & 0xff] & 0x000000ff)
+          ^ rk[1]);
+    put_u32(output +  4, s1);
+    s2 = ((Te4[(t2 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t3 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t0 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t1      ) & 0xff] & 0x000000ff)
+          ^ rk[2]);
+    put_u32(output +  8, s2);
+    s3 = ((Te4[(t3 >> 24)       ] & 0xff000000)
+          ^ (Te4[(t0 >> 16) & 0xff] & 0x00ff0000)
+          ^ (Te4[(t1 >>  8) & 0xff] & 0x0000ff00)
+          ^ (Te4[(t2      ) & 0xff] & 0x000000ff)
+          ^ rk[3]);
+    put_u32(output + 12, s3);
+}
diff --git a/lib/aes128.h b/lib/aes128.h
new file mode 100644 (file)
index 0000000..bbd7475
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Based on rijndael.txt by Philip J. Erdelsky, downloaded from
+ * http://www.efgh.com/software/rijndael.htm on September 24, 2009.  The
+ * license information there is: "Public domain; no restrictions on use."
+ * The Apache license above applies only to Nicira's modifications to the
+ * original code.
+ */
+
+#ifndef AES128_H
+#define AES128_H
+
+#include <stdint.h>
+
+struct aes128 {
+    uint32_t rk[128/8 + 28];
+};
+
+void aes128_schedule(struct aes128 *, const uint8_t key[16]);
+void aes128_encrypt(const struct aes128 *, const void *, void *);
+
+#endif  /* aes128.h */
index 67100fc..51d3c11 100644 (file)
@@ -8,12 +8,14 @@
 noinst_LIBRARIES += lib/libopenvswitch.a
 
 lib_libopenvswitch_a_SOURCES = \
+       lib/aes128.c \
+       lib/aes128.h \
        lib/backtrace.c \
        lib/backtrace.h \
        lib/bitmap.c \
        lib/bitmap.h \
-       lib/cfg.c \
-       lib/cfg.h \
+       lib/byteq.c \
+       lib/byteq.h \
        lib/classifier.c \
        lib/classifier.h \
        lib/command-line.c \
@@ -41,20 +43,24 @@ lib_libopenvswitch_a_SOURCES = \
        lib/dynamic-string.h \
        lib/fatal-signal.c \
        lib/fatal-signal.h \
-       lib/fault.c \
-       lib/fault.h \
        lib/flow.c \
        lib/flow.h \
        lib/hash.c \
        lib/hash.h \
        lib/hmap.c \
        lib/hmap.h \
+       lib/json.c \
+       lib/json.h \
+       lib/jsonrpc.c \
+       lib/jsonrpc.h \
        lib/leak-checker.c \
        lib/leak-checker.h \
        lib/learning-switch.c \
        lib/learning-switch.h \
        lib/list.c \
        lib/list.h \
+       lib/lockfile.c \
+       lib/lockfile.h \
        lib/mac-learning.c \
        lib/mac-learning.h \
        lib/netdev-linux.c \
@@ -67,6 +73,17 @@ lib_libopenvswitch_a_SOURCES = \
        lib/ofp-print.h \
        lib/ofpbuf.c \
        lib/ofpbuf.h \
+       lib/ovsdb-data.c \
+       lib/ovsdb-data.h \
+       lib/ovsdb-error.c \
+       lib/ovsdb-error.h \
+       lib/ovsdb-idl-provider.h \
+       lib/ovsdb-idl.c \
+       lib/ovsdb-idl.h \
+       lib/ovsdb-parser.c \
+       lib/ovsdb-parser.h \
+       lib/ovsdb-types.c \
+       lib/ovsdb-types.h \
        lib/packets.c \
        lib/packets.h \
        lib/pcap.c \
@@ -83,6 +100,8 @@ lib_libopenvswitch_a_SOURCES = \
        lib/random.h \
        lib/rconn.c \
        lib/rconn.h \
+       lib/reconnect.c \
+       lib/reconnect.h \
        lib/rtnetlink.c \
        lib/rtnetlink.h \
        lib/sat-math.h \
@@ -94,8 +113,18 @@ lib_libopenvswitch_a_SOURCES = \
        lib/signals.h \
        lib/socket-util.c \
        lib/socket-util.h \
+       lib/sort.c \
+       lib/sort.h \
        lib/stp.c \
        lib/stp.h \
+       lib/stream-fd.c \
+       lib/stream-fd.h \
+       lib/stream-provider.h \
+       lib/stream-ssl.h \
+       lib/stream-tcp.c \
+       lib/stream-unix.c \
+       lib/stream.c \
+       lib/stream.h \
        lib/string.h \
        lib/svec.c \
        lib/svec.h \
@@ -104,17 +133,17 @@ lib_libopenvswitch_a_SOURCES = \
        lib/timeval.c \
        lib/timeval.h \
        lib/type-props.h \
+       lib/unicode.c \
+       lib/unicode.h \
        lib/unixctl.c \
        lib/unixctl.h \
        lib/util.c \
        lib/util.h \
+       lib/uuid.c \
+       lib/uuid.h \
        lib/valgrind.h \
        lib/vconn-provider.h \
-       lib/vconn-ssl.h \
        lib/vconn-stream.c \
-       lib/vconn-stream.h \
-       lib/vconn-tcp.c \
-       lib/vconn-unix.c \
        lib/vconn.c \
        lib/vconn.h \
        lib/vlog-modules.def \
@@ -147,8 +176,7 @@ lib_libopenvswitch_a_SOURCES += \
 endif
 
 if HAVE_OPENSSL
-lib_libopenvswitch_a_SOURCES += \
-       lib/vconn-ssl.c 
+lib_libopenvswitch_a_SOURCES += lib/stream-ssl.c
 nodist_lib_libopenvswitch_a_SOURCES += lib/dhparams.c
 lib/dhparams.c: lib/dh1024.pem lib/dh2048.pem lib/dh4096.pem
        (echo '#include "lib/dhparams.h"' &&                            \
@@ -167,10 +195,20 @@ EXTRA_DIST += \
 
 EXTRA_DIST += \
        lib/common.man \
+       lib/common-syn.man \
        lib/daemon.man \
+       lib/daemon-syn.man \
        lib/dpif.man \
        lib/leak-checker.man \
+       lib/ssl-bootstrap.man \
+       lib/ssl-bootstrap-syn.man \
+       lib/ssl-peer-ca-cert.man \
+       lib/ssl.man \
+       lib/ssl-syn.man \
+       lib/vconn-active.man \
+       lib/vconn-passive.man \
        lib/vlog-unixctl.man \
+       lib/vlog-syn.man \
        lib/vlog.man
 
 
@@ -189,9 +227,9 @@ install-data-local:
 
 # All the source files that have coverage counters.
 COVERAGE_FILES = \
-       lib/cfg.c \
        lib/dpif.c \
        lib/flow.c \
+       lib/lockfile.c \
        lib/hmap.c \
        lib/mac-learning.c \
        lib/netdev.c \
@@ -202,6 +240,7 @@ COVERAGE_FILES = \
        lib/process.c \
        lib/rconn.c \
        lib/rtnetlink.c \
+       lib/stream.c \
        lib/timeval.c \
        lib/unixctl.c \
        lib/util.c \
@@ -209,7 +248,6 @@ COVERAGE_FILES = \
        ofproto/ofproto.c \
        ofproto/pktbuf.c \
        vswitchd/bridge.c \
-       vswitchd/mgmt.c \
        vswitchd/ovs-brcompatd.c
 lib/coverage-counters.c: $(COVERAGE_FILES) lib/coverage-scan.pl
        (cd $(srcdir) && $(PERL) lib/coverage-scan.pl $(COVERAGE_FILES)) > $@.tmp
index f021a7e..df3c4eb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -53,3 +53,20 @@ bitmap_equal(const unsigned long *a, const unsigned long *b, size_t n)
     }
     return true;
 }
+
+/* Scans 'bitmap' from bit offset 'start' to 'end', excluding 'end' itself.
+ * Returns the bit offset of the lowest-numbered bit set to 1, or 'end' if
+ * all of the bits are set to 0. */
+size_t
+bitmap_scan(const unsigned long int *bitmap, size_t start, size_t end)
+{
+    /* XXX slow */
+    size_t i;
+
+    for (i = start; i < end; i++) {
+        if (bitmap_is_set(bitmap, i)) {
+            break;
+        }
+    }
+    return i;
+}
index 2042810..5b50c9c 100644 (file)
@@ -79,5 +79,10 @@ bitmap_set(unsigned long *bitmap, size_t offset, bool value)
 void bitmap_set_multiple(unsigned long *, size_t start, size_t count,
                          bool value);
 bool bitmap_equal(const unsigned long *, const unsigned long *, size_t n);
+size_t bitmap_scan(const unsigned long int *, size_t start, size_t end);
+
+#define BITMAP_FOR_EACH_1(IDX, SIZE, BITMAP) \
+    for ((IDX) = bitmap_scan(BITMAP, 0, SIZE); (IDX) < (SIZE); \
+         (IDX) = bitmap_scan(BITMAP, (IDX) + 1, SIZE))
 
 #endif /* bitmap.h */
similarity index 81%
rename from extras/ezio/byteq.c
rename to lib/byteq.c
index b7df400..aa1e06f 100644 (file)
@@ -14,7 +14,7 @@
  */
 
 #include <config.h>
-#include "extras/ezio/byteq.h"
+#include "byteq.h"
 #include <assert.h>
 #include <errno.h>
 #include <string.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)
@@ -73,7 +66,7 @@ void
 byteq_put(struct byteq *q, uint8_t c)
 {
     assert(!byteq_is_full(q));
-    *head(q) = c;
+    *byteq_head(q) = c;
     q->head++;
 }
 
@@ -85,9 +78,9 @@ 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);
+        size_t chunk = MIN(n, byteq_headroom(q));
+        memcpy(byteq_head(q), p, chunk);
+        byteq_advance_head(q, chunk);
         p += chunk;
         n -= chunk;
     }
@@ -108,7 +101,7 @@ byteq_get(struct byteq *q)
 {
     uint8_t c;
     assert(!byteq_is_empty(q));
-    c = *tail(q);
+    c = *byteq_tail(q);
     q->tail++;
     return c;
 }
@@ -120,9 +113,9 @@ int
 byteq_write(struct byteq *q, int fd)
 {
     while (!byteq_is_empty(q)) {
-        ssize_t n = write(fd, tail(q), tailroom(q));
+        ssize_t n = write(fd, byteq_tail(q), byteq_tailroom(q));
         if (n > 0) {
-            advance_tail(q, n);
+            byteq_advance_tail(q, n);
         } else {
             assert(n < 0);
             return errno;
@@ -139,20 +132,20 @@ int
 byteq_read(struct byteq *q, int fd)
 {
     while (!byteq_is_full(q)) {
-        ssize_t n = read(fd, head(q), headroom(q));
+        ssize_t n = read(fd, byteq_head(q), byteq_headroom(q));
         if (n > 0) {
-            advance_head(q, n);
+            byteq_advance_head(q, n);
         } else {
             return !n ? EOF : errno;
         }
     }
     return 0;
 }
-\f
+
 /* Returns the number of contiguous bytes of in-use space starting at the tail
  * of 'q'. */
-static int
-tailroom(const struct byteq *q)
+int
+byteq_tailroom(const struct byteq *q)
 {
     int used = byteq_used(q);
     int tail_to_end = BYTEQ_SIZE - (q->tail & (BYTEQ_SIZE - 1));
@@ -161,33 +154,33 @@ tailroom(const struct byteq *q)
 
 /* 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)
+const uint8_t *
+byteq_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)
+void
+byteq_advance_tail(struct byteq *q, unsigned int n)
 {
-    assert(tailroom(q) >= n);
+    assert(byteq_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)
+uint8_t *
+byteq_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
+byteq_headroom(const struct byteq *q)
 {
     int avail = byteq_avail(q);
     int head_to_end = BYTEQ_SIZE - (q->head & (BYTEQ_SIZE - 1));
@@ -196,9 +189,9 @@ headroom(const struct byteq *q)
 
 /* 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)
+void
+byteq_advance_head(struct byteq *q, unsigned int n)
 {
-    assert(headroom(q) >= n);
+    assert(byteq_headroom(q) >= n);
     q->head += n;
 }
similarity index 83%
rename from extras/ezio/byteq.h
rename to lib/byteq.h
index 0a016cd..84d8696 100644 (file)
@@ -42,4 +42,11 @@ uint8_t byteq_get(struct byteq *);
 int byteq_write(struct byteq *, int fd);
 int byteq_read(struct byteq *, int fd);
 
+uint8_t *byteq_head(struct byteq *);
+int byteq_headroom(const struct byteq *);
+void byteq_advance_head(struct byteq *, unsigned int n);
+int byteq_tailroom(const struct byteq *);
+const uint8_t *byteq_tail(const struct byteq *);
+void byteq_advance_tail(struct byteq *, unsigned int n);
+
 #endif /* byteq.h */
diff --git a/lib/cfg.c b/lib/cfg.c
deleted file mode 100644 (file)
index d0170b6..0000000
--- a/lib/cfg.c
+++ /dev/null
@@ -1,1193 +0,0 @@
-/* Copyright (c) 2008, 2009 Nicira Networks
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-#include "cfg.h"
-#include <arpa/inet.h>
-#include <assert.h>
-#include <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <fnmatch.h>
-#include <inttypes.h>
-#include <netinet/in.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include "coverage.h"
-#include "dynamic-string.h"
-#include "ofpbuf.h"
-#include "packets.h"
-#include "svec.h"
-#include "timeval.h"
-#include "util.h"
-
-#define THIS_MODULE VLM_cfg
-#include "vlog.h"
-
-/* XXX This file really needs a unit test!  For a while, cfg_get_string(0,
- * "bridge.a.controller") would return the value of
- * "bridge.a.controller.in-band", if it existed, and I'm really not certain
- * that the fix didn't break other things. */
-
-/* Configuration file name. */
-static char *cfg_name;
-
-/* Put the temporary file in the same directory as cfg_name, so that
- * they are guaranteed to be in the same file system and therefore we can
- * rename() tmp_name over cfg_name. */
-static char *tmp_name;
-
-/* Lock information. */
-static char *lock_name;
-static int lock_fd = -1;
-
-/* Flag to indicate whether local modifications have been made. */
-static bool dirty;
-
-static uint8_t cfg_cookie[CFG_COOKIE_LEN];
-
-/* Current configuration.  Maintained in sorted order. */
-static struct svec cfg = SVEC_EMPTY_INITIALIZER;
-
-static bool has_double_dot(const char *key, size_t len);
-static bool is_valid_key(const char *key, size_t len,
-                         const char *file_name, int line_number,
-                         const char *id);
-static char *parse_section(const char *file_name, int line_number,
-                           const char *);
-static void parse_setting(const char *file_name, int line_number,
-                          const char *section, const char *);
-static int compare_key(const char *a, const char *b);
-static char **find_key_le(const char *key);
-static char **find_key_ge(const char *key);
-static char *find_key(const char *);
-static bool parse_mac(const char *, uint8_t mac[6]);
-static bool parse_dpid(const char *, uint64_t *);
-static bool is_key(const char *);
-static bool is_int(const char *);
-static bool is_bool(const char *);
-static const char *extract_value(const char *key);
-static const char *get_nth_value(int idx, const char *key);
-static bool is_type(const char *s, enum cfg_flags);
-
-#define CC_ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
-#define CC_DIGIT "0123456789"
-#define CC_ALNUM CC_ALPHA CC_DIGIT
-#define CC_SPACE " \t\r\n\v"
-
-#define CC_FILE_NAME CC_ALNUM "._-"
-#define CC_KEY CC_ALNUM "._-@$:+"
-
-void
-cfg_init(void)
-{
-    svec_terminate(&cfg);
-}
-
-/* Sets 'file_name' as the configuration file read by cfg_read().  Returns 0 on
- * success, otherwise a positive errno value if 'file_name' cannot be opened.
- *
- * This function does not actually read the named file or directory.  Use
- * cfg_read() to (re)read all the configuration files. */
-int
-cfg_set_file(const char *file_name)
-{
-    const char *slash;
-    int fd;
-
-    if (cfg_name) {
-        assert(lock_fd < 0);
-        free(cfg_name);
-        free(lock_name);
-        free(tmp_name);
-        cfg_name = lock_name = tmp_name = NULL;
-    }
-
-    /* Make sure that we can open this file for reading. */
-    fd = open(file_name, O_RDONLY);
-    if (fd < 0) {
-        return errno;
-    }
-    close(fd);
-
-    cfg_name = xstrdup(file_name);
-
-    /* Put the temporary file in the same directory as cfg_name, so that they
-     * are guaranteed to be in the same file system, to guarantee that
-     * rename(tmp_name, cfg_name) will work. */
-    tmp_name = xasprintf("%s.~tmp~", file_name);
-
-    /* Put the lock file in the same directory as cfg_name, but prefixed by
-     * a dot so as not to garner administrator interest. */
-    slash = strrchr(file_name, '/');
-    if (slash) {
-        lock_name = xasprintf("%.*s/.%s.~lock~",
-                              (int) (slash - file_name), file_name, slash + 1);
-    } else {
-        lock_name = xasprintf(".%s.~lock~", file_name);
-    }
-
-    VLOG_INFO("using \"%s\" as configuration file, \"%s\" as lock file",
-              file_name, lock_name);
-    return 0;
-}
-
-static int
-update_cookie(void)
-{
-    struct sha1_ctx context;
-    int i;
-
-    sha1_init(&context);
-    for (i = 0; i < cfg.n; i++) {
-        sha1_update(&context, cfg.names[i], strlen(cfg.names[i]));
-        sha1_update(&context, "\n", 1);
-    }
-    sha1_final(&context, cfg_cookie);
-
-    return 0;
-}
-
-/* Reads all of the configuration files or directories that have been added
- * with cfg_add_file(), merges their content.  Any previous configuration is
- * replaced.  Returns 0 if successful, otherwise a positive errno value. */
-int
-cfg_read(void)
-{
-    struct svec old_cfg;
-    struct ds ds;
-    FILE *file;
-    char *section;
-    int line_number;
-
-
-    if (!cfg_name) {
-        return ENODEV;
-    }
-
-    /* Save old configuration data and clear the active configuration. */
-    svec_init(&old_cfg);
-    svec_swap(&old_cfg, &cfg);
-
-    /* Read new configuration. */
-    VLOG_DBG("reading configuration from %s", cfg_name);
-
-    file = fopen(cfg_name, "r");
-    if (!file) {
-        VLOG_ERR("failed to open \"%s\": %s", cfg_name, strerror(errno));
-        svec_terminate(&cfg);
-        return errno;
-    }
-
-    ds_init(&ds);
-    section = NULL;
-    line_number = 0;
-    while (!ds_get_line(&ds, file)) {
-        const char *s = ds_cstr(&ds);
-        size_t indent = strspn(s, CC_SPACE);
-
-        line_number++;
-        s += indent;
-        if (*s == '#' || *s == '\0') {
-            /* Ignore comments and lines that contain only white space. */
-        } else if (*s == '[') {
-            if (!indent) {
-                free(section);
-                section = parse_section(cfg_name, line_number, s);
-            } else {
-                VLOG_ERR("%s:%d: ignoring indented section header",
-                         cfg_name, line_number);
-            }
-        } else if (indent && !section) {
-            VLOG_ERR("%s:%d: ignoring indented line outside any section",
-                     cfg_name, line_number);
-        } else {
-            if (!indent) {
-                free(section);
-                section = NULL;
-            }
-            parse_setting(cfg_name, line_number, section, s);
-        }
-    }
-    ds_destroy(&ds);
-    free(section);
-
-    svec_sort(&cfg);
-    svec_terminate(&cfg);
-    update_cookie();
-
-    fclose(file);
-
-    if (VLOG_IS_DBG_ENABLED()) {
-        struct svec removed, added;
-        size_t i;
-
-        svec_diff(&old_cfg, &cfg, &removed, NULL, &added);
-        if (removed.n || added.n) {
-            VLOG_DBG("configuration changes:");
-            for (i = 0; i < removed.n; i++) {
-                VLOG_DBG("-%s", removed.names[i]);
-            }
-            for (i = 0; i < added.n; i++) {
-                VLOG_DBG("+%s", added.names[i]);
-            }
-        } else {
-            VLOG_DBG("configuration unchanged");
-        }
-        svec_destroy(&added);
-        svec_destroy(&removed);
-    }
-    svec_destroy(&old_cfg);
-
-    dirty = false;
-
-    return 0;
-}
-
-/* Fills 'svec' with the entire configuration file. */
-void
-cfg_get_all(struct svec *svec)
-{
-    svec_clear(svec);
-    svec_append(svec, &cfg);
-}
-
-int
-cfg_get_cookie(uint8_t *cookie)
-{
-    if (dirty) {
-        update_cookie();
-    }
-
-    memcpy(cookie, cfg_cookie, sizeof(cfg_cookie));
-    return 0;
-}
-
-void
-cfg_unlock(void)
-{
-    if (lock_fd != -1) {
-        COVERAGE_INC(cfg_unlock);
-        close(lock_fd);
-        lock_fd = -1;
-    }
-}
-
-static int
-open_lockfile(const char *name)
-{
-    for (;;) {
-        /* Try to open an existing lock file. */
-        int fd = open(name, O_RDWR);
-        if (fd >= 0) {
-            return fd;
-        } else if (errno != ENOENT) {
-            VLOG_WARN("%s: failed to open lock file: %s",
-                      name, strerror(errno));
-            return -errno;
-        }
-
-        /* Try to create a new lock file. */
-        VLOG_INFO("%s: lock file does not exist, creating", name);
-        fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
-        if (fd >= 0) {
-            return fd;
-        } else if (errno != EEXIST) {
-            VLOG_WARN("%s: failed to create lock file: %s",
-                      name, strerror(errno));
-            return -errno;
-        }
-
-        /* Someone else created the lock file.  Try again. */
-    }
-}
-
-static int
-try_lock(int fd, bool block)
-{
-    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, block ? F_SETLKW : F_SETLK, &l) == -1 ? errno : 0;
-}
-
-/* Locks the configuration file against modification by other processes and
- * re-reads it from disk.
- *
- * The 'timeout' specifies the maximum number of milliseconds to wait for the
- * config file to become free.  Use 0 to avoid waiting or INT_MAX to wait
- * forever.
- *
- * Returns 0 on success, otherwise a positive errno value. */
-int
-cfg_lock(uint8_t *cookie, int timeout)
-{
-    long long int start;
-    long long int elapsed = 0;
-    int fd;
-    uint8_t curr_cookie[CFG_COOKIE_LEN];
-
-    assert(lock_fd < 0);
-    COVERAGE_INC(cfg_lock);
-
-    time_refresh();
-    start = time_msec();
-    for (;;) {
-        int error;
-
-        /* Open lock file. */
-        fd = open_lockfile(lock_name);
-        if (fd < 0) {
-            return -fd;
-        }
-
-        /* Try to lock it.  This will block (if 'timeout' > 0). */
-        error = try_lock(fd, timeout > 0);
-        time_refresh();
-        elapsed = time_msec() - start;
-        if (!error) {
-            /* Success! */
-            break;
-        }
-
-        /* Lock failed.  Close the lock file and reopen it on the next
-         * iteration, just in case someone deletes it underneath us (even
-         * though that should not happen). */
-        close(fd);
-        if (error != EINTR) {
-            /* Hard error, give up. */
-            COVERAGE_INC(cfg_lock_error);
-            VLOG_WARN("%s: failed to lock file "
-                      "(after %lld ms, with %d-ms timeout): %s",
-                      lock_name, elapsed, timeout, strerror(error));
-            return error;
-        }
-
-        /* Probably, the periodic timer set up by time_init() woke up us.  Just
-         * check whether it's time to give up. */
-        if (timeout != INT_MAX && elapsed >= timeout) {
-            COVERAGE_INC(cfg_lock_timeout);
-            VLOG_WARN("%s: giving up on lock file after %lld ms",
-                      lock_name, elapsed);
-            return ETIMEDOUT;
-        }
-        COVERAGE_INC(cfg_lock_retry);
-    }
-    if (elapsed) {
-        VLOG_WARN("%s: waited %lld ms for lock file", lock_name, elapsed);
-    }
-    lock_fd = fd;
-
-    cfg_read();
-
-    if (cookie) {
-        cfg_get_cookie(curr_cookie);
-
-        if (memcmp(curr_cookie, cookie, sizeof *curr_cookie)) {
-            /* Configuration has changed, so reject. */
-            cfg_unlock();
-            return EINVAL;
-        }
-    }
-
-    return 0;
-}
-
-static int
-do_write_config(const void *data, size_t len)
-{
-    FILE *file;
-    int error;
-
-    file = fopen(tmp_name, "w");
-    if (file == NULL) {
-        VLOG_WARN("could not open %s for writing: %s",
-                  tmp_name, strerror(errno));
-        return errno;
-    }
-
-    fwrite(data, 1, len, file);
-
-    /* This is essentially equivalent to:
-     *       error = ferror(file) || fflush(file) || fclose(file);
-     * but it doesn't short-circuit, so that it always closes 'file'. */
-    error = ferror(file);
-    error = fflush(file) || error;
-    error = fclose(file) || error;
-    if (error) {
-        VLOG_WARN("problem writing to %s: %s", tmp_name, strerror(errno));
-        return errno;
-    }
-
-    if (rename(tmp_name, cfg_name) < 0) {
-        VLOG_WARN("could not rename %s to %s: %s",
-                  tmp_name, cfg_name, strerror(errno));
-        return errno;
-    }
-
-    dirty = false;
-
-    return 0;
-}
-
-/* Write the current configuration into the configuration file.  Returns 0 if
- * successful, otherwise a negative errno value. */
-int
-cfg_write(void)
-{
-    char *content;
-    int retval;
-
-    svec_sort(&cfg);
-    content = (cfg.n
-               ? svec_join(&cfg, "\n", "\n")
-               : xstrdup("# This file intentionally left blank.\n"));
-    retval = do_write_config(content, strlen(content));
-    free(content);
-
-    return retval;
-}
-
-int
-cfg_write_data(uint8_t *data, size_t len)
-{
-    int retval = do_write_config(data, len);
-    if (!retval) {
-        cfg_read();
-    }
-    return retval;
-}
-
-/* Returns true if the configuration has changed since the last time it was
- * read or written. */
-bool
-cfg_is_dirty(void)
-{
-    return dirty;
-}
-
-void
-cfg_buf_put(struct ofpbuf *buffer)
-{
-    int i;
-
-    for (i = 0; i < cfg.n; i++) {
-        ofpbuf_put(buffer, cfg.names[i], strlen(cfg.names[i]));
-        ofpbuf_put(buffer, "\n", 1);
-    }
-}
-
-/* Formats the printf()-style format string in the parameter 'format', which
- * must be the function's last parameter, into string variable 'dst'.  The
- * function is responsible for freeing 'dst'. */
-#define FORMAT_KEY(FORMAT, DST)                 \
-    do {                                        \
-      va_list args__;                           \
-      va_start(args__, FORMAT);                 \
-      (DST) = xvasprintf(FORMAT, args__);       \
-      va_end(args__);                           \
-    } while (0)
-
-/* Returns true if the configuration includes a key named 'key'. */
-bool
-cfg_has(const char *key_, ...)
-{
-    char *key;
-    bool retval;
-
-    FORMAT_KEY(key_, key);
-    retval = find_key(key) != NULL;
-    free(key);
-    return retval;
-}
-
-bool
-cfg_is_valid(enum cfg_flags flags, const char *key_, ...)
-{
-    char *key, **first, **last, **p;
-    size_t n;
-    bool retval;
-
-    FORMAT_KEY(key_, key);
-    first = find_key_le(key);
-    last = find_key_ge(key);
-    n = last - first;
-    retval = ((!(flags & CFG_REQUIRED) || n)
-              && (!(flags & CFG_MULTIPLE) || n <= 1));
-    for (p = first; retval && p < last; p++) {
-        retval = is_type(strchr(*p, '=') + 1, flags);
-    }
-    free(key);
-    return retval;
-}
-
-/* Returns true if the configuration includes at least one key whose name
- * begins with 'section' followed by a dot. */
-bool
-cfg_has_section(const char *section_, ...)
-{
-    struct ds section;
-    bool retval = false;
-    va_list args;
-    char **p;
-
-    ds_init(&section);
-    va_start(args, section_);
-    ds_put_format_valist(&section, section_, args);
-    ds_put_char(&section, '.');
-    va_end(args);
-
-    for (p = cfg.names; *p; p++) { /* XXX this is inefficient */
-        if (!strncmp(section.string, *p, section.length)) {
-            retval = true;
-            break;
-        }
-    }
-
-    ds_destroy(&section);
-    return retval;
-}
-
-/* Returns the number of values for the given 'key'.  The return value is 0 if
- * no values exist for 'key'. */
-int
-cfg_count(const char *key_, ...)
-{
-    char *key;
-    int retval;
-
-    FORMAT_KEY(key_, key);
-    retval = find_key_ge(key) - find_key_le(key);
-    free(key);
-    return retval;
-}
-
-/* Fills 'svec' with all of the immediate subsections of 'section'.  For
- * example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c,
- * and bridge.c.x.y.z exist, then 'svec' would be initialized to a, b, and
- * c.  The caller must first initialize 'svec'. */
-void
-cfg_get_subsections(struct svec *svec, const char *section_, ...)
-{
-    struct ds section;
-    va_list args;
-    char **p;
-
-    ds_init(&section);
-    va_start(args, section_);
-    ds_put_format_valist(&section, section_, args);
-    ds_put_char(&section, '.');
-    va_end(args);
-
-    svec_clear(svec);
-    for (p = cfg.names; *p; p++) { /* XXX this is inefficient */
-        if (!strncmp(section.string, *p, section.length)) {
-            const char *ss = *p + section.length;
-            size_t ss_len = strcspn(ss, ".=");
-            svec_add_nocopy(svec, xmemdup0(ss, ss_len));
-        }
-    }
-    svec_unique(svec);
-    ds_destroy(&section);
-}
-
-void
-cfg_add_entry(const char *entry_, ...)
-{
-    char *entry;
-
-    FORMAT_KEY(entry_, entry);
-    svec_add_nocopy(&cfg, entry);
-    svec_sort(&cfg);
-    svec_terminate(&cfg);
-    dirty = true;
-}
-
-void
-cfg_del_entry(const char *entry_, ...)
-{
-    char *entry;
-
-    FORMAT_KEY(entry_, entry);
-    svec_del(&cfg, entry);
-    svec_terminate(&cfg);
-    free(entry);
-    dirty = true;
-}
-
-void
-cfg_del_section(const char *section_, ...)
-{
-    struct ds section;
-    va_list args;
-    char **p;
-
-    ds_init(&section);
-    va_start(args, section_);
-    ds_put_format_valist(&section, section_, args);
-    ds_put_char(&section, '.');
-    va_end(args);
-
-    for (p = cfg.names; *p; p++) {
-        if (!strncmp(section.string, *p, section.length)) {
-            free(*p);
-            *p = NULL;
-        }
-    }
-    svec_compact(&cfg);
-    svec_terminate(&cfg);
-
-    ds_destroy(&section);
-    dirty = true;
-}
-
-void
-cfg_del_match(const char *pattern_, ...)
-{
-    bool matched = false;
-    char *pattern;
-    char **p;
-
-    FORMAT_KEY(pattern_, pattern);
-
-    for (p = cfg.names; *p; p++) {
-        if (!fnmatch(pattern, *p, 0)) {
-            free(*p);
-            *p = NULL;
-            matched = true;
-        }
-    }
-    if (matched) {
-        svec_compact(&cfg);
-        svec_terminate(&cfg);
-        dirty = true;
-    }
-
-    free(pattern);
-}
-
-/* Fills 'svec' with all of the key-value pairs that match shell glob pattern
- * 'pattern'.  The caller must first initialize 'svec'. */
-void
-cfg_get_matches(struct svec *svec, const char *pattern_, ...)
-{
-    char *pattern;
-    char **p;
-
-    FORMAT_KEY(pattern_, pattern);
-
-    for (p = cfg.names; *p; p++) {
-        if (!fnmatch(pattern, *p, 0)) {
-            svec_add(svec, *p);
-        }
-    }
-
-    free(pattern);
-}
-
-/* Fills 'svec' with all of the key-value pairs that have sections that
- * begin with 'section'.  The caller must first initialize 'svec'. */
-void
-cfg_get_section(struct svec *svec, const char *section_, ...)
-{
-    struct ds section;
-    va_list args;
-    char **p;
-
-    ds_init(&section);
-    va_start(args, section_);
-    ds_put_format_valist(&section, section_, args);
-    ds_put_char(&section, '.');
-    va_end(args);
-
-    for (p = cfg.names; *p; p++) { /* XXX this is inefficient */
-        if (!strncmp(section.string, *p, section.length)) {
-            svec_add(svec, *p);
-        }
-    }
-    ds_destroy(&section);
-}
-
-/* Returns the value numbered 'idx' of 'key'.  Returns a null pointer if 'idx'
- * is greater than or equal to cfg_count(key).  The caller must not modify or
- * free the returned string or retain its value beyond the next call to
- * cfg_read(). */
-const char *
-cfg_get_string(int idx, const char *key_, ...)
-{
-    const char *retval;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    retval = get_nth_value(idx, key);
-    free(key);
-    return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key'.  Returns a null pointer if 'idx'
- * is greater than or equal to cfg_count(key) or if the value 'idx' of 'key' is
- * not a valid key.  The caller must not modify or free the returned string or
- * retain its value beyond the next call to cfg_read(). */
-const char *
-cfg_get_key(int idx, const char *key_, ...)
-{
-    const char *value, *retval;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    retval = value && is_key(value) ? value : NULL;
-    free(key);
-    return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an integer.  Returns
- * 0 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx'
- * of 'key' is not a valid integer.  */
-int
-cfg_get_int(int idx, const char *key_, ...)
-{
-    const char *value;
-    int retval;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    retval = value && is_int(value) ? atoi(value) : 0;
-    free(key);
-    return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to a boolean value.
- * Returns false if 'idx' is greater than or equal to cfg_count(key) or if the
- * value 'idx' of 'key' is not a valid boolean.  */
-bool
-cfg_get_bool(int idx, const char *key_, ...)
-{
-    const char *value;
-    bool retval;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    retval = value && is_bool(value) ? !strcmp(value, "true") : false;
-    free(key);
-    return retval;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an IP address in
- * network byte order.  Returns 0 if 'idx' is greater than or equal to
- * cfg_count(key) or if the value 'idx' of 'key' is not a valid IP address (as
- * determined by inet_aton()).  */
-uint32_t
-cfg_get_ip(int idx, const char *key_, ...)
-{
-    struct in_addr addr;
-    const char *value;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    if (!value || !inet_aton(value, &addr)) {
-        addr.s_addr = htonl(0);
-    }
-    free(key);
-    return addr.s_addr;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an MAC address in
- * host byte order.  Returns 0 if 'idx' is greater than or equal to
- * cfg_count(key) or if the value 'idx' of 'key' is not a valid MAC address in
- * the format "##:##:##:##:##:##".  */
-uint64_t
-cfg_get_mac(int idx, const char *key_, ...)
-{
-    uint8_t mac[ETH_ADDR_LEN];
-    const char *value;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    if (!value || !parse_mac(value, mac)) {
-        memset(mac, 0, sizeof mac);
-    }
-    free(key);
-    return eth_addr_to_uint64(mac);
-}
-
-/* Returns the value numbered 'idx' of 'key', parsed as an datapath ID.
- * Returns 0 if 'idx' is greater than or equal to cfg_count(key) or if the
- * value 'idx' of 'key' is not a valid datapath ID consisting of exactly 12
- * hexadecimal digits.  */
-uint64_t
-cfg_get_dpid(int idx, const char *key_, ...)
-{
-    uint64_t dpid;
-    const char *value;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    if (!value || !parse_dpid(value, &dpid)) {
-        dpid = 0;
-    }
-    free(key);
-    return dpid;
-}
-
-/* Returns the value numbered 'idx' of 'key', converted to an integer.  Returns
- * -1 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx'
- * of 'key' is not a valid integer between 0 and 4095.  */
-int
-cfg_get_vlan(int idx, const char *key_, ...)
-{
-    const char *value;
-    int retval;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    value = get_nth_value(idx, key);
-    if (value && is_int(value)) {
-        retval = atoi(value);
-        if (retval < 0 || retval > 4095) {
-            retval = -1;
-        }
-    } else {
-        retval = -1;
-    }
-    free(key);
-    return retval;
-}
-
-/* Fills 'svec' with all of the string values of 'key'.  The caller must
- * first initialize 'svec'. */
-void
-cfg_get_all_strings(struct svec *svec, const char *key_, ...)
-{
-    char **p, **q;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    svec_clear(svec);
-    for (p = find_key_le(key), q = find_key_ge(key); p < q; p++) {
-        svec_add(svec, extract_value(*p));
-    }
-    free(key);
-}
-
-/* Fills 'svec' with all of the values of 'key' that are valid keys.
- * Values of 'key' that are not valid keys are omitted.   The caller 
- * must first initialize 'svec'. */
-void
-cfg_get_all_keys(struct svec *svec, const char *key_, ...)
-{
-    char **p, **q;
-    char *key;
-
-    FORMAT_KEY(key_, key);
-    svec_clear(svec);
-    for (p = find_key_le(key), q = find_key_ge(key); p < q; p++) {
-        const char *value = extract_value(*p);
-        if (is_key(value)) {
-            svec_add(svec, value);
-        }
-    }
-    free(key);
-}
-\f
-static bool
-has_double_dot(const char *key, size_t len)
-{
-    if (len >= 2) {
-        size_t i;
-
-        for (i = 0; i < len - 1; i++) {
-            if (key[i] == '.' && key[i + 1] == '.') {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
-static bool
-is_valid_key(const char *key, size_t len,
-                 const char *file_name, int line_number, const char *id)
-{
-    if (!len) {
-        VLOG_ERR("%s:%d: missing %s name", file_name, line_number, id);
-        return false;
-    } else if (key[0] == '.') {
-        VLOG_ERR("%s:%d: %s name \"%.*s\" begins with invalid character '.'",
-                 file_name, line_number, id, (int) len, key);
-        return false;
-    } else if (key[len - 1] == '.') {
-        VLOG_ERR("%s:%d: %s name \"%.*s\" ends with invalid character '.'",
-                 file_name, line_number, id, (int) len, key);
-        return false;
-    } else if (has_double_dot(key, len)) {
-        VLOG_ERR("%s:%d: %s name \"%.*s\" contains '..', which is not allowed",
-                 file_name, line_number, id, (int) len, key);
-        return false;
-    } else {
-        return true;
-    }
-}
-
-static char *
-parse_section(const char *file_name, int line_number, const char *s)
-{
-    struct ds section;
-    size_t len;
-
-    ds_init(&section);
-
-    /* Skip [ and any white space. */
-    s++;
-    s += strspn(s, CC_SPACE);
-
-    /* Obtain the section name. */
-    len = strspn(s, CC_KEY);
-    if (!is_valid_key(s, len, file_name, line_number, "section")) {
-        goto error;
-    }
-    ds_put_buffer(&section, s, len);
-    s += len;
-
-    /* Obtain the subsection name, if any. */
-    s += strspn(s, CC_SPACE);
-    if (*s == '"') {
-        s++;
-        len = strspn(s, CC_KEY);
-        if (!is_valid_key(s, len, file_name, line_number, "subsection")) {
-            goto error;
-        }
-        ds_put_char(&section, '.');
-        ds_put_buffer(&section, s, len);
-        s += len;
-        if (*s != '"') {
-            VLOG_ERR("%s:%d: missing '\"' following subsection name",
-                     file_name, line_number);
-            goto error;
-        }
-        s++;
-        s += strspn(s, CC_SPACE);
-    }
-
-    /* Check for ]. */
-    if (*s != ']') {
-        VLOG_ERR("%s:%d: missing ']' following section name",
-                 file_name, line_number);
-        goto error;
-    }
-    s++;
-    s += strspn(s, CC_SPACE);
-    if (*s != '\0') {
-        VLOG_ERR("%s:%d: trailing garbage following ']'",
-                 file_name, line_number);
-        goto error;
-    }
-
-    return ds_cstr(&section);
-
-error:
-    ds_destroy(&section);
-    return NULL;
-}
-
-static void
-parse_setting(const char *file_name, int line_number, const char *section,
-              const char *s)
-{
-    struct ds key = DS_EMPTY_INITIALIZER;
-    struct ds value = DS_EMPTY_INITIALIZER;
-    size_t len;
-
-    if (section) {
-        ds_put_format(&key, "%s.", section);
-    }
-
-    /* Obtain the key. */
-    len = strspn(s, CC_KEY);
-    if (!len) {
-        VLOG_ERR("%s:%d: missing key name", file_name, line_number);
-        goto done;
-    }
-    if (!is_valid_key(s, len, file_name, line_number, "key")) {
-        goto done;
-    }
-    ds_put_buffer(&key, s, len);
-    s += len;
-
-    /* Skip the '='. */
-    s += strspn(s, CC_SPACE);
-    if (*s != '=') {
-        VLOG_ERR("%s:%d: missing '=' following key", file_name, line_number);
-        goto done;
-    }
-    s++;
-    s += strspn(s, CC_SPACE);
-
-    /* Obtain the value. */
-    ds_put_cstr(&value, s);
-    while (value.length > 0 && strchr(CC_SPACE, ds_last(&value))) {
-        value.length--;
-    }
-
-    /* Add the setting. */
-    svec_add_nocopy(&cfg, xasprintf("%s=%s", ds_cstr(&key), ds_cstr(&value)));
-
-done:
-    ds_destroy(&key);
-    ds_destroy(&value);
-}
-
-static int
-compare_key(const char *a, const char *b)
-{
-    for (;;) {
-        int ac = *a == '\0' || *a == '=' ? INT_MAX : *a;
-        int bc = *b == '\0' || *b == '=' ? INT_MAX : *b;
-        if (ac != bc) {
-            return ac < bc ? -1 : 1;
-        } else if (ac == INT_MAX) {
-            return 0;
-        }
-        a++;
-        b++;
-    }
-}
-
-/* Returns the address of the greatest configuration string with a key less
- * than or equal to 'key'.  Returns the address of the null terminator if all
- * configuration strings are greater than 'key'. */
-static char **
-find_key_le(const char *key)
-{
-    int low = 0;
-    int len = cfg.n;
-    while (len > 0) {
-        int half = len >> 1;
-        int middle = low + half;
-        if (compare_key(cfg.names[middle], key) < 0) {
-            low = middle + 1;
-            len -= half + 1;
-        } else {
-            len = half;
-        }
-    }
-    return &cfg.names[low];
-}
-
-/* Returns the address of the least configuration string with a key greater
- * than or equal to 'key'.  Returns the address of the null terminator if all
- * configuration strings are less than 'key'. */
-static char **
-find_key_ge(const char *key)
-{
-    int low = 0;
-    int len = cfg.n;
-    while (len > 0) {
-        int half = len >> 1;
-        int middle = low + half;
-        if (compare_key(cfg.names[middle], key) > 0) {
-            len = half;
-        } else {
-            low = middle + 1;
-            len -= half + 1;
-        }
-    }
-    return &cfg.names[low];
-}
-
-static char *
-find_key(const char *key)
-{
-    char **p = find_key_le(key);
-    return p < &cfg.names[cfg.n] && !compare_key(*p, key) ? *p : NULL;
-}
-
-static bool
-parse_mac(const char *s, uint8_t mac[6])
-{
-    return (sscanf(s, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))
-            == ETH_ADDR_SCAN_COUNT);
-}
-
-static bool
-parse_dpid(const char *s, uint64_t *dpid)
-{
-    if (strlen(s) == 12 && strspn(s, "0123456789abcdefABCDEF") == 12) {
-        *dpid = strtoll(s, NULL, 16);
-        return true;
-    } else {
-        return false;
-    }
-}
-
-static bool
-is_key(const char *s)
-{
-    /* XXX needs to check the same things as is_valid_key() too. */
-    return *s && s[strspn(s, CC_KEY)] == '\0';
-}
-
-static bool
-is_int(const char *s)
-{
-    return *s && s[strspn(s, CC_DIGIT)] == '\0';
-}
-
-static bool
-is_bool(const char *s)
-{
-    return !strcmp(s, "true") || !strcmp(s, "false");
-}
-
-static const char *
-extract_value(const char *key)
-{
-    const char *p = strchr(key, '=');
-    return p ? p + 1 : NULL;
-}
-
-static const char *
-get_nth_value(int idx, const char *key)
-{
-    char **p = find_key_le(key);
-    char **q = find_key_ge(key);
-    return idx < q - p ? extract_value(p[idx]) : NULL;
-}
-
-static bool
-is_type(const char *s, enum cfg_flags flags)
-{
-    uint8_t mac[ETH_ADDR_LEN];
-    struct in_addr addr;
-    uint64_t dpid;
-
-    return (flags & CFG_STRING
-            || (flags & CFG_KEY && is_key(s))
-            || (flags & CFG_INT && is_int(s))
-            || (flags & CFG_BOOL && is_bool(s))
-            || (flags & CFG_IP && inet_aton(s, &addr))
-            || (flags & CFG_MAC && parse_mac(s, mac))
-            || (flags & CFG_DPID && parse_dpid(s, &dpid)));
-}
diff --git a/lib/cfg.h b/lib/cfg.h
deleted file mode 100644 (file)
index e159244..0000000
--- a/lib/cfg.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/* Copyright (c) 2008, 2009 Nicira Networks
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#ifndef VSWITCHD_CFG_H
-#define VSWITCHD_CFG_H 1
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <unistd.h>
-#include "compiler.h"
-#include "sha1.h"
-
-struct svec;
-struct ofpbuf;
-
-void cfg_init(void);
-int cfg_set_file(const char *file_name);
-int cfg_read(void);
-int cfg_lock(uint8_t *cookie, int timeout);
-void cfg_unlock(void);
-int cfg_write(void);
-int cfg_write_data(uint8_t *data, size_t len);
-bool cfg_is_dirty(void);
-
-void cfg_get_all(struct svec *);
-
-#define CFG_COOKIE_LEN SHA1_DIGEST_SIZE
-int cfg_get_cookie(uint8_t *cookie);
-
-void cfg_buf_put(struct ofpbuf *buffer);
-void cfg_get_subsections(struct svec *, const char *, ...) PRINTF_FORMAT(2, 3);
-
-enum cfg_flags {
-    /* Types allowed. */
-    CFG_STRING = 1 << 0,        /* Arbitrary content. */
-    CFG_KEY = 1 << 0,           /* Valid key name. */
-    CFG_INT = 1 << 2,           /* Integer value. */
-    CFG_BOOL = 1 << 3,          /* Boolean. */
-    CFG_IP = 1 << 4,            /* IPv4 address. */
-    CFG_MAC = 1 << 5,           /* MAC address. */
-    CFG_VLAN = 1 << 6,          /* Integer in range 0...4095. */
-    CFG_DPID = 1 << 7,          /* 12 hexadecimal digits. */
-
-    /* Number allowed. */
-    CFG_REQUIRED = 1 << 8,      /* At least one value allowed. */
-    CFG_MULTIPLE = 1 << 9       /* More than one value allowed. */
-};
-void cfg_register(const char *key_spec, enum cfg_flags);
-
-void cfg_add_entry(const char *key, ...) PRINTF_FORMAT(1, 2);
-void cfg_del_entry(const char *key, ...) PRINTF_FORMAT(1, 2);
-void cfg_del_section(const char *key, ...) PRINTF_FORMAT(1, 2);
-void cfg_del_match(const char *pattern, ...) PRINTF_FORMAT(1, 2);
-void cfg_get_matches(struct svec *svec, const char *pattern, ...)
-    PRINTF_FORMAT(2, 3);
-void cfg_get_section(struct svec *svec, const char *key, ...) 
-    PRINTF_FORMAT(2, 3);
-
-bool cfg_has(const char *key, ...) PRINTF_FORMAT(1, 2);
-bool cfg_is_valid(enum cfg_flags, const char *key, ...) PRINTF_FORMAT(2, 3);
-bool cfg_has_section(const char *key, ...) PRINTF_FORMAT(1, 2);
-int cfg_count(const char *key, ...) PRINTF_FORMAT(1, 2);
-
-const char *cfg_get_string(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-const char *cfg_get_key(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-int cfg_get_int(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-bool cfg_get_bool(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-uint32_t cfg_get_ip(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-uint64_t cfg_get_mac(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-int cfg_get_vlan(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-uint64_t cfg_get_dpid(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
-
-void cfg_get_all_strings(struct svec *, const char *key, ...)
-    PRINTF_FORMAT(2, 3);
-void cfg_get_all_keys(struct svec *, const char *key, ...) PRINTF_FORMAT(2, 3);
-
-#endif /* vswitchd/cfg.h */
index 94c8380..f4280ef 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -102,7 +102,8 @@ cls_rule_moved(struct classifier *cls, struct cls_rule *old,
         if (new->wc.wildcards) {
             list_moved(&new->node.list);
         } else {
-            hmap_moved(&cls->exact_table, &old->node.hmap, &new->node.hmap);
+            hmap_node_moved(&cls->exact_table,
+                            &old->node.hmap, &new->node.hmap);
         }
     }
 }
index 6b79c5e..2f99798 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@
 #include "command-line.h"
 #include <getopt.h>
 #include <limits.h>
+#include <stdlib.h>
 #include "util.h"
-#include "vlog.h"
 
 /* Given the GNU-style long options in 'options', returns a string that may be
  * passed to getopt() with the corresponding short options.  The caller is
@@ -47,3 +47,150 @@ long_options_to_short_options(const struct option options[])
     return xstrdup(short_options);
 }
 
+/* Runs the command designated by argv[0] within the command table specified by
+ * 'commands', which must be terminated by a command whose 'name' member is a
+ * null pointer.
+ *
+ * Command-line options should be stripped off, so that a typical invocation
+ * looks like "run_command(argc - optind, argv + optind, my_commands);". */
+void
+run_command(int argc, char *argv[], const struct command commands[])
+{
+    const struct command *p;
+
+    if (argc < 1) {
+        ovs_fatal(0, "missing command name; use --help for help");
+    }
+
+    for (p = commands; p->name != NULL; p++) {
+        if (!strcmp(p->name, argv[0])) {
+            int n_arg = argc - 1;
+            if (n_arg < p->min_args) {
+                ovs_fatal(0, "'%s' command requires at least %d arguments",
+                          p->name, p->min_args);
+            } else if (n_arg > p->max_args) {
+                ovs_fatal(0, "'%s' command takes at most %d arguments",
+                          p->name, p->max_args);
+            } else {
+                p->handler(argc, argv);
+                if (ferror(stdout)) {
+                    ovs_fatal(0, "write to stdout failed");
+                }
+                if (ferror(stderr)) {
+                    ovs_fatal(0, "write to stderr failed");
+                }
+                return;
+            }
+        }
+    }
+
+    ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
+}
+\f
+/* Process title. */
+
+#ifdef __linux__
+static char *argv_start;       /* Start of command-line arguments in memory. */
+static size_t argv_size;       /* Number of bytes of command-line arguments. */
+static char *saved_proctitle;  /* Saved command-line arguments. */
+
+/* Prepares the process so that proctitle_set() can later succeed.
+ *
+ * This modifies the argv[] array so that it no longer points into the memory
+ * that it originally does.  Later, proctitle_set() might overwrite that
+ * memory.  That means that this function should be called before anything else
+ * that accesses the process's argv[] array.  Ideally, it should be called
+ * before anything else, period, at the very beginning of program
+ * execution.  */
+void
+proctitle_init(int argc, char **argv)
+{
+    int i;
+
+    if (!argc || !argv[0]) {
+        /* This situation should never occur, but... */
+        return;
+    }
+
+    /* Specialized version of first loop iteration below. */
+    argv_start = argv[0];
+    argv_size = strlen(argv[0]) + 1;
+    argv[0] = xstrdup(argv[0]);
+
+    for (i = 1; i < argc; i++) {
+        size_t size = strlen(argv[i]) + 1;
+
+        /* Add (argv[i], strlen(argv[i])+1) to (argv_start, argv_size). */
+        if (argv[i] + size == argv_start) {
+            /* Arguments grow downward in memory. */
+            argv_start -= size;
+            argv_size += size;
+        } else if (argv[i] == argv_start + argv_size) {
+            /* Arguments grow upward in memory. */
+            argv_size += size;
+        } else {
+            /* Arguments not contiguous.  (Is this really Linux?) */
+        }
+
+        /* Copy out the old argument so we can reuse the space. */
+        argv[i] = xstrdup(argv[i]);
+    }
+}
+
+/* Changes the name of the process, as shown by "ps", to 'format', which is
+ * formatted as if by printf(). */
+void
+proctitle_set(const char *format, ...)
+{
+    va_list args;
+    int n;
+
+    if (!argv_start || argv_size < 8) {
+        return;
+    }
+
+    if (!saved_proctitle) {
+        saved_proctitle = xmemdup(argv_start, argv_size);
+    }
+
+    va_start(args, format);
+    n = vsnprintf(argv_start, argv_size, format, args);
+    if (n >= argv_size) {
+        /* The name is too long, so add an ellipsis at the end. */
+        strcpy(&argv_start[argv_size - 4], "...");
+    } else {
+        /* Fill the extra space with null bytes, so that trailing bytes don't
+         * show up in the command line. */
+        memset(&argv_start[n], '\0', argv_size - n);
+    }
+    va_end(args);
+}
+
+/* Restores the process's original command line, as seen by "ps". */
+void
+proctitle_restore(void)
+{
+    if (saved_proctitle) {
+        memcpy(argv_start, saved_proctitle, argv_size);
+        free(saved_proctitle);
+        saved_proctitle = NULL;
+    }
+}
+#else  /* !__linux__ */
+/* Stubs that don't do anything on non-Linux systems. */
+
+void
+proctitle_init(int argc UNUSED, char **argv UNUSED)
+{
+}
+
+void
+proctitle_set(const char *format UNUSED, ...)
+{
+}
+
+void
+proctitle_restore(void)
+{
+}
+#endif  /* !__linux__ */
index 426ce62..1c88003 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 /* Utilities for command-line parsing. */
 
+#include "compiler.h"
+
 struct option;
+
+struct command {
+    const char *name;
+    int min_args;
+    int max_args;
+    void (*handler)(int argc, char *argv[]);
+};
+
 char *long_options_to_short_options(const struct option *options);
+void run_command(int argc, char *argv[], const struct command[]);
+
+void proctitle_init(int argc, char **argv);
+void proctitle_set(const char *, ...)
+    PRINTF_FORMAT(1, 2);
+void proctitle_restore(void);
 
 #endif /* command-line.h */
diff --git a/lib/common-syn.man b/lib/common-syn.man
new file mode 100644 (file)
index 0000000..ae1bed5
--- /dev/null
@@ -0,0 +1,4 @@
+.IP "Common options:"
+[\fB-h\fR | \fB--help\fR]
+[\fB-V\fR | \fB--version\fR]
+
index 17e245f..216dd6a 100644 (file)
@@ -26,5 +26,6 @@
 #define ALWAYS_INLINE __attribute__((always_inline))
 #define likely(x) __builtin_expect((x),1)
 #define unlikely(x) __builtin_expect((x),0)
+#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
 
 #endif /* compiler.h */
index cdc796e..8b5e9d9 100644 (file)
@@ -30,7 +30,8 @@
 static unsigned int epoch;
 
 static void
-coverage_unixctl_log(struct unixctl_conn *conn, const char *args UNUSED)
+coverage_unixctl_log(struct unixctl_conn *conn, const char *args UNUSED,
+                     void *aux UNUSED)
 {
     coverage_log(VLL_WARN, false);
     unixctl_command_reply(conn, 200, NULL);
@@ -39,7 +40,7 @@ coverage_unixctl_log(struct unixctl_conn *conn, const char *args UNUSED)
 void
 coverage_init(void)
 {
-    unixctl_command_register("coverage/log", coverage_unixctl_log);
+    unixctl_command_register("coverage/log", coverage_unixctl_log, NULL);
 }
 
 /* Sorts coverage counters in descending order by count, within equal counts
diff --git a/lib/daemon-syn.man b/lib/daemon-syn.man
new file mode 100644 (file)
index 0000000..4970564
--- /dev/null
@@ -0,0 +1,5 @@
+.IP "Daemon options:"
+[\fB--pidfile\fR[\fB=\fIpidfile\fR]]
+[\fB--overwrite-pidfile\fR]
+[\fB--detach\fR]
+[\fB--no-chdir\fR]
index a35c639..46c9a88 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <fcntl.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/wait.h>
 #include <unistd.h>
+#include "command-line.h"
 #include "fatal-signal.h"
 #include "dirs.h"
+#include "lockfile.h"
+#include "process.h"
+#include "socket-util.h"
 #include "timeval.h"
 #include "util.h"
 
@@ -38,9 +43,16 @@ static char *pidfile;
 /* Create pidfile even if one already exists and is locked? */
 static bool overwrite_pidfile;
 
-/* Should we chdir to "/". */
+/* Should we chdir to "/"? */
 static bool chdir_ = true;
 
+/* File descriptor used by daemonize_start() and daemonize_complete(). */
+static int daemonize_fd = -1;
+
+/* --monitor: Should a supervisory process monitor the daemon and restart it if
+ * it dies due to an error signal? */
+static bool monitor;
+
 /* Returns the file name that would be used for a pidfile if 'name' were
  * provided to set_pidfile().  The caller must free the returned string. */
 char *
@@ -80,6 +92,13 @@ set_no_chdir(void)
     chdir_ = false;
 }
 
+/* Will we chdir to "/" as part of daemonizing? */
+bool
+is_chdir_enabled(void)
+{
+    return chdir_;
+}
+
 /* Normally, die_if_already_running() will terminate the program with a message
  * if a locked pidfile already exists.  If this function is called,
  * die_if_already_running() will merely log a warning. */
@@ -97,6 +116,21 @@ set_detach(void)
     detach = true;
 }
 
+/* Will daemonize() really detach? */
+bool
+get_detach(void)
+{
+    return detach;
+}
+
+/* Sets up a following call to daemonize() to fork a supervisory process to
+ * monitor the daemon and restart it if it dies due to an error signal.  */
+void
+daemon_set_monitor(void)
+{
+    monitor = true;
+}
+
 /* If a pidfile has been configured and that pidfile already exists and is
  * locked by a running process, returns the pid of the running process.
  * Otherwise, returns 0. */
@@ -196,43 +230,209 @@ make_pidfile(void)
 void
 daemonize(void)
 {
-    if (detach) {
-        char c = 0;
-        int fds[2];
-        if (pipe(fds) < 0) {
-            ovs_fatal(errno, "pipe failed");
+    daemonize_start();
+    daemonize_complete();
+}
+
+static pid_t
+fork_and_wait_for_startup(int *fdp)
+{
+    int fds[2];
+    pid_t pid;
+
+    if (pipe(fds) < 0) {
+        ovs_fatal(errno, "pipe failed");
+    }
+
+    pid = fork();
+    if (pid > 0) {
+        /* Running in parent process. */
+        char c;
+
+        close(fds[1]);
+        fatal_signal_fork();
+        if (read(fds[0], &c, 1) != 1) {
+            int retval;
+            int status;
+
+            do {
+                retval = waitpid(pid, &status, 0);
+            } while (retval == -1 && errno == EINTR);
+
+            if (retval == pid
+                && WIFEXITED(status)
+                && WEXITSTATUS(status)) {
+                /* Child exited with an error.  Convey the same error to
+                 * our parent process as a courtesy. */
+                exit(WEXITSTATUS(status));
+            }
+
+            ovs_fatal(errno, "fork child failed to signal startup");
+        }
+        close(fds[0]);
+        *fdp = -1;
+    } else if (!pid) {
+        /* Running in child process. */
+        close(fds[0]);
+        time_postfork();
+        lockfile_postfork();
+        *fdp = fds[1];
+    } else {
+        ovs_fatal(errno, "could not fork");
+    }
+
+    return pid;
+}
+
+static void
+fork_notify_startup(int fd)
+{
+    if (fd != -1) {
+        size_t bytes_written;
+        int error;
+
+        error = write_fully(fd, "", 1, &bytes_written);
+        if (error) {
+            ovs_fatal(error, "could not write to pipe");
+        }
+
+        close(fd);
+    }
+}
+
+static bool
+should_restart(int status)
+{
+    if (WIFSIGNALED(status)) {
+        static const int error_signals[] = {
+            SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGILL, SIGPIPE, SIGSEGV,
+            SIGXCPU, SIGXFSZ
+        };
+
+        size_t i;
+
+        for (i = 0; i < ARRAY_SIZE(error_signals); i++) {
+            if (error_signals[i] == WTERMSIG(status)) {
+                return true;
+            }
         }
+    }
+    return false;
+}
+
+static void
+monitor_daemon(pid_t daemon_pid)
+{
+    /* XXX Should limit the rate at which we restart the daemon. */
+    /* XXX Should log daemon's stderr output at startup time. */
+    const char *saved_program_name;
+    char *status_msg;
+
+    saved_program_name = program_name;
+    program_name = xasprintf("monitor(%s)", program_name);
+    status_msg = xstrdup("healthy");
+    for (;;) {
+        int retval;
+        int status;
+
+        proctitle_set("%s: monitoring pid %lu (%s)",
+                      saved_program_name, (unsigned long int) daemon_pid,
+                      status_msg);
+
+        do {
+            retval = waitpid(daemon_pid, &status, 0);
+        } while (retval == -1 && errno == EINTR);
 
-        switch (fork()) {
-        default:
-            /* Parent process: wait for child to create pidfile, then exit. */
-            close(fds[1]);
-            fatal_signal_fork();
-            if (read(fds[0], &c, 1) != 1) {
-                ovs_fatal(errno, "daemon child failed to signal startup");
+        if (retval == -1) {
+            ovs_fatal(errno, "waitpid failed");
+        } else if (retval == daemon_pid) {
+            char *s = process_status_msg(status);
+            free(status_msg);
+            status_msg = xasprintf("pid %lu died, %s",
+                                   (unsigned long int) daemon_pid, s);
+            free(s);
+
+            if (should_restart(status)) {
+                VLOG_ERR("%s, restarting", status_msg);
+                daemon_pid = fork_and_wait_for_startup(&daemonize_fd);
+                if (!daemon_pid) {
+                    break;
+                }
+            } else {
+                VLOG_INFO("%s, exiting", status_msg);
+                exit(0);
             }
+        }
+    }
+    free(status_msg);
+
+    /* Running in new daemon process. */
+    proctitle_restore();
+    free((char *) program_name);
+    program_name = saved_program_name;
+}
+
+/* Close stdin, stdout, stderr.  If we're started from e.g. an SSH session,
+ * then this keeps us from holding that session open artificially. */
+static void
+close_standard_fds(void)
+{
+    int null_fd = get_null_fd();
+    if (null_fd >= 0) {
+        dup2(null_fd, STDIN_FILENO);
+        dup2(null_fd, STDOUT_FILENO);
+        dup2(null_fd, STDERR_FILENO);
+    }
+}
+
+/* If daemonization is configured, then starts daemonization, by forking and
+ * returning in the child process.  The parent process hangs around until the
+ * child lets it know either that it completed startup successfully (by calling
+ * daemon_complete()) or that it failed to start up (by exiting with a nonzero
+ * exit code). */
+void
+daemonize_start(void)
+{
+    daemonize_fd = -1;
+
+    if (detach) {
+        if (fork_and_wait_for_startup(&daemonize_fd) > 0) {
+            /* Running in parent process. */
             exit(0);
+        }
+        /* Running in daemon or monitor process. */
+    }
 
-        case 0:
-            /* Child process. */
-            close(fds[0]);
-            make_pidfile();
-            write(fds[1], &c, 1);
-            close(fds[1]);
-            setsid();
-            if (chdir_) {
-                chdir("/");
-            }
-            time_postfork();
-            break;
+    if (monitor) {
+        int saved_daemonize_fd = daemonize_fd;
+        pid_t daemon_pid;
 
-        case -1:
-            /* Error. */
-            ovs_fatal(errno, "could not fork");
-            break;
+        daemon_pid = fork_and_wait_for_startup(&daemonize_fd);
+        if (daemon_pid > 0) {
+            /* Running in monitor process. */
+            fork_notify_startup(saved_daemonize_fd);
+            close_standard_fds();
+            monitor_daemon(daemon_pid);
         }
-    } else {
-        make_pidfile();
+        /* Running in daemon process. */
+    }
+
+    make_pidfile();
+}
+
+/* If daemonization is configured, then this function notifies the parent
+ * process that the child process has completed startup successfully. */
+void
+daemonize_complete(void)
+{
+    fork_notify_startup(daemonize_fd);
+
+    if (detach) {
+        setsid();
+        if (chdir_) {
+            ignore(chdir("/"));
+        }
+        close_standard_fds();
     }
 }
 
index d0c324c..1d63076 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,13 +26,15 @@ enum {
     OPT_NO_CHDIR,
     OPT_OVERWRITE_PIDFILE,
     OPT_PIDFILE,
+    OPT_MONITOR
 };
 
 #define DAEMON_LONG_OPTIONS                                          \
         {"detach",            no_argument, 0, OPT_DETACH},           \
         {"no-chdir",          no_argument, 0, OPT_NO_CHDIR},         \
         {"pidfile",           optional_argument, 0, OPT_PIDFILE},    \
-        {"overwrite-pidfile", no_argument, 0, OPT_OVERWRITE_PIDFILE}
+        {"overwrite-pidfile", no_argument, 0, OPT_OVERWRITE_PIDFILE},\
+        {"monitor",           no_argument, 0, OPT_MONITOR}
 
 #define DAEMON_OPTION_HANDLERS                  \
         case OPT_DETACH:                        \
@@ -49,14 +51,23 @@ enum {
                                                 \
         case OPT_OVERWRITE_PIDFILE:             \
             ignore_existing_pidfile();          \
+            break;                              \
+                                                \
+        case OPT_MONITOR:                       \
+            daemon_set_monitor();               \
             break;
 
 char *make_pidfile_name(const char *name);
 void set_pidfile(const char *name);
 const char *get_pidfile(void);
 void set_no_chdir(void);
+bool is_chdir_enabled(void);
 void set_detach(void);
+bool get_detach(void);
+void daemon_set_monitor(void);
 void daemonize(void);
+void daemonize_start(void);
+void daemonize_complete(void);
 void die_if_already_running(void);
 void ignore_existing_pidfile(void);
 void daemon_usage(void);
index ea0561d..c239377 100644 (file)
@@ -19,6 +19,17 @@ When \fB--pidfile\fR is not specified, this option has no effect.
 Causes \fB\*(PN\fR to detach itself from the foreground session and
 run as a background process.
 
+.TP
+\fB--monitor\fR
+Creates an additional process to monitor the \fB\*(PN\fR daemon.  If
+the daemon dies due to a signal that indicates a programming error
+(e.g. \fBSIGSEGV\fR, \fBSIGABRT\fR), then the monitor process starts a
+new copy of it.  If the daemon die or exits for another reason, the
+monitor process exits.
+.IP
+This option is normally used with \fB--detach\fR, but it also
+functions without it.
+
 .TP
 \fB--no-chdir\fR
 By default, when \fB--detach\fR is specified, \fB\*(PN\fR 
index 4f90781..fb6835c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -151,12 +151,19 @@ dhclient_create(const char *netdev_name,
                 void *aux, struct dhclient **cli_)
 {
     struct dhclient *cli;
+    struct netdev_options netdev_options;
     struct netdev *netdev;
     int error;
 
     *cli_ = NULL;
 
-    error = netdev_open(netdev_name, ETH_TYPE_IP, &netdev);
+    memset(&netdev_options, 0, sizeof netdev_options);
+    netdev_options.name = netdev_name;
+    netdev_options.ethertype = ETH_TYPE_IP;
+    netdev_options.may_create = true;
+    netdev_options.may_open = true;
+
+    error = netdev_open(&netdev_options, &netdev);
     /* XXX install socket filter to catch only DHCP packets. */
     if (error) {
         VLOG_ERR("could not open %s network device: %s",
@@ -172,7 +179,7 @@ dhclient_create(const char *netdev_name,
         return error;
     }
 
-    cli = xcalloc(1, sizeof *cli);
+    cli = xzalloc(sizeof *cli);
     cli->modify_request = modify_request;
     cli->validate_offer = validate_offer;
     cli->aux = aux;
index e7d4381..d707b46 100644 (file)
@@ -95,10 +95,10 @@ dpif_linux_enumerate(struct svec *all_dps)
         int retval;
 
         sprintf(devname, "dp%d", i);
-        retval = dpif_open(devname, &dpif);
+        retval = dpif_open(devname, "system", &dpif);
         if (!retval) {
             svec_add(all_dps, devname);
-            dpif_close(dpif);
+            dpif_uninit(dpif, true);
         } else if (retval != ENODEV && !error) {
             error = retval;
         }
@@ -107,7 +107,7 @@ dpif_linux_enumerate(struct svec *all_dps)
 }
 
 static int
-dpif_linux_open(const char *name UNUSED, char *suffix, bool create,
+dpif_linux_open(const char *name, const char *type UNUSED, bool create,
                 struct dpif **dpifp)
 {
     int minor;
@@ -116,11 +116,11 @@ dpif_linux_open(const char *name UNUSED, char *suffix, bool create,
             && isdigit((unsigned char)name[2]) ? atoi(name + 2) : -1;
     if (create) {
         if (minor >= 0) {
-            return create_minor(suffix, minor, dpifp);
+            return create_minor(name, minor, dpifp);
         } else {
             /* Scan for unused minor number. */
             for (minor = 0; minor < ODP_MAX; minor++) {
-                int error = create_minor(suffix, minor, dpifp);
+                int error = create_minor(name, minor, dpifp);
                 if (error != EBUSY) {
                     return error;
                 }
@@ -135,7 +135,7 @@ dpif_linux_open(const char *name UNUSED, char *suffix, bool create,
         int error;
 
         if (minor < 0) {
-            error = lookup_minor(suffix, &minor);
+            error = lookup_minor(name, &minor);
             if (error) {
                 return error;
             }
@@ -157,7 +157,7 @@ dpif_linux_open(const char *name UNUSED, char *suffix, bool create,
                 VLOG_WARN("%s: probe returned unexpected error: %s",
                           dpif_name(*dpifp), strerror(error));
             }
-            dpif_close(*dpifp);
+            dpif_uninit(*dpifp, true);
             return error;
         }
 
@@ -460,8 +460,7 @@ dpif_linux_recv_wait(struct dpif *dpif_)
 }
 
 const struct dpif_class dpif_linux_class = {
-    "",                         /* This is the default class. */
-    "linux",
+    "system",
     NULL,
     NULL,
     dpif_linux_enumerate,
@@ -686,11 +685,11 @@ static int
 finish_open(struct dpif *dpif_, const char *local_ifname)
 {
     struct dpif_linux *dpif = dpif_linux_cast(dpif_);
-    dpif->local_ifname = strdup(local_ifname);
+    dpif->local_ifname = xstrdup(local_ifname);
     dpif->local_ifindex = if_nametoindex(local_ifname);
     if (!dpif->local_ifindex) {
         int error = errno;
-        dpif_close(dpif_);
+        dpif_uninit(dpif_, true);
         VLOG_WARN("could not get ifindex of %s device: %s",
                   local_ifname, strerror(errno));
         return error;
@@ -707,7 +706,7 @@ create_minor(const char *name, int minor, struct dpif **dpifp)
         if (!error) {
             error = finish_open(*dpifp, name);
         } else {
-            dpif_close(*dpifp);
+            dpif_uninit(*dpifp, true);
         }
     }
     return error;
index 720e8cb..8abf1f9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -196,7 +196,7 @@ create_dpif_netdev(struct dp_netdev *dp)
 
     dp->open_cnt++;
 
-    dpname = xasprintf("netdev:dp%d", dp->dp_idx);
+    dpname = xasprintf("dp%d", dp->dp_idx);
     dpif = xmalloc(sizeof *dpif);
     dpif_init(&dpif->dpif, &dpif_netdev_class, dpname, dp->dp_idx, dp->dp_idx);
     dpif->dp = dp;
@@ -219,7 +219,7 @@ create_dp_netdev(const char *name, int dp_idx, struct dpif **dpifp)
     }
 
     /* Create datapath. */
-    dp_netdevs[dp_idx] = dp = xcalloc(1, sizeof *dp);
+    dp_netdevs[dp_idx] = dp = xzalloc(sizeof *dp);
     list_push_back(&dp_netdev_list, &dp->node);
     dp->dp_idx = dp_idx;
     dp->open_cnt = 0;
@@ -237,7 +237,7 @@ create_dp_netdev(const char *name, int dp_idx, struct dpif **dpifp)
     error = do_add_port(dp, name, ODP_PORT_INTERNAL, ODPP_LOCAL);
     if (error) {
         dp_netdev_free(dp);
-        return error;
+        return ENODEV;
     }
 
     *dpifp = create_dpif_netdev(dp);
@@ -245,20 +245,20 @@ create_dp_netdev(const char *name, int dp_idx, struct dpif **dpifp)
 }
 
 static int
-dpif_netdev_open(const char *name UNUSED, char *suffix, bool create,
+dpif_netdev_open(const char *name, const char *type UNUSED, bool create,
                  struct dpif **dpifp)
 {
     if (create) {
-        if (find_dp_netdev(suffix)) {
+        if (find_dp_netdev(name)) {
             return EEXIST;
         } else {
-            int dp_idx = name_to_dp_idx(suffix);
+            int dp_idx = name_to_dp_idx(name);
             if (dp_idx >= 0) {
-                return create_dp_netdev(suffix, dp_idx, dpifp);
+                return create_dp_netdev(name, dp_idx, dpifp);
             } else {
                 /* Scan for unused dp_idx number. */
                 for (dp_idx = 0; dp_idx < N_DP_NETDEVS; dp_idx++) {
-                    int error = create_dp_netdev(suffix, dp_idx, dpifp);
+                    int error = create_dp_netdev(name, dp_idx, dpifp);
                     if (error != EBUSY) {
                         return error;
                     }
@@ -269,7 +269,7 @@ dpif_netdev_open(const char *name UNUSED, char *suffix, bool create,
             }
         }
     } else {
-        struct dp_netdev *dp = find_dp_netdev(suffix);
+        struct dp_netdev *dp = find_dp_netdev(name);
         if (dp) {
             *dpifp = create_dpif_netdev(dp);
             return 0;
@@ -363,6 +363,7 @@ do_add_port(struct dp_netdev *dp, const char *devname, uint16_t flags,
 {
     bool internal = (flags & ODP_PORT_INTERNAL) != 0;
     struct dp_netdev_port *port;
+    struct netdev_options netdev_options;
     struct netdev *netdev;
     int mtu;
     int error;
@@ -370,17 +371,17 @@ do_add_port(struct dp_netdev *dp, const char *devname, uint16_t flags,
     /* XXX reject devices already in some dp_netdev. */
 
     /* Open and validate network device. */
-    if (!internal) {
-        error = netdev_open(devname, NETDEV_ETH_TYPE_ANY, &netdev);
+    memset(&netdev_options, 0, sizeof netdev_options);
+    netdev_options.name = devname;
+    netdev_options.ethertype = NETDEV_ETH_TYPE_ANY;
+    netdev_options.may_create = true;
+    if (internal) {
+        netdev_options.type = "tap";
     } else {
-        error = netdev_create(devname, "tap", NULL);
-        if (!error) {
-            error = netdev_open(devname, NETDEV_ETH_TYPE_ANY, &netdev);
-            if (error) {
-                netdev_destroy(devname);
-            }
-        }
+        netdev_options.may_open = true;
     }
+
+    error = netdev_open(&netdev_options, &netdev);
     if (error) {
         return error;
     }
@@ -487,9 +488,7 @@ do_del_port(struct dp_netdev *dp, uint16_t port_no)
 
     name = xstrdup(netdev_get_name(port->netdev));
     netdev_close(port->netdev);
-    if (port->internal) {
-        netdev_destroy(name);
-    }
+
     free(name);
     free(port);
 
@@ -804,7 +803,7 @@ add_flow(struct dpif *dpif, struct odp_flow *odp_flow)
     struct dp_netdev_flow *flow;
     int error;
 
-    flow = xcalloc(1, sizeof *flow);
+    flow = xzalloc(sizeof *flow);
     flow->key = odp_flow->key;
     flow->key.reserved = 0;
 
@@ -1304,7 +1303,6 @@ dp_netdev_execute_actions(struct dp_netdev *dp,
 }
 
 const struct dpif_class dpif_netdev_class = {
-    "netdev",
     "netdev",
     dp_netdev_run,
     dp_netdev_wait,
index 39c66e1..fddc8ea 100644 (file)
@@ -32,13 +32,16 @@ extern "C" {
  * This structure should be treated as opaque by dpif implementations. */
 struct dpif {
     const struct dpif_class *dpif_class;
-    char *name;
+    char *base_name;
+    char *full_name;
     uint8_t netflow_engine_type;
     uint8_t netflow_engine_id;
 };
 
 void dpif_init(struct dpif *, const struct dpif_class *, const char *name,
                uint8_t netflow_engine_type, uint8_t netflow_engine_id);
+void dpif_uninit(struct dpif *dpif, bool close);
+
 static inline void dpif_assert_class(const struct dpif *dpif,
                                      const struct dpif_class *dpif_class)
 {
@@ -56,15 +59,11 @@ static inline void dpif_assert_class(const struct dpif *dpif,
  * EWOULDBLOCK or EINPROGRESS.  We may relax this requirement in the future if
  * and when we encounter performance problems. */
 struct dpif_class {
-    /* Prefix for names of dpifs in this class, e.g. "netdev:".
+    /* Type of dpif in this class, e.g. "system", "netdev", etc.
      *
-     * One dpif class may have the empty string "" as its prefix, in which case
-     * that dpif class is associated with dpif names that don't match any other
-     * class name. */
-    const char *prefix;
-
-    /* Class name, for use in error messages. */
-    const char *name;
+     * One of the providers should supply a "system" type, since this is
+     * the type assumed if no type is specified when opening a dpif. */
+    const char *type;
 
     /* Performs periodic work needed by dpifs of this class, if any is
      * necessary. */
@@ -85,16 +84,14 @@ struct dpif_class {
      * case this function may be a null pointer. */
     int (*enumerate)(struct svec *all_dps);
 
-    /* Attempts to open an existing dpif, if 'create' is false, or to open an
-     * existing dpif or create a new one, if 'create' is true.  'name' is the
-     * full dpif name provided by the user, e.g. "udatapath:/var/run/mypath".
-     * This name is useful for error messages but must not be modified.
-     *
-     * 'suffix' is a copy of 'name' following the dpif's 'prefix'.
+    /* Attempts to open an existing dpif called 'name', if 'create' is false,
+     * or to open an existing dpif or create a new one, if 'create' is true.
+     * 'type' corresponds to the 'type' field used in the dpif_class
+     * structure.
      *
      * If successful, stores a pointer to the new dpif in '*dpifp'.  On failure
      * there are no requirements on what is stored in '*dpifp'. */
-    int (*open)(const char *name, char *suffix, bool create,
+    int (*open)(const char *name, const char *type, bool create,
                 struct dpif **dpifp);
 
     /* Closes 'dpif' and frees associated memory. */
index 7edaf31..315f11f 100644 (file)
@@ -33,6 +33,7 @@
 #include "ofpbuf.h"
 #include "packets.h"
 #include "poll-loop.h"
+#include "shash.h"
 #include "svec.h"
 #include "util.h"
 #include "valgrind.h"
 #include "vlog.h"
 #define THIS_MODULE VLM_dpif
 
-static const struct dpif_class *dpif_classes[] = {
+static const struct dpif_class *base_dpif_classes[] = {
     &dpif_linux_class,
     &dpif_netdev_class,
 };
-enum { N_DPIF_CLASSES = ARRAY_SIZE(dpif_classes) };
+
+struct registered_dpif_class {
+    struct dpif_class dpif_class;
+    int refcount;
+};
+static struct shash dpif_classes = SHASH_INITIALIZER(&dpif_classes);
 
 /* Rate limit for individual messages going to or from the datapath, output at
  * DBG level.  This is very high because, if these are enabled, it is because
@@ -63,6 +69,21 @@ static void log_flow_put(struct dpif *, int error,
 static bool should_log_flow_message(int error);
 static void check_rw_odp_flow(struct odp_flow *);
 
+static void
+dp_initialize(void)
+{
+    static int status = -1;
+
+    if (status < 0) {
+        int i;
+
+        status = 0;
+        for (i = 0; i < ARRAY_SIZE(base_dpif_classes); i++) {
+            dp_register_provider(base_dpif_classes[i]);
+        }
+    }
+}
+
 /* Performs periodic work needed by all the various kinds of dpifs.
  *
  * If your program opens any dpifs, it must call both this function and
@@ -70,11 +91,11 @@ static void check_rw_odp_flow(struct odp_flow *);
 void
 dp_run(void)
 {
-    int i;
-    for (i = 0; i < N_DPIF_CLASSES; i++) {
-        const struct dpif_class *class = dpif_classes[i];
-        if (class->run) {
-            class->run();
+    struct shash_node *node;
+    SHASH_FOR_EACH(node, &dpif_classes) {
+        const struct registered_dpif_class *registered_class = node->data;
+        if (registered_class->dpif_class.run) {
+            registered_class->dpif_class.run();
         }
     }
 }
@@ -86,108 +107,200 @@ dp_run(void)
 void
 dp_wait(void)
 {
-    int i;
-    for (i = 0; i < N_DPIF_CLASSES; i++) {
-        const struct dpif_class *class = dpif_classes[i];
-        if (class->wait) {
-            class->wait();
+    struct shash_node *node;
+    SHASH_FOR_EACH(node, &dpif_classes) {
+        const struct registered_dpif_class *registered_class = node->data;
+        if (registered_class->dpif_class.wait) {
+            registered_class->dpif_class.wait();
         }
     }
 }
 
-/* Clears 'all_dps' and enumerates the names of all known created datapaths, 
- * where possible, into it.  The caller must first initialize 'all_dps'.
- * Returns 0 if successful, otherwise a positive errno value.
+/* Registers a new datapath provider.  After successful registration, new
+ * datapaths of that type can be opened using dpif_open(). */
+int
+dp_register_provider(const struct dpif_class *new_class)
+{
+    struct registered_dpif_class *registered_class;
+
+    if (shash_find(&dpif_classes, new_class->type)) {
+        VLOG_WARN("attempted to register duplicate datapath provider: %s",
+                  new_class->type);
+        return EEXIST;
+    }
+
+    registered_class = xmalloc(sizeof *registered_class);
+    memcpy(&registered_class->dpif_class, new_class,
+           sizeof registered_class->dpif_class);
+    registered_class->refcount = 0;
+
+    shash_add(&dpif_classes, new_class->type, registered_class);
+
+    return 0;
+}
+
+/* Unregisters a datapath provider.  'type' must have been previously
+ * registered and not currently be in use by any dpifs.  After unregistration
+ * new datapaths of that type cannot be opened using dpif_open(). */
+int
+dp_unregister_provider(const char *type)
+{
+    struct shash_node *node;
+    struct registered_dpif_class *registered_class;
+
+    node = shash_find(&dpif_classes, type);
+    if (!node) {
+        VLOG_WARN("attempted to unregister a datapath provider that is not "
+                  "registered: %s", type);
+        return EAFNOSUPPORT;
+    }
+
+    registered_class = node->data;
+    if (registered_class->refcount) {
+        VLOG_WARN("attempted to unregister in use datapath provider: %s", type);
+        return EBUSY;
+    }
+
+    shash_delete(&dpif_classes, node);
+    free(registered_class);
+
+    return 0;
+}
+
+/* Clears 'types' and enumerates the types of all currently registered datapath
+ * providers into it.  The caller must first initialize the svec. */
+void
+dp_enumerate_types(struct svec *types)
+{
+    struct shash_node *node;
+
+    dp_initialize();
+    svec_clear(types);
+
+    SHASH_FOR_EACH(node, &dpif_classes) {
+        const struct registered_dpif_class *registered_class = node->data;
+        svec_add(types, registered_class->dpif_class.type);
+    }
+}
+
+/* Clears 'names' and enumerates the names of all known created datapaths with
+ * the given 'type'.  The caller must first initialize the svec. Returns 0 if
+ * successful, otherwise a positive errno value.
  *
  * Some kinds of datapaths might not be practically enumerable.  This is not
  * considered an error. */
 int
-dp_enumerate(struct svec *all_dps)
+dp_enumerate_names(const char *type, struct svec *names)
 {
+    const struct registered_dpif_class *registered_class;
+    const struct dpif_class *dpif_class;
     int error;
-    int i;
-
-    svec_clear(all_dps);
-    error = 0;
-    for (i = 0; i < N_DPIF_CLASSES; i++) {
-        const struct dpif_class *class = dpif_classes[i];
-        int retval = class->enumerate ? class->enumerate(all_dps) : 0;
-        if (retval) {
-            VLOG_WARN("failed to enumerate %s datapaths: %s",
-                      class->name, strerror(retval));
-            if (!error) {
-                error = retval;
-            }
-        }
+
+    dp_initialize();
+    svec_clear(names);
+
+    registered_class = shash_find_data(&dpif_classes, type);
+    if (!registered_class) {
+        VLOG_WARN("could not enumerate unknown type: %s", type);
+        return EAFNOSUPPORT;
+    }
+
+    dpif_class = &registered_class->dpif_class;
+    error = dpif_class->enumerate ? dpif_class->enumerate(names) : 0;
+
+    if (error) {
+        VLOG_WARN("failed to enumerate %s datapaths: %s", dpif_class->type,
+                   strerror(error));
     }
+
     return error;
 }
 
+/* Parses 'datapath name', which is of the form type@name into its
+ * component pieces.  'name' and 'type' must be freed by the caller. */
+void
+dp_parse_name(const char *datapath_name_, char **name, char **type)
+{
+    char *datapath_name = xstrdup(datapath_name_);
+    char *separator;
+
+    separator = strchr(datapath_name, '@');
+    if (separator) {
+        *separator = '\0';
+        *type = datapath_name;
+        *name = xstrdup(separator + 1);
+    } else {
+        *name = datapath_name;
+        *type = NULL;
+    }
+}
+
 static int
-do_open(const char *name_, bool create, struct dpif **dpifp)
+do_open(const char *name, const char *type, bool create, struct dpif **dpifp)
 {
-    char *name = xstrdup(name_);
-    char *prefix, *suffix, *colon;
     struct dpif *dpif = NULL;
     int error;
-    int i;
+    struct registered_dpif_class *registered_class;
 
-    colon = strchr(name, ':');
-    if (colon) {
-        *colon = '\0';
-        prefix = name;
-        suffix = colon + 1;
-    } else {
-        prefix = "";
-        suffix = name;
+    dp_initialize();
+
+    if (!type || *type == '\0') {
+        type = "system";
     }
 
-    for (i = 0; i < N_DPIF_CLASSES; i++) {
-        const struct dpif_class *class = dpif_classes[i];
-        if (!strcmp(prefix, class->prefix)) {
-            error = class->open(name_, suffix, create, &dpif);
-            goto exit;
-        }
+    registered_class = shash_find_data(&dpif_classes, type);
+    if (!registered_class) {
+        VLOG_WARN("could not create datapath %s of unknown type %s", name,
+                  type);
+        error = EAFNOSUPPORT;
+        goto exit;
+    }
+
+    error = registered_class->dpif_class.open(name, type, create, &dpif);
+    if (!error) {
+        registered_class->refcount++;
     }
-    error = EAFNOSUPPORT;
 
 exit:
     *dpifp = error ? NULL : dpif;
     return error;
 }
 
-/* Tries to open an existing datapath named 'name'.  Will fail if no datapath
- * named 'name' exists.  Returns 0 if successful, otherwise a positive errno
- * value.  On success stores a pointer to the datapath in '*dpifp', otherwise a
- * null pointer. */
+/* Tries to open an existing datapath named 'name' and type 'type'.  Will fail
+ * if no datapath with 'name' and 'type' exists.  'type' may be either NULL or
+ * the empty string to specify the default system type.  Returns 0 if
+ * successful, otherwise a positive errno value.  On success stores a pointer
+ * to the datapath in '*dpifp', otherwise a null pointer. */
 int
-dpif_open(const char *name, struct dpif **dpifp)
+dpif_open(const char *name, const char *type, struct dpif **dpifp)
 {
-    return do_open(name, false, dpifp);
+    return do_open(name, type, false, dpifp);
 }
 
-/* Tries to create and open a new datapath with the given 'name'.  Will fail if
- * a datapath named 'name' already exists.  Returns 0 if successful, otherwise
- * a positive errno value.  On success stores a pointer to the datapath in
- * '*dpifp', otherwise a null pointer. */
+/* Tries to create and open a new datapath with the given 'name' and 'type'.
+ * 'type' may be either NULL or the empty string to specify the default system
+ * type.  Will fail if a datapath with 'name' and 'type' already exists.
+ * Returns 0 if successful, otherwise a positive errno value.  On success
+ * stores a pointer to the datapath in '*dpifp', otherwise a null pointer. */
 int
-dpif_create(const char *name, struct dpif **dpifp)
+dpif_create(const char *name, const char *type, struct dpif **dpifp)
 {
-    return do_open(name, true, dpifp);
+    return do_open(name, type, true, dpifp);
 }
 
-/* Tries to open a datapath with the given 'name', creating it if it does not
- * exist.  Returns 0 if successful, otherwise a positive errno value.  On
- * success stores a pointer to the datapath in '*dpifp', otherwise a null
- * pointer. */
+/* Tries to open a datapath with the given 'name' and 'type', creating it if it
+ * does not exist.  'type' may be either NULL or the empty string to specify
+ * the default system type.  Returns 0 if successful, otherwise a positive
+ * errno value. On success stores a pointer to the datapath in '*dpifp',
+ * otherwise a null pointer. */
 int
-dpif_create_and_open(const char *name, struct dpif **dpifp)
+dpif_create_and_open(const char *name, const char *type, struct dpif **dpifp)
 {
     int error;
 
-    error = dpif_create(name, dpifp);
+    error = dpif_create(name, type, dpifp);
     if (error == EEXIST || error == EBUSY) {
-        error = dpif_open(name, dpifp);
+        error = dpif_open(name, type, dpifp);
         if (error) {
             VLOG_WARN("datapath %s already exists but cannot be opened: %s",
                       name, strerror(error));
@@ -204,17 +317,32 @@ void
 dpif_close(struct dpif *dpif)
 {
     if (dpif) {
-        char *name = dpif->name;
-        dpif->dpif_class->close(dpif);
-        free(name);
+        struct registered_dpif_class *registered_class;
+
+        registered_class = shash_find_data(&dpif_classes, 
+                dpif->dpif_class->type);
+        assert(registered_class);
+        assert(registered_class->refcount);
+
+        registered_class->refcount--;
+        dpif_uninit(dpif, true);
     }
 }
 
-/* Returns the name of datapath 'dpif' (for use in log messages). */
+/* Returns the name of datapath 'dpif' prefixed with the type
+ * (for use in log messages). */
 const char *
 dpif_name(const struct dpif *dpif)
 {
-    return dpif->name;
+    return dpif->full_name;
+}
+
+/* Returns the name of datapath 'dpif' without the type
+ * (for use in device names). */
+const char *
+dpif_base_name(const struct dpif *dpif)
+{
+    return dpif->base_name;
 }
 
 /* Enumerates all names that may be used to open 'dpif' into 'all_names'.  The
@@ -238,7 +366,7 @@ dpif_get_all_names(const struct dpif *dpif, struct svec *all_names)
         }
         return error;
     } else {
-        svec_add(all_names, dpif_name(dpif));
+        svec_add(all_names, dpif_base_name(dpif));
         return 0;
     }
 }
@@ -965,10 +1093,31 @@ dpif_init(struct dpif *dpif, const struct dpif_class *dpif_class,
           uint8_t netflow_engine_type, uint8_t netflow_engine_id)
 {
     dpif->dpif_class = dpif_class;
-    dpif->name = xstrdup(name);
+    dpif->base_name = xstrdup(name);
+    dpif->full_name = xasprintf("%s@%s", dpif_class->type, name);
     dpif->netflow_engine_type = netflow_engine_type;
     dpif->netflow_engine_id = netflow_engine_id;
 }
+
+/* Undoes the results of initialization.
+ *
+ * Normally this function only needs to be called from dpif_close().
+ * However, it may be called by providers due to an error on opening
+ * that occurs after initialization.  It this case dpif_close() would
+ * never be called. */
+void
+dpif_uninit(struct dpif *dpif, bool close)
+{
+    char *base_name = dpif->base_name;
+    char *full_name = dpif->full_name;
+
+    if (close) {
+        dpif->dpif_class->close(dpif);
+    }
+
+    free(base_name);
+    free(full_name);
+}
 \f
 static void
 log_operation(const struct dpif *dpif, const char *operation, int error)
index bf3c648..b171793 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #ifndef DPIF_H
 #define DPIF_H 1
 
-/* Operations for the datapath running in the local kernel.  The interface can
- * generalize to multiple types of local datapaths, but the implementation only
- * supports the openflow kernel module. */
-
 #include "openvswitch/datapath-protocol.h"
 #include <stdbool.h>
 #include <stddef.h>
 struct dpif;
 struct ofpbuf;
 struct svec;
+struct dpif_class;
 
 void dp_run(void);
 void dp_wait(void);
-int dp_enumerate(struct svec *);
 
-int dpif_open(const char *name, struct dpif **);
-int dpif_create(const char *name, struct dpif **);
-int dpif_create_and_open(const char *name, struct dpif **);
+int dp_register_provider(const struct dpif_class *);
+int dp_unregister_provider(const char *type);
+void dp_enumerate_types(struct svec *types);
+
+int dp_enumerate_names(const char *type, struct svec *names);
+void dp_parse_name(const char *datapath_name, char **name, char **type);
+
+int dpif_open(const char *name, const char *type, struct dpif **);
+int dpif_create(const char *name, const char *type, struct dpif **);
+int dpif_create_and_open(const char *name, const char *type, struct dpif **);
 void dpif_close(struct dpif *);
 
 const char *dpif_name(const struct dpif *);
+const char *dpif_base_name(const struct dpif *);
 int dpif_get_all_names(const struct dpif *, struct svec *);
 
 int dpif_delete(struct dpif *);
index 3a58caf..63e172f 100644 (file)
@@ -1,11 +1,13 @@
 .RS
 .TP
-\fBdp\fIN\fR
+[\fItype\fB@\fR]\fBdp\fIN\fR
 Datapath number \fIN\fR, where \fIN\fR is a number between 0 and 255,
-inclusive.
+inclusive.  If \fItype\fR is given, it specifies the datapath provider of
+\fBdp\fIN\fR, otherwise the default provider \fBsystem\fR is assumed.
 
 .TP
-\fIname\fR
+[\fItype\fB@\fR]\fIname\fR
 The name of the network device associated with the datapath's local
 port.  (\fB\*(PN\fR internally converts this into a datapath number,
-as above.)
+as above.)  If \fItype\fR is given, it specifies the datapath provider of
+\fIname\fR, otherwise the default provider \fBsystem\fR is assumed.
index 9684ffa..c333818 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -71,6 +71,31 @@ ds_put_char(struct ds *ds, char c)
     *ds_put_uninit(ds, 1) = c;
 }
 
+/* Appends unicode code point 'uc' to 'ds' in UTF-8 encoding. */
+void
+ds_put_utf8(struct ds *ds, int uc)
+{
+    if (uc <= 0x7f) {
+        ds_put_char(ds, uc);
+    } else if (uc <= 0x7ff) {
+        ds_put_char(ds, 0xc0 | (uc >> 6));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else if (uc <= 0xffff) {
+        ds_put_char(ds, 0xe0 | (uc >> 12));
+        ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else if (uc <= 0x10ffff) {
+        ds_put_char(ds, 0xf0 | (uc >> 18));
+        ds_put_char(ds, 0x80 | ((uc >> 12) & 0x3f));
+        ds_put_char(ds, 0x80 | ((uc >> 6) & 0x3f));
+        ds_put_char(ds, 0x80 | (uc & 0x3f));
+    } else {
+        /* Invalid code point.  Insert the Unicode general substitute
+         * REPLACEMENT CHARACTER. */
+        ds_put_utf8(ds, 0xfffd);
+    }
+}
+
 void
 ds_put_char_multiple(struct ds *ds, char c, size_t n)
 {
@@ -90,6 +115,13 @@ ds_put_cstr(struct ds *ds, const char *s)
     memcpy(ds_put_uninit(ds, s_len), s, s_len);
 }
 
+void
+ds_put_and_free_cstr(struct ds *ds, char *s)
+{
+    ds_put_cstr(ds, s);
+    free(s);
+}
+
 void
 ds_put_format(struct ds *ds, const char *format, ...)
 {
@@ -187,6 +219,12 @@ ds_cstr(struct ds *ds)
     return ds->string;
 }
 
+const char *
+ds_cstr_ro(const struct ds *ds)
+{
+    return ds_cstr((struct ds *) ds);
+}
+
 /* Returns a null-terminated string representing the current contents of 'ds',
  * which the caller is expected to free with free(), then clears the contents
  * of 'ds'. */
@@ -204,6 +242,15 @@ ds_destroy(struct ds *ds)
     free(ds->string);
 }
 
+/* Swaps the content of 'a' and 'b'. */
+void
+ds_swap(struct ds *a, struct ds *b)
+{
+    struct ds temp = *a;
+    *a = *b;
+    *b = temp;
+}
+
 /* Writes the 'size' bytes in 'buf' to 'string' as hex bytes arranged 16 per
  * line.  Numeric offsets are also included, starting at 'ofs' for the first
  * byte in 'buf'.  If 'ascii' is true then the corresponding ASCII characters
index a44e0b3..eebbdbf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -40,9 +40,11 @@ void ds_truncate(struct ds *, size_t new_length);
 void ds_reserve(struct ds *, size_t min_length);
 char *ds_put_uninit(struct ds *, size_t n);
 void ds_put_char(struct ds *, char);
+void ds_put_utf8(struct ds *, int uc);
 void ds_put_char_multiple(struct ds *, char, size_t n);
 void ds_put_buffer(struct ds *, const char *, size_t n);
 void ds_put_cstr(struct ds *, const char *);
+void ds_put_and_free_cstr(struct ds *, char *);
 void ds_put_format(struct ds *, const char *, ...) PRINTF_FORMAT(2, 3);
 void ds_put_format_valist(struct ds *, const char *, va_list)
     PRINTF_FORMAT(2, 0);
@@ -54,8 +56,10 @@ void ds_put_hex_dump(struct ds *ds, const void *buf_, size_t size,
 int ds_get_line(struct ds *, FILE *);
 
 char *ds_cstr(struct ds *);
+const char *ds_cstr_ro(const struct ds *);
 char *ds_steal_cstr(struct ds *);
 void ds_destroy(struct ds *);
+void ds_swap(struct ds *, struct ds *);
 
 int ds_last(const struct ds *);
 void ds_chomp(struct ds *, int c);
index 8180521..60a188e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <signal.h>
 #include <stdbool.h>
 #include <stdio.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include "poll-loop.h"
 #include "shash.h"
+#include "socket-util.h"
 #include "util.h"
 
 #define THIS_MODULE VLM_fatal_signal
@@ -37,7 +40,8 @@ static sigset_t fatal_signal_set;
 
 /* Hooks to call upon catching a signal */
 struct hook {
-    void (*func)(void *aux);
+    void (*hook_cb)(void *aux);
+    void (*cancel_cb)(void *aux);
     void *aux;
     bool run_at_exit;
 };
@@ -45,53 +49,29 @@ struct hook {
 static struct hook hooks[MAX_HOOKS];
 static size_t n_hooks;
 
-/* Number of nesting signal blockers. */
-static int block_level = 0;
+static int signal_fds[2];
+static volatile sig_atomic_t stored_sig_nr = SIG_ATOMIC_MAX;
 
-/* Signal mask saved by outermost signal blocker. */
-static sigset_t saved_signal_mask;
-
-/* Disabled by fatal_signal_fork()? */
-static bool disabled;
-
-static void call_sigprocmask(int how, sigset_t* new_set, sigset_t* old_set);
+static void fatal_signal_init(void);
 static void atexit_handler(void);
 static void call_hooks(int sig_nr);
 
-/* Registers 'hook' to be called when a process termination signal is raised.
- * If 'run_at_exit' is true, 'hook' is also called during normal process
- * termination, e.g. when exit() is called or when main() returns.
- *
- * 'func' will be invoked from an asynchronous signal handler, so it must be
- * written appropriately.  For example, it must not call most C library
- * functions, including malloc() or free(). */
-void
-fatal_signal_add_hook(void (*func)(void *aux), void *aux, bool run_at_exit)
-{
-    fatal_signal_block();
-    assert(n_hooks < MAX_HOOKS);
-    hooks[n_hooks].func = func;
-    hooks[n_hooks].aux = aux;
-    hooks[n_hooks].run_at_exit = run_at_exit;
-    n_hooks++;
-    fatal_signal_unblock();
-}
-
-/* Blocks program termination signals until fatal_signal_unblock() is called.
- * May be called multiple times with nesting; if so, fatal_signal_unblock()
- * must be called the same number of times to unblock signals.
- *
- * This is needed while adjusting a data structure that will be accessed by a
- * fatal signal hook, so that the hook is not invoked while the data structure
- * is in an inconsistent state. */
-void
-fatal_signal_block(void)
+static void
+fatal_signal_init(void)
 {
     static bool inited = false;
+
     if (!inited) {
         size_t i;
 
         inited = true;
+
+        if (pipe(signal_fds)) {
+            ovs_fatal(errno, "could not create pipe");
+        }
+        set_nonblocking(signal_fds[0]);
+        set_nonblocking(signal_fds[1]);
+
         sigemptyset(&fatal_signal_set);
         for (i = 0; i < ARRAY_SIZE(fatal_signals); i++) {
             int sig_nr = fatal_signals[i];
@@ -108,23 +88,34 @@ fatal_signal_block(void)
         }
         atexit(atexit_handler);
     }
-
-    if (++block_level == 1) {
-        call_sigprocmask(SIG_BLOCK, &fatal_signal_set, &saved_signal_mask);
-    }
 }
 
-/* Unblocks program termination signals blocked by fatal_signal_block() is
- * called.  If multiple calls to fatal_signal_block() are nested,
- * fatal_signal_unblock() must be called the same number of times to unblock
- * signals. */
+/* Registers 'hook_cb' to be called when a process termination signal is
+ * raised.  If 'run_at_exit' is true, 'hook_cb' is also called during normal
+ * process termination, e.g. when exit() is called or when main() returns.
+ *
+ * 'hook_cb' is not called immediately from the signal handler but rather the
+ * next time the poll loop iterates, so it is freed from the usual restrictions
+ * on signal handler functions.
+ *
+ * If the current process forks, fatal_signal_fork() may be called to clear the
+ * parent process's fatal signal hooks, so that 'hook_cb' is only called when
+ * the child terminates, not when the parent does.  When fatal_signal_fork() is
+ * called, it calls the 'cancel_cb' function if it is nonnull, passing 'aux',
+ * to notify that the hook has been canceled.  This allows the hook to free
+ * memory, etc. */
 void
-fatal_signal_unblock(void)
+fatal_signal_add_hook(void (*hook_cb)(void *aux), void (*cancel_cb)(void *aux),
+                      void *aux, bool run_at_exit)
 {
-    assert(block_level > 0);
-    if (--block_level == 0) {
-        call_sigprocmask(SIG_SETMASK, &saved_signal_mask, NULL);
-    }
+    fatal_signal_init();
+
+    assert(n_hooks < MAX_HOOKS);
+    hooks[n_hooks].hook_cb = hook_cb;
+    hooks[n_hooks].cancel_cb = cancel_cb;
+    hooks[n_hooks].aux = aux;
+    hooks[n_hooks].run_at_exit = run_at_exit;
+    n_hooks++;
 }
 
 /* Handles fatal signal number 'sig_nr'.
@@ -139,20 +130,35 @@ fatal_signal_unblock(void)
 void
 fatal_signal_handler(int sig_nr)
 {
-    call_hooks(sig_nr);
+    ignore(write(signal_fds[1], "", 1));
+    stored_sig_nr = sig_nr;
+}
+
+void
+fatal_signal_run(void)
+{
+    int sig_nr = stored_sig_nr;
+
+    if (sig_nr != SIG_ATOMIC_MAX) {
+        call_hooks(sig_nr);
 
-    /* Re-raise the signal with the default handling so that the program
-     * termination status reflects that we were killed by this signal */
-    signal(sig_nr, SIG_DFL);
-    raise(sig_nr);
+        /* Re-raise the signal with the default handling so that the program
+         * termination status reflects that we were killed by this signal */
+        signal(sig_nr, SIG_DFL);
+        raise(sig_nr);
+    }
+}
+
+void
+fatal_signal_wait(void)
+{
+    poll_fd_wait(signal_fds[0], POLLIN);
 }
 
 static void
 atexit_handler(void)
 {
-    if (!disabled) {
-        call_hooks(0);
-    }
+    call_hooks(0);
 }
 
 static void
@@ -167,15 +173,21 @@ call_hooks(int sig_nr)
         for (i = 0; i < n_hooks; i++) {
             struct hook *h = &hooks[i];
             if (sig_nr || h->run_at_exit) {
-                h->func(h->aux);
+                h->hook_cb(h->aux);
             }
         }
     }
 }
 \f
+/* Files to delete on exit.  (The 'data' member of each node is unused.) */
 static struct shash files = SHASH_INITIALIZER(&files);
 
+/* Has a hook function been registered with fatal_signal_add_hook() (and not
+ * cleared by fatal_signal_fork())? */
+static bool added_hook;
+
 static void unlink_files(void *aux);
+static void cancel_files(void *aux);
 static void do_unlink_files(void);
 
 /* Registers 'file' to be unlinked when the program terminates via exit() or a
@@ -183,17 +195,14 @@ static void do_unlink_files(void);
 void
 fatal_signal_add_file_to_unlink(const char *file)
 {
-    static bool added_hook = false;
     if (!added_hook) {
         added_hook = true;
-        fatal_signal_add_hook(unlink_files, NULL, true);
+        fatal_signal_add_hook(unlink_files, cancel_files, NULL, true);
     }
 
-    fatal_signal_block();
     if (!shash_find(&files, file)) {
         shash_add(&files, file, NULL);
     }
-    fatal_signal_unblock();
 }
 
 /* Unregisters 'file' from being unlinked when the program terminates via
@@ -203,12 +212,10 @@ fatal_signal_remove_file_to_unlink(const char *file)
 {
     struct shash_node *node;
 
-    fatal_signal_block();
     node = shash_find(&files, file);
     if (node) {
         shash_delete(&files, node);
     }
-    fatal_signal_unblock();
 }
 
 /* Like fatal_signal_remove_file_to_unlink(), but also unlinks 'file'.
@@ -232,12 +239,13 @@ unlink_files(void *aux UNUSED)
     do_unlink_files(); 
 }
 
-/* This is a fatal_signal_add_hook() callback (via unlink_files()).  It will be
- * invoked from an asynchronous signal handler, so it cannot call most C
- * library functions (unlink() is an explicit exception, see
- * http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html).
- * That includes free(), so it doesn't try to free the 'files' data
- * structure. */
+static void
+cancel_files(void *aux UNUSED)
+{
+    shash_clear(&files);
+    added_hook = false;
+}
+
 static void
 do_unlink_files(void)
 {
@@ -248,30 +256,30 @@ do_unlink_files(void)
     }
 }
 \f
-/* Disables the fatal signal hook mechanism.  Following a fork, one of the
- * resulting processes can call this function to allow it to terminate without
- * triggering fatal signal processing or removing files.  Fatal signal
- * processing is still enabled in the other process. */
+/* Clears all of the fatal signal hooks without executing them.  If any of the
+ * hooks passed a 'cancel_cb' function to fatal_signal_add_hook(), then those
+ * functions will be called, allowing them to free resources, etc.
+ *
+ * Following a fork, one of the resulting processes can call this function to
+ * allow it to terminate without calling the hooks registered before calling
+ * this function.  New hooks registered after calling this function will take
+ * effect normally. */
 void
 fatal_signal_fork(void)
 {
     size_t i;
 
-    disabled = true;
-
-    for (i = 0; i < ARRAY_SIZE(fatal_signals); i++) {
-        int sig_nr = fatal_signals[i];
-        if (signal(sig_nr, SIG_DFL) == SIG_IGN) {
-            signal(sig_nr, SIG_IGN);
+    for (i = 0; i < n_hooks; i++) {
+        struct hook *h = &hooks[i];
+        if (h->cancel_cb) {
+            h->cancel_cb(h->aux);
         }
     }
-}
-\f
-static void
-call_sigprocmask(int how, sigset_t* new_set, sigset_t* old_set)
-{
-    int error = sigprocmask(how, new_set, old_set);
-    if (error) {
-        fprintf(stderr, "sigprocmask: %s\n", strerror(errno));
+    n_hooks = 0;
+
+    /* Raise any signals that we have already received with the default
+     * handler. */
+    if (stored_sig_nr != SIG_ATOMIC_MAX) {
+        raise(stored_sig_nr);
     }
 }
index c96db86..94a1f1f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <stdbool.h>
 
 /* Basic interface. */
-void fatal_signal_add_hook(void (*)(void *aux), void *aux, bool run_at_exit);
-void fatal_signal_block(void);
-void fatal_signal_unblock(void);
+void fatal_signal_add_hook(void (*hook_cb)(void *aux),
+                           void (*cancel_cb)(void *aux), void *aux,
+                           bool run_at_exit);
 void fatal_signal_fork(void);
+void fatal_signal_run(void);
+void fatal_signal_wait(void);
 
 /* Convenience functions for unlinking files upon termination.
  *
diff --git a/lib/fault.c b/lib/fault.c
deleted file mode 100644 (file)
index 14e229e..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2008, 2009 Nicira Networks.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-#include "fault.h"
-#include <dlfcn.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include "util.h"
-
-#include "vlog.h"
-#define THIS_MODULE VLM_fault
-
-static void
-fault_handler(int sig_nr)
-{
-    VLOG_EMER("Caught signal %d.", sig_nr);
-    log_backtrace();
-    fflush(stdout);
-    fflush(stderr);
-
-    signal(sig_nr, SIG_DFL);
-    raise(sig_nr);
-}
-
-void
-log_backtrace(void)
-{
-    /* During the loop:
-
-       frame[0] points to the next frame.
-       frame[1] points to the return address. */
-    void **frame;
-    for (frame = __builtin_frame_address(0);
-         frame != NULL && frame[0] != NULL;
-         frame = frame[0]) {
-        Dl_info addrinfo;
-        if (!dladdr(frame[1], &addrinfo) || !addrinfo.dli_sname) {
-            fprintf(stderr, "  0x%08"PRIxPTR"\n", (uintptr_t) frame[1]);
-        } else {
-            fprintf(stderr, "  0x%08"PRIxPTR" (%s+0x%tx)\n",
-                    (uintptr_t) frame[1], addrinfo.dli_sname,
-                    (char *) frame[1] - (char *) addrinfo.dli_saddr); 
-        }
-    }
-    fflush(stderr);
-}
-
-void
-register_fault_handlers(void)
-{
-    signal(SIGABRT, fault_handler);
-    signal(SIGBUS, fault_handler);
-    signal(SIGFPE, fault_handler);
-    signal(SIGILL, fault_handler);
-    signal(SIGSEGV, fault_handler);
-}
index f1daa6b..63b4784 100644 (file)
@@ -52,6 +52,21 @@ hash_words(const uint32_t *p, size_t n, uint32_t basis)
     return c;
 }
 
+/* Returns the hash of the pair of aligned 32-bit words at 'p', starting from
+ * 'basis'. */
+uint32_t
+hash_2words(const uint32_t *p, uint32_t basis)
+{
+    uint32_t a, b, c;
+
+    a = b = c = 0xdeadbeef + (2 << 2) + basis;
+    b += p[1];
+    a += p[0];
+    HASH_FINAL(a, b, c);
+
+    return c;
+}
+
 /* Returns the hash of the 'n' bytes at 'p', starting from 'basis'. */
 uint32_t
 hash_bytes(const void *p_, size_t n, uint32_t basis)
index 5485f8c..5f6409c 100644 (file)
 #ifndef HASH_H
 #define HASH_H 1
 
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 #include <string.h>
+#include "util.h"
 
 /* This is the public domain lookup3 hash by Bob Jenkins from
  * http://burtleburtle.net/bob/c/lookup3.c, modified for style. */
@@ -47,6 +49,7 @@
     } while (0)
 
 uint32_t hash_words(const uint32_t *, size_t n_word, uint32_t basis);
+uint32_t hash_2words(const uint32_t *, uint32_t basis);
 uint32_t hash_bytes(const void *, size_t n_bytes, uint32_t basis);
 
 static inline uint32_t hash_string(const char *s, uint32_t basis)
@@ -62,10 +65,42 @@ static inline uint32_t hash_int(uint32_t x, uint32_t basis)
     x ^= x >> 17;
     x -= x << 9;
     x ^= x << 4;
+    x += basis;
     x -= x << 3;
     x ^= x << 10;
     x ^= x >> 15;
-    return x + basis;
+    return x;
+}
+
+/* An attempt at a useful 1-bit hash function.  Has not been analyzed for
+ * quality. */
+static inline uint32_t hash_boolean(bool x, uint32_t basis)
+{
+    enum { P0 = 0xc2b73583 };   /* This is hash_int(1, 0). */
+    enum { P1 = 0xe90f1258 };   /* This is hash_int(2, 0). */
+    return (x ? P0 : P1) ^ HASH_ROT(basis, 1);
+}
+
+static inline uint32_t hash_double(double x, uint32_t basis)
+{
+    uint32_t value[2];
+    BUILD_ASSERT_DECL(sizeof x == sizeof value);
+
+    memcpy(value, &x, sizeof value);
+    return hash_2words(value, basis);
+}
+
+static inline uint32_t hash_pointer(const void *p, uint32_t basis)
+{
+    /* Often pointers are hashed simply by casting to integer type, but that
+     * has pitfalls since the lower bits of a pointer are often all 0 for
+     * alignment reasons.  It's hard to guess where the entropy really is, so
+     * we give up here and just use a high-quality hash function.
+     *
+     * The double cast suppresses a warning on 64-bit systems about casting to
+     * an integer to different size.  That's OK in this case, since most of the
+     * entropy in the pointer is almost certainly in the lower 32 bits. */
+    return hash_int((uint32_t) (uintptr_t) p, basis);
 }
 
 #endif /* hash.h */
index f75fc07..71943a7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -48,11 +48,17 @@ hmap_swap(struct hmap *a, struct hmap *b)
     struct hmap tmp = *a;
     *a = *b;
     *b = tmp;
-    if (a->buckets == &b->one) {
-        a->buckets = &a->one;
-    }
-    if (b->buckets == &a->one) {
-        b->buckets = &b->one;
+    hmap_moved(a);
+    hmap_moved(b);
+}
+
+/* Adjusts 'hmap' to compensate for having moved position in memory (e.g. due
+ * to realloc()). */
+void
+hmap_moved(struct hmap *hmap)
+{
+    if (!hmap->mask) {
+        hmap->buckets = &hmap->one;
     }
 }
 
@@ -143,3 +149,17 @@ hmap_reserve(struct hmap *hmap, size_t n)
         resize(hmap, new_mask);
     }
 }
+
+/* Adjusts 'hmap' to compensate for 'old_node' having moved position in memory
+ * to 'node' (e.g. due to realloc()). */
+void
+hmap_node_moved(struct hmap *hmap,
+                struct hmap_node *old_node, struct hmap_node *node)
+{
+    struct hmap_node **bucket = &hmap->buckets[node->hash & hmap->mask];
+    while (*bucket != old_node) {
+        bucket = &(*bucket)->next;
+    }
+    *bucket = node;
+}
+
index d770af8..2f4a302 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -37,9 +37,27 @@ static inline size_t hmap_node_hash(const struct hmap_node *node)
     return node->hash;
 }
 
+#define HMAP_NODE_NULL ((struct hmap_node *) 1)
+
+/* Returns true if 'node' has been set to null by hmap_node_nullify() and has
+ * not been un-nullified by being inserted into an hmap. */
+static inline bool
+hmap_node_is_null(const struct hmap_node *node)
+{
+    return node->next == HMAP_NODE_NULL;
+}
+
+/* Marks 'node' with a distinctive value that can be tested with
+ * hmap_node_is_null().  */
+static inline void
+hmap_node_nullify(struct hmap_node *node)
+{
+    node->next = HMAP_NODE_NULL;
+}
+
 /* A hash map. */
 struct hmap {
-    struct hmap_node **buckets;
+    struct hmap_node **buckets; /* Must point to 'one' iff 'mask' == 0. */
     struct hmap_node *one;
     size_t mask;
     size_t n;
@@ -52,6 +70,7 @@ struct hmap {
 void hmap_init(struct hmap *);
 void hmap_destroy(struct hmap *);
 void hmap_swap(struct hmap *a, struct hmap *b);
+void hmap_moved(struct hmap *hmap);
 static inline size_t hmap_count(const struct hmap *);
 static inline bool hmap_is_empty(const struct hmap *);
 
@@ -65,23 +84,51 @@ static inline void hmap_insert_fast(struct hmap *,
                                     struct hmap_node *, size_t hash);
 static inline void hmap_insert(struct hmap *, struct hmap_node *, size_t hash);
 static inline void hmap_remove(struct hmap *, struct hmap_node *);
-static inline void hmap_moved(struct hmap *,
-                              struct hmap_node *, struct hmap_node *);
-static inline void hmap_replace(struct hmap *,
-                                const struct hmap_node *old_node,
-                                struct hmap_node *new_node);
 
-/* Search. */
+void hmap_node_moved(struct hmap *, struct hmap_node *, struct hmap_node *);
+static inline void hmap_replace(struct hmap *, const struct hmap_node *old,
+                                struct hmap_node *new);
+
+/* Search.
+ *
+ * HMAP_FOR_EACH_WITH_HASH iterates NODE over all of the nodes in HMAP that
+ * have hash value equal to HASH.  HMAP_FOR_EACH_IN_BUCKET iterates NODE over
+ * all of the nodes in HMAP that would fall in the same bucket as HASH.  STRUCT
+ * and MEMBER must be the name of the struct that contains the 'struct
+ * hmap_node' and the name of the 'struct hmap_node' member, respectively.
+ *
+ * These macros may be used interchangeably to search for a particular value in
+ * an hmap, see, e.g. shash_find() for an example.  Usually, using
+ * HMAP_FOR_EACH_WITH_HASH provides an optimization, because comparing a hash
+ * value is usually cheaper than comparing an entire hash map key.  But for
+ * simple hash map keys, it makes sense to use HMAP_FOR_EACH_IN_BUCKET because
+ * it avoids doing two comparisons when a single simple comparison suffices.
+ *
+ * The loop should not change NODE to point to a different node or insert or
+ * delete nodes in HMAP (unless it "break"s out of the loop to terminate
+ * iteration).
+ *
+ * HASH is only evaluated once.
+ */
 #define HMAP_FOR_EACH_WITH_HASH(NODE, STRUCT, MEMBER, HASH, HMAP)       \
     for ((NODE) = CONTAINER_OF(hmap_first_with_hash(HMAP, HASH),        \
                                STRUCT, MEMBER);                         \
          &(NODE)->MEMBER != NULL;                                       \
          (NODE) = CONTAINER_OF(hmap_next_with_hash(&(NODE)->MEMBER),    \
                                STRUCT, MEMBER))
+#define HMAP_FOR_EACH_IN_BUCKET(NODE, STRUCT, MEMBER, HASH, HMAP)       \
+    for ((NODE) = CONTAINER_OF(hmap_first_in_bucket(HMAP, HASH),        \
+                               STRUCT, MEMBER);                         \
+         &(NODE)->MEMBER != NULL;                                       \
+         (NODE) = CONTAINER_OF(hmap_next_in_bucket(&(NODE)->MEMBER),    \
+                               STRUCT, MEMBER))
 
 static inline struct hmap_node *hmap_first_with_hash(const struct hmap *,
                                                      size_t hash);
 static inline struct hmap_node *hmap_next_with_hash(const struct hmap_node *);
+static inline struct hmap_node *hmap_first_in_bucket(const struct hmap *,
+                                                     size_t hash);
+static inline struct hmap_node *hmap_next_in_bucket(const struct hmap_node *);
 
 /* Iteration.
  *
@@ -165,19 +212,6 @@ hmap_remove(struct hmap *hmap, struct hmap_node *node)
     hmap->n--;
 }
 
-/* Adjusts 'hmap' to compensate for 'old_node' having moved position in memory
- * to 'node' (e.g. due to realloc()). */
-static inline void
-hmap_moved(struct hmap *hmap,
-           struct hmap_node *old_node, struct hmap_node *node)
-{
-    struct hmap_node **bucket = &hmap->buckets[node->hash & hmap->mask];
-    while (*bucket != old_node) {
-        bucket = &(*bucket)->next;
-    }
-    *bucket = node;
-}
-
 /* Puts 'new_node' in the position in 'hmap' currently occupied by 'old_node'.
  * The 'new_node' must hash to the same value as 'old_node'.  The client is
  * responsible for ensuring that the replacement does not violate any
@@ -195,6 +229,7 @@ hmap_replace(struct hmap *hmap,
     }
     *bucket = new_node;
     new_node->hash = old_node->hash;
+    new_node->next = old_node->next;
 }
 
 static inline struct hmap_node *
@@ -214,6 +249,28 @@ hmap_first_with_hash(const struct hmap *hmap, size_t hash)
     return hmap_next_with_hash__(hmap->buckets[hash & hmap->mask], hash);
 }
 
+/* Returns the first node in 'hmap' in the bucket in which the given 'hash'
+ * would land, or a null pointer if that bucket is empty. */
+static inline struct hmap_node *
+hmap_first_in_bucket(const struct hmap *hmap, size_t hash)
+{
+    return hmap->buckets[hash & hmap->mask];
+}
+
+/* Returns the next node in the same bucket as 'node', or a null pointer if
+ * there are no more nodes in that bucket.
+ *
+ * If the hash map has been reallocated since 'node' was visited, some nodes
+ * may be skipped; if new nodes with the same hash value have been added, they
+ * will be skipped.  (Removing 'node' from the hash map does not prevent
+ * calling this function, since node->next is preserved, although freeing
+ * 'node' of course does.) */
+static inline struct hmap_node *
+hmap_next_in_bucket(const struct hmap_node *node)
+{
+    return node->next;
+}
+
 /* Returns the next node in the same hash map as 'node' with the same hash
  * value, or a null pointer if no more nodes have that hash value.
  *
diff --git a/lib/json.c b/lib/json.c
new file mode 100644 (file)
index 0000000..8fd9c3a
--- /dev/null
@@ -0,0 +1,1657 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "json.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <string.h>
+
+#include "dynamic-string.h"
+#include "hash.h"
+#include "shash.h"
+#include "unicode.h"
+#include "util.h"
+
+/* The type of a JSON token. */
+enum json_token_type {
+    T_EOF = 0,
+    T_BEGIN_ARRAY = '[',
+    T_END_ARRAY = ']',
+    T_BEGIN_OBJECT = '{',
+    T_END_OBJECT = '}',
+    T_NAME_SEPARATOR = ':',
+    T_VALUE_SEPARATOR = ',',
+    T_FALSE = UCHAR_MAX + 1,
+    T_NULL,
+    T_TRUE,
+    T_INTEGER,
+    T_REAL,
+    T_STRING
+};
+
+/* A JSON token.
+ *
+ * RFC 4627 doesn't define a lexical structure for JSON but I believe this to
+ * be compliant with the standard.
+ */
+struct json_token {
+    enum json_token_type type;
+    union {
+        double real;
+        long long int integer;
+        const char *string;
+    } u;
+};
+
+enum json_lex_state {
+    JSON_LEX_START,             /* Not inside a token. */
+    JSON_LEX_NUMBER,            /* Reading a number. */
+    JSON_LEX_KEYWORD,           /* Reading a keyword. */
+    JSON_LEX_STRING,            /* Reading a quoted string. */
+    JSON_LEX_ESCAPE             /* In a quoted string just after a "\". */
+};
+
+enum json_parse_state {
+    JSON_PARSE_START,           /* Beginning of input. */
+    JSON_PARSE_END,             /* End of input. */
+
+    /* Objects. */
+    JSON_PARSE_OBJECT_INIT,     /* Expecting '}' or an object name. */
+    JSON_PARSE_OBJECT_NAME,     /* Expecting an object name. */
+    JSON_PARSE_OBJECT_COLON,    /* Expecting ':'. */
+    JSON_PARSE_OBJECT_VALUE,    /* Expecting an object value. */
+    JSON_PARSE_OBJECT_NEXT,     /* Expecting ',' or '}'. */
+
+    /* Arrays. */
+    JSON_PARSE_ARRAY_INIT,      /* Expecting ']' or a value. */
+    JSON_PARSE_ARRAY_VALUE,     /* Expecting a value. */
+    JSON_PARSE_ARRAY_NEXT       /* Expecting ',' or ']'. */
+};
+
+struct json_parser_node {
+    struct json *json;
+};
+
+/* A JSON parser. */
+struct json_parser {
+    int flags;
+
+    /* Lexical analysis. */
+    enum json_lex_state lex_state;
+    struct ds buffer;           /* Buffer for accumulating token text. */
+    int line_number;
+    int column_number;
+    int byte_number;
+
+    /* Parsing. */
+    enum json_parse_state parse_state;
+#define JSON_MAX_HEIGHT 1000
+    struct json_parser_node *stack;
+    size_t height, allocated_height;
+    char *member_name;
+
+    /* Parse status. */
+    bool done;
+    char *error;                /* Error message, if any, null if none yet. */
+};
+
+static struct json *json_create(enum json_type type);
+static void json_parser_input(struct json_parser *, struct json_token *);
+
+static void json_error(struct json_parser *p, const char *format, ...)
+    PRINTF_FORMAT(2, 3);
+\f
+const char *
+json_type_to_string(enum json_type type)
+{
+    switch (type) {
+    case JSON_NULL:
+        return "null";
+
+    case JSON_FALSE:
+        return "false";
+
+    case JSON_TRUE:
+        return "true";
+
+    case JSON_OBJECT:
+        return "object";
+
+    case JSON_ARRAY:
+        return "array";
+
+    case JSON_INTEGER:
+    case JSON_REAL:
+        return "number";
+
+    case JSON_STRING:
+        return "string";
+
+    case JSON_N_TYPES:
+    default:
+        return "<invalid>";
+    }
+}
+\f
+/* Functions for manipulating struct json. */
+
+struct json *
+json_null_create(void)
+{
+    return json_create(JSON_NULL);
+}
+
+struct json *
+json_boolean_create(bool b)
+{
+    return json_create(b ? JSON_TRUE : JSON_FALSE);
+}
+
+struct json *
+json_string_create_nocopy(char *s)
+{
+    struct json *json = json_create(JSON_STRING);
+    json->u.string = s;
+    return json;
+}
+
+struct json *
+json_string_create(const char *s)
+{
+    return json_string_create_nocopy(xstrdup(s));
+}
+
+struct json *
+json_array_create_empty(void)
+{
+    struct json *json = json_create(JSON_ARRAY);
+    json->u.array.elems = NULL;
+    json->u.array.n = 0;
+    json->u.array.n_allocated = 0;
+    return json;
+}
+
+void
+json_array_add(struct json *array_, struct json *element)
+{
+    struct json_array *array = json_array(array_);
+    if (array->n >= array->n_allocated) {
+        array->elems = x2nrealloc(array->elems, &array->n_allocated,
+                                  sizeof *array->elems);
+    }
+    array->elems[array->n++] = element;
+}
+
+void
+json_array_trim(struct json *array_)
+{
+    struct json_array *array = json_array(array_);
+    if (array->n < array->n_allocated){
+        array->n_allocated = array->n;
+        array->elems = xrealloc(array->elems, array->n * sizeof *array->elems);
+    }
+}
+
+struct json *
+json_array_create(struct json **elements, size_t n)
+{
+    struct json *json = json_create(JSON_ARRAY);
+    json->u.array.elems = elements;
+    json->u.array.n = n;
+    json->u.array.n_allocated = n;
+    return json;
+}
+
+struct json *
+json_array_create_1(struct json *elem0)
+{
+    struct json **elems = xmalloc(sizeof *elems);
+    elems[0] = elem0;
+    return json_array_create(elems, 1);
+}
+
+struct json *
+json_array_create_2(struct json *elem0, struct json *elem1)
+{
+    struct json **elems = xmalloc(2 * sizeof *elems);
+    elems[0] = elem0;
+    elems[1] = elem1;
+    return json_array_create(elems, 2);
+}
+
+struct json *
+json_array_create_3(struct json *elem0, struct json *elem1, struct json *elem2)
+{
+    struct json **elems = xmalloc(3 * sizeof *elems);
+    elems[0] = elem0;
+    elems[1] = elem1;
+    elems[2] = elem2;
+    return json_array_create(elems, 3);
+}
+
+struct json *
+json_object_create(void)
+{
+    struct json *json = json_create(JSON_OBJECT);
+    json->u.object = xmalloc(sizeof *json->u.object);
+    shash_init(json->u.object);
+    return json;
+}
+
+struct json *
+json_integer_create(long long int integer)
+{
+    struct json *json = json_create(JSON_INTEGER);
+    json->u.integer = integer;
+    return json;
+}
+
+struct json *
+json_real_create(double real)
+{
+    struct json *json = json_create(JSON_REAL);
+    json->u.real = real;
+    return json;
+}
+
+void
+json_object_put(struct json *json, const char *name, struct json *value)
+{
+    shash_add(json->u.object, name, value);
+}
+
+void
+json_object_put_string(struct json *json, const char *name, const char *value)
+{
+    json_object_put(json, name, json_string_create(value));
+}
+
+const char *
+json_string(const struct json *json)
+{
+    assert(json->type == JSON_STRING);
+    return json->u.string;
+}
+
+struct json_array *
+json_array(const struct json *json)
+{
+    assert(json->type == JSON_ARRAY);
+    return (struct json_array *) &json->u.array;
+}
+
+struct shash *
+json_object(const struct json *json)
+{
+    assert(json->type == JSON_OBJECT);
+    return (struct shash *) json->u.object;
+}
+
+bool
+json_boolean(const struct json *json)
+{
+    assert(json->type == JSON_TRUE || json->type == JSON_FALSE);
+    return json->type == JSON_TRUE;
+}
+
+double
+json_real(const struct json *json)
+{
+    assert(json->type == JSON_REAL || json->type == JSON_INTEGER);
+    return json->type == JSON_REAL ? json->u.real : json->u.integer;
+}
+
+int64_t
+json_integer(const struct json *json)
+{
+    assert(json->type == JSON_INTEGER);
+    return json->u.integer;
+}
+\f
+static void json_destroy_object(struct shash *object);
+static void json_destroy_array(struct json_array *array);
+
+/* Frees 'json' and everything it points to, recursively. */
+void
+json_destroy(struct json *json)
+{
+    if (json) {
+        switch (json->type) {
+        case JSON_OBJECT:
+            json_destroy_object(json->u.object);
+            break;
+
+        case JSON_ARRAY:
+            json_destroy_array(&json->u.array);
+            break;
+
+        case JSON_STRING:
+            free(json->u.string);
+            break;
+
+        case JSON_NULL:
+        case JSON_FALSE:
+        case JSON_TRUE:
+        case JSON_INTEGER:
+        case JSON_REAL:
+            break;
+
+        case JSON_N_TYPES:
+            NOT_REACHED();
+        }
+        free(json);
+    }
+}
+
+static void
+json_destroy_object(struct shash *object)
+{
+    struct shash_node *node, *next;
+
+    SHASH_FOR_EACH_SAFE (node, next, object) {
+        struct json *value = node->data;
+
+        json_destroy(value);
+        shash_delete(object, node);
+    }
+    shash_destroy(object);
+    free(object);
+}
+
+static void
+json_destroy_array(struct json_array *array)
+{
+    size_t i;
+
+    for (i = 0; i < array->n; i++) {
+        json_destroy(array->elems[i]);
+    }
+    free(array->elems);
+}
+\f
+static struct json *json_clone_object(const struct shash *object);
+static struct json *json_clone_array(const struct json_array *array);
+
+/* Returns a deep copy of 'json'. */
+struct json *
+json_clone(const struct json *json)
+{
+    switch (json->type) {
+    case JSON_OBJECT:
+        return json_clone_object(json->u.object);
+
+    case JSON_ARRAY:
+        return json_clone_array(&json->u.array);
+
+    case JSON_STRING:
+        return json_string_create(json->u.string);
+
+    case JSON_NULL:
+    case JSON_FALSE:
+    case JSON_TRUE:
+        return json_create(json->type);
+
+    case JSON_INTEGER:
+        return json_integer_create(json->u.integer);
+
+    case JSON_REAL:
+        return json_real_create(json->u.real);
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct json *
+json_clone_object(const struct shash *object)
+{
+    struct shash_node *node;
+    struct json *json;
+
+    json = json_object_create();
+    SHASH_FOR_EACH (node, object) {
+        struct json *value = node->data;
+        json_object_put(json, node->name, json_clone(value));
+    }
+    return json;
+}
+
+static struct json *
+json_clone_array(const struct json_array *array)
+{
+    struct json **elems;
+    size_t i;
+
+    elems = xmalloc(array->n * sizeof *elems);
+    for (i = 0; i < array->n; i++) {
+        elems[i] = json_clone(array->elems[i]);
+    }
+    return json_array_create(elems, array->n);
+}
+\f
+static size_t
+json_hash_object(const struct shash *object, size_t basis)
+{
+    const struct shash_node **nodes;
+    size_t n, i;
+
+    nodes = shash_sort(object);
+    n = shash_count(object);
+    for (i = 0; i < n; i++) {
+        const struct shash_node *node = nodes[i];
+        basis = hash_string(node->name, basis);
+        basis = json_hash(node->data, basis);
+    }
+    return basis;
+}
+
+static size_t
+json_hash_array(const struct json_array *array, size_t basis)
+{
+    size_t i;
+
+    basis = hash_int(array->n, basis);
+    for (i = 0; i < array->n; i++) {
+        basis = json_hash(array->elems[i], basis);
+    }
+    return basis;
+}
+
+size_t
+json_hash(const struct json *json, size_t basis)
+{
+    switch (json->type) {
+    case JSON_OBJECT:
+        return json_hash_object(json->u.object, basis);
+
+    case JSON_ARRAY:
+        return json_hash_array(&json->u.array, basis);
+
+    case JSON_STRING:
+        return hash_string(json->u.string, basis);
+
+    case JSON_NULL:
+    case JSON_FALSE:
+    case JSON_TRUE:
+        return hash_int(json->type << 8, basis);
+
+    case JSON_INTEGER:
+        return hash_int(json->u.integer, basis);
+
+    case JSON_REAL:
+        return hash_double(json->u.real, basis);
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static bool
+json_equal_object(const struct shash *a, const struct shash *b)
+{
+    struct shash_node *a_node;
+
+    if (shash_count(a) != shash_count(b)) {
+        return false;
+    }
+
+    SHASH_FOR_EACH (a_node, a) {
+        struct shash_node *b_node = shash_find(b, a_node->name);
+        if (!b_node || !json_equal(a_node->data, b_node->data)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool
+json_equal_array(const struct json_array *a, const struct json_array *b)
+{
+    size_t i;
+
+    if (a->n != b->n) {
+        return false;
+    }
+
+    for (i = 0; i < a->n; i++) {
+        if (!json_equal(a->elems[i], b->elems[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+json_equal(const struct json *a, const struct json *b)
+{
+    if (a->type != b->type) {
+        return false;
+    }
+
+    switch (a->type) {
+    case JSON_OBJECT:
+        return json_equal_object(a->u.object, b->u.object);
+
+    case JSON_ARRAY:
+        return json_equal_array(&a->u.array, &b->u.array);
+
+    case JSON_STRING:
+        return !strcmp(a->u.string, b->u.string);
+
+    case JSON_NULL:
+    case JSON_FALSE:
+    case JSON_TRUE:
+        return true;
+
+    case JSON_INTEGER:
+        return a->u.integer == b->u.integer;
+
+    case JSON_REAL:
+        return a->u.real == b->u.real;
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+\f
+/* Lexical analysis. */
+
+static void
+json_lex_keyword(struct json_parser *p)
+{
+    struct json_token token;
+    const char *s;
+
+    s = ds_cstr(&p->buffer);
+    if (!strcmp(s, "false")) {
+        token.type = T_FALSE;
+    } else if (!strcmp(s, "true")) {
+        token.type = T_TRUE;
+    } else if (!strcmp(s, "null")) {
+        token.type = T_NULL;
+    } else {
+        json_error(p, "invalid keyword '%s'", s);
+        return;
+    }
+    json_parser_input(p, &token);
+}
+
+static void
+json_lex_number(struct json_parser *p)
+{
+    const char *cp = ds_cstr(&p->buffer);
+    unsigned long long int significand = 0;
+    struct json_token token;
+    int sig_digits = 0;
+    bool imprecise = false;
+    bool negative = false;
+    int pow10 = 0;
+
+    /* Leading minus sign. */
+    if (*cp == '-') {
+        negative = true;
+        cp++;
+    }
+
+    /* At least one integer digit, but 0 may not be used as a leading digit for
+     * a longer number. */
+    significand = 0;
+    sig_digits = 0;
+    if (*cp == '0') {
+        cp++;
+        if (isdigit(*cp)) {
+            json_error(p, "leading zeros not allowed");
+            return;
+        }
+    } else if (isdigit(*cp)) {
+        do {
+            if (significand <= ULLONG_MAX / 10) {
+                significand = significand * 10 + (*cp - '0');
+                sig_digits++;
+            } else {
+                pow10++;
+                if (*cp != '0') {
+                    imprecise = true;
+                }
+            }
+            cp++;
+        } while (isdigit(*cp));
+    } else {
+        json_error(p, "'-' must be followed by digit");
+        return;
+    }
+
+    /* Optional fraction. */
+    if (*cp == '.') {
+        cp++;
+        if (!isdigit(*cp)) {
+            json_error(p, "decimal point must be followed by digit");
+            return;
+        }
+        do {
+            if (significand <= ULLONG_MAX / 10) {
+                significand = significand * 10 + (*cp - '0');
+                sig_digits++;
+                pow10--;
+            } else if (*cp != '0') {
+                imprecise = true;
+            }
+            cp++;
+        } while (isdigit(*cp));
+    }
+
+    /* Optional exponent. */
+    if (*cp == 'e' || *cp == 'E') {
+        bool negative_exponent = false;
+        int exponent;
+
+        cp++;
+        if (*cp == '+') {
+            cp++;
+        } else if (*cp == '-') {
+            negative_exponent = true;
+            cp++;
+        }
+
+        if (!isdigit(*cp)) {
+            json_error(p, "exponent must contain at least one digit");
+            return;
+        }
+
+        exponent = 0;
+        do {
+            if (exponent >= INT_MAX / 10) {
+                json_error(p, "exponent outside valid range");
+                return;
+            }
+            exponent = exponent * 10 + (*cp - '0');
+            cp++;
+        } while (isdigit(*cp));
+
+        if (negative_exponent) {
+            pow10 -= exponent;
+        } else {
+            pow10 += exponent;
+        }
+    }
+
+    if (*cp != '\0') {
+        json_error(p, "syntax error in number");
+        return;
+    }
+
+    /* Figure out number.
+     *
+     * We suppress negative zeros as a matter of policy. */
+    if (!significand) {
+        struct json_token token;
+        token.type = T_INTEGER;
+        token.u.integer = 0;
+        json_parser_input(p, &token);
+        return;
+    }
+
+    if (!imprecise) {
+        while (pow10 > 0 && significand < ULLONG_MAX / 10) {
+            significand *= 10;
+            sig_digits++;
+            pow10--;
+        }
+        while (pow10 < 0 && significand % 10 == 0) {
+            significand /= 10;
+            sig_digits--;
+            pow10++;
+        }
+        if (pow10 == 0
+            && significand <= (negative
+                               ? (unsigned long long int) LLONG_MAX + 1
+                               : LLONG_MAX)) {
+            token.type = T_INTEGER;
+            token.u.integer = negative ? -significand : significand;
+            json_parser_input(p, &token);
+            return;
+        }
+    }
+
+    token.type = T_REAL;
+    if (!str_to_double(ds_cstr(&p->buffer), &token.u.real)) {
+        json_error(p, "number outside valid range");
+        return;
+    }
+    /* Suppress negative zero. */
+    if (token.u.real == 0) {
+        token.u.real = 0;
+    }
+    json_parser_input(p, &token);
+}
+
+static const char *
+json_lex_4hex(const char *cp, const char *end, int *valuep)
+{
+    int value, i;
+
+    if (cp + 4 > end) {
+        return "quoted string ends within \\u escape";
+    }
+
+    value = 0;
+    for (i = 0; i < 4; i++) {
+        unsigned char c = *cp++;
+        if (!isxdigit(c)) {
+            return "malformed \\u escape";
+        }
+        value = (value << 4) | hexit_value(c);
+    }
+    if (!value) {
+        return "null bytes not supported in quoted strings";
+    }
+    *valuep = value;
+    return NULL;
+}
+
+static const char *
+json_lex_unicode(const char *cp, const char *end, struct ds *out)
+{
+    const char *error;
+    int c0, c1;
+
+    error = json_lex_4hex(cp, end, &c0);
+    if (error) {
+        ds_clear(out);
+        ds_put_cstr(out, error);
+        return NULL;
+    }
+    cp += 4;
+    if (!uc_is_leading_surrogate(c0)) {
+        ds_put_utf8(out, c0);
+        return cp;
+    }
+
+    if (cp + 2 > end || *cp++ != '\\' || *cp++ != 'u') {
+        ds_clear(out);
+        ds_put_cstr(out, "malformed escaped surrogate pair");
+        return NULL;
+    }
+
+    error = json_lex_4hex(cp, end, &c1);
+    if (error) {
+        ds_clear(out);
+        ds_put_cstr(out, error);
+        return NULL;
+    }
+    cp += 4;
+    if (!uc_is_trailing_surrogate(c1)) {
+        ds_clear(out);
+        ds_put_cstr(out, "second half of escaped surrogate pair is not "
+                    "trailing surrogate");
+        return NULL;
+    }
+
+    ds_put_utf8(out, utf16_decode_surrogate_pair(c0, c1));
+    return cp;
+}
+
+bool
+json_string_unescape(const char *in, size_t in_len, char **outp)
+{
+    const char *end = in + in_len;
+    bool ok = false;
+    struct ds out;
+
+    ds_init(&out);
+    ds_reserve(&out, in_len);
+    if (in_len > 0 && in[in_len - 1] == '\\') {
+        ds_put_cstr(&out, "quoted string may not end with backslash");
+        goto exit;
+    }
+    while (in < end) {
+        if (*in == '"') {
+            ds_clear(&out);
+            ds_put_cstr(&out, "quoted string may not include unescape \"");
+            goto exit;
+        }
+        if (*in != '\\') {
+            ds_put_char(&out, *in++);
+            continue;
+        }
+
+        in++;
+        switch (*in++) {
+        case '"': case '\\': case '/':
+            ds_put_char(&out, in[-1]);
+            break;
+
+        case 'b':
+            ds_put_char(&out, '\b');
+            break;
+
+        case 'f':
+            ds_put_char(&out, '\f');
+            break;
+
+        case 'n':
+            ds_put_char(&out, '\n');
+            break;
+
+        case 'r':
+            ds_put_char(&out, '\r');
+            break;
+
+        case 't':
+            ds_put_char(&out, '\t');
+            break;
+
+        case 'u':
+            in = json_lex_unicode(in, end, &out);
+            if (!in) {
+                goto exit;
+            }
+            break;
+
+        default:
+            ds_clear(&out);
+            ds_put_format(&out, "bad escape \\%c", in[-1]);
+            goto exit;
+        }
+    }
+    ok = true;
+
+exit:
+    *outp = ds_cstr(&out);
+    return ok;
+}
+
+static void
+json_parser_input_string(struct json_parser *p, const char *s)
+{
+    struct json_token token;
+
+    token.type = T_STRING;
+    token.u.string = s;
+    json_parser_input(p, &token);
+}
+
+static void
+json_lex_string(struct json_parser *p)
+{
+    const char *raw = ds_cstr(&p->buffer);
+    if (!strchr(raw, '\\')) {
+        json_parser_input_string(p, raw);
+    } else {
+        char *cooked;
+
+        if (json_string_unescape(raw, strlen(raw), &cooked)) {
+            json_parser_input_string(p, cooked);
+        } else {
+            json_error(p, "%s", cooked);
+        }
+
+        free(cooked);
+    }
+}
+
+static bool
+json_lex_input(struct json_parser *p, unsigned char c)
+{
+    struct json_token token;
+
+    p->byte_number++;
+    if (c == '\n') {
+        p->column_number = 0;
+        p->line_number++;
+    } else {
+        p->column_number++;
+    }
+
+    switch (p->lex_state) {
+    case JSON_LEX_START:
+        switch (c) {
+        case ' ': case '\t': case '\n': case '\r':
+            /* Nothing to do. */
+            return true;
+
+        case 'a': case 'b': case 'c': case 'd': case 'e':
+        case 'f': case 'g': case 'h': case 'i': case 'j':
+        case 'k': case 'l': case 'm': case 'n': case 'o':
+        case 'p': case 'q': case 'r': case 's': case 't':
+        case 'u': case 'v': case 'w': case 'x': case 'y':
+        case 'z':
+            p->lex_state = JSON_LEX_KEYWORD;
+            break;
+
+        case '[': case '{': case ']': case '}': case ':': case ',':
+            token.type = c;
+            json_parser_input(p, &token);
+            return true;
+
+        case '-':
+        case '0': case '1': case '2': case '3': case '4':
+        case '5': case '6': case '7': case '8': case '9':
+            p->lex_state = JSON_LEX_NUMBER;
+            break;
+
+        case '"':
+            p->lex_state = JSON_LEX_STRING;
+            return true;
+
+        default:
+            if (isprint(c)) {
+                json_error(p, "invalid character '%c'", c);
+            } else {
+                json_error(p, "invalid character U+%04x", c);
+            }
+            return true;
+        }
+        break;
+
+    case JSON_LEX_KEYWORD:
+        if (!isalpha((unsigned char) c)) {
+            json_lex_keyword(p);
+            return false;
+        }
+        break;
+
+    case JSON_LEX_NUMBER:
+        if (!strchr(".0123456789eE-+", c)) {
+            json_lex_number(p);
+            return false;
+        }
+        break;
+
+    case JSON_LEX_STRING:
+        if (c == '\\') {
+            p->lex_state = JSON_LEX_ESCAPE;
+        } else if (c == '"') {
+            json_lex_string(p);
+            return true;
+        } else if (c < 0x20) {
+            json_error(p, "U+%04X must be escaped in quoted string", c);
+            return true;
+        }
+        break;
+
+    case JSON_LEX_ESCAPE:
+        p->lex_state = JSON_LEX_STRING;
+        break;
+
+    default:
+        abort();
+    }
+    ds_put_char(&p->buffer, c);
+    return true;
+}
+\f
+/* Parsing. */
+
+/* Parses 'string' as a JSON object or array and returns a newly allocated
+ * 'struct json'.  The caller must free the returned structure with
+ * json_destroy() when it is no longer needed.
+ *
+ * 'string' must be encoded in UTF-8.
+ *
+ * If 'string' is valid JSON, then the returned 'struct json' will be either an
+ * object (JSON_OBJECT) or an array (JSON_ARRAY).
+ *
+ * If 'string' is not valid JSON, then the returned 'struct json' will be a
+ * string (JSON_STRING) that describes the particular error encountered during
+ * parsing.  (This is an acceptable means of error reporting because at its top
+ * level JSON must be either an object or an array; a bare string is not
+ * valid.) */
+struct json *
+json_from_string(const char *string)
+{
+    struct json_parser *p = json_parser_create(JSPF_TRAILER);
+    json_parser_feed(p, string, strlen(string));
+    return json_parser_finish(p);
+}
+
+/* Reads the file named 'file_name', parses its contents as a JSON object or
+ * array, and returns a newly allocated 'struct json'.  The caller must free
+ * the returned structure with json_destroy() when it is no longer needed.
+ *
+ * The file must be encoded in UTF-8.
+ *
+ * See json_from_string() for return value semantics.
+ */
+struct json *
+json_from_file(const char *file_name)
+{
+    struct json *json;
+    FILE *stream;
+
+    stream = fopen(file_name, "r");
+    if (!stream) {
+        return json_string_create_nocopy(
+            xasprintf("error opening \"%s\": %s", file_name, strerror(errno)));
+    }
+    json = json_from_stream(stream);
+    fclose(stream);
+
+    return json;
+}
+
+/* Parses the contents of 'stream' as a JSON object or array, and returns a
+ * newly allocated 'struct json'.  The caller must free the returned structure
+ * with json_destroy() when it is no longer needed.
+ *
+ * The file must be encoded in UTF-8.
+ *
+ * See json_from_string() for return value semantics.
+ */
+struct json *
+json_from_stream(FILE *stream)
+{
+    struct json_parser *p;
+    struct json *json;
+
+    p = json_parser_create(JSPF_TRAILER);
+    for (;;) {
+        char buffer[BUFSIZ];
+        size_t n;
+
+        n = fread(buffer, 1, sizeof buffer, stream);
+        if (!n || json_parser_feed(p, buffer, n) != n) {
+            break;
+        }
+    }
+    json = json_parser_finish(p);
+
+    if (ferror(stream)) {
+        json_destroy(json);
+        json = json_string_create_nocopy(
+            xasprintf("error reading JSON stream: %s", strerror(errno)));
+    }
+
+    return json;
+}
+
+struct json_parser *
+json_parser_create(int flags)
+{
+    struct json_parser *p = xzalloc(sizeof *p);
+    p->flags = flags;
+    return p;
+}
+
+size_t
+json_parser_feed(struct json_parser *p, const char *input, size_t n)
+{
+    size_t i;
+    for (i = 0; !p->done && i < n; ) {
+        if (json_lex_input(p, input[i])) {
+            i++;
+        }
+    }
+    return i;
+}
+
+bool
+json_parser_is_done(const struct json_parser *p)
+{
+    return p->done;
+}
+
+struct json *
+json_parser_finish(struct json_parser *p)
+{
+    struct json *json;
+
+    switch (p->lex_state) {
+    case JSON_LEX_START:
+        break;
+
+    case JSON_LEX_STRING:
+    case JSON_LEX_ESCAPE:
+        json_error(p, "unexpected end of input in quoted string");
+        break;
+
+    case JSON_LEX_NUMBER:
+    case JSON_LEX_KEYWORD:
+        json_lex_input(p, ' ');
+        break;
+    }
+
+    if (p->parse_state == JSON_PARSE_START) {
+        json_error(p, "empty input stream");
+    } else if (p->parse_state != JSON_PARSE_END) {
+        json_error(p, "unexpected end of input");
+    }
+
+    if (!p->error) {
+        assert(p->height == 1);
+        assert(p->stack[0].json != NULL);
+        json = p->stack[--p->height].json;
+    } else {
+        json = json_string_create_nocopy(p->error);
+        p->error = NULL;
+    }
+
+    json_parser_abort(p);
+
+    return json;
+}
+
+void
+json_parser_abort(struct json_parser *p)
+{
+    if (p) {
+        ds_destroy(&p->buffer);
+        if (p->height) {
+            json_destroy(p->stack[0].json);
+        }
+        free(p->stack);
+        free(p->member_name);
+        free(p->error);
+        free(p);
+    }
+}
+
+static struct json_parser_node *
+json_parser_top(struct json_parser *p)
+{
+    return &p->stack[p->height - 1];
+}
+
+static void
+json_parser_put_value(struct json_parser *p, struct json *value)
+{
+    struct json_parser_node *node = json_parser_top(p);
+    if (node->json->type == JSON_OBJECT) {
+        json_object_put(node->json, p->member_name, value);
+        free(p->member_name);
+        p->member_name = NULL;
+    } else if (node->json->type == JSON_ARRAY) {
+        json_array_add(node->json, value);
+    } else {
+        NOT_REACHED();
+    }
+}
+
+static struct json_parser_node *
+json_parser_push(struct json_parser *p,
+                 struct json *new_json, enum json_parse_state new_state)
+{
+    if (p->height < JSON_MAX_HEIGHT) {
+        struct json_parser_node *node;
+
+        if (p->height >= p->allocated_height) {
+            p->stack = x2nrealloc(p->stack, &p->allocated_height,
+                                  sizeof *p->stack);
+        }
+
+        if (p->height > 0) {
+            json_parser_put_value(p, new_json);
+        }
+
+        node = &p->stack[p->height++];
+        node->json = new_json;
+        p->parse_state = new_state;
+        return node;
+    } else {
+        json_destroy(new_json);
+        json_error(p, "input exceeds maximum nesting depth %d",
+                   JSON_MAX_HEIGHT);
+        return NULL;
+    }
+}
+
+static void
+json_parser_push_object(struct json_parser *p)
+{
+    json_parser_push(p, json_object_create(), JSON_PARSE_OBJECT_INIT);
+}
+
+static void
+json_parser_push_array(struct json_parser *p)
+{
+    json_parser_push(p, json_array_create_empty(), JSON_PARSE_ARRAY_INIT);
+}
+
+static void
+json_parse_value(struct json_parser *p, struct json_token *token,
+                 enum json_parse_state next_state)
+{
+    struct json *value;
+
+    switch (token->type) {
+    case T_FALSE:
+        value = json_boolean_create(false);
+        break;
+
+    case T_NULL:
+        value = json_null_create();
+        break;
+
+    case T_TRUE:
+        value = json_boolean_create(true);
+        break;
+
+    case '{':
+        json_parser_push_object(p);
+        return;
+
+    case '[':
+        json_parser_push_array(p);
+        return;
+
+    case T_INTEGER:
+        value = json_integer_create(token->u.integer);
+        break;
+
+    case T_REAL:
+        value = json_real_create(token->u.real);
+        break;
+
+    case T_STRING:
+        value = json_string_create(token->u.string);
+        break;
+
+    case T_EOF:
+    case '}':
+    case ']':
+    case ':':
+    case ',':
+    default:
+        json_error(p, "syntax error expecting value");
+        return;
+    }
+
+    json_parser_put_value(p, value);
+    p->parse_state = next_state;
+}
+
+static void
+json_parser_pop(struct json_parser *p)
+{
+    struct json_parser_node *node;
+
+    /* Conserve memory. */
+    node = json_parser_top(p);
+    if (node->json->type == JSON_ARRAY) {
+        json_array_trim(node->json);
+    }
+
+    /* Pop off the top-of-stack. */
+    if (p->height == 1) {
+        p->parse_state = JSON_PARSE_END;
+        if (!(p->flags & JSPF_TRAILER)) {
+            p->done = true;
+        }
+    } else {
+        p->height--;
+        node = json_parser_top(p);
+        if (node->json->type == JSON_ARRAY) {
+            p->parse_state = JSON_PARSE_ARRAY_NEXT;
+        } else if (node->json->type == JSON_OBJECT) {
+            p->parse_state = JSON_PARSE_OBJECT_NEXT;
+        } else {
+            NOT_REACHED();
+        }
+    }
+}
+
+static void
+json_parser_input(struct json_parser *p, struct json_token *token)
+{
+    switch (p->parse_state) {
+    case JSON_PARSE_START:
+        if (token->type == '{') {
+            json_parser_push_object(p);
+        } else if (token->type == '[') {
+            json_parser_push_array(p);
+        } else {
+            json_error(p, "syntax error at beginning of input");
+        }
+        break;
+
+    case JSON_PARSE_END:
+        json_error(p, "trailing garbage at end of input");
+        break;
+
+    case JSON_PARSE_OBJECT_INIT:
+        if (token->type == '}') {
+            json_parser_pop(p);
+            break;
+        }
+        /* Fall through. */
+    case JSON_PARSE_OBJECT_NAME:
+        if (token->type == T_STRING) {
+            p->member_name = xstrdup(token->u.string);
+            p->parse_state = JSON_PARSE_OBJECT_COLON;
+        } else {
+            json_error(p, "syntax error parsing object expecting string");
+        }
+        break;
+
+    case JSON_PARSE_OBJECT_COLON:
+        if (token->type == ':') {
+            p->parse_state = JSON_PARSE_OBJECT_VALUE;
+        } else {
+            json_error(p, "syntax error parsing object expecting ':'");
+        }
+        break;
+
+    case JSON_PARSE_OBJECT_VALUE:
+        json_parse_value(p, token, JSON_PARSE_OBJECT_NEXT);
+        break;
+
+    case JSON_PARSE_OBJECT_NEXT:
+        if (token->type == ',') {
+            p->parse_state = JSON_PARSE_OBJECT_NAME;
+        } else if (token->type == '}') {
+            json_parser_pop(p);
+        } else {
+            json_error(p, "syntax error expecting '}' or ','");
+        }
+        break;
+
+    case JSON_PARSE_ARRAY_INIT:
+        if (token->type == ']') {
+            json_parser_pop(p);
+            break;
+        }
+        /* Fall through. */
+    case JSON_PARSE_ARRAY_VALUE:
+        json_parse_value(p, token, JSON_PARSE_ARRAY_NEXT);
+        break;
+
+    case JSON_PARSE_ARRAY_NEXT:
+        if (token->type == ',') {
+            p->parse_state = JSON_PARSE_ARRAY_VALUE;
+        } else if (token->type == ']') {
+            json_parser_pop(p);
+        } else {
+            json_error(p, "syntax error expecting ']' or ','");
+        }
+        break;
+
+    default:
+        abort();
+    }
+
+    p->lex_state = JSON_LEX_START;
+    ds_clear(&p->buffer);
+}
+
+static struct json *
+json_create(enum json_type type)
+{
+    struct json *json = xmalloc(sizeof *json);
+    json->type = type;
+    return json;
+}
+
+static void
+json_error(struct json_parser *p, const char *format, ...)
+{
+    if (!p->error) {
+        struct ds msg;
+        va_list args;
+
+        ds_init(&msg);
+        ds_put_format(&msg, "line %d, column %d, byte %d: ",
+                      p->line_number, p->column_number, p->byte_number);
+        va_start(args, format);
+        ds_put_format_valist(&msg, format, args);
+        va_end(args);
+
+        p->error = ds_steal_cstr(&msg);
+
+        p->done = true;
+    }
+}
+\f
+#define SPACES_PER_LEVEL 2
+
+struct json_serializer {
+    struct ds *ds;
+    int depth;
+    int flags;
+};
+
+static void json_serialize(const struct json *, struct json_serializer *);
+static void json_serialize_object(const struct shash *object,
+                                  struct json_serializer *);
+static void json_serialize_array(const struct json_array *,
+                                 struct json_serializer *);
+static void json_serialize_string(const char *, struct ds *);
+
+/* Converts 'json' to a string in JSON format, encoded in UTF-8, and returns
+ * that string.  The caller is responsible for freeing the returned string,
+ * with free(), when it is no longer needed.
+ *
+ * If 'flags' contains JSSF_PRETTY, the output is pretty-printed with each
+ * nesting level introducing an additional indentation.  Otherwise, the
+ * returned string does not contain any new-line characters.
+ *
+ * If 'flags' contains JSSF_SORT, members of objects in the output are sorted
+ * in bytewise lexicographic order for reproducibility.  Otherwise, members of
+ * objects are output in an indeterminate order.
+ *
+ * The returned string is valid JSON only if 'json' represents an array or an
+ * object, since a bare literal does not satisfy the JSON grammar. */
+char *
+json_to_string(const struct json *json, int flags)
+{
+    struct ds ds;
+
+    ds_init(&ds);
+    json_to_ds(json, flags, &ds);
+    return ds_steal_cstr(&ds);
+}
+
+/* Same as json_to_string(), but the output is appended to 'ds'. */
+void
+json_to_ds(const struct json *json, int flags, struct ds *ds)
+{
+    struct json_serializer s;
+
+    s.ds = ds;
+    s.depth = 0;
+    s.flags = flags;
+    json_serialize(json, &s);
+}
+
+static void
+json_serialize(const struct json *json, struct json_serializer *s)
+{
+    struct ds *ds = s->ds;
+
+    switch (json->type) {
+    case JSON_NULL:
+        ds_put_cstr(ds, "null");
+        break;
+
+    case JSON_FALSE:
+        ds_put_cstr(ds, "false");
+        break;
+
+    case JSON_TRUE:
+        ds_put_cstr(ds, "true");
+        break;
+
+    case JSON_OBJECT:
+        json_serialize_object(json->u.object, s);
+        break;
+
+    case JSON_ARRAY:
+        json_serialize_array(&json->u.array, s);
+        break;
+
+    case JSON_INTEGER:
+        ds_put_format(ds, "%lld", json->u.integer);
+        break;
+
+    case JSON_REAL:
+        ds_put_format(ds, "%.*g", DBL_DIG, json->u.real);
+        break;
+
+    case JSON_STRING:
+        json_serialize_string(json->u.string, ds);
+        break;
+
+    case JSON_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static void
+indent_line(struct json_serializer *s)
+{
+    if (s->flags & JSSF_PRETTY) {
+        ds_put_char(s->ds, '\n');
+        ds_put_char_multiple(s->ds, ' ', SPACES_PER_LEVEL * s->depth);
+    }
+}
+
+static void
+json_serialize_object_member(size_t i, const struct shash_node *node,
+                             struct json_serializer *s)
+{
+    struct ds *ds = s->ds;
+
+    if (i) {
+        ds_put_char(ds, ',');
+        indent_line(s);
+    }
+
+    json_serialize_string(node->name, ds);
+    ds_put_char(ds, ':');
+    if (s->flags & JSSF_PRETTY) {
+        ds_put_char(ds, ' ');
+    }
+    json_serialize(node->data, s);
+}
+
+static void
+json_serialize_object(const struct shash *object, struct json_serializer *s)
+{
+    struct ds *ds = s->ds;
+
+    ds_put_char(ds, '{');
+
+    s->depth++;
+    indent_line(s);
+
+    if (s->flags & JSSF_SORT) {
+        const struct shash_node **nodes;
+        size_t n, i;
+
+        nodes = shash_sort(object);
+        n = shash_count(object);
+        for (i = 0; i < n; i++) {
+            json_serialize_object_member(i, nodes[i], s);
+        }
+        free(nodes);
+    } else {
+        struct shash_node *node;
+        size_t i;
+
+        i = 0;
+        SHASH_FOR_EACH (node, object) {
+            json_serialize_object_member(i++, node, s);
+        }
+    }
+
+    ds_put_char(ds, '}');
+    s->depth--;
+}
+
+static void
+json_serialize_array(const struct json_array *array, struct json_serializer *s)
+{
+    struct ds *ds = s->ds;
+    size_t i;
+
+    ds_put_char(ds, '[');
+    s->depth++;
+
+    if (array->n > 0) {
+        indent_line(s);
+
+        for (i = 0; i < array->n; i++) {
+            if (i) {
+                ds_put_char(ds, ',');
+                indent_line(s);
+            }
+            json_serialize(array->elems[i], s);
+        }
+    }
+
+    s->depth--;
+    ds_put_char(ds, ']');
+}
+
+static void
+json_serialize_string(const char *string, struct ds *ds)
+{
+    uint8_t c;
+
+    ds_put_char(ds, '"');
+    while ((c = *string++) != '\0') {
+        switch (c) {
+        case '"':
+            ds_put_cstr(ds, "\\\"");
+            break;
+
+        case '\\':
+            ds_put_cstr(ds, "\\\\");
+            break;
+
+        case '\b':
+            ds_put_cstr(ds, "\\b");
+            break;
+
+        case '\f':
+            ds_put_cstr(ds, "\\f");
+            break;
+
+        case '\n':
+            ds_put_cstr(ds, "\\n");
+            break;
+
+        case '\r':
+            ds_put_cstr(ds, "\\r");
+            break;
+
+        case '\t':
+            ds_put_cstr(ds, "\\t");
+            break;
+
+        default:
+            if (c >= 32) {
+                ds_put_char(ds, c);
+            } else {
+                ds_put_format(ds, "\\u%04x", c);
+            }
+            break;
+        }
+    }
+    ds_put_char(ds, '"');
+}
diff --git a/lib/json.h b/lib/json.h
new file mode 100644 (file)
index 0000000..41257b0
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSON_H
+#define JSON_H 1
+
+/* This is an implementation of JavaScript Object Notation (JSON) as specified
+ * by RFC 4627.  It is intended to fully comply with RFC 4627, with the
+ * following known exceptions and clarifications:
+ *
+ *      - Null bytes (\u0000) are not allowed in strings.
+ *
+ *      - Only UTF-8 encoding is supported (RFC 4627 allows for other Unicode
+ *        encodings).
+ *
+ *      - Names within an object must be unique (RFC 4627 says that they
+ *        "should" be unique).
+ */
+
+#include "shash.h"
+
+struct ds;
+
+/* Type of a JSON value. */
+enum json_type {
+    JSON_NULL,                  /* null */
+    JSON_FALSE,                 /* false */
+    JSON_TRUE,                  /* true */
+    JSON_OBJECT,                /* {"a": b, "c": d, ...} */
+    JSON_ARRAY,                 /* [1, 2, 3, ...] */
+    JSON_INTEGER,               /* 123. */
+    JSON_REAL,                  /* 123.456. */
+    JSON_STRING,                /* "..." */
+    JSON_N_TYPES
+};
+
+const char *json_type_to_string(enum json_type);
+
+/* A JSON array. */
+struct json_array {
+    size_t n, n_allocated;
+    struct json **elems;
+};
+
+/* A JSON value. */
+struct json {
+    enum json_type type;
+    union {
+        struct shash *object;   /* Contains "struct json *"s. */
+        struct json_array array;
+        long long int integer;
+        double real;
+        char *string;
+    } u;
+};
+
+struct json *json_null_create(void);
+struct json *json_boolean_create(bool);
+struct json *json_string_create(const char *);
+struct json *json_string_create_nocopy(char *);
+struct json *json_integer_create(long long int);
+struct json *json_real_create(double);
+
+struct json *json_array_create_empty(void);
+void json_array_add(struct json *, struct json *element);
+void json_array_trim(struct json *);
+struct json *json_array_create(struct json **, size_t n);
+struct json *json_array_create_1(struct json *);
+struct json *json_array_create_2(struct json *, struct json *);
+struct json *json_array_create_3(struct json *, struct json *, struct json *);
+
+struct json *json_object_create(void);
+void json_object_put(struct json *, const char *name, struct json *value);
+void json_object_put_string(struct json *,
+                            const char *name, const char *value);
+
+const char *json_string(const struct json *);
+struct json_array *json_array(const struct json *);
+struct shash *json_object(const struct json *);
+bool json_boolean(const struct json *);
+double json_real(const struct json *);
+int64_t json_integer(const struct json *);
+
+struct json *json_clone(const struct json *);
+void json_destroy(struct json *);
+
+size_t json_hash(const struct json *, size_t basis);
+bool json_equal(const struct json *, const struct json *);
+\f
+/* Parsing JSON. */
+enum {
+    JSPF_TRAILER = 1 << 0       /* Check for garbage following input.  */
+};
+
+struct json_parser *json_parser_create(int flags);
+size_t json_parser_feed(struct json_parser *, const char *, size_t);
+bool json_parser_is_done(const struct json_parser *);
+struct json *json_parser_finish(struct json_parser *);
+void json_parser_abort(struct json_parser *);
+
+struct json *json_from_string(const char *string);
+struct json *json_from_file(const char *file_name);
+struct json *json_from_stream(FILE *stream);
+\f
+/* Serializing JSON. */
+
+enum {
+    JSSF_PRETTY = 1 << 0,       /* Multiple lines with indentation, if true. */
+    JSSF_SORT = 1 << 1          /* Object members in sorted order, if true. */
+};
+char *json_to_string(const struct json *, int flags);
+void json_to_ds(const struct json *, int flags, struct ds *);
+\f
+/* JSON string formatting operations. */
+
+bool json_string_unescape(const char *in, size_t in_len, char **outp);
+
+#endif /* json.h */
diff --git a/lib/jsonrpc.c b/lib/jsonrpc.c
new file mode 100644 (file)
index 0000000..5c7dfca
--- /dev/null
@@ -0,0 +1,867 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc.h"
+
+#include <assert.h>
+#include <errno.h>
+
+#include "byteq.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "list.h"
+#include "ofpbuf.h"
+#include "poll-loop.h"
+#include "queue.h"
+#include "reconnect.h"
+#include "stream.h"
+#include "timeval.h"
+
+#define THIS_MODULE VLM_jsonrpc
+#include "vlog.h"
+\f
+struct jsonrpc {
+    struct stream *stream;
+    char *name;
+    int status;
+
+    /* Input. */
+    struct byteq input;
+    struct json_parser *parser;
+    struct jsonrpc_msg *received;
+
+    /* Output. */
+    struct ovs_queue output;
+    size_t backlog;
+};
+
+/* Rate limit for error messages. */
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
+
+static void jsonrpc_received(struct jsonrpc *);
+static void jsonrpc_cleanup(struct jsonrpc *);
+
+struct jsonrpc *
+jsonrpc_open(struct stream *stream)
+{
+    struct jsonrpc *rpc;
+
+    assert(stream != NULL);
+
+    rpc = xzalloc(sizeof *rpc);
+    rpc->name = xstrdup(stream_get_name(stream));
+    rpc->stream = stream;
+    byteq_init(&rpc->input);
+    queue_init(&rpc->output);
+
+    return rpc;
+}
+
+void
+jsonrpc_close(struct jsonrpc *rpc)
+{
+    if (rpc) {
+        jsonrpc_cleanup(rpc);
+        free(rpc->name);
+        free(rpc);
+    }
+}
+
+void
+jsonrpc_run(struct jsonrpc *rpc)
+{
+    if (rpc->status) {
+        return;
+    }
+
+    stream_run(rpc->stream);
+    while (!queue_is_empty(&rpc->output)) {
+        struct ofpbuf *buf = rpc->output.head;
+        int retval;
+
+        retval = stream_send(rpc->stream, buf->data, buf->size);
+        if (retval >= 0) {
+            rpc->backlog -= retval;
+            ofpbuf_pull(buf, retval);
+            if (!buf->size) {
+                ofpbuf_delete(queue_pop_head(&rpc->output));
+            }
+        } else {
+            if (retval != -EAGAIN) {
+                VLOG_WARN_RL(&rl, "%s: send error: %s",
+                             rpc->name, strerror(-retval));
+                jsonrpc_error(rpc, -retval);
+            }
+            break;
+        }
+    }
+}
+
+void
+jsonrpc_wait(struct jsonrpc *rpc)
+{
+    if (!rpc->status) {
+        stream_run_wait(rpc->stream);
+        if (!queue_is_empty(&rpc->output)) {
+            stream_send_wait(rpc->stream);
+        }
+    }
+}
+
+int
+jsonrpc_get_status(const struct jsonrpc *rpc)
+{
+    return rpc->status;
+}
+
+size_t
+jsonrpc_get_backlog(const struct jsonrpc *rpc)
+{
+    return rpc->status ? 0 : rpc->backlog;
+}
+
+const char *
+jsonrpc_get_name(const struct jsonrpc *rpc)
+{
+    return rpc->name;
+}
+
+static void
+jsonrpc_log_msg(const struct jsonrpc *rpc, const char *title,
+                const struct jsonrpc_msg *msg)
+{
+    if (VLOG_IS_DBG_ENABLED()) {
+        struct ds s = DS_EMPTY_INITIALIZER;
+        if (msg->method) {
+            ds_put_format(&s, ", method=\"%s\"", msg->method);
+        }
+        if (msg->params) {
+            ds_put_cstr(&s, ", params=");
+            json_to_ds(msg->params, 0, &s);
+        }
+        if (msg->result) {
+            ds_put_cstr(&s, ", result=");
+            json_to_ds(msg->result, 0, &s);
+        }
+        if (msg->error) {
+            ds_put_cstr(&s, ", error=");
+            json_to_ds(msg->error, 0, &s);
+        }
+        if (msg->id) {
+            ds_put_cstr(&s, ", id=");
+            json_to_ds(msg->id, 0, &s);
+        }
+        VLOG_DBG("%s: %s %s%s", rpc->name, title,
+                 jsonrpc_msg_type_to_string(msg->type), ds_cstr(&s));
+        ds_destroy(&s);
+    }
+}
+
+/* Always takes ownership of 'msg', regardless of success. */
+int
+jsonrpc_send(struct jsonrpc *rpc, struct jsonrpc_msg *msg)
+{
+    struct ofpbuf *buf;
+    struct json *json;
+    size_t length;
+    char *s;
+
+    if (rpc->status) {
+        jsonrpc_msg_destroy(msg);
+        return rpc->status;
+    }
+
+    jsonrpc_log_msg(rpc, "send", msg);
+
+    json = jsonrpc_msg_to_json(msg);
+    s = json_to_string(json, 0);
+    length = strlen(s);
+    json_destroy(json);
+
+    buf = xmalloc(sizeof *buf);
+    ofpbuf_use(buf, s, length);
+    buf->size = length;
+    queue_push_tail(&rpc->output, buf);
+    rpc->backlog += length;
+
+    if (rpc->output.n == 1) {
+        jsonrpc_run(rpc);
+    }
+    return rpc->status;
+}
+
+int
+jsonrpc_recv(struct jsonrpc *rpc, struct jsonrpc_msg **msgp)
+{
+    *msgp = NULL;
+    if (rpc->status) {
+        return rpc->status;
+    }
+
+    while (!rpc->received) {
+        if (byteq_is_empty(&rpc->input)) {
+            size_t chunk;
+            int retval;
+
+            chunk = byteq_headroom(&rpc->input);
+            retval = stream_recv(rpc->stream, byteq_head(&rpc->input), chunk);
+            if (retval < 0) {
+                if (retval == -EAGAIN) {
+                    return EAGAIN;
+                } else {
+                    VLOG_WARN_RL(&rl, "%s: receive error: %s",
+                                 rpc->name, strerror(-retval));
+                    jsonrpc_error(rpc, -retval);
+                    return rpc->status;
+                }
+            } else if (retval == 0) {
+                VLOG_INFO_RL(&rl, "%s: connection closed", rpc->name);
+                jsonrpc_error(rpc, EOF);
+                return EOF;
+            }
+            byteq_advance_head(&rpc->input, retval);
+        } else {
+            size_t n, used;
+
+            if (!rpc->parser) {
+                rpc->parser = json_parser_create(0);
+            }
+            n = byteq_tailroom(&rpc->input);
+            used = json_parser_feed(rpc->parser,
+                                    (char *) byteq_tail(&rpc->input), n);
+            byteq_advance_tail(&rpc->input, used);
+            if (json_parser_is_done(rpc->parser)) {
+                jsonrpc_received(rpc);
+                if (rpc->status) {
+                    return rpc->status;
+                }
+            }
+        }
+    }
+
+    *msgp = rpc->received;
+    rpc->received = NULL;
+    return 0;
+}
+
+void
+jsonrpc_recv_wait(struct jsonrpc *rpc)
+{
+    if (rpc->status || rpc->received || !byteq_is_empty(&rpc->input)) {
+        poll_immediate_wake();
+    } else {
+        stream_recv_wait(rpc->stream);
+    }
+}
+
+/* Always takes ownership of 'msg', regardless of success. */
+int
+jsonrpc_send_block(struct jsonrpc *rpc, struct jsonrpc_msg *msg)
+{
+    int error;
+
+    error = jsonrpc_send(rpc, msg);
+    if (error) {
+        return error;
+    }
+
+    for (;;) {
+        jsonrpc_run(rpc);
+        if (queue_is_empty(&rpc->output) || rpc->status) {
+            return rpc->status;
+        }
+        jsonrpc_wait(rpc);
+        poll_block();
+    }
+}
+
+int
+jsonrpc_recv_block(struct jsonrpc *rpc, struct jsonrpc_msg **msgp)
+{
+    for (;;) {
+        int error = jsonrpc_recv(rpc, msgp);
+        if (error != EAGAIN) {
+            return error;
+        }
+
+        jsonrpc_run(rpc);
+        jsonrpc_wait(rpc);
+        jsonrpc_recv_wait(rpc);
+        poll_block();
+    }
+}
+
+/* Always takes ownership of 'request', regardless of success. */
+int
+jsonrpc_transact_block(struct jsonrpc *rpc, struct jsonrpc_msg *request,
+                       struct jsonrpc_msg **replyp)
+{
+    struct jsonrpc_msg *reply = NULL;
+    struct json *id;
+    int error;
+
+    id = json_clone(request->id);
+    error = jsonrpc_send_block(rpc, request);
+    if (!error) {
+        for (;;) {
+            error = jsonrpc_recv_block(rpc, &reply);
+            if (error
+                || (reply->type == JSONRPC_REPLY
+                    && json_equal(id, reply->id))) {
+                break;
+            }
+            jsonrpc_msg_destroy(reply);
+        }
+    }
+    *replyp = error ? NULL : reply;
+    json_destroy(id);
+    return error;
+}
+
+static void
+jsonrpc_received(struct jsonrpc *rpc)
+{
+    struct jsonrpc_msg *msg;
+    struct json *json;
+    char *error;
+
+    json = json_parser_finish(rpc->parser);
+    rpc->parser = NULL;
+    if (json->type == JSON_STRING) {
+        VLOG_WARN_RL(&rl, "%s: error parsing stream: %s",
+                     rpc->name, json_string(json));
+        jsonrpc_error(rpc, EPROTO);
+        json_destroy(json);
+        return;
+    }
+
+    error = jsonrpc_msg_from_json(json, &msg);
+    if (error) {
+        VLOG_WARN_RL(&rl, "%s: received bad JSON-RPC message: %s",
+                     rpc->name, error);
+        free(error);
+        jsonrpc_error(rpc, EPROTO);
+        return;
+    }
+
+    jsonrpc_log_msg(rpc, "received", msg);
+    rpc->received = msg;
+}
+
+void
+jsonrpc_error(struct jsonrpc *rpc, int error)
+{
+    assert(error);
+    if (!rpc->status) {
+        rpc->status = error;
+        jsonrpc_cleanup(rpc);
+    }
+}
+
+static void
+jsonrpc_cleanup(struct jsonrpc *rpc)
+{
+    stream_close(rpc->stream);
+    rpc->stream = NULL;
+
+    json_parser_abort(rpc->parser);
+    rpc->parser = NULL;
+
+    jsonrpc_msg_destroy(rpc->received);
+    rpc->received = NULL;
+
+    queue_clear(&rpc->output);
+    rpc->backlog = 0;
+}
+\f
+static struct jsonrpc_msg *
+jsonrpc_create(enum jsonrpc_msg_type type, const char *method,
+                struct json *params, struct json *result, struct json *error,
+                struct json *id)
+{
+    struct jsonrpc_msg *msg = xmalloc(sizeof *msg);
+    msg->type = type;
+    msg->method = method ? xstrdup(method) : NULL;
+    msg->params = params;
+    msg->result = result;
+    msg->error = error;
+    msg->id = id;
+    return msg;
+}
+
+static struct json *
+jsonrpc_create_id(void)
+{
+    static unsigned int id;
+    return json_integer_create(id++);
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_request(const char *method, struct json *params,
+                       struct json **idp)
+{
+    struct json *id = jsonrpc_create_id();
+    if (idp) {
+        *idp = json_clone(id);
+    }
+    return jsonrpc_create(JSONRPC_REQUEST, method, params, NULL, NULL, id);
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_notify(const char *method, struct json *params)
+{
+    return jsonrpc_create(JSONRPC_NOTIFY, method, params, NULL, NULL, NULL);
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_reply(struct json *result, const struct json *id)
+{
+    return jsonrpc_create(JSONRPC_REPLY, NULL, NULL, result, NULL,
+                           json_clone(id));
+}
+
+struct jsonrpc_msg *
+jsonrpc_create_error(struct json *error, const struct json *id)
+{
+    return jsonrpc_create(JSONRPC_REPLY, NULL, NULL, NULL, error,
+                           json_clone(id));
+}
+
+const char *
+jsonrpc_msg_type_to_string(enum jsonrpc_msg_type type)
+{
+    switch (type) {
+    case JSONRPC_REQUEST:
+        return "request";
+
+    case JSONRPC_NOTIFY:
+        return "notification";
+
+    case JSONRPC_REPLY:
+        return "reply";
+
+    case JSONRPC_ERROR:
+        return "error";
+    }
+    return "(null)";
+}
+
+char *
+jsonrpc_msg_is_valid(const struct jsonrpc_msg *m)
+{
+    const char *type_name;
+    unsigned int pattern;
+
+    if (m->params && m->params->type != JSON_ARRAY) {
+        return xstrdup("\"params\" must be JSON array");
+    }
+
+    switch (m->type) {
+    case JSONRPC_REQUEST:
+        pattern = 0x11001;
+        break;
+
+    case JSONRPC_NOTIFY:
+        pattern = 0x11000;
+        break;
+
+    case JSONRPC_REPLY:
+        pattern = 0x00101;
+        break;
+
+    case JSONRPC_ERROR:
+        pattern = 0x00011;
+        break;
+
+    default:
+        return xasprintf("invalid JSON-RPC message type %d", m->type);
+    }
+
+    type_name = jsonrpc_msg_type_to_string(m->type);
+    if ((m->method != NULL) != ((pattern & 0x10000) != 0)) {
+        return xasprintf("%s must%s have \"method\"",
+                         type_name, (pattern & 0x10000) ? "" : " not");
+
+    }
+    if ((m->params != NULL) != ((pattern & 0x1000) != 0)) {
+        return xasprintf("%s must%s have \"params\"",
+                         type_name, (pattern & 0x1000) ? "" : " not");
+
+    }
+    if ((m->result != NULL) != ((pattern & 0x100) != 0)) {
+        return xasprintf("%s must%s have \"result\"",
+                         type_name, (pattern & 0x100) ? "" : " not");
+
+    }
+    if ((m->error != NULL) != ((pattern & 0x10) != 0)) {
+        return xasprintf("%s must%s have \"error\"",
+                         type_name, (pattern & 0x10) ? "" : " not");
+
+    }
+    if ((m->id != NULL) != ((pattern & 0x1) != 0)) {
+        return xasprintf("%s must%s have \"id\"",
+                         type_name, (pattern & 0x1) ? "" : " not");
+
+    }
+    return NULL;
+}
+
+void
+jsonrpc_msg_destroy(struct jsonrpc_msg *m)
+{
+    if (m) {
+        free(m->method);
+        json_destroy(m->params);
+        json_destroy(m->result);
+        json_destroy(m->error);
+        json_destroy(m->id);
+        free(m);
+    }
+}
+
+static struct json *
+null_from_json_null(struct json *json)
+{
+    if (json && json->type == JSON_NULL) {
+        json_destroy(json);
+        return NULL;
+    }
+    return json;
+}
+
+char *
+jsonrpc_msg_from_json(struct json *json, struct jsonrpc_msg **msgp)
+{
+    struct json *method = NULL;
+    struct jsonrpc_msg *msg = NULL;
+    struct shash *object;
+    char *error;
+
+    if (json->type != JSON_OBJECT) {
+        error = xstrdup("message is not a JSON object");
+        goto exit;
+    }
+    object = json_object(json);
+
+    method = shash_find_and_delete(object, "method");
+    if (method && method->type != JSON_STRING) {
+        error = xstrdup("method is not a JSON string");
+        goto exit;
+    }
+
+    msg = xzalloc(sizeof *msg);
+    msg->method = method ? xstrdup(method->u.string) : NULL;
+    msg->params = null_from_json_null(shash_find_and_delete(object, "params"));
+    msg->result = null_from_json_null(shash_find_and_delete(object, "result"));
+    msg->error = null_from_json_null(shash_find_and_delete(object, "error"));
+    msg->id = null_from_json_null(shash_find_and_delete(object, "id"));
+    msg->type = (msg->result ? JSONRPC_REPLY
+                 : msg->error ? JSONRPC_ERROR
+                 : msg->id ? JSONRPC_REQUEST
+                 : JSONRPC_NOTIFY);
+    if (!shash_is_empty(object)) {
+        error = xasprintf("message has unexpected member \"%s\"",
+                          shash_first(object)->name);
+        goto exit;
+    }
+    error = jsonrpc_msg_is_valid(msg);
+    if (error) {
+        goto exit;
+    }
+
+exit:
+    json_destroy(method);
+    json_destroy(json);
+    if (error) {
+        jsonrpc_msg_destroy(msg);
+        msg = NULL;
+    }
+    *msgp = msg;
+    return error;
+}
+
+struct json *
+jsonrpc_msg_to_json(struct jsonrpc_msg *m)
+{
+    struct json *json = json_object_create();
+
+    if (m->method) {
+        json_object_put(json, "method", json_string_create_nocopy(m->method));
+    }
+
+    if (m->params) {
+        json_object_put(json, "params", m->params);
+    }
+
+    if (m->result) {
+        json_object_put(json, "result", m->result);
+    } else if (m->type == JSONRPC_ERROR) {
+        json_object_put(json, "result", json_null_create());
+    }
+
+    if (m->error) {
+        json_object_put(json, "error", m->error);
+    } else if (m->type == JSONRPC_REPLY) {
+        json_object_put(json, "error", json_null_create());
+    }
+
+    if (m->id) {
+        json_object_put(json, "id", m->id);
+    } else if (m->type == JSONRPC_NOTIFY) {
+        json_object_put(json, "id", json_null_create());
+    }
+
+    free(m);
+
+    return json;
+}
+\f
+/* A JSON-RPC session with reconnection. */
+
+struct jsonrpc_session {
+    struct reconnect *reconnect;
+    struct jsonrpc *rpc;
+    struct stream *stream;
+    unsigned int seqno;
+};
+
+/* Creates and returns a jsonrpc_session that connects and reconnects, with
+ * back-off, to 'name', which should be a string acceptable to
+ * stream_open(). */
+struct jsonrpc_session *
+jsonrpc_session_open(const char *name)
+{
+    struct jsonrpc_session *s;
+
+    s = xmalloc(sizeof *s);
+    s->reconnect = reconnect_create(time_msec());
+    reconnect_set_name(s->reconnect, name);
+    reconnect_enable(s->reconnect, time_msec());
+    s->rpc = NULL;
+    s->stream = NULL;
+    s->seqno = 0;
+
+    return s;
+}
+
+/* Creates and returns a jsonrpc_session that is initially connected to
+ * 'jsonrpc'.  If the connection is dropped, it will not be reconnected. */
+struct jsonrpc_session *
+jsonrpc_session_open_unreliably(struct jsonrpc *jsonrpc)
+{
+    struct jsonrpc_session *s;
+
+    s = xmalloc(sizeof *s);
+    s->reconnect = reconnect_create(time_msec());
+    reconnect_set_name(s->reconnect, jsonrpc_get_name(jsonrpc));
+    reconnect_set_max_tries(s->reconnect, 0);
+    reconnect_connected(s->reconnect, time_msec());
+    s->rpc = jsonrpc;
+    s->stream = NULL;
+    s->seqno = 0;
+
+    return s;
+}
+
+void
+jsonrpc_session_close(struct jsonrpc_session *s)
+{
+    if (s) {
+        jsonrpc_close(s->rpc);
+        reconnect_destroy(s->reconnect);
+        free(s);
+    }
+}
+
+static void
+jsonrpc_session_disconnect(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        jsonrpc_error(s->rpc, EOF);
+        jsonrpc_close(s->rpc);
+        s->rpc = NULL;
+        s->seqno++;
+    } else if (s->stream) {
+        stream_close(s->stream);
+        s->stream = NULL;
+        s->seqno++;
+    }
+}
+
+static void
+jsonrpc_session_connect(struct jsonrpc_session *s)
+{
+    int error;
+
+    jsonrpc_session_disconnect(s);
+    error = stream_open(reconnect_get_name(s->reconnect), &s->stream);
+    if (error) {
+        reconnect_connect_failed(s->reconnect, time_msec(), error);
+    } else {
+        reconnect_connecting(s->reconnect, time_msec());
+    }
+    s->seqno++;
+}
+
+void
+jsonrpc_session_run(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        int error;
+
+        jsonrpc_run(s->rpc);
+        error = jsonrpc_get_status(s->rpc);
+        if (error) {
+            reconnect_disconnected(s->reconnect, time_msec(), 0);
+            jsonrpc_session_disconnect(s);
+        }
+    } else if (s->stream) {
+        int error;
+
+        stream_run(s->stream);
+        error = stream_connect(s->stream);
+        if (!error) {
+            reconnect_connected(s->reconnect, time_msec());
+            s->rpc = jsonrpc_open(s->stream);
+            s->stream = NULL;
+        } else if (error != EAGAIN) {
+            reconnect_connect_failed(s->reconnect, time_msec(), error);
+            stream_close(s->stream);
+            s->stream = NULL;
+        }
+    }
+
+    switch (reconnect_run(s->reconnect, time_msec())) {
+    case RECONNECT_CONNECT:
+        jsonrpc_session_connect(s);
+        break;
+
+    case RECONNECT_DISCONNECT:
+        reconnect_disconnected(s->reconnect, time_msec(), 0);
+        jsonrpc_session_disconnect(s);
+        break;
+
+    case RECONNECT_PROBE:
+        if (s->rpc) {
+            struct json *params;
+            struct jsonrpc_msg *request;
+
+            params = json_array_create_empty();
+            request = jsonrpc_create_request("echo", params, NULL);
+            json_destroy(request->id);
+            request->id = json_string_create("echo");
+            jsonrpc_send(s->rpc, request);
+        }
+        break;
+    }
+}
+
+void
+jsonrpc_session_wait(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        jsonrpc_wait(s->rpc);
+    } else if (s->stream) {
+        stream_run_wait(s->stream);
+        stream_connect_wait(s->stream);
+    }
+    reconnect_wait(s->reconnect, time_msec());
+}
+
+size_t
+jsonrpc_session_get_backlog(const struct jsonrpc_session *s)
+{
+    return s->rpc ? jsonrpc_get_backlog(s->rpc) : 0;
+}
+
+const char *
+jsonrpc_session_get_name(const struct jsonrpc_session *s)
+{
+    return reconnect_get_name(s->reconnect);
+}
+
+/* Always takes ownership of 'msg', regardless of success. */
+int
+jsonrpc_session_send(struct jsonrpc_session *s, struct jsonrpc_msg *msg)
+{
+    if (s->rpc) {
+        return jsonrpc_send(s->rpc, msg);
+    } else {
+        jsonrpc_msg_destroy(msg);
+        return ENOTCONN;
+    }
+}
+
+struct jsonrpc_msg *
+jsonrpc_session_recv(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        struct jsonrpc_msg *msg;
+        jsonrpc_recv(s->rpc, &msg);
+        if (msg) {
+            reconnect_received(s->reconnect, time_msec());
+            if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
+                /* Echo request.  Send reply. */
+                struct jsonrpc_msg *reply;
+
+                reply = jsonrpc_create_reply(json_clone(msg->params), msg->id);
+                jsonrpc_session_send(s, reply);
+            } else if (msg->type == JSONRPC_REPLY
+                && msg->id && msg->id->type == JSON_STRING
+                && !strcmp(msg->id->u.string, "echo")) {
+                /* It's a reply to our echo request.  Suppress it. */
+            } else {
+                return msg;
+            }
+            jsonrpc_msg_destroy(msg);
+        }
+    }
+    return NULL;
+}
+
+void
+jsonrpc_session_recv_wait(struct jsonrpc_session *s)
+{
+    if (s->rpc) {
+        jsonrpc_recv_wait(s->rpc);
+    }
+}
+
+bool
+jsonrpc_session_is_alive(const struct jsonrpc_session *s)
+{
+    return s->rpc || s->stream || reconnect_get_max_tries(s->reconnect);
+}
+
+bool
+jsonrpc_session_is_connected(const struct jsonrpc_session *s)
+{
+    return s->rpc != NULL;
+}
+
+unsigned int
+jsonrpc_session_get_seqno(const struct jsonrpc_session *s)
+{
+    return s->seqno;
+}
+
+void
+jsonrpc_session_force_reconnect(struct jsonrpc_session *s)
+{
+    reconnect_force_reconnect(s->reconnect, time_msec());
+}
diff --git a/lib/jsonrpc.h b/lib/jsonrpc.h
new file mode 100644 (file)
index 0000000..ae8b9de
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSONRPC_H
+#define JSONRPC_H 1
+
+/* This is an implementation of the JSON-RPC 1.0 specification defined at
+ * http://json-rpc.org/wiki/specification. */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct json;
+struct jsonrpc_msg;
+struct stream;
+\f
+/* API for a JSON-RPC stream. */
+
+struct jsonrpc *jsonrpc_open(struct stream *);
+void jsonrpc_close(struct jsonrpc *);
+
+void jsonrpc_run(struct jsonrpc *);
+void jsonrpc_wait(struct jsonrpc *);
+
+void jsonrpc_error(struct jsonrpc *, int error);
+int jsonrpc_get_status(const struct jsonrpc *);
+size_t jsonrpc_get_backlog(const struct jsonrpc *);
+const char *jsonrpc_get_name(const struct jsonrpc *);
+
+int jsonrpc_send(struct jsonrpc *, struct jsonrpc_msg *);
+int jsonrpc_recv(struct jsonrpc *, struct jsonrpc_msg **);
+void jsonrpc_recv_wait(struct jsonrpc *);
+
+int jsonrpc_send_block(struct jsonrpc *, struct jsonrpc_msg *);
+int jsonrpc_recv_block(struct jsonrpc *, struct jsonrpc_msg **);
+int jsonrpc_transact_block(struct jsonrpc *, struct jsonrpc_msg *,
+                           struct jsonrpc_msg **);
+
+/* Messages. */
+enum jsonrpc_msg_type {
+    JSONRPC_REQUEST,           /* Request. */
+    JSONRPC_NOTIFY,            /* Notification. */
+    JSONRPC_REPLY,             /* Successful reply. */
+    JSONRPC_ERROR              /* Error reply. */
+};
+
+struct jsonrpc_msg {
+    enum jsonrpc_msg_type type;
+    char *method;               /* Request or notification only. */
+    struct json *params;        /* Request or notification only. */
+    struct json *result;        /* Successful reply only. */
+    struct json *error;         /* Error reply only. */
+    struct json *id;            /* Request or reply only. */
+};
+
+struct jsonrpc_msg *jsonrpc_create_request(const char *method,
+                                           struct json *params,
+                                           struct json **idp);
+struct jsonrpc_msg *jsonrpc_create_notify(const char *method,
+                                          struct json *params);
+struct jsonrpc_msg *jsonrpc_create_reply(struct json *result,
+                                         const struct json *id);
+struct jsonrpc_msg *jsonrpc_create_error(struct json *error,
+                                         const struct json *id);
+
+const char *jsonrpc_msg_type_to_string(enum jsonrpc_msg_type);
+char *jsonrpc_msg_is_valid(const struct jsonrpc_msg *);
+void jsonrpc_msg_destroy(struct jsonrpc_msg *);
+
+char *jsonrpc_msg_from_json(struct json *, struct jsonrpc_msg **);
+struct json *jsonrpc_msg_to_json(struct jsonrpc_msg *);
+\f
+/* A JSON-RPC session with reconnection. */
+
+struct jsonrpc_session *jsonrpc_session_open(const char *name);
+struct jsonrpc_session *jsonrpc_session_open_unreliably(struct jsonrpc *);
+void jsonrpc_session_close(struct jsonrpc_session *);
+
+void jsonrpc_session_run(struct jsonrpc_session *);
+void jsonrpc_session_wait(struct jsonrpc_session *);
+
+size_t jsonrpc_session_get_backlog(const struct jsonrpc_session *);
+const char *jsonrpc_session_get_name(const struct jsonrpc_session *);
+
+int jsonrpc_session_send(struct jsonrpc_session *, struct jsonrpc_msg *);
+struct jsonrpc_msg *jsonrpc_session_recv(struct jsonrpc_session *);
+void jsonrpc_session_recv_wait(struct jsonrpc_session *);
+
+bool jsonrpc_session_is_alive(const struct jsonrpc_session *);
+bool jsonrpc_session_is_connected(const struct jsonrpc_session *);
+unsigned int jsonrpc_session_get_seqno(const struct jsonrpc_session *);
+void jsonrpc_session_force_reconnect(struct jsonrpc_session *);
+
+#endif /* jsonrpc.h */
index ecfa87f..78346ac 100644 (file)
@@ -113,7 +113,7 @@ lswitch_create(struct rconn *rconn, bool learn_macs,
     struct lswitch *sw;
     size_t i;
 
-    sw = xcalloc(1, sizeof *sw);
+    sw = xzalloc(sizeof *sw);
     sw->max_idle = max_idle;
     sw->datapath_id = 0;
     sw->last_features_request = time_now() - 1;
diff --git a/lib/lockfile.c b/lib/lockfile.c
new file mode 100644 (file)
index 0000000..9bb7c6b
--- /dev/null
@@ -0,0 +1,280 @@
+ /* Copyright (c) 2008, 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "lockfile.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "coverage.h"
+#include "hash.h"
+#include "hmap.h"
+#include "timeval.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_lockfile
+#include "vlog.h"
+
+struct lockfile {
+    struct hmap_node hmap_node;
+    char *name;
+    dev_t device;
+    ino_t inode;
+    int fd;
+};
+
+/* Lock table.
+ *
+ * We have to do this stupid dance because POSIX says that closing *any* file
+ * descriptor for a file on which a process holds a lock drops *all* locks on
+ * that file.  That means that we can't afford to open a lockfile more than
+ * once. */
+static struct hmap lock_table = HMAP_INITIALIZER(&lock_table);
+
+static void lockfile_unhash(struct lockfile *);
+static int lockfile_try_lock(const char *name, bool block,
+                             struct lockfile **lockfilep);
+
+/* Returns the name of the lockfile that would be created for locking a file
+ * named 'file_name'.  The caller is responsible for freeing the returned
+ * name, with free(), when it is no longer needed. */
+char *
+lockfile_name(const char *file_name)
+{
+    const char *slash = strrchr(file_name, '/');
+    return (slash
+            ? xasprintf("%.*s/.%s.~lock~",
+                        (int) (slash - file_name), file_name, slash + 1)
+            : xasprintf(".%s.~lock~", file_name));
+}
+
+/* Locks the configuration file against modification by other processes and
+ * re-reads it from disk.
+ *
+ * The 'timeout' specifies the maximum number of milliseconds to wait for the
+ * config file to become free.  Use 0 to avoid waiting or INT_MAX to wait
+ * forever.
+ *
+ * Returns 0 on success, otherwise a positive errno value.  On success,
+ * '*lockfilep' is set to point to a new "struct lockfile *" that may be
+ * unlocked with lockfile_unlock().  On failure, '*lockfilep' is set to
+ * NULL. */
+int
+lockfile_lock(const char *file, int timeout, struct lockfile **lockfilep)
+{
+    /* Only exclusive ("write") locks are supported.  This is not a problem
+     * because the Open vSwitch code that currently uses lock files does so in
+     * stylized ways such that any number of readers may access a file while it
+     * is being written. */
+    long long int start, elapsed;
+    char *lock_name;
+    int error;
+
+    COVERAGE_INC(lockfile_lock);
+
+    lock_name = lockfile_name(file);
+    time_refresh();
+    start = time_msec();
+
+    do {
+        error = lockfile_try_lock(lock_name, timeout > 0, lockfilep);
+        time_refresh();
+        elapsed = time_msec() - start;
+    } while (error == EINTR && (timeout == INT_MAX || elapsed < timeout));
+
+    if (!error) {
+        if (elapsed) {
+            VLOG_WARN("%s: waited %lld ms for lock file",
+                      lock_name, elapsed);
+        }
+    } else if (error == EINTR) {
+        COVERAGE_INC(lockfile_timeout);
+        VLOG_WARN("%s: giving up on lock file after %lld ms",
+                  lock_name, elapsed);
+        error = ETIMEDOUT;
+    } else {
+        COVERAGE_INC(lockfile_error);
+        if (error == EACCES) {
+            error = EAGAIN;
+        }
+        VLOG_WARN("%s: failed to lock file "
+                  "(after %lld ms, with %d-ms timeout): %s",
+                  lock_name, elapsed, timeout, strerror(error));
+    }
+
+    free(lock_name);
+    return error;
+}
+
+/* Unlocks 'lockfile', which must have been created by a call to
+ * lockfile_lock(), and frees 'lockfile'. */
+void
+lockfile_unlock(struct lockfile *lockfile)
+{
+    if (lockfile) {
+        COVERAGE_INC(lockfile_unlock);
+        lockfile_unhash(lockfile);
+        free(lockfile->name);
+        free(lockfile);
+    }
+}
+
+/* Marks all the currently locked lockfiles as no longer locked.  It makes
+ * sense to call this function after fork(), because a child created by fork()
+ * does not hold its parents' locks. */
+void
+lockfile_postfork(void)
+{
+    struct lockfile *lockfile;
+
+    HMAP_FOR_EACH (lockfile, struct lockfile, hmap_node, &lock_table) {
+        if (lockfile->fd >= 0) {
+            VLOG_WARN("%s: child does not inherit lock", lockfile->name);
+            lockfile_unhash(lockfile);
+        }
+    }
+}
+\f
+static uint32_t
+lockfile_hash(dev_t device, ino_t inode)
+{
+    return hash_bytes(&device, sizeof device,
+                      hash_bytes(&inode, sizeof inode, 0));
+}
+
+static struct lockfile *
+lockfile_find(dev_t device, ino_t inode)
+{
+    struct lockfile *lockfile;
+
+    HMAP_FOR_EACH_WITH_HASH (lockfile, struct lockfile, hmap_node,
+                             lockfile_hash(device, inode), &lock_table) {
+        if (lockfile->device == device && lockfile->inode == inode) {
+            return lockfile;
+        }
+    }
+    return NULL;
+}
+
+static void
+lockfile_unhash(struct lockfile *lockfile)
+{
+    if (lockfile->fd >= 0) {
+        close(lockfile->fd);
+        lockfile->fd = -1;
+        hmap_remove(&lock_table, &lockfile->hmap_node);
+    }
+}
+
+static struct lockfile *
+lockfile_register(const char *name, dev_t device, ino_t inode, int fd)
+{
+    struct lockfile *lockfile;
+
+    lockfile = lockfile_find(device, inode);
+    if (lockfile) {
+        VLOG_ERR("%s: lock file disappeared and reappeared!", name);
+        lockfile_unhash(lockfile);
+    }
+
+    lockfile = xmalloc(sizeof *lockfile);
+    lockfile->name = xstrdup(name);
+    lockfile->device = device;
+    lockfile->inode = inode;
+    lockfile->fd = fd;
+    hmap_insert(&lock_table, &lockfile->hmap_node,
+                lockfile_hash(device, inode));
+    return lockfile;
+}
+
+static int
+lockfile_try_lock(const char *name, bool block, struct lockfile **lockfilep)
+{
+    struct flock l;
+    struct stat s;
+    int error;
+    int fd;
+
+    *lockfilep = NULL;
+
+    /* Open the lock file, first creating it if necessary. */
+    for (;;) {
+        /* Check whether we've already got a lock on that file. */
+        if (!stat(name, &s)) {
+            if (lockfile_find(s.st_dev, s.st_ino)) {
+                return EDEADLK;
+            }
+        } else if (errno != ENOENT) {
+            VLOG_WARN("%s: failed to stat lock file: %s",
+                      name, strerror(errno));
+            return errno;
+        }
+
+        /* Try to open an existing lock file. */
+        fd = open(name, O_RDWR);
+        if (fd >= 0) {
+            break;
+        } else if (errno != ENOENT) {
+            VLOG_WARN("%s: failed to open lock file: %s",
+                      name, strerror(errno));
+            return errno;
+        }
+
+        /* Try to create a new lock file. */
+        VLOG_INFO("%s: lock file does not exist, creating", name);
+        fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
+        if (fd >= 0) {
+            break;
+        } else if (errno != EEXIST) {
+            VLOG_WARN("%s: failed to create lock file: %s",
+                      name, strerror(errno));
+            return errno;
+        }
+
+        /* Someone else created the lock file.  Try again. */
+    }
+
+    /* Get the inode and device number for the lock table. */
+    if (fstat(fd, &s)) {
+        VLOG_ERR("%s: failed to fstat lock file: %s", name, strerror(errno));
+        close(fd);
+        return errno;
+    }
+
+    /* Try to lock the file. */
+    memset(&l, 0, sizeof l);
+    l.l_type = F_WRLCK;
+    l.l_whence = SEEK_SET;
+    l.l_start = 0;
+    l.l_len = 0;
+
+    time_disable_restart();
+    error = fcntl(fd, block ? F_SETLKW : F_SETLK, &l) == -1 ? errno : 0;
+    time_enable_restart();
+
+    if (!error) {
+        *lockfilep = lockfile_register(name, s.st_dev, s.st_ino, fd);
+    } else {
+        close(fd);
+    }
+    return error;
+}
+
diff --git a/lib/lockfile.h b/lib/lockfile.h
new file mode 100644 (file)
index 0000000..c52fa21
--- /dev/null
@@ -0,0 +1,26 @@
+/* Copyright (c) 2008, 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LOCKFILE_H
+#define LOCKFILE_H 1
+
+struct lockfile;
+
+char *lockfile_name(const char *file);
+int lockfile_lock(const char *file, int timeout, struct lockfile **);
+void lockfile_unlock(struct lockfile *);
+void lockfile_postfork(void);
+
+#endif /* lib/lockfile.h */
index 3f1db14..a9d414d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -130,7 +130,7 @@ mac_learning_create(void)
         list_push_front(&ml->free, &s->lru_node);
     }
     ml->secret = random_uint32();
-    ml->non_learning_vlans = NULL;
+    ml->flood_vlans = NULL;
     return ml;
 }
 
@@ -139,24 +139,24 @@ void
 mac_learning_destroy(struct mac_learning *ml)
 {
     if (ml) {
-        bitmap_free(ml->non_learning_vlans);
+        bitmap_free(ml->flood_vlans);
     }
     free(ml);
 }
 
-/* Provides a bitmap of VLANs which have learning disabled.  It takes
- * ownership of the bitmap.  Returns true if the set has changed from
- * the previous value. */
+/* Provides a bitmap of VLANs which have learning disabled, that is, VLANs on
+ * which all packets are flooded.  It takes ownership of the bitmap.  Returns
+ * true if the set has changed from the previous value. */
 bool
-mac_learning_set_disabled_vlans(struct mac_learning *ml, unsigned long *bitmap)
+mac_learning_set_flood_vlans(struct mac_learning *ml, unsigned long *bitmap)
 {
     bool ret = (bitmap == NULL
-        ? ml->non_learning_vlans != NULL
-        : (ml->non_learning_vlans == NULL
-          || !bitmap_equal(bitmap, ml->non_learning_vlans, 4096)));
+                ? ml->flood_vlans != NULL
+                : (ml->flood_vlans == NULL
+                   || !bitmap_equal(bitmap, ml->flood_vlans, 4096)));
 
-    bitmap_free(ml->non_learning_vlans);
-    ml->non_learning_vlans = bitmap;
+    bitmap_free(ml->flood_vlans);
+    ml->flood_vlans = bitmap;
 
     return ret;
 }
@@ -164,8 +164,7 @@ mac_learning_set_disabled_vlans(struct mac_learning *ml, unsigned long *bitmap)
 static bool
 is_learning_vlan(const struct mac_learning *ml, uint16_t vlan)
 {
-    return !(ml->non_learning_vlans
-            && bitmap_is_set(ml->non_learning_vlans, vlan));
+    return !(ml->flood_vlans && bitmap_is_set(ml->flood_vlans, vlan));
 }
 
 /* Attempts to make 'ml' learn from the fact that a frame from 'src_mac' was
index ed843cd..c4a0e28 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,14 +51,14 @@ struct mac_learning {
                                    front, most recently used at the back. */
     struct list table[MAC_HASH_SIZE]; /* Hash table. */
     struct mac_entry entries[MAC_MAX]; /* All entries. */
-    uint32_t secret;            /* Secret for  */
-    unsigned long *non_learning_vlans; /* Bitmap of learning disabled VLANs. */
+    uint32_t secret;            /* Secret for randomizing hash table. */
+    unsigned long *flood_vlans; /* Bitmap of learning disabled VLANs. */
 };
 
 struct mac_learning *mac_learning_create(void);
 void mac_learning_destroy(struct mac_learning *);
-bool mac_learning_set_disabled_vlans(struct mac_learning *,
-                                     unsigned long *bitmap);
+bool mac_learning_set_flood_vlans(struct mac_learning *,
+                                  unsigned long *bitmap);
 tag_type mac_learning_learn(struct mac_learning *,
                             const uint8_t src[ETH_ADDR_LEN], uint16_t vlan,
                             uint16_t src_port);
index 61994c2..ccc3f84 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 #include <arpa/inet.h>
 #include <inttypes.h>
 #include <linux/if_tun.h>
+#include <linux/ip.h>
 #include <linux/types.h>
 #include <linux/ethtool.h>
 #include <linux/rtnetlink.h>
@@ -32,6 +33,7 @@
 #include <netpacket/packet.h>
 #include <net/ethernet.h>
 #include <net/if.h>
+#include <linux/if_tunnel.h>
 #include <net/if_arp.h>
 #include <net/if_packet.h>
 #include <net/route.h>
@@ -48,6 +50,7 @@
 #include "netlink.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
+#include "openvswitch/gre.h"
 #include "packets.h"
 #include "poll-loop.h"
 #include "rtnetlink.h"
 #include "shash.h"
 #include "svec.h"
 
+#ifndef GRE_IOCTL_ONLY
+#include <linux/if_link.h>
+#endif
+
 #define THIS_MODULE VLM_netdev_linux
 #include "vlog.h"
 \f
 #define ADVERTISED_Asym_Pause           (1 << 14)
 #endif
 
-/* Provider-specific netdev object.  Netdev objects are devices that are
- * created by the netdev library through a netdev_create() call. */
-struct netdev_obj_linux {
-    struct netdev_obj netdev_obj;
-
-    int tap_fd;                 /* File descriptor for TAP device. */
-};
-
-struct netdev_linux {
-    struct netdev netdev;
-
-    /* File descriptors.  For ordinary network devices, the two fds below are
-     * the same; for tap devices, they differ. */
-    int netdev_fd;              /* Network device. */
-    int tap_fd;                 /* TAP character device, if any, otherwise the
-                                 * network device. */
-
-    struct netdev_linux_cache *cache;
-};
+static struct rtnetlink_notifier netdev_linux_cache_notifier;
+static int cache_notifier_refcount;
 
 enum {
     VALID_IFINDEX = 1 << 0,
@@ -97,11 +87,15 @@ enum {
     VALID_IS_INTERNAL = 1 << 6
 };
 
-/* Cached network device information. */
-struct netdev_linux_cache {
+struct tap_state {
+    int fd;
+};
+
+struct netdev_dev_linux {
+    struct netdev_dev netdev_dev;
+
     struct shash_node *shash_node;
-    unsigned int valid;
-    int ref_cnt;
+    unsigned int cache_valid;
 
     int ifindex;
     uint8_t etheraddr[ETH_ADDR_LEN];
@@ -110,14 +104,39 @@ struct netdev_linux_cache {
     int mtu;
     int carrier;
     bool is_internal;
+
+    union {
+        struct tap_state tap;
+    } state;
 };
 
-static struct shash cache_map = SHASH_INITIALIZER(&cache_map);
-static struct rtnetlink_notifier netdev_linux_cache_notifier;
+struct netdev_linux {
+    struct netdev netdev;
+    int fd;
+};
 
 /* An AF_INET socket (used for ioctl operations). */
 static int af_inet_sock = -1;
 
+struct gre_config {
+    uint32_t local_ip;
+    uint32_t remote_ip;
+    uint32_t in_key;
+    uint32_t out_key;
+    bool have_in_key;
+    bool have_out_key;
+    bool in_csum;
+    bool out_csum;
+};
+
+static struct {
+    union {
+        struct nl_sock *nl_sock;
+        int ioctl_fd;
+    };
+    bool use_ioctl;
+} gre_descriptors;
+
 struct netdev_linux_notifier {
     struct netdev_notifier notifier;
     struct list node;
@@ -131,10 +150,11 @@ static struct rtnetlink_notifier netdev_linux_poll_notifier;
  * additional log messages. */
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
 
-static int netdev_linux_do_ethtool(struct netdev *, struct ethtool_cmd *,
+static int destroy_gre(const char *name);
+static int netdev_linux_do_ethtool(const char *name, struct ethtool_cmd *,
                                    int cmd, const char *cmd_name);
-static int netdev_linux_do_ioctl(const struct netdev *, struct ifreq *,
-                                 int cmd, const char *cmd_name);
+static int netdev_linux_do_ioctl(const char *name, struct ifreq *, int cmd,
+                                 const char *cmd_name);
 static int netdev_linux_get_ipv4(const struct netdev *, struct in_addr *,
                                  int cmd, const char *cmd_name);
 static int get_flags(const struct netdev *, int *flagsp);
@@ -150,17 +170,21 @@ static int set_etheraddr(const char *netdev_name, int hwaddr_family,
 static int get_stats_via_netlink(int ifindex, struct netdev_stats *stats);
 static int get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats);
 
-static struct netdev_obj_linux *
-netdev_obj_linux_cast(const struct netdev_obj *netdev_obj)
+static struct netdev_dev_linux *
+netdev_dev_linux_cast(const struct netdev_dev *netdev_dev)
 {
-    netdev_obj_assert_class(netdev_obj, &netdev_linux_class);
-    return CONTAINER_OF(netdev_obj, struct netdev_obj_linux, netdev_obj);
+    const char *type = netdev_dev_get_type(netdev_dev);
+    assert(!strcmp(type, "system") || !strcmp(type, "tap")
+            || !strcmp(type, "gre"));
+    return CONTAINER_OF(netdev_dev, struct netdev_dev_linux, netdev_dev);
 }
 
 static struct netdev_linux *
 netdev_linux_cast(const struct netdev *netdev)
 {
-    netdev_assert_class(netdev, &netdev_linux_class);
+    const char *type = netdev_get_type(netdev);
+    assert(!strcmp(type, "system") || !strcmp(type, "tap")
+            || !strcmp(type, "gre"));
     return CONTAINER_OF(netdev, struct netdev_linux, netdev);
 }
 
@@ -194,48 +218,409 @@ static void
 netdev_linux_cache_cb(const struct rtnetlink_change *change,
                       void *aux UNUSED)
 {
-    struct netdev_linux_cache *cache;
+    struct netdev_dev_linux *dev;
     if (change) {
-        cache = shash_find_data(&cache_map, change->ifname);
-        if (cache) {
-            cache->valid = 0;
+        struct netdev_dev *base_dev = netdev_dev_from_name(change->ifname);
+        if (base_dev) {
+            dev = netdev_dev_linux_cast(base_dev);
+            dev->cache_valid = 0;
         }
     } else {
+        struct shash device_shash;
         struct shash_node *node;
-        SHASH_FOR_EACH (node, &cache_map) {
-            cache = node->data;
-            cache->valid = 0;
+
+        shash_init(&device_shash);
+        netdev_dev_get_devices(&netdev_linux_class, &device_shash);
+        SHASH_FOR_EACH (node, &device_shash) {
+            dev = node->data;
+            dev->cache_valid = 0;
         }
+        shash_destroy(&device_shash);
     }
 }
 
-/* Creates the netdev object of 'type' with 'name'. */
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
 static int
-netdev_linux_create(const char *name, const char *type, 
-                    const struct shash *args, bool created)
+setup_gre_netlink(const char *name UNUSED, struct gre_config *config UNUSED,
+                  bool create UNUSED)
 {
-    struct netdev_obj_linux *netdev_obj;
-    static const char tap_dev[] = "/dev/net/tun";
+#ifdef GRE_IOCTL_ONLY
+    return EOPNOTSUPP;
+#else
+    int error;
+    struct ofpbuf request, *reply;
+    unsigned int nl_flags;
+    struct ifinfomsg ifinfomsg;
+    struct nlattr *linkinfo_hdr;
+    struct nlattr *info_data_hdr;
+    uint16_t iflags = 0;
+    uint16_t oflags = 0;
+    uint8_t pmtudisc = 0;
+
+    VLOG_DBG("%s: attempting to create gre device using netlink", name);
+
+    if (!gre_descriptors.nl_sock) {
+        error = nl_sock_create(NETLINK_ROUTE, 0, 0, 0,
+                               &gre_descriptors.nl_sock);
+        if (error) {
+            VLOG_WARN("couldn't create netlink socket: %s", strerror(error));
+            goto error;
+        }
+    }
+
+    ofpbuf_init(&request, 0);
+
+    nl_flags = NLM_F_REQUEST;
+    if (create) {
+        nl_flags |= NLM_F_CREATE|NLM_F_EXCL;
+    }
+
+    /* We over-reserve space, because we do some pointer arithmetic
+     * and don't want the buffer address shifting under us. */
+    nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock, 2048, RTM_NEWLINK,
+                        nl_flags);
+
+    memset(&ifinfomsg, 0, sizeof ifinfomsg);
+    ifinfomsg.ifi_family = AF_UNSPEC;
+    nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+    linkinfo_hdr = ofpbuf_tail(&request);
+    nl_msg_put_unspec(&request, IFLA_LINKINFO, NULL, 0);
+
+    nl_msg_put_unspec(&request, IFLA_INFO_KIND, "gretap", 6);
+
+    info_data_hdr = ofpbuf_tail(&request);
+    nl_msg_put_unspec(&request, IFLA_INFO_DATA, NULL, 0);
+
+    /* Set flags */
+    if (config->have_in_key) {
+        iflags |= GRE_KEY;
+    }
+    if (config->have_out_key) {
+        oflags |= GRE_KEY;
+    }
+
+    if (config->in_csum) {
+        iflags |= GRE_CSUM;
+    }
+    if (config->out_csum) {
+        oflags |= GRE_CSUM;
+    }
+
+    /* Add options */
+    nl_msg_put_u32(&request, IFLA_GRE_IKEY, config->in_key);
+    nl_msg_put_u32(&request, IFLA_GRE_OKEY, config->out_key);
+    nl_msg_put_u16(&request, IFLA_GRE_IFLAGS, iflags);
+    nl_msg_put_u16(&request, IFLA_GRE_OFLAGS, oflags);
+    nl_msg_put_u32(&request, IFLA_GRE_LOCAL, config->local_ip);
+    nl_msg_put_u32(&request, IFLA_GRE_REMOTE, config->remote_ip);
+    nl_msg_put_u8(&request, IFLA_GRE_PMTUDISC, pmtudisc);
+    nl_msg_put_u8(&request, IFLA_GRE_TTL, 0);
+    nl_msg_put_u8(&request, IFLA_GRE_TOS, 0);
+
+    info_data_hdr->nla_len = (char *)ofpbuf_tail(&request)
+                                - (char *)info_data_hdr;
+    linkinfo_hdr->nla_len = (char *)ofpbuf_tail(&request)
+                                - (char *)linkinfo_hdr;
+
+    nl_msg_put_string(&request, IFLA_IFNAME, name);
+
+    error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+    ofpbuf_uninit(&request);
+    if (error) {
+        VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
+        goto error;
+    }
+    ofpbuf_delete(reply);
+
+error:
+    return error;
+#endif
+}
+
+static int
+setup_gre_ioctl(const char *name, struct gre_config *config, bool create)
+{
+    struct ip_tunnel_parm p;
     struct ifreq ifr;
+
+    VLOG_DBG("%s: attempting to create gre device using ioctl", name);
+
+    memset(&p, 0, sizeof p);
+
+    strncpy(p.name, name, IFNAMSIZ);
+
+    p.iph.version = 4;
+    p.iph.ihl = 5;
+    p.iph.protocol = IPPROTO_GRE;
+    p.iph.saddr = config->local_ip;
+    p.iph.daddr = config->remote_ip;
+
+    if (config->have_in_key) {
+        p.i_flags |= GRE_KEY;
+        p.i_key = config->in_key;
+    }
+    if (config->have_out_key) {
+        p.o_flags |= GRE_KEY;
+        p.o_key = config->out_key;
+    }
+
+    if (config->in_csum) {
+        p.i_flags |= GRE_CSUM;
+    }
+    if (config->out_csum) {
+        p.o_flags |= GRE_CSUM;
+    }
+
+    strncpy(ifr.ifr_name, create ? GRE_IOCTL_DEVICE : name, IFNAMSIZ);
+    ifr.ifr_ifru.ifru_data = (void *)&p;
+
+    if (!gre_descriptors.ioctl_fd) {
+        gre_descriptors.ioctl_fd = socket(AF_INET, SOCK_DGRAM, 0);
+        if (gre_descriptors.ioctl_fd < 0) {
+            VLOG_WARN("couldn't create gre ioctl socket: %s", strerror(errno));
+            gre_descriptors.ioctl_fd = 0;
+            return errno;
+        }
+    }
+
+    if (ioctl(gre_descriptors.ioctl_fd, create ? SIOCADDGRETAP : SIOCCHGGRETAP,
+              &ifr) < 0) {
+        VLOG_WARN("couldn't do gre ioctl: %s", strerror(errno));
+        return errno;
+    }
+
+    return 0;
+}
+
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
+static bool
+check_gre_device_netlink(const char *name UNUSED)
+{
+#ifdef GRE_IOCTL_ONLY
+    return false;
+#else
+    static const struct nl_policy getlink_policy[] = {
+        [IFLA_LINKINFO] = { .type = NL_A_NESTED, .optional = false },
+    };
+
+    static const struct nl_policy linkinfo_policy[] = {
+        [IFLA_INFO_KIND] = { .type = NL_A_STRING, .optional = false },
+    };
+
+    int error;
+    bool ret = false;
+    struct ofpbuf request, *reply;
+    struct ifinfomsg ifinfomsg;
+    struct nlattr *getlink_attrs[ARRAY_SIZE(getlink_policy)];
+    struct nlattr *linkinfo_attrs[ARRAY_SIZE(linkinfo_policy)];
+    struct ofpbuf linkinfo;
+    const char *device_kind;
+
+    ofpbuf_init(&request, 0);
+
+    nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock,
+                        NLMSG_LENGTH(sizeof ifinfomsg), RTM_GETLINK,
+                        NLM_F_REQUEST);
+
+    memset(&ifinfomsg, 0, sizeof ifinfomsg);
+    ifinfomsg.ifi_family = AF_UNSPEC;
+    ifinfomsg.ifi_index =  do_get_ifindex(name);
+    nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+    error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+    ofpbuf_uninit(&request);
+    if (error) {
+        VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
+        return false;
+    }
+
+    if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
+                         getlink_policy, getlink_attrs,
+                         ARRAY_SIZE(getlink_policy))) {
+        VLOG_WARN("received bad rtnl message (getlink policy)");
+        goto error;
+    }
+
+    linkinfo.data = (void *)nl_attr_get(getlink_attrs[IFLA_LINKINFO]);
+    linkinfo.size = nl_attr_get_size(getlink_attrs[IFLA_LINKINFO]);
+    if (!nl_policy_parse(&linkinfo, 0, linkinfo_policy,
+                        linkinfo_attrs, ARRAY_SIZE(linkinfo_policy))) {
+        VLOG_WARN("received bad rtnl message (linkinfo policy)");
+        goto error;
+    }
+
+    device_kind = nl_attr_get_string(linkinfo_attrs[IFLA_INFO_KIND]);
+    ret = !strcmp(device_kind, "gretap");
+
+error:
+    ofpbuf_delete(reply);
+    return ret;
+#endif
+}
+
+static bool
+check_gre_device_ioctl(const char *name)
+{
+    struct ethtool_drvinfo drvinfo;
+    int error;
+
+    memset(&drvinfo, 0, sizeof drvinfo);
+    error = netdev_linux_do_ethtool(name, (struct ethtool_cmd *)&drvinfo,
+                                    ETHTOOL_GDRVINFO, "ETHTOOL_GDRVINFO");
+
+    return !error && !strcmp(drvinfo.driver, "ip_gre")
+           && !strcmp(drvinfo.bus_info, "gretap");
+}
+
+static int
+setup_gre(const char *name, const struct shash *args, bool create)
+{
+    int error;
+    struct in_addr in_addr;
+    struct shash_node *node;
+    struct gre_config config;
+
+    memset(&config, 0, sizeof config);
+    config.in_csum = true;
+    config.out_csum = true;
+
+    SHASH_FOR_EACH (node, args) {
+        if (!strcmp(node->name, "remote_ip")) {
+            if (lookup_ip(node->data, &in_addr)) {
+                VLOG_WARN("bad 'remote_ip' for gre device %s ", name);
+            } else {
+                config.remote_ip = in_addr.s_addr;
+            }
+        } else if (!strcmp(node->name, "local_ip")) {
+            if (lookup_ip(node->data, &in_addr)) {
+                VLOG_WARN("bad 'local_ip' for gre device %s ", name);
+            } else {
+                config.local_ip = in_addr.s_addr;
+            }
+        } else if (!strcmp(node->name, "key")) {
+            config.have_in_key = true;
+            config.have_out_key = true;
+            config.in_key = htonl(atoi(node->data));
+            config.out_key = htonl(atoi(node->data));
+        } else if (!strcmp(node->name, "in_key")) {
+            config.have_in_key = true;
+            config.in_key = htonl(atoi(node->data));
+        } else if (!strcmp(node->name, "out_key")) {
+            config.have_out_key = true;
+            config.out_key = htonl(atoi(node->data));
+        } else if (!strcmp(node->name, "csum")) {
+            if (!strcmp(node->data, "false")) {
+                config.in_csum = false;
+                config.out_csum = false;
+            }
+        } else {
+            VLOG_WARN("unknown gre argument '%s'", node->name);
+        }
+    }
+
+    if (!config.remote_ip) {
+        VLOG_WARN("gre type requires valid 'remote_ip' argument");
+        error = EINVAL;
+        goto error;
+    }
+
+    if (!gre_descriptors.use_ioctl) {
+        error = setup_gre_netlink(name, &config, create);
+        if (error == EOPNOTSUPP) {
+            gre_descriptors.use_ioctl = true;
+        }
+    }
+    if (gre_descriptors.use_ioctl) {
+        error = setup_gre_ioctl(name, &config, create);
+    }
+
+    if (create && error == EEXIST) {
+        bool gre_device;
+
+        if (gre_descriptors.use_ioctl) {
+            gre_device = check_gre_device_ioctl(name);
+        } else {
+            gre_device = check_gre_device_netlink(name);
+        }
+
+        if (!gre_device) {
+            goto error;
+        }
+
+        VLOG_WARN("replacing existing gre device %s", name);
+        error = destroy_gre(name);
+        if (error) {
+            goto error;
+        }
+
+        if (gre_descriptors.use_ioctl) {
+            error = setup_gre_ioctl(name, &config, create);
+        } else {
+            error = setup_gre_netlink(name, &config, create);
+        }
+    }
+
+error:
+    return error;
+}
+
+/* Creates the netdev device of 'type' with 'name'. */
+static int
+netdev_linux_create_system(const char *name, const char *type UNUSED,
+                    const struct shash *args, struct netdev_dev **netdev_devp)
+{
+    struct netdev_dev_linux *netdev_dev;
     int error;
 
     if (!shash_is_empty(args)) {
-        VLOG_WARN("arguments for %s devices should be empty", type);
+        VLOG_WARN("%s: arguments for system devices should be empty", name);
     }
 
-    /* Create the name binding in the netdev library for this object. */
-    netdev_obj = xcalloc(1, sizeof *netdev_obj);
-    netdev_obj_init(&netdev_obj->netdev_obj, name, &netdev_linux_class,
-                    created);
-    netdev_obj->tap_fd = -1;
+    if (!cache_notifier_refcount) {
+        error = rtnetlink_notifier_register(&netdev_linux_cache_notifier,
+                                            netdev_linux_cache_cb, NULL);
+        if (error) {
+            return error;
+        }
+    }
+    cache_notifier_refcount++;
 
-    if (strcmp(type, "tap")) {
-        return 0;
+    netdev_dev = xzalloc(sizeof *netdev_dev);
+    netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_linux_class);
+
+    *netdev_devp = &netdev_dev->netdev_dev;
+    return 0;
+}
+
+/* For most types of netdevs we open the device for each call of
+ * netdev_open().  However, this is not the case with tap devices,
+ * since it is only possible to open the device once.  In this
+ * situation we share a single file descriptor, and consequently
+ * buffers, across all readers.  Therefore once data is read it will
+ * be unavailable to other reads for tap devices. */
+static int
+netdev_linux_create_tap(const char *name, const char *type UNUSED,
+                    const struct shash *args, struct netdev_dev **netdev_devp)
+{
+    struct netdev_dev_linux *netdev_dev;
+    struct tap_state *state;
+    static const char tap_dev[] = "/dev/net/tun";
+    struct ifreq ifr;
+    int error;
+
+    if (!shash_is_empty(args)) {
+        VLOG_WARN("%s: arguments for TAP devices should be empty", name);
     }
 
+    netdev_dev = xzalloc(sizeof *netdev_dev);
+    state = &netdev_dev->state.tap;
+
     /* Open tap device. */
-    netdev_obj->tap_fd = open(tap_dev, O_RDWR);
-    if (netdev_obj->tap_fd < 0) {
+    state->fd = open(tap_dev, O_RDWR);
+    if (state->fd < 0) {
         error = errno;
         VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error));
         goto error;
@@ -244,7 +629,7 @@ netdev_linux_create(const char *name, const char *type,
     /* Create tap device. */
     ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
     strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
-    if (ioctl(netdev_obj->tap_fd, TUNSETIFF, &ifr) == -1) {
+    if (ioctl(state->fd, TUNSETIFF, &ifr) == -1) {
         VLOG_WARN("%s: creating tap device failed: %s", name,
                   strerror(errno));
         error = errno;
@@ -252,97 +637,198 @@ netdev_linux_create(const char *name, const char *type,
     }
 
     /* Make non-blocking. */
-    error = set_nonblocking(netdev_obj->tap_fd);
+    error = set_nonblocking(state->fd);
     if (error) {
         goto error;
     }
 
+    netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_tap_class);
+    *netdev_devp = &netdev_dev->netdev_dev;
     return 0;
 
 error:
-    netdev_destroy(name);
+    free(netdev_dev);
     return error;
 }
 
-/* Destroys the netdev object 'netdev_obj_'. */
-static void
-netdev_linux_destroy(struct netdev_obj *netdev_obj_)
+static int
+if_up(const char *name)
 {
-    struct netdev_obj_linux *netdev_obj = netdev_obj_linux_cast(netdev_obj_);
+    struct ifreq ifr;
+
+    strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
+    ifr.ifr_flags = IFF_UP;
 
-    if (netdev_obj->tap_fd >= 0) {
-        close(netdev_obj->tap_fd);
+    if (ioctl(af_inet_sock, SIOCSIFFLAGS, &ifr) == -1) {
+        VLOG_DBG_RL(&rl, "%s: failed to bring device up: %s",
+                    name, strerror(errno));
+        return errno;
     }
-    free(netdev_obj);
 
-    return;
+    return 0;
 }
 
 static int
-netdev_linux_open(const char *name, int ethertype, struct netdev **netdevp)
+netdev_linux_create_gre(const char *name, const char *type UNUSED,
+                    const struct shash *args, struct netdev_dev **netdev_devp)
 {
-    struct netdev_linux *netdev;
-    enum netdev_flags flags;
+    struct netdev_dev_linux *netdev_dev;
     int error;
 
-    /* Allocate network device. */
-    netdev = xcalloc(1, sizeof *netdev);
-    netdev_init(&netdev->netdev, name, &netdev_linux_class);
-    netdev->netdev_fd = -1;
-    netdev->tap_fd = -1;
-    netdev->cache = shash_find_data(&cache_map, name);
-    if (!netdev->cache) {
-        if (shash_is_empty(&cache_map)) {
-            int error = rtnetlink_notifier_register(
-                &netdev_linux_cache_notifier, netdev_linux_cache_cb, NULL);
-            if (error) {
-                netdev_close(&netdev->netdev);
-                return error;
-            }
-        }
-        netdev->cache = xmalloc(sizeof *netdev->cache);
-        netdev->cache->shash_node = shash_add(&cache_map, name,
-                                              netdev->cache);
-        netdev->cache->valid = 0;
-        netdev->cache->ref_cnt = 0;
+    netdev_dev = xzalloc(sizeof *netdev_dev);
+
+    error = setup_gre(name, args, true);
+    if (error) {
+        goto error;
     }
-    netdev->cache->ref_cnt++;
 
-    if (!strcmp(netdev_get_type(&netdev->netdev), "tap")) {
-        static const char tap_dev[] = "/dev/net/tun";
-        struct ifreq ifr;
+    error = if_up(name);
+    if (error) {
+        goto error;
+    }
 
-        /* Open tap device. */
-        netdev->tap_fd = open(tap_dev, O_RDWR);
-        if (netdev->tap_fd < 0) {
-            error = errno;
-            VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error));
-            goto error;
-        }
+    netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_gre_class);
+    *netdev_devp = &netdev_dev->netdev_dev;
+    return 0;
 
-        /* Create tap device. */
-        ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
-        strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
-        if (ioctl(netdev->tap_fd, TUNSETIFF, &ifr) == -1) {
-            VLOG_WARN("%s: creating tap device failed: %s", name,
-                      strerror(errno));
-            error = errno;
-            goto error;
-        }
+error:
+    free(netdev_dev);
+    return error;
+}
 
-        /* Make non-blocking. */
-        error = set_nonblocking(netdev->tap_fd);
-        if (error) {
-            goto error;
+static int
+netdev_linux_reconfigure_gre(struct netdev_dev *netdev_dev_,
+                             const struct shash *args)
+{
+    const char *name = netdev_dev_get_name(netdev_dev_);
+
+    return setup_gre(name, args, false);
+}
+
+/* The arguments are marked as unused to prevent warnings on platforms where
+ * the Netlink interface isn't supported. */
+static int
+destroy_gre_netlink(const char *name UNUSED)
+{
+#ifdef GRE_IOCTL_ONLY
+    return EOPNOTSUPP;
+#else
+    int error;
+    struct ofpbuf request, *reply;
+    struct ifinfomsg ifinfomsg;
+    int ifindex;
+
+    ofpbuf_init(&request, 0);
+    
+    nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock, 0, RTM_DELLINK,
+                        NLM_F_REQUEST);
+
+    memset(&ifinfomsg, 0, sizeof ifinfomsg);
+    ifinfomsg.ifi_family = AF_UNSPEC;
+    nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg);
+
+    ifindex = do_get_ifindex(name);
+    nl_msg_put_u32(&request, IFLA_LINK, ifindex);
+
+    nl_msg_put_string(&request, IFLA_IFNAME, name);
+
+    error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply);
+    ofpbuf_uninit(&request);
+    if (error) {
+        VLOG_WARN("couldn't transact netlink socket: %s", strerror(error));
+        goto error;
+    }
+    ofpbuf_delete(reply);
+
+error:
+    return 0;
+#endif
+}
+
+static int
+destroy_gre_ioctl(const char *name)
+{
+    struct ip_tunnel_parm p;
+    struct ifreq ifr;
+
+    memset(&p, 0, sizeof p);
+    strncpy(p.name, name, IFNAMSIZ);
+
+    strncpy(ifr.ifr_name, name, IFNAMSIZ);
+    ifr.ifr_ifru.ifru_data = (void *)&p;
+
+    if (ioctl(gre_descriptors.ioctl_fd, SIOCDELGRETAP, &ifr) < 0) {
+        VLOG_WARN("couldn't do gre ioctl: %s\n", strerror(errno));
+        return errno;
+    }
+
+    return 0;
+}
+
+static void
+destroy_tap(struct netdev_dev_linux *netdev_dev)
+{
+    struct tap_state *state = &netdev_dev->state.tap;
+
+    if (state->fd >= 0) {
+        close(state->fd);
+    }
+}
+
+static int
+destroy_gre(const char *name)
+{
+    if (gre_descriptors.use_ioctl) {
+        return destroy_gre_ioctl(name);
+    } else {
+        return destroy_gre_netlink(name);
+    }
+}
+
+/* Destroys the netdev device 'netdev_dev_'. */
+static void
+netdev_linux_destroy(struct netdev_dev *netdev_dev_)
+{
+    struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_);
+    const char *type = netdev_dev_get_type(netdev_dev_);
+
+    if (!strcmp(type, "system")) {
+        cache_notifier_refcount--;
+
+        if (!cache_notifier_refcount) {
+            rtnetlink_notifier_unregister(&netdev_linux_cache_notifier);
         }
+    } else if (!strcmp(type, "tap")) {
+        destroy_tap(netdev_dev);
+    } else if (!strcmp(type, "gre")) {
+        destroy_gre(netdev_dev_get_name(&netdev_dev->netdev_dev));
     }
 
+    free(netdev_dev_);
+}
+
+static int
+netdev_linux_open(struct netdev_dev *netdev_dev_, int ethertype,
+                  struct netdev **netdevp)
+{
+    struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_);
+    struct netdev_linux *netdev;
+    enum netdev_flags flags;
+    int error;
+
+    /* Allocate network device. */
+    netdev = xzalloc(sizeof *netdev);
+    netdev->fd = -1;
+    netdev_init(&netdev->netdev, netdev_dev_);
+
     error = netdev_get_flags(&netdev->netdev, &flags);
     if (error == ENODEV) {
         goto error;
     }
 
-    if (netdev->tap_fd >= 0 || ethertype != NETDEV_ETH_TYPE_NONE) {
+    if (!strcmp(netdev_dev_get_type(netdev_dev_), "tap")) {
+        netdev->fd = netdev_dev->state.tap.fd;
+    } else if (ethertype != NETDEV_ETH_TYPE_NONE) {
         struct sockaddr_ll sll;
         int protocol;
         int ifindex;
@@ -351,17 +837,14 @@ netdev_linux_open(const char *name, int ethertype, struct netdev **netdevp)
         protocol = (ethertype == NETDEV_ETH_TYPE_ANY ? ETH_P_ALL
                     : ethertype == NETDEV_ETH_TYPE_802_2 ? ETH_P_802_2
                     : ethertype);
-        netdev->netdev_fd = socket(PF_PACKET, SOCK_RAW, htons(protocol));
-        if (netdev->netdev_fd < 0) {
+        netdev->fd = socket(PF_PACKET, SOCK_RAW, htons(protocol));
+        if (netdev->fd < 0) {
             error = errno;
             goto error;
         }
-        if (netdev->tap_fd < 0) {
-            netdev->tap_fd = netdev->netdev_fd;
-        }
 
         /* Set non-blocking mode. */
-        error = set_nonblocking(netdev->netdev_fd);
+        error = set_nonblocking(netdev->fd);
         if (error) {
             goto error;
         }
@@ -376,10 +859,11 @@ netdev_linux_open(const char *name, int ethertype, struct netdev **netdevp)
         memset(&sll, 0, sizeof sll);
         sll.sll_family = AF_PACKET;
         sll.sll_ifindex = ifindex;
-        if (bind(netdev->netdev_fd,
+        if (bind(netdev->fd,
                  (struct sockaddr *) &sll, sizeof sll) < 0) {
             error = errno;
-            VLOG_ERR("bind to %s failed: %s", name, strerror(error));
+            VLOG_ERR("bind to %s failed: %s", netdev_dev_get_name(netdev_dev_),
+                     strerror(error));
             goto error;
         }
 
@@ -387,7 +871,7 @@ netdev_linux_open(const char *name, int ethertype, struct netdev **netdevp)
          * packets of the requested type on all system interfaces.  We do not
          * want to receive that data, but there is no way to avoid it.  So we
          * must now drain out the receive queue. */
-        error = drain_rcvbuf(netdev->netdev_fd);
+        error = drain_rcvbuf(netdev->fd);
         if (error) {
             goto error;
         }
@@ -397,7 +881,7 @@ netdev_linux_open(const char *name, int ethertype, struct netdev **netdevp)
     return 0;
 
 error:
-    netdev_close(&netdev->netdev);
+    netdev_uninit(&netdev->netdev, true);
     return error;
 }
 
@@ -407,19 +891,8 @@ netdev_linux_close(struct netdev *netdev_)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
 
-    if (netdev->cache && !--netdev->cache->ref_cnt) {
-        shash_delete(&cache_map, netdev->cache->shash_node);
-        free(netdev->cache);
-
-        if (shash_is_empty(&cache_map)) {
-            rtnetlink_notifier_unregister(&netdev_linux_cache_notifier);
-        }
-    }
-    if (netdev->netdev_fd >= 0) {
-        close(netdev->netdev_fd);
-    }
-    if (netdev->tap_fd >= 0 && netdev->netdev_fd != netdev->tap_fd) {
-        close(netdev->tap_fd);
+    if (netdev->fd > 0 && strcmp(netdev_get_type(netdev_), "tap")) {
+        close(netdev->fd);
     }
     free(netdev);
 }
@@ -451,13 +924,13 @@ netdev_linux_recv(struct netdev *netdev_, void *data, size_t size)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
 
-    if (netdev->tap_fd < 0) {
+    if (netdev->fd < 0) {
         /* Device was opened with NETDEV_ETH_TYPE_NONE. */
         return -EAGAIN;
     }
 
     for (;;) {
-        ssize_t retval = read(netdev->tap_fd, data, size);
+        ssize_t retval = read(netdev->fd, data, size);
         if (retval >= 0) {
             return retval;
         } else if (errno != EINTR) {
@@ -476,8 +949,8 @@ static void
 netdev_linux_recv_wait(struct netdev *netdev_)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
-    if (netdev->tap_fd >= 0) {
-        poll_fd_wait(netdev->tap_fd, POLLIN);
+    if (netdev->fd >= 0) {
+        poll_fd_wait(netdev->fd, POLLIN);
     }
 }
 
@@ -486,19 +959,19 @@ static int
 netdev_linux_drain(struct netdev *netdev_)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
-    if (netdev->tap_fd < 0 && netdev->netdev_fd < 0) {
+    if (netdev->fd < 0) {
         return 0;
-    } else if (netdev->tap_fd != netdev->netdev_fd) {
+    } else if (!strcmp(netdev_get_type(netdev_), "tap")) {
         struct ifreq ifr;
-        int error = netdev_linux_do_ioctl(netdev_, &ifr,
+        int error = netdev_linux_do_ioctl(netdev_get_name(netdev_), &ifr,
                                           SIOCGIFTXQLEN, "SIOCGIFTXQLEN");
         if (error) {
             return error;
         }
-        drain_fd(netdev->tap_fd, ifr.ifr_qlen);
+        drain_fd(netdev->fd, ifr.ifr_qlen);
         return 0;
     } else {
-        return drain_rcvbuf(netdev->netdev_fd);
+        return drain_rcvbuf(netdev->fd);
     }
 }
 
@@ -518,12 +991,12 @@ netdev_linux_send(struct netdev *netdev_, const void *data, size_t size)
 
     /* XXX should support sending even if 'ethertype' was NETDEV_ETH_TYPE_NONE.
      */
-    if (netdev->tap_fd < 0) {
+    if (netdev->fd < 0) {
         return EPIPE;
     }
 
     for (;;) {
-        ssize_t retval = write(netdev->tap_fd, data, size);
+        ssize_t retval = write(netdev->fd, data, size);
         if (retval < 0) {
             /* The Linux AF_PACKET implementation never blocks waiting for room
              * for packets, instead returning ENOBUFS.  Translate this into
@@ -558,10 +1031,10 @@ static void
 netdev_linux_send_wait(struct netdev *netdev_)
 {
     struct netdev_linux *netdev = netdev_linux_cast(netdev_);
-    if (netdev->tap_fd < 0 && netdev->netdev_fd < 0) {
+    if (netdev->fd < 0) {
         /* Nothing to do. */
-    } else if (netdev->tap_fd == netdev->netdev_fd) {
-        poll_fd_wait(netdev->tap_fd, POLLOUT);
+    } else if (strcmp(netdev_get_type(netdev_), "tap")) {
+        poll_fd_wait(netdev->fd, POLLOUT);
     } else {
         /* TAP device always accepts packets.*/
         poll_immediate_wake();
@@ -574,15 +1047,16 @@ static int
 netdev_linux_set_etheraddr(struct netdev *netdev_,
                            const uint8_t mac[ETH_ADDR_LEN])
 {
-    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
     int error;
 
-    if (!(netdev->cache->valid & VALID_ETHERADDR)
-        || !eth_addr_equals(netdev->cache->etheraddr, mac)) {
+    if (!(netdev_dev->cache_valid & VALID_ETHERADDR)
+        || !eth_addr_equals(netdev_dev->etheraddr, mac)) {
         error = set_etheraddr(netdev_get_name(netdev_), ARPHRD_ETHER, mac);
         if (!error) {
-            netdev->cache->valid |= VALID_ETHERADDR;
-            memcpy(netdev->cache->etheraddr, mac, ETH_ADDR_LEN);
+            netdev_dev->cache_valid |= VALID_ETHERADDR;
+            memcpy(netdev_dev->etheraddr, mac, ETH_ADDR_LEN);
         }
     } else {
         error = 0;
@@ -596,16 +1070,17 @@ static int
 netdev_linux_get_etheraddr(const struct netdev *netdev_,
                            uint8_t mac[ETH_ADDR_LEN])
 {
-    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
-    if (!(netdev->cache->valid & VALID_ETHERADDR)) {
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    if (!(netdev_dev->cache_valid & VALID_ETHERADDR)) {
         int error = get_etheraddr(netdev_get_name(netdev_),
-                                  netdev->cache->etheraddr);
+                                  netdev_dev->etheraddr);
         if (error) {
             return error;
         }
-        netdev->cache->valid |= VALID_ETHERADDR;
+        netdev_dev->cache_valid |= VALID_ETHERADDR;
     }
-    memcpy(mac, netdev->cache->etheraddr, ETH_ADDR_LEN);
+    memcpy(mac, netdev_dev->etheraddr, ETH_ADDR_LEN);
     return 0;
 }
 
@@ -615,19 +1090,21 @@ netdev_linux_get_etheraddr(const struct netdev *netdev_,
 static int
 netdev_linux_get_mtu(const struct netdev *netdev_, int *mtup)
 {
-    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
-    if (!(netdev->cache->valid & VALID_MTU)) {
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    if (!(netdev_dev->cache_valid & VALID_MTU)) {
         struct ifreq ifr;
         int error;
 
-        error = netdev_linux_do_ioctl(netdev_, &ifr, SIOCGIFMTU, "SIOCGIFMTU");
+        error = netdev_linux_do_ioctl(netdev_get_name(netdev_), &ifr,
+                                      SIOCGIFMTU, "SIOCGIFMTU");
         if (error) {
             return error;
         }
-        netdev->cache->mtu = ifr.ifr_mtu;
-        netdev->cache->valid |= VALID_MTU;
+        netdev_dev->mtu = ifr.ifr_mtu;
+        netdev_dev->cache_valid |= VALID_MTU;
     }
-    *mtup = netdev->cache->mtu;
+    *mtup = netdev_dev->mtu;
     return 0;
 }
 
@@ -645,16 +1122,18 @@ netdev_linux_get_ifindex(const struct netdev *netdev)
 static int
 netdev_linux_get_carrier(const struct netdev *netdev_, bool *carrier)
 {
-    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
     int error = 0;
     char *fn = NULL;
     int fd = -1;
 
-    if (!(netdev->cache->valid & VALID_CARRIER)) {
+    if (!(netdev_dev->cache_valid & VALID_CARRIER)) {
         char line[8];
         int retval;
 
-        fn = xasprintf("/sys/class/net/%s/carrier", netdev_get_name(netdev_));
+        fn = xasprintf("/sys/class/net/%s/carrier",
+                       netdev_get_name(netdev_));
         fd = open(fn, O_RDONLY);
         if (fd < 0) {
             error = errno;
@@ -684,10 +1163,10 @@ netdev_linux_get_carrier(const struct netdev *netdev_, bool *carrier)
                          fn, line[0]);
             goto exit;
         }
-        netdev->cache->carrier = line[0] != '0';
-        netdev->cache->valid |= VALID_CARRIER;
+        netdev_dev->carrier = line[0] != '0';
+        netdev_dev->cache_valid |= VALID_CARRIER;
     }
-    *carrier = netdev->cache->carrier;
+    *carrier = netdev_dev->carrier;
     error = 0;
 
 exit:
@@ -731,9 +1210,11 @@ check_for_working_netlink_stats(void)
  * XXX All of the members of struct netdev_stats are 64 bits wide, but on
  * 32-bit architectures the Linux network stats are only 32 bits. */
 static int
-netdev_linux_get_stats(const struct netdev *netdev_, struct netdev_stats *stats)
+netdev_linux_get_stats(const struct netdev *netdev_,
+                       struct netdev_stats *stats)
 {
-    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
     static int use_netlink_stats = -1;
     int error;
     struct netdev_stats raw_stats;
@@ -741,28 +1222,29 @@ netdev_linux_get_stats(const struct netdev *netdev_, struct netdev_stats *stats)
 
     COVERAGE_INC(netdev_get_stats);
 
-    if (!(netdev->cache->valid & VALID_IS_INTERNAL)) {
-        netdev->cache->is_internal = (netdev->tap_fd != -1);
+    if (!(netdev_dev->cache_valid & VALID_IS_INTERNAL)) {
+        netdev_dev->is_internal = !strcmp(netdev_get_type(netdev_),
+                                                "tap");
 
-        if (!netdev->cache->is_internal) {
+        if (!netdev_dev->is_internal) {
             struct ethtool_drvinfo drvinfo;
 
             memset(&drvinfo, 0, sizeof drvinfo);
-            error = netdev_linux_do_ethtool(&netdev->netdev,
+            error = netdev_linux_do_ethtool(netdev_get_name(netdev_),
                                             (struct ethtool_cmd *)&drvinfo,
                                             ETHTOOL_GDRVINFO,
                                             "ETHTOOL_GDRVINFO");
 
             if (!error) {
-                netdev->cache->is_internal = !strcmp(drvinfo.driver,
-                                                     "openvswitch");
+                netdev_dev->is_internal = !strcmp(drvinfo.driver,
+                                                        "openvswitch");
             }
         }
 
-        netdev->cache->valid |= VALID_IS_INTERNAL;
+        netdev_dev->cache_valid |= VALID_IS_INTERNAL;
     }
 
-    if (netdev->cache->is_internal) {
+    if (netdev_dev->is_internal) {
         collect_stats = &raw_stats;
     }
 
@@ -772,19 +1254,19 @@ netdev_linux_get_stats(const struct netdev *netdev_, struct netdev_stats *stats)
     if (use_netlink_stats) {
         int ifindex;
 
-        error = get_ifindex(&netdev->netdev, &ifindex);
+        error = get_ifindex(netdev_, &ifindex);
         if (!error) {
             error = get_stats_via_netlink(ifindex, collect_stats);
         }
     } else {
-        error = get_stats_via_proc(netdev->netdev.name, collect_stats);
+        error = get_stats_via_proc(netdev_get_name(netdev_), collect_stats);
     }
 
     /* If this port is an internal port then the transmit and receive stats
      * will appear to be swapped relative to the other ports since we are the
      * one sending the data, not a remote computer.  For consistency, we swap
      * them back here. */
-    if (netdev->cache->is_internal) {
+    if (netdev_dev->is_internal) {
         stats->rx_packets = raw_stats.tx_packets;
         stats->tx_packets = raw_stats.rx_packets;
         stats->rx_bytes = raw_stats.tx_bytes;
@@ -824,7 +1306,7 @@ netdev_linux_get_features(struct netdev *netdev,
     int error;
 
     memset(&ecmd, 0, sizeof ecmd);
-    error = netdev_linux_do_ethtool(netdev, &ecmd,
+    error = netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
                                     ETHTOOL_GSET, "ETHTOOL_GSET");
     if (error) {
         return error;
@@ -945,7 +1427,7 @@ netdev_linux_set_advertisements(struct netdev *netdev, uint32_t advertise)
     int error;
 
     memset(&ecmd, 0, sizeof ecmd);
-    error = netdev_linux_do_ethtool(netdev, &ecmd,
+    error = netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
                                     ETHTOOL_GSET, "ETHTOOL_GSET");
     if (error) {
         return error;
@@ -988,7 +1470,7 @@ netdev_linux_set_advertisements(struct netdev *netdev, uint32_t advertise)
     if (advertise & OFPPF_PAUSE_ASYM) {
         ecmd.advertising |= ADVERTISED_Asym_Pause;
     }
-    return netdev_linux_do_ethtool(netdev, &ecmd,
+    return netdev_linux_do_ethtool(netdev_get_name(netdev), &ecmd,
                                    ETHTOOL_SSET, "ETHTOOL_SSET");
 }
 
@@ -1066,8 +1548,8 @@ netdev_linux_set_policing(struct netdev *netdev,
     COVERAGE_INC(netdev_set_policing);
     if (kbits_rate) {
         if (!kbits_burst) {
-            /* Default to 10 kilobits if not specified. */
-            kbits_burst = 10;
+            /* Default to 1000 kilobits if not specified. */
+            kbits_burst = 1000;
         }
 
         /* xxx This should be more careful about only adding if it
@@ -1104,26 +1586,28 @@ static int
 netdev_linux_get_in4(const struct netdev *netdev_,
                      struct in_addr *address, struct in_addr *netmask)
 {
-    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
-    if (!(netdev->cache->valid & VALID_IN4)) {
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+
+    if (!(netdev_dev->cache_valid & VALID_IN4)) {
         int error;
 
-        error = netdev_linux_get_ipv4(netdev_, &netdev->cache->address,
+        error = netdev_linux_get_ipv4(netdev_, &netdev_dev->address,
                                       SIOCGIFADDR, "SIOCGIFADDR");
         if (error) {
             return error;
         }
 
-        error = netdev_linux_get_ipv4(netdev_, &netdev->cache->netmask,
+        error = netdev_linux_get_ipv4(netdev_, &netdev_dev->netmask,
                                       SIOCGIFNETMASK, "SIOCGIFNETMASK");
         if (error) {
             return error;
         }
 
-        netdev->cache->valid |= VALID_IN4;
+        netdev_dev->cache_valid |= VALID_IN4;
     }
-    *address = netdev->cache->address;
-    *netmask = netdev->cache->netmask;
+    *address = netdev_dev->address;
+    *netmask = netdev_dev->netmask;
     return address->s_addr == INADDR_ANY ? EADDRNOTAVAIL : 0;
 }
 
@@ -1131,14 +1615,15 @@ static int
 netdev_linux_set_in4(struct netdev *netdev_, struct in_addr address,
                      struct in_addr netmask)
 {
-    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
     int error;
 
     error = do_set_addr(netdev_, SIOCSIFADDR, "SIOCSIFADDR", address);
     if (!error) {
-        netdev->cache->valid |= VALID_IN4;
-        netdev->cache->address = address;
-        netdev->cache->netmask = netmask;
+        netdev_dev->cache_valid |= VALID_IN4;
+        netdev_dev->address = address;
+        netdev_dev->netmask = netmask;
         if (address.s_addr != INADDR_ANY) {
             error = do_set_addr(netdev_, SIOCSIFNETMASK,
                                 "SIOCSIFNETMASK", netmask);
@@ -1168,12 +1653,13 @@ parse_if_inet6_line(const char *line,
 static int
 netdev_linux_get_in6(const struct netdev *netdev_, struct in6_addr *in6)
 {
-    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
-    if (!(netdev->cache->valid & VALID_IN6)) {
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
+    if (!(netdev_dev->cache_valid & VALID_IN6)) {
         FILE *file;
         char line[128];
 
-        netdev->cache->in6 = in6addr_any;
+        netdev_dev->in6 = in6addr_any;
 
         file = fopen("/proc/net/if_inet6", "r");
         if (file != NULL) {
@@ -1184,15 +1670,15 @@ netdev_linux_get_in6(const struct netdev *netdev_, struct in6_addr *in6)
                 if (parse_if_inet6_line(line, &in6, ifname)
                     && !strcmp(name, ifname))
                 {
-                    netdev->cache->in6 = in6;
+                    netdev_dev->in6 = in6;
                     break;
                 }
             }
             fclose(file);
         }
-        netdev->cache->valid |= VALID_IN6;
+        netdev_dev->cache_valid |= VALID_IN6;
     }
-    *in6 = netdev->cache->in6;
+    *in6 = netdev_dev->in6;
     return 0;
 }
 
@@ -1214,9 +1700,11 @@ do_set_addr(struct netdev *netdev,
             int ioctl_nr, const char *ioctl_name, struct in_addr addr)
 {
     struct ifreq ifr;
-    strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
+    strncpy(ifr.ifr_name, netdev_get_name(netdev), sizeof ifr.ifr_name);
     make_in4_sockaddr(&ifr.ifr_addr, addr);
-    return netdev_linux_do_ioctl(netdev, &ifr, ioctl_nr, ioctl_name);
+
+    return netdev_linux_do_ioctl(netdev_get_name(netdev), &ifr, ioctl_nr,
+                                 ioctl_name);
 }
 
 /* Adds 'router' as a default IP gateway. */
@@ -1310,24 +1798,24 @@ netdev_linux_arp_lookup(const struct netdev *netdev,
                         uint32_t ip, uint8_t mac[ETH_ADDR_LEN])
 {
     struct arpreq r;
-    struct sockaddr_in *pa;
+    struct sockaddr_in sin;
     int retval;
 
     memset(&r, 0, sizeof r);
-    pa = (struct sockaddr_in *) &r.arp_pa;
-    pa->sin_family = AF_INET;
-    pa->sin_addr.s_addr = ip;
-    pa->sin_port = 0;
+    sin.sin_family = AF_INET;
+    sin.sin_addr.s_addr = ip;
+    sin.sin_port = 0;
+    memcpy(&r.arp_pa, &sin, sizeof sin);
     r.arp_ha.sa_family = ARPHRD_ETHER;
     r.arp_flags = 0;
-    strncpy(r.arp_dev, netdev->name, sizeof r.arp_dev);
+    strncpy(r.arp_dev, netdev_get_name(netdev), sizeof r.arp_dev);
     COVERAGE_INC(netdev_arp_lookup);
     retval = ioctl(af_inet_sock, SIOCGARP, &r) < 0 ? errno : 0;
     if (!retval) {
         memcpy(mac, r.arp_ha.sa_data, ETH_ADDR_LEN);
     } else if (retval != ENXIO) {
         VLOG_WARN_RL(&rl, "%s: could not look up ARP entry for "IP_FMT": %s",
-                     netdev->name, IP_ARGS(&ip), strerror(retval));
+                     netdev_get_name(netdev), IP_ARGS(&ip), strerror(retval));
     }
     return retval;
 }
@@ -1460,13 +1948,13 @@ netdev_linux_poll_remove(struct netdev_notifier *notifier_)
 }
 
 const struct netdev_class netdev_linux_class = {
-    "system",                   /* type */
+    "system",
 
     netdev_linux_init,
     netdev_linux_run,
     netdev_linux_wait,
 
-    netdev_linux_create,
+    netdev_linux_create_system,
     netdev_linux_destroy,
     NULL,                       /* reconfigure */
 
@@ -1508,20 +1996,68 @@ const struct netdev_class netdev_linux_class = {
 };
 
 const struct netdev_class netdev_tap_class = {
-    "tap",                      /* type */
+    "tap",
 
     netdev_linux_init,
-    NULL,                       /* run */
-    NULL,                       /* wait */
+    netdev_linux_run,
+    netdev_linux_wait,
 
-    netdev_linux_create,
+    netdev_linux_create_tap,
     netdev_linux_destroy,
     NULL,                       /* reconfigure */
 
     netdev_linux_open,
     netdev_linux_close,
 
-    netdev_linux_enumerate,
+    NULL,                       /* enumerate */
+
+    netdev_linux_recv,
+    netdev_linux_recv_wait,
+    netdev_linux_drain,
+
+    netdev_linux_send,
+    netdev_linux_send_wait,
+
+    netdev_linux_set_etheraddr,
+    netdev_linux_get_etheraddr,
+    netdev_linux_get_mtu,
+    netdev_linux_get_ifindex,
+    netdev_linux_get_carrier,
+    netdev_linux_get_stats,
+
+    netdev_linux_get_features,
+    netdev_linux_set_advertisements,
+    netdev_linux_get_vlan_vid,
+    netdev_linux_set_policing,
+
+    netdev_linux_get_in4,
+    netdev_linux_set_in4,
+    netdev_linux_get_in6,
+    netdev_linux_add_router,
+    netdev_linux_get_next_hop,
+    netdev_linux_arp_lookup,
+
+    netdev_linux_update_flags,
+
+    netdev_linux_poll_add,
+    netdev_linux_poll_remove,
+};
+
+const struct netdev_class netdev_gre_class = {
+    "gre",
+
+    netdev_linux_init,
+    netdev_linux_run,
+    netdev_linux_wait,
+
+    netdev_linux_create_gre,
+    netdev_linux_destroy,
+    netdev_linux_reconfigure_gre,
+
+    netdev_linux_open,
+    netdev_linux_close,
+
+    NULL,                       /* enumerate */
 
     netdev_linux_recv,
     netdev_linux_recv_wait,
@@ -1702,7 +2238,8 @@ get_flags(const struct netdev *netdev, int *flags)
     struct ifreq ifr;
     int error;
 
-    error = netdev_linux_do_ioctl(netdev, &ifr, SIOCGIFFLAGS, "SIOCGIFFLAGS");
+    error = netdev_linux_do_ioctl(netdev_get_name(netdev), &ifr, SIOCGIFFLAGS,
+                                  "SIOCGIFFLAGS");
     *flags = ifr.ifr_flags;
     return error;
 }
@@ -1713,7 +2250,8 @@ set_flags(struct netdev *netdev, int flags)
     struct ifreq ifr;
 
     ifr.ifr_flags = flags;
-    return netdev_linux_do_ioctl(netdev, &ifr, SIOCSIFFLAGS, "SIOCSIFFLAGS");
+    return netdev_linux_do_ioctl(netdev_get_name(netdev), &ifr, SIOCSIFFLAGS,
+                                 "SIOCSIFFLAGS");
 }
 
 static int
@@ -1734,17 +2272,18 @@ do_get_ifindex(const char *netdev_name)
 static int
 get_ifindex(const struct netdev *netdev_, int *ifindexp)
 {
-    struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+    struct netdev_dev_linux *netdev_dev =
+                                netdev_dev_linux_cast(netdev_get_dev(netdev_));
     *ifindexp = 0;
-    if (!(netdev->cache->valid & VALID_IFINDEX)) {
+    if (!(netdev_dev->cache_valid & VALID_IFINDEX)) {
         int ifindex = do_get_ifindex(netdev_get_name(netdev_));
         if (ifindex < 0) {
             return -ifindex;
         }
-        netdev->cache->valid |= VALID_IFINDEX;
-        netdev->cache->ifindex = ifindex;
+        netdev_dev->cache_valid |= VALID_IFINDEX;
+        netdev_dev->ifindex = ifindex;
     }
-    *ifindexp = netdev->cache->ifindex;
+    *ifindexp = netdev_dev->ifindex;
     return 0;
 }
 
@@ -1791,13 +2330,13 @@ set_etheraddr(const char *netdev_name, int hwaddr_family,
 }
 
 static int
-netdev_linux_do_ethtool(struct netdev *netdev, struct ethtool_cmd *ecmd,
+netdev_linux_do_ethtool(const char *name, struct ethtool_cmd *ecmd,
                         int cmd, const char *cmd_name)
 {
     struct ifreq ifr;
 
     memset(&ifr, 0, sizeof ifr);
-    strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name);
+    strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name);
     ifr.ifr_data = (caddr_t) ecmd;
 
     ecmd->cmd = cmd;
@@ -1807,8 +2346,7 @@ netdev_linux_do_ethtool(struct netdev *netdev, struct ethtool_cmd *ecmd,
     } else {
         if (errno != EOPNOTSUPP) {
             VLOG_WARN_RL(&rl, "ethtool command %s on network device %s "
-                         "failed: %s", cmd_name, netdev->name,
-                         strerror(errno));
+                         "failed: %s", cmd_name, name, strerror(errno));
         } else {
             /* The device doesn't support this operation.  That's pretty
              * common, so there's no point in logging anything. */
@@ -1818,13 +2356,13 @@ netdev_linux_do_ethtool(struct netdev *netdev, struct ethtool_cmd *ecmd,
 }
 
 static int
-netdev_linux_do_ioctl(const struct netdev *netdev, struct ifreq *ifr,
-                      int cmd, const char *cmd_name)
+netdev_linux_do_ioctl(const char *name, struct ifreq *ifr, int cmd,
+                      const char *cmd_name)
 {
-    strncpy(ifr->ifr_name, netdev_get_name(netdev), sizeof ifr->ifr_name);
+    strncpy(ifr->ifr_name, name, sizeof ifr->ifr_name);
     if (ioctl(af_inet_sock, cmd, ifr) == -1) {
-        VLOG_DBG_RL(&rl, "%s: ioctl(%s) failed: %s",
-                    netdev_get_name(netdev), cmd_name, strerror(errno));
+        VLOG_DBG_RL(&rl, "%s: ioctl(%s) failed: %s", name, cmd_name,
+                     strerror(errno));
         return errno;
     }
     return 0;
@@ -1838,7 +2376,7 @@ netdev_linux_get_ipv4(const struct netdev *netdev, struct in_addr *ip,
     int error;
 
     ifr.ifr_addr.sa_family = AF_INET;
-    error = netdev_linux_do_ioctl(netdev, &ifr, cmd, cmd_name);
+    error = netdev_linux_do_ioctl(netdev_get_name(netdev), &ifr, cmd, cmd_name);
     if (!error) {
         const struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr;
         *ip = sin->sin_addr;
index 07141db..1eb1b1e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
 /* Generic interface to network devices. */
 
 #include <assert.h>
+
 #include "netdev.h"
 #include "list.h"
 #include "shash.h"
 extern "C" {
 #endif
 
-/* A network device object that was created through the netdev_create()
- * call.
+struct arg {
+    char *key;
+    char *value;
+};
+
+/* A network device (e.g. an Ethernet device).
  *
  * This structure should be treated as opaque by network device
  * implementations. */
-struct netdev_obj {
-    const struct netdev_class *netdev_class;
-    int ref_cnt;
-    bool created;                    /* Was netdev_create() called? */
+struct netdev_dev {
+    char *name;                         /* Name of network device. */
+    const struct netdev_class *netdev_class; /* Functions to control 
+                                                this device. */
+    int ref_cnt;                        /* Times this devices was opened. */
+    struct shash_node *node;            /* Pointer to element in global map. */
+    struct arg *args;                   /* Argument list from last config. */
+    int n_args;                         /* Number of arguments in 'args'. */
 };
 
-void netdev_obj_init(struct netdev_obj *, const char *name,
-                     const struct netdev_class *, bool created);
-static inline void netdev_obj_assert_class(const struct netdev_obj *netdev_obj,
+void netdev_dev_init(struct netdev_dev *, const char *name,
+                     const struct netdev_class *);
+void netdev_dev_uninit(struct netdev_dev *, bool destroy);
+const char *netdev_dev_get_type(const struct netdev_dev *);
+const char *netdev_dev_get_name(const struct netdev_dev *);
+struct netdev_dev *netdev_dev_from_name(const char *name);
+void netdev_dev_get_devices(const struct netdev_class *,
+                            struct shash *device_list);
+
+static inline void netdev_dev_assert_class(const struct netdev_dev *netdev_dev,
                                            const struct netdev_class *class_)
 {
-    assert(netdev_obj->netdev_class == class_);
+    assert(netdev_dev->netdev_class == class_);
 }
 
-/* A network device (e.g. an Ethernet device).
+/* A instance of an open network device.
  *
  * This structure should be treated as opaque by network device
  * implementations. */
 struct netdev {
-    const struct netdev_class *netdev_class;
-    char *name;                      /* e.g. "eth0" */
+    struct netdev_dev *netdev_dev;   /* Parent netdev_dev. */
+    struct list node;                /* Element in global list. */
 
     enum netdev_flags save_flags;    /* Initial device flags. */
     enum netdev_flags changed_flags; /* Flags that we changed. */
-    struct list node;                /* Element in global list. */
 };
 
-void netdev_init(struct netdev *, const char *name,
-                 const struct netdev_class *);
+void netdev_init(struct netdev *, struct netdev_dev *);
+void netdev_uninit(struct netdev *, bool close);
+struct netdev_dev *netdev_get_dev(const struct netdev *);
+
 static inline void netdev_assert_class(const struct netdev *netdev,
                                        const struct netdev_class *netdev_class)
 {
-    assert(netdev->netdev_class == netdev_class);
+    netdev_dev_assert_class(netdev_get_dev(netdev), netdev_class);
 }
-const char *netdev_get_type(const struct netdev *netdev);
 
 /* A network device notifier.
  *
@@ -91,17 +107,17 @@ struct netdev_class {
     /* Type of netdevs in this class, e.g. "system", "tap", "gre", etc.
      *
      * One of the providers should supply a "system" type, since this is
-     * the type assumed when a device name was not bound through the 
-     * netdev_create() call.  The "system" type corresponds to an 
-     * existing network device on the system. */
+     * the type assumed if no type is specified when opening a netdev.
+     * The "system" type corresponds to an existing network device on
+     * the system. */
     const char *type;
 
-    /* Called only once, at program startup.  Returning an error from this
-     * function will prevent any network device in this class from being
-     * opened.
+    /* Called when the netdev provider is registered, typically at program
+     * startup.  Returning an error from this function will prevent any network
+     * device in this class from being opened.
      *
      * This function may be set to null if a network device class needs no
-     * initialization at program startup. */
+     * initialization at registration time. */
     int (*init)(void);
 
     /* Performs periodic work needed by netdevs of this class.  May be null if
@@ -112,45 +128,36 @@ struct netdev_class {
      * to be called.  May be null if nothing is needed here. */
     void (*wait)(void);
 
-    /* Attempts to create a network device object of 'type' with 'name'.  
+    /* Attempts to create a network device of 'type' with 'name'.
      * 'type' corresponds to the 'type' field used in the netdev_class
-     * structure.  
-     *
-     * The 'created' flag indicates that the user called netdev_create()
-     * and thus will eventually call netdev_destroy().  If the flag is 
-     * false, then the object was dynamically created based on a call to 
-     * netdev_open() without first calling netdev_create() and will be
-     * automatically destroyed when no more netdevs have 'name' open.  A 
-     * provider implementation should pass this flag to netdev_obj_init(). */
-    int (*create)(const char *name, const char *type, 
-                  const struct shash *args, bool created);
-
-    /* Destroys 'netdev_obj'.  
+     * structure. On success sets 'netdev_devp' to the newly created device. */
+    int (*create)(const char *name, const char *type, const struct shash *args,
+                  struct netdev_dev **netdev_devp);
+
+    /* Destroys 'netdev_dev'.
      *
-     * Netdev objects maintain a reference count that is incremented on 
-     * netdev_open() and decremented on netdev_close().  If 'netdev_obj' 
-     * has a non-zero reference count, then this function will not be 
+     * Netdev devices maintain a reference count that is incremented on
+     * netdev_open() and decremented on netdev_close().  If 'netdev_dev'
+     * has a non-zero reference count, then this function will not be
      * called. */
-    void (*destroy)(struct netdev_obj *netdev_obj);
+    void (*destroy)(struct netdev_dev *netdev_dev);
 
-    /* Reconfigures the device object 'netdev_obj' with 'args'. 
+    /* Reconfigures the device 'netdev_dev' with 'args'.
      *
      * If this netdev class does not support reconfiguring a netdev
-     * object, this may be a null pointer.
+     * device, this may be a null pointer.
      */
-    int (*reconfigure)(struct netdev_obj *netdev_obj, 
-                       const struct shash *args);
+    int (*reconfigure)(struct netdev_dev *netdev_dev, const struct shash *args);
 
-    /* Attempts to open a network device.  On success, sets '*netdevp' to the
-     * new network device.  'name' is the network device name provided by
-     * the user.  This name is useful for error messages but must not be
-     * modified.
+    /* Attempts to open a network device.  On success, sets 'netdevp'
+     * to the new network device.
      *
      * 'ethertype' may be a 16-bit Ethernet protocol value in host byte order
      * to capture frames of that type received on the device.  It may also be
      * one of the 'enum netdev_pseudo_ethertype' values to receive frames in
      * one of those categories. */
-    int (*open)(const char *name, int ethertype, struct netdev **netdevp);
+    int (*open)(struct netdev_dev *netdev_dev, int ethertype,
+                struct netdev **netdevp);
 
     /* Closes 'netdev'. */
     void (*close)(struct netdev *netdev);
@@ -212,7 +219,7 @@ struct netdev_class {
      * The MTU is the maximum size of transmitted (and received) packets, in
      * bytes, not including the hardware header; thus, this is typically 1500
      * bytes for Ethernet devices.*/
-    int (*get_mtu)(const struct netdev *, int *mtup);
+    int (*get_mtu)(const struct netdev *netdev, int *mtup);
 
     /* Returns the ifindex of 'netdev', if successful, as a positive number.
      * On failure, returns a negative errno value.
@@ -222,7 +229,7 @@ struct netdev_class {
      * ifindex value should be unique within a host and remain stable at least
      * until reboot.  SNMP says an ifindex "ranges between 1 and the value of
      * ifNumber" but many systems do not follow this rule anyhow. */
-    int (*get_ifindex)(const struct netdev *);
+    int (*get_ifindex)(const struct netdev *netdev);
 
     /* Sets 'carrier' to true if carrier is active (link light is on) on
      * 'netdev'. */
@@ -233,7 +240,7 @@ struct netdev_class {
      * A network device that supports some statistics but not others, it should
      * set the values of the unsupported statistics to all-1-bits
      * (UINT64_MAX). */
-    int (*get_stats)(const struct netdev *netdev, struct netdev_stats *stats);
+    int (*get_stats)(const struct netdev *netdev, struct netdev_stats *);
 
     /* Stores the features supported by 'netdev' into each of '*current',
      * '*advertised', '*supported', and '*peer'.  Each value is a bitmap of
@@ -247,14 +254,14 @@ struct netdev_class {
      *
      * This function may be set to null for a network device that does not
      * support configuring advertisements. */
-    int (*set_advertisements)(struct netdev *, uint32_t advertise);
+    int (*set_advertisements)(struct netdev *netdev, uint32_t advertise);
 
     /* If 'netdev' is a VLAN network device (e.g. one created with vconfig(8)),
      * sets '*vlan_vid' to the VLAN VID associated with that device and returns
      * 0.
      *
-     * Returns ENOENT if 'netdev_name' is the name of a network device that is
-     * not a VLAN device.
+     * Returns ENOENT if 'netdev' is a network device that is not a
+     * VLAN device.
      *
      * This function should be set to null if it doesn't make any sense for
      * your network device (it probably doesn't). */
@@ -287,7 +294,8 @@ struct netdev_class {
      *
      * This function may be set to null if it would always return EOPNOTSUPP
      * anyhow. */
-    int (*set_in4)(struct netdev *, struct in_addr addr, struct in_addr mask);
+    int (*set_in4)(struct netdev *netdev, struct in_addr addr,
+                   struct in_addr mask);
 
     /* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address.
      *
@@ -326,12 +334,12 @@ struct netdev_class {
      *
      * This function may be set to null if it would always return EOPNOTSUPP
      * anyhow. */
-    int (*arp_lookup)(const struct netdev *, uint32_t ip, uint8_t mac[6]);
+    int (*arp_lookup)(const struct netdev *netdev, uint32_t ip, uint8_t mac[6]);
 
-    /* Retrieves the current set of flags on 'netdev' into '*old_flags'.  Then,
-     * turns off the flags that are set to 1 in 'off' and turns on the flags
-     * that are set to 1 in 'on'.  (No bit will be set to 1 in both 'off' and
-     * 'on'; that is, off & on == 0.)
+    /* Retrieves the current set of flags on 'netdev' into '*old_flags'.
+     * Then, turns off the flags that are set to 1 in 'off' and turns on the
+     * flags that are set to 1 in 'on'.  (No bit will be set to 1 in both 'off'
+     * and 'on'; that is, off & on == 0.)
      *
      * This function may be invoked from a signal handler.  Therefore, it
      * should not do anything that is not signal-safe (such as logging). */
@@ -344,7 +352,7 @@ struct netdev_class {
      * will have its 'netdev', 'cb', and 'aux' members set to the values of the
      * corresponding parameters. */
     int (*poll_add)(struct netdev *netdev,
-                    void (*cb)(struct netdev_notifier *), void *aux,
+                    void (*cb)(struct netdev_notifier *notifier), void *aux,
                     struct netdev_notifier **notifierp);
 
     /* Cancels poll notification for 'notifier'. */
@@ -353,6 +361,7 @@ struct netdev_class {
 
 extern const struct netdev_class netdev_linux_class;
 extern const struct netdev_class netdev_tap_class;
+extern const struct netdev_class netdev_gre_class;
 
 #ifdef  __cplusplus
 }
index 804050f..ddd6e92 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
 #include "coverage.h"
 #include "dynamic-string.h"
 #include "fatal-signal.h"
+#include "hash.h"
 #include "list.h"
 #include "netdev-provider.h"
 #include "ofpbuf.h"
 #define THIS_MODULE VLM_netdev
 #include "vlog.h"
 
-static const struct netdev_class *netdev_classes[] = {
+static const struct netdev_class *base_netdev_classes[] = {
     &netdev_linux_class,
     &netdev_tap_class,
+    &netdev_gre_class,
 };
-static int n_netdev_classes = ARRAY_SIZE(netdev_classes);
+
+static struct shash netdev_classes = SHASH_INITIALIZER(&netdev_classes);
 
 /* All created network devices. */
-static struct shash netdev_obj_shash = SHASH_INITIALIZER(&netdev_obj_shash);
+static struct shash netdev_dev_shash = SHASH_INITIALIZER(&netdev_dev_shash);
 
 /* All open network devices. */
 static struct list netdev_list = LIST_INITIALIZER(&netdev_list);
@@ -56,45 +59,25 @@ static struct list netdev_list = LIST_INITIALIZER(&netdev_list);
  * additional log messages. */
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
 
-static void restore_all_flags(void *aux);
+static void close_all_netdevs(void *aux UNUSED);
 static int restore_flags(struct netdev *netdev);
+void update_device_args(struct netdev_dev *, const struct shash *args);
 
-/* Attempts to initialize the netdev module.  Returns 0 if successful,
- * otherwise a positive errno value.
- *
- * Calling this function is optional.  If not called explicitly, it will
- * automatically be called upon the first attempt to open or create a 
- * network device. */
-int
+static void
 netdev_initialize(void)
 {
     static int status = -1;
+
     if (status < 0) {
-        int i, j;
+        int i;
 
-        fatal_signal_add_hook(restore_all_flags, NULL, true);
+        fatal_signal_add_hook(close_all_netdevs, NULL, NULL, true);
 
         status = 0;
-        for (i = j = 0; i < n_netdev_classes; i++) {
-            const struct netdev_class *class = netdev_classes[i];
-            if (class->init) {
-                int retval = class->init();
-                if (!retval) {
-                    netdev_classes[j++] = class;
-                } else {
-                    VLOG_ERR("failed to initialize %s network device "
-                             "class: %s", class->type, strerror(retval));
-                    if (!status) {
-                        status = retval;
-                    }
-                }
-            } else {
-                netdev_classes[j++] = class;
-            }
+        for (i = 0; i < ARRAY_SIZE(base_netdev_classes); i++) {
+            netdev_register_provider(base_netdev_classes[i]);
         }
-        n_netdev_classes = j;
     }
-    return status;
 }
 
 /* Performs periodic work needed by all the various kinds of netdevs.
@@ -104,9 +87,9 @@ netdev_initialize(void)
 void
 netdev_run(void)
 {
-    int i;
-    for (i = 0; i < n_netdev_classes; i++) {
-        const struct netdev_class *class = netdev_classes[i];
+    struct shash_node *node;
+    SHASH_FOR_EACH(node, &netdev_classes) {
+        const struct netdev_class *class = node->data;
         if (class->run) {
             class->run();
         }
@@ -120,186 +103,313 @@ netdev_run(void)
 void
 netdev_wait(void)
 {
-    int i;
-    for (i = 0; i < n_netdev_classes; i++) {
-        const struct netdev_class *class = netdev_classes[i];
+    struct shash_node *node;
+    SHASH_FOR_EACH(node, &netdev_classes) {
+        const struct netdev_class *class = node->data;
         if (class->wait) {
             class->wait();
         }
     }
 }
 
-/* Attempts to create a network device object of 'type' with 'name'.  'type' 
- * corresponds to the 'type' field used in the netdev_class * structure.  
- * Arguments for creation are provided in 'args', which may be empty or NULL 
- * if none are needed. */
+/* Initializes and registers a new netdev provider.  After successful
+ * registration, new netdevs of that type can be opened using netdev_open(). */
 int
-netdev_create(const char *name, const char *type, const struct shash *args)
+netdev_register_provider(const struct netdev_class *new_class)
 {
-    struct shash empty_args = SHASH_INITIALIZER(&empty_args);
-    int i;
+    struct netdev_class *new_provider;
 
-    netdev_initialize();
+    if (shash_find(&netdev_classes, new_class->type)) {
+        VLOG_WARN("attempted to register duplicate netdev provider: %s",
+                   new_class->type);
+        return EEXIST;
+    }
 
-    if (!args) {
-        args = &empty_args;
+    if (new_class->init) {
+        int error = new_class->init();
+        if (error) {
+            VLOG_ERR("failed to initialize %s network device class: %s",
+                     new_class->type, strerror(error));
+            return error;
+        }
     }
 
-    if (shash_find(&netdev_obj_shash, name)) {
-        VLOG_WARN("attempted to create a netdev object with bound name: %s",
-                name);
-        return EEXIST;
+    new_provider = xmalloc(sizeof *new_provider);
+    memcpy(new_provider, new_class, sizeof *new_provider);
+
+    shash_add(&netdev_classes, new_class->type, new_provider);
+
+    return 0;
+}
+
+/* Unregisters a netdev provider.  'type' must have been previously
+ * registered and not currently be in use by any netdevs.  After unregistration
+ * new netdevs of that type cannot be opened using netdev_open(). */
+int
+netdev_unregister_provider(const char *type)
+{
+    struct shash_node *del_node, *netdev_dev_node;
+
+    del_node = shash_find(&netdev_classes, type);
+    if (!del_node) {
+        VLOG_WARN("attempted to unregister a netdev provider that is not "
+                  "registered: %s", type);
+        return EAFNOSUPPORT;
     }
 
-    for (i = 0; i < n_netdev_classes; i++) {
-        const struct netdev_class *class = netdev_classes[i];
-        if (!strcmp(type, class->type)) {
-            return class->create(name, type, args, true);
+    SHASH_FOR_EACH(netdev_dev_node, &netdev_dev_shash) {
+        struct netdev_dev *netdev_dev = netdev_dev_node->data;
+        if (!strcmp(netdev_dev->netdev_class->type, type)) {
+            VLOG_WARN("attempted to unregister in use netdev provider: %s",
+                      type);
+            return EBUSY;
         }
     }
 
-    VLOG_WARN("could not create netdev object of unknown type: %s", type);
+    shash_delete(&netdev_classes, del_node);
+    free(del_node->data);
 
-    return EINVAL;
+    return 0;
 }
 
-/* Destroys netdev object 'name'.  Netdev objects maintain a reference count
- * which is incremented on netdev_open() and decremented on netdev_close().  
- * If 'name' has a non-zero reference count, it will not destroy the object 
- * and return EBUSY. */
-int
-netdev_destroy(const char *name)
+/* Clears 'types' and enumerates the types of all currently registered netdev
+ * providers into it.  The caller must first initialize the svec. */
+void
+netdev_enumerate_types(struct svec *types)
 {
     struct shash_node *node;
-    struct netdev_obj *netdev_obj;
 
-    node = shash_find(&netdev_obj_shash, name);
-    if (!node) {
-        return ENODEV;
+    netdev_initialize();
+    svec_clear(types);
+
+    SHASH_FOR_EACH(node, &netdev_classes) {
+        const struct netdev_class *netdev_class = node->data;
+        svec_add(types, netdev_class->type);
     }
+}
+
+/* Compares 'args' to those used to those used by 'dev'.  Returns true
+ * if the arguments are the same, false otherwise.  Does not update the
+ * values stored in 'dev'. */
+static bool
+compare_device_args(const struct netdev_dev *dev, const struct shash *args)
+{
+    const struct shash_node **new_args;
+    bool result = true;
+    int i;
 
-    netdev_obj = node->data;
-    if (netdev_obj->ref_cnt != 0) {
-        VLOG_WARN("attempt to destroy open netdev object (%d): %s", 
-                netdev_obj->ref_cnt, name);
-        return EBUSY;
+    if (shash_count(args) != dev->n_args) {
+        return false;
     }
 
-    shash_delete(&netdev_obj_shash, node);
-    netdev_obj->netdev_class->destroy(netdev_obj);
+    new_args = shash_sort(args);
+    for (i = 0; i < dev->n_args; i++) {
+        if (strcmp(dev->args[i].key, new_args[i]->name) || 
+            strcmp(dev->args[i].value, new_args[i]->data)) {
+            result = false;
+            goto finish;
+        }
+    }
 
-    return 0;
+finish:
+    free(new_args);
+    return result;
 }
 
-/* Reconfigures the device object 'name' with 'args'.  'args' may be empty 
- * or NULL if none are needed. */
-int
-netdev_reconfigure(const char *name, const struct shash *args)
+static int
+compare_args(const void *a_, const void *b_)
 {
-    struct shash empty_args = SHASH_INITIALIZER(&empty_args);
-    struct netdev_obj *netdev_obj;
+    const struct arg *a = a_;
+    const struct arg *b = b_;
+    return strcmp(a->key, b->key);
+}
 
-    if (!args) {
-        args = &empty_args;
+void
+update_device_args(struct netdev_dev *dev, const struct shash *args)
+{
+    struct shash_node *node;
+    int i;
+
+    if (dev->n_args) {
+        for (i = 0; i < dev->n_args; i++) {
+            free(dev->args[i].key);
+            free(dev->args[i].value);
+        }
+
+        free(dev->args);
+        dev->n_args = 0;
+    }
+
+    if (!args || shash_is_empty(args)) {
+        return;
     }
 
-    netdev_obj = shash_find_data(&netdev_obj_shash, name);
-    if (!netdev_obj) {
+    dev->n_args = shash_count(args);
+    dev->args = xmalloc(dev->n_args * sizeof *dev->args);
+
+    i = 0;
+    SHASH_FOR_EACH(node, args) {
+        dev->args[i].key = xstrdup(node->name);
+        dev->args[i].value = xstrdup(node->data);
+        i++;
+    }
+
+    qsort(dev->args, dev->n_args, sizeof *dev->args, compare_args);
+}
+
+static int
+create_device(struct netdev_options *options, struct netdev_dev **netdev_devp)
+{
+    struct netdev_class *netdev_class;
+
+    if (!options->may_create) {
+        VLOG_WARN("attempted to create a device that may not be created: %s",
+                  options->name);
         return ENODEV;
     }
 
-    if (netdev_obj->netdev_class->reconfigure) {
-        return netdev_obj->netdev_class->reconfigure(netdev_obj, args);
+    if (!options->type || strlen(options->type) == 0) {
+        /* Default to system. */
+        options->type = "system";
     }
 
-    return 0;
+    netdev_class = shash_find_data(&netdev_classes, options->type);
+    if (!netdev_class) {
+        VLOG_WARN("could not create netdev %s of unknown type %s",
+                  options->name, options->type);
+        return EAFNOSUPPORT;
+    }
+
+    return netdev_class->create(options->name, options->type, options->args,
+                                netdev_devp);
 }
 
 /* Opens the network device named 'name' (e.g. "eth0") and returns zero if
  * successful, otherwise a positive errno value.  On success, sets '*netdevp'
  * to the new network device, otherwise to null.
  *
+ * If this is the first time the device has been opened, then create is called
+ * before opening.  The device is  created using the given type and arguments.
+ *
  * 'ethertype' may be a 16-bit Ethernet protocol value in host byte order to
  * capture frames of that type received on the device.  It may also be one of
  * the 'enum netdev_pseudo_ethertype' values to receive frames in one of those
- * categories. */
+ * categories.
+ *
+ * If the 'may_create' flag is set then this is allowed to be the first time
+ * the device is opened (i.e. the refcount will be 1 after this call).  It
+ * may be set to false if the device should have already been created.
+ *
+ * If the 'may_open' flag is set then the call will succeed even if another
+ * caller has already opened it.  It may be to false if the device should not
+ * currently be open. */
+
 int
-netdev_open(const char *name, int ethertype, struct netdev **netdevp)
+netdev_open(struct netdev_options *options, struct netdev **netdevp)
 {
-    struct netdev_obj *netdev_obj;
-    struct netdev *netdev = NULL;
+    struct shash empty_args = SHASH_INITIALIZER(&empty_args);
+    struct netdev_dev *netdev_dev;
     int error;
-    int i;
 
+    *netdevp = NULL;
     netdev_initialize();
 
-    netdev_obj = shash_find_data(&netdev_obj_shash, name);
-    if (netdev_obj) {
-        error = netdev_obj->netdev_class->open(name, ethertype, &netdev);
-    } else {
-        /* Default to "system". */
-        error = EAFNOSUPPORT;
-        for (i = 0; i < n_netdev_classes; i++) {
-            const struct netdev_class *class = netdev_classes[i];
-            if (!strcmp(class->type, "system")) {
-                struct shash empty_args = SHASH_INITIALIZER(&empty_args);
-
-                /* Dynamically create the netdev object, but indicate
-                 * that it should be destroyed when the the last user
-                 * closes its handle. */
-                error = class->create(name, "system", &empty_args, false);
-                if (!error) {
-                    error = class->open(name, ethertype, &netdev);
-                    netdev_obj = shash_find_data(&netdev_obj_shash, name);
-                }
-                break;
-            }
+    if (!options->args) {
+        options->args = &empty_args;
+    }
+
+    netdev_dev = shash_find_data(&netdev_dev_shash, options->name);
+
+    if (!netdev_dev) {
+        error = create_device(options, &netdev_dev);
+        if (error) {
+            return error;
         }
+        update_device_args(netdev_dev, options->args);
+
+    } else if (options->may_open) {
+        if (!shash_is_empty(options->args) &&
+            !compare_device_args(netdev_dev, options->args)) {
+
+            VLOG_WARN("%s: attempted to open already created netdev with "
+                      "different arguments", options->name);
+            return EINVAL;
+        }
+    } else {
+        VLOG_WARN("%s: attempted to create a netdev device with bound name",
+                  options->name);
+        return EEXIST;
     }
+
+    error = netdev_dev->netdev_class->open(netdev_dev, options->ethertype, 
+                netdevp);
+
     if (!error) {
-        netdev_obj->ref_cnt++;
+        netdev_dev->ref_cnt++;
+    } else {
+        if (!netdev_dev->ref_cnt) {
+            netdev_dev_uninit(netdev_dev, true);
+        }
     }
 
-    *netdevp = error ? NULL : netdev;
     return error;
 }
 
+int
+netdev_open_default(const char *name, struct netdev **netdevp)
+{
+    struct netdev_options options;
+
+    memset(&options, 0, sizeof options);
+
+    options.name = name;
+    options.ethertype = NETDEV_ETH_TYPE_NONE;
+    options.may_create = true;
+    options.may_open = true;
+
+    return netdev_open(&options, netdevp);
+}
+
+/* Reconfigures the device 'netdev' with 'args'.  'args' may be empty
+ * or NULL if none are needed. */
+int
+netdev_reconfigure(struct netdev *netdev, const struct shash *args)
+{
+    struct shash empty_args = SHASH_INITIALIZER(&empty_args);
+    struct netdev_dev *netdev_dev = netdev_get_dev(netdev);
+
+    if (!args) {
+        args = &empty_args;
+    }
+
+    if (netdev_dev->netdev_class->reconfigure) {
+        if (!compare_device_args(netdev_dev, args)) {
+            update_device_args(netdev_dev, args);
+            return netdev_dev->netdev_class->reconfigure(netdev_dev, args);
+        }
+    } else if (!shash_is_empty(args)) {
+        VLOG_WARN("%s: arguments provided to device that does not have a "
+                  "reconfigure function", netdev_get_name(netdev));
+    }
+
+    return 0;
+}
+
 /* Closes and destroys 'netdev'. */
 void
 netdev_close(struct netdev *netdev)
 {
     if (netdev) {
-        struct netdev_obj *netdev_obj;
-        char *name = netdev->name;
-        int error;
-
-        netdev_obj = shash_find_data(&netdev_obj_shash, name);
-        assert(netdev_obj);
-        if (netdev_obj->ref_cnt > 0) {
-            netdev_obj->ref_cnt--;
-        } else {
-            VLOG_WARN("netdev %s closed too many times", name);
-        }
+        struct netdev_dev *netdev_dev = netdev_get_dev(netdev);
 
-        /* If the reference count for the netdev object is zero, and it
-         * was dynamically created by netdev_open(), destroy it. */
-        if (!netdev_obj->ref_cnt && !netdev_obj->created) {
-            netdev_destroy(name);
-        }
+        assert(netdev_dev->ref_cnt);
+        netdev_dev->ref_cnt--;
+        netdev_uninit(netdev, true);
 
-        /* Restore flags that we changed, if any. */
-        fatal_signal_block();
-        error = restore_flags(netdev);
-        list_remove(&netdev->node);
-        fatal_signal_unblock();
-        if (error) {
-            VLOG_WARN("failed to restore network device flags on %s: %s",
-                      name, strerror(error));
+        /* If the reference count for the netdev device is zero, destroy it. */
+        if (!netdev_dev->ref_cnt) {
+            netdev_dev_uninit(netdev_dev, true);
         }
-
-        /* Free. */
-        netdev->netdev_class->close(netdev);
-        free(name);
     }
 }
 
@@ -311,7 +421,7 @@ netdev_exists(const char *name)
     struct netdev *netdev;
     int error;
 
-    error = netdev_open(name, NETDEV_ETH_TYPE_NONE, &netdev);
+    error = netdev_open_default(name, &netdev);
     if (!error) {
         netdev_close(netdev);
         return true;
@@ -324,31 +434,30 @@ netdev_exists(const char *name)
     }
 }
 
-/* Initializes 'svec' with a list of the names of all known network devices. */
+/*  Clears 'svec' and enumerates the names of all known network devices. */
 int
 netdev_enumerate(struct svec *svec)
 {
-    int error;
-    int i;
-
-    svec_init(svec);
+    struct shash_node *node;
+    int error = 0;
 
     netdev_initialize();
+    svec_clear(svec);
 
-    error = 0;
-    for (i = 0; i < n_netdev_classes; i++) {
-        const struct netdev_class *class = netdev_classes[i];
-        if (class->enumerate) {
-            int retval = class->enumerate(svec);
+    SHASH_FOR_EACH(node, &netdev_classes) {
+        const struct netdev_class *netdev_class = node->data;
+        if (netdev_class->enumerate) {
+            int retval = netdev_class->enumerate(svec);
             if (retval) {
                 VLOG_WARN("failed to enumerate %s network devices: %s",
-                          class->type, strerror(retval));
+                          netdev_class->type, strerror(retval));
                 if (!error) {
                     error = retval;
                 }
             }
         }
     }
+
     return error;
 }
 
@@ -372,8 +481,8 @@ netdev_recv(struct netdev *netdev, struct ofpbuf *buffer)
     assert(buffer->size == 0);
     assert(ofpbuf_tailroom(buffer) >= ETH_TOTAL_MIN);
 
-    retval = netdev->netdev_class->recv(netdev,
-                                        buffer->data, ofpbuf_tailroom(buffer));
+    retval = netdev_get_dev(netdev)->netdev_class->recv(netdev, buffer->data,
+             ofpbuf_tailroom(buffer));
     if (retval >= 0) {
         COVERAGE_INC(netdev_received);
         buffer->size += retval;
@@ -391,14 +500,14 @@ netdev_recv(struct netdev *netdev, struct ofpbuf *buffer)
 void
 netdev_recv_wait(struct netdev *netdev)
 {
-    netdev->netdev_class->recv_wait(netdev);
+    netdev_get_dev(netdev)->netdev_class->recv_wait(netdev);
 }
 
 /* Discards all packets waiting to be received from 'netdev'. */
 int
 netdev_drain(struct netdev *netdev)
 {
-    return netdev->netdev_class->drain(netdev);
+    return netdev_get_dev(netdev)->netdev_class->drain(netdev);
 }
 
 /* Sends 'buffer' on 'netdev'.  Returns 0 if successful, otherwise a positive
@@ -413,7 +522,8 @@ netdev_drain(struct netdev *netdev)
 int
 netdev_send(struct netdev *netdev, const struct ofpbuf *buffer)
 {
-    int error = netdev->netdev_class->send(netdev, buffer->data, buffer->size);
+    int error = netdev_get_dev(netdev)->netdev_class->send(netdev, 
+            buffer->data, buffer->size);
     if (!error) {
         COVERAGE_INC(netdev_sent);
     }
@@ -430,7 +540,7 @@ netdev_send(struct netdev *netdev, const struct ofpbuf *buffer)
 void
 netdev_send_wait(struct netdev *netdev)
 {
-    return netdev->netdev_class->send_wait(netdev);
+    return netdev_get_dev(netdev)->netdev_class->send_wait(netdev);
 }
 
 /* Attempts to set 'netdev''s MAC address to 'mac'.  Returns 0 if successful,
@@ -438,7 +548,7 @@ netdev_send_wait(struct netdev *netdev)
 int
 netdev_set_etheraddr(struct netdev *netdev, const uint8_t mac[ETH_ADDR_LEN])
 {
-    return netdev->netdev_class->set_etheraddr(netdev, mac);
+    return netdev_get_dev(netdev)->netdev_class->set_etheraddr(netdev, mac);
 }
 
 /* Retrieves 'netdev''s MAC address.  If successful, returns 0 and copies the
@@ -447,7 +557,7 @@ netdev_set_etheraddr(struct netdev *netdev, const uint8_t mac[ETH_ADDR_LEN])
 int
 netdev_get_etheraddr(const struct netdev *netdev, uint8_t mac[ETH_ADDR_LEN])
 {
-    return netdev->netdev_class->get_etheraddr(netdev, mac);
+    return netdev_get_dev(netdev)->netdev_class->get_etheraddr(netdev, mac);
 }
 
 /* Returns the name of the network device that 'netdev' represents,
@@ -455,7 +565,7 @@ netdev_get_etheraddr(const struct netdev *netdev, uint8_t mac[ETH_ADDR_LEN])
 const char *
 netdev_get_name(const struct netdev *netdev)
 {
-    return netdev->name;
+    return netdev_get_dev(netdev)->name;
 }
 
 /* Retrieves the MTU of 'netdev'.  The MTU is the maximum size of transmitted
@@ -468,7 +578,7 @@ netdev_get_name(const struct netdev *netdev)
 int
 netdev_get_mtu(const struct netdev *netdev, int *mtup)
 {
-    int error = netdev->netdev_class->get_mtu(netdev, mtup);
+    int error = netdev_get_dev(netdev)->netdev_class->get_mtu(netdev, mtup);
     if (error) {
         VLOG_WARN_RL(&rl, "failed to retrieve MTU for network device %s: %s",
                      netdev_get_name(netdev), strerror(error));
@@ -489,7 +599,7 @@ netdev_get_mtu(const struct netdev *netdev, int *mtup)
 int
 netdev_get_ifindex(const struct netdev *netdev)
 {
-    return netdev->netdev_class->get_ifindex(netdev);
+    return netdev_get_dev(netdev)->netdev_class->get_ifindex(netdev);
 }
 
 /* Stores the features supported by 'netdev' into each of '*current',
@@ -518,8 +628,8 @@ netdev_get_features(struct netdev *netdev,
         peer = &dummy[3];
     }
 
-    error = netdev->netdev_class->get_features(netdev, current, advertised,
-                                               supported, peer);
+    error = netdev_get_dev(netdev)->netdev_class->get_features(netdev, current,
+            advertised, supported, peer);
     if (error) {
         *current = *advertised = *supported = *peer = 0;
     }
@@ -560,8 +670,9 @@ netdev_features_is_full_duplex(uint32_t features)
 int
 netdev_set_advertisements(struct netdev *netdev, uint32_t advertise)
 {
-    return (netdev->netdev_class->set_advertisements
-            ? netdev->netdev_class->set_advertisements(netdev, advertise)
+    return (netdev_get_dev(netdev)->netdev_class->set_advertisements
+            ? netdev_get_dev(netdev)->netdev_class->set_advertisements(
+                    netdev, advertise)
             : EOPNOTSUPP);
 }
 
@@ -585,8 +696,9 @@ netdev_get_in4(const struct netdev *netdev,
     struct in_addr netmask;
     int error;
 
-    error = (netdev->netdev_class->get_in4
-             ? netdev->netdev_class->get_in4(netdev, &address, &netmask)
+    error = (netdev_get_dev(netdev)->netdev_class->get_in4
+             ? netdev_get_dev(netdev)->netdev_class->get_in4(netdev, 
+                    &address, &netmask)
              : EOPNOTSUPP);
     if (address_) {
         address_->s_addr = error ? 0 : address.s_addr;
@@ -603,8 +715,8 @@ netdev_get_in4(const struct netdev *netdev,
 int
 netdev_set_in4(struct netdev *netdev, struct in_addr addr, struct in_addr mask)
 {
-    return (netdev->netdev_class->set_in4
-            ? netdev->netdev_class->set_in4(netdev, addr, mask)
+    return (netdev_get_dev(netdev)->netdev_class->set_in4
+            ? netdev_get_dev(netdev)->netdev_class->set_in4(netdev, addr, mask)
             : EOPNOTSUPP);
 }
 
@@ -614,8 +726,8 @@ int
 netdev_add_router(struct netdev *netdev, struct in_addr router)
 {
     COVERAGE_INC(netdev_add_router);
-    return (netdev->netdev_class->add_router
-            ? netdev->netdev_class->add_router(netdev, router)
+    return (netdev_get_dev(netdev)->netdev_class->add_router
+            ? netdev_get_dev(netdev)->netdev_class->add_router(netdev, router)
             : EOPNOTSUPP);
 }
 
@@ -631,9 +743,9 @@ netdev_get_next_hop(const struct netdev *netdev,
                     const struct in_addr *host, struct in_addr *next_hop,
                     char **netdev_name)
 {
-    int error = (netdev->netdev_class->get_next_hop
-                 ? netdev->netdev_class->get_next_hop(host, next_hop,
-                                                      netdev_name)
+    int error = (netdev_get_dev(netdev)->netdev_class->get_next_hop
+                 ? netdev_get_dev(netdev)->netdev_class->get_next_hop(
+                        host, next_hop, netdev_name)
                  : EOPNOTSUPP);
     if (error) {
         next_hop->s_addr = 0;
@@ -659,8 +771,9 @@ netdev_get_in6(const struct netdev *netdev, struct in6_addr *in6)
     struct in6_addr dummy;
     int error;
 
-    error = (netdev->netdev_class->get_in6
-             ? netdev->netdev_class->get_in6(netdev, in6 ? in6 : &dummy)
+    error = (netdev_get_dev(netdev)->netdev_class->get_in6
+             ? netdev_get_dev(netdev)->netdev_class->get_in6(netdev, 
+                    in6 ? in6 : &dummy)
              : EOPNOTSUPP);
     if (error && in6) {
         memset(in6, 0, sizeof *in6);
@@ -680,8 +793,8 @@ do_update_flags(struct netdev *netdev, enum netdev_flags off,
     enum netdev_flags old_flags;
     int error;
 
-    error = netdev->netdev_class->update_flags(netdev, off & ~on,
-                                               on, &old_flags);
+    error = netdev_get_dev(netdev)->netdev_class->update_flags(netdev, 
+                off & ~on, on, &old_flags);
     if (error) {
         VLOG_WARN_RL(&rl, "failed to %s flags for network device %s: %s",
                      off || on ? "set" : "get", netdev_get_name(netdev),
@@ -754,8 +867,9 @@ int
 netdev_arp_lookup(const struct netdev *netdev,
                   uint32_t ip, uint8_t mac[ETH_ADDR_LEN])
 {
-    int error = (netdev->netdev_class->arp_lookup
-                 ? netdev->netdev_class->arp_lookup(netdev, ip, mac)
+    int error = (netdev_get_dev(netdev)->netdev_class->arp_lookup
+                 ? netdev_get_dev(netdev)->netdev_class->arp_lookup(netdev, 
+                        ip, mac)
                  : EOPNOTSUPP);
     if (error) {
         memset(mac, 0, ETH_ADDR_LEN);
@@ -768,8 +882,9 @@ netdev_arp_lookup(const struct netdev *netdev,
 int
 netdev_get_carrier(const struct netdev *netdev, bool *carrier)
 {
-    int error = (netdev->netdev_class->get_carrier
-                 ? netdev->netdev_class->get_carrier(netdev, carrier)
+    int error = (netdev_get_dev(netdev)->netdev_class->get_carrier
+                 ? netdev_get_dev(netdev)->netdev_class->get_carrier(netdev, 
+                        carrier)
                  : EOPNOTSUPP);
     if (error) {
         *carrier = false;
@@ -784,8 +899,8 @@ netdev_get_stats(const struct netdev *netdev, struct netdev_stats *stats)
     int error;
 
     COVERAGE_INC(netdev_get_stats);
-    error = (netdev->netdev_class->get_stats
-             ? netdev->netdev_class->get_stats(netdev, stats)
+    error = (netdev_get_dev(netdev)->netdev_class->get_stats
+             ? netdev_get_dev(netdev)->netdev_class->get_stats(netdev, stats)
              : EOPNOTSUPP);
     if (error) {
         memset(stats, 0xff, sizeof *stats);
@@ -800,9 +915,9 @@ int
 netdev_set_policing(struct netdev *netdev, uint32_t kbits_rate,
                     uint32_t kbits_burst)
 {
-    return (netdev->netdev_class->set_policing
-            ? netdev->netdev_class->set_policing(netdev,
-                                                 kbits_rate, kbits_burst)
+    return (netdev_get_dev(netdev)->netdev_class->set_policing
+            ? netdev_get_dev(netdev)->netdev_class->set_policing(netdev, 
+                    kbits_rate, kbits_burst)
             : EOPNOTSUPP);
 }
 
@@ -814,8 +929,9 @@ netdev_set_policing(struct netdev *netdev, uint32_t kbits_rate,
 int
 netdev_get_vlan_vid(const struct netdev *netdev, int *vlan_vid)
 {
-    int error = (netdev->netdev_class->get_vlan_vid
-                 ? netdev->netdev_class->get_vlan_vid(netdev, vlan_vid)
+    int error = (netdev_get_dev(netdev)->netdev_class->get_vlan_vid
+                 ? netdev_get_dev(netdev)->netdev_class->get_vlan_vid(netdev, 
+                        vlan_vid)
                  : ENOENT);
     if (error) {
         *vlan_vid = 0;
@@ -829,7 +945,7 @@ struct netdev *
 netdev_find_dev_by_in4(const struct in_addr *in4)
 {
     struct netdev *netdev;
-    struct svec dev_list;
+    struct svec dev_list = SVEC_EMPTY_INITIALIZER;
     size_t i;
 
     netdev_enumerate(&dev_list);
@@ -837,7 +953,7 @@ netdev_find_dev_by_in4(const struct in_addr *in4)
         const char *name = dev_list.names[i];
         struct in_addr dev_in4;
 
-        if (!netdev_open(name, NETDEV_ETH_TYPE_NONE, &netdev)
+        if (!netdev_open_default(name, &netdev)
             && !netdev_get_in4(netdev, &dev_in4, NULL)
             && dev_in4.s_addr == in4->s_addr) {
             goto exit;
@@ -851,46 +967,140 @@ exit:
     return netdev;
 }
 \f
-/* Initializes 'netdev_obj' as a netdev object named 'name' of the 
+/* Initializes 'netdev_dev' as a netdev device named 'name' of the
  * specified 'netdev_class'.
  *
- * This function adds 'netdev_obj' to a netdev-owned shash, so it is
- * very important that 'netdev_obj' only be freed after calling
- * netdev_destroy().  */
+ * This function adds 'netdev_dev' to a netdev-owned shash, so it is
+ * very important that 'netdev_dev' only be freed after calling
+ * the refcount drops to zero.  */
 void
-netdev_obj_init(struct netdev_obj *netdev_obj, const char *name,
-                const struct netdev_class *netdev_class, bool created)
+netdev_dev_init(struct netdev_dev *netdev_dev, const char *name,
+                const struct netdev_class *class_)
 {
-    assert(!shash_find(&netdev_obj_shash, name));
+    assert(!shash_find(&netdev_dev_shash, name));
 
-    netdev_obj->netdev_class = netdev_class;
-    netdev_obj->ref_cnt = 0;
-    netdev_obj->created = created;
-    shash_add(&netdev_obj_shash, name, netdev_obj);
+    memset(netdev_dev, 0, sizeof *netdev_dev);
+    netdev_dev->netdev_class = class_;
+    netdev_dev->name = xstrdup(name);
+    netdev_dev->node = shash_add(&netdev_dev_shash, name, netdev_dev);
 }
 
-/* Initializes 'netdev' as a netdev named 'name' of the specified
- * 'netdev_class'.
+/* Undoes the results of initialization.
+ *
+ * Normally this function does not need to be called as netdev_close has
+ * the same effect when the refcount drops to zero.
+ * However, it may be called by providers due to an error on creation
+ * that occurs after initialization.  It this case netdev_close() would
+ * never be called. */
+void
+netdev_dev_uninit(struct netdev_dev *netdev_dev, bool destroy)
+{
+    char *name = netdev_dev->name;
+
+    assert(!netdev_dev->ref_cnt);
+
+    shash_delete(&netdev_dev_shash, netdev_dev->node);
+    update_device_args(netdev_dev, NULL);
+
+    if (destroy) {
+        netdev_dev->netdev_class->destroy(netdev_dev);
+    }
+    free(name);
+}
+
+/* Returns the class type of 'netdev_dev'.
+ *
+ * The caller must not free the returned value. */
+const char *
+netdev_dev_get_type(const struct netdev_dev *netdev_dev)
+{
+    return netdev_dev->netdev_class->type;
+}
+
+/* Returns the name of 'netdev_dev'.
+ *
+ * The caller must not free the returned value. */
+const char *
+netdev_dev_get_name(const struct netdev_dev *netdev_dev)
+{
+    return netdev_dev->name;
+}
+
+/* Returns the netdev_dev with 'name' or NULL if there is none.
+ *
+ * The caller must not free the returned value. */
+struct netdev_dev *
+netdev_dev_from_name(const char *name)
+{
+    return shash_find_data(&netdev_dev_shash, name);
+}
+
+/* Fills 'device_list' with devices that match 'class'.
+ *
+ * The caller is responsible for initializing and destroying 'device_list'
+ * but the contained netdev_devs must not be freed. */
+void
+netdev_dev_get_devices(const struct netdev_class *class_,
+                       struct shash *device_list)
+{
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, &netdev_dev_shash) {
+        struct netdev_dev *dev = node->data;
+
+        if (dev->netdev_class == class_) {
+            shash_add(device_list, node->name, node->data);
+        }
+    }
+}
+
+/* Initializes 'netdev' as a instance of the netdev_dev.
  *
  * This function adds 'netdev' to a netdev-owned linked list, so it is very
  * important that 'netdev' only be freed after calling netdev_close(). */
 void
-netdev_init(struct netdev *netdev, const char *name,
-            const struct netdev_class *netdev_class)
+netdev_init(struct netdev *netdev, struct netdev_dev *netdev_dev)
 {
-    netdev->netdev_class = netdev_class;
-    netdev->name = xstrdup(name);
-    netdev->save_flags = 0;
-    netdev->changed_flags = 0;
+    memset(netdev, 0, sizeof *netdev);
+    netdev->netdev_dev = netdev_dev;
     list_push_back(&netdev_list, &netdev->node);
 }
 
+/* Undoes the results of initialization.
+ *
+ * Normally this function only needs to be called from netdev_close().
+ * However, it may be called by providers due to an error on opening
+ * that occurs after initialization.  It this case netdev_close() would
+ * never be called. */
+void
+netdev_uninit(struct netdev *netdev, bool close)
+{
+    /* Restore flags that we changed, if any. */
+    int error = restore_flags(netdev);
+    list_remove(&netdev->node);
+    if (error) {
+        VLOG_WARN("failed to restore network device flags on %s: %s",
+                  netdev_get_name(netdev), strerror(error));
+    }
+
+    if (close) {
+        netdev_get_dev(netdev)->netdev_class->close(netdev);
+    }
+}
+
+
 /* Returns the class type of 'netdev'.  
  *
  * The caller must not free the returned value. */
-const char *netdev_get_type(const struct netdev *netdev)
+const char *
+netdev_get_type(const struct netdev *netdev)
 {
-    return netdev->netdev_class->type;
+    return netdev_get_dev(netdev)->netdev_class->type;
+}
+
+struct netdev_dev *
+netdev_get_dev(const struct netdev *netdev)
+{
+    return netdev->netdev_dev;
 }
 
 /* Initializes 'notifier' as a netdev notifier for 'netdev', for which
@@ -930,7 +1140,8 @@ netdev_monitor_destroy(struct netdev_monitor *monitor)
 
         SHASH_FOR_EACH (node, &monitor->polled_netdevs) {
             struct netdev_notifier *notifier = node->data;
-            notifier->netdev->netdev_class->poll_remove(notifier);
+            netdev_get_dev(notifier->netdev)->netdev_class->poll_remove(
+                    notifier);
         }
 
         shash_destroy(&monitor->polled_netdevs);
@@ -960,11 +1171,11 @@ netdev_monitor_add(struct netdev_monitor *monitor, struct netdev *netdev)
     const char *netdev_name = netdev_get_name(netdev);
     int error = 0;
     if (!shash_find(&monitor->polled_netdevs, netdev_name)
-        && netdev->netdev_class->poll_add)
+            && netdev_get_dev(netdev)->netdev_class->poll_add)
     {
         struct netdev_notifier *notifier;
-        error = netdev->netdev_class->poll_add(netdev, netdev_monitor_cb,
-                                               monitor, &notifier);
+        error = netdev_get_dev(netdev)->netdev_class->poll_add(netdev,
+                    netdev_monitor_cb, monitor, &notifier);
         if (!error) {
             assert(notifier->netdev == netdev);
             shash_add(&monitor->polled_netdevs, netdev_name, notifier);
@@ -986,7 +1197,7 @@ netdev_monitor_remove(struct netdev_monitor *monitor, struct netdev *netdev)
     if (node) {
         /* Cancel future notifications. */
         struct netdev_notifier *notifier = node->data;
-        netdev->netdev_class->poll_remove(notifier);
+        netdev_get_dev(netdev)->netdev_class->poll_remove(notifier);
         shash_delete(&monitor->polled_netdevs, node);
 
         /* Drop any pending notification. */
@@ -1044,21 +1255,20 @@ restore_flags(struct netdev *netdev)
     if (netdev->changed_flags) {
         enum netdev_flags restore = netdev->save_flags & netdev->changed_flags;
         enum netdev_flags old_flags;
-        return netdev->netdev_class->update_flags(netdev,
-                                                  netdev->changed_flags
-                                                  & ~restore,
-                                                  restore, &old_flags);
+        return netdev_get_dev(netdev)->netdev_class->update_flags(netdev,
+                                           netdev->changed_flags & ~restore,
+                                           restore, &old_flags);
     }
     return 0;
 }
 
-/* Retores all the flags on all network devices that we modified.  Called from
- * a signal handler, so it does not attempt to report error conditions. */
+/* Close all netdevs on shutdown so they can do any needed cleanup such as
+ * destroying devices, restoring flags, etc. */
 static void
-restore_all_flags(void *aux UNUSED)
+close_all_netdevs(void *aux UNUSED)
 {
-    struct netdev *netdev;
-    LIST_FOR_EACH (netdev, struct netdev, node, &netdev_list) {
-        restore_flags(netdev);
+    struct netdev *netdev, *next;
+    LIST_FOR_EACH_SAFE(netdev, next, struct netdev, node, &netdev_list) {
+        netdev_close(netdev);
     }
 }
index 8060ddb..e3a3176 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -76,18 +76,28 @@ struct netdev_stats {
     uint64_t tx_window_errors;
 };
 
+struct netdev_options {
+    const char *name;
+    const char *type;
+    const struct shash *args;
+    int ethertype;
+    bool may_create;
+    bool may_open;
+};
+
 struct netdev;
+struct netdev_class;
 
-int netdev_initialize(void);
 void netdev_run(void);
 void netdev_wait(void);
 
-int netdev_create(const char *name, const char *type, 
-                  const struct shash *args);
-int netdev_destroy(const char *name);
-int netdev_reconfigure(const char *name, const struct shash *args);
+int netdev_register_provider(const struct netdev_class *);
+int netdev_unregister_provider(const char *type);
+void netdev_enumerate_types(struct svec *types);
 
-int netdev_open(const char *name, int ethertype, struct netdev **);
+int netdev_open(struct netdev_options *, struct netdev **);
+int netdev_open_default(const char *name, struct netdev **);
+int netdev_reconfigure(struct netdev *, const struct shash *args);
 void netdev_close(struct netdev *);
 
 bool netdev_exists(const char *name);
@@ -95,6 +105,7 @@ bool netdev_exists(const char *name);
 int netdev_enumerate(struct svec *);
 
 const char *netdev_get_name(const struct netdev *);
+const char *netdev_get_type(const struct netdev *);
 int netdev_get_mtu(const struct netdev *, int *mtup);
 int netdev_get_ifindex(const struct netdev *);
 
diff --git a/lib/ovsdb-data.c b/lib/ovsdb-data.c
new file mode 100644 (file)
index 0000000..8631016
--- /dev/null
@@ -0,0 +1,1395 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-data.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <float.h>
+#include <inttypes.h>
+#include <limits.h>
+
+#include "dynamic-string.h"
+#include "hash.h"
+#include "ovsdb-error.h"
+#include "json.h"
+#include "shash.h"
+#include "sort.h"
+
+static struct json *
+wrap_json(const char *name, struct json *wrapped)
+{
+    return json_array_create_2(json_string_create(name), wrapped);
+}
+
+void
+ovsdb_atom_init_default(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        atom->integer = 0;
+        break;
+
+    case OVSDB_TYPE_REAL:
+        atom->real = 0.0;
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        atom->boolean = false;
+        break;
+
+    case OVSDB_TYPE_STRING:
+        atom->string = xmemdup("", 1);
+        break;
+
+    case OVSDB_TYPE_UUID:
+        uuid_zero(&atom->uuid);
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+bool
+ovsdb_atom_is_default(const union ovsdb_atom *atom,
+                      enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return atom->integer == 0;
+
+    case OVSDB_TYPE_REAL:
+        return atom->real == 0.0;
+
+    case OVSDB_TYPE_BOOLEAN:
+        return atom->boolean == false;
+
+    case OVSDB_TYPE_STRING:
+        return atom->string[0] == '\0';
+
+    case OVSDB_TYPE_UUID:
+        return uuid_is_zero(&atom->uuid);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_atom_clone(union ovsdb_atom *new, const union ovsdb_atom *old,
+                 enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        new->integer = old->integer;
+        break;
+
+    case OVSDB_TYPE_REAL:
+        new->real = old->real;
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        new->boolean = old->boolean;
+        break;
+
+    case OVSDB_TYPE_STRING:
+        new->string = xstrdup(old->string);
+        break;
+
+    case OVSDB_TYPE_UUID:
+        new->uuid = old->uuid;
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+void
+ovsdb_atom_swap(union ovsdb_atom *a, union ovsdb_atom *b)
+{
+    union ovsdb_atom tmp = *a;
+    *a = *b;
+    *b = tmp;
+}
+
+uint32_t
+ovsdb_atom_hash(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                uint32_t basis)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return hash_int(atom->integer, basis);
+
+    case OVSDB_TYPE_REAL:
+        return hash_double(atom->real, basis);
+
+    case OVSDB_TYPE_BOOLEAN:
+        return hash_boolean(atom->boolean, basis);
+
+    case OVSDB_TYPE_STRING:
+        return hash_string(atom->string, basis);
+
+    case OVSDB_TYPE_UUID:
+        return hash_int(uuid_hash(&atom->uuid), basis);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+int
+ovsdb_atom_compare_3way(const union ovsdb_atom *a,
+                        const union ovsdb_atom *b,
+                        enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return a->integer < b->integer ? -1 : a->integer > b->integer;
+
+    case OVSDB_TYPE_REAL:
+        return a->real < b->real ? -1 : a->real > b->real;
+
+    case OVSDB_TYPE_BOOLEAN:
+        return a->boolean - b->boolean;
+
+    case OVSDB_TYPE_STRING:
+        return strcmp(a->string, b->string);
+
+    case OVSDB_TYPE_UUID:
+        return uuid_compare_3way(&a->uuid, &b->uuid);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct ovsdb_error *
+unwrap_json(const struct json *json, const char *name,
+            enum json_type value_type, const struct json **value)
+{
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 2
+        || json->u.array.elems[0]->type != JSON_STRING
+        || (name && strcmp(json->u.array.elems[0]->u.string, name))
+        || json->u.array.elems[1]->type != value_type)
+    {
+        return ovsdb_syntax_error(json, NULL, "expected [\"%s\", <%s>]", name,
+                                  json_type_to_string(value_type));
+    }
+    *value = json->u.array.elems[1];
+    return NULL;
+}
+
+static struct ovsdb_error *
+parse_json_pair(const struct json *json,
+                const struct json **elem0, const struct json **elem1)
+{
+    if (json->type != JSON_ARRAY || json->u.array.n != 2) {
+        return ovsdb_syntax_error(json, NULL, "expected 2-element array");
+    }
+    *elem0 = json->u.array.elems[0];
+    *elem1 = json->u.array.elems[1];
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json,
+                      const struct ovsdb_symbol_table *symtab)
+    WARN_UNUSED_RESULT;
+
+static struct ovsdb_error *
+ovsdb_atom_parse_uuid(struct uuid *uuid, const struct json *json,
+                      const struct ovsdb_symbol_table *symtab)
+{
+    struct ovsdb_error *error0;
+    const struct json *value;
+
+    error0 = unwrap_json(json, "uuid", JSON_STRING, &value);
+    if (!error0) {
+        const char *uuid_string = json_string(value);
+        if (!uuid_from_string(uuid, uuid_string)) {
+            return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
+                                      uuid_string);
+        }
+    } else if (symtab) {
+        struct ovsdb_error *error1;
+
+        error1 = unwrap_json(json, "named-uuid", JSON_STRING, &value);
+        if (!error1) {
+            const char *name = json_string(value);
+            const struct ovsdb_symbol *symbol;
+
+            ovsdb_error_destroy(error0);
+
+            symbol = ovsdb_symbol_table_get(symtab, name);
+            if (symbol) {
+                *uuid = symbol->uuid;
+                return NULL;
+            } else {
+                return ovsdb_syntax_error(json, NULL,
+                                          "unknown named-uuid \"%s\"", name);
+            }
+        }
+        ovsdb_error_destroy(error1);
+    }
+
+    return error0;
+}
+
+struct ovsdb_error *
+ovsdb_atom_from_json(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                     const struct json *json,
+                     const struct ovsdb_symbol_table *symtab)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        if (json->type == JSON_INTEGER) {
+            atom->integer = json->u.integer;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_REAL:
+        if (json->type == JSON_INTEGER) {
+            atom->real = json->u.integer;
+            return NULL;
+        } else if (json->type == JSON_REAL) {
+            atom->real = json->u.real;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        if (json->type == JSON_TRUE) {
+            atom->boolean = true;
+            return NULL;
+        } else if (json->type == JSON_FALSE) {
+            atom->boolean = false;
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (json->type == JSON_STRING) {
+            atom->string = xstrdup(json->u.string);
+            return NULL;
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        return ovsdb_atom_parse_uuid(&atom->uuid, json, symtab);
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+
+    return ovsdb_syntax_error(json, NULL, "expected %s",
+                              ovsdb_atomic_type_to_string(type));
+}
+
+struct json *
+ovsdb_atom_to_json(const union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        return json_integer_create(atom->integer);
+
+    case OVSDB_TYPE_REAL:
+        return json_real_create(atom->real);
+
+    case OVSDB_TYPE_BOOLEAN:
+        return json_boolean_create(atom->boolean);
+
+    case OVSDB_TYPE_STRING:
+        return json_string_create(atom->string);
+
+    case OVSDB_TYPE_UUID:
+        return wrap_json("uuid", json_string_create_nocopy(
+                             xasprintf(UUID_FMT, UUID_ARGS(&atom->uuid))));
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+
+/* Initializes 'atom' to a value of the given 'type' parsed from 's', which
+ * takes one of the following forms:
+ *
+ *      - OVSDB_TYPE_INTEGER: A decimal integer optionally preceded by a sign.
+ *
+ *      - OVSDB_TYPE_REAL: A floating-point number in the format accepted by
+ *        strtod().
+ *
+ *      - OVSDB_TYPE_BOOLEAN: "true", "yes", "on", "1" for true, or "false",
+ *        "no", "off", or "0" for false.
+ *
+ *      - OVSDB_TYPE_STRING: A JSON string if it begins with a quote, otherwise
+ *        an arbitrary string.
+ *
+ *      - OVSDB_TYPE_UUID: A UUID in RFC 4122 format.
+ *
+ * Returns a null pointer if successful, otherwise an error message describing
+ * the problem.  The caller is responsible for freeing the error.
+ */
+char *
+ovsdb_atom_from_string(union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                       const char *s)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER: {
+        long long int integer;
+        if (!str_to_llong(s, 10, &integer)) {
+            return xasprintf("\"%s\" is not a valid integer", s);
+        }
+        atom->integer = integer;
+    }
+        break;
+
+    case OVSDB_TYPE_REAL:
+        if (!str_to_double(s, &atom->real)) {
+            return xasprintf("\"%s\" is not a valid real number", s);
+        }
+        /* Our JSON input routines map negative zero to zero, so do that here
+         * too for consistency. */
+        if (atom->real == 0.0) {
+            atom->real = 0.0;
+        }
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        if (!strcmp(s, "true") || !strcmp(s, "yes") || !strcmp(s, "on")
+            || !strcmp(s, "1")) {
+            atom->boolean = true;
+        } else if (!strcmp(s, "false") || !strcmp(s, "no") || !strcmp(s, "off")
+                   || !strcmp(s, "0")) {
+            atom->boolean = false;
+        } else {
+            return xasprintf("\"%s\" is not a valid boolean "
+                             "(use \"true\" or \"false\")", s);
+        }
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (*s == '\0') {
+            return xstrdup("An empty string is not valid as input; "
+                           "use \"\" to represent the empty string");
+        } else if (*s == '"') {
+            size_t s_len = strlen(s);
+
+            if (s_len < 2 || s[s_len - 1] != '"') {
+                return xasprintf("%s: missing quote at end of "
+                                 "quoted string", s);
+            } else if (!json_string_unescape(s + 1, s_len - 2,
+                                             &atom->string)) {
+                char *error = xasprintf("%s: %s", s, atom->string);
+                free(atom->string);
+                return error;
+            }
+        } else {
+            atom->string = xstrdup(s);
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        if (!uuid_from_string(&atom->uuid, s)) {
+            return xasprintf("\"%s\" is not a valid UUID", s);
+        }
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+
+    return NULL;
+}
+
+static bool
+string_needs_quotes(const char *s)
+{
+    const char *p = s;
+    unsigned char c;
+
+    c = *p++;
+    if (!isalpha(c) && c != '_') {
+        return true;
+    }
+
+    while ((c = *p++) != '\0') {
+        if (!isalpha(c) && c != '_' && c != '-' && c != '.') {
+            return true;
+        }
+    }
+
+    if (!strcmp(s, "true") || !strcmp(s, "false")) {
+        return true;
+    }
+
+    return false;
+}
+
+/* Appends 'atom' (which has the given 'type') to 'out', in a format acceptable
+ * to ovsdb_atom_from_string().  */
+void
+ovsdb_atom_to_string(const union ovsdb_atom *atom, enum ovsdb_atomic_type type,
+                     struct ds *out)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        NOT_REACHED();
+
+    case OVSDB_TYPE_INTEGER:
+        ds_put_format(out, "%"PRId64, atom->integer);
+        break;
+
+    case OVSDB_TYPE_REAL:
+        ds_put_format(out, "%.*g", DBL_DIG, atom->real);
+        break;
+
+    case OVSDB_TYPE_BOOLEAN:
+        ds_put_cstr(out, atom->boolean ? "true" : "false");
+        break;
+
+    case OVSDB_TYPE_STRING:
+        if (string_needs_quotes(atom->string)) {
+            struct json json;
+
+            json.type = JSON_STRING;
+            json.u.string = atom->string;
+            json_to_ds(&json, 0, out);
+        } else {
+            ds_put_cstr(out, atom->string);
+        }
+        break;
+
+    case OVSDB_TYPE_UUID:
+        ds_put_format(out, UUID_FMT, UUID_ARGS(&atom->uuid));
+        break;
+
+    case OVSDB_N_TYPES:
+    default:
+        NOT_REACHED();
+    }
+}
+\f
+static union ovsdb_atom *
+alloc_default_atoms(enum ovsdb_atomic_type type, size_t n)
+{
+    if (type != OVSDB_TYPE_VOID && n) {
+        union ovsdb_atom *atoms;
+        unsigned int i;
+
+        atoms = xmalloc(n * sizeof *atoms);
+        for (i = 0; i < n; i++) {
+            ovsdb_atom_init_default(&atoms[i], type);
+        }
+        return atoms;
+    } else {
+        /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is
+         * treated as xmalloc(1). */
+        return NULL;
+    }
+}
+
+void
+ovsdb_datum_init_empty(struct ovsdb_datum *datum)
+{
+    datum->n = 0;
+    datum->keys = NULL;
+    datum->values = NULL;
+}
+
+void
+ovsdb_datum_init_default(struct ovsdb_datum *datum,
+                         const struct ovsdb_type *type)
+{
+    datum->n = type->n_min;
+    datum->keys = alloc_default_atoms(type->key_type, datum->n);
+    datum->values = alloc_default_atoms(type->value_type, datum->n);
+}
+
+bool
+ovsdb_datum_is_default(const struct ovsdb_datum *datum,
+                       const struct ovsdb_type *type)
+{
+    size_t i;
+
+    if (datum->n != type->n_min) {
+        return false;
+    }
+    for (i = 0; i < datum->n; i++) {
+        if (!ovsdb_atom_is_default(&datum->keys[i], type->key_type)) {
+            return false;
+        }
+        if (type->value_type != OVSDB_TYPE_VOID
+            && !ovsdb_atom_is_default(&datum->values[i], type->value_type)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static union ovsdb_atom *
+clone_atoms(const union ovsdb_atom *old, enum ovsdb_atomic_type type, size_t n)
+{
+    if (type != OVSDB_TYPE_VOID && n) {
+        union ovsdb_atom *new;
+        unsigned int i;
+
+        new = xmalloc(n * sizeof *new);
+        for (i = 0; i < n; i++) {
+            ovsdb_atom_clone(&new[i], &old[i], type);
+        }
+        return new;
+    } else {
+        /* Avoid wasting memory in the n == 0 case, because xmalloc(0) is
+         * treated as xmalloc(1). */
+        return NULL;
+    }
+}
+
+void
+ovsdb_datum_clone(struct ovsdb_datum *new, const struct ovsdb_datum *old,
+                  const struct ovsdb_type *type)
+{
+    unsigned int n = old->n;
+    new->n = n;
+    new->keys = clone_atoms(old->keys, type->key_type, n);
+    new->values = clone_atoms(old->values, type->value_type, n);
+}
+
+static void
+free_data(enum ovsdb_atomic_type type,
+          union ovsdb_atom *atoms, size_t n_atoms)
+{
+    if (ovsdb_atom_needs_destruction(type)) {
+        unsigned int i;
+        for (i = 0; i < n_atoms; i++) {
+            ovsdb_atom_destroy(&atoms[i], type);
+        }
+    }
+    free(atoms);
+}
+
+void
+ovsdb_datum_destroy(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+{
+    free_data(type->key_type, datum->keys, datum->n);
+    free_data(type->value_type, datum->values, datum->n);
+}
+
+void
+ovsdb_datum_swap(struct ovsdb_datum *a, struct ovsdb_datum *b)
+{
+    struct ovsdb_datum tmp = *a;
+    *a = *b;
+    *b = tmp;
+}
+
+struct ovsdb_datum_sort_cbdata {
+    const struct ovsdb_type *type;
+    struct ovsdb_datum *datum;
+};
+
+static int
+ovsdb_datum_sort_compare_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
+
+    return ovsdb_atom_compare_3way(&cbdata->datum->keys[a],
+                                   &cbdata->datum->keys[b],
+                                   cbdata->type->key_type);
+}
+
+static void
+ovsdb_datum_sort_swap_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_datum_sort_cbdata *cbdata = cbdata_;
+
+    ovsdb_atom_swap(&cbdata->datum->keys[a], &cbdata->datum->keys[b]);
+    if (cbdata->type->value_type != OVSDB_TYPE_VOID) {
+        ovsdb_atom_swap(&cbdata->datum->values[a], &cbdata->datum->values[b]);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_datum_sort(struct ovsdb_datum *datum, const struct ovsdb_type *type)
+{
+    if (datum->n < 2) {
+        return NULL;
+    } else {
+        struct ovsdb_datum_sort_cbdata cbdata;
+        size_t i;
+
+        cbdata.type = type;
+        cbdata.datum = datum;
+        sort(datum->n, ovsdb_datum_sort_compare_cb, ovsdb_datum_sort_swap_cb,
+             &cbdata);
+
+        for (i = 0; i < datum->n - 1; i++) {
+            if (ovsdb_atom_equals(&datum->keys[i], &datum->keys[i + 1],
+                                  type->key_type)) {
+                if (ovsdb_type_is_map(type)) {
+                    return ovsdb_error(NULL, "map contains duplicate key");
+                } else {
+                    return ovsdb_error(NULL, "set contains duplicate");
+                }
+            }
+        }
+
+        return NULL;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_datum_from_json(struct ovsdb_datum *datum,
+                      const struct ovsdb_type *type,
+                      const struct json *json,
+                      const struct ovsdb_symbol_table *symtab)
+{
+    struct ovsdb_error *error;
+
+    if (ovsdb_type_is_scalar(type)) {
+        datum->n = 1;
+        datum->keys = xmalloc(sizeof *datum->keys);
+        datum->values = NULL;
+
+        error = ovsdb_atom_from_json(&datum->keys[0], type->key_type,
+                                     json, symtab);
+        if (error) {
+            free(datum->keys);
+        }
+        return error;
+    } else {
+        bool is_map = ovsdb_type_is_map(type);
+        const char *class = is_map ? "map" : "set";
+        const struct json *inner;
+        unsigned int i;
+        size_t n;
+
+        assert(is_map || ovsdb_type_is_set(type));
+
+        error = unwrap_json(json, class, JSON_ARRAY, &inner);
+        if (error) {
+            return error;
+        }
+
+        n = inner->u.array.n;
+        if (n < type->n_min || n > type->n_max) {
+            return ovsdb_syntax_error(json, NULL, "%s must have %u to "
+                                      "%u members but %zu are present",
+                                      class, type->n_min, type->n_max, n);
+        }
+
+        datum->n = 0;
+        datum->keys = xmalloc(n * sizeof *datum->keys);
+        datum->values = is_map ? xmalloc(n * sizeof *datum->values) : NULL;
+        for (i = 0; i < n; i++) {
+            const struct json *element = inner->u.array.elems[i];
+            const struct json *key = NULL;
+            const struct json *value = NULL;
+
+            if (!is_map) {
+                key = element;
+            } else {
+                error = parse_json_pair(element, &key, &value);
+                if (error) {
+                    goto error;
+                }
+            }
+
+            error = ovsdb_atom_from_json(&datum->keys[i], type->key_type,
+                                         key, symtab);
+            if (error) {
+                goto error;
+            }
+
+            if (is_map) {
+                error = ovsdb_atom_from_json(&datum->values[i],
+                                             type->value_type, value, symtab);
+                if (error) {
+                    ovsdb_atom_destroy(&datum->keys[i], type->key_type);
+                    goto error;
+                }
+            }
+
+            datum->n++;
+        }
+
+        error = ovsdb_datum_sort(datum, type);
+        if (error) {
+            goto error;
+        }
+
+        return NULL;
+
+    error:
+        ovsdb_datum_destroy(datum, type);
+        return error;
+    }
+}
+
+struct json *
+ovsdb_datum_to_json(const struct ovsdb_datum *datum,
+                    const struct ovsdb_type *type)
+{
+    /* These tests somewhat tolerate a 'datum' that does not exactly match
+     * 'type', in particular a datum with 'n' not in the allowed range. */
+    if (datum->n == 1 && ovsdb_type_is_scalar(type)) {
+        return ovsdb_atom_to_json(&datum->keys[0], type->key_type);
+    } else if (type->value_type == OVSDB_TYPE_VOID) {
+        struct json **elems;
+        size_t i;
+
+        elems = xmalloc(datum->n * sizeof *elems);
+        for (i = 0; i < datum->n; i++) {
+            elems[i] = ovsdb_atom_to_json(&datum->keys[i], type->key_type);
+        }
+
+        return wrap_json("set", json_array_create(elems, datum->n));
+    } else {
+        struct json **elems;
+        size_t i;
+
+        elems = xmalloc(datum->n * sizeof *elems);
+        for (i = 0; i < datum->n; i++) {
+            elems[i] = json_array_create_2(
+                ovsdb_atom_to_json(&datum->keys[i], type->key_type),
+                ovsdb_atom_to_json(&datum->values[i], type->value_type));
+        }
+
+        return wrap_json("map", json_array_create(elems, datum->n));
+    }
+}
+
+static const char *
+skip_spaces(const char *p)
+{
+    while (isspace((unsigned char) *p)) {
+        p++;
+    }
+    return p;
+}
+
+static char *
+parse_atom_token(const char **s, enum ovsdb_atomic_type type,
+                 union ovsdb_atom *atom)
+{
+    char *token, *error;
+
+    error = ovsdb_token_parse(s, &token);
+    if (!error) {
+        error = ovsdb_atom_from_string(atom, type, token);
+        free(token);
+    }
+    return error;
+}
+
+
+static char *
+parse_key_value(const char **s, const struct ovsdb_type *type,
+                union ovsdb_atom *key, union ovsdb_atom *value)
+{
+    const char *start = *s;
+    char *error;
+
+    error = parse_atom_token(s, type->key_type, key);
+    if (!error && type->value_type != OVSDB_TYPE_VOID) {
+        *s = skip_spaces(*s);
+        if (**s == '=') {
+            (*s)++;
+            *s = skip_spaces(*s);
+            error = parse_atom_token(s, type->value_type, value);
+        } else {
+            error = xasprintf("%s: syntax error at \"%c\" expecting \"=\"",
+                              start, **s);
+        }
+        if (error) {
+            ovsdb_atom_destroy(key, type->key_type);
+        }
+    }
+    return error;
+}
+
+static void
+free_key_value(const struct ovsdb_type *type,
+               union ovsdb_atom *key, union ovsdb_atom *value)
+{
+    ovsdb_atom_destroy(key, type->key_type);
+    if (type->value_type != OVSDB_TYPE_VOID) {
+        ovsdb_atom_destroy(value, type->value_type);
+    }
+}
+
+/* Initializes 'datum' as a datum of the given 'type', parsing its contents
+ * from 's'.  The format of 's' is a series of space or comma separated atoms
+ * or, for a map, '='-delimited pairs of atoms.  Each atom must in a format
+ * acceptable to ovsdb_atom_from_string().  Optionally, a set may be enclosed
+ * in "[]" or a map in "{}"; for an empty set or map these punctuators are
+ * required. */
+char *
+ovsdb_datum_from_string(struct ovsdb_datum *datum,
+                        const struct ovsdb_type *type, const char *s)
+{
+    bool is_map = ovsdb_type_is_map(type);
+    struct ovsdb_error *dberror;
+    const char *p;
+    int end_delim;
+    char *error;
+
+    ovsdb_datum_init_empty(datum);
+
+    /* Swallow a leading delimiter if there is one. */
+    p = skip_spaces(s);
+    if (*p == (is_map ? '{' : '[')) {
+        end_delim = is_map ? '}' : ']';
+        p = skip_spaces(p + 1);
+    } else if (!*p) {
+        if (is_map) {
+            return xstrdup("use \"{}\" to specify the empty map");
+        } else {
+            return xstrdup("use \"[]\" to specify the empty set");
+        }
+    } else {
+        end_delim = 0;
+    }
+
+    while (*p && *p != end_delim) {
+        union ovsdb_atom key, value;
+
+        if (ovsdb_token_is_delim(*p)) {
+            error = xasprintf("%s: unexpected \"%c\" parsing %s",
+                              s, *p, ovsdb_type_to_english(type));
+            goto error;
+        }
+
+        /* Add to datum. */
+        error = parse_key_value(&p, type, &key, &value);
+        if (error) {
+            goto error;
+        }
+        ovsdb_datum_add_unsafe(datum, &key, &value, type);
+        free_key_value(type, &key, &value);
+
+        /* Skip optional white space and comma. */
+        p = skip_spaces(p);
+        if (*p == ',') {
+            p = skip_spaces(p + 1);
+        }
+    }
+
+    if (*p != end_delim) {
+        error = xasprintf("%s: missing \"%c\" at end of data", s, end_delim);
+        goto error;
+    }
+    if (end_delim) {
+        p = skip_spaces(p + 1);
+        if (*p) {
+            error = xasprintf("%s: trailing garbage after \"%c\"",
+                              s, end_delim);
+            goto error;
+        }
+    }
+
+    if (datum->n < type->n_min) {
+        error = xasprintf("%s: %u %s specified but the minimum number is %u",
+                          s, datum->n, is_map ? "pair(s)" : "value(s)",
+                          type->n_min);
+        goto error;
+    } else if (datum->n > type->n_max) {
+        error = xasprintf("%s: %u %s specified but the maximum number is %u",
+                          s, datum->n, is_map ? "pair(s)" : "value(s)",
+            type->n_max);
+        goto error;
+    }
+
+    dberror = ovsdb_datum_sort(datum, type);
+    if (dberror) {
+        ovsdb_error_destroy(dberror);
+        if (ovsdb_type_is_map(type)) {
+            error = xasprintf("%s: map contains duplicate key", s);
+        } else {
+            error = xasprintf("%s: set contains duplicate value", s);
+        }
+        goto error;
+    }
+
+    return NULL;
+
+error:
+    ovsdb_datum_destroy(datum, type);
+    ovsdb_datum_init_empty(datum);
+    return error;
+}
+
+/* Appends to 'out' the 'datum' (with the given 'type') in a format acceptable
+ * to ovsdb_datum_from_string(). */
+void
+ovsdb_datum_to_string(const struct ovsdb_datum *datum,
+                      const struct ovsdb_type *type, struct ds *out)
+{
+    bool is_map = ovsdb_type_is_map(type);
+    size_t i;
+
+    if (type->n_max > 1 || !datum->n) {
+        ds_put_char(out, is_map ? '{' : '[');
+    }
+    for (i = 0; i < datum->n; i++) {
+        if (i > 0) {
+            ds_put_cstr(out, ", ");
+        }
+
+        ovsdb_atom_to_string(&datum->keys[i], type->key_type, out);
+        if (is_map) {
+            ds_put_char(out, '=');
+            ovsdb_atom_to_string(&datum->values[i], type->value_type, out);
+        }
+    }
+    if (type->n_max > 1 || !datum->n) {
+        ds_put_char(out, is_map ? '}' : ']');
+    }
+}
+
+static uint32_t
+hash_atoms(enum ovsdb_atomic_type type, const union ovsdb_atom *atoms,
+           unsigned int n, uint32_t basis)
+{
+    if (type != OVSDB_TYPE_VOID) {
+        unsigned int i;
+
+        for (i = 0; i < n; i++) {
+            basis = ovsdb_atom_hash(&atoms[i], type, basis);
+        }
+    }
+    return basis;
+}
+
+uint32_t
+ovsdb_datum_hash(const struct ovsdb_datum *datum,
+                 const struct ovsdb_type *type, uint32_t basis)
+{
+    basis = hash_atoms(type->key_type, datum->keys, datum->n, basis);
+    basis ^= (type->key_type << 24) | (type->value_type << 16) | datum->n;
+    basis = hash_atoms(type->value_type, datum->values, datum->n, basis);
+    return basis;
+}
+
+static int
+atom_arrays_compare_3way(const union ovsdb_atom *a,
+                         const union ovsdb_atom *b,
+                         enum ovsdb_atomic_type type,
+                         size_t n)
+{
+    unsigned int i;
+
+    for (i = 0; i < n; i++) {
+        int cmp = ovsdb_atom_compare_3way(&a[i], &b[i], type);
+        if (cmp) {
+            return cmp;
+        }
+    }
+
+    return 0;
+}
+
+bool
+ovsdb_datum_equals(const struct ovsdb_datum *a,
+                   const struct ovsdb_datum *b,
+                   const struct ovsdb_type *type)
+{
+    return !ovsdb_datum_compare_3way(a, b, type);
+}
+
+int
+ovsdb_datum_compare_3way(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    int cmp;
+
+    if (a->n != b->n) {
+        return a->n < b->n ? -1 : 1;
+    }
+
+    cmp = atom_arrays_compare_3way(a->keys, b->keys, type->key_type, a->n);
+    if (cmp) {
+        return cmp;
+    }
+
+    return (type->value_type == OVSDB_TYPE_VOID ? 0
+            : atom_arrays_compare_3way(a->values, b->values, type->value_type,
+                                       a->n));
+}
+
+/* If 'key' is one of the keys in 'datum', returns its index within 'datum',
+ * otherwise UINT_MAX.  'key_type' must be the type of the atoms stored in the
+ * 'keys' array in 'datum'.
+ */
+unsigned int
+ovsdb_datum_find_key(const struct ovsdb_datum *datum,
+                     const union ovsdb_atom *key,
+                     enum ovsdb_atomic_type key_type)
+{
+    unsigned int low = 0;
+    unsigned int high = datum->n;
+    while (low < high) {
+        unsigned int idx = (low + high) / 2;
+        int cmp = ovsdb_atom_compare_3way(key, &datum->keys[idx], key_type);
+        if (cmp < 0) {
+            high = idx;
+        } else if (cmp > 0) {
+            low = idx + 1;
+        } else {
+            return idx;
+        }
+    }
+    return UINT_MAX;
+}
+
+/* If 'key' and 'value' is one of the key-value pairs in 'datum', returns its
+ * index within 'datum', otherwise UINT_MAX.  'key_type' must be the type of
+ * the atoms stored in the 'keys' array in 'datum'.  'value_type' may be the
+ * type of the 'values' atoms or OVSDB_TYPE_VOID to compare only keys.
+ */
+unsigned int
+ovsdb_datum_find_key_value(const struct ovsdb_datum *datum,
+                           const union ovsdb_atom *key,
+                           enum ovsdb_atomic_type key_type,
+                           const union ovsdb_atom *value,
+                           enum ovsdb_atomic_type value_type)
+{
+    unsigned int idx = ovsdb_datum_find_key(datum, key, key_type);
+    if (idx != UINT_MAX
+        && value_type != OVSDB_TYPE_VOID
+        && !ovsdb_atom_equals(&datum->values[idx], value, value_type)) {
+        idx = UINT_MAX;
+    }
+    return idx;
+}
+
+/* If atom 'i' in 'a' is also in 'b', returns its index in 'b', otherwise
+ * UINT_MAX.  'type' must be the type of 'a' and 'b', except that
+ * type->value_type may be set to OVSDB_TYPE_VOID to compare keys but not
+ * values. */
+static unsigned int
+ovsdb_datum_find(const struct ovsdb_datum *a, int i,
+                 const struct ovsdb_datum *b,
+                 const struct ovsdb_type *type)
+{
+    return ovsdb_datum_find_key_value(b,
+                                      &a->keys[i], type->key_type,
+                                      a->values ? &a->values[i] : NULL,
+                                      type->value_type);
+}
+
+/* Returns true if every element in 'a' is also in 'b', false otherwise. */
+bool
+ovsdb_datum_includes_all(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    size_t i;
+
+    for (i = 0; i < a->n; i++) {
+        if (ovsdb_datum_find(a, i, b, type) == UINT_MAX) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/* Returns true if no element in 'a' is also in 'b', false otherwise. */
+bool
+ovsdb_datum_excludes_all(const struct ovsdb_datum *a,
+                         const struct ovsdb_datum *b,
+                         const struct ovsdb_type *type)
+{
+    size_t i;
+
+    for (i = 0; i < a->n; i++) {
+        if (ovsdb_datum_find(a, i, b, type) != UINT_MAX) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static void
+ovsdb_datum_reallocate(struct ovsdb_datum *a, const struct ovsdb_type *type,
+                       unsigned int capacity)
+{
+    a->keys = xrealloc(a->keys, capacity * sizeof *a->keys);
+    if (type->value_type != OVSDB_TYPE_VOID) {
+        a->values = xrealloc(a->values, capacity * sizeof *a->values);
+    }
+}
+
+/* Removes the element with index 'idx' from 'datum', which has type 'type'.
+ * If 'idx' is not the last element in 'datum', then the removed element is
+ * replaced by the (former) last element.
+ *
+ * This function does not maintain ovsdb_datum invariants.  Use
+ * ovsdb_datum_sort() to check and restore these invariants. */
+void
+ovsdb_datum_remove_unsafe(struct ovsdb_datum *datum, size_t idx,
+                          const struct ovsdb_type *type)
+{
+    ovsdb_atom_destroy(&datum->keys[idx], type->key_type);
+    datum->keys[idx] = datum->keys[datum->n - 1];
+    if (type->value_type != OVSDB_TYPE_VOID) {
+        ovsdb_atom_destroy(&datum->values[idx], type->value_type);
+        datum->values[idx] = datum->values[datum->n - 1];
+    }
+    datum->n--;
+}
+
+/* Adds the element with the given 'key' and 'value' to 'datum', which must
+ * have the specified 'type'.
+ *
+ * This function always allocates memory, so it is not an efficient way to add
+ * a number of elements to a datum.
+ *
+ * This function does not maintain ovsdb_datum invariants.  Use
+ * ovsdb_datum_sort() to check and restore these invariants.  (But a datum with
+ * 0 or 1 elements cannot violate the invariants anyhow.) */
+void
+ovsdb_datum_add_unsafe(struct ovsdb_datum *datum,
+                       const union ovsdb_atom *key,
+                       const union ovsdb_atom *value,
+                       const struct ovsdb_type *type)
+{
+    size_t idx = datum->n++;
+    datum->keys = xrealloc(datum->keys, datum->n * sizeof *datum->keys);
+    ovsdb_atom_clone(&datum->keys[idx], key, type->key_type);
+    if (type->value_type != OVSDB_TYPE_VOID) {
+        datum->values = xrealloc(datum->values,
+                                 datum->n * sizeof *datum->values);
+        ovsdb_atom_clone(&datum->values[idx], value, type->value_type);
+    }
+}
+
+void
+ovsdb_datum_union(struct ovsdb_datum *a, const struct ovsdb_datum *b,
+                  const struct ovsdb_type *type, bool replace)
+{
+    unsigned int n;
+    size_t bi;
+
+    n = a->n;
+    for (bi = 0; bi < b->n; bi++) {
+        unsigned int ai;
+
+        ai = ovsdb_datum_find_key(a, &b->keys[bi], type->key_type);
+        if (ai == UINT_MAX) {
+            if (n == a->n) {
+                ovsdb_datum_reallocate(a, type, a->n + (b->n - bi));
+            }
+            ovsdb_atom_clone(&a->keys[n], &b->keys[bi], type->key_type);
+            if (type->value_type != OVSDB_TYPE_VOID) {
+                ovsdb_atom_clone(&a->values[n], &b->values[bi],
+                                 type->value_type);
+            }
+            n++;
+        } else if (replace && type->value_type != OVSDB_TYPE_VOID) {
+            ovsdb_atom_destroy(&a->values[ai], type->value_type);
+            ovsdb_atom_clone(&a->values[ai], &b->values[bi],
+                             type->value_type);
+        }
+    }
+    if (n != a->n) {
+        struct ovsdb_error *error;
+        a->n = n;
+        error = ovsdb_datum_sort(a, type);
+        assert(!error);
+    }
+}
+
+void
+ovsdb_datum_subtract(struct ovsdb_datum *a, const struct ovsdb_type *a_type,
+                     const struct ovsdb_datum *b,
+                     const struct ovsdb_type *b_type)
+{
+    bool changed = false;
+    size_t i;
+
+    assert(a_type->key_type == b_type->key_type);
+    assert(a_type->value_type == b_type->value_type
+           || b_type->value_type == OVSDB_TYPE_VOID);
+
+    /* XXX The big-O of this could easily be improved. */
+    for (i = 0; i < a->n; ) {
+        unsigned int idx = ovsdb_datum_find(a, i, b, b_type);
+        if (idx != UINT_MAX) {
+            changed = true;
+            ovsdb_datum_remove_unsafe(a, i, a_type);
+        } else {
+            i++;
+        }
+    }
+    if (changed) {
+        struct ovsdb_error *error = ovsdb_datum_sort(a, a_type);
+        assert(!error);
+    }
+}
+\f
+struct ovsdb_symbol_table {
+    struct shash sh;
+};
+
+struct ovsdb_symbol_table *
+ovsdb_symbol_table_create(void)
+{
+    struct ovsdb_symbol_table *symtab = xmalloc(sizeof *symtab);
+    shash_init(&symtab->sh);
+    return symtab;
+}
+
+void
+ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *symtab)
+{
+    if (symtab) {
+        struct shash_node *node, *next;
+
+        SHASH_FOR_EACH_SAFE (node, next, &symtab->sh) {
+            struct ovsdb_symbol *symbol = node->data;
+            free(symbol);
+            shash_delete(&symtab->sh, node);
+        }
+        shash_destroy(&symtab->sh);
+        free(symtab);
+    }
+}
+
+struct ovsdb_symbol *
+ovsdb_symbol_table_get(const struct ovsdb_symbol_table *symtab,
+                       const char *name)
+{
+    return shash_find_data(&symtab->sh, name);
+}
+
+void
+ovsdb_symbol_table_put(struct ovsdb_symbol_table *symtab, const char *name,
+                       const struct uuid *uuid, bool used)
+{
+    struct ovsdb_symbol *symbol;
+
+    assert(!ovsdb_symbol_table_get(symtab, name));
+    symbol = xmalloc(sizeof *symbol);
+    symbol->uuid = *uuid;
+    symbol->used = used;
+    shash_add(&symtab->sh, name, symbol);
+}
+\f
+/* Extracts a token from the beginning of 's' and returns a pointer just after
+ * the token.  Stores the token itself into '*outp', which the caller is
+ * responsible for freeing (with free()).
+ *
+ * If 's[0]' is a delimiter, the returned token is the empty string.
+ *
+ * A token extends from 's' to the first delimiter, as defined by
+ * ovsdb_token_is_delim(), or until the end of the string.  A delimiter can be
+ * escaped with a backslash, in which case the backslash does not appear in the
+ * output.  Double quotes also cause delimiters to be ignored, but the double
+ * quotes are retained in the output.  (Backslashes inside double quotes are
+ * not removed, either.)
+ */
+char *
+ovsdb_token_parse(const char **s, char **outp)
+{
+    const char *p;
+    struct ds out;
+    bool in_quotes;
+    char *error;
+
+    ds_init(&out);
+    in_quotes = false;
+    for (p = *s; *p != '\0'; ) {
+        int c = *p++;
+        if (c == '\\') {
+            if (in_quotes) {
+                ds_put_char(&out, '\\');
+            }
+            if (!*p) {
+                error = xasprintf("%s: backslash at end of argument", *s);
+                goto error;
+            }
+            ds_put_char(&out, *p++);
+        } else if (!in_quotes && ovsdb_token_is_delim(c)) {
+            p--;
+            break;
+        } else {
+            ds_put_char(&out, c);
+            if (c == '"') {
+                in_quotes = !in_quotes;
+            }
+        }
+    }
+    if (in_quotes) {
+        error = xasprintf("%s: quoted string extends past end of argument",
+                          *s);
+        goto error;
+    }
+    *outp = ds_cstr(&out);
+    *s = p;
+    return NULL;
+
+error:
+    ds_destroy(&out);
+    *outp = NULL;
+    return error;
+}
+
+/* Returns true if 'c' delimits tokens, or if 'c' is 0, and false otherwise. */
+bool
+ovsdb_token_is_delim(unsigned char c)
+{
+    return strchr(":=, []{}", c) != NULL;
+}
diff --git a/lib/ovsdb-data.h b/lib/ovsdb-data.h
new file mode 100644 (file)
index 0000000..0638fa1
--- /dev/null
@@ -0,0 +1,212 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_DATA_H
+#define OVSDB_DATA_H 1
+
+#include <stdlib.h>
+#include "compiler.h"
+#include "ovsdb-types.h"
+
+struct ds;
+struct ovsdb_symbol_table;
+
+/* One value of an atomic type (given by enum ovs_atomic_type). */
+union ovsdb_atom {
+    int64_t integer;
+    double real;
+    bool boolean;
+    char *string;
+    struct uuid uuid;
+};
+
+void ovsdb_atom_init_default(union ovsdb_atom *, enum ovsdb_atomic_type);
+bool ovsdb_atom_is_default(const union ovsdb_atom *, enum ovsdb_atomic_type);
+void ovsdb_atom_clone(union ovsdb_atom *, const union ovsdb_atom *,
+                      enum ovsdb_atomic_type);
+void ovsdb_atom_swap(union ovsdb_atom *, union ovsdb_atom *);
+
+static inline bool
+ovsdb_atom_needs_destruction(enum ovsdb_atomic_type type)
+{
+    return type == OVSDB_TYPE_STRING;
+}
+
+static inline void
+ovsdb_atom_destroy(union ovsdb_atom *atom, enum ovsdb_atomic_type type)
+{
+    if (type == OVSDB_TYPE_STRING) {
+        free(atom->string);
+    }
+}
+
+uint32_t ovsdb_atom_hash(const union ovsdb_atom *, enum ovsdb_atomic_type,
+                         uint32_t basis);
+
+int ovsdb_atom_compare_3way(const union ovsdb_atom *,
+                            const union ovsdb_atom *,
+                            enum ovsdb_atomic_type);
+
+static inline bool ovsdb_atom_equals(const union ovsdb_atom *a,
+                                     const union ovsdb_atom *b,
+                                     enum ovsdb_atomic_type type)
+{
+    return !ovsdb_atom_compare_3way(a, b, type);
+}
+
+struct ovsdb_error *ovsdb_atom_from_json(union ovsdb_atom *,
+                                         enum ovsdb_atomic_type,
+                                         const struct json *,
+                                         const struct ovsdb_symbol_table *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_atom_to_json(const union ovsdb_atom *,
+                                enum ovsdb_atomic_type);
+
+char *ovsdb_atom_from_string(union ovsdb_atom *, enum ovsdb_atomic_type,
+                             const char *)
+    WARN_UNUSED_RESULT;
+void ovsdb_atom_to_string(const union ovsdb_atom *, enum ovsdb_atomic_type,
+                          struct ds *);
+\f
+/* An instance of an OVSDB type (given by struct ovsdb_type).
+ *
+ * - The 'keys' must be unique and in sorted order.  Most functions that modify
+ *   an ovsdb_datum maintain these invariants.  Functions that don't maintain
+ *   the invariants have names that end in "_unsafe".  Use ovsdb_datum_sort()
+ *   to check and restore these invariants.
+ *
+ * - 'n' is constrained by the ovsdb_type's 'n_min' and 'n_max'.
+ *
+ *   If 'n' is nonzero, then 'keys' points to an array of 'n' atoms of the type
+ *   specified by the ovsdb_type's 'key_type'.  (Otherwise, 'keys' should be
+ *   null.)
+ *
+ *   If 'n' is nonzero and the ovsdb_type's 'value_type' is not
+ *   OVSDB_TYPE_VOID, then 'values' points to an array of 'n' atoms of the type
+ *   specified by the 'value_type'.  (Otherwise, 'values' should be null.)
+ *
+ *   Thus, for 'n' > 0, 'keys' will always be nonnull and 'values' will be
+ *   nonnull only for "map" types.
+ */
+struct ovsdb_datum {
+    unsigned int n;             /* Number of 'keys' and 'values'. */
+    union ovsdb_atom *keys;     /* Each of the ovsdb_type's 'key_type'. */
+    union ovsdb_atom *values;   /* Each of the ovsdb_type's 'value_type'. */
+};
+
+/* Basics. */
+void ovsdb_datum_init_empty(struct ovsdb_datum *);
+void ovsdb_datum_init_default(struct ovsdb_datum *, const struct ovsdb_type *);
+bool ovsdb_datum_is_default(const struct ovsdb_datum *,
+                            const struct ovsdb_type *);
+void ovsdb_datum_clone(struct ovsdb_datum *, const struct ovsdb_datum *,
+                       const struct ovsdb_type *);
+void ovsdb_datum_destroy(struct ovsdb_datum *, const struct ovsdb_type *);
+void ovsdb_datum_swap(struct ovsdb_datum *, struct ovsdb_datum *);
+
+/* Checking and maintaining invariants. */
+struct ovsdb_error *ovsdb_datum_sort(struct ovsdb_datum *,
+                                     const struct ovsdb_type *);
+
+/* Type conversion. */
+struct ovsdb_error *ovsdb_datum_from_json(struct ovsdb_datum *,
+                                          const struct ovsdb_type *,
+                                          const struct json *,
+                                          const struct ovsdb_symbol_table *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_datum_to_json(const struct ovsdb_datum *,
+                                 const struct ovsdb_type *);
+
+char *ovsdb_datum_from_string(struct ovsdb_datum *,
+                             const struct ovsdb_type *, const char *)
+    WARN_UNUSED_RESULT;
+void ovsdb_datum_to_string(const struct ovsdb_datum *,
+                           const struct ovsdb_type *, struct ds *);
+
+/* Comparison. */
+uint32_t ovsdb_datum_hash(const struct ovsdb_datum *,
+                          const struct ovsdb_type *, uint32_t basis);
+int ovsdb_datum_compare_3way(const struct ovsdb_datum *,
+                             const struct ovsdb_datum *,
+                             const struct ovsdb_type *);
+bool ovsdb_datum_equals(const struct ovsdb_datum *,
+                        const struct ovsdb_datum *,
+                        const struct ovsdb_type *);
+
+/* Search. */
+unsigned int ovsdb_datum_find_key(const struct ovsdb_datum *,
+                                  const union ovsdb_atom *key,
+                                  enum ovsdb_atomic_type key_type);
+unsigned int ovsdb_datum_find_key_value(const struct ovsdb_datum *,
+                                        const union ovsdb_atom *key,
+                                        enum ovsdb_atomic_type key_type,
+                                        const union ovsdb_atom *value,
+                                        enum ovsdb_atomic_type value_type);
+
+/* Set operations. */
+bool ovsdb_datum_includes_all(const struct ovsdb_datum *,
+                              const struct ovsdb_datum *,
+                              const struct ovsdb_type *);
+bool ovsdb_datum_excludes_all(const struct ovsdb_datum *,
+                              const struct ovsdb_datum *,
+                              const struct ovsdb_type *);
+void ovsdb_datum_union(struct ovsdb_datum *,
+                       const struct ovsdb_datum *,
+                       const struct ovsdb_type *,
+                       bool replace);
+void ovsdb_datum_subtract(struct ovsdb_datum *a,
+                          const struct ovsdb_type *a_type,
+                          const struct ovsdb_datum *b,
+                          const struct ovsdb_type *b_type);
+
+/* Raw operations that may not maintain the invariants. */
+void ovsdb_datum_remove_unsafe(struct ovsdb_datum *, size_t idx,
+                               const struct ovsdb_type *);
+void ovsdb_datum_add_unsafe(struct ovsdb_datum *,
+                            const union ovsdb_atom *key,
+                            const union ovsdb_atom *value,
+                            const struct ovsdb_type *);
+
+/* Type checking. */
+static inline bool
+ovsdb_datum_conforms_to_type(const struct ovsdb_datum *datum,
+                             const struct ovsdb_type *type)
+{
+    return datum->n >= type->n_min && datum->n <= type->n_max;
+}
+\f
+/* A table mapping from names to data items.  Currently the data items are
+ * always UUIDs; perhaps this will be expanded in the future. */
+
+struct ovsdb_symbol {
+    struct uuid uuid;           /* The UUID that the symbol represents. */
+    bool used;                  /* Already used as row UUID? */
+};
+
+struct ovsdb_symbol_table *ovsdb_symbol_table_create(void);
+void ovsdb_symbol_table_destroy(struct ovsdb_symbol_table *);
+struct ovsdb_symbol *ovsdb_symbol_table_get(const struct ovsdb_symbol_table *,
+                                            const char *name);
+void ovsdb_symbol_table_put(struct ovsdb_symbol_table *, const char *name,
+                            const struct uuid *, bool used);
+\f
+/* Tokenization
+ *
+ * Used by ovsdb_atom_from_string() and ovsdb_datum_from_string(). */
+
+char *ovsdb_token_parse(const char **, char **outp) WARN_UNUSED_RESULT;
+bool ovsdb_token_is_delim(unsigned char);
+
+#endif /* ovsdb-data.h */
diff --git a/lib/ovsdb-error.c b/lib/ovsdb-error.c
new file mode 100644 (file)
index 0000000..c0eddf2
--- /dev/null
@@ -0,0 +1,221 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-error.h"
+
+#include <inttypes.h>
+
+#include "backtrace.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "util.h"
+
+struct ovsdb_error {
+    const char *tag;            /* String for "error" member. */
+    char *details;              /* String for "details" member. */
+    char *syntax;               /* String for "syntax" member. */
+    int errno_;                 /* Unix errno value, 0 if none. */
+};
+
+static struct ovsdb_error *
+ovsdb_error_valist(const char *tag, const char *details, va_list args)
+{
+    struct ovsdb_error *error = xmalloc(sizeof *error);
+    error->tag = tag ? tag : "ovsdb error";
+    error->details = details ? xvasprintf(details, args) : NULL;
+    error->syntax = NULL;
+    error->errno_ = 0;
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_error(const char *tag, const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist(tag, details, args);
+    va_end(args);
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_io_error(int errno_, const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist("I/O error", details, args);
+    va_end(args);
+
+    error->errno_ = errno_;
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_syntax_error(const struct json *json, const char *tag,
+                   const char *details, ...)
+{
+    struct ovsdb_error *error;
+    va_list args;
+
+    va_start(args, details);
+    error = ovsdb_error_valist(tag ? tag : "syntax error", details, args);
+    va_end(args);
+
+    if (json) {
+        /* XXX this is much too much information in some cases */
+        error->syntax = json_to_string(json, 0);
+    }
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_wrap_error(struct ovsdb_error *error, const char *details, ...)
+{
+    va_list args;
+    char *msg;
+
+    va_start(args, details);
+    msg = xvasprintf(details, args);
+    va_end(args);
+
+    if (error->details) {
+        char *new = xasprintf("%s: %s", msg, error->details);
+        free(error->details);
+        error->details = new;
+        free(msg);
+    } else {
+        error->details = msg;
+    }
+
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_internal_error(const char *file, int line, const char *details, ...)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    struct backtrace backtrace;
+    struct ovsdb_error *error;
+    va_list args;
+
+    ds_put_format(&ds, "%s:%d:", file, line);
+
+    if (details) {
+        ds_put_char(&ds, ' ');
+        va_start(args, details);
+        ds_put_format_valist(&ds, details, args);
+        va_end(args);
+    }
+
+    backtrace_capture(&backtrace);
+    if (backtrace.n_frames) {
+        int i;
+
+        ds_put_cstr(&ds, " (backtrace:");
+        for (i = 0; i < backtrace.n_frames; i++) {
+            ds_put_format(&ds, " 0x%08"PRIxPTR, backtrace.frames[i]);
+        }
+        ds_put_char(&ds, ')');
+    }
+
+    ds_put_format(&ds, " (%s %s%s)", program_name, VERSION, BUILDNR);
+
+    error = ovsdb_error("internal error", "%s", ds_cstr(&ds));
+
+    ds_destroy(&ds);
+
+    return error;
+}
+
+void
+ovsdb_error_destroy(struct ovsdb_error *error)
+{
+    if (error) {
+        free(error->details);
+        free(error->syntax);
+        free(error);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_error_clone(const struct ovsdb_error *old)
+{
+    if (old) {
+        struct ovsdb_error *new = xmalloc(sizeof *new);
+        new->tag = old->tag;
+        new->details = old->details ? xstrdup(old->details) : NULL;
+        new->syntax = old->syntax ? xstrdup(old->syntax) : NULL;
+        new->errno_ = old->errno_;
+        return new;
+    } else {
+        return NULL;
+    }
+}
+
+static const char *
+ovsdb_errno_string(int error)
+{
+    return error == EOF ? "unexpected end of file" : strerror(error);
+}
+
+struct json *
+ovsdb_error_to_json(const struct ovsdb_error *error)
+{
+    struct json *json = json_object_create();
+    json_object_put_string(json, "error", error->tag);
+    if (error->details) {
+        json_object_put_string(json, "details", error->details);
+    }
+    if (error->syntax) {
+        json_object_put_string(json, "syntax", error->syntax);
+    }
+    if (error->errno_) {
+        json_object_put_string(json, "io-error",
+                               ovsdb_errno_string(error->errno_));
+    }
+    return json;
+}
+
+char *
+ovsdb_error_to_string(const struct ovsdb_error *error)
+{
+    struct ds ds = DS_EMPTY_INITIALIZER;
+    if (error->syntax) {
+        ds_put_format(&ds, "syntax \"%s\": ", error->syntax);
+    }
+    ds_put_cstr(&ds, error->tag);
+    if (error->details) {
+        ds_put_format(&ds, ": %s", error->details);
+    }
+    if (error->errno_) {
+        ds_put_format(&ds, " (%s)", ovsdb_errno_string(error->errno_));
+    }
+    return ds_steal_cstr(&ds);
+}
+
+const char *
+ovsdb_error_get_tag(const struct ovsdb_error *error)
+{
+    return error->tag;
+}
diff --git a/lib/ovsdb-error.h b/lib/ovsdb-error.h
new file mode 100644 (file)
index 0000000..7e2523e
--- /dev/null
@@ -0,0 +1,53 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_ERROR_H
+#define OVSDB_ERROR_H 1
+
+#include "compiler.h"
+
+struct json;
+
+struct ovsdb_error *ovsdb_error(const char *tag, const char *details, ...)
+    PRINTF_FORMAT(2, 3)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_io_error(int error, const char *details, ...)
+    PRINTF_FORMAT(2, 3)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_syntax_error(const struct json *, const char *tag,
+                                       const char *details, ...)
+    PRINTF_FORMAT(3, 4)
+    WARN_UNUSED_RESULT;
+
+struct ovsdb_error *ovsdb_wrap_error(struct ovsdb_error *error,
+                                     const char *details, ...)
+    PRINTF_FORMAT(2, 3);
+
+struct ovsdb_error *ovsdb_internal_error(const char *file, int line,
+                                         const char *details, ...)
+    PRINTF_FORMAT(3, 4)
+    WARN_UNUSED_RESULT;
+#define OVSDB_BUG(MSG) ovsdb_internal_error(__FILE__, __LINE__, "%s", MSG)
+
+void ovsdb_error_destroy(struct ovsdb_error *);
+struct ovsdb_error *ovsdb_error_clone(const struct ovsdb_error *)
+    WARN_UNUSED_RESULT;
+
+char *ovsdb_error_to_string(const struct ovsdb_error *);
+struct json *ovsdb_error_to_json(const struct ovsdb_error *);
+
+const char *ovsdb_error_get_tag(const struct ovsdb_error *);
+
+#endif /* ovsdb-error.h */
diff --git a/lib/ovsdb-idl-provider.h b/lib/ovsdb-idl-provider.h
new file mode 100644 (file)
index 0000000..ed3874d
--- /dev/null
@@ -0,0 +1,77 @@
+/* Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_IDL_PROVIDER_H
+#define OVSDB_IDL_PROVIDER_H 1
+
+#include "hmap.h"
+#include "list.h"
+#include "ovsdb-idl.h"
+#include "ovsdb-types.h"
+#include "shash.h"
+#include "uuid.h"
+
+struct ovsdb_idl_row {
+    struct hmap_node hmap_node; /* In struct ovsdb_idl_table's 'rows'. */
+    struct uuid uuid;           /* Row "_uuid" field. */
+    struct list src_arcs;       /* Forward arcs (ovsdb_idl_arc.src_node). */
+    struct list dst_arcs;       /* Backward arcs (ovsdb_idl_arc.dst_node). */
+    struct ovsdb_idl_table *table; /* Containing table. */
+    struct ovsdb_datum *old;    /* Committed data (null if orphaned). */
+
+    /* Transactional data. */
+    struct ovsdb_datum *new;    /* Modified data (null to delete row). */
+    unsigned long int *prereqs; /* Bitmap of columns to verify in "old". */
+    unsigned long int *written; /* Bitmap of columns from "new" to write. */
+    struct hmap_node txn_node;  /* Node in ovsdb_idl_txn's list. */
+};
+
+struct ovsdb_idl_column {
+    char *name;
+    struct ovsdb_type type;
+    void (*parse)(struct ovsdb_idl_row *, const struct ovsdb_datum *);
+    void (*unparse)(struct ovsdb_idl_row *);
+};
+
+struct ovsdb_idl_table_class {
+    char *name;
+    const struct ovsdb_idl_column *columns;
+    size_t n_columns;
+    size_t allocation_size;
+};
+
+struct ovsdb_idl_table {
+    const struct ovsdb_idl_table_class *class;
+    struct shash columns;    /* Contains "const struct ovsdb_idl_column *"s. */
+    struct hmap rows;        /* Contains "struct ovsdb_idl_row"s. */
+    struct ovsdb_idl *idl;   /* Containing idl. */
+};
+
+struct ovsdb_idl_class {
+    const struct ovsdb_idl_table_class *tables;
+    size_t n_tables;
+};
+
+struct ovsdb_idl_row *ovsdb_idl_get_row_arc(
+    struct ovsdb_idl_row *src,
+    struct ovsdb_idl_table_class *dst_table,
+    const struct uuid *dst_uuid);
+
+void ovsdb_idl_txn_verify(const struct ovsdb_idl_row *,
+                          const struct ovsdb_idl_column *);
+
+struct ovsdb_idl_txn *ovsdb_idl_txn_get(const struct ovsdb_idl_row *);
+
+#endif /* ovsdb-idl-provider.h */
diff --git a/lib/ovsdb-idl.c b/lib/ovsdb-idl.c
new file mode 100644 (file)
index 0000000..2051000
--- /dev/null
@@ -0,0 +1,1613 @@
+/* Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-idl.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "bitmap.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-idl-provider.h"
+#include "poll-loop.h"
+#include "shash.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_idl
+#include "vlog.h"
+
+/* An arc from one idl_row to another.  When row A contains a UUID that
+ * references row B, this is represented by an arc from A (the source) to B
+ * (the destination).
+ *
+ * Arcs from a row to itself are omitted, that is, src and dst are always
+ * different.
+ *
+ * Arcs are never duplicated, that is, even if there are multiple references
+ * from A to B, there is only a single arc from A to B.
+ *
+ * Arcs are directed: an arc from A to B is the converse of an an arc from B to
+ * A.  Both an arc and its converse may both be present, if each row refers
+ * to the other circularly.
+ *
+ * The source and destination row may be in the same table or in different
+ * tables.
+ */
+struct ovsdb_idl_arc {
+    struct list src_node;       /* In src->src_arcs list. */
+    struct list dst_node;       /* In dst->dst_arcs list. */
+    struct ovsdb_idl_row *src;  /* Source row. */
+    struct ovsdb_idl_row *dst;  /* Destination row. */
+};
+
+struct ovsdb_idl {
+    const struct ovsdb_idl_class *class;
+    struct jsonrpc_session *session;
+    struct shash table_by_name;
+    struct ovsdb_idl_table *tables;
+    struct json *monitor_request_id;
+    unsigned int last_monitor_request_seqno;
+    unsigned int change_seqno;
+
+    /* Transaction support. */
+    struct ovsdb_idl_txn *txn;
+    struct hmap outstanding_txns;
+};
+
+struct ovsdb_idl_txn {
+    struct hmap_node hmap_node;
+    struct json *request_id;
+    struct ovsdb_idl *idl;
+    struct hmap txn_rows;
+    enum ovsdb_idl_txn_status status;
+    bool dry_run;
+    struct ds comment;
+
+    /* Increments. */
+    char *inc_table;
+    char *inc_column;
+    struct json *inc_where;
+    unsigned int inc_index;
+    int64_t inc_new_value;
+
+    /* Inserted rows. */
+    struct hmap inserted_rows;
+};
+
+struct ovsdb_idl_txn_insert {
+    struct hmap_node hmap_node; /* In struct ovsdb_idl_txn's inserted_rows. */
+    struct uuid dummy;          /* Dummy UUID used locally. */
+    int op_index;               /* Index into transaction's operation array. */
+    struct uuid real;           /* Real UUID used by database server. */
+};
+
+static struct vlog_rate_limit syntax_rl = VLOG_RATE_LIMIT_INIT(1, 5);
+static struct vlog_rate_limit semantic_rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+static void ovsdb_idl_clear(struct ovsdb_idl *);
+static void ovsdb_idl_send_monitor_request(struct ovsdb_idl *);
+static void ovsdb_idl_parse_update(struct ovsdb_idl *, const struct json *);
+static struct ovsdb_error *ovsdb_idl_parse_update__(struct ovsdb_idl *,
+                                                    const struct json *);
+static void ovsdb_idl_process_update(struct ovsdb_idl_table *,
+                                     const struct uuid *,
+                                     const struct json *old,
+                                     const struct json *new);
+static void ovsdb_idl_insert_row(struct ovsdb_idl_row *, const struct json *);
+static void ovsdb_idl_delete_row(struct ovsdb_idl_row *);
+static void ovsdb_idl_modify_row(struct ovsdb_idl_row *, const struct json *);
+
+static bool ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *);
+static struct ovsdb_idl_row *ovsdb_idl_row_create__(
+    const struct ovsdb_idl_table_class *);
+static struct ovsdb_idl_row *ovsdb_idl_row_create(struct ovsdb_idl_table *,
+                                                  const struct uuid *);
+static void ovsdb_idl_row_destroy(struct ovsdb_idl_row *);
+
+static void ovsdb_idl_row_parse(struct ovsdb_idl_row *);
+static void ovsdb_idl_row_unparse(struct ovsdb_idl_row *);
+static void ovsdb_idl_row_clear_old(struct ovsdb_idl_row *);
+static void ovsdb_idl_row_clear_new(struct ovsdb_idl_row *);
+
+static void ovsdb_idl_txn_abort_all(struct ovsdb_idl *);
+static bool ovsdb_idl_txn_process_reply(struct ovsdb_idl *,
+                                        const struct jsonrpc_msg *msg);
+
+struct ovsdb_idl *
+ovsdb_idl_create(const char *remote, const struct ovsdb_idl_class *class)
+{
+    struct ovsdb_idl *idl;
+    size_t i;
+
+    idl = xzalloc(sizeof *idl);
+    idl->class = class;
+    idl->session = jsonrpc_session_open(remote);
+    shash_init(&idl->table_by_name);
+    idl->tables = xmalloc(class->n_tables * sizeof *idl->tables);
+    for (i = 0; i < class->n_tables; i++) {
+        const struct ovsdb_idl_table_class *tc = &class->tables[i];
+        struct ovsdb_idl_table *table = &idl->tables[i];
+        size_t j;
+
+        assert(!shash_find(&idl->table_by_name, tc->name));
+        shash_add(&idl->table_by_name, tc->name, table);
+        table->class = tc;
+        shash_init(&table->columns);
+        for (j = 0; j < tc->n_columns; j++) {
+            const struct ovsdb_idl_column *column = &tc->columns[j];
+
+            assert(!shash_find(&table->columns, column->name));
+            shash_add(&table->columns, column->name, column);
+        }
+        hmap_init(&table->rows);
+        table->idl = idl;
+    }
+    idl->last_monitor_request_seqno = UINT_MAX;
+    hmap_init(&idl->outstanding_txns);
+
+    return idl;
+}
+
+void
+ovsdb_idl_destroy(struct ovsdb_idl *idl)
+{
+    if (idl) {
+        size_t i;
+
+        assert(!idl->txn);
+        ovsdb_idl_clear(idl);
+        jsonrpc_session_close(idl->session);
+
+        for (i = 0; i < idl->class->n_tables; i++) {
+            struct ovsdb_idl_table *table = &idl->tables[i];
+            shash_destroy(&table->columns);
+            hmap_destroy(&table->rows);
+        }
+        shash_destroy(&idl->table_by_name);
+        free(idl->tables);
+        json_destroy(idl->monitor_request_id);
+        free(idl);
+    }
+}
+
+static void
+ovsdb_idl_clear(struct ovsdb_idl *idl)
+{
+    bool changed = false;
+    size_t i;
+
+    for (i = 0; i < idl->class->n_tables; i++) {
+        struct ovsdb_idl_table *table = &idl->tables[i];
+        struct ovsdb_idl_row *row, *next_row;
+
+        if (hmap_is_empty(&table->rows)) {
+            continue;
+        }
+
+        changed = true;
+        HMAP_FOR_EACH_SAFE (row, next_row, struct ovsdb_idl_row, hmap_node,
+                            &table->rows) {
+            struct ovsdb_idl_arc *arc, *next_arc;
+
+            if (!ovsdb_idl_row_is_orphan(row)) {
+                ovsdb_idl_row_unparse(row);
+            }
+            LIST_FOR_EACH_SAFE (arc, next_arc, struct ovsdb_idl_arc, src_node,
+                                &row->src_arcs) {
+                free(arc);
+            }
+            /* No need to do anything with dst_arcs: some node has those arcs
+             * as forward arcs and will destroy them itself. */
+
+            ovsdb_idl_row_destroy(row);
+        }
+    }
+
+    if (changed) {
+        idl->change_seqno++;
+    }
+}
+
+void
+ovsdb_idl_run(struct ovsdb_idl *idl)
+{
+    int i;
+
+    assert(!idl->txn);
+    jsonrpc_session_run(idl->session);
+    for (i = 0; jsonrpc_session_is_connected(idl->session) && i < 50; i++) {
+        struct jsonrpc_msg *msg, *reply;
+        unsigned int seqno;
+
+        seqno = jsonrpc_session_get_seqno(idl->session);
+        if (idl->last_monitor_request_seqno != seqno) {
+            idl->last_monitor_request_seqno = seqno;
+            ovsdb_idl_txn_abort_all(idl);
+            ovsdb_idl_send_monitor_request(idl);
+            break;
+        }
+
+        msg = jsonrpc_session_recv(idl->session);
+        if (!msg) {
+            break;
+        }
+
+        reply = NULL;
+        if (msg->type == JSONRPC_NOTIFY
+                   && !strcmp(msg->method, "update")
+                   && msg->params->type == JSON_ARRAY
+                   && msg->params->u.array.n == 2
+                   && msg->params->u.array.elems[0]->type == JSON_NULL) {
+            ovsdb_idl_parse_update(idl, msg->params->u.array.elems[1]);
+        } else if (msg->type == JSONRPC_REPLY
+                   && idl->monitor_request_id
+                   && json_equal(idl->monitor_request_id, msg->id)) {
+            json_destroy(idl->monitor_request_id);
+            idl->monitor_request_id = NULL;
+            ovsdb_idl_clear(idl);
+            ovsdb_idl_parse_update(idl, msg->result);
+        } else if (msg->type == JSONRPC_REPLY
+                   && msg->id && msg->id->type == JSON_STRING
+                   && !strcmp(msg->id->u.string, "echo")) {
+            /* It's a reply to our echo request.  Ignore it. */
+        } else if ((msg->type == JSONRPC_ERROR
+                    || msg->type == JSONRPC_REPLY)
+                   && ovsdb_idl_txn_process_reply(idl, msg)) {
+            /* ovsdb_idl_txn_process_reply() did everything needful. */
+        } else {
+            /* This can happen if ovsdb_idl_txn_destroy() is called to destroy
+             * a transaction before we receive the reply, so keep the log level
+             * low. */
+            VLOG_DBG("%s: received unexpected %s message",
+                     jsonrpc_session_get_name(idl->session),
+                     jsonrpc_msg_type_to_string(msg->type));
+        }
+        if (reply) {
+            jsonrpc_session_send(idl->session, reply);
+        }
+        jsonrpc_msg_destroy(msg);
+    }
+}
+
+void
+ovsdb_idl_wait(struct ovsdb_idl *idl)
+{
+    jsonrpc_session_wait(idl->session);
+    jsonrpc_session_recv_wait(idl->session);
+}
+
+unsigned int
+ovsdb_idl_get_seqno(const struct ovsdb_idl *idl)
+{
+    return idl->change_seqno;
+}
+
+bool
+ovsdb_idl_has_ever_connected(const struct ovsdb_idl *idl)
+{
+    return ovsdb_idl_get_seqno(idl) != 0;
+}
+
+void
+ovsdb_idl_force_reconnect(struct ovsdb_idl *idl)
+{
+    jsonrpc_session_force_reconnect(idl->session);
+}
+\f
+static void
+ovsdb_idl_send_monitor_request(struct ovsdb_idl *idl)
+{
+    struct json *monitor_requests;
+    struct jsonrpc_msg *msg;
+    size_t i;
+
+    monitor_requests = json_object_create();
+    for (i = 0; i < idl->class->n_tables; i++) {
+        const struct ovsdb_idl_table *table = &idl->tables[i];
+        const struct ovsdb_idl_table_class *tc = table->class;
+        struct json *monitor_request, *columns;
+        size_t i;
+
+        monitor_request = json_object_create();
+        columns = json_array_create_empty();
+        for (i = 0; i < tc->n_columns; i++) {
+            const struct ovsdb_idl_column *column = &tc->columns[i];
+            json_array_add(columns, json_string_create(column->name));
+        }
+        json_object_put(monitor_request, "columns", columns);
+        json_object_put(monitor_requests, tc->name, monitor_request);
+    }
+
+    json_destroy(idl->monitor_request_id);
+    msg = jsonrpc_create_request(
+        "monitor", json_array_create_2(json_null_create(), monitor_requests),
+        &idl->monitor_request_id);
+    jsonrpc_session_send(idl->session, msg);
+}
+
+static void
+ovsdb_idl_parse_update(struct ovsdb_idl *idl, const struct json *table_updates)
+{
+    struct ovsdb_error *error;
+
+    idl->change_seqno++;
+
+    error = ovsdb_idl_parse_update__(idl, table_updates);
+    if (error) {
+        if (!VLOG_DROP_WARN(&syntax_rl)) {
+            char *s = ovsdb_error_to_string(error);
+            VLOG_WARN_RL(&syntax_rl, "%s", s);
+            free(s);
+        }
+        ovsdb_error_destroy(error);
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_idl_parse_update__(struct ovsdb_idl *idl,
+                         const struct json *table_updates)
+{
+    const struct shash_node *tables_node;
+
+    if (table_updates->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(table_updates, NULL,
+                                  "<table-updates> is not an object");
+    }
+    SHASH_FOR_EACH (tables_node, json_object(table_updates)) {
+        const struct json *table_update = tables_node->data;
+        const struct shash_node *table_node;
+        struct ovsdb_idl_table *table;
+
+        table = shash_find_data(&idl->table_by_name, tables_node->name);
+        if (!table) {
+            return ovsdb_syntax_error(
+                table_updates, NULL,
+                "<table-updates> includes unknown table \"%s\"",
+                tables_node->name);
+        }
+
+        if (table_update->type != JSON_OBJECT) {
+            return ovsdb_syntax_error(table_update, NULL,
+                                      "<table-update> for table \"%s\" is "
+                                      "not an object", table->class->name);
+        }
+        SHASH_FOR_EACH (table_node, json_object(table_update)) {
+            const struct json *row_update = table_node->data;
+            const struct json *old_json, *new_json;
+            struct uuid uuid;
+
+            if (!uuid_from_string(&uuid, table_node->name)) {
+                return ovsdb_syntax_error(table_update, NULL,
+                                          "<table-update> for table \"%s\" "
+                                          "contains bad UUID "
+                                          "\"%s\" as member name",
+                                          table->class->name,
+                                          table_node->name);
+            }
+            if (row_update->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<table-update> for table \"%s\" "
+                                          "contains <row-update> for %s that "
+                                          "is not an object",
+                                          table->class->name,
+                                          table_node->name);
+            }
+
+            old_json = shash_find_data(json_object(row_update), "old");
+            new_json = shash_find_data(json_object(row_update), "new");
+            if (old_json && old_json->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(old_json, NULL,
+                                          "\"old\" <row> is not object");
+            } else if (new_json && new_json->type != JSON_OBJECT) {
+                return ovsdb_syntax_error(new_json, NULL,
+                                          "\"new\" <row> is not object");
+            } else if ((old_json != NULL) + (new_json != NULL)
+                       != shash_count(json_object(row_update))) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<row-update> contains unexpected "
+                                          "member");
+            } else if (!old_json && !new_json) {
+                return ovsdb_syntax_error(row_update, NULL,
+                                          "<row-update> missing \"old\" "
+                                          "and \"new\" members");
+            }
+
+            ovsdb_idl_process_update(table, &uuid, old_json, new_json);
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_idl_row *
+ovsdb_idl_get_row(struct ovsdb_idl_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_idl_row *row;
+
+    HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_idl_row, hmap_node,
+                             uuid_hash(uuid), &table->rows) {
+        if (uuid_equals(&row->uuid, uuid)) {
+            return row;
+        }
+    }
+    return NULL;
+}
+
+static void
+ovsdb_idl_process_update(struct ovsdb_idl_table *table,
+                         const struct uuid *uuid, const struct json *old,
+                         const struct json *new)
+{
+    struct ovsdb_idl_row *row;
+
+    row = ovsdb_idl_get_row(table, uuid);
+    if (!new) {
+        /* Delete row. */
+        if (row && !ovsdb_idl_row_is_orphan(row)) {
+            /* XXX perhaps we should check the 'old' values? */
+            ovsdb_idl_delete_row(row);
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot delete missing row "UUID_FMT" "
+                         "from table %s",
+                         UUID_ARGS(uuid), table->class->name);
+        }
+    } else if (!old) {
+        /* Insert row. */
+        if (!row) {
+            ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new);
+        } else if (ovsdb_idl_row_is_orphan(row)) {
+            ovsdb_idl_insert_row(row, new);
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot add existing row "UUID_FMT" to "
+                         "table %s", UUID_ARGS(uuid), table->class->name);
+            ovsdb_idl_modify_row(row, new);
+        }
+    } else {
+        /* Modify row. */
+        if (row) {
+            /* XXX perhaps we should check the 'old' values? */
+            if (!ovsdb_idl_row_is_orphan(row)) {
+                ovsdb_idl_modify_row(row, new);
+            } else {
+                VLOG_WARN_RL(&semantic_rl, "cannot modify missing but "
+                             "referenced row "UUID_FMT" in table %s",
+                             UUID_ARGS(uuid), table->class->name);
+                ovsdb_idl_insert_row(row, new);
+            }
+        } else {
+            VLOG_WARN_RL(&semantic_rl, "cannot modify missing row "UUID_FMT" "
+                         "in table %s", UUID_ARGS(uuid), table->class->name);
+            ovsdb_idl_insert_row(ovsdb_idl_row_create(table, uuid), new);
+        }
+    }
+}
+
+static void
+ovsdb_idl_row_update(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    struct ovsdb_idl_table *table = row->table;
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, json_object(row_json)) {
+        const char *column_name = node->name;
+        const struct ovsdb_idl_column *column;
+        struct ovsdb_datum datum;
+        struct ovsdb_error *error;
+
+        column = shash_find_data(&table->columns, column_name);
+        if (!column) {
+            VLOG_WARN_RL(&syntax_rl, "unknown column %s updating row "UUID_FMT,
+                         column_name, UUID_ARGS(&row->uuid));
+            continue;
+        }
+
+        error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL);
+        if (!error) {
+            ovsdb_datum_swap(&row->old[column - table->class->columns],
+                             &datum);
+            ovsdb_datum_destroy(&datum, &column->type);
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            VLOG_WARN_RL(&syntax_rl, "error parsing column %s in row "UUID_FMT
+                         " in table %s: %s", column_name,
+                         UUID_ARGS(&row->uuid), table->class->name, s);
+            free(s);
+            ovsdb_error_destroy(error);
+        }
+    }
+}
+
+static bool
+ovsdb_idl_row_is_orphan(const struct ovsdb_idl_row *row)
+{
+    return !row->old;
+}
+
+static void
+ovsdb_idl_row_parse(struct ovsdb_idl_row *row)
+{
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t i;
+
+    for (i = 0; i < class->n_columns; i++) {
+        const struct ovsdb_idl_column *c = &class->columns[i];
+        (c->parse)(row, &row->old[i]);
+    }
+}
+
+static void
+ovsdb_idl_row_unparse(struct ovsdb_idl_row *row)
+{
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t i;
+
+    for (i = 0; i < class->n_columns; i++) {
+        const struct ovsdb_idl_column *c = &class->columns[i];
+        (c->unparse)(row);
+    }
+}
+
+static void
+ovsdb_idl_row_clear_old(struct ovsdb_idl_row *row)
+{
+    assert(row->old == row->new);
+    if (!ovsdb_idl_row_is_orphan(row)) {
+        const struct ovsdb_idl_table_class *class = row->table->class;
+        size_t i;
+
+        for (i = 0; i < class->n_columns; i++) {
+            ovsdb_datum_destroy(&row->old[i], &class->columns[i].type);
+        }
+        free(row->old);
+        row->old = row->new = NULL;
+    }
+}
+
+static void
+ovsdb_idl_row_clear_new(struct ovsdb_idl_row *row)
+{
+    if (row->old != row->new) {
+        if (row->new) {
+            const struct ovsdb_idl_table_class *class = row->table->class;
+            size_t i;
+
+            BITMAP_FOR_EACH_1 (i, class->n_columns, row->written) {
+                ovsdb_datum_destroy(&row->new[i], &class->columns[i].type);
+            }
+            free(row->new);
+            free(row->written);
+            row->written = NULL;
+        }
+        row->new = row->old;
+    }
+}
+
+static void
+ovsdb_idl_row_clear_arcs(struct ovsdb_idl_row *row, bool destroy_dsts)
+{
+    struct ovsdb_idl_arc *arc, *next;
+
+    /* Delete all forward arcs.  If 'destroy_dsts', destroy any orphaned rows
+     * that this causes to be unreferenced. */
+    LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, src_node,
+                        &row->src_arcs) {
+        list_remove(&arc->dst_node);
+        if (destroy_dsts
+            && ovsdb_idl_row_is_orphan(arc->dst)
+            && list_is_empty(&arc->dst->dst_arcs)) {
+            ovsdb_idl_row_destroy(arc->dst);
+        }
+        free(arc);
+    }
+    list_init(&row->src_arcs);
+}
+
+/* Force nodes that reference 'row' to reparse. */
+static void
+ovsdb_idl_row_reparse_backrefs(struct ovsdb_idl_row *row)
+{
+    struct ovsdb_idl_arc *arc, *next;
+
+    /* This is trickier than it looks.  ovsdb_idl_row_clear_arcs() will destroy
+     * 'arc', so we need to use the "safe" variant of list traversal.  However,
+     * calling an ovsdb_idl_column's 'parse' function will add an arc
+     * equivalent to 'arc' to row->arcs.  That could be a problem for
+     * traversal, but it adds it at the beginning of the list to prevent us
+     * from stumbling upon it again.
+     *
+     * (If duplicate arcs were possible then we would need to make sure that
+     * 'next' didn't also point into 'arc''s destination, but we forbid
+     * duplicate arcs.) */
+    LIST_FOR_EACH_SAFE (arc, next, struct ovsdb_idl_arc, dst_node,
+                        &row->dst_arcs) {
+        struct ovsdb_idl_row *ref = arc->src;
+
+        ovsdb_idl_row_unparse(ref);
+        ovsdb_idl_row_clear_arcs(ref, false);
+        ovsdb_idl_row_parse(ref);
+    }
+}
+
+static struct ovsdb_idl_row *
+ovsdb_idl_row_create__(const struct ovsdb_idl_table_class *class)
+{
+    struct ovsdb_idl_row *row = xzalloc(class->allocation_size);
+    list_init(&row->src_arcs);
+    list_init(&row->dst_arcs);
+    hmap_node_nullify(&row->txn_node);
+    return row;
+}
+
+static struct ovsdb_idl_row *
+ovsdb_idl_row_create(struct ovsdb_idl_table *table, const struct uuid *uuid)
+{
+    struct ovsdb_idl_row *row = ovsdb_idl_row_create__(table->class);
+    hmap_insert(&table->rows, &row->hmap_node, uuid_hash(uuid));
+    row->uuid = *uuid;
+    row->table = table;
+    return row;
+}
+
+static void
+ovsdb_idl_row_destroy(struct ovsdb_idl_row *row)
+{
+    if (row) {
+        ovsdb_idl_row_clear_old(row);
+        hmap_remove(&row->table->rows, &row->hmap_node);
+        free(row);
+    }
+}
+
+static void
+ovsdb_idl_insert_row(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t i;
+
+    assert(!row->old && !row->new);
+    row->old = row->new = xmalloc(class->n_columns * sizeof *row->old);
+    for (i = 0; i < class->n_columns; i++) {
+        ovsdb_datum_init_default(&row->old[i], &class->columns[i].type);
+    }
+    ovsdb_idl_row_update(row, row_json);
+    ovsdb_idl_row_parse(row);
+
+    ovsdb_idl_row_reparse_backrefs(row);
+}
+
+static void
+ovsdb_idl_delete_row(struct ovsdb_idl_row *row)
+{
+    ovsdb_idl_row_unparse(row);
+    ovsdb_idl_row_clear_arcs(row, true);
+    ovsdb_idl_row_clear_old(row);
+    if (list_is_empty(&row->dst_arcs)) {
+        ovsdb_idl_row_destroy(row);
+    } else {
+        ovsdb_idl_row_reparse_backrefs(row);
+    }
+}
+
+static void
+ovsdb_idl_modify_row(struct ovsdb_idl_row *row, const struct json *row_json)
+{
+    ovsdb_idl_row_unparse(row);
+    ovsdb_idl_row_clear_arcs(row, true);
+    ovsdb_idl_row_update(row, row_json);
+    ovsdb_idl_row_parse(row);
+}
+
+static bool
+may_add_arc(const struct ovsdb_idl_row *src, const struct ovsdb_idl_row *dst)
+{
+    const struct ovsdb_idl_arc *arc;
+
+    /* No self-arcs. */
+    if (src == dst) {
+        return false;
+    }
+
+    /* No duplicate arcs.
+     *
+     * We only need to test whether the first arc in dst->dst_arcs originates
+     * at 'src', since we add all of the arcs from a given source in a clump
+     * (in a single call to ovsdb_idl_row_parse()) and new arcs are always
+     * added at the front of the dst_arcs list. */
+    if (list_is_empty(&dst->dst_arcs)) {
+        return true;
+    }
+    arc = CONTAINER_OF(dst->dst_arcs.next, struct ovsdb_idl_arc, dst_node);
+    return arc->src != src;
+}
+
+static struct ovsdb_idl_table *
+ovsdb_idl_table_from_class(const struct ovsdb_idl *idl,
+                           const struct ovsdb_idl_table_class *table_class)
+{
+    return &idl->tables[table_class - idl->class->tables];
+}
+
+struct ovsdb_idl_row *
+ovsdb_idl_get_row_arc(struct ovsdb_idl_row *src,
+                      struct ovsdb_idl_table_class *dst_table_class,
+                      const struct uuid *dst_uuid)
+{
+    struct ovsdb_idl *idl = src->table->idl;
+    struct ovsdb_idl_table *dst_table;
+    struct ovsdb_idl_arc *arc;
+    struct ovsdb_idl_row *dst;
+
+    dst_table = ovsdb_idl_table_from_class(idl, dst_table_class);
+    dst = ovsdb_idl_get_row(dst_table, dst_uuid);
+    if (idl->txn) {
+        /* We're being called from ovsdb_idl_txn_write().  We must not update
+         * any arcs, because the transaction will be backed out at commit or
+         * abort time and we don't want our graph screwed up.
+         *
+         * Just return the destination row, if there is one and it has not been
+         * deleted. */
+        if (dst && (hmap_node_is_null(&dst->txn_node) || dst->new)) {
+            return dst;
+        }
+        return NULL;
+    } else {
+        /* We're being called from some other context.  Update the graph. */
+        if (!dst) {
+            dst = ovsdb_idl_row_create(dst_table, dst_uuid);
+        }
+
+        /* Add a new arc, if it wouldn't be a self-arc or a duplicate arc. */
+        if (may_add_arc(src, dst)) {
+            /* The arc *must* be added at the front of the dst_arcs list.  See
+             * ovsdb_idl_row_reparse_backrefs() for details. */
+            arc = xmalloc(sizeof *arc);
+            list_push_front(&src->src_arcs, &arc->src_node);
+            list_push_front(&dst->dst_arcs, &arc->dst_node);
+            arc->src = src;
+            arc->dst = dst;
+        }
+
+        return !ovsdb_idl_row_is_orphan(dst) ? dst : NULL;
+    }
+}
+
+const struct ovsdb_idl_row *
+ovsdb_idl_get_row_for_uuid(const struct ovsdb_idl *idl,
+                           const struct ovsdb_idl_table_class *tc,
+                           const struct uuid *uuid)
+{
+    return ovsdb_idl_get_row(ovsdb_idl_table_from_class(idl, tc), uuid);
+}
+
+static struct ovsdb_idl_row *
+next_real_row(struct ovsdb_idl_table *table, struct hmap_node *node)
+{
+    for (; node; node = hmap_next(&table->rows, node)) {
+        struct ovsdb_idl_row *row;
+
+        row = CONTAINER_OF(node, struct ovsdb_idl_row, hmap_node);
+        if (row->new || !ovsdb_idl_row_is_orphan(row)) {
+            return row;
+        }
+    }
+    return NULL;
+}
+
+const struct ovsdb_idl_row *
+ovsdb_idl_first_row(const struct ovsdb_idl *idl,
+                    const struct ovsdb_idl_table_class *table_class)
+{
+    struct ovsdb_idl_table *table
+        = ovsdb_idl_table_from_class(idl, table_class);
+    return next_real_row(table, hmap_first(&table->rows));
+}
+
+const struct ovsdb_idl_row *
+ovsdb_idl_next_row(const struct ovsdb_idl_row *row)
+{
+    struct ovsdb_idl_table *table = row->table;
+
+    return next_real_row(table, hmap_next(&table->rows, &row->hmap_node));
+}
+\f
+/* Transactions. */
+
+static void ovsdb_idl_txn_complete(struct ovsdb_idl_txn *txn,
+                                   enum ovsdb_idl_txn_status);
+
+const char *
+ovsdb_idl_txn_status_to_string(enum ovsdb_idl_txn_status status)
+{
+    switch (status) {
+    case TXN_UNCHANGED:
+        return "unchanged";
+    case TXN_INCOMPLETE:
+        return "incomplete";
+    case TXN_ABORTED:
+        return "aborted";
+    case TXN_SUCCESS:
+        return "success";
+    case TXN_TRY_AGAIN:
+        return "try again";
+    case TXN_ERROR:
+        return "error";
+    }
+    return "<unknown>";
+}
+
+struct ovsdb_idl_txn *
+ovsdb_idl_txn_create(struct ovsdb_idl *idl)
+{
+    struct ovsdb_idl_txn *txn;
+
+    assert(!idl->txn);
+    idl->txn = txn = xmalloc(sizeof *txn);
+    txn->request_id = NULL;
+    txn->idl = idl;
+    hmap_init(&txn->txn_rows);
+    txn->status = TXN_INCOMPLETE;
+    txn->dry_run = false;
+    ds_init(&txn->comment);
+
+    txn->inc_table = NULL;
+    txn->inc_column = NULL;
+    txn->inc_where = NULL;
+
+    hmap_init(&txn->inserted_rows);
+
+    return txn;
+}
+
+void
+ovsdb_idl_txn_add_comment(struct ovsdb_idl_txn *txn, const char *s)
+{
+    if (txn->comment.length) {
+        ds_put_char(&txn->comment, '\n');
+    }
+    ds_put_cstr(&txn->comment, s);
+}
+
+void
+ovsdb_idl_txn_set_dry_run(struct ovsdb_idl_txn *txn)
+{
+    txn->dry_run = true;
+}
+
+void
+ovsdb_idl_txn_increment(struct ovsdb_idl_txn *txn, const char *table,
+                        const char *column, const struct json *where)
+{
+    assert(!txn->inc_table);
+    txn->inc_table = xstrdup(table);
+    txn->inc_column = xstrdup(column);
+    txn->inc_where = where ? json_clone(where) : json_array_create_empty();
+}
+
+void
+ovsdb_idl_txn_destroy(struct ovsdb_idl_txn *txn)
+{
+    struct ovsdb_idl_txn_insert *insert, *next;
+
+    json_destroy(txn->request_id);
+    if (txn->status == TXN_INCOMPLETE) {
+        hmap_remove(&txn->idl->outstanding_txns, &txn->hmap_node);
+    }
+    ovsdb_idl_txn_abort(txn);
+    ds_destroy(&txn->comment);
+    free(txn->inc_table);
+    free(txn->inc_column);
+    json_destroy(txn->inc_where);
+    HMAP_FOR_EACH_SAFE (insert, next, struct ovsdb_idl_txn_insert, hmap_node,
+                        &txn->inserted_rows) {
+        free(insert);
+    }
+    hmap_destroy(&txn->inserted_rows);
+    free(txn);
+}
+
+void
+ovsdb_idl_txn_wait(const struct ovsdb_idl_txn *txn)
+{
+    if (txn->status != TXN_INCOMPLETE) {
+        poll_immediate_wake();
+    }
+}
+
+static struct json *
+where_uuid_equals(const struct uuid *uuid)
+{
+    return
+        json_array_create_1(
+            json_array_create_3(
+                json_string_create("_uuid"),
+                json_string_create("=="),
+                json_array_create_2(
+                    json_string_create("uuid"),
+                    json_string_create_nocopy(
+                        xasprintf(UUID_FMT, UUID_ARGS(uuid))))));
+}
+
+static char *
+uuid_name_from_uuid(const struct uuid *uuid)
+{
+    char *name;
+    char *p;
+
+    name = xasprintf("row"UUID_FMT, UUID_ARGS(uuid));
+    for (p = name; *p != '\0'; p++) {
+        if (*p == '-') {
+            *p = '_';
+        }
+    }
+
+    return name;
+}
+
+static const struct ovsdb_idl_row *
+ovsdb_idl_txn_get_row(const struct ovsdb_idl_txn *txn, const struct uuid *uuid)
+{
+    const struct ovsdb_idl_row *row;
+
+    HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_idl_row, txn_node,
+                             uuid_hash(uuid), &txn->txn_rows) {
+        if (uuid_equals(&row->uuid, uuid)) {
+            return row;
+        }
+    }
+    return NULL;
+}
+
+/* XXX there must be a cleaner way to do this */
+static struct json *
+substitute_uuids(struct json *json, const struct ovsdb_idl_txn *txn)
+{
+    if (json->type == JSON_ARRAY) {
+        struct uuid uuid;
+        size_t i;
+
+        if (json->u.array.n == 2
+            && json->u.array.elems[0]->type == JSON_STRING
+            && json->u.array.elems[1]->type == JSON_STRING
+            && !strcmp(json->u.array.elems[0]->u.string, "uuid")
+            && uuid_from_string(&uuid, json->u.array.elems[1]->u.string)) {
+            const struct ovsdb_idl_row *row;
+
+            row = ovsdb_idl_txn_get_row(txn, &uuid);
+            if (row && !row->old && row->new) {
+                json_destroy(json);
+
+                return json_array_create_2(
+                    json_string_create("named-uuid"),
+                    json_string_create_nocopy(uuid_name_from_uuid(&uuid)));
+            }
+        }
+
+        for (i = 0; i < json->u.array.n; i++) {
+            json->u.array.elems[i] = substitute_uuids(json->u.array.elems[i],
+                                                      txn);
+        }
+    } else if (json->type == JSON_OBJECT) {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, json_object(json)) {
+            node->data = substitute_uuids(node->data, txn);
+        }
+    }
+    return json;
+}
+
+static void
+ovsdb_idl_txn_disassemble(struct ovsdb_idl_txn *txn)
+{
+    struct ovsdb_idl_row *row, *next;
+
+    /* This must happen early.  Otherwise, ovsdb_idl_row_parse() will call an
+     * ovsdb_idl_column's 'parse' function, which will call
+     * ovsdb_idl_get_row_arc(), which will seen that the IDL is in a
+     * transaction and fail to update the graph.  */
+    txn->idl->txn = NULL;
+
+    HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_idl_row, txn_node,
+                        &txn->txn_rows) {
+        if (row->old) {
+            if (row->written) {
+                ovsdb_idl_row_unparse(row);
+                ovsdb_idl_row_clear_arcs(row, false);
+                ovsdb_idl_row_parse(row);
+            }
+        } else {
+            ovsdb_idl_row_unparse(row);
+        }
+        ovsdb_idl_row_clear_new(row);
+
+        free(row->prereqs);
+        row->prereqs = NULL;
+
+        free(row->written);
+        row->written = NULL;
+
+        hmap_remove(&txn->txn_rows, &row->txn_node);
+        hmap_node_nullify(&row->txn_node);
+        if (!row->old) {
+            hmap_remove(&row->table->rows, &row->hmap_node);
+            free(row);
+        }
+    }
+    hmap_destroy(&txn->txn_rows);
+    hmap_init(&txn->txn_rows);
+}
+
+enum ovsdb_idl_txn_status
+ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn)
+{
+    struct ovsdb_idl_row *row;
+    struct json *operations;
+    bool any_updates;
+
+    if (txn != txn->idl->txn) {
+        return txn->status;
+    }
+
+    operations = json_array_create_empty();
+
+    /* Add prerequisites and declarations of new rows. */
+    HMAP_FOR_EACH (row, struct ovsdb_idl_row, txn_node, &txn->txn_rows) {
+        /* XXX check that deleted rows exist even if no prereqs? */
+        if (row->prereqs) {
+            const struct ovsdb_idl_table_class *class = row->table->class;
+            size_t n_columns = class->n_columns;
+            struct json *op, *columns, *row_json;
+            size_t idx;
+
+            op = json_object_create();
+            json_array_add(operations, op);
+            json_object_put_string(op, "op", "wait");
+            json_object_put_string(op, "table", class->name);
+            json_object_put(op, "timeout", json_integer_create(0));
+            json_object_put(op, "where", where_uuid_equals(&row->uuid));
+            json_object_put_string(op, "until", "==");
+            columns = json_array_create_empty();
+            json_object_put(op, "columns", columns);
+            row_json = json_object_create();
+            json_object_put(op, "rows", json_array_create_1(row_json));
+
+            BITMAP_FOR_EACH_1 (idx, n_columns, row->prereqs) {
+                const struct ovsdb_idl_column *column = &class->columns[idx];
+                json_array_add(columns, json_string_create(column->name));
+                json_object_put(row_json, column->name,
+                                ovsdb_datum_to_json(&row->old[idx],
+                                                    &column->type));
+            }
+        }
+        if (row->new && !row->old) {
+            struct json *op;
+
+            op = json_object_create();
+            json_array_add(operations, op);
+            json_object_put_string(op, "op", "declare");
+            json_object_put(op, "uuid-name",
+                            json_string_create_nocopy(
+                                uuid_name_from_uuid(&row->uuid)));
+        }
+    }
+
+    /* Add updates. */
+    any_updates = false;
+    HMAP_FOR_EACH (row, struct ovsdb_idl_row, txn_node, &txn->txn_rows) {
+        const struct ovsdb_idl_table_class *class = row->table->class;
+
+        if (row->old == row->new) {
+            continue;
+        } else if (!row->new) {
+            struct json *op = json_object_create();
+            json_object_put_string(op, "op", "delete");
+            json_object_put_string(op, "table", class->name);
+            json_object_put(op, "where", where_uuid_equals(&row->uuid));
+            json_array_add(operations, op);
+            any_updates = true;
+        } else {
+            struct json *row_json;
+            struct json *op;
+            size_t idx;
+
+            op = json_object_create();
+            json_object_put_string(op, "op", row->old ? "update" : "insert");
+            json_object_put_string(op, "table", class->name);
+            if (row->old) {
+                json_object_put(op, "where", where_uuid_equals(&row->uuid));
+            } else {
+                struct ovsdb_idl_txn_insert *insert;
+
+                json_object_put(op, "uuid-name",
+                                json_string_create_nocopy(
+                                    uuid_name_from_uuid(&row->uuid)));
+
+                insert = xmalloc(sizeof *insert);
+                insert->dummy = row->uuid;
+                insert->op_index = operations->u.array.n;
+                uuid_zero(&insert->real);
+                hmap_insert(&txn->inserted_rows, &insert->hmap_node,
+                            uuid_hash(&insert->dummy));
+            }
+            row_json = json_object_create();
+            json_object_put(op, "row", row_json);
+
+            BITMAP_FOR_EACH_1 (idx, class->n_columns, row->written) {
+                const struct ovsdb_idl_column *column = &class->columns[idx];
+
+                if (row->old
+                    ? !ovsdb_datum_equals(&row->old[idx], &row->new[idx],
+                                          &column->type)
+                    : !ovsdb_datum_is_default(&row->new[idx], &column->type)) {
+                    json_object_put(row_json, column->name,
+                                    substitute_uuids(
+                                        ovsdb_datum_to_json(&row->new[idx],
+                                                            &column->type),
+                                        txn));
+                }
+            }
+
+            if (!row->old || !shash_is_empty(json_object(row_json))) {
+                json_array_add(operations, op);
+                any_updates = true;
+            } else {
+                json_destroy(op);
+            }
+        }
+    }
+
+    /* Add increment. */
+    if (txn->inc_table && any_updates) {
+        struct json *op;
+
+        txn->inc_index = operations->u.array.n;
+
+        op = json_object_create();
+        json_object_put_string(op, "op", "mutate");
+        json_object_put_string(op, "table", txn->inc_table);
+        json_object_put(op, "where",
+                        substitute_uuids(json_clone(txn->inc_where), txn));
+        json_object_put(op, "mutations",
+                        json_array_create_1(
+                            json_array_create_3(
+                                json_string_create(txn->inc_column),
+                                json_string_create("+="),
+                                json_integer_create(1))));
+        json_array_add(operations, op);
+
+        op = json_object_create();
+        json_object_put_string(op, "op", "select");
+        json_object_put_string(op, "table", txn->inc_table);
+        json_object_put(op, "where",
+                        substitute_uuids(json_clone(txn->inc_where), txn));
+        json_object_put(op, "columns",
+                        json_array_create_1(json_string_create(
+                                                txn->inc_column)));
+        json_array_add(operations, op);
+    }
+
+    if (txn->comment.length) {
+        struct json *op = json_object_create();
+        json_object_put_string(op, "op", "comment");
+        json_object_put_string(op, "comment", ds_cstr(&txn->comment));
+        json_array_add(operations, op);
+    }
+
+    if (txn->dry_run) {
+        struct json *op = json_object_create();
+        json_object_put_string(op, "op", "abort");
+        json_array_add(operations, op);
+    }
+
+    if (!any_updates) {
+        txn->status = TXN_UNCHANGED;
+        json_destroy(operations);
+    } else if (!jsonrpc_session_send(
+                   txn->idl->session,
+                   jsonrpc_create_request(
+                       "transact", operations, &txn->request_id))) {
+        hmap_insert(&txn->idl->outstanding_txns, &txn->hmap_node,
+                    json_hash(txn->request_id, 0));
+    } else {
+        txn->status = TXN_INCOMPLETE;
+    }
+
+    ovsdb_idl_txn_disassemble(txn);
+    return txn->status;
+}
+
+int64_t
+ovsdb_idl_txn_get_increment_new_value(const struct ovsdb_idl_txn *txn)
+{
+    assert(txn->status == TXN_SUCCESS);
+    return txn->inc_new_value;
+}
+
+void
+ovsdb_idl_txn_abort(struct ovsdb_idl_txn *txn)
+{
+    ovsdb_idl_txn_disassemble(txn);
+    if (txn->status == TXN_INCOMPLETE) {
+        txn->status = TXN_ABORTED;
+    }
+}
+
+/* For transaction 'txn' that completed successfully, finds and returns the
+ * permanent UUID that the database assigned to a newly inserted row, given the
+ * 'uuid' that ovsdb_idl_txn_insert() assigned locally to that row.
+ *
+ * Returns NULL if 'uuid' is not a UUID assigned by ovsdb_idl_txn_insert() or
+ * if it was assigned by that function and then deleted by
+ * ovsdb_idl_txn_delete() within the same transaction.  (Rows that are inserted
+ * and then deleted within a single transaction are never sent to the database
+ * server, so it never assigns them a permanent UUID.) */
+const struct uuid *
+ovsdb_idl_txn_get_insert_uuid(const struct ovsdb_idl_txn *txn,
+                              const struct uuid *uuid)
+{
+    const struct ovsdb_idl_txn_insert *insert;
+
+    assert(txn->status == TXN_SUCCESS || txn->status == TXN_UNCHANGED);
+    HMAP_FOR_EACH_IN_BUCKET (insert, struct ovsdb_idl_txn_insert, hmap_node,
+                             uuid_hash(uuid), &txn->inserted_rows) {
+        if (uuid_equals(uuid, &insert->dummy)) {
+            return &insert->real;
+        }
+    }
+    return NULL;
+}
+
+static void
+ovsdb_idl_txn_complete(struct ovsdb_idl_txn *txn,
+                       enum ovsdb_idl_txn_status status)
+{
+    txn->status = status;
+    hmap_remove(&txn->idl->outstanding_txns, &txn->hmap_node);
+}
+
+void
+ovsdb_idl_txn_read(const struct ovsdb_idl_row *row,
+                   const struct ovsdb_idl_column *column,
+                   struct ovsdb_datum *datum)
+{
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t column_idx = column - class->columns;
+
+    assert(row->new != NULL);
+    if (row->written && bitmap_is_set(row->written, column_idx)) {
+        ovsdb_datum_clone(datum, &row->new[column_idx], &column->type);
+    } else if (row->old) {
+        ovsdb_datum_clone(datum, &row->old[column_idx], &column->type);
+    } else {
+        ovsdb_datum_init_default(datum, &column->type);
+    }
+}
+
+void
+ovsdb_idl_txn_write(const struct ovsdb_idl_row *row_,
+                    const struct ovsdb_idl_column *column,
+                    struct ovsdb_datum *datum)
+{
+    struct ovsdb_idl_row *row = (struct ovsdb_idl_row *) row_;
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t column_idx = column - class->columns;
+
+    assert(row->new != NULL);
+    if (hmap_node_is_null(&row->txn_node)) {
+        hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node,
+                    uuid_hash(&row->uuid));
+    }
+    if (row->old == row->new) {
+        row->new = xmalloc(class->n_columns * sizeof *row->new);
+    }
+    if (!row->written) {
+        row->written = bitmap_allocate(class->n_columns);
+    }
+    if (bitmap_is_set(row->written, column_idx)) {
+        ovsdb_datum_destroy(&row->new[column_idx], &column->type);
+    } else {
+        bitmap_set1(row->written, column_idx);
+    }
+    row->new[column_idx] = *datum;
+    (column->unparse)(row);
+    (column->parse)(row, &row->new[column_idx]);
+}
+
+void
+ovsdb_idl_txn_verify(const struct ovsdb_idl_row *row_,
+                     const struct ovsdb_idl_column *column)
+{
+    struct ovsdb_idl_row *row = (struct ovsdb_idl_row *) row_;
+    const struct ovsdb_idl_table_class *class = row->table->class;
+    size_t column_idx = column - class->columns;
+
+    assert(row->new != NULL);
+    if (!row->old
+        || (row->written && bitmap_is_set(row->written, column_idx))) {
+        return;
+    }
+
+    if (hmap_node_is_null(&row->txn_node)) {
+        hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node,
+                    uuid_hash(&row->uuid));
+    }
+    if (!row->prereqs) {
+        row->prereqs = bitmap_allocate(class->n_columns);
+    }
+    bitmap_set1(row->prereqs, column_idx);
+}
+
+void
+ovsdb_idl_txn_delete(const struct ovsdb_idl_row *row_)
+{
+    struct ovsdb_idl_row *row = (struct ovsdb_idl_row *) row_;
+
+    assert(row->new != NULL);
+    if (!row->old) {
+        ovsdb_idl_row_unparse(row);
+        ovsdb_idl_row_clear_new(row);
+        assert(!row->prereqs);
+        hmap_remove(&row->table->rows, &row->hmap_node);
+        hmap_remove(&row->table->idl->txn->txn_rows, &row->txn_node);
+        free(row);
+        return;
+    }
+    if (hmap_node_is_null(&row->txn_node)) {
+        hmap_insert(&row->table->idl->txn->txn_rows, &row->txn_node,
+                    uuid_hash(&row->uuid));
+    }
+    ovsdb_idl_row_clear_new(row);
+    row->new = NULL;
+}
+
+const struct ovsdb_idl_row *
+ovsdb_idl_txn_insert(struct ovsdb_idl_txn *txn,
+                     const struct ovsdb_idl_table_class *class)
+{
+    struct ovsdb_idl_row *row = ovsdb_idl_row_create__(class);
+    uuid_generate(&row->uuid);
+    row->table = ovsdb_idl_table_from_class(txn->idl, class);
+    row->new = xmalloc(class->n_columns * sizeof *row->new);
+    row->written = bitmap_allocate(class->n_columns);
+    hmap_insert(&row->table->rows, &row->hmap_node, uuid_hash(&row->uuid));
+    hmap_insert(&txn->txn_rows, &row->txn_node, uuid_hash(&row->uuid));
+    return row;
+}
+
+static void
+ovsdb_idl_txn_abort_all(struct ovsdb_idl *idl)
+{
+    struct ovsdb_idl_txn *txn;
+
+    HMAP_FOR_EACH (txn, struct ovsdb_idl_txn, hmap_node,
+                   &idl->outstanding_txns) {
+        ovsdb_idl_txn_complete(txn, TXN_TRY_AGAIN);
+    }
+}
+
+static struct ovsdb_idl_txn *
+ovsdb_idl_txn_find(struct ovsdb_idl *idl, const struct json *id)
+{
+    struct ovsdb_idl_txn *txn;
+
+    HMAP_FOR_EACH_WITH_HASH (txn, struct ovsdb_idl_txn, hmap_node,
+                             json_hash(id, 0), &idl->outstanding_txns) {
+        if (json_equal(id, txn->request_id)) {
+            return txn;
+        }
+    }
+    return NULL;
+}
+
+static bool
+check_json_type(const struct json *json, enum json_type type, const char *name)
+{
+    if (!json) {
+        VLOG_WARN_RL(&syntax_rl, "%s is missing", name);
+        return false;
+    } else if (json->type != type) {
+        VLOG_WARN_RL(&syntax_rl, "%s is %s instead of %s",
+                     name, json_type_to_string(json->type),
+                     json_type_to_string(type));
+        return false;
+    } else {
+        return true;
+    }
+}
+
+static bool
+ovsdb_idl_txn_process_inc_reply(struct ovsdb_idl_txn *txn,
+                                const struct json_array *results)
+{
+    struct json *count, *rows, *row, *column;
+    struct shash *mutate, *select;
+
+    if (txn->inc_index + 2 > results->n) {
+        VLOG_WARN_RL(&syntax_rl, "reply does not contain enough operations "
+                     "for increment (has %u, needs %u)",
+                     results->n, txn->inc_index + 2);
+        return false;
+    }
+
+    /* We know that this is a JSON object because the loop in
+     * ovsdb_idl_txn_process_reply() checked. */
+    mutate = json_object(results->elems[txn->inc_index]);
+    count = shash_find_data(mutate, "count");
+    if (!check_json_type(count, JSON_INTEGER, "\"mutate\" reply \"count\"")) {
+        return false;
+    }
+    if (count->u.integer != 1) {
+        VLOG_WARN_RL(&syntax_rl,
+                     "\"mutate\" reply \"count\" is %"PRId64" instead of 1",
+                     count->u.integer);
+        return false;
+    }
+
+    select = json_object(results->elems[txn->inc_index + 1]);
+    rows = shash_find_data(select, "rows");
+    if (!check_json_type(rows, JSON_ARRAY, "\"select\" reply \"rows\"")) {
+        return false;
+    }
+    if (rows->u.array.n != 1) {
+        VLOG_WARN_RL(&syntax_rl, "\"select\" reply \"rows\" has %u elements "
+                     "instead of 1",
+                     rows->u.array.n);
+        return false;
+    }
+    row = rows->u.array.elems[0];
+    if (!check_json_type(row, JSON_OBJECT, "\"select\" reply row")) {
+        return false;
+    }
+    column = shash_find_data(json_object(row), txn->inc_column);
+    if (!check_json_type(column, JSON_INTEGER,
+                         "\"select\" reply inc column")) {
+        return false;
+    }
+    txn->inc_new_value = column->u.integer;
+    return true;
+}
+
+static bool
+ovsdb_idl_txn_process_insert_reply(struct ovsdb_idl_txn_insert *insert,
+                                   const struct json_array *results)
+{
+    struct ovsdb_error *error;
+    struct json *json_uuid;
+    union ovsdb_atom uuid;
+    struct shash *reply;
+
+    if (insert->op_index >= results->n) {
+        VLOG_WARN_RL(&syntax_rl, "reply does not contain enough operations "
+                     "for insert (has %u, needs %u)",
+                     results->n, insert->op_index);
+        return false;
+    }
+
+    /* We know that this is a JSON object because the loop in
+     * ovsdb_idl_txn_process_reply() checked. */
+    reply = json_object(results->elems[insert->op_index]);
+    json_uuid = shash_find_data(reply, "uuid");
+    if (!check_json_type(json_uuid, JSON_ARRAY, "\"insert\" reply \"uuid\"")) {
+        return false;
+    }
+
+    error = ovsdb_atom_from_json(&uuid, OVSDB_TYPE_UUID, json_uuid, NULL);
+    if (error) {
+        char *s = ovsdb_error_to_string(error);
+        VLOG_WARN_RL(&syntax_rl, "\"insert\" reply \"uuid\" is not a JSON "
+                     "UUID: %s", s);
+        free(s);
+        return false;
+    }
+
+    insert->real = uuid.uuid;
+
+    return true;
+}
+
+static bool
+ovsdb_idl_txn_process_reply(struct ovsdb_idl *idl,
+                            const struct jsonrpc_msg *msg)
+{
+    struct ovsdb_idl_txn *txn;
+    enum ovsdb_idl_txn_status status;
+
+    txn = ovsdb_idl_txn_find(idl, msg->id);
+    if (!txn) {
+        return false;
+    }
+
+    if (msg->type == JSONRPC_ERROR) {
+        status = TXN_ERROR;
+    } else if (msg->result->type != JSON_ARRAY) {
+        VLOG_WARN_RL(&syntax_rl, "reply to \"transact\" is not JSON array");
+        status = TXN_ERROR;
+    } else {
+        struct json_array *ops = &msg->result->u.array;
+        int hard_errors = 0;
+        int soft_errors = 0;
+        size_t i;
+
+        for (i = 0; i < ops->n; i++) {
+            struct json *op = ops->elems[i];
+
+            if (op->type == JSON_NULL) {
+                /* This isn't an error in itself but indicates that some prior
+                 * operation failed, so make sure that we know about it. */
+                soft_errors++;
+            } else if (op->type == JSON_OBJECT) {
+                struct json *error;
+
+                error = shash_find_data(json_object(op), "error");
+                if (error) {
+                    if (error->type == JSON_STRING) {
+                        if (!strcmp(error->u.string, "timed out")) {
+                            soft_errors++;
+                        } else if (strcmp(error->u.string, "aborted")) {
+                            hard_errors++;
+                        }
+                    } else {
+                        hard_errors++;
+                        VLOG_WARN_RL(&syntax_rl,
+                                     "\"error\" in reply is not JSON string");
+                    }
+                }
+            } else {
+                hard_errors++;
+                VLOG_WARN_RL(&syntax_rl,
+                             "operation reply is not JSON null or object");
+            }
+        }
+
+        if (!soft_errors && !hard_errors) {
+            struct ovsdb_idl_txn_insert *insert;
+
+            if (txn->inc_table && !ovsdb_idl_txn_process_inc_reply(txn, ops)) {
+                hard_errors++;
+            }
+
+            HMAP_FOR_EACH (insert, struct ovsdb_idl_txn_insert, hmap_node,
+                           &txn->inserted_rows) {
+                if (!ovsdb_idl_txn_process_insert_reply(insert, ops)) {
+                    hard_errors++;
+                }
+            }
+        }
+
+        status = (hard_errors ? TXN_ERROR
+                  : soft_errors ? TXN_TRY_AGAIN
+                  : TXN_SUCCESS);
+    }
+
+    ovsdb_idl_txn_complete(txn, status);
+    return true;
+}
+
+struct ovsdb_idl_txn *
+ovsdb_idl_txn_get(const struct ovsdb_idl_row *row)
+{
+    struct ovsdb_idl_txn *txn = row->table->idl->txn;
+    assert(txn != NULL);
+    return txn;
+}
diff --git a/lib/ovsdb-idl.h b/lib/ovsdb-idl.h
new file mode 100644 (file)
index 0000000..3b8582d
--- /dev/null
@@ -0,0 +1,84 @@
+/* Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_IDL_H
+#define OVSDB_IDL_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+
+struct json;
+struct ovsdb_datum;
+struct ovsdb_idl_class;
+struct ovsdb_idl_column;
+struct ovsdb_idl_table_class;
+struct uuid;
+
+struct ovsdb_idl *ovsdb_idl_create(const char *remote,
+                                   const struct ovsdb_idl_class *);
+void ovsdb_idl_destroy(struct ovsdb_idl *);
+
+void ovsdb_idl_run(struct ovsdb_idl *);
+void ovsdb_idl_wait(struct ovsdb_idl *);
+
+unsigned int ovsdb_idl_get_seqno(const struct ovsdb_idl *);
+bool ovsdb_idl_has_ever_connected(const struct ovsdb_idl *);
+void ovsdb_idl_force_reconnect(struct ovsdb_idl *);
+
+const struct ovsdb_idl_row *ovsdb_idl_get_row_for_uuid(
+    const struct ovsdb_idl *, const struct ovsdb_idl_table_class *,
+    const struct uuid *);
+const struct ovsdb_idl_row *ovsdb_idl_first_row(
+    const struct ovsdb_idl *, const struct ovsdb_idl_table_class *);
+const struct ovsdb_idl_row *ovsdb_idl_next_row(const struct ovsdb_idl_row *);
+
+enum ovsdb_idl_txn_status {
+    TXN_UNCHANGED,              /* Transaction didn't include any changes. */
+    TXN_INCOMPLETE,             /* Commit in progress, please wait. */
+    TXN_ABORTED,                /* ovsdb_idl_txn_abort() called. */
+    TXN_SUCCESS,                /* Commit successful. */
+    TXN_TRY_AGAIN,              /* Commit failed because a "verify" operation
+                                 * reported an inconsistency, due to a network
+                                 * problem, or other transient failure. */
+    TXN_ERROR                   /* Commit failed due to a hard error. */
+};
+
+const char *ovsdb_idl_txn_status_to_string(enum ovsdb_idl_txn_status);
+
+struct ovsdb_idl_txn *ovsdb_idl_txn_create(struct ovsdb_idl *);
+void ovsdb_idl_txn_add_comment(struct ovsdb_idl_txn *, const char *);
+void ovsdb_idl_txn_set_dry_run(struct ovsdb_idl_txn *);
+void ovsdb_idl_txn_increment(struct ovsdb_idl_txn *, const char *table,
+                             const char *column, const struct json *where);
+void ovsdb_idl_txn_destroy(struct ovsdb_idl_txn *);
+void ovsdb_idl_txn_wait(const struct ovsdb_idl_txn *);
+enum ovsdb_idl_txn_status ovsdb_idl_txn_commit(struct ovsdb_idl_txn *);
+void ovsdb_idl_txn_abort(struct ovsdb_idl_txn *);
+
+int64_t ovsdb_idl_txn_get_increment_new_value(const struct ovsdb_idl_txn *);
+const struct uuid *ovsdb_idl_txn_get_insert_uuid(const struct ovsdb_idl_txn *,
+                                                 const struct uuid *);
+
+void ovsdb_idl_txn_read(const struct ovsdb_idl_row *,
+                        const struct ovsdb_idl_column *,
+                        struct ovsdb_datum *);
+void ovsdb_idl_txn_write(const struct ovsdb_idl_row *,
+                         const struct ovsdb_idl_column *,
+                         struct ovsdb_datum *);
+void ovsdb_idl_txn_delete(const struct ovsdb_idl_row *);
+const struct ovsdb_idl_row *ovsdb_idl_txn_insert(
+    struct ovsdb_idl_txn *, const struct ovsdb_idl_table_class *);
+
+#endif /* ovsdb-idl.h */
diff --git a/lib/ovsdb-parser.c b/lib/ovsdb-parser.c
new file mode 100644 (file)
index 0000000..2a4c3d9
--- /dev/null
@@ -0,0 +1,166 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-parser.h"
+
+#include <ctype.h>
+#include <stdarg.h>
+
+#include "ovsdb-error.h"
+
+void
+ovsdb_parser_init(struct ovsdb_parser *parser, const struct json *json,
+                  const char *name, ...)
+{
+    va_list args;
+
+    va_start(args, name);
+    parser->name = xvasprintf(name, args);
+    va_end(args);
+
+    svec_init(&parser->used);
+    parser->error = NULL;
+
+    parser->json = (json && json->type == JSON_OBJECT ? json : NULL);
+    if (!parser->json) {
+        ovsdb_parser_raise_error(parser, "Object expected.");
+    }
+}
+
+bool
+ovsdb_parser_is_id(const char *string)
+{
+    unsigned char c;
+
+    c = *string;
+    if (!isalpha(c) && c != '_') {
+        return false;
+    }
+
+    for (;;) {
+        c = *++string;
+        if (c == '\0') {
+            return true;
+        } else if (!isalpha(c) && !isdigit(c) && c != '_') {
+            return false;
+        }
+    }
+}
+
+const struct json *
+ovsdb_parser_member(struct ovsdb_parser *parser, const char *name,
+                    enum ovsdb_parser_types types)
+{
+    struct json *value;
+
+    if (!parser->json) {
+        return NULL;
+    }
+
+    value = shash_find_data(json_object(parser->json), name);
+    if (!value) {
+        if (!(types & OP_OPTIONAL)) {
+            ovsdb_parser_raise_error(parser,
+                                     "Required '%s' member is missing.", name);
+        }
+        return NULL;
+    }
+
+    if ((value->type >= 0 && value->type < JSON_N_TYPES
+         && types & (1u << value->type))
+        || (types & OP_ID && value->type == JSON_STRING
+            && ovsdb_parser_is_id(value->u.string)))
+    {
+        svec_add(&parser->used, name);
+        return value;
+    } else {
+        ovsdb_parser_raise_error(parser, "Type mismatch for member '%s'.",
+                                 name);
+        return NULL;
+    }
+}
+
+void
+ovsdb_parser_raise_error(struct ovsdb_parser *parser, const char *format, ...)
+{
+    if (!parser->error) {
+        struct ovsdb_error *error;
+        va_list args;
+        char *message;
+
+        va_start(args, format);
+        message = xvasprintf(format, args);
+        va_end(args);
+
+        error = ovsdb_syntax_error(parser->json, NULL, "Parsing %s failed: %s",
+                                   parser->name, message);
+        free(message);
+
+        parser->error = error;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_parser_get_error(const struct ovsdb_parser *parser)
+{
+    return parser->error ? ovsdb_error_clone(parser->error) : NULL;
+}
+
+bool
+ovsdb_parser_has_error(const struct ovsdb_parser *parser)
+{
+    return parser->error != NULL;
+}
+
+struct ovsdb_error *
+ovsdb_parser_finish(struct ovsdb_parser *parser)
+{
+    if (!parser->error) {
+        const struct shash *object = json_object(parser->json);
+        size_t n_unused;
+
+        /* XXX this method of detecting unused members can be made cheaper */
+        svec_sort_unique(&parser->used);
+        n_unused = shash_count(object) - parser->used.n;
+        if (n_unused) {
+            struct shash_node *node;
+
+            SHASH_FOR_EACH (node, object) {
+                if (!svec_contains(&parser->used, node->name)) {
+                    if (n_unused > 1) {
+                        ovsdb_parser_raise_error(
+                            parser,
+                            "Member '%s' and %zu other member%s "
+                            "are present but not allowed here.",
+                            node->name, n_unused - 1, n_unused > 2 ? "s" : "");
+                    } else {
+                        ovsdb_parser_raise_error(
+                            parser,
+                            "Member '%s' is present but not allowed here.",
+                            node->name);
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    free(parser->name);
+    svec_destroy(&parser->used);
+
+    return parser->error;
+}
diff --git a/lib/ovsdb-parser.h b/lib/ovsdb-parser.h
new file mode 100644 (file)
index 0000000..6efa0a7
--- /dev/null
@@ -0,0 +1,76 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_PARSER_H
+#define OVSDB_PARSER_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "json.h"
+#include "svec.h"
+#include "util.h"
+
+struct ovsdb_parser {
+    char *name;                 /* Used only in error messages. */
+    struct svec used;           /* Already-parsed names from 'object'. */
+    const struct json *json;    /* JSON object being parsed. */
+    struct ovsdb_error *error;  /* Error signaled, if any. */
+};
+
+/* Check that the JSON types make the bitwise tricks below work OK. */
+BUILD_ASSERT_DECL(JSON_NULL >= 0 && JSON_NULL < 10);
+BUILD_ASSERT_DECL(JSON_FALSE >= 0 && JSON_FALSE < 10);
+BUILD_ASSERT_DECL(JSON_TRUE >= 0 && JSON_TRUE < 10);
+BUILD_ASSERT_DECL(JSON_OBJECT >= 0 && JSON_OBJECT < 10);
+BUILD_ASSERT_DECL(JSON_ARRAY >= 0 && JSON_ARRAY < 10);
+BUILD_ASSERT_DECL(JSON_INTEGER >= 0 && JSON_INTEGER < 10);
+BUILD_ASSERT_DECL(JSON_REAL >= 0 && JSON_REAL < 10);
+BUILD_ASSERT_DECL(JSON_STRING >= 0 && JSON_STRING < 10);
+BUILD_ASSERT_DECL(JSON_N_TYPES == 8);
+
+enum ovsdb_parser_types {
+    OP_NULL = 1 << JSON_NULL,             /* null */
+    OP_FALSE = 1 << JSON_FALSE,           /* false */
+    OP_TRUE = 1 << JSON_TRUE,             /* true */
+    OP_OBJECT = 1 << JSON_OBJECT,         /* {"a": b, "c": d, ...} */
+    OP_ARRAY = 1 << JSON_ARRAY,           /* [1, 2, 3, ...] */
+    OP_INTEGER = 1 << JSON_INTEGER,       /* 123. */
+    OP_NONINTEGER = 1 << JSON_REAL,       /* 123.456. */
+    OP_STRING = 1 << JSON_STRING,         /* "..." */
+
+    OP_BOOLEAN = OP_FALSE | OP_TRUE,
+    OP_NUMBER = OP_INTEGER | OP_NONINTEGER,
+
+    OP_ID = 1 << JSON_N_TYPES,            /* "[_a-zA-Z][_a-zA-Z0-9]*" */
+    OP_OPTIONAL = 1 << (JSON_N_TYPES + 1) /* no value at all */
+};
+
+void ovsdb_parser_init(struct ovsdb_parser *, const struct json *,
+                       const char *name, ...)
+    PRINTF_FORMAT(3, 4);
+const struct json *ovsdb_parser_member(struct ovsdb_parser *, const char *name,
+                                       enum ovsdb_parser_types);
+
+void ovsdb_parser_raise_error(struct ovsdb_parser *parser,
+                              const char *format, ...)
+    PRINTF_FORMAT(2, 3);
+bool ovsdb_parser_has_error(const struct ovsdb_parser *);
+struct ovsdb_error *ovsdb_parser_get_error(const struct ovsdb_parser *);
+struct ovsdb_error *ovsdb_parser_finish(struct ovsdb_parser *)
+    WARN_UNUSED_RESULT;
+
+bool ovsdb_parser_is_id(const char *string);
+
+#endif /* ovsdb-parser.h */
diff --git a/lib/ovsdb-types.c b/lib/ovsdb-types.c
new file mode 100644 (file)
index 0000000..659b50d
--- /dev/null
@@ -0,0 +1,252 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb-types.h"
+
+#include <limits.h>
+
+#include "dynamic-string.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+
+const struct ovsdb_type ovsdb_type_integer =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_INTEGER);
+const struct ovsdb_type ovsdb_type_real =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_REAL);
+const struct ovsdb_type ovsdb_type_boolean =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_BOOLEAN);
+const struct ovsdb_type ovsdb_type_string =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_STRING);
+const struct ovsdb_type ovsdb_type_uuid =
+    OVSDB_TYPE_SCALAR_INITIALIZER(OVSDB_TYPE_UUID);
+
+const char *
+ovsdb_atomic_type_to_string(enum ovsdb_atomic_type type)
+{
+    switch (type) {
+    case OVSDB_TYPE_VOID:
+        return "void";
+
+    case OVSDB_TYPE_INTEGER:
+        return "integer";
+
+    case OVSDB_TYPE_REAL:
+        return "real";
+
+    case OVSDB_TYPE_BOOLEAN:
+        return "boolean";
+
+    case OVSDB_TYPE_STRING:
+        return "string";
+
+    case OVSDB_TYPE_UUID:
+        return "uuid";
+
+    case OVSDB_N_TYPES:
+    default:
+        return "<invalid>";
+    }
+}
+
+struct json *
+ovsdb_atomic_type_to_json(enum ovsdb_atomic_type type)
+{
+    return json_string_create(ovsdb_atomic_type_to_string(type));
+}
+
+bool
+ovsdb_type_is_valid(const struct ovsdb_type *type)
+{
+    return (type->key_type != OVSDB_TYPE_VOID
+            && ovsdb_atomic_type_is_valid(type->key_type)
+            && ovsdb_atomic_type_is_valid(type->value_type)
+            && type->n_min <= 1
+            && type->n_min <= type->n_max
+            && (type->value_type == OVSDB_TYPE_VOID
+                || ovsdb_atomic_type_is_valid_key(type->key_type)));
+}
+
+bool
+ovsdb_atomic_type_from_string(const char *string, enum ovsdb_atomic_type *type)
+{
+    if (!strcmp(string, "integer")) {
+        *type = OVSDB_TYPE_INTEGER;
+    } else if (!strcmp(string, "real")) {
+        *type = OVSDB_TYPE_REAL;
+    } else if (!strcmp(string, "boolean")) {
+        *type = OVSDB_TYPE_BOOLEAN;
+    } else if (!strcmp(string, "string")) {
+        *type = OVSDB_TYPE_STRING;
+    } else if (!strcmp(string, "uuid")) {
+        *type = OVSDB_TYPE_UUID;
+    } else {
+        return false;
+    }
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *type,
+                            const struct json *json)
+{
+    if (json->type == JSON_STRING) {
+        if (ovsdb_atomic_type_from_string(json_string(json), type)) {
+            return NULL;
+        } else {
+            *type = OVSDB_TYPE_VOID;
+            return ovsdb_syntax_error(json, NULL,
+                                      "\"%s\" is not an atomic-type",
+                                      json_string(json));
+        }
+    } else {
+        *type = OVSDB_TYPE_VOID;
+        return ovsdb_syntax_error(json, NULL, "atomic-type expected");
+    }
+}
+
+static struct ovsdb_error *
+n_from_json(const struct json *json, unsigned int *n)
+{
+    if (!json) {
+        return NULL;
+    } else if (json->type == JSON_INTEGER
+               && json->u.integer >= 0 && json->u.integer < UINT_MAX) {
+        *n = json->u.integer;
+        return NULL;
+    } else {
+        return ovsdb_syntax_error(json, NULL, "bad min or max value");
+    }
+}
+
+char *
+ovsdb_type_to_english(const struct ovsdb_type *type)
+{
+    const char *key = ovsdb_atomic_type_to_string(type->key_type);
+    const char *value = ovsdb_atomic_type_to_string(type->value_type);
+    if (ovsdb_type_is_scalar(type)) {
+        return xstrdup(key);
+    } else {
+        struct ds s = DS_EMPTY_INITIALIZER;
+        ds_put_cstr(&s, ovsdb_type_is_set(type) ? "set" : "map");
+        if (type->n_max == UINT_MAX) {
+            if (type->n_min) {
+                ds_put_format(&s, " of %u or more", type->n_min);
+            } else {
+                ds_put_cstr(&s, " of");
+            }
+        } else if (type->n_min) {
+            ds_put_format(&s, " of %u to %u", type->n_min, type->n_max);
+        } else {
+            ds_put_format(&s, " of up to %u", type->n_max);
+        }
+        if (ovsdb_type_is_set(type)) {
+            ds_put_format(&s, " %ss", key);
+        } else {
+            ds_put_format(&s, " (%s, %s) pairs", key, value);
+        }
+        return ds_cstr(&s);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_type_from_json(struct ovsdb_type *type, const struct json *json)
+{
+    type->value_type = OVSDB_TYPE_VOID;
+    type->n_min = 1;
+    type->n_max = 1;
+
+    if (json->type == JSON_STRING) {
+        return ovsdb_atomic_type_from_json(&type->key_type, json);
+    } else if (json->type == JSON_OBJECT) {
+        const struct json *key, *value, *min, *max;
+        struct ovsdb_error *error;
+        struct ovsdb_parser parser;
+
+        ovsdb_parser_init(&parser, json, "ovsdb type");
+        key = ovsdb_parser_member(&parser, "key", OP_STRING);
+        value = ovsdb_parser_member(&parser, "value", OP_STRING | OP_OPTIONAL);
+        min = ovsdb_parser_member(&parser, "min", OP_INTEGER | OP_OPTIONAL);
+        max = ovsdb_parser_member(&parser, "max",
+                                  OP_INTEGER | OP_STRING | OP_OPTIONAL);
+        error = ovsdb_parser_finish(&parser);
+        if (error) {
+            return error;
+        }
+
+        error = ovsdb_atomic_type_from_json(&type->key_type, key);
+        if (error) {
+            return error;
+        }
+
+        if (value) {
+            error = ovsdb_atomic_type_from_json(&type->value_type, value);
+            if (error) {
+                return error;
+            }
+        }
+
+        error = n_from_json(min, &type->n_min);
+        if (error) {
+            return error;
+        }
+
+        if (max && max->type == JSON_STRING
+            && !strcmp(max->u.string, "unlimited")) {
+            type->n_max = UINT_MAX;
+        } else {
+            error = n_from_json(max, &type->n_max);
+            if (error) {
+                return error;
+            }
+        }
+
+        if (!ovsdb_type_is_valid(type)) {
+            return ovsdb_syntax_error(json, NULL,
+                                      "ovsdb type fails constraint checks");
+        }
+
+        return NULL;
+    } else {
+        return ovsdb_syntax_error(json, NULL, "ovsdb type expected");
+    }
+}
+
+struct json *
+ovsdb_type_to_json(const struct ovsdb_type *type)
+{
+    if (ovsdb_type_is_scalar(type)) {
+        return ovsdb_atomic_type_to_json(type->key_type);
+    } else {
+        struct json *json = json_object_create();
+        json_object_put(json, "key",
+                        ovsdb_atomic_type_to_json(type->key_type));
+        if (type->value_type != OVSDB_TYPE_VOID) {
+            json_object_put(json, "value",
+                            ovsdb_atomic_type_to_json(type->value_type));
+        }
+        if (type->n_min != 1) {
+            json_object_put(json, "min", json_integer_create(type->n_min));
+        }
+        if (type->n_max == UINT_MAX) {
+            json_object_put_string(json, "max", "unlimited");
+        } else if (type->n_max != 1) {
+            json_object_put(json, "max", json_integer_create(type->n_max));
+        }
+        return json;
+    }
+}
diff --git a/lib/ovsdb-types.h b/lib/ovsdb-types.h
new file mode 100644 (file)
index 0000000..b9b3d5a
--- /dev/null
@@ -0,0 +1,135 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TYPES_H
+#define OVSDB_TYPES_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "compiler.h"
+#include "uuid.h"
+
+struct json;
+
+/* An atomic type: one that OVSDB regards as a single unit of data. */
+enum ovsdb_atomic_type {
+    OVSDB_TYPE_VOID,            /* No value. */
+    OVSDB_TYPE_INTEGER,         /* Signed 64-bit integer. */
+    OVSDB_TYPE_REAL,            /* IEEE 754 double-precision floating point. */
+    OVSDB_TYPE_BOOLEAN,         /* True or false. */
+    OVSDB_TYPE_STRING,          /* UTF-8 string. */
+    OVSDB_TYPE_UUID,            /* RFC 4122 UUID referencing a table row. */
+    OVSDB_N_TYPES
+};
+
+static inline bool ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type);
+static inline bool ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type);
+bool ovsdb_atomic_type_from_string(const char *, enum ovsdb_atomic_type *);
+struct ovsdb_error *ovsdb_atomic_type_from_json(enum ovsdb_atomic_type *,
+                                                const struct json *);
+const char *ovsdb_atomic_type_to_string(enum ovsdb_atomic_type);
+struct json *ovsdb_atomic_type_to_json(enum ovsdb_atomic_type);
+\f
+/* An OVSDB type.
+ *
+ * Several rules constrain the valid types.  See ovsdb_type_is_valid() (in
+ * ovsdb-types.c) for details.
+ *
+ * If 'value_type' is OVSDB_TYPE_VOID, 'n_min' is 1, and 'n_max' is 1, then the
+ * type is a single atomic 'key_type'.
+ *
+ * If 'value_type' is OVSDB_TYPE_VOID and 'n_min' or 'n_max' (or both) has a
+ * value other than 1, then the type is a set of 'key_type'.  If 'n_min' is 0
+ * and 'n_max' is 1, then the type can also be considered an optional
+ * 'key_type'.
+ *
+ * If 'value_type' is not OVSDB_TYPE_VOID, then the type is a map from
+ * 'key_type' to 'value_type'.  If 'n_min' is 0 and 'n_max' is 1, then the type
+ * can also be considered an optional pair of 'key_type' and 'value_type'.
+ */
+struct ovsdb_type {
+    enum ovsdb_atomic_type key_type;
+    enum ovsdb_atomic_type value_type;
+    unsigned int n_min;
+    unsigned int n_max;         /* UINT_MAX stands in for "unlimited". */
+};
+
+#define OVSDB_TYPE_SCALAR_INITIALIZER(KEY_TYPE) \
+        { KEY_TYPE, OVSDB_TYPE_VOID, 1, 1 }
+
+extern const struct ovsdb_type ovsdb_type_integer;
+extern const struct ovsdb_type ovsdb_type_real;
+extern const struct ovsdb_type ovsdb_type_boolean;
+extern const struct ovsdb_type ovsdb_type_string;
+extern const struct ovsdb_type ovsdb_type_uuid;
+
+bool ovsdb_type_is_valid(const struct ovsdb_type *);
+
+static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_optional(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_composite(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_set(const struct ovsdb_type *);
+static inline bool ovsdb_type_is_map(const struct ovsdb_type *);
+
+char *ovsdb_type_to_english(const struct ovsdb_type *);
+
+struct ovsdb_error *ovsdb_type_from_json(struct ovsdb_type *,
+                                         const struct json *)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_type_to_json(const struct ovsdb_type *);
+\f
+/* Inline function implementations. */
+
+static inline bool
+ovsdb_atomic_type_is_valid(enum ovsdb_atomic_type atomic_type)
+{
+    return atomic_type >= 0 && atomic_type < OVSDB_N_TYPES;
+}
+
+static inline bool
+ovsdb_atomic_type_is_valid_key(enum ovsdb_atomic_type atomic_type)
+{
+    /* XXX should we disallow reals or booleans as keys? */
+    return ovsdb_atomic_type_is_valid(atomic_type);
+}
+
+static inline bool ovsdb_type_is_scalar(const struct ovsdb_type *type)
+{
+    return (type->value_type == OVSDB_TYPE_VOID
+            && type->n_min == 1 && type->n_max == 1);
+}
+
+static inline bool ovsdb_type_is_optional(const struct ovsdb_type *type)
+{
+    return type->n_min == 0;
+}
+
+static inline bool ovsdb_type_is_composite(const struct ovsdb_type *type)
+{
+    return type->n_max > 1;
+}
+
+static inline bool ovsdb_type_is_set(const struct ovsdb_type *type)
+{
+    return (type->value_type == OVSDB_TYPE_VOID
+            && (type->n_min != 1 || type->n_max != 1));
+}
+
+static inline bool ovsdb_type_is_map(const struct ovsdb_type *type)
+{
+    return type->value_type != OVSDB_TYPE_VOID;
+}
+
+#endif /* ovsdb-types.h */
index 0547791..29c4e7e 100644 (file)
 #include <config.h>
 #include "packets.h"
 #include <netinet/in.h>
+#include <stdlib.h>
 #include "ofpbuf.h"
 
+bool
+dpid_from_string(const char *s, uint64_t *dpidp)
+{
+    *dpidp = (strlen(s) == 12 && strspn(s, "0123456789abcdefABCDEF") == 12
+              ? strtoll(s, NULL, 16)
+              : 0);
+    return *dpidp != 0;
+}
+
+bool
+eth_addr_from_string(const char *s, uint8_t ea[ETH_ADDR_LEN])
+{
+    if (sscanf(s, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(ea))
+        == ETH_ADDR_SCAN_COUNT) {
+        return true;
+    } else {
+        memset(ea, 0, ETH_ADDR_LEN);
+        return false;
+    }
+}
+
 /* Fills 'b' with an 802.2 SNAP packet with Ethernet source address 'eth_src',
  * the Nicira OUI as SNAP organization and 'snap_type' as SNAP type.  The text
  * string in 'tag' is enclosed as the packet payload.
index 4595c12..f61c20c 100644 (file)
@@ -26,6 +26,8 @@
 
 struct ofpbuf;
 
+bool dpid_from_string(const char *s, uint64_t *dpidp);
+
 #define ETH_ADDR_LEN           6
 
 static const uint8_t eth_addr_broadcast[ETH_ADDR_LEN] UNUSED
@@ -36,14 +38,6 @@ static inline bool eth_addr_is_broadcast(const uint8_t ea[6])
     return (ea[0] & ea[1] & ea[2] & ea[3] & ea[4] & ea[5]) == 0xff;
 }
 
-/* Returns true if 'ea' is an Ethernet address used for virtual interfaces
- * under XenServer.  Generally the actual Ethernet address is FE:FF:FF:FF:FF:FF
- * but it can be FE:FE:FE:FE:FE:FE in some cases. */
-static inline bool eth_addr_is_vif(const uint8_t ea[6])
-{
-    return ea[0] == 0xfe && (ea[1] & ea[2] & ea[3] & ea[4] & ea[5]) >= 0xfe;
-}
-
 static inline bool eth_addr_is_multicast(const uint8_t ea[6])
 {
     return ea[0] & 1;
@@ -101,6 +95,8 @@ static inline bool eth_addr_is_reserved(const uint8_t ea[ETH_ADDR_LEN])
             && (ea[5] & 0xf0) == 0x00);
 }
 
+bool eth_addr_from_string(const char *, uint8_t ea[ETH_ADDR_LEN]);
+
 void compose_benign_packet(struct ofpbuf *, const char *tag,
                            uint16_t snap_type,
                            const uint8_t eth_src[ETH_ADDR_LEN]);
index 945b5c4..8f65f94 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
 #include "backtrace.h"
 #include "coverage.h"
 #include "dynamic-string.h"
+#include "fatal-signal.h"
 #include "list.h"
 #include "timeval.h"
 
@@ -37,13 +38,10 @@ struct poll_waiter {
     struct list node;           /* Element in global waiters list. */
     int fd;                     /* File descriptor. */
     short int events;           /* Events to wait for (POLLIN, POLLOUT). */
-    poll_fd_func *function;     /* Callback function, if any, or null. */
-    void *aux;                  /* Argument to callback function. */
     struct backtrace *backtrace; /* Optionally, event that created waiter. */
 
     /* Set only when poll_block() is called. */
-    struct pollfd *pollfd;      /* Pointer to element of the pollfds array
-                                   (null if added from a callback). */
+    struct pollfd *pollfd;      /* Pointer to element of the pollfds array. */
 };
 
 /* All active poll waiters. */
@@ -59,12 +57,6 @@ static int timeout = -1;
 /* Backtrace of 'timeout''s registration, if debugging is enabled. */
 static struct backtrace timeout_backtrace;
 
-/* Callback currently running, to allow verifying that poll_cancel() is not
- * being called on a running callback. */
-#ifndef NDEBUG
-static struct poll_waiter *running_cb;
-#endif
-
 static struct poll_waiter *new_waiter(int fd, short int events);
 
 /* Registers 'fd' as waiting for the specified 'events' (which should be POLLIN
@@ -132,22 +124,21 @@ log_wakeup(const struct backtrace *backtrace, const char *format, ...)
 
 /* Blocks until one or more of the events registered with poll_fd_wait()
  * occurs, or until the minimum duration registered with poll_timer_wait()
- * elapses, or not at all if poll_immediate_wake() has been called.
- *
- * Also executes any autonomous subroutines registered with poll_fd_callback(),
- * if their file descriptors have become ready. */
+ * elapses, or not at all if poll_immediate_wake() has been called. */
 void
 poll_block(void)
 {
     static struct pollfd *pollfds;
     static size_t max_pollfds;
 
-    struct poll_waiter *pw;
-    struct list *node;
+    struct poll_waiter *pw, *next;
     int n_pollfds;
     int retval;
 
-    assert(!running_cb);
+    /* Register fatal signal events before actually doing any real work for
+     * poll_block. */
+    fatal_signal_wait();
+
     if (max_pollfds < n_waiters) {
         max_pollfds = n_waiters;
         pollfds = xrealloc(pollfds, max_pollfds * sizeof *pollfds);
@@ -173,76 +164,36 @@ poll_block(void)
         log_wakeup(&timeout_backtrace, "%d-ms timeout", timeout);
     }
 
-    for (node = waiters.next; node != &waiters; ) {
-        pw = CONTAINER_OF(node, struct poll_waiter, node);
-        if (!pw->pollfd || !pw->pollfd->revents) {
-            if (pw->function) {
-                node = node->next;
-                continue;
-            }
-        } else {
-            if (VLOG_IS_DBG_ENABLED()) {
-                log_wakeup(pw->backtrace, "%s%s%s%s%s on fd %d",
-                           pw->pollfd->revents & POLLIN ? "[POLLIN]" : "",
-                           pw->pollfd->revents & POLLOUT ? "[POLLOUT]" : "",
-                           pw->pollfd->revents & POLLERR ? "[POLLERR]" : "",
-                           pw->pollfd->revents & POLLHUP ? "[POLLHUP]" : "",
-                           pw->pollfd->revents & POLLNVAL ? "[POLLNVAL]" : "",
-                           pw->fd);
-            }
-
-            if (pw->function) {
-#ifndef NDEBUG
-                running_cb = pw;
-#endif
-                pw->function(pw->fd, pw->pollfd->revents, pw->aux);
-#ifndef NDEBUG
-                running_cb = NULL;
-#endif
-            }
+    LIST_FOR_EACH_SAFE (pw, next, struct poll_waiter, node, &waiters) {
+        if (pw->pollfd->revents && VLOG_IS_DBG_ENABLED()) {
+            log_wakeup(pw->backtrace, "%s%s%s%s%s on fd %d",
+                       pw->pollfd->revents & POLLIN ? "[POLLIN]" : "",
+                       pw->pollfd->revents & POLLOUT ? "[POLLOUT]" : "",
+                       pw->pollfd->revents & POLLERR ? "[POLLERR]" : "",
+                       pw->pollfd->revents & POLLHUP ? "[POLLHUP]" : "",
+                       pw->pollfd->revents & POLLNVAL ? "[POLLNVAL]" : "",
+                       pw->fd);
         }
-        node = node->next;
         poll_cancel(pw);
     }
 
     timeout = -1;
     timeout_backtrace.n_frames = 0;
-}
 
-/* Registers 'function' to be called with argument 'aux' by poll_block() when
- * 'fd' becomes ready for one of the events in 'events', which should be POLLIN
- * or POLLOUT or POLLIN | POLLOUT.
- *
- * The callback registration persists until the event actually occurs.  At that
- * point, it is automatically de-registered.  The callback function must
- * re-register the event by calling poll_fd_callback() again within the
- * callback, if it wants to be called back again later. */
-struct poll_waiter *
-poll_fd_callback(int fd, short int events, poll_fd_func *function, void *aux)
-{
-    struct poll_waiter *pw = new_waiter(fd, events);
-    pw->function = function;
-    pw->aux = aux;
-    return pw;
+    /* Handle any pending signals before doing anything else. */
+    fatal_signal_run();
 }
 
-/* Cancels the file descriptor event registered with poll_fd_wait() or
- * poll_fd_callback().  'pw' must be the struct poll_waiter returned by one of
- * those functions.
+/* Cancels the file descriptor event registered with poll_fd_wait() using 'pw',
+ * the struct poll_waiter returned by that function.
  *
  * An event registered with poll_fd_wait() may be canceled from its time of
  * registration until the next call to poll_block().  At that point, the event
- * is automatically canceled by the system and its poll_waiter is freed.
- *
- * An event registered with poll_fd_callback() may be canceled from its time of
- * registration until its callback is actually called.  At that point, the
- * event is automatically canceled by the system and its poll_waiter is
- * freed. */
+ * is automatically canceled by the system and its poll_waiter is freed. */
 void
 poll_cancel(struct poll_waiter *pw)
 {
     if (pw) {
-        assert(pw != running_cb);
         list_remove(&pw->node);
         free(pw->backtrace);
         free(pw);
@@ -254,7 +205,7 @@ poll_cancel(struct poll_waiter *pw)
 static struct poll_waiter *
 new_waiter(int fd, short int events)
 {
-    struct poll_waiter *waiter = xcalloc(1, sizeof *waiter);
+    struct poll_waiter *waiter = xzalloc(sizeof *waiter);
     assert(fd >= 0);
     waiter->fd = fd;
     waiter->events = events;
index 341d063..89c8e57 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -44,11 +44,6 @@ void poll_immediate_wake(void);
 /* Wait until an event occurs. */
 void poll_block(void);
 
-/* Autonomous function callbacks. */
-typedef void poll_fd_func(int fd, short int revents, void *aux);
-struct poll_waiter *poll_fd_callback(int fd, short int events,
-                                     poll_fd_func *, void *aux);
-
 /* Cancel a file descriptor callback or event. */
 void poll_cancel(struct poll_waiter *);
 
index 1fe3c12..3f66ddd 100644 (file)
@@ -161,7 +161,7 @@ process_register(const char *name, pid_t pid)
 
     assert(sigchld_is_blocked());
 
-    p = xcalloc(1, sizeof *p);
+    p = xzalloc(sizeof *p);
     p->pid = pid;
     slash = strrchr(name, '/');
     p->name = xstrdup(slash ? slash + 1 : name);
@@ -201,17 +201,14 @@ process_start(char **argv,
     }
 
     block_sigchld(&oldsigs);
-    fatal_signal_block();
     pid = fork();
     if (pid < 0) {
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
         VLOG_WARN("fork failed: %s", strerror(errno));
         return errno;
     } else if (pid) {
         /* Running in parent process. */
         *pp = process_register(argv[0], pid);
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
         return 0;
     } else {
@@ -220,7 +217,6 @@ process_start(char **argv,
         int fd;
 
         fatal_signal_fork();
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
         for (fd = 0; fd < fd_max; fd++) {
             if (is_member(fd, null_fds, n_null_fds)) {
@@ -289,7 +285,7 @@ process_exited(struct process *p)
         return true;
     } else {
         char buf[_POSIX_PIPE_BUF];
-        read(fds[0], buf, sizeof buf);
+        ignore(read(fds[0], buf, sizeof buf));
         return false;
     }
 }
@@ -521,12 +517,10 @@ process_run_capture(char **argv, char **stdout_log, char **stderr_log,
     }
 
     block_sigchld(&oldsigs);
-    fatal_signal_block();
     pid = fork();
     if (pid < 0) {
         int error = errno;
 
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
         VLOG_WARN("fork failed: %s", strerror(error));
 
@@ -539,7 +533,6 @@ process_run_capture(char **argv, char **stdout_log, char **stderr_log,
         struct process *p;
 
         p = process_register(argv[0], pid);
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
 
         close(s_stdout.fds[1]);
@@ -575,7 +568,6 @@ process_run_capture(char **argv, char **stdout_log, char **stderr_log,
         int i;
 
         fatal_signal_fork();
-        fatal_signal_unblock();
         unblock_sigchld(&oldsigs);
 
         dup2(get_null_fd(), 0);
@@ -617,7 +609,7 @@ sigchld_handler(int signr UNUSED)
             }
         }
     }
-    write(fds[1], "", 1);
+    ignore(write(fds[1], "", 1));
 }
 
 static bool
index e94d20c..879f7a2 100644 (file)
@@ -17,6 +17,8 @@
 #ifndef QUEUE_H
 #define QUEUE_H 1
 
+#include <stdbool.h>
+
 /* Packet queue. */
 struct ovs_queue {
     int n;                      /* Number of queued packets. */
@@ -31,4 +33,9 @@ void queue_advance_head(struct ovs_queue *, struct ofpbuf *next);
 void queue_push_tail(struct ovs_queue *, struct ofpbuf *);
 struct ofpbuf *queue_pop_head(struct ovs_queue *);
 
+static inline bool queue_is_empty(const struct ovs_queue *q)
+{
+    return q->n == 0;
+}
+
 #endif /* queue.h */
index 2cbe43e..f2d074a 100644 (file)
@@ -176,7 +176,7 @@ rconn_new_from_vconn(const char *name, struct vconn *vconn)
 struct rconn *
 rconn_create(int probe_interval, int max_backoff)
 {
-    struct rconn *rc = xcalloc(1, sizeof *rc);
+    struct rconn *rc = xzalloc(sizeof *rc);
 
     rc->state = S_VOID;
     rc->state_entered = time_now();
@@ -459,6 +459,15 @@ void
 rconn_run(struct rconn *rc)
 {
     int old_state;
+    size_t i;
+
+    if (rc->vconn) {
+        vconn_run(rc->vconn);
+    }
+    for (i = 0; i < rc->n_monitors; i++) {
+        vconn_run(rc->monitors[i]);
+    }
+
     do {
         old_state = rc->state;
         switch (rc->state) {
@@ -476,7 +485,17 @@ rconn_run(struct rconn *rc)
 void
 rconn_run_wait(struct rconn *rc)
 {
-    unsigned int timeo = timeout(rc);
+    unsigned int timeo;
+    size_t i;
+
+    if (rc->vconn) {
+        vconn_run_wait(rc->vconn);
+    }
+    for (i = 0; i < rc->n_monitors; i++) {
+        vconn_run_wait(rc->monitors[i]);
+    }
+
+    timeo = timeout(rc);
     if (timeo != UINT_MAX) {
         unsigned int expires = sat_add(rc->state_entered, timeo);
         unsigned int remaining = sat_sub(expires, time_now());
diff --git a/lib/reconnect.c b/lib/reconnect.c
new file mode 100644 (file)
index 0000000..f159f01
--- /dev/null
@@ -0,0 +1,562 @@
+/*
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "reconnect.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "poll-loop.h"
+
+#define THIS_MODULE VLM_reconnect
+#include "vlog.h"
+
+#define STATES                                  \
+    STATE(VOID, 1 << 0)                         \
+    STATE(BACKOFF, 1 << 1)                      \
+    STATE(START_CONNECT, 1 << 2)                \
+    STATE(CONNECT_IN_PROGRESS, 1 << 3)          \
+    STATE(ACTIVE, 1 << 4)                       \
+    STATE(IDLE, 1 << 5)                         \
+    STATE(RECONNECT, 1 << 6)
+enum state {
+#define STATE(NAME, VALUE) S_##NAME = VALUE,
+    STATES
+#undef STATE
+};
+
+static bool
+is_connected_state(enum state state)
+{
+    return (state & (S_ACTIVE | S_IDLE)) != 0;
+}
+
+struct reconnect {
+    /* Configuration. */
+    char *name;
+    int min_backoff;
+    int max_backoff;
+    int probe_interval;
+
+    /* State. */
+    enum state state;
+    long long int state_entered;
+    int backoff;
+    long long int last_received;
+    long long int last_connected;
+    unsigned int max_tries;
+
+    /* These values are simply for statistics reporting, not otherwise used
+     * directly by anything internal. */
+    long long int creation_time;
+    unsigned int n_attempted_connections, n_successful_connections;
+    unsigned int total_connected_duration;
+    unsigned int seqno;
+};
+
+static void reconnect_transition__(struct reconnect *, long long int now,
+                                   enum state state);
+static long long int reconnect_deadline__(const struct reconnect *);
+static bool reconnect_may_retry(struct reconnect *);
+
+static const char *
+reconnect_state_name__(enum state state)
+{
+    switch (state) {
+#define STATE(NAME, VALUE) case S_##NAME: return #NAME;
+        STATES
+#undef STATE
+    }
+    return "***ERROR***";
+}
+
+/* Creates and returns a new reconnect FSM with default settings.  The FSM is
+ * initially disabled.  The caller will likely want to call reconnect_enable()
+ * and reconnect_set_name() on the returned object. */
+struct reconnect *
+reconnect_create(long long int now)
+{
+    struct reconnect *fsm = xzalloc(sizeof *fsm);
+
+    fsm->name = xstrdup("void");
+    fsm->min_backoff = 1000;
+    fsm->max_backoff = 8000;
+    fsm->probe_interval = 5000;
+
+    fsm->state = S_VOID;
+    fsm->state_entered = now;
+    fsm->backoff = 0;
+    fsm->last_received = now;
+    fsm->last_connected = now;
+    fsm->max_tries = UINT_MAX;
+    fsm->creation_time = now;
+
+    return fsm;
+}
+
+/* Frees 'fsm'. */
+void
+reconnect_destroy(struct reconnect *fsm)
+{
+    if (fsm) {
+        free(fsm->name);
+        free(fsm);
+    }
+}
+
+/* Returns 'fsm''s name. */
+const char *
+reconnect_get_name(const struct reconnect *fsm)
+{
+    return fsm->name;
+}
+
+/* Sets 'fsm''s name to 'name'.  If 'name' is null, then "void" is used
+ * instead.
+ *
+ * The name set for 'fsm' is used in log messages. */
+void
+reconnect_set_name(struct reconnect *fsm, const char *name)
+{
+    free(fsm->name);
+    fsm->name = xstrdup(name ? name : "void");
+}
+
+/* Return the minimum number of milliseconds to back off between consecutive
+ * connection attempts.  The default is 1000 ms. */
+int
+reconnect_get_min_backoff(const struct reconnect *fsm)
+{
+    return fsm->min_backoff;
+}
+
+/* Return the maximum number of milliseconds to back off between consecutive
+ * connection attempts.  The default is 8000 ms. */
+int
+reconnect_get_max_backoff(const struct reconnect *fsm)
+{
+    return fsm->max_backoff;
+}
+
+/* Returns the "probe interval" for 'fsm' in milliseconds.  If this is zero, it
+ * disables the connection keepalive feature.  If it is nonzero, then if the
+ * interval passes while 'fsm' is connected and without reconnect_received()
+ * being called for 'fsm', reconnect_run() returns RECONNECT_PROBE.  If the
+ * interval passes again without reconnect_received() being called,
+ * reconnect_run() returns RECONNECT_DISCONNECT for 'fsm'. */
+int
+reconnect_get_probe_interval(const struct reconnect *fsm)
+{
+    return fsm->probe_interval;
+}
+
+/* Limits the maximum number of times that 'fsm' will ask the client to try to
+ * reconnect to 'max_tries'.  UINT_MAX (the default) means an unlimited number
+ * of tries.
+ *
+ * After the number of tries has expired, the 'fsm' will disable itself
+ * instead of backing off and retrying. */
+void
+reconnect_set_max_tries(struct reconnect *fsm, unsigned int max_tries)
+{
+    fsm->max_tries = max_tries;
+}
+
+/* Returns the current remaining number of connection attempts, UINT_MAX if
+ * the number is unlimited.  */
+unsigned int
+reconnect_get_max_tries(struct reconnect *fsm)
+{
+    return fsm->max_tries;
+}
+
+/* Configures the backoff parameters for 'fsm'.  'min_backoff' is the minimum
+ * number of milliseconds, and 'max_backoff' is the maximum, between connection
+ * attempts.
+ *
+ * 'min_backoff' must be at least 1000, and 'max_backoff' must be greater than
+ * or equal to 'min_backoff'. */
+void
+reconnect_set_backoff(struct reconnect *fsm, int min_backoff, int max_backoff)
+{
+    fsm->min_backoff = MAX(min_backoff, 1000);
+    fsm->max_backoff = max_backoff ? MAX(max_backoff, 1000) : 8000;
+    if (fsm->min_backoff > fsm->max_backoff) {
+        fsm->max_backoff = fsm->min_backoff;
+    }
+
+    if (fsm->state == S_BACKOFF && fsm->backoff > max_backoff) {
+        fsm->backoff = max_backoff;
+    }
+}
+
+/* Sets the "probe interval" for 'fsm' to 'probe_interval', in milliseconds.
+ * If this is zero, it disables the connection keepalive feature.  If it is
+ * nonzero, then if the interval passes while 'fsm' is connected and without
+ * reconnect_received() being called for 'fsm', reconnect_run() returns
+ * RECONNECT_PROBE.  If the interval passes again without reconnect_received()
+ * being called, reconnect_run() returns RECONNECT_DISCONNECT for 'fsm'.
+ *
+ * If 'probe_interval' is nonzero, then it will be forced to a value of at
+ * least 1000 ms. */
+void
+reconnect_set_probe_interval(struct reconnect *fsm, int probe_interval)
+{
+    fsm->probe_interval = probe_interval ? MAX(1000, probe_interval) : 0;
+}
+
+/* Returns true if 'fsm' has been enabled with reconnect_enable().  Calling
+ * another function that indicates a change in connection state, such as
+ * reconnect_disconnected() or reconnect_force_reconnect(), will also enable
+ * a reconnect FSM. */
+bool
+reconnect_is_enabled(const struct reconnect *fsm)
+{
+    return fsm->state != S_VOID;
+}
+
+/* If 'fsm' is disabled (the default for newly created FSMs), enables it, so
+ * that the next call to reconnect_run() for 'fsm' will return
+ * RECONNECT_CONNECT.
+ *
+ * If 'fsm' is not disabled, this function has no effect. */
+void
+reconnect_enable(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state == S_VOID && reconnect_may_retry(fsm)) {
+        reconnect_transition__(fsm, now, S_BACKOFF);
+        fsm->backoff = 0;
+    }
+}
+
+/* Disables 'fsm'.  Until 'fsm' is enabled again, reconnect_run() will always
+ * return 0. */
+void
+reconnect_disable(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state != S_VOID) {
+        reconnect_transition__(fsm, now, S_VOID);
+    }
+}
+
+/* If 'fsm' is enabled and currently connected (or attempting to connect),
+ * forces reconnect_run() for 'fsm' to return RECONNECT_DISCONNECT the next
+ * time it is called, which should cause the client to drop the connection (or
+ * attempt), back off, and then reconnect. */
+void
+reconnect_force_reconnect(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state & (S_START_CONNECT | S_CONNECT_IN_PROGRESS
+                      | S_ACTIVE | S_IDLE)) {
+        reconnect_transition__(fsm, now, S_RECONNECT);
+    }
+}
+
+/* Tell 'fsm' that the connection dropped or that a connection attempt failed.
+ * 'error' specifies the reason: a positive value represents an errno value,
+ * EOF indicates that the connection was closed by the peer (e.g. read()
+ * returned 0), and 0 indicates no specific error.
+ *
+ * The FSM will back off, then reconnect. */
+void
+reconnect_disconnected(struct reconnect *fsm, long long int now, int error)
+{
+    if (!(fsm->state & (S_BACKOFF | S_VOID))) {
+        /* Report what happened. */
+        if (fsm->state & (S_ACTIVE | S_IDLE)) {
+            if (error > 0) {
+                VLOG_WARN("%s: connection dropped (%s)",
+                          fsm->name, strerror(error));
+            } else if (error == EOF) {
+                VLOG_INFO("%s: connection closed by peer", fsm->name);
+            } else {
+                VLOG_INFO("%s: connection dropped", fsm->name);
+            }
+        } else {
+            if (error > 0) {
+                VLOG_WARN("%s: connection attempt failed (%s)",
+                          fsm->name, strerror(error));
+            } else {
+                VLOG_INFO("%s: connection attempt timed out", fsm->name);
+            }
+        }
+
+        /* Back off. */
+        if (fsm->state & (S_ACTIVE | S_IDLE)
+            && fsm->last_received - fsm->last_connected >= fsm->backoff) {
+            fsm->backoff = fsm->min_backoff;
+        } else {
+            if (fsm->backoff < fsm->min_backoff) {
+                fsm->backoff = fsm->min_backoff;
+            } else if (fsm->backoff >= fsm->max_backoff / 2) {
+                fsm->backoff = fsm->max_backoff;
+            } else {
+                fsm->backoff *= 2;
+            }
+            VLOG_INFO("%s: waiting %.3g seconds before reconnect\n",
+                      fsm->name, fsm->backoff / 1000.0);
+        }
+
+        reconnect_transition__(fsm, now,
+                               reconnect_may_retry(fsm) ? S_BACKOFF : S_VOID);
+    }
+}
+
+/* Tell 'fsm' that a connection attempt is in progress.
+ *
+ * The FSM will start a timer, after which the connection attempt will be
+ * aborted (by returning RECONNECT_DISCONNECT from reconect_run()).  */
+void
+reconnect_connecting(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state != S_CONNECT_IN_PROGRESS) {
+        VLOG_INFO("%s: connecting...", fsm->name);
+        reconnect_transition__(fsm, now, S_CONNECT_IN_PROGRESS);
+    }
+}
+
+/* Tell 'fsm' that the connection was successful.
+ *
+ * The FSM will start the probe interval timer, which is reset by
+ * reconnect_received().  If the timer expires, a probe will be sent (by
+ * returning RECONNECT_PROBE from reconnect_run()).  If the timer expires
+ * again without being reset, the connection will be aborted (by returning
+ * RECONNECT_DISCONNECT from reconnect_run()). */
+void
+reconnect_connected(struct reconnect *fsm, long long int now)
+{
+    if (!is_connected_state(fsm->state)) {
+        reconnect_connecting(fsm, now);
+
+        VLOG_INFO("%s: connected", fsm->name);
+        reconnect_transition__(fsm, now, S_ACTIVE);
+        fsm->last_connected = now;
+    }
+}
+
+/* Tell 'fsm' that the connection attempt failed.
+ *
+ * The FSM will back off and attempt to reconnect. */
+void
+reconnect_connect_failed(struct reconnect *fsm, long long int now, int error)
+{
+    reconnect_connecting(fsm, now);
+    reconnect_disconnected(fsm, now, error);
+}
+
+/* Tell 'fsm' that some data was received.  This resets the probe interval
+ * timer, so that the connection is known not to be idle. */
+void
+reconnect_received(struct reconnect *fsm, long long int now)
+{
+    if (fsm->state != S_ACTIVE) {
+        reconnect_transition__(fsm, now, S_ACTIVE);
+    }
+    fsm->last_received = now;
+}
+
+static void
+reconnect_transition__(struct reconnect *fsm, long long int now,
+                       enum state state)
+{
+    if (fsm->state == S_CONNECT_IN_PROGRESS) {
+        fsm->n_attempted_connections++;
+        if (state == S_ACTIVE) {
+            fsm->n_successful_connections++;
+        }
+    }
+    if (is_connected_state(fsm->state) != is_connected_state(state)) {
+        if (is_connected_state(fsm->state)) {
+            fsm->total_connected_duration += now - fsm->last_connected;
+        }
+        fsm->seqno++;
+    }
+
+    VLOG_DBG("%s: entering %s", fsm->name, reconnect_state_name__(state));
+    fsm->state = state;
+    fsm->state_entered = now;
+}
+
+static long long int
+reconnect_deadline__(const struct reconnect *fsm)
+{
+    assert(fsm->state_entered != LLONG_MIN);
+    switch (fsm->state) {
+    case S_VOID:
+        return LLONG_MAX;
+
+    case S_BACKOFF:
+        return fsm->state_entered + fsm->backoff;
+
+    case S_START_CONNECT:
+    case S_CONNECT_IN_PROGRESS:
+        return fsm->state_entered + MAX(1000, fsm->backoff);
+
+    case S_ACTIVE:
+        if (fsm->probe_interval) {
+            long long int base = MAX(fsm->last_received, fsm->state_entered);
+            return base + fsm->probe_interval;
+        }
+        return LLONG_MAX;
+
+    case S_IDLE:
+        return fsm->state_entered + fsm->probe_interval;
+
+    case S_RECONNECT:
+        return fsm->state_entered;
+    }
+
+    NOT_REACHED();
+}
+
+/* Assesses whether any action should be taken on 'fsm'.  The return value is
+ * one of:
+ *
+ *     - 0: The client need not take any action.
+ *
+ *     - RECONNECT_CONNECT: The client should start a connection attempt and
+ *       indicate this by calling reconnect_connecting().  If the connection
+ *       attempt has definitely succeeded, it should call
+ *       reconnect_connected().  If the connection attempt has definitely
+ *       failed, it should call reconnect_connect_failed().
+ *
+ *       The FSM is smart enough to back off correctly after successful
+ *       connections that quickly abort, so it is OK to call
+ *       reconnect_connected() after a low-level successful connection
+ *       (e.g. connect()) even if the connection might soon abort due to a
+ *       failure at a high-level (e.g. SSL negotiation failure).
+ *
+ *     - RECONNECT_DISCONNECT: The client should abort the current connection
+ *       or connection attempt and call reconnect_disconnected() or
+ *       reconnect_connect_failed() to indicate it.
+ *
+ *     - RECONNECT_PROBE: The client should send some kind of request to the
+ *       peer that will elicit a response, to ensure that the connection is
+ *       indeed in working order.  (This will only be returned if the "probe
+ *       interval" is nonzero--see reconnect_set_probe_interval()).
+ */
+enum reconnect_action
+reconnect_run(struct reconnect *fsm, long long int now)
+{
+    if (now >= reconnect_deadline__(fsm)) {
+        switch (fsm->state) {
+        case S_VOID:
+            return 0;
+
+        case S_BACKOFF:
+            return RECONNECT_CONNECT;
+
+        case S_START_CONNECT:
+        case S_CONNECT_IN_PROGRESS:
+            return RECONNECT_DISCONNECT;
+
+        case S_ACTIVE:
+            VLOG_DBG("%s: idle %lld ms, sending inactivity probe", fsm->name,
+                     now - MAX(fsm->last_received, fsm->state_entered));
+            reconnect_transition__(fsm, now, S_IDLE);
+            return RECONNECT_PROBE;
+
+        case S_IDLE:
+            VLOG_ERR("%s: no response to inactivity probe after %.3g "
+                     "seconds, disconnecting",
+                     fsm->name, (now - fsm->state_entered) / 1000.0);
+            return RECONNECT_DISCONNECT;
+
+        case S_RECONNECT:
+            return RECONNECT_DISCONNECT;
+        }
+
+        NOT_REACHED();
+    } else {
+        return fsm->state == S_START_CONNECT ? RECONNECT_CONNECT : 0;
+    }
+}
+
+/* Causes the next call to poll_block() to wake up when reconnect_run() should
+ * be called on 'fsm'. */
+void
+reconnect_wait(struct reconnect *fsm, long long int now)
+{
+    int timeout = reconnect_timeout(fsm, now);
+    if (timeout >= 0) {
+        poll_timer_wait(timeout);
+    }
+}
+
+/* Returns the number of milliseconds after which reconnect_run() should be
+ * called on 'fsm' if nothing else notable happens in the meantime, or a
+ * negative number if this is currently unnecessary. */
+int
+reconnect_timeout(struct reconnect *fsm, long long int now)
+{
+    long long int deadline = reconnect_deadline__(fsm);
+    if (deadline != LLONG_MAX) {
+        long long int remaining = deadline - now;
+        return MAX(0, MIN(INT_MAX, remaining));
+    }
+    return -1;
+}
+
+/* Returns true if 'fsm' is currently believed to be connected, that is, if
+ * reconnect_connected() was called more recently than any call to
+ * reconnect_connect_failed() or reconnect_disconnected() or
+ * reconnect_disable(), and false otherwise.  */
+bool
+reconnect_is_connected(const struct reconnect *fsm)
+{
+    return is_connected_state(fsm->state);
+}
+
+/* Returns the number of milliseconds for which 'fsm' has been continuously
+ * connected to its peer.  (If 'fsm' is not currently connected, this is 0.) */
+unsigned int
+reconnect_get_connection_duration(const struct reconnect *fsm,
+                                  long long int now)
+{
+    return reconnect_is_connected(fsm) ? now - fsm->last_connected : 0;
+}
+
+/* Copies various statistics for 'fsm' into '*stats'. */
+void
+reconnect_get_stats(const struct reconnect *fsm, long long int now,
+                    struct reconnect_stats *stats)
+{
+    stats->creation_time = fsm->creation_time;
+    stats->last_received = fsm->last_received;
+    stats->last_connected = fsm->last_connected;
+    stats->backoff = fsm->backoff;
+    stats->seqno = fsm->seqno;
+    stats->is_connected = reconnect_is_connected(fsm);
+    stats->current_connection_duration
+        = reconnect_get_connection_duration(fsm, now);
+    stats->total_connected_duration = (stats->current_connection_duration
+                                       + fsm->total_connected_duration);
+    stats->n_attempted_connections = fsm->n_attempted_connections;
+    stats->n_successful_connections = fsm->n_successful_connections;
+    stats->state = reconnect_state_name__(fsm->state);
+    stats->state_elapsed = now - fsm->state_entered;
+}
+
+static bool
+reconnect_may_retry(struct reconnect *fsm)
+{
+    bool may_retry = fsm->max_tries > 0;
+    if (may_retry && fsm->max_tries != UINT_MAX) {
+        fsm->max_tries--;
+    }
+    return may_retry;
+}
diff --git a/lib/reconnect.h b/lib/reconnect.h
new file mode 100644 (file)
index 0000000..76c7f78
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RECONNECT_H
+#define RECONNECT_H 1
+
+/* This library implements a finite-state machine for connecting and
+ * reconnecting to a network resource with exponential backoff.  It also
+ * provides optional support for detecting a connection on which the peer is no
+ * longer responding.
+ *
+ * The library does not implement anything networking related, only an FSM for
+ * networking code to use.
+ *
+ * Many "reconnect" functions take a "now" argument.  This makes testing easier
+ * since there is no hidden state.  When not testing, just pass the return
+ * value of time_msec() from timeval.h.  (Perhaps this design should be
+ * revisited later.) */
+
+#include <stdbool.h>
+
+struct reconnect *reconnect_create(long long int now);
+void reconnect_destroy(struct reconnect *);
+
+const char *reconnect_get_name(const struct reconnect *);
+void reconnect_set_name(struct reconnect *, const char *name);
+
+int reconnect_get_min_backoff(const struct reconnect *);
+int reconnect_get_max_backoff(const struct reconnect *);
+int reconnect_get_probe_interval(const struct reconnect *);
+
+void reconnect_set_max_tries(struct reconnect *, unsigned int max_tries);
+unsigned int reconnect_get_max_tries(struct reconnect *);
+
+void reconnect_set_backoff(struct reconnect *,
+                           int min_backoff, int max_backoff);
+void reconnect_set_probe_interval(struct reconnect *, int probe_interval);
+
+bool reconnect_is_enabled(const struct reconnect *);
+void reconnect_enable(struct reconnect *, long long int now);
+void reconnect_disable(struct reconnect *, long long int now);
+
+void reconnect_force_reconnect(struct reconnect *, long long int now);
+
+bool reconnect_is_connected(const struct reconnect *);
+unsigned int reconnect_get_connection_duration(const struct reconnect *,
+                                               long long int now);
+
+void reconnect_disconnected(struct reconnect *, long long int now, int error);
+void reconnect_connecting(struct reconnect *, long long int now);
+void reconnect_connected(struct reconnect *, long long int now);
+void reconnect_connect_failed(struct reconnect *, long long int now,
+                              int error);
+void reconnect_received(struct reconnect *, long long int now);
+
+enum reconnect_action {
+    RECONNECT_CONNECT = 1,
+    RECONNECT_DISCONNECT,
+    RECONNECT_PROBE,
+};
+enum reconnect_action reconnect_run(struct reconnect *, long long int now);
+void reconnect_wait(struct reconnect *, long long int now);
+int reconnect_timeout(struct reconnect *, long long int now);
+
+struct reconnect_stats {
+    /* All times and durations in this structure are in milliseconds. */
+    long long int creation_time;  /* Time reconnect_create() called. */
+    long long int last_received; /* Last call to reconnect_received(). */
+    long long int last_connected; /* Last call to reconnect_connected(). */
+    int backoff;                  /* Current backoff duration.  */
+
+    unsigned int seqno;         /* # of connections + # of disconnections. */
+
+    bool is_connected;          /* Currently connected? */
+    unsigned int current_connection_duration; /* Time of current connection. */
+    unsigned int total_connected_duration;    /* Sum of all connections. */
+    unsigned int n_attempted_connections;
+    unsigned int n_successful_connections;
+
+    /* These should only be provided to a human user for debugging purposes.
+     * The client should not attempt to interpret them. */
+    const char *state;            /* FSM state. */
+    unsigned int state_elapsed;   /* Time since FSM state entered. */
+};
+
+void reconnect_get_stats(const struct reconnect *, long long int now,
+                         struct reconnect_stats *);
+
+#endif /* reconnect.h */
index 29e02f6..1d302ea 100644 (file)
@@ -76,7 +76,7 @@ rtnetlink_notifier_register(struct rtnetlink_notifier *notifier,
 }
 
 /* Cancels notification on 'notifier', which must have previously been
- * registered with lxnetdev_notifier_register(). */
+ * registered with rtnetlink_notifier_register(). */
 void
 rtnetlink_notifier_unregister(struct rtnetlink_notifier *notifier)
 {
index ca7df7b..8f18805 100644 (file)
@@ -40,7 +40,7 @@ struct rtnetlink_change {
  * specific change.  It may be null if the buffer of change information
  * overflowed, in which case the function must assume that every device may
  * have changed.  'aux' is as specified in the call to
- * lxnetdev_notifier_register().  */
+ * rtnetlink_notifier_register().  */
 typedef void rtnetlink_notify_func(const struct rtnetlink_change *, void *aux);
 
 struct rtnetlink_notifier {
index f73f2d6..205b82f 100644 (file)
@@ -31,7 +31,9 @@
 
 #include <config.h>
 #include "sha1.h"
+#include <ctype.h>
 #include <string.h>
+#include "util.h"
 
 /* a bit faster & bigger, if defined */
 #define UNROLL_LOOPS
@@ -279,3 +281,32 @@ sha1_bytes(const void *data, size_t n, uint8_t digest[SHA1_DIGEST_SIZE])
     sha1_update(&ctx, data, n);
     sha1_final(&ctx, digest);
 }
+
+void
+sha1_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE],
+            char hex[SHA1_HEX_DIGEST_LEN + 1])
+{
+    int i;
+
+    for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
+        *hex++ = "0123456789abcdef"[digest[i] >> 4];
+        *hex++ = "0123456789abcdef"[digest[i] & 15];
+    }
+    *hex = '\0';
+}
+
+bool
+sha1_from_hex(uint8_t digest[SHA1_DIGEST_SIZE], const char *hex)
+{
+    int i;
+
+    for (i = 0; i < SHA1_DIGEST_SIZE; i++) {
+        if (!isxdigit(hex[0]) || !isxdigit(hex[1])) {
+            return false;
+        }
+        digest[i] = (hexit_value(hex[0]) << 4) | hexit_value(hex[1]);
+        hex += 2;
+    }
+    return true;
+}
+
index 75d3533..9a37277 100644 (file)
 #ifndef SHA1_H
 #define SHA1_H
 
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
-/* Size of the SHA1 digest. */
-#define SHA1_DIGEST_SIZE 20
+#define SHA1_DIGEST_SIZE 20     /* Size of the SHA1 digest. */
+#define SHA1_HEX_DIGEST_LEN 40  /* Length of SHA1 digest as hex in ASCII. */
 
 /* SHA1 context structure. */
 struct sha1_ctx {
@@ -48,4 +49,18 @@ void sha1_update(struct sha1_ctx *, const void *, size_t);
 void sha1_final(struct sha1_ctx *, uint8_t digest[SHA1_DIGEST_SIZE]);
 void sha1_bytes(const void *, size_t, uint8_t digest[SHA1_DIGEST_SIZE]);
 
+#define SHA1_FMT \
+        "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" \
+        "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
+#define SHA1_ARGS(DIGEST) \
+    ((DIGEST)[0]), ((DIGEST)[1]), ((DIGEST)[2]), ((DIGEST)[3]), \
+    ((DIGEST)[4]), ((DIGEST)[5]), ((DIGEST)[6]), ((DIGEST)[7]), \
+    ((DIGEST)[8]), ((DIGEST)[9]), ((DIGEST)[10]), ((DIGEST)[11]), \
+    ((DIGEST)[12]), ((DIGEST)[13]), ((DIGEST)[14]), ((DIGEST)[15]), \
+    ((DIGEST)[16]), ((DIGEST)[17]), ((DIGEST)[18]), ((DIGEST)[19])
+
+void sha1_to_hex(const uint8_t digest[SHA1_DIGEST_SIZE],
+                 char hex[SHA1_HEX_DIGEST_LEN + 1]);
+bool sha1_from_hex(uint8_t digest[SHA1_DIGEST_SIZE], const char *hex);
+
 #endif  /* sha1.h */
index da33fe8..a5bfecf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -40,6 +40,18 @@ shash_destroy(struct shash *sh)
     }
 }
 
+void
+shash_swap(struct shash *a, struct shash *b)
+{
+    hmap_swap(&a->map, &b->map);
+}
+
+void
+shash_moved(struct shash *sh)
+{
+    hmap_moved(&sh->map);
+}
+
 void
 shash_clear(struct shash *sh)
 {
@@ -58,18 +70,35 @@ shash_is_empty(const struct shash *shash)
     return hmap_is_empty(&shash->map);
 }
 
+size_t
+shash_count(const struct shash *shash)
+{
+    return hmap_count(&shash->map);
+}
+
 /* It is the caller's responsibility to avoid duplicate names, if that is
  * desirable. */
 struct shash_node *
-shash_add(struct shash *sh, const char *name, void *data)
+shash_add(struct shash *sh, const char *name, const void *data)
 {
     struct shash_node *node = xmalloc(sizeof *node);
     node->name = xstrdup(name);
-    node->data = data;
+    node->data = (void *) data;
     hmap_insert(&sh->map, &node->node, hash_name(name));
     return node;
 }
 
+bool
+shash_add_once(struct shash *sh, const char *name, const void *data)
+{
+    if (!shash_find(sh, name)) {
+        shash_add(sh, name, data);
+        return true;
+    } else {
+        return false;
+    }
+}
+
 void
 shash_delete(struct shash *sh, struct shash_node *node)
 {
@@ -100,6 +129,19 @@ shash_find_data(const struct shash *sh, const char *name)
     return node ? node->data : NULL;
 }
 
+void *
+shash_find_and_delete(struct shash *sh, const char *name)
+{
+    struct shash_node *node = shash_find(sh, name);
+    if (node) {
+        void *data = node->data;
+        shash_delete(sh, node);
+        return data;
+    } else {
+        return NULL;
+    }
+}
+
 struct shash_node *
 shash_first(const struct shash *shash)
 {
@@ -107,3 +149,52 @@ shash_first(const struct shash *shash)
     return node ? CONTAINER_OF(node, struct shash_node, node) : NULL;
 }
 
+static int
+compare_nodes_by_name(const void *a_, const void *b_)
+{
+    const struct shash_node *const *a = a_;
+    const struct shash_node *const *b = b_;
+    return strcmp((*a)->name, (*b)->name);
+}
+
+const struct shash_node **
+shash_sort(const struct shash *sh)
+{
+    if (shash_is_empty(sh)) {
+        return NULL;
+    } else {
+        const struct shash_node **nodes;
+        struct shash_node *node;
+        size_t i, n;
+
+        n = shash_count(sh);
+        nodes = xmalloc(n * sizeof *nodes);
+        i = 0;
+        SHASH_FOR_EACH (node, sh) {
+            nodes[i++] = node;
+        }
+        assert(i == n);
+
+        qsort(nodes, n, sizeof *nodes, compare_nodes_by_name);
+
+        return nodes;
+    }
+}
+
+/* Returns true if 'a' and 'b' contain the same keys (regardless of their
+ * values), false otherwise. */
+bool
+shash_equal_keys(const struct shash *a, const struct shash *b)
+{
+    struct shash_node *node;
+
+    if (hmap_count(&a->map) != hmap_count(&b->map)) {
+        return false;
+    }
+    SHASH_FOR_EACH (node, a) {
+        if (!shash_find(b, node->name)) {
+            return false;
+        }
+    }
+    return true;
+}
index 5794a20..52cd4dc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -40,12 +40,19 @@ struct shash {
 
 void shash_init(struct shash *);
 void shash_destroy(struct shash *);
+void shash_swap(struct shash *, struct shash *);
+void shash_moved(struct shash *);
 void shash_clear(struct shash *);
 bool shash_is_empty(const struct shash *);
-struct shash_node *shash_add(struct shash *, const char *, void *);
+size_t shash_count(const struct shash *);
+struct shash_node *shash_add(struct shash *, const char *, const void *);
+bool shash_add_once(struct shash *, const char *, const void *);
 void shash_delete(struct shash *, struct shash_node *);
 struct shash_node *shash_find(const struct shash *, const char *);
 void *shash_find_data(const struct shash *, const char *);
+void *shash_find_and_delete(struct shash *, const char *);
 struct shash_node *shash_first(const struct shash *);
+const struct shash_node **shash_sort(const struct shash *);
+bool shash_equal_keys(const struct shash *, const struct shash *);
 
 #endif /* shash.h */
index d8e51b6..eabbcc3 100644 (file)
@@ -98,7 +98,7 @@ bool
 signal_poll(struct signal *s)
 {
     char buf[_POSIX_PIPE_BUF];
-    read(fds[0], buf, sizeof buf);
+    ignore(read(fds[0], buf, sizeof buf));
     if (signaled[s->signr]) {
         signaled[s->signr] = 0;
         return true;
@@ -122,7 +122,7 @@ static void
 signal_handler(int signr)
 {
     if (signr >= 1 && signr < N_SIGNALS) {
-        write(fds[1], "", 1);
+        ignore(write(fds[1], "", 1));
         signaled[signr] = true;
     }
 }
index e6a6c70..d2ee8ea 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -249,6 +249,7 @@ make_unix_socket(int style, bool nonblock, bool passcred UNUSED,
         make_sockaddr_un(connect_path, &un, &un_len);
         if (connect(fd, (struct sockaddr*) &un, un_len)
             && errno != EINPROGRESS) {
+            printf("connect failed with %s\n", strerror(errno));
             goto error;
         }
     }
@@ -265,10 +266,10 @@ make_unix_socket(int style, bool nonblock, bool passcred UNUSED,
     return fd;
 
 error:
+    error = errno == EAGAIN ? EPROTO : errno;
     if (bind_path) {
         fatal_signal_remove_file_to_unlink(bind_path);
     }
-    error = errno;
     close(fd);
     return -error;
 }
@@ -384,25 +385,35 @@ exit:
 
 /* Opens a non-blocking IPv4 socket of the specified 'style', binds to
  * 'target', and listens for incoming connections.  'target' should be a string
- * in the format "[<port>][:<ip>]".  <port> may be omitted if 'default_port' is
- * nonzero, in which case it defaults to 'default_port'.  If <ip> is omitted it
- * defaults to the wildcard IP address.
+ * in the format "[<port>][:<ip>]":
+ *
+ *      - If 'default_port' is -1, then <port> is required.  Otherwise, if
+ *        <port> is omitted, then 'default_port' is used instead.
+ *
+ *      - If <port> (or 'default_port', if used) is 0, then no port is bound
+ *        and the TCP/IP stack will select a port.
+ *
+ *      - If <ip> is omitted then the IP address is wildcarded.
  *
  * 'style' should be SOCK_STREAM (for TCP) or SOCK_DGRAM (for UDP).
  *
  * For TCP, the socket will have SO_REUSEADDR turned on.
  *
  * On success, returns a non-negative file descriptor.  On failure, returns a
- * negative errno value. */
+ * negative errno value.
+ *
+ * If 'sinp' is non-null, then on success the bound address is stored into
+ * '*sinp'. */
 int
-inet_open_passive(int style, const char *target_, uint16_t default_port)
+inet_open_passive(int style, const char *target_, int default_port,
+                  struct sockaddr_in *sinp)
 {
     char *target = xstrdup(target_);
     char *string_ptr = target;
     struct sockaddr_in sin;
     const char *host_name;
     const char *port_string;
-    int fd, error;
+    int fd, error, port;
     unsigned int yes  = 1;
 
     /* Address defaults. */
@@ -413,9 +424,9 @@ inet_open_passive(int style, const char *target_, uint16_t default_port)
 
     /* Parse optional port number. */
     port_string = strsep(&string_ptr, ":");
-    if (port_string && atoi(port_string)) {
-        sin.sin_port = htons(atoi(port_string));
-    } else if (!default_port) {
+    if (port_string && str_to_int(port_string, 10, &port)) {
+        sin.sin_port = htons(port);
+    } else if (default_port < 0) {
         VLOG_ERR("%s: port number must be specified", target_);
         error = EAFNOSUPPORT;
         goto exit;
@@ -461,6 +472,21 @@ inet_open_passive(int style, const char *target_, uint16_t default_port)
         VLOG_ERR("%s: listen: %s", target_, strerror(error));
         goto exit_close;
     }
+
+    if (sinp) {
+        socklen_t sin_len = sizeof sin;
+        if (getsockname(fd, (struct sockaddr *) &sin, &sin_len) < 0){
+            error = errno;
+            VLOG_ERR("%s: getsockname: %s", target_, strerror(error));
+            goto exit_close;
+        }
+        if (sin.sin_family != AF_INET || sin_len != sizeof sin) {
+            VLOG_ERR("%s: getsockname: invalid socket name", target_);
+            goto exit_close;
+        }
+        *sinp = sin;
+    }
+
     error = 0;
     goto exit;
 
index 4259115..11f21c2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,7 +36,8 @@ int get_null_fd(void);
 
 int inet_open_active(int style, const char *target, uint16_t default_port,
                     struct sockaddr_in *sinp, int *fdp);
-int inet_open_passive(int style, const char *target, uint16_t default_port);
+int inet_open_passive(int style, const char *target, int default_port,
+                      struct sockaddr_in *sinp);
 
 int read_fully(int fd, void *, size_t, size_t *bytes_read);
 int write_fully(int fd, const void *, size_t, size_t *bytes_written);
diff --git a/lib/sort.c b/lib/sort.c
new file mode 100644 (file)
index 0000000..017b0a9
--- /dev/null
@@ -0,0 +1,70 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "sort.h"
+
+#include "random.h"
+
+static size_t
+partition(size_t p, size_t r,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux)
+{
+    size_t x = r - 1;
+    size_t i, j;
+
+    i = p;
+    for (j = p; j < x; j++) {
+        if (compare(j, x, aux) <= 0) {
+            swap(i++, j, aux);
+        }
+    }
+    swap(i, x, aux);
+    return i;
+}
+
+static void
+quicksort(size_t p, size_t r,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux)
+{
+    size_t i, q;
+
+    if (r - p < 2) {
+        return;
+    }
+
+    i = random_range(r - p) + p;
+    if (r - 1 != i) {
+        swap(r - 1, i, aux);
+    }
+
+    q = partition(p, r, compare, swap, aux);
+    quicksort(p, q, compare, swap, aux);
+    quicksort(q, r, compare, swap, aux);
+}
+
+void
+sort(size_t count,
+     int (*compare)(size_t a, size_t b, void *aux),
+     void (*swap)(size_t a, size_t b, void *aux),
+     void *aux)
+{
+    quicksort(0, count, compare, swap, aux);
+}
similarity index 72%
rename from vswitchd/ovs-vswitchd.h
rename to lib/sort.h
index 3877f88..c952f44 100644 (file)
  * limitations under the License.
  */
 
-#ifndef VSWITCHD_H
-#define VSWITCHD_H 1
+#ifndef SORT_H
+#define SORT_H 1
 
-void reconfigure(void);
+#include <stddef.h>
 
-#endif /* ovs-vswitchd.h */
+void sort(size_t count,
+          int (*compare)(size_t a, size_t b, void *aux),
+          void (*swap)(size_t a, size_t b, void *aux),
+          void *aux);
+
+#endif /* sort.h */
diff --git a/lib/ssl-bootstrap-syn.man b/lib/ssl-bootstrap-syn.man
new file mode 100644 (file)
index 0000000..9425321
--- /dev/null
@@ -0,0 +1,2 @@
+.br
+[\fB\-\-bootstrap\-ca\-cert=\fIcacert.pem]
diff --git a/lib/ssl-bootstrap.man b/lib/ssl-bootstrap.man
new file mode 100644 (file)
index 0000000..178350d
--- /dev/null
@@ -0,0 +1,22 @@
+.IP "\fB\-\-bootstrap\-ca\-cert=\fIcacert.pem\fR"
+When \fIcacert.pem\fR exists, this option has the same effect as
+\fB\-C\fR or \fB\-\-ca\-cert\fR.  If it does not exist, then
+\fB\*(PN\fR will attempt to obtain the CA certificate from the
+SSL peer on its first SSL connection and save it to the named PEM
+file.  If it is successful, it will immediately drop the connection
+and reconnect, and from then on all SSL connections must be
+authenticated by a certificate signed by the CA certificate thus
+obtained.
+.IP
+\fBThis option exposes the SSL connection to a man-in-the-middle
+attack obtaining the initial CA certificate\fR, but it may be useful
+for bootstrapping.
+.IP
+This option is only useful if the SSL peer sends its CA certificate as
+part of the SSL certificate chain.  The SSL protocol does not require
+the controller to send the CA certificate, but
+\fBovs\-controller\fR(8) can be configured to do so with the
+\fB\-\-peer\-ca\-cert\fR option.
+.IP
+This option is mutually exclusive with \fB-C\fR and
+\fB\-\-ca\-cert\fR.
diff --git a/lib/ssl-peer-ca-cert.man b/lib/ssl-peer-ca-cert.man
new file mode 100644 (file)
index 0000000..183f93e
--- /dev/null
@@ -0,0 +1,12 @@
+.IP "\fB--peer-ca-cert=\fIpeer-cacert.pem\fR"
+Specifies a PEM file that contains one or more additional certificates
+to send to SSL peers.  \fIpeer-cacert.pem\fR should be the CA
+certificate used to sign the \fB\*(PN\fR own certificate (the
+certificate specified on \fB-c\fR or \fB--certificate\fR).
+.IP
+This option is not useful in normal operation, because the SSL peer
+must already have the CA certificate for the peer to have any
+confidence in \fB\*(PN\fR's identity.  However, this option allows a
+newly installed switch to obtain the peer CA certificate on first boot
+using, e.g., the \fB\-\-bootstrap-ca-cert\fR option to
+\fBovs\-openflowd\fR(8).
diff --git a/lib/ssl-syn.man b/lib/ssl-syn.man
new file mode 100644 (file)
index 0000000..4914841
--- /dev/null
@@ -0,0 +1,6 @@
+.IP "Public key infrastructure options:"
+[\fB\-\-private\-key=\fIprivkey.pem\fR]
+.br
+[\fB\-\-certificate=\fIcert.pem\fR]
+.br
+[\fB\-\-ca\-cert=\fIswitch\-cacert.pem\fR]
diff --git a/lib/ssl.man b/lib/ssl.man
new file mode 100644 (file)
index 0000000..8e530f4
--- /dev/null
@@ -0,0 +1,20 @@
+.SS "Public Key Infrastructure Options"
+.IP "\fB\-p\fR \fIprivkey.pem\fR"
+.IQ "\fB\-\-private\-key=\fIprivkey.pem\fR"
+Specifies a PEM file containing the private key used as \fB\*(PN\fR's
+identity for outgoing SSL connections.
+
+.IP "\fB\-c\fR \fIcert.pem\fR"
+.IQ "\fB\-\-certificate=\fIcert.pem\fR"
+Specifies a PEM file containing a certificate that certifies the
+private key specified on \fB\-p\fR or \fB\-\-private\-key\fR to be
+trustworthy.  The certificate must be signed by the certificate
+authority (CA) that the peer in SSL connections will use to verify it.
+
+.IP "\fB\-C\fR \fIswitch\-cacert.pem\fR"
+.IQ "\fB\-\-ca\-cert=\fIswitch\-cacert.pem\fR"
+Specifies a PEM file containing the CA certificate that \fB\*(PN\fR
+should use to verify certificates presented to it by SSL peers.  (This
+may be the same certificate that SSL peers use to verify the
+certificate specified on \fB\-c\fR or \fB\-\-certificate\fR, or it may
+beq a different one, depending on the PKI design in use.)
index 87230bd..6fad3a0 100644 (file)
--- a/lib/stp.c
+++ b/lib/stp.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -214,7 +214,7 @@ stp_create(const char *name, stp_identifier bridge_id,
     struct stp *stp;
     struct stp_port *p;
 
-    stp = xcalloc(1, sizeof *stp);
+    stp = xzalloc(sizeof *stp);
     stp->name = xstrdup(name);
     stp->bridge_id = bridge_id;
     if (!(stp->bridge_id >> 48)) {
@@ -256,7 +256,10 @@ stp_create(const char *name, stp_identifier bridge_id,
 void
 stp_destroy(struct stp *stp)
 {
-    free(stp);
+    if (stp) {
+        free(stp->name);
+        free(stp);
+    }
 }
 
 /* Runs 'stp' given that 'ms' milliseconds have passed. */
diff --git a/lib/stream-fd.c b/lib/stream-fd.c
new file mode 100644 (file)
index 0000000..94c8434
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2008, 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream-fd.h"
+#include <assert.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "fatal-signal.h"
+#include "leak-checker.h"
+#include "poll-loop.h"
+#include "socket-util.h"
+#include "util.h"
+#include "stream-provider.h"
+#include "stream.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_stream_fd
+
+/* Active file descriptor stream. */
+
+struct stream_fd
+{
+    struct stream stream;
+    int fd;
+    char *unlink_path;
+};
+
+static struct stream_class stream_fd_class;
+
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
+
+static void maybe_unlink_and_free(char *path);
+
+/* Creates a new stream named 'name' that will send and receive data on 'fd'
+ * and stores a pointer to the stream in '*streamp'.  Initial connection status
+ * 'connect_status' is interpreted as described for stream_init().
+ *
+ * When '*streamp' is closed, then 'unlink_path' (if nonnull) will be passed to
+ * fatal_signal_unlink_file_now() and then freed with free().
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  (The current
+ * implementation never fails.) */
+int
+new_fd_stream(const char *name, int fd, int connect_status,
+              char *unlink_path, struct stream **streamp)
+{
+    struct stream_fd *s;
+
+    s = xmalloc(sizeof *s);
+    stream_init(&s->stream, &stream_fd_class, connect_status, name);
+    s->fd = fd;
+    s->unlink_path = unlink_path;
+    *streamp = &s->stream;
+    return 0;
+}
+
+static struct stream_fd *
+stream_fd_cast(struct stream *stream)
+{
+    stream_assert_class(stream, &stream_fd_class);
+    return CONTAINER_OF(stream, struct stream_fd, stream);
+}
+
+static void
+fd_close(struct stream *stream)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    close(s->fd);
+    maybe_unlink_and_free(s->unlink_path);
+    free(s);
+}
+
+static int
+fd_connect(struct stream *stream)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    return check_connection_completion(s->fd);
+}
+
+static ssize_t
+fd_recv(struct stream *stream, void *buffer, size_t n)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    ssize_t retval = read(s->fd, buffer, n);
+    return retval >= 0 ? retval : -errno;
+}
+
+static ssize_t
+fd_send(struct stream *stream, const void *buffer, size_t n)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    ssize_t retval = write(s->fd, buffer, n);
+    return (retval > 0 ? retval
+            : retval == 0 ? -EAGAIN
+            : -errno);
+}
+
+static void
+fd_wait(struct stream *stream, enum stream_wait_type wait)
+{
+    struct stream_fd *s = stream_fd_cast(stream);
+    switch (wait) {
+    case STREAM_CONNECT:
+    case STREAM_SEND:
+        poll_fd_wait(s->fd, POLLOUT);
+        break;
+
+    case STREAM_RECV:
+        poll_fd_wait(s->fd, POLLIN);
+        break;
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+static struct stream_class stream_fd_class = {
+    "fd",                       /* name */
+    NULL,                       /* open */
+    fd_close,                   /* close */
+    fd_connect,                 /* connect */
+    fd_recv,                    /* recv */
+    fd_send,                    /* send */
+    NULL,                       /* run */
+    NULL,                       /* run_wait */
+    fd_wait,                    /* wait */
+};
+\f
+/* Passive file descriptor stream. */
+
+struct fd_pstream
+{
+    struct pstream pstream;
+    int fd;
+    int (*accept_cb)(int fd, const struct sockaddr *, size_t sa_len,
+                     struct stream **);
+    char *unlink_path;
+};
+
+static struct pstream_class fd_pstream_class;
+
+static struct fd_pstream *
+fd_pstream_cast(struct pstream *pstream)
+{
+    pstream_assert_class(pstream, &fd_pstream_class);
+    return CONTAINER_OF(pstream, struct fd_pstream, pstream);
+}
+
+/* Creates a new pstream named 'name' that will accept new socket connections
+ * on 'fd' and stores a pointer to the stream in '*pstreamp'.
+ *
+ * When a connection has been accepted, 'accept_cb' will be called with the new
+ * socket fd 'fd' and the remote address of the connection 'sa' and 'sa_len'.
+ * accept_cb must return 0 if the connection is successful, in which case it
+ * must initialize '*streamp' to the new stream, or a positive errno value on
+ * error.  In either case accept_cb takes ownership of the 'fd' passed in.
+ *
+ * When '*pstreamp' is closed, then 'unlink_path' (if nonnull) will be passed
+ * to fatal_signal_unlink_file_now() and freed with free().
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  (The current
+ * implementation never fails.) */
+int
+new_fd_pstream(const char *name, int fd,
+               int (*accept_cb)(int fd, const struct sockaddr *sa,
+                                size_t sa_len, struct stream **streamp),
+               char *unlink_path, struct pstream **pstreamp)
+{
+    struct fd_pstream *ps = xmalloc(sizeof *ps);
+    pstream_init(&ps->pstream, &fd_pstream_class, name);
+    ps->fd = fd;
+    ps->accept_cb = accept_cb;
+    ps->unlink_path = unlink_path;
+    *pstreamp = &ps->pstream;
+    return 0;
+}
+
+static void
+pfd_close(struct pstream *pstream)
+{
+    struct fd_pstream *ps = fd_pstream_cast(pstream);
+    close(ps->fd);
+    maybe_unlink_and_free(ps->unlink_path);
+    free(ps);
+}
+
+static int
+pfd_accept(struct pstream *pstream, struct stream **new_streamp)
+{
+    struct fd_pstream *ps = fd_pstream_cast(pstream);
+    struct sockaddr_storage ss;
+    socklen_t ss_len = sizeof ss;
+    int new_fd;
+    int retval;
+
+    new_fd = accept(ps->fd, (struct sockaddr *) &ss, &ss_len);
+    if (new_fd < 0) {
+        int retval = errno;
+        if (retval != EAGAIN) {
+            VLOG_DBG_RL(&rl, "accept: %s", strerror(retval));
+        }
+        return retval;
+    }
+
+    retval = set_nonblocking(new_fd);
+    if (retval) {
+        close(new_fd);
+        return retval;
+    }
+
+    return ps->accept_cb(new_fd, (const struct sockaddr *) &ss, ss_len,
+                         new_streamp);
+}
+
+static void
+pfd_wait(struct pstream *pstream)
+{
+    struct fd_pstream *ps = fd_pstream_cast(pstream);
+    poll_fd_wait(ps->fd, POLLIN);
+}
+
+static struct pstream_class fd_pstream_class = {
+    "pstream",
+    NULL,
+    pfd_close,
+    pfd_accept,
+    pfd_wait
+};
+\f
+/* Helper functions. */
+static void
+maybe_unlink_and_free(char *path)
+{
+    if (path) {
+        fatal_signal_unlink_file_now(path);
+        free(path);
+    }
+}
similarity index 57%
rename from lib/vconn-stream.h
rename to lib/stream-fd.h
index 91904ff..d2a34eb 100644 (file)
  * limitations under the License.
  */
 
-#ifndef VCONN_STREAM_H
-#define VCONN_STREAM_H 1
+#ifndef STREAM_FD_H
+#define STREAM_FD_H 1
 
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
-struct vconn;
-struct pvconn;
+struct stream;
+struct pstream;
 struct sockaddr;
 
-int new_stream_vconn(const char *name, int fd, int connect_status,
-                     char *unlink_path, struct vconn **vconnp);
-int new_pstream_pvconn(const char *name, int fd,
-                      int (*accept_cb)(int fd, const struct sockaddr *,
-                                       size_t sa_len, struct vconn **),
-                      char *unlink_path,
-                      struct pvconn **pvconnp);
+int new_fd_stream(const char *name, int fd, int connect_status,
+                      char *unlink_path, struct stream **streamp);
+int new_fd_pstream(const char *name, int fd,
+                   int (*accept_cb)(int fd, const struct sockaddr *,
+                                    size_t sa_len, struct stream **),
+                   char *unlink_path,
+                   struct pstream **pstreamp);
 
-#endif /* vconn-stream.h */
+#endif /* stream-fd.h */
diff --git a/lib/stream-provider.h b/lib/stream-provider.h
new file mode 100644 (file)
index 0000000..d6bf0a2
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef STREAM_PROVIDER_H
+#define STREAM_PROVIDER_H 1
+
+#include <assert.h>
+#include <sys/types.h>
+#include "stream.h"
+
+/* Active stream connection. */
+
+/* Active stream connection.
+ *
+ * This structure should be treated as opaque by implementation. */
+struct stream {
+    struct stream_class *class;
+    int state;
+    int error;
+    uint32_t remote_ip;
+    uint16_t remote_port;
+    uint32_t local_ip;
+    uint16_t local_port;
+    char *name;
+};
+
+void stream_init(struct stream *, struct stream_class *, int connect_status,
+                 const char *name);
+void stream_set_remote_ip(struct stream *, uint32_t remote_ip);
+void stream_set_remote_port(struct stream *, uint16_t remote_port);
+void stream_set_local_ip(struct stream *, uint32_t local_ip);
+void stream_set_local_port(struct stream *, uint16_t local_port);
+static inline void stream_assert_class(const struct stream *stream,
+                                       const struct stream_class *class)
+{
+    assert(stream->class == class);
+}
+
+struct stream_class {
+    /* Prefix for connection names, e.g. "tcp", "ssl", "unix". */
+    const char *name;
+
+    /* Attempts to connect to a peer.  'name' is the full connection name
+     * provided by the user, e.g. "tcp:1.2.3.4".  This name is useful for error
+     * messages but must not be modified.
+     *
+     * 'suffix' is a copy of 'name' following the colon and may be modified.
+     *
+     * Returns 0 if successful, otherwise a positive errno value.  If
+     * successful, stores a pointer to the new connection in '*streamp'.
+     *
+     * The open function must not block waiting for a connection to complete.
+     * If the connection cannot be completed immediately, it should return
+     * EAGAIN (not EINPROGRESS, as returned by the connect system call) and
+     * continue the connection in the background. */
+    int (*open)(const char *name, char *suffix, struct stream **streamp);
+
+    /* Closes 'stream' and frees associated memory. */
+    void (*close)(struct stream *stream);
+
+    /* Tries to complete the connection on 'stream'.  If 'stream''s connection
+     * is complete, returns 0 if the connection was successful or a positive
+     * errno value if it failed.  If the connection is still in progress,
+     * returns EAGAIN.
+     *
+     * The connect function must not block waiting for the connection to
+     * complete; instead, it should return EAGAIN immediately. */
+    int (*connect)(struct stream *stream);
+
+    /* Tries to receive up to 'n' bytes from 'stream' into 'buffer', and
+     * returns:
+     *
+     *     - If successful, the number of bytes received (between 1 and 'n').
+     *
+     *     - On error, a negative errno value.
+     *
+     *     - 0, if the connection has been closed in the normal fashion.
+     *
+     * The recv function will not be passed a zero 'n'.
+     *
+     * The recv function must not block waiting for data to arrive.  If no data
+     * have been received, it should return -EAGAIN immediately. */
+    ssize_t (*recv)(struct stream *stream, void *buffer, size_t n);
+
+    /* Tries to send up to 'n' bytes of 'buffer' on 'stream', and returns:
+     *
+     *     - If successful, the number of bytes sent (between 1 and 'n').
+     *
+     *     - On error, a negative errno value.
+     *
+     *     - Never returns 0.
+     *
+     * The send function will not be passed a zero 'n'.
+     *
+     * The send function must not block.  If no bytes can be immediately
+     * accepted for transmission, it should return -EAGAIN immediately. */
+    ssize_t (*send)(struct stream *stream, const void *buffer, size_t n);
+
+    /* Allows 'stream' to perform maintenance activities, such as flushing
+     * output buffers.
+     *
+     * May be null if 'stream' doesn't have anything to do here. */
+    void (*run)(struct stream *stream);
+
+    /* Arranges for the poll loop to wake up when 'stream' needs to perform
+     * maintenance activities.
+     *
+     * May be null if 'stream' doesn't have anything to do here. */
+    void (*run_wait)(struct stream *stream);
+
+    /* Arranges for the poll loop to wake up when 'stream' is ready to take an
+     * action of the given 'type'. */
+    void (*wait)(struct stream *stream, enum stream_wait_type type);
+};
+\f
+/* Passive listener for incoming stream connections.
+ *
+ * This structure should be treated as opaque by stream implementations. */
+struct pstream {
+    struct pstream_class *class;
+    char *name;
+};
+
+void pstream_init(struct pstream *, struct pstream_class *, const char *name);
+static inline void pstream_assert_class(const struct pstream *pstream,
+                                        const struct pstream_class *class)
+{
+    assert(pstream->class == class);
+}
+
+struct pstream_class {
+    /* Prefix for connection names, e.g. "ptcp", "pssl", "punix". */
+    const char *name;
+
+    /* Attempts to start listening for stream connections.  'name' is the full
+     * connection name provided by the user, e.g. "ptcp:1234".  This name is
+     * useful for error messages but must not be modified.
+     *
+     * 'suffix' is a copy of 'name' following the colon and may be modified.
+     *
+     * Returns 0 if successful, otherwise a positive errno value.  If
+     * successful, stores a pointer to the new connection in '*pstreamp'.
+     *
+     * The listen function must not block.  If the connection cannot be
+     * completed immediately, it should return EAGAIN (not EINPROGRESS, as
+     * returned by the connect system call) and continue the connection in the
+     * background. */
+    int (*listen)(const char *name, char *suffix, struct pstream **pstreamp);
+
+    /* Closes 'pstream' and frees associated memory. */
+    void (*close)(struct pstream *pstream);
+
+    /* Tries to accept a new connection on 'pstream'.  If successful, stores
+     * the new connection in '*new_streamp' and returns 0.  Otherwise, returns
+     * a positive errno value.
+     *
+     * The accept function must not block waiting for a connection.  If no
+     * connection is ready to be accepted, it should return EAGAIN. */
+    int (*accept)(struct pstream *pstream, struct stream **new_streamp);
+
+    /* Arranges for the poll loop to wake up when a connection is ready to be
+     * accepted on 'pstream'. */
+    void (*wait)(struct pstream *pstream);
+};
+
+/* Active and passive stream classes. */
+extern struct stream_class tcp_stream_class;
+extern struct pstream_class ptcp_pstream_class;
+extern struct stream_class unix_stream_class;
+extern struct pstream_class punix_pstream_class;
+#ifdef HAVE_OPENSSL
+extern struct stream_class ssl_stream_class;
+extern struct pstream_class pssl_pstream_class;
+#endif
+
+#endif /* stream-provider.h */
similarity index 81%
rename from lib/vconn-ssl.c
rename to lib/stream-ssl.c
index 58c54f8..941f779 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
  */
 
 #include <config.h>
-#include "vconn-ssl.h"
+#include "stream-ssl.h"
 #include "dhparams.h"
 #include <assert.h>
 #include <ctype.h>
 #include "socket-util.h"
 #include "socket-util.h"
 #include "util.h"
-#include "vconn-provider.h"
-#include "vconn.h"
+#include "stream-provider.h"
+#include "stream.h"
 
 #include "vlog.h"
-#define THIS_MODULE VLM_vconn_ssl
+#define THIS_MODULE VLM_stream_ssl
 
 /* Active SSL. */
 
@@ -57,17 +57,15 @@ enum session_type {
     SERVER
 };
 
-struct ssl_vconn
+struct ssl_stream
 {
-    struct vconn vconn;
+    struct stream stream;
     enum ssl_state state;
     int connect_error;
     enum session_type type;
     int fd;
     SSL *ssl;
-    struct ofpbuf *rxbuf;
     struct ofpbuf *txbuf;
-    struct poll_waiter *tx_waiter;
 
     /* rx_want and tx_want record the result of the last call to SSL_read()
      * and SSL_write(), respectively:
@@ -97,8 +95,8 @@ struct ssl_vconn
      *    - SSL_read() laters succeeds reading from 'fd' and clears out the
      *      whole receive buffer, so rx_want gets SSL_READING.
      *
-     *    - Client calls vconn_wait(WAIT_RECV) and vconn_wait(WAIT_SEND) and
-     *      blocks.
+     *    - Client calls stream_wait(STREAM_RECV) and stream_wait(STREAM_SEND)
+     *      and blocks.
      *
      *    - Now we're stuck blocking until the peer sends us data, even though
      *      SSL_write() could now succeed, which could easily be a deadlock
@@ -113,8 +111,8 @@ struct ssl_vconn
      *    - SSL_read() blocks, so rx_want gets SSL_READING or SSL_WRITING,
      *      but tx_want gets reset to SSL_NOTHING.
      *
-     *    - Client calls vconn_wait(WAIT_RECV) and vconn_wait(WAIT_SEND) and
-     *      blocks.
+     *    - Client calls stream_wait(STREAM_RECV) and stream_wait(STREAM_SEND)
+     *      and blocks.
      *
      *    - Client wakes up immediately since SSL_NOTHING in tx_want indicates
      *      that no blocking is necessary.
@@ -153,11 +151,10 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
 static int ssl_init(void);
 static int do_ssl_init(void);
 static bool ssl_wants_io(int ssl_error);
-static void ssl_close(struct vconn *);
-static void ssl_clear_txbuf(struct ssl_vconn *);
+static void ssl_close(struct stream *);
+static void ssl_clear_txbuf(struct ssl_stream *);
 static int interpret_ssl_error(const char *function, int ret, int error,
                                int *want);
-static void ssl_tx_poll_callback(int fd, short int revents, void *vconn_);
 static DH *tmp_dh_callback(SSL *ssl, int is_export UNUSED, int keylength);
 static void log_ca_cert(const char *file_name, X509 *cert);
 
@@ -180,13 +177,13 @@ want_to_poll_events(int want)
 }
 
 static int
-new_ssl_vconn(const char *name, int fd, enum session_type type,
+new_ssl_stream(const char *name, int fd, enum session_type type,
               enum ssl_state state, const struct sockaddr_in *remote,
-              struct vconn **vconnp)
+              struct stream **streamp)
 {
     struct sockaddr_in local;
     socklen_t local_len = sizeof local;
-    struct ssl_vconn *sslv;
+    struct ssl_stream *sslv;
     SSL *ssl = NULL;
     int on = 1;
     int retval;
@@ -244,22 +241,20 @@ new_ssl_vconn(const char *name, int fd, enum session_type type,
         SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
     }
 
-    /* Create and return the ssl_vconn. */
+    /* Create and return the ssl_stream. */
     sslv = xmalloc(sizeof *sslv);
-    vconn_init(&sslv->vconn, &ssl_vconn_class, EAGAIN, name);
-    vconn_set_remote_ip(&sslv->vconn, remote->sin_addr.s_addr);
-    vconn_set_remote_port(&sslv->vconn, remote->sin_port);
-    vconn_set_local_ip(&sslv->vconn, local.sin_addr.s_addr);
-    vconn_set_local_port(&sslv->vconn, local.sin_port);
+    stream_init(&sslv->stream, &ssl_stream_class, EAGAIN, name);
+    stream_set_remote_ip(&sslv->stream, remote->sin_addr.s_addr);
+    stream_set_remote_port(&sslv->stream, remote->sin_port);
+    stream_set_local_ip(&sslv->stream, local.sin_addr.s_addr);
+    stream_set_local_port(&sslv->stream, local.sin_port);
     sslv->state = state;
     sslv->type = type;
     sslv->fd = fd;
     sslv->ssl = ssl;
-    sslv->rxbuf = NULL;
     sslv->txbuf = NULL;
-    sslv->tx_waiter = NULL;
     sslv->rx_want = sslv->tx_want = SSL_NOTHING;
-    *vconnp = &sslv->vconn;
+    *streamp = &sslv->stream;
     return 0;
 
 error:
@@ -270,15 +265,15 @@ error:
     return retval;
 }
 
-static struct ssl_vconn *
-ssl_vconn_cast(struct vconn *vconn)
+static struct ssl_stream *
+ssl_stream_cast(struct stream *stream)
 {
-    vconn_assert_class(vconn, &ssl_vconn_class);
-    return CONTAINER_OF(vconn, struct ssl_vconn, vconn);
+    stream_assert_class(stream, &ssl_stream_class);
+    return CONTAINER_OF(stream, struct ssl_stream, stream);
 }
 
 static int
-ssl_open(const char *name, char *suffix, struct vconn **vconnp)
+ssl_open(const char *name, char *suffix, struct stream **streamp)
 {
     struct sockaddr_in sin;
     int error, fd;
@@ -291,7 +286,7 @@ ssl_open(const char *name, char *suffix, struct vconn **vconnp)
     error = inet_open_active(SOCK_STREAM, suffix, OFP_SSL_PORT, &sin, &fd);
     if (fd >= 0) {
         int state = error ? STATE_TCP_CONNECTING : STATE_SSL_CONNECTING;
-        return new_ssl_vconn(name, fd, CLIENT, state, &sin, vconnp);
+        return new_ssl_stream(name, fd, CLIENT, state, &sin, streamp);
     } else {
         VLOG_ERR("%s: connect: %s", name, strerror(error));
         return error;
@@ -299,9 +294,9 @@ ssl_open(const char *name, char *suffix, struct vconn **vconnp)
 }
 
 static int
-do_ca_cert_bootstrap(struct vconn *vconn)
+do_ca_cert_bootstrap(struct stream *stream)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
     STACK_OF(X509) *chain;
     X509 *ca_cert;
     FILE *file;
@@ -386,9 +381,9 @@ do_ca_cert_bootstrap(struct vconn *vconn)
 }
 
 static int
-ssl_connect(struct vconn *vconn)
+ssl_connect(struct stream *stream)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
     int retval;
 
     switch (sslv->state) {
@@ -415,7 +410,7 @@ ssl_connect(struct vconn *vconn)
                 return EPROTO;
             }
         } else if (bootstrap_ca_cert) {
-            return do_ca_cert_bootstrap(vconn);
+            return do_ca_cert_bootstrap(stream);
         } else if ((SSL_get_verify_mode(sslv->ssl)
                     & (SSL_VERIFY_NONE | SSL_VERIFY_PEER))
                    != SSL_VERIFY_PEER) {
@@ -438,12 +433,18 @@ ssl_connect(struct vconn *vconn)
 }
 
 static void
-ssl_close(struct vconn *vconn)
+ssl_close(struct stream *stream)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
-    poll_cancel(sslv->tx_waiter);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
     ssl_clear_txbuf(sslv);
-    ofpbuf_delete(sslv->rxbuf);
+
+    /* Attempt clean shutdown of the SSL connection.  This will work most of
+     * the time, as long as the kernel send buffer has some free space and the
+     * SSL connection isn't renegotiating, etc.  That has to be good enough,
+     * since we don't have any way to continue the close operation in the
+     * background. */
+    SSL_shutdown(sslv->ssl);
+
     SSL_free(sslv->ssl);
     close(sslv->fd);
     free(sslv);
@@ -524,103 +525,47 @@ interpret_ssl_error(const char *function, int ret, int error,
     return EIO;
 }
 
-static int
-ssl_recv(struct vconn *vconn, struct ofpbuf **bufferp)
+static ssize_t
+ssl_recv(struct stream *stream, void *buffer, size_t n)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
-    struct ofpbuf *rx;
-    size_t want_bytes;
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
     int old_state;
     ssize_t ret;
 
-    if (sslv->rxbuf == NULL) {
-        sslv->rxbuf = ofpbuf_new(1564);
-    }
-    rx = sslv->rxbuf;
-
-again:
-    if (sizeof(struct ofp_header) > rx->size) {
-        want_bytes = sizeof(struct ofp_header) - rx->size;
-    } else {
-        struct ofp_header *oh = rx->data;
-        size_t length = ntohs(oh->length);
-        if (length < sizeof(struct ofp_header)) {
-            VLOG_ERR_RL(&rl, "received too-short ofp_header (%zu bytes)",
-                        length);
-            return EPROTO;
-        }
-        want_bytes = length - rx->size;
-        if (!want_bytes) {
-            *bufferp = rx;
-            sslv->rxbuf = NULL;
-            return 0;
-        }
-    }
-    ofpbuf_prealloc_tailroom(rx, want_bytes);
-
     /* Behavior of zero-byte SSL_read is poorly defined. */
-    assert(want_bytes > 0);
+    assert(n > 0);
 
     old_state = SSL_get_state(sslv->ssl);
-    ret = SSL_read(sslv->ssl, ofpbuf_tail(rx), want_bytes);
+    ret = SSL_read(sslv->ssl, buffer, n);
     if (old_state != SSL_get_state(sslv->ssl)) {
         sslv->tx_want = SSL_NOTHING;
-        if (sslv->tx_waiter) {
-            poll_cancel(sslv->tx_waiter);
-            ssl_tx_poll_callback(sslv->fd, POLLIN, vconn);
-        }
     }
     sslv->rx_want = SSL_NOTHING;
 
     if (ret > 0) {
-        rx->size += ret;
-        if (ret == want_bytes) {
-            if (rx->size > sizeof(struct ofp_header)) {
-                *bufferp = rx;
-                sslv->rxbuf = NULL;
-                return 0;
-            } else {
-                goto again;
-            }
-        }
-        return EAGAIN;
+        return ret;
     } else {
         int error = SSL_get_error(sslv->ssl, ret);
         if (error == SSL_ERROR_ZERO_RETURN) {
-            /* Connection closed (EOF). */
-            if (rx->size) {
-                VLOG_WARN_RL(&rl, "SSL_read: unexpected connection close");
-                return EPROTO;
-            } else {
-                return EOF;
-            }
+            return 0;
         } else {
-            return interpret_ssl_error("SSL_read", ret, error, &sslv->rx_want);
+            return -interpret_ssl_error("SSL_read", ret, error,
+                                        &sslv->rx_want);
         }
     }
 }
 
 static void
-ssl_clear_txbuf(struct ssl_vconn *sslv)
+ssl_clear_txbuf(struct ssl_stream *sslv)
 {
     ofpbuf_delete(sslv->txbuf);
     sslv->txbuf = NULL;
-    sslv->tx_waiter = NULL;
-}
-
-static void
-ssl_register_tx_waiter(struct vconn *vconn)
-{
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
-    sslv->tx_waiter = poll_fd_callback(sslv->fd,
-                                       want_to_poll_events(sslv->tx_want),
-                                       ssl_tx_poll_callback, vconn);
 }
 
 static int
-ssl_do_tx(struct vconn *vconn)
+ssl_do_tx(struct stream *stream)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
 
     for (;;) {
         int old_state = SSL_get_state(sslv->ssl);
@@ -647,54 +592,60 @@ ssl_do_tx(struct vconn *vconn)
     }
 }
 
-static void
-ssl_tx_poll_callback(int fd UNUSED, short int revents UNUSED, void *vconn_)
+static ssize_t
+ssl_send(struct stream *stream, const void *buffer, size_t n)
 {
-    struct vconn *vconn = vconn_;
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
-    int error = ssl_do_tx(vconn);
-    if (error != EAGAIN) {
-        ssl_clear_txbuf(sslv);
-    } else {
-        ssl_register_tx_waiter(vconn);
-    }
-}
-
-static int
-ssl_send(struct vconn *vconn, struct ofpbuf *buffer)
-{
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
 
     if (sslv->txbuf) {
-        return EAGAIN;
+        return -EAGAIN;
     } else {
         int error;
 
-        sslv->txbuf = buffer;
-        error = ssl_do_tx(vconn);
+        sslv->txbuf = ofpbuf_clone_data(buffer, n);
+        error = ssl_do_tx(stream);
         switch (error) {
         case 0:
             ssl_clear_txbuf(sslv);
-            return 0;
+            return n;
         case EAGAIN:
             leak_checker_claim(buffer);
-            ssl_register_tx_waiter(vconn);
-            return 0;
+            return n;
         default:
             sslv->txbuf = NULL;
-            return error;
+            return -error;
         }
     }
 }
 
 static void
-ssl_wait(struct vconn *vconn, enum vconn_wait_type wait)
+ssl_run(struct stream *stream)
+{
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
+
+    if (sslv->txbuf && ssl_do_tx(stream) != EAGAIN) {
+        ssl_clear_txbuf(sslv);
+    }
+}
+
+static void
+ssl_run_wait(struct stream *stream)
 {
-    struct ssl_vconn *sslv = ssl_vconn_cast(vconn);
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
+
+    if (sslv->tx_want != SSL_NOTHING) {
+        poll_fd_wait(sslv->fd, want_to_poll_events(sslv->tx_want));
+    }
+}
+
+static void
+ssl_wait(struct stream *stream, enum stream_wait_type wait)
+{
+    struct ssl_stream *sslv = ssl_stream_cast(stream);
 
     switch (wait) {
-    case WAIT_CONNECT:
-        if (vconn_connect(vconn) != EAGAIN) {
+    case STREAM_CONNECT:
+        if (stream_connect(stream) != EAGAIN) {
             poll_immediate_wake();
         } else {
             switch (sslv->state) {
@@ -715,7 +666,7 @@ ssl_wait(struct vconn *vconn, enum vconn_wait_type wait)
         }
         break;
 
-    case WAIT_RECV:
+    case STREAM_RECV:
         if (sslv->rx_want != SSL_NOTHING) {
             poll_fd_wait(sslv->fd, want_to_poll_events(sslv->rx_want));
         } else {
@@ -723,12 +674,13 @@ ssl_wait(struct vconn *vconn, enum vconn_wait_type wait)
         }
         break;
 
-    case WAIT_SEND:
+    case STREAM_SEND:
         if (!sslv->txbuf) {
             /* We have room in our tx queue. */
             poll_immediate_wake();
         } else {
-            /* The call to ssl_tx_poll_callback() will wake us up. */
+            /* stream_run_wait() will do the right thing; don't bother with
+             * redundancy. */
         }
         break;
 
@@ -737,37 +689,41 @@ ssl_wait(struct vconn *vconn, enum vconn_wait_type wait)
     }
 }
 
-struct vconn_class ssl_vconn_class = {
+struct stream_class ssl_stream_class = {
     "ssl",                      /* name */
     ssl_open,                   /* open */
     ssl_close,                  /* close */
     ssl_connect,                /* connect */
     ssl_recv,                   /* recv */
     ssl_send,                   /* send */
+    ssl_run,                    /* run */
+    ssl_run_wait,               /* run_wait */
     ssl_wait,                   /* wait */
 };
 \f
 /* Passive SSL. */
 
-struct pssl_pvconn
+struct pssl_pstream
 {
-    struct pvconn pvconn;
+    struct pstream pstream;
     int fd;
 };
 
-struct pvconn_class pssl_pvconn_class;
+struct pstream_class pssl_pstream_class;
 
-static struct pssl_pvconn *
-pssl_pvconn_cast(struct pvconn *pvconn)
+static struct pssl_pstream *
+pssl_pstream_cast(struct pstream *pstream)
 {
-    pvconn_assert_class(pvconn, &pssl_pvconn_class);
-    return CONTAINER_OF(pvconn, struct pssl_pvconn, pvconn);
+    pstream_assert_class(pstream, &pssl_pstream_class);
+    return CONTAINER_OF(pstream, struct pssl_pstream, pstream);
 }
 
 static int
-pssl_open(const char *name, char *suffix, struct pvconn **pvconnp)
+pssl_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp)
 {
-    struct pssl_pvconn *pssl;
+    struct pssl_pstream *pssl;
+    struct sockaddr_in sin;
+    char bound_name[128];
     int retval;
     int fd;
 
@@ -776,30 +732,32 @@ pssl_open(const char *name, char *suffix, struct pvconn **pvconnp)
         return retval;
     }
 
-    fd = inet_open_passive(SOCK_STREAM, suffix, OFP_SSL_PORT);
+    fd = inet_open_passive(SOCK_STREAM, suffix, OFP_SSL_PORT, &sin);
     if (fd < 0) {
         return -fd;
     }
+    sprintf(bound_name, "pssl:%"PRIu16":"IP_FMT,
+            ntohs(sin.sin_port), IP_ARGS(&sin.sin_addr.s_addr));
 
     pssl = xmalloc(sizeof *pssl);
-    pvconn_init(&pssl->pvconn, &pssl_pvconn_class, name);
+    pstream_init(&pssl->pstream, &pssl_pstream_class, bound_name);
     pssl->fd = fd;
-    *pvconnp = &pssl->pvconn;
+    *pstreamp = &pssl->pstream;
     return 0;
 }
 
 static void
-pssl_close(struct pvconn *pvconn)
+pssl_close(struct pstream *pstream)
 {
-    struct pssl_pvconn *pssl = pssl_pvconn_cast(pvconn);
+    struct pssl_pstream *pssl = pssl_pstream_cast(pstream);
     close(pssl->fd);
     free(pssl);
 }
 
 static int
-pssl_accept(struct pvconn *pvconn, struct vconn **new_vconnp)
+pssl_accept(struct pstream *pstream, struct stream **new_streamp)
 {
-    struct pssl_pvconn *pssl = pssl_pvconn_cast(pvconn);
+    struct pssl_pstream *pssl = pssl_pstream_cast(pstream);
     struct sockaddr_in sin;
     socklen_t sin_len = sizeof sin;
     char name[128];
@@ -825,18 +783,18 @@ pssl_accept(struct pvconn *pvconn, struct vconn **new_vconnp)
     if (sin.sin_port != htons(OFP_SSL_PORT)) {
         sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin.sin_port));
     }
-    return new_ssl_vconn(name, new_fd, SERVER, STATE_SSL_CONNECTING, &sin,
-                         new_vconnp);
+    return new_ssl_stream(name, new_fd, SERVER, STATE_SSL_CONNECTING, &sin,
+                         new_streamp);
 }
 
 static void
-pssl_wait(struct pvconn *pvconn)
+pssl_wait(struct pstream *pstream)
 {
-    struct pssl_pvconn *pssl = pssl_pvconn_cast(pvconn);
+    struct pssl_pstream *pssl = pssl_pstream_cast(pstream);
     poll_fd_wait(pssl->fd, POLLIN);
 }
 
-struct pvconn_class pssl_pvconn_class = {
+struct pstream_class pssl_pstream_class = {
     "pssl",
     pssl_open,
     pssl_close,
@@ -932,13 +890,13 @@ tmp_dh_callback(SSL *ssl UNUSED, int is_export UNUSED, int keylength)
 
 /* Returns true if SSL is at least partially configured. */
 bool
-vconn_ssl_is_configured(void) 
+stream_ssl_is_configured(void) 
 {
     return has_private_key || has_certificate || has_ca_cert;
 }
 
 void
-vconn_ssl_set_private_key_file(const char *file_name)
+stream_ssl_set_private_key_file(const char *file_name)
 {
     if (ssl_init()) {
         return;
@@ -952,7 +910,7 @@ vconn_ssl_set_private_key_file(const char *file_name)
 }
 
 void
-vconn_ssl_set_certificate_file(const char *file_name)
+stream_ssl_set_certificate_file(const char *file_name)
 {
     if (ssl_init()) {
         return;
@@ -1033,7 +991,7 @@ read_cert_file(const char *file_name, X509 ***certs, size_t *n_certs)
  * certificate to the peer, which enables a switch to pick up the controller's
  * CA certificate on its first connection. */
 void
-vconn_ssl_set_peer_ca_cert_file(const char *file_name)
+stream_ssl_set_peer_ca_cert_file(const char *file_name)
 {
     X509 **certs;
     size_t n_certs;
@@ -1088,7 +1046,7 @@ log_ca_cert(const char *file_name, X509 *cert)
  * read if it is exists; if it does not, then it will be created from the CA
  * certificate received from the peer on the first SSL connection. */
 void
-vconn_ssl_set_ca_cert_file(const char *file_name, bool bootstrap)
+stream_ssl_set_ca_cert_file(const char *file_name, bool bootstrap)
 {
     X509 **certs;
     size_t n_certs;
@@ -1116,6 +1074,7 @@ vconn_ssl_set_ca_cert_file(const char *file_name, bool bootstrap)
             }
             X509_free(certs[i]);
         }
+        free(certs);
 
         /* Set up CAs for OpenSSL to trust in verifying the peer's
          * certificate. */
similarity index 65%
rename from lib/vconn-ssl.h
rename to lib/stream-ssl.h
index 564d303..dd2a16e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2008, 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#ifndef VCONN_SSL_H
-#define VCONN_SSL_H 1
+#ifndef STREAM_SSL_H
+#define STREAM_SSL_H 1
 
 #include <stdbool.h>
 
 #ifdef HAVE_OPENSSL
-bool vconn_ssl_is_configured(void);
-void vconn_ssl_set_private_key_file(const char *file_name);
-void vconn_ssl_set_certificate_file(const char *file_name);
-void vconn_ssl_set_ca_cert_file(const char *file_name, bool bootstrap);
-void vconn_ssl_set_peer_ca_cert_file(const char *file_name);
+bool stream_ssl_is_configured(void);
+void stream_ssl_set_private_key_file(const char *file_name);
+void stream_ssl_set_certificate_file(const char *file_name);
+void stream_ssl_set_ca_cert_file(const char *file_name, bool bootstrap);
+void stream_ssl_set_peer_ca_cert_file(const char *file_name);
 
 /* Define the long options for SSL support.
  *
  * Note that the definition includes a final comma, and therefore a comma 
  * must not be supplied when using the definition.  This is done so that 
  * compilation succeeds whether or not HAVE_OPENSSL is defined. */
-#define VCONN_SSL_LONG_OPTIONS                      \
+#define STREAM_SSL_LONG_OPTIONS                      \
         {"private-key", required_argument, 0, 'p'}, \
         {"certificate", required_argument, 0, 'c'}, \
         {"ca-cert",     required_argument, 0, 'C'},
 
-#define VCONN_SSL_OPTION_HANDLERS                       \
+#define STREAM_SSL_OPTION_HANDLERS                      \
         case 'p':                                       \
-            vconn_ssl_set_private_key_file(optarg);     \
+            stream_ssl_set_private_key_file(optarg);    \
             break;                                      \
                                                         \
         case 'c':                                       \
-            vconn_ssl_set_certificate_file(optarg);     \
+            stream_ssl_set_certificate_file(optarg);    \
             break;                                      \
                                                         \
         case 'C':                                       \
-            vconn_ssl_set_ca_cert_file(optarg, false);  \
+            stream_ssl_set_ca_cert_file(optarg, false); \
             break;
 #else /* !HAVE_OPENSSL */
-static inline bool vconn_ssl_is_configured(void) 
+static inline bool stream_ssl_is_configured(void) 
 {
     return false;
 }
-#define VCONN_SSL_LONG_OPTIONS
-#define VCONN_SSL_OPTION_HANDLERS
+#define STREAM_SSL_LONG_OPTIONS
+#define STREAM_SSL_OPTION_HANDLERS
 #endif /* !HAVE_OPENSSL */
 
-#endif /* vconn-ssl.h */
+#endif /* stream-ssl.h */
similarity index 61%
rename from lib/vconn-tcp.c
rename to lib/stream-tcp.c
index aac7166..e690e9c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
  */
 
 #include <config.h>
-#include "vconn.h"
+#include "stream.h"
 #include <errno.h>
 #include <inttypes.h>
 #include <sys/types.h>
 #include "packets.h"
 #include "socket-util.h"
 #include "util.h"
-#include "openflow/openflow.h"
-#include "vconn-provider.h"
-#include "vconn-stream.h"
+#include "stream-provider.h"
+#include "stream-fd.h"
 
 #include "vlog.h"
-#define THIS_MODULE VLM_vconn_tcp
+#define THIS_MODULE VLM_stream_tcp
 
 /* Active TCP. */
 
 static int
-new_tcp_vconn(const char *name, int fd, int connect_status,
-              const struct sockaddr_in *remote, struct vconn **vconnp)
+new_tcp_stream(const char *name, int fd, int connect_status,
+              const struct sockaddr_in *remote, struct stream **streamp)
 {
     struct sockaddr_in local;
     socklen_t local_len = sizeof local;
@@ -58,79 +57,83 @@ new_tcp_vconn(const char *name, int fd, int connect_status,
         return errno;
     }
 
-    retval = new_stream_vconn(name, fd, connect_status, NULL, vconnp);
+    retval = new_fd_stream(name, fd, connect_status, NULL, streamp);
     if (!retval) {
-        struct vconn *vconn = *vconnp;
-        vconn_set_remote_ip(vconn, remote->sin_addr.s_addr);
-        vconn_set_remote_port(vconn, remote->sin_port);
-        vconn_set_local_ip(vconn, local.sin_addr.s_addr);
-        vconn_set_local_port(vconn, local.sin_port);
+        struct stream *stream = *streamp;
+        stream_set_remote_ip(stream, remote->sin_addr.s_addr);
+        stream_set_remote_port(stream, remote->sin_port);
+        stream_set_local_ip(stream, local.sin_addr.s_addr);
+        stream_set_local_port(stream, local.sin_port);
     }
     return retval;
 }
 
 static int
-tcp_open(const char *name, char *suffix, struct vconn **vconnp)
+tcp_open(const char *name, char *suffix, struct stream **streamp)
 {
     struct sockaddr_in sin;
     int fd, error;
 
-    error = inet_open_active(SOCK_STREAM, suffix, OFP_TCP_PORT, &sin, &fd);
+    error = inet_open_active(SOCK_STREAM, suffix, 0, &sin, &fd);
     if (fd >= 0) {
-        return new_tcp_vconn(name, fd, error, &sin, vconnp);
+        return new_tcp_stream(name, fd, error, &sin, streamp);
     } else {
         VLOG_ERR("%s: connect: %s", name, strerror(error));
         return error;
     }
 }
 
-struct vconn_class tcp_vconn_class = {
+struct stream_class tcp_stream_class = {
     "tcp",                      /* name */
     tcp_open,                   /* open */
     NULL,                       /* close */
     NULL,                       /* connect */
     NULL,                       /* recv */
     NULL,                       /* send */
+    NULL,                       /* run */
+    NULL,                       /* run_wait */
     NULL,                       /* wait */
 };
 \f
 /* Passive TCP. */
 
 static int ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len,
-                       struct vconn **vconnp);
+                       struct stream **streamp);
 
 static int
-ptcp_open(const char *name UNUSED, char *suffix, struct pvconn **pvconnp)
+ptcp_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp)
 {
+    struct sockaddr_in sin;
+    char bound_name[128];
     int fd;
 
-    fd = inet_open_passive(SOCK_STREAM, suffix, OFP_TCP_PORT);
+    fd = inet_open_passive(SOCK_STREAM, suffix, -1, &sin);
     if (fd < 0) {
         return -fd;
-    } else {
-        return new_pstream_pvconn("ptcp", fd, ptcp_accept, NULL, pvconnp);
     }
+
+    sprintf(bound_name, "ptcp:%"PRIu16":"IP_FMT,
+            ntohs(sin.sin_port), IP_ARGS(&sin.sin_addr.s_addr));
+    return new_fd_pstream(bound_name, fd, ptcp_accept, NULL, pstreamp);
 }
 
 static int
 ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len,
-            struct vconn **vconnp)
+            struct stream **streamp)
 {
     const struct sockaddr_in *sin = (const struct sockaddr_in *) sa;
     char name[128];
 
     if (sa_len == sizeof(struct sockaddr_in) && sin->sin_family == AF_INET) {
         sprintf(name, "tcp:"IP_FMT, IP_ARGS(&sin->sin_addr));
-        if (sin->sin_port != htons(OFP_TCP_PORT)) {
-            sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin->sin_port));
-        }
+        sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin->sin_port));
     } else {
         strcpy(name, "tcp");
     }
-    return new_tcp_vconn(name, fd, 0, sin, vconnp);
+    return new_tcp_stream(name, fd, 0, sin, streamp);
 }
 
-struct pvconn_class ptcp_pvconn_class = {
+struct pstream_class ptcp_pstream_class = {
     "ptcp",
     ptcp_open,
     NULL,
similarity index 74%
rename from lib/vconn-unix.c
rename to lib/stream-unix.c
index f24c846..6ce7790 100644 (file)
@@ -15,7 +15,7 @@
  */
 
 #include <config.h>
-#include "vconn.h"
+#include "stream.h"
 #include <assert.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include "ofpbuf.h"
-#include "openflow/openflow.h"
 #include "packets.h"
 #include "poll-loop.h"
 #include "socket-util.h"
 #include "util.h"
-#include "vconn-provider.h"
-#include "vconn-stream.h"
+#include "stream-provider.h"
+#include "stream-fd.h"
 
 #include "vlog.h"
-#define THIS_MODULE VLM_vconn_unix
+#define THIS_MODULE VLM_stream_unix
 
 /* Active UNIX socket. */
 
 static int n_unix_sockets;
 
 static int
-unix_open(const char *name, char *suffix, struct vconn **vconnp)
+unix_open(const char *name, char *suffix, struct stream **streamp)
 {
     const char *connect_path = suffix;
     char *bind_path;
     int fd;
 
-    bind_path = xasprintf("/tmp/vconn-unix.%ld.%d",
+    bind_path = xasprintf("/tmp/stream-unix.%ld.%d",
                           (long int) getpid(), n_unix_sockets++);
     fd = make_unix_socket(SOCK_STREAM, true, false, bind_path, connect_path);
     if (fd < 0) {
@@ -60,27 +58,29 @@ unix_open(const char *name, char *suffix, struct vconn **vconnp)
         return -fd;
     }
 
-    return new_stream_vconn(name, fd, check_connection_completion(fd),
-                            bind_path, vconnp);
+    return new_fd_stream(name, fd, check_connection_completion(fd),
+                         bind_path, streamp);
 }
 
-struct vconn_class unix_vconn_class = {
+struct stream_class unix_stream_class = {
     "unix",                     /* name */
     unix_open,                  /* open */
     NULL,                       /* close */
     NULL,                       /* connect */
     NULL,                       /* recv */
     NULL,                       /* send */
+    NULL,                       /* run */
+    NULL,                       /* run_wait */
     NULL,                       /* wait */
 };
 \f
 /* Passive UNIX socket. */
 
 static int punix_accept(int fd, const struct sockaddr *sa, size_t sa_len,
-                        struct vconn **vconnp);
+                        struct stream **streamp);
 
 static int
-punix_open(const char *name UNUSED, char *suffix, struct pvconn **pvconnp)
+punix_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp)
 {
     int fd, error;
 
@@ -90,12 +90,6 @@ punix_open(const char *name UNUSED, char *suffix, struct pvconn **pvconnp)
         return errno;
     }
 
-    error = set_nonblocking(fd);
-    if (error) {
-        close(fd);
-        return error;
-    }
-
     if (listen(fd, 10) < 0) {
         error = errno;
         VLOG_ERR("%s: listen: %s", name, strerror(error));
@@ -103,13 +97,13 @@ punix_open(const char *name UNUSED, char *suffix, struct pvconn **pvconnp)
         return error;
     }
 
-    return new_pstream_pvconn("punix", fd, punix_accept,
-                              xstrdup(suffix), pvconnp);
+    return new_fd_pstream("punix", fd, punix_accept,
+                          xstrdup(suffix), pstreamp);
 }
 
 static int
 punix_accept(int fd, const struct sockaddr *sa, size_t sa_len,
-             struct vconn **vconnp)
+             struct stream **streamp)
 {
     const struct sockaddr_un *sun = (const struct sockaddr_un *) sa;
     int name_len = get_unix_name_len(sa_len);
@@ -120,10 +114,10 @@ punix_accept(int fd, const struct sockaddr *sa, size_t sa_len,
     } else {
         strcpy(name, "unix");
     }
-    return new_stream_vconn(name, fd, 0, NULL, vconnp);
+    return new_fd_stream(name, fd, 0, NULL, streamp);
 }
 
-struct pvconn_class punix_pvconn_class = {
+struct pstream_class punix_pstream_class = {
     "punix",
     punix_open,
     NULL,
diff --git a/lib/stream.c b/lib/stream.c
new file mode 100644 (file)
index 0000000..dcd8da5
--- /dev/null
@@ -0,0 +1,563 @@
+/*
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "stream-provider.h"
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include "coverage.h"
+#include "dynamic-string.h"
+#include "flow.h"
+#include "ofp-print.h"
+#include "ofpbuf.h"
+#include "openflow/nicira-ext.h"
+#include "openflow/openflow.h"
+#include "packets.h"
+#include "poll-loop.h"
+#include "random.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_stream
+#include "vlog.h"
+
+/* State of an active stream.*/
+enum stream_state {
+    SCS_CONNECTING,             /* Underlying stream is not connected. */
+    SCS_CONNECTED,              /* Connection established. */
+    SCS_DISCONNECTED            /* Connection failed or connection closed. */
+};
+
+static struct stream_class *stream_classes[] = {
+    &tcp_stream_class,
+    &unix_stream_class,
+#ifdef HAVE_OPENSSL
+    &ssl_stream_class,
+#endif
+};
+
+static struct pstream_class *pstream_classes[] = {
+    &ptcp_pstream_class,
+    &punix_pstream_class,
+#ifdef HAVE_OPENSSL
+    &pssl_pstream_class,
+#endif
+};
+
+/* Check the validity of the stream class structures. */
+static void
+check_stream_classes(void)
+{
+#ifndef NDEBUG
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE(stream_classes); i++) {
+        struct stream_class *class = stream_classes[i];
+        assert(class->name != NULL);
+        assert(class->open != NULL);
+        if (class->close || class->recv || class->send || class->run
+            || class->run_wait || class->wait) {
+            assert(class->close != NULL);
+            assert(class->recv != NULL);
+            assert(class->send != NULL);
+            assert(class->wait != NULL);
+        } else {
+            /* This class delegates to another one. */
+        }
+    }
+
+    for (i = 0; i < ARRAY_SIZE(pstream_classes); i++) {
+        struct pstream_class *class = pstream_classes[i];
+        assert(class->name != NULL);
+        assert(class->listen != NULL);
+        if (class->close || class->accept || class->wait) {
+            assert(class->close != NULL);
+            assert(class->accept != NULL);
+            assert(class->wait != NULL);
+        } else {
+            /* This class delegates to another one. */
+        }
+    }
+#endif
+}
+
+/* Prints information on active (if 'active') and passive (if 'passive')
+ * connection methods supported by the stream. */
+void
+stream_usage(const char *name, bool active, bool passive,
+             bool bootstrap UNUSED)
+{
+    /* Really this should be implemented via callbacks into the stream
+     * providers, but that seems too heavy-weight to bother with at the
+     * moment. */
+
+    printf("\n");
+    if (active) {
+        printf("Active %s connection methods:\n", name);
+        printf("  tcp:IP:PORT             "
+               "PORT at remote IP\n");
+#ifdef HAVE_OPENSSL
+        printf("  ssl:IP:PORT             "
+               "SSL PORT at remote IP\n");
+#endif
+        printf("  unix:FILE               "
+               "Unix domain socket named FILE\n");
+    }
+
+    if (passive) {
+        printf("Passive %s connection methods:\n", name);
+        printf("  ptcp:PORT[:IP]          "
+               "listen to TCP PORT on IP\n");
+#ifdef HAVE_OPENSSL
+        printf("  pssl:PORT[:IP]          "
+               "listen for SSL on PORT on IP\n");
+#endif
+        printf("  punix:FILE              "
+               "listen on Unix domain socket FILE\n");
+    }
+
+#ifdef HAVE_OPENSSL
+    printf("PKI configuration (required to use SSL):\n"
+           "  -p, --private-key=FILE  file with private key\n"
+           "  -c, --certificate=FILE  file with certificate for private key\n"
+           "  -C, --ca-cert=FILE      file with peer CA certificate\n");
+    if (bootstrap) {
+        printf("  --bootstrap-ca-cert=FILE  file with peer CA certificate "
+               "to read or create\n");
+    }
+#endif
+}
+
+/* Attempts to connect a stream to a remote peer.  'name' is a connection name
+ * in the form "TYPE:ARGS", where TYPE is an active stream class's name and
+ * ARGS are stream class-specific.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  If successful,
+ * stores a pointer to the new connection in '*streamp', otherwise a null
+ * pointer.  */
+int
+stream_open(const char *name, struct stream **streamp)
+{
+    size_t prefix_len;
+    size_t i;
+
+    COVERAGE_INC(stream_open);
+    check_stream_classes();
+
+    *streamp = NULL;
+    prefix_len = strcspn(name, ":");
+    if (prefix_len == strlen(name)) {
+        return EAFNOSUPPORT;
+    }
+    for (i = 0; i < ARRAY_SIZE(stream_classes); i++) {
+        struct stream_class *class = stream_classes[i];
+        if (strlen(class->name) == prefix_len
+            && !memcmp(class->name, name, prefix_len)) {
+            struct stream *stream;
+            char *suffix_copy = xstrdup(name + prefix_len + 1);
+            int retval = class->open(name, suffix_copy, &stream);
+            free(suffix_copy);
+            if (!retval) {
+                assert(stream->state != SCS_CONNECTING
+                       || stream->class->connect);
+                *streamp = stream;
+            }
+            return retval;
+        }
+    }
+    return EAFNOSUPPORT;
+}
+
+int
+stream_open_block(const char *name, struct stream **streamp)
+{
+    struct stream *stream;
+    int error;
+
+    error = stream_open(name, &stream);
+    while (error == EAGAIN) {
+        stream_run(stream);
+        stream_run_wait(stream);
+        stream_connect_wait(stream);
+        poll_block();
+        error = stream_connect(stream);
+        assert(error != EINPROGRESS);
+    }
+    if (error) {
+        stream_close(stream);
+        *streamp = NULL;
+    } else {
+        *streamp = stream;
+    }
+    return error;
+}
+
+/* Closes 'stream'. */
+void
+stream_close(struct stream *stream)
+{
+    if (stream != NULL) {
+        char *name = stream->name;
+        (stream->class->close)(stream);
+        free(name);
+    }
+}
+
+/* Returns the name of 'stream', that is, the string passed to
+ * stream_open(). */
+const char *
+stream_get_name(const struct stream *stream)
+{
+    return stream ? stream->name : "(null)";
+}
+
+/* Returns the IP address of the peer, or 0 if the peer is not connected over
+ * an IP-based protocol or if its IP address is not yet known. */
+uint32_t
+stream_get_remote_ip(const struct stream *stream)
+{
+    return stream->remote_ip;
+}
+
+/* Returns the transport port of the peer, or 0 if the connection does not
+ * contain a port or if the port is not yet known. */
+uint16_t
+stream_get_remote_port(const struct stream *stream)
+{
+    return stream->remote_port;
+}
+
+/* Returns the IP address used to connect to the peer, or 0 if the connection
+ * is not an IP-based protocol or if its IP address is not yet known. */
+uint32_t
+stream_get_local_ip(const struct stream *stream)
+{
+    return stream->local_ip;
+}
+
+/* Returns the transport port used to connect to the peer, or 0 if the
+ * connection does not contain a port or if the port is not yet known. */
+uint16_t
+stream_get_local_port(const struct stream *stream)
+{
+    return stream->local_port;
+}
+
+static void
+scs_connecting(struct stream *stream)
+{
+    int retval = (stream->class->connect)(stream);
+    assert(retval != EINPROGRESS);
+    if (!retval) {
+        stream->state = SCS_CONNECTED;
+    } else if (retval != EAGAIN) {
+        stream->state = SCS_DISCONNECTED;
+        stream->error = retval;
+    }
+}
+
+/* Tries to complete the connection on 'stream', which must be an active
+ * stream.  If 'stream''s connection is complete, returns 0 if the connection
+ * was successful or a positive errno value if it failed.  If the
+ * connection is still in progress, returns EAGAIN. */
+int
+stream_connect(struct stream *stream)
+{
+    enum stream_state last_state;
+
+    do {
+        last_state = stream->state;
+        switch (stream->state) {
+        case SCS_CONNECTING:
+            scs_connecting(stream);
+            break;
+
+        case SCS_CONNECTED:
+            return 0;
+
+        case SCS_DISCONNECTED:
+            return stream->error;
+
+        default:
+            NOT_REACHED();
+        }
+    } while (stream->state != last_state);
+
+    return EAGAIN;
+}
+
+/* Tries to receive up to 'n' bytes from 'stream' into 'buffer', and returns:
+ *
+ *     - If successful, the number of bytes received (between 1 and 'n').
+ *
+ *     - On error, a negative errno value.
+ *
+ *     - 0, if the connection has been closed in the normal fashion, or if 'n'
+ *       is zero.
+ *
+ * The recv function will not block waiting for a packet to arrive.  If no
+ * data have been received, it returns -EAGAIN immediately. */
+int
+stream_recv(struct stream *stream, void *buffer, size_t n)
+{
+    int retval = stream_connect(stream);
+    return (retval ? -retval
+            : n == 0 ? 0
+            : (stream->class->recv)(stream, buffer, n));
+}
+
+/* Tries to send up to 'n' bytes of 'buffer' on 'stream', and returns:
+ *
+ *     - If successful, the number of bytes sent (between 1 and 'n').  0 is
+ *       only a valid return value if 'n' is 0.
+ *
+ *     - On error, a negative errno value.
+ *
+ * The send function will not block.  If no bytes can be immediately accepted
+ * for transmission, it returns -EAGAIN immediately. */
+int
+stream_send(struct stream *stream, const void *buffer, size_t n)
+{
+    int retval = stream_connect(stream);
+    return (retval ? -retval
+            : n == 0 ? 0
+            : (stream->class->send)(stream, buffer, n));
+}
+
+/* Allows 'stream' to perform maintenance activities, such as flushing
+ * output buffers. */
+void
+stream_run(struct stream *stream)
+{
+    if (stream->class->run) {
+        (stream->class->run)(stream);
+    }
+}
+
+/* Arranges for the poll loop to wake up when 'stream' needs to perform
+ * maintenance activities. */
+void
+stream_run_wait(struct stream *stream)
+{
+    if (stream->class->run_wait) {
+        (stream->class->run_wait)(stream);
+    }
+}
+
+/* Arranges for the poll loop to wake up when 'stream' is ready to take an
+ * action of the given 'type'. */
+void
+stream_wait(struct stream *stream, enum stream_wait_type wait)
+{
+    assert(wait == STREAM_CONNECT || wait == STREAM_RECV
+           || wait == STREAM_SEND);
+
+    switch (stream->state) {
+    case SCS_CONNECTING:
+        wait = STREAM_CONNECT;
+        break;
+
+    case SCS_DISCONNECTED:
+        poll_immediate_wake();
+        return;
+    }
+    (stream->class->wait)(stream, wait);
+}
+
+void
+stream_connect_wait(struct stream *stream)
+{
+    stream_wait(stream, STREAM_CONNECT);
+}
+
+void
+stream_recv_wait(struct stream *stream)
+{
+    stream_wait(stream, STREAM_RECV);
+}
+
+void
+stream_send_wait(struct stream *stream)
+{
+    stream_wait(stream, STREAM_SEND);
+}
+
+/* Attempts to start listening for remote stream connections.  'name' is a
+ * connection name in the form "TYPE:ARGS", where TYPE is an passive stream
+ * class's name and ARGS are stream class-specific.
+ *
+ * Returns 0 if successful, otherwise a positive errno value.  If successful,
+ * stores a pointer to the new connection in '*pstreamp', otherwise a null
+ * pointer.  */
+int
+pstream_open(const char *name, struct pstream **pstreamp)
+{
+    size_t prefix_len;
+    size_t i;
+
+    check_stream_classes();
+
+    *pstreamp = NULL;
+    prefix_len = strcspn(name, ":");
+    if (prefix_len == strlen(name)) {
+        return EAFNOSUPPORT;
+    }
+    for (i = 0; i < ARRAY_SIZE(pstream_classes); i++) {
+        struct pstream_class *class = pstream_classes[i];
+        if (strlen(class->name) == prefix_len
+            && !memcmp(class->name, name, prefix_len)) {
+            char *suffix_copy = xstrdup(name + prefix_len + 1);
+            int retval = class->listen(name, suffix_copy, pstreamp);
+            free(suffix_copy);
+            if (retval) {
+                *pstreamp = NULL;
+            }
+            return retval;
+        }
+    }
+    return EAFNOSUPPORT;
+}
+
+/* Returns the name that was used to open 'pstream'.  The caller must not
+ * modify or free the name. */
+const char *
+pstream_get_name(const struct pstream *pstream)
+{
+    return pstream->name;
+}
+
+/* Closes 'pstream'. */
+void
+pstream_close(struct pstream *pstream)
+{
+    if (pstream != NULL) {
+        char *name = pstream->name;
+        (pstream->class->close)(pstream);
+        free(name);
+    }
+}
+
+/* Tries to accept a new connection on 'pstream'.  If successful, stores the
+ * new connection in '*new_stream' and returns 0.  Otherwise, returns a
+ * positive errno value.
+ *
+ * pstream_accept() will not block waiting for a connection.  If no connection
+ * is ready to be accepted, it returns EAGAIN immediately. */
+int
+pstream_accept(struct pstream *pstream, struct stream **new_stream)
+{
+    int retval = (pstream->class->accept)(pstream, new_stream);
+    if (retval) {
+        *new_stream = NULL;
+    } else {
+        assert((*new_stream)->state != SCS_CONNECTING
+               || (*new_stream)->class->connect);
+    }
+    return retval;
+}
+
+/* Tries to accept a new connection on 'pstream'.  If successful, stores the
+ * new connection in '*new_stream' and returns 0.  Otherwise, returns a
+ * positive errno value.
+ *
+ * pstream_accept_block() blocks until a connection is ready or until an error
+ * occurs.  It will not return EAGAIN. */
+int
+pstream_accept_block(struct pstream *pstream, struct stream **new_stream)
+{
+    int error;
+
+    while ((error = pstream_accept(pstream, new_stream)) == EAGAIN) {
+        pstream_wait(pstream);
+        poll_block();
+    }
+    if (error) {
+        *new_stream = NULL;
+    }
+    return error;
+}
+
+void
+pstream_wait(struct pstream *pstream)
+{
+    (pstream->class->wait)(pstream);
+}
+\f
+/* Initializes 'stream' as a new stream named 'name', implemented via 'class'.
+ * The initial connection status, supplied as 'connect_status', is interpreted
+ * as follows:
+ *
+ *      - 0: 'stream' is connected.  Its 'send' and 'recv' functions may be
+ *        called in the normal fashion.
+ *
+ *      - EAGAIN: 'stream' is trying to complete a connection.  Its 'connect'
+ *        function should be called to complete the connection.
+ *
+ *      - Other positive errno values indicate that the connection failed with
+ *        the specified error.
+ *
+ * After calling this function, stream_close() must be used to destroy
+ * 'stream', otherwise resources will be leaked.
+ *
+ * The caller retains ownership of 'name'. */
+void
+stream_init(struct stream *stream, struct stream_class *class,
+            int connect_status, const char *name)
+{
+    stream->class = class;
+    stream->state = (connect_status == EAGAIN ? SCS_CONNECTING
+                    : !connect_status ? SCS_CONNECTED
+                    : SCS_DISCONNECTED);
+    stream->error = connect_status;
+    stream->name = xstrdup(name);
+    assert(stream->state != SCS_CONNECTING || class->connect);
+}
+
+void
+stream_set_remote_ip(struct stream *stream, uint32_t ip)
+{
+    stream->remote_ip = ip;
+}
+
+void
+stream_set_remote_port(struct stream *stream, uint16_t port)
+{
+    stream->remote_port = port;
+}
+
+void
+stream_set_local_ip(struct stream *stream, uint32_t ip)
+{
+    stream->local_ip = ip;
+}
+
+void
+stream_set_local_port(struct stream *stream, uint16_t port)
+{
+    stream->local_port = port;
+}
+
+void
+pstream_init(struct pstream *pstream, struct pstream_class *class,
+            const char *name)
+{
+    pstream->class = class;
+    pstream->name = xstrdup(name);
+}
diff --git a/lib/stream.h b/lib/stream.h
new file mode 100644 (file)
index 0000000..e7eef36
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef STREAM_H
+#define STREAM_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct pstream;
+struct stream;
+
+void stream_usage(const char *name, bool active, bool passive, bool bootstrap);
+
+/* Bidirectional byte streams. */
+int stream_open(const char *name, struct stream **);
+int stream_open_block(const char *name, struct stream **);
+void stream_close(struct stream *);
+const char *stream_get_name(const struct stream *);
+uint32_t stream_get_remote_ip(const struct stream *);
+uint16_t stream_get_remote_port(const struct stream *);
+uint32_t stream_get_local_ip(const struct stream *);
+uint16_t stream_get_local_port(const struct stream *);
+int stream_connect(struct stream *);
+int stream_recv(struct stream *, void *buffer, size_t n);
+int stream_send(struct stream *, const void *buffer, size_t n);
+
+void stream_run(struct stream *);
+void stream_run_wait(struct stream *);
+
+enum stream_wait_type {
+    STREAM_CONNECT,
+    STREAM_RECV,
+    STREAM_SEND
+};
+void stream_wait(struct stream *, enum stream_wait_type);
+void stream_connect_wait(struct stream *);
+void stream_recv_wait(struct stream *);
+void stream_send_wait(struct stream *);
+
+/* Passive streams: listeners for incoming stream connections. */
+int pstream_open(const char *name, struct pstream **);
+const char *pstream_get_name(const struct pstream *);
+void pstream_close(struct pstream *);
+int pstream_accept(struct pstream *, struct stream **);
+int pstream_accept_block(struct pstream *, struct stream **);
+void pstream_wait(struct pstream *);
+
+#endif /* stream.h */
index 9d0f313..bc3df23 100644 (file)
@@ -59,6 +59,12 @@ svec_clear(struct svec *svec)
     svec->n = 0;
 }
 
+bool
+svec_is_empty(const struct svec *svec)
+{
+    return svec->n == 0;
+}
+
 void
 svec_add(struct svec *svec, const char *name)
 {
index ac22662..2a93139 100644 (file)
@@ -32,6 +32,7 @@ void svec_init(struct svec *);
 void svec_clone(struct svec *, const struct svec *);
 void svec_destroy(struct svec *);
 void svec_clear(struct svec *);
+bool svec_is_empty(const struct svec *);
 void svec_add(struct svec *, const char *);
 void svec_add_nocopy(struct svec *, char *);
 void svec_del(struct svec *, const char *);
index 8ad8d06..5e42387 100644 (file)
@@ -44,6 +44,7 @@ static struct timeval now;
 static time_t deadline = TIME_MIN;
 
 static void set_up_timer(void);
+static void set_up_signal(int flags);
 static void sigalrm_handler(int);
 static void refresh_if_ticked(void);
 static time_t time_add(time_t, time_t);
@@ -51,13 +52,11 @@ static void block_sigalrm(sigset_t *);
 static void unblock_sigalrm(const sigset_t *);
 static void log_poll_interval(long long int last_wakeup,
                               const struct rusage *last_rusage);
-static long long int timeval_to_msec(const struct timeval *);
 
 /* Initializes the timetracking module. */
 void
 time_init(void)
 {
-    struct sigaction sa;
     if (inited) {
         return;
     }
@@ -68,17 +67,49 @@ time_init(void)
     gettimeofday(&now, NULL);
     tick = false;
 
-    /* Set up signal handler. */
+    set_up_signal(SA_RESTART);
+    set_up_timer();
+}
+
+static void
+set_up_signal(int flags)
+{
+    struct sigaction sa;
+
     memset(&sa, 0, sizeof sa);
     sa.sa_handler = sigalrm_handler;
     sigemptyset(&sa.sa_mask);
-    sa.sa_flags = SA_RESTART;
+    sa.sa_flags = flags;
     if (sigaction(SIGALRM, &sa, NULL)) {
         ovs_fatal(errno, "sigaction(SIGALRM) failed");
     }
+}
 
-    /* Set up periodic signal. */
-    set_up_timer();
+/* Remove SA_RESTART from the flags for SIGALRM, so that any system call that
+ * is interrupted by the periodic timer interrupt will return EINTR instead of
+ * continuing after the signal handler returns.
+ *
+ * time_disable_restart() and time_enable_restart() may be usefully wrapped
+ * around function calls that might otherwise block forever unless interrupted
+ * by a signal, e.g.:
+ *
+ *   time_disable_restart();
+ *   fcntl(fd, F_SETLKW, &lock);
+ *   time_enable_restart();
+ */
+void
+time_disable_restart(void)
+{
+    set_up_signal(0);
+}
+
+/* Add SA_RESTART to the flags for SIGALRM, so that any system call that
+ * is interrupted by the periodic timer interrupt will continue after the
+ * signal handler returns instead of returning EINTR. */
+void
+time_enable_restart(void)
+{
+    set_up_signal(SA_RESTART);
 }
 
 static void
@@ -255,7 +286,7 @@ unblock_sigalrm(const sigset_t *oldsigs)
     }
 }
 
-static long long int
+long long int
 timeval_to_msec(const struct timeval *tv)
 {
     return (long long int) tv->tv_sec * 1000 + tv->tv_usec / 1000;
index 8567d75..5ba903e 100644 (file)
@@ -41,6 +41,8 @@ BUILD_ASSERT_DECL(TYPE_IS_SIGNED(time_t));
 #define TIME_UPDATE_INTERVAL 100
 
 void time_init(void);
+void time_disable_restart(void);
+void time_enable_restart(void);
 void time_postfork(void);
 void time_refresh(void);
 time_t time_now(void);
@@ -49,4 +51,6 @@ void time_timeval(struct timeval *);
 void time_alarm(unsigned int secs);
 int time_poll(struct pollfd *, int n_pollfds, int timeout);
 
+long long int timeval_to_msec(const struct timeval *);
+
 #endif /* timeval.h */
diff --git a/lib/unicode.c b/lib/unicode.c
new file mode 100644 (file)
index 0000000..69ebcfc
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "unicode.h"
+
+/* Returns the unicode code point corresponding to leading surrogate 'leading'
+ * and trailing surrogate 'trailing'.  The return value will not make any
+ * sense if 'leading' or 'trailing' are not in the correct ranges for leading
+ * or trailing surrogates. */
+int
+utf16_decode_surrogate_pair(int leading, int trailing)
+{
+    /*
+     *  Leading surrogate:         110110wwwwxxxxxx
+     * Trailing surrogate:         110111xxxxxxxxxx
+     *         Code point: 000uuuuuxxxxxxxxxxxxxxxx
+     */
+    int w = (leading >> 6) & 0xf;
+    int u = w + 1;
+    int x0 = leading & 0x3f;
+    int x1 = trailing & 0x3ff;
+    return (u << 16) | (x0 << 10) | x1;
+}
diff --git a/lib/unicode.h b/lib/unicode.h
new file mode 100644 (file)
index 0000000..0f20bdc
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UNICODE_H
+#define UNICODE_H 1
+
+#include <stdbool.h>
+
+/* Returns true if 'c' is a Unicode code point, otherwise false. */
+static inline bool
+uc_is_code_point(int c)
+{
+    return c >= 0 && c <= 0x10ffff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a leading surrogate. */
+static inline bool
+uc_is_leading_surrogate(int c)
+{
+    return c >= 0xd800 && c <= 0xdbff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a trailing surrogate. */
+static inline bool
+uc_is_trailing_surrogate(int c)
+{
+    return c >= 0xdc00 && c <= 0xdfff;
+}
+
+/* Returns true if 'c' is a Unicode code point for a leading or trailing
+ * surrogate. */
+static inline bool
+uc_is_surrogate(int c)
+{
+    return c >= 0xd800 && c <= 0xdfff;
+}
+
+int utf16_decode_surrogate_pair(int leading, int trailing);
+
+#endif /* unicode.h */
index 164a7db..e648cc8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -44,7 +44,8 @@
 #include "vlog.h"
 \f
 struct unixctl_command {
-    void (*cb)(struct unixctl_conn *, const char *args);
+    unixctl_cb_func *cb;
+    void *aux;
 };
 
 struct unixctl_conn {
@@ -76,7 +77,8 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
 static struct shash commands = SHASH_INITIALIZER(&commands);
 
 static void
-unixctl_help(struct unixctl_conn *conn, const char *args UNUSED)
+unixctl_help(struct unixctl_conn *conn, const char *args UNUSED,
+             void *aux UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     struct shash_node *node;
@@ -90,8 +92,7 @@ unixctl_help(struct unixctl_conn *conn, const char *args UNUSED)
 }
 
 void
-unixctl_command_register(const char *name,
-                         void (*cb)(struct unixctl_conn *, const char *args))
+unixctl_command_register(const char *name, unixctl_cb_func *cb, void *aux)
 {
     struct unixctl_command *command;
 
@@ -99,6 +100,7 @@ unixctl_command_register(const char *name,
            || shash_find_data(&commands, name) == cb);
     command = xmalloc(sizeof *command);
     command->cb = cb;
+    command->aux = aux;
     shash_add(&commands, name, command);
 }
 
@@ -178,7 +180,7 @@ unixctl_server_create(const char *path, struct unixctl_server **serverp)
     struct unixctl_server *server;
     int error;
 
-    unixctl_command_register("help", unixctl_help);
+    unixctl_command_register("help", unixctl_help, NULL);
 
     server = xmalloc(sizeof *server);
     list_init(&server->conns);
@@ -198,22 +200,21 @@ unixctl_server_create(const char *path, struct unixctl_server **serverp)
                                   NULL);
     if (server->fd < 0) {
         error = -server->fd;
-        fprintf(stderr, "Could not initialize control socket %s (%s)\n",
-                server->path, strerror(error));
+        ovs_error(error, "could not initialize control socket %s",
+                  server->path);
         goto error;
     }
 
     if (chmod(server->path, S_IRUSR | S_IWUSR) < 0) {
         error = errno;
-        fprintf(stderr, "Failed to chmod control socket %s (%s)\n",
-                server->path, strerror(error));
+        ovs_error(error, "failed to chmod control socket %s", server->path);
         goto error;
     }
 
     if (listen(server->fd, 10) < 0) {
         error = errno;
-        fprintf(stderr, "Failed to listen on control socket %s (%s)\n",
-                server->path, strerror(error));
+        ovs_error(error, "Failed to listen on control socket %s",
+                  server->path);
         goto error;
     }
 
@@ -282,7 +283,7 @@ process_command(struct unixctl_conn *conn, char *s)
 
     command = shash_find_data(&commands, name);
     if (command) {
-        command->cb(conn, args);
+        command->cb(conn, args, command->aux);
     } else {
         char *msg = xasprintf("\"%s\" is not a valid command", name);
         unixctl_command_reply(conn, 400, msg);
index 18748aa..0b6cbf3 100644 (file)
@@ -35,9 +35,10 @@ const char *unixctl_client_target(const struct unixctl_client *);
 
 /* Command registration. */
 struct unixctl_conn;
+typedef void unixctl_cb_func(struct unixctl_conn *,
+                             const char *args, void *aux);
 void unixctl_command_register(const char *name,
-                              void (*cb)(struct unixctl_conn *,
-                                         const char *args));
+                              unixctl_cb_func *cb, void *aux);
 void unixctl_command_reply(struct unixctl_conn *, int code,
                            const char *body);
 
index f766d59..e928480 100644 (file)
@@ -42,6 +42,12 @@ xcalloc(size_t count, size_t size)
     return p;
 }
 
+void *
+xzalloc(size_t size)
+{
+    return xcalloc(1, size);
+}
+
 void *
 xmalloc(size_t size) 
 {
@@ -162,8 +168,10 @@ ovs_error(int err_no, const char *format, ...)
     va_start(args, format);
     vfprintf(stderr, format, args);
     va_end(args);
-    if (err_no != 0)
-        fprintf(stderr, " (%s)", strerror(err_no));
+    if (err_no != 0) {
+        fprintf(stderr, " (%s)",
+                err_no == EOF ? "end of file" : strerror(err_no));
+    }
     putc('\n', stderr);
 
     errno = save_errno;
@@ -294,3 +302,90 @@ str_to_ullong(const char *s, int base, unsigned long long *ull)
 {
     return str_to_llong(s, base, (long long *) ull);
 }
+
+/* Converts floating-point string 's' into a double.  If successful, stores
+ * the double in '*d' and returns true; on failure, stores 0 in '*d' and
+ * returns false.
+ *
+ * Underflow (e.g. "1e-9999") is not considered an error, but overflow
+ * (e.g. "1e9999)" is. */
+bool
+str_to_double(const char *s, double *d)
+{
+    int save_errno = errno;
+    char *tail;
+    errno = 0;
+    *d = strtod(s, &tail);
+    if (errno == EINVAL || (errno == ERANGE && *d != 0)
+        || tail == s || *tail != '\0') {
+        errno = save_errno;
+        *d = 0;
+        return false;
+    } else {
+        errno = save_errno;
+        return true;
+    }
+}
+
+/* Returns the value of 'c' as a hexadecimal digit. */
+int
+hexit_value(int c)
+{
+    switch (c) {
+    case '0': case '1': case '2': case '3': case '4':
+    case '5': case '6': case '7': case '8': case '9':
+        return c - '0';
+
+    case 'a': case 'A':
+        return 0xa;
+
+    case 'b': case 'B':
+        return 0xb;
+
+    case 'c': case 'C':
+        return 0xc;
+
+    case 'd': case 'D':
+        return 0xd;
+
+    case 'e': case 'E':
+        return 0xe;
+
+    case 'f': case 'F':
+        return 0xf;
+    }
+
+    NOT_REACHED();
+}
+
+/* Returns the directory name portion of 'file_name' as a malloc()'d string,
+ * similar to the POSIX dirname() function but thread-safe. */
+char *
+dir_name(const char *file_name)
+{
+    size_t len = strlen(file_name);
+    while (len > 0 && file_name[len - 1] == '/') {
+        len--;
+    }
+    while (len > 0 && file_name[len - 1] != '/') {
+        len--;
+    }
+    while (len > 0 && file_name[len - 1] == '/') {
+        len--;
+    }
+    if (!len) {
+        return xstrdup((file_name[0] == '/'
+                        && file_name[1] == '/'
+                        && file_name[2] != '/') ? "//"
+                       : file_name[0] == '/' ? "/"
+                       : ".");
+    } else {
+        return xmemdup0(file_name, len);
+    }
+}
+
+/* Pass a value to this function if it is marked with
+ * __attribute__((warn_unused_result)) and you genuinely want to ignore 
+ * its return value.  (Note that every scalar type can be implicitly 
+ * converted to bool.) */
+void ignore(bool x UNUSED) { }
index 962bad2..0101bf7 100644 (file)
@@ -98,6 +98,7 @@ void ovs_print_version(char *date, char *time,
 void out_of_memory(void) NO_RETURN;
 void *xmalloc(size_t) MALLOC_LIKE;
 void *xcalloc(size_t, size_t) MALLOC_LIKE;
+void *xzalloc(size_t) MALLOC_LIKE;
 void *xrealloc(void *, size_t);
 void *xmemdup(const void *, size_t) MALLOC_LIKE;
 char *xmemdup0(const char *, size_t) MALLOC_LIKE;
@@ -120,6 +121,14 @@ bool str_to_uint(const char *, int base, unsigned int *);
 bool str_to_ulong(const char *, int base, unsigned long *);
 bool str_to_ullong(const char *, int base, unsigned long long *);
 
+bool str_to_double(const char *, double *);
+
+int hexit_value(int c);
+
+char *dir_name(const char *file_name);
+
+void ignore(bool x UNUSED);
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/lib/uuid.c b/lib/uuid.c
new file mode 100644 (file)
index 0000000..620c039
--- /dev/null
@@ -0,0 +1,224 @@
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "uuid.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "aes128.h"
+#include "sha1.h"
+#include "socket-util.h"
+#include "util.h"
+
+static struct aes128 key;
+static uint64_t counter[2];
+BUILD_ASSERT_DECL(sizeof counter == 16);
+
+static void do_init(void);
+static void read_urandom(void *buffer, size_t n);
+
+/*
+ * Initialize the UUID module.  Aborts the program with an error message if
+ * initialization fails (which should never happen on a properly configured
+ * machine.)
+ *
+ * Currently initialization is only needed by uuid_generate().  uuid_generate()
+ * will automatically call uuid_init() itself, so it's only necessary to call
+ * this function explicitly if you want to abort the program earlier than the
+ * first UUID generation in case of failure.
+ */
+void
+uuid_init(void)
+{
+    static bool inited;
+    if (!inited) {
+        do_init();
+        inited = true;
+    }
+}
+
+/* Generates a new random UUID in 'uuid'.
+ *
+ * We go to some trouble to ensure as best we can that the generated UUID has
+ * these properties:
+ *
+ *      - Uniqueness.  The random number generator is seeded using both the
+ *        system clock and the system random number generator, plus a few
+ *        other identifiers, which is about as good as we can get in any kind
+ *        of simple way.
+ *
+ *      - Unpredictability.  In some situations it could be bad for an
+ *        adversary to be able to guess the next UUID to be generated with some
+ *        probability of success.  This property may or may not be important
+ *        for our purposes, but it is better if we can get it.
+ *
+ * To ensure both of these, we start by taking our seed data and passing it
+ * through SHA-1.  We use the result as an AES-128 key.  We also generate a
+ * random 16-byte value[*] which we then use as the counter for CTR mode.  To
+ * generate a UUID in a manner compliant with the above goals, we merely
+ * increment the counter and encrypt it.
+ *
+ * [*] It is not actually important that the initial value of the counter be
+ *     random.  AES-128 in counter mode is secure either way.
+ */
+void
+uuid_generate(struct uuid *uuid)
+{
+    uuid_init();
+
+    /* Increment the counter. */
+    if (++counter[1] == 0) {
+        counter[0]++;
+    }
+
+    /* AES output is exactly 16 bytes, so we encrypt directly into 'uuid'. */
+    aes128_encrypt(&key, counter, uuid);
+
+    /* Set bits to indicate a random UUID.  See RFC 4122 section 4.4. */
+    uuid->parts[2] &= ~0xc0000000;
+    uuid->parts[2] |=  0x80000000;
+    uuid->parts[1] &= ~0x0000f000;
+    uuid->parts[1] |=  0x00004000;
+}
+
+/* Sets 'uuid' to all-zero-bits. */
+void
+uuid_zero(struct uuid *uuid)
+{
+    uuid->parts[0] = uuid->parts[1] = uuid->parts[2] = uuid->parts[3] = 0;
+}
+
+/* Returns true if 'uuid' is all zero, otherwise false. */
+bool
+uuid_is_zero(const struct uuid *uuid)
+{
+    return (!uuid->parts[0] && !uuid->parts[1]
+            && !uuid->parts[2] && !uuid->parts[3]);
+}
+
+/* Compares 'a' and 'b'.  Returns a negative value if 'a < b', zero if 'a ==
+ * b', or positive if 'a > b'.  The ordering is lexicographical order of the
+ * conventional way of writing out UUIDs as strings. */
+int
+uuid_compare_3way(const struct uuid *a, const struct uuid *b)
+{
+    if (a->parts[0] != b->parts[0]) {
+        return a->parts[0] > b->parts[0] ? 1 : -1;
+    } else if (a->parts[1] != b->parts[1]) {
+        return a->parts[1] > b->parts[1] ? 1 : -1;
+    } else if (a->parts[2] != b->parts[2]) {
+        return a->parts[2] > b->parts[2] ? 1 : -1;
+    } else if (a->parts[3] != b->parts[3]) {
+        return a->parts[3] > b->parts[3] ? 1 : -1;
+    } else {
+        return 0;
+    }
+}
+
+/* Attempts to convert string 's' into a UUID in 'uuid'.  Returns true if
+ * successful, which will be the case only if 's' has the exact format
+ * specified by RFC 4122.  Returns false on failure.  On failure, 'uuid' will
+ * be set to all-zero-bits. */
+bool
+uuid_from_string(struct uuid *uuid, const char *s)
+{
+    static const char template[] = "00000000-1111-1111-2222-222233333333";
+    const char *t;
+
+    uuid_zero(uuid);
+    for (t = template; ; t++, s++) {
+        if (*t >= '0' && *t <= '3') {
+            uint32_t *part = &uuid->parts[*t - '0'];
+            if (!isxdigit(*s)) {
+                goto error;
+            }
+            *part = (*part << 4) + hexit_value(*s);
+        } else if (*t != *s) {
+            goto error;
+        } else if (*t == 0) {
+            return true;
+        }
+    }
+
+error:
+    uuid_zero(uuid);
+    return false;
+}
+\f
+static void
+read_urandom(void *buffer, size_t n)
+{
+    static const char urandom[] = "/dev/urandom";
+    size_t bytes_read;
+    int error;
+    int fd;
+
+    fd = open(urandom, O_RDONLY);
+    if (fd < 0) {
+        ovs_fatal(errno, "%s: open failed", urandom);
+    }
+    error = read_fully(fd, buffer, n, &bytes_read);
+    if (error == EOF) {
+        ovs_fatal(0, "%s: unexpected end of file", urandom);
+    } else if (error) {
+        ovs_fatal(error, "%s: read error", urandom);
+    }
+    close(fd);
+}
+
+static void
+do_init(void)
+{
+    uint8_t sha1[SHA1_DIGEST_SIZE];
+    struct sha1_ctx sha1_ctx;
+    uint8_t random_seed[16];
+    struct timeval now;
+    pid_t pid, ppid;
+    uid_t uid;
+    gid_t gid;
+
+    /* Get seed data. */
+    read_urandom(random_seed, sizeof random_seed);
+    if (gettimeofday(&now, NULL)) {
+        ovs_fatal(errno, "gettimeofday failed");
+    }
+    pid = getpid();
+    ppid = getppid();
+    uid = getuid();
+    gid = getgid();
+
+    /* Convert seed into key. */
+    sha1_init(&sha1_ctx);
+    sha1_update(&sha1_ctx, random_seed, sizeof random_seed);
+    sha1_update(&sha1_ctx, &pid, sizeof pid);
+    sha1_update(&sha1_ctx, &ppid, sizeof ppid);
+    sha1_update(&sha1_ctx, &uid, sizeof uid);
+    sha1_update(&sha1_ctx, &gid, sizeof gid);
+    sha1_final(&sha1_ctx, sha1);
+
+    /* Generate key. */
+    BUILD_ASSERT(sizeof sha1 >= 16);
+    aes128_schedule(&key, sha1);
+
+    /* Generate initial counter. */
+    read_urandom(counter, sizeof counter);
+}
diff --git a/lib/uuid.h b/lib/uuid.h
new file mode 100644 (file)
index 0000000..1059c26
--- /dev/null
@@ -0,0 +1,81 @@
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UUID_H
+#define UUID_H 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "util.h"
+
+#define UUID_BIT 128            /* Number of bits in a UUID. */
+#define UUID_OCTET (UUID_BIT / 8) /* Number of bytes in a UUID. */
+
+/* A Universally Unique IDentifier (UUID) compliant with RFC 4122.
+ *
+ * Each of the parts is stored in host byte order, but the parts themselves are
+ * ordered from left to right.  That is, (parts[0] >> 24) is the first 8 bits
+ * of the UUID when output in the standard form, and (parts[3] & 0xff) is the
+ * final 8 bits. */
+struct uuid {
+    uint32_t parts[4];
+};
+BUILD_ASSERT_DECL(sizeof(struct uuid) == UUID_OCTET);
+
+/* Formats a UUID as a string, in the conventional format.
+ *
+ * Example:
+ *   struct uuid uuid = ...;
+ *   printf("This UUID is "UUID_FMT"\n", UUID_ARGS(&uuid));
+ *
+ */
+#define UUID_LEN 36
+#define UUID_FMT "%08x-%04x-%04x-%04x-%04x%08x"
+#define UUID_ARGS(UUID)                             \
+    ((unsigned int) ((UUID)->parts[0])),            \
+    ((unsigned int) ((UUID)->parts[1] >> 16)),      \
+    ((unsigned int) ((UUID)->parts[1] & 0xffff)),   \
+    ((unsigned int) ((UUID)->parts[2] >> 16)),      \
+    ((unsigned int) ((UUID)->parts[2] & 0xffff)),   \
+    ((unsigned int) ((UUID)->parts[3]))
+
+/* Returns a hash value for 'uuid'.  This hash value is the same regardless of
+ * whether we are running on a 32-bit or 64-bit or big-endian or little-endian
+ * architecture. */
+static inline size_t
+uuid_hash(const struct uuid *uuid)
+{
+    return uuid->parts[0];
+}
+
+/* Returns true if 'a == b', false otherwise. */
+static inline bool
+uuid_equals(const struct uuid *a, const struct uuid *b)
+{
+    return (a->parts[0] == b->parts[0]
+            && a->parts[1] == b->parts[1]
+            && a->parts[2] == b->parts[2]
+            && a->parts[3] == b->parts[3]);
+}
+
+void uuid_init(void);
+void uuid_generate(struct uuid *);
+void uuid_zero(struct uuid *);
+bool uuid_is_zero(const struct uuid *);
+int uuid_compare_3way(const struct uuid *, const struct uuid *);
+bool uuid_from_string(struct uuid *, const char *);
+
+#endif /* vswitchd/cfgdb.h */
diff --git a/lib/vconn-active.man b/lib/vconn-active.man
new file mode 100644 (file)
index 0000000..ef86f73
--- /dev/null
@@ -0,0 +1,13 @@
+.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
+The specified SSL \fIport\fR (default: 6633) on the host at the given
+\fIip\fR, which must be expressed as an IP address (not a DNS name).
+The \fB\-\-private\-key\fR, \fB\-\-certificate\fR, and
+\fB\-\-ca\-cert\fR options are mandatory when this form is used.
+
+.IP "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
+The specified TCP \fIport\fR (default: 6633) on the host at the given
+\fIip\fR, which must be expressed as an IP address (not a DNS name).
+
+.TP
+\fBunix:\fIfile\fR
+The Unix domain server socket named \fIfile\fR.
diff --git a/lib/vconn-passive.man b/lib/vconn-passive.man
new file mode 100644 (file)
index 0000000..4b7f492
--- /dev/null
@@ -0,0 +1,17 @@
+.IP "\fBpssl:\fR[\fIport\fR][\fB:\fIip\fR]"
+Listens for OpenFlow SSL connections on \fIport\fR (default: 6633).
+The \fB\-\-private\-key\fR, \fB\-\-certificate\fR, and
+\fB\-\-ca\-cert\fR options are mandatory when this form is used.  By
+default, \fB\*(PN\fR listens for connections to any local IP address,
+but \fIip\fR may be specified to listen only for connections to the
+given \fIip\fR.
+
+.IP "\fBptcp:\fR[\fIport\fR][\fB:\fIip\fR]"
+Listens for OpenFlow TCP connections on \fIport\fR (default: 6633).
+By default, \fB\*(PN\fR listens for connections to any local IP
+address, but \fIip\fR may be specified to listen only for connections
+to the given \fIip\fR.
+
+.IP "\fBpunix:\fIfile\fR"
+Listens for OpenFlow connections on the Unix domain server socket
+named \fIfile\fR.
index f245e4c..1c8b86d 100644 (file)
@@ -108,6 +108,18 @@ struct vconn_class {
      * accepted for transmission, it should return EAGAIN. */
     int (*send)(struct vconn *vconn, struct ofpbuf *msg);
 
+    /* Allows 'vconn' to perform maintenance activities, such as flushing
+     * output buffers.
+     *
+     * May be null if 'vconn' doesn't have anything to do here. */
+    void (*run)(struct vconn *vconn);
+
+    /* Arranges for the poll loop to wake up when 'vconn' needs to perform
+     * maintenance activities.
+     *
+     * May be null if 'vconn' doesn't have anything to do here. */
+    void (*run_wait)(struct vconn *vconn);
+
     /* Arranges for the poll loop to wake up when 'vconn' is ready to take an
      * action of the given 'type'. */
     void (*wait)(struct vconn *vconn, enum vconn_wait_type type);
index 0551c9e..594eded 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,7 +15,6 @@
  */
 
 #include <config.h>
-#include "vconn-stream.h"
 #include <assert.h>
 #include <errno.h>
 #include <poll.h>
@@ -29,6 +28,7 @@
 #include "openflow/openflow.h"
 #include "poll-loop.h"
 #include "socket-util.h"
+#include "stream.h"
 #include "util.h"
 #include "vconn-provider.h"
 #include "vconn.h"
 
 /* Active stream socket vconn. */
 
-struct stream_vconn
+struct vconn_stream
 {
     struct vconn vconn;
-    int fd;
+    struct stream *stream;
     struct ofpbuf *rxbuf;
     struct ofpbuf *txbuf;
-    struct poll_waiter *tx_waiter;
-    char *unlink_path;
 };
 
 static struct vconn_class stream_vconn_class;
 
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
 
-static void stream_clear_txbuf(struct stream_vconn *);
-static void maybe_unlink_and_free(char *path);
+static void vconn_stream_clear_txbuf(struct vconn_stream *);
+static int count_fields(const char *);
 
-/* Creates a new vconn named 'name' that will send and receive data on 'fd' and
- * stores a pointer to the vconn in '*vconnp'.  Initial connection status
- * 'connect_status' is interpreted as described for vconn_init().
- *
- * When '*vconnp' is closed, then 'unlink_path' (if nonnull) will be passed to
- * fatal_signal_unlink_file_now() and then freed with free().
- *
- * Returns 0 if successful, otherwise a positive errno value.  (The current
- * implementation never fails.) */
-int
-new_stream_vconn(const char *name, int fd, int connect_status,
-                 char *unlink_path, struct vconn **vconnp)
+static struct vconn *
+vconn_stream_new(struct stream *stream, int connect_status)
 {
-    struct stream_vconn *s;
+    struct vconn_stream *s;
 
     s = xmalloc(sizeof *s);
-    vconn_init(&s->vconn, &stream_vconn_class, connect_status, name);
-    s->fd = fd;
+    vconn_init(&s->vconn, &stream_vconn_class, connect_status,
+               stream_get_name(stream));
+    s->stream = stream;
     s->txbuf = NULL;
-    s->tx_waiter = NULL;
     s->rxbuf = NULL;
-    s->unlink_path = unlink_path;
-    *vconnp = &s->vconn;
+    return &s->vconn;
+}
+
+/* Creates a new vconn that will send and receive data on a stream named 'name'
+ * and stores a pointer to the vconn in '*vconnp'.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+static int
+vconn_stream_open(const char *name_, char *suffix UNUSED,
+                  struct vconn **vconnp)
+{
+    struct stream *stream;
+    char *name;
+    int error;
+
+    if (!strncmp(name_, "tcp:", 4) && count_fields(name_) < 3) {
+        name = xasprintf("%s:%d", name_, OFP_TCP_PORT);
+    } else if (!strncmp(name_, "ssl:", 4) && count_fields(name_) < 3) {
+        name = xasprintf("%s:%d", name_, OFP_SSL_PORT);
+    } else {
+        name = xstrdup(name_);
+    }
+    error = stream_open(name, &stream);
+    free(name);
+
+    if (error && error != EAGAIN) {
+        return error;
+    }
+
+    *vconnp = vconn_stream_new(stream, error);
     return 0;
 }
 
-static struct stream_vconn *
-stream_vconn_cast(struct vconn *vconn)
+static struct vconn_stream *
+vconn_stream_cast(struct vconn *vconn)
 {
-    vconn_assert_class(vconn, &stream_vconn_class);
-    return CONTAINER_OF(vconn, struct stream_vconn, vconn);
+    return CONTAINER_OF(vconn, struct vconn_stream, vconn);
 }
 
 static void
-stream_close(struct vconn *vconn)
+vconn_stream_close(struct vconn *vconn)
 {
-    struct stream_vconn *s = stream_vconn_cast(vconn);
-    poll_cancel(s->tx_waiter);
-    stream_clear_txbuf(s);
+    struct vconn_stream *s = vconn_stream_cast(vconn);
+    stream_close(s->stream);
+    vconn_stream_clear_txbuf(s);
     ofpbuf_delete(s->rxbuf);
-    close(s->fd);
-    maybe_unlink_and_free(s->unlink_path);
     free(s);
 }
 
 static int
-stream_connect(struct vconn *vconn)
+vconn_stream_connect(struct vconn *vconn)
 {
-    struct stream_vconn *s = stream_vconn_cast(vconn);
-    return check_connection_completion(s->fd);
+    struct vconn_stream *s = vconn_stream_cast(vconn);
+    return stream_connect(s->stream);
 }
 
 static int
-stream_recv(struct vconn *vconn, struct ofpbuf **bufferp)
+vconn_stream_recv(struct vconn *vconn, struct ofpbuf **bufferp)
 {
-    struct stream_vconn *s = stream_vconn_cast(vconn);
+    struct vconn_stream *s = vconn_stream_cast(vconn);
     struct ofpbuf *rx;
     size_t want_bytes;
     ssize_t retval;
@@ -140,7 +153,7 @@ again:
     }
     ofpbuf_prealloc_tailroom(rx, want_bytes);
 
-    retval = read(s->fd, ofpbuf_tail(rx), want_bytes);
+    retval = stream_recv(s->stream, ofpbuf_tail(rx), want_bytes);
     if (retval > 0) {
         rx->size += retval;
         if (retval == want_bytes) {
@@ -161,210 +174,244 @@ again:
             return EOF;
         }
     } else {
-        return errno;
+        return -retval;
     }
 }
 
 static void
-stream_clear_txbuf(struct stream_vconn *s)
+vconn_stream_clear_txbuf(struct vconn_stream *s)
 {
     ofpbuf_delete(s->txbuf);
     s->txbuf = NULL;
-    s->tx_waiter = NULL;
-}
-
-static void
-stream_do_tx(int fd UNUSED, short int revents UNUSED, void *vconn_)
-{
-    struct vconn *vconn = vconn_;
-    struct stream_vconn *s = stream_vconn_cast(vconn);
-    ssize_t n = write(s->fd, s->txbuf->data, s->txbuf->size);
-    if (n < 0) {
-        if (errno != EAGAIN) {
-            VLOG_ERR_RL(&rl, "send: %s", strerror(errno));
-            stream_clear_txbuf(s);
-            return;
-        }
-    } else if (n > 0) {
-        ofpbuf_pull(s->txbuf, n);
-        if (!s->txbuf->size) {
-            stream_clear_txbuf(s);
-            return;
-        }
-    }
-    s->tx_waiter = poll_fd_callback(s->fd, POLLOUT, stream_do_tx, vconn);
 }
 
 static int
-stream_send(struct vconn *vconn, struct ofpbuf *buffer)
+vconn_stream_send(struct vconn *vconn, struct ofpbuf *buffer)
 {
-    struct stream_vconn *s = stream_vconn_cast(vconn);
+    struct vconn_stream *s = vconn_stream_cast(vconn);
     ssize_t retval;
 
     if (s->txbuf) {
         return EAGAIN;
     }
 
-    retval = write(s->fd, buffer->data, buffer->size);
+    retval = stream_send(s->stream, buffer->data, buffer->size);
     if (retval == buffer->size) {
         ofpbuf_delete(buffer);
         return 0;
-    } else if (retval >= 0 || errno == EAGAIN) {
+    } else if (retval >= 0 || retval == -EAGAIN) {
         leak_checker_claim(buffer);
         s->txbuf = buffer;
         if (retval > 0) {
             ofpbuf_pull(buffer, retval);
         }
-        s->tx_waiter = poll_fd_callback(s->fd, POLLOUT, stream_do_tx, vconn);
         return 0;
     } else {
-        return errno;
+        return -retval;
+    }
+}
+
+static void
+vconn_stream_run(struct vconn *vconn)
+{
+    struct vconn_stream *s = vconn_stream_cast(vconn);
+    ssize_t retval;
+
+    if (!s->txbuf) {
+        return;
+    }
+
+    retval = stream_send(s->stream, s->txbuf->data, s->txbuf->size);
+    if (retval < 0) {
+        if (retval != -EAGAIN) {
+            VLOG_ERR_RL(&rl, "send: %s", strerror(-retval));
+            vconn_stream_clear_txbuf(s);
+            return;
+        }
+    } else if (retval > 0) {
+        ofpbuf_pull(s->txbuf, retval);
+        if (!s->txbuf->size) {
+            vconn_stream_clear_txbuf(s);
+            return;
+        }
+    }
+}
+
+static void
+vconn_stream_run_wait(struct vconn *vconn)
+{
+    struct vconn_stream *s = vconn_stream_cast(vconn);
+
+    if (s->txbuf) {
+        stream_send_wait(s->stream);
     }
 }
 
 static void
-stream_wait(struct vconn *vconn, enum vconn_wait_type wait)
+vconn_stream_wait(struct vconn *vconn, enum vconn_wait_type wait)
 {
-    struct stream_vconn *s = stream_vconn_cast(vconn);
+    struct vconn_stream *s = vconn_stream_cast(vconn);
     switch (wait) {
     case WAIT_CONNECT:
-        poll_fd_wait(s->fd, POLLOUT);
+        stream_connect_wait(s->stream);
         break;
 
     case WAIT_SEND:
         if (!s->txbuf) {
-            poll_fd_wait(s->fd, POLLOUT);
+            stream_send_wait(s->stream);
         } else {
-            /* Nothing to do: need to drain txbuf first. */
+            /* Nothing to do: need to drain txbuf first.
+             * vconn_stream_run_wait() will arrange to wake up when there room
+             * to send data, so there's no point in calling poll_fd_wait()
+             * redundantly here. */
         }
         break;
 
     case WAIT_RECV:
-        poll_fd_wait(s->fd, POLLIN);
+        stream_recv_wait(s->stream);
         break;
 
     default:
         NOT_REACHED();
     }
 }
-
-static struct vconn_class stream_vconn_class = {
-    "stream",                   /* name */
-    NULL,                       /* open */
-    stream_close,               /* close */
-    stream_connect,             /* connect */
-    stream_recv,                /* recv */
-    stream_send,                /* send */
-    stream_wait,                /* wait */
-};
 \f
 /* Passive stream socket vconn. */
 
-struct pstream_pvconn
+struct pvconn_pstream
 {
     struct pvconn pvconn;
-    int fd;
-    int (*accept_cb)(int fd, const struct sockaddr *, size_t sa_len,
-                     struct vconn **);
-    char *unlink_path;
+    struct pstream *pstream;
 };
 
 static struct pvconn_class pstream_pvconn_class;
 
-static struct pstream_pvconn *
-pstream_pvconn_cast(struct pvconn *pvconn)
+static struct pvconn_pstream *
+pvconn_pstream_cast(struct pvconn *pvconn)
 {
-    pvconn_assert_class(pvconn, &pstream_pvconn_class);
-    return CONTAINER_OF(pvconn, struct pstream_pvconn, pvconn);
+    return CONTAINER_OF(pvconn, struct pvconn_pstream, pvconn);
 }
 
-/* Creates a new pvconn named 'name' that will accept new socket connections on
- * 'fd' and stores a pointer to the vconn in '*pvconnp'.
- *
- * When a connection has been accepted, 'accept_cb' will be called with the new
- * socket fd 'fd' and the remote address of the connection 'sa' and 'sa_len'.
- * accept_cb must return 0 if the connection is successful, in which case it
- * must initialize '*vconnp' to the new vconn, or a positive errno value on
- * error.  In either case accept_cb takes ownership of the 'fd' passed in.
- *
- * When '*pvconnp' is closed, then 'unlink_path' (if nonnull) will be passed to
- * fatal_signal_unlink_file_now() and freed with free().
+/* Creates a new pvconn named 'name' that will accept new connections using
+ * pstream_accept() and stores a pointer to the pvconn in '*pvconnp'.
  *
  * Returns 0 if successful, otherwise a positive errno value.  (The current
  * implementation never fails.) */
-int
-new_pstream_pvconn(const char *name, int fd,
-                  int (*accept_cb)(int fd, const struct sockaddr *sa,
-                                   size_t sa_len, struct vconn **vconnp),
-                  char *unlink_path, struct pvconn **pvconnp)
+static int
+pvconn_pstream_listen(const char *name_, char *suffix UNUSED,
+                      struct pvconn **pvconnp)
 {
-    struct pstream_pvconn *ps = xmalloc(sizeof *ps);
-    pvconn_init(&ps->pvconn, &pstream_pvconn_class, name);
-    ps->fd = fd;
-    ps->accept_cb = accept_cb;
-    ps->unlink_path = unlink_path;
+    struct pvconn_pstream *ps;
+    struct pstream *pstream;
+    char *name;
+    int error;
+
+    if (!strncmp(name_, "ptcp:", 5) && count_fields(name_) < 2) {
+        name = xasprintf("%s:%d", name_, OFP_TCP_PORT);
+    } else if (!strncmp(name_, "pssl:", 5) && count_fields(name_) < 2) {
+        name = xasprintf("%s:%d", name_, OFP_SSL_PORT);
+    } else {
+        name = xstrdup(name_);
+    }
+    error = pstream_open(name, &pstream);
+    free(name);
+    if (error) {
+        return error;
+    }
+
+    ps = xmalloc(sizeof *ps);
+    pvconn_init(&ps->pvconn, &pstream_pvconn_class, name_);
+    ps->pstream = pstream;
     *pvconnp = &ps->pvconn;
     return 0;
 }
 
 static void
-pstream_close(struct pvconn *pvconn)
+pvconn_pstream_close(struct pvconn *pvconn)
 {
-    struct pstream_pvconn *ps = pstream_pvconn_cast(pvconn);
-    close(ps->fd);
-    maybe_unlink_and_free(ps->unlink_path);
+    struct pvconn_pstream *ps = pvconn_pstream_cast(pvconn);
+    pstream_close(ps->pstream);
     free(ps);
 }
 
 static int
-pstream_accept(struct pvconn *pvconn, struct vconn **new_vconnp)
+pvconn_pstream_accept(struct pvconn *pvconn, struct vconn **new_vconnp)
 {
-    struct pstream_pvconn *ps = pstream_pvconn_cast(pvconn);
-    struct sockaddr_storage ss;
-    socklen_t ss_len = sizeof ss;
-    int new_fd;
-    int retval;
-
-    new_fd = accept(ps->fd, (struct sockaddr *) &ss, &ss_len);
-    if (new_fd < 0) {
-        int retval = errno;
-        if (retval != EAGAIN) {
-            VLOG_DBG_RL(&rl, "accept: %s", strerror(retval));
+    struct pvconn_pstream *ps = pvconn_pstream_cast(pvconn);
+    struct stream *stream;
+    int error;
+
+    error = pstream_accept(ps->pstream, &stream);
+    if (error) {
+        if (error != EAGAIN) {
+            VLOG_DBG_RL(&rl, "%s: accept: %s",
+                        pstream_get_name(ps->pstream), strerror(error));
         }
-        return retval;
-    }
-
-    retval = set_nonblocking(new_fd);
-    if (retval) {
-        close(new_fd);
-        return retval;
+        return error;
     }
 
-    return ps->accept_cb(new_fd, (const struct sockaddr *) &ss, ss_len,
-                         new_vconnp);
+    *new_vconnp = vconn_stream_new(stream, 0);
+    return 0;
 }
 
 static void
-pstream_wait(struct pvconn *pvconn)
+pvconn_pstream_wait(struct pvconn *pvconn)
 {
-    struct pstream_pvconn *ps = pstream_pvconn_cast(pvconn);
-    poll_fd_wait(ps->fd, POLLIN);
+    struct pvconn_pstream *ps = pvconn_pstream_cast(pvconn);
+    pstream_wait(ps->pstream);
 }
-
-static struct pvconn_class pstream_pvconn_class = {
-    "pstream",
-    NULL,
-    pstream_close,
-    pstream_accept,
-    pstream_wait
-};
 \f
-/* Helper functions. */
-static void
-maybe_unlink_and_free(char *path)
+static int
+count_fields(const char *s_)
 {
-    if (path) {
-        fatal_signal_unlink_file_now(path);
-        free(path);
+    char *s, *field, *save_ptr;
+    int n = 0;
+
+    save_ptr = NULL;
+    s = xstrdup(s_);
+    for (field = strtok_r(s, ":", &save_ptr); field != NULL;
+         field = strtok_r(NULL, ":", &save_ptr)) {
+        n++;
     }
+    free(s);
+
+    return n;
 }
+\f
+/* Stream-based vconns and pvconns. */
+
+#define DEFINE_VCONN_STREAM_CLASS(NAME)             \
+        struct vconn_class NAME##_vconn_class = {   \
+            #NAME,                                  \
+            vconn_stream_open,                      \
+            vconn_stream_close,                     \
+            vconn_stream_connect,                   \
+            vconn_stream_recv,                      \
+            vconn_stream_send,                      \
+            vconn_stream_run,                       \
+            vconn_stream_run_wait,                  \
+            vconn_stream_wait,                      \
+        };
+
+#define DEFINE_PVCONN_STREAM_CLASS(NAME)            \
+        struct pvconn_class NAME##_pvconn_class = { \
+            #NAME,                                  \
+            pvconn_pstream_listen,                  \
+            pvconn_pstream_close,                   \
+            pvconn_pstream_accept,                  \
+            pvconn_pstream_wait                     \
+        };
+
+static DEFINE_VCONN_STREAM_CLASS(stream);
+static DEFINE_PVCONN_STREAM_CLASS(pstream);
+
+DEFINE_VCONN_STREAM_CLASS(tcp);
+DEFINE_PVCONN_STREAM_CLASS(ptcp);
+
+DEFINE_VCONN_STREAM_CLASS(unix);
+DEFINE_PVCONN_STREAM_CLASS(punix);
+
+#ifdef HAVE_OPENSSL
+DEFINE_VCONN_STREAM_CLASS(ssl);
+DEFINE_PVCONN_STREAM_CLASS(pssl);
+#endif
index b11650f..1445be4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -90,7 +90,8 @@ check_vconn_classes(void)
         struct vconn_class *class = vconn_classes[i];
         assert(class->name != NULL);
         assert(class->open != NULL);
-        if (class->close || class->recv || class->send || class->wait) {
+        if (class->close || class->recv || class->send
+            || class->run || class->run_wait || class->wait) {
             assert(class->close != NULL);
             assert(class->recv != NULL);
             assert(class->send != NULL);
@@ -208,6 +209,26 @@ vconn_open(const char *name, int min_version, struct vconn **vconnp)
     return EAFNOSUPPORT;
 }
 
+/* Allows 'vconn' to perform maintenance activities, such as flushing output
+ * buffers. */
+void
+vconn_run(struct vconn *vconn)
+{
+    if (vconn->class->run) {
+        (vconn->class->run)(vconn);
+    }
+}
+
+/* Arranges for the poll loop to wake up when 'vconn' needs to perform
+ * maintenance activities. */
+void
+vconn_run_wait(struct vconn *vconn)
+{
+    if (vconn->class->run_wait) {
+        (vconn->class->run_wait)(vconn);
+    }
+}
+
 int
 vconn_open_block(const char *name, int min_version, struct vconn **vconnp)
 {
@@ -216,6 +237,8 @@ vconn_open_block(const char *name, int min_version, struct vconn **vconnp)
 
     error = vconn_open(name, min_version, &vconn);
     while (error == EAGAIN) {
+        vconn_run(vconn);
+        vconn_run_wait(vconn);
         vconn_connect_wait(vconn);
         poll_block();
         error = vconn_connect(vconn);
@@ -547,6 +570,8 @@ vconn_send_block(struct vconn *vconn, struct ofpbuf *msg)
 {
     int retval;
     while ((retval = vconn_send(vconn, msg)) == EAGAIN) {
+        vconn_run(vconn);
+        vconn_run_wait(vconn);
         vconn_send_wait(vconn);
         poll_block();
     }
@@ -559,6 +584,8 @@ vconn_recv_block(struct vconn *vconn, struct ofpbuf **msgp)
 {
     int retval;
     while ((retval = vconn_recv(vconn, msgp)) == EAGAIN) {
+        vconn_run(vconn);
+        vconn_run_wait(vconn);
         vconn_recv_wait(vconn);
         poll_block();
     }
@@ -1444,6 +1471,7 @@ vconn_init(struct vconn *vconn, struct vconn_class *class, int connect_status,
     vconn->local_ip = 0;
     vconn->local_port = 0;
     vconn->name = xstrdup(name);
+    assert(vconn->state != VCS_CONNECTING || class->connect);
 }
 
 void
index 0c13744..9bd235a 100644 (file)
@@ -48,6 +48,9 @@ int vconn_send(struct vconn *, struct ofpbuf *);
 int vconn_recv_xid(struct vconn *, uint32_t xid, struct ofpbuf **);
 int vconn_transact(struct vconn *, struct ofpbuf *, struct ofpbuf **);
 
+void vconn_run(struct vconn *);
+void vconn_run_wait(struct vconn *);
+
 int vconn_open_block(const char *name, int min_version, struct vconn **);
 int vconn_send_block(struct vconn *, struct ofpbuf *);
 int vconn_recv_block(struct vconn *, struct ofpbuf **);
index b791525..f012e10 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,8 +19,6 @@ VLOG_MODULE(backtrace)
 VLOG_MODULE(brcompatd)
 VLOG_MODULE(bridge)
 VLOG_MODULE(chain)
-VLOG_MODULE(cfg)
-VLOG_MODULE(cfg_mod)
 VLOG_MODULE(collectors)
 VLOG_MODULE(controller)
 VLOG_MODULE(coverage)
@@ -34,17 +32,16 @@ VLOG_MODULE(dpif)
 VLOG_MODULE(dpif_linux)
 VLOG_MODULE(dpif_netdev)
 VLOG_MODULE(dpctl)
-VLOG_MODULE(executer)
 VLOG_MODULE(ezio_term)
 VLOG_MODULE(fail_open)
 VLOG_MODULE(fatal_signal)
-VLOG_MODULE(fault)
 VLOG_MODULE(flow)
 VLOG_MODULE(in_band)
+VLOG_MODULE(jsonrpc)
 VLOG_MODULE(leak_checker)
 VLOG_MODULE(learning_switch)
+VLOG_MODULE(lockfile)
 VLOG_MODULE(mac_learning)
-VLOG_MODULE(mgmt)
 VLOG_MODULE(netdev)
 VLOG_MODULE(netdev_linux)
 VLOG_MODULE(netflow)
@@ -53,6 +50,13 @@ VLOG_MODULE(ofctl)
 VLOG_MODULE(ovs_discover)
 VLOG_MODULE(ofproto)
 VLOG_MODULE(openflowd)
+VLOG_MODULE(ovsdb_client)
+VLOG_MODULE(ovsdb_file)
+VLOG_MODULE(ovsdb_idl)
+VLOG_MODULE(ovsdb_log)
+VLOG_MODULE(ovsdb_jsonrpc_server)
+VLOG_MODULE(ovsdb_server)
+VLOG_MODULE(ovsdb_tool)
 VLOG_MODULE(pktbuf)
 VLOG_MODULE(pcap)
 VLOG_MODULE(poll_loop)
@@ -60,9 +64,15 @@ VLOG_MODULE(port_watcher)
 VLOG_MODULE(proc_net_compat)
 VLOG_MODULE(process)
 VLOG_MODULE(rconn)
+VLOG_MODULE(reconnect)
 VLOG_MODULE(rtnetlink)
 VLOG_MODULE(sflow)
 VLOG_MODULE(stp)
+VLOG_MODULE(stream_fd)
+VLOG_MODULE(stream_ssl)
+VLOG_MODULE(stream_tcp)
+VLOG_MODULE(stream_unix)
+VLOG_MODULE(stream)
 VLOG_MODULE(stats)
 VLOG_MODULE(status)
 VLOG_MODULE(svec)
@@ -78,6 +88,7 @@ VLOG_MODULE(vconn_ssl)
 VLOG_MODULE(vconn_stream)
 VLOG_MODULE(vconn_unix)
 VLOG_MODULE(vconn)
+VLOG_MODULE(vsctl)
 VLOG_MODULE(vlog)
 VLOG_MODULE(wcelim)
 VLOG_MODULE(vswitchd)
diff --git a/lib/vlog-syn.man b/lib/vlog-syn.man
new file mode 100644 (file)
index 0000000..873b2f0
--- /dev/null
@@ -0,0 +1,6 @@
+.IP "Logging options:"
+[\fB-v\fR[\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]]]\&...
+.br
+[\fB--verbose[=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]]]\&...
+.br
+[\fB--log-file\fR[\fB=\fIfile\fR]]
index 5496f01..b2d0c06 100644 (file)
@@ -385,7 +385,7 @@ vlog_set_verbosity(const char *arg)
 }
 
 static void
-vlog_unixctl_set(struct unixctl_conn *conn, const char *args)
+vlog_unixctl_set(struct unixctl_conn *conn, const char *args, void *aux UNUSED)
 {
     char *msg = vlog_set_levels_from_string(args);
     unixctl_command_reply(conn, msg ? 501 : 202, msg);
@@ -393,7 +393,8 @@ vlog_unixctl_set(struct unixctl_conn *conn, const char *args)
 }
 
 static void
-vlog_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED)
+vlog_unixctl_list(struct unixctl_conn *conn,
+                  const char *args UNUSED, void *aux UNUSED)
 {
     char *msg = vlog_get_levels();
     unixctl_command_reply(conn, 200, msg);
@@ -401,7 +402,8 @@ vlog_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED)
 }
 
 static void
-vlog_unixctl_reopen(struct unixctl_conn *conn, const char *args UNUSED)
+vlog_unixctl_reopen(struct unixctl_conn *conn,
+                    const char *args UNUSED, void *aux UNUSED)
 {
     if (log_file_name) {
         int error = vlog_reopen_log_file();
@@ -435,9 +437,9 @@ vlog_init(void)
         VLOG_ERR("current time is negative: %s (%ld)", s, (long int) now);
     }
 
-    unixctl_command_register("vlog/set", vlog_unixctl_set);
-    unixctl_command_register("vlog/list", vlog_unixctl_list);
-    unixctl_command_register("vlog/reopen", vlog_unixctl_reopen);
+    unixctl_command_register("vlog/set", vlog_unixctl_set, NULL);
+    unixctl_command_register("vlog/list", vlog_unixctl_list, NULL);
+    unixctl_command_register("vlog/reopen", vlog_unixctl_reopen, NULL);
 }
 
 /* Closes the logging subsystem. */
index b726743..7b827e7 100644 (file)
@@ -1,6 +1,6 @@
 # -*- autoconf -*-
 
-# Copyright (c) 2008, 2009 Nicira Networks.
+# Copyright (c) 2008, 2009, 2010 Nicira Networks.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -22,22 +22,15 @@ AC_DEFUN([OVS_CHECK_COVERAGE],
      [AC_HELP_STRING([--enable-coverage], 
                      [Enable gcov coverage tool.])],
      [case "${enableval}" in
-        (lcov) coverage=true lcov=true ;;
-        (yes) coverage=true lcov=false ;;
-        (no)  coverage=false lcov=false ;;
+        (lcov|yes) coverage=true ;;
+        (no)  coverage=false ;;
         (*) AC_MSG_ERROR([bad value ${enableval} for --enable-coverage]) ;;
       esac],
-     [coverage=false lcov=false])
+     [coverage=false])
    if $coverage; then
      CFLAGS="$CFLAGS -O0 --coverage"
      LDFLAGS="$LDFLAGS --coverage"
-   fi
-   if $lcov; then
-     if lcov --version >/dev/null 2>&1; then :; else
-       AC_MSG_ERROR([--enable-coverage=lcov was specified but lcov is not in \$PATH])
-     fi
-   fi
-   AC_SUBST([LCOV], [$lcov])])
+   fi])
 
 dnl Checks for --enable-ndebug and defines NDEBUG if it is specified.
 AC_DEFUN([OVS_CHECK_NDEBUG],
@@ -81,28 +74,25 @@ AC_DEFUN([OVS_CHECK_OPENSSL],
      [ssl=false])
 
    if test "$ssl" = true; then
-   dnl Make sure that pkg-config is installed.
-   m4_pattern_forbid([PKG_CHECK_MODULES])
-   PKG_CHECK_MODULES([SSL], [libssl], 
-     [HAVE_OPENSSL=yes],
-     [HAVE_OPENSSL=no
-      AC_MSG_WARN([Cannot find libssl:
-
-   $SSL_PKG_ERRORS
+       dnl Make sure that pkg-config is installed.
+       m4_pattern_forbid([PKG_CHECK_MODULES])
+       PKG_CHECK_MODULES([SSL], [libssl], 
+         [HAVE_OPENSSL=yes],
+         [HAVE_OPENSSL=no
+          AC_MSG_WARN([Cannot find libssl:
 
-   OpenFlow connections over SSL will not be supported.])])
+$SSL_PKG_ERRORS
 
+OpenFlow connections over SSL will not be supported.])])
+   else
+       HAVE_OPENSSL=no
    fi
+   AC_SUBST([HAVE_OPENSSL])
    AM_CONDITIONAL([HAVE_OPENSSL], [test "$HAVE_OPENSSL" = yes])
    if test "$HAVE_OPENSSL" = yes; then
       AC_DEFINE([HAVE_OPENSSL], [1], [Define to 1 if OpenSSL is installed.])
    fi])
 
-dnl Checks for libraries needed by lib/fault.c.
-AC_DEFUN([OVS_CHECK_FAULT_LIBS],
-  [AC_CHECK_LIB([dl], [dladdr], [FAULT_LIBS=-ldl])
-   AC_SUBST([FAULT_LIBS])])
-
 dnl Checks for libraries needed by lib/socket-util.c.
 AC_DEFUN([OVS_CHECK_SOCKET_LIBS],
   [AC_CHECK_LIB([socket], [connect])
@@ -236,8 +226,7 @@ AC_DEFUN([OVS_CHECK_PCRE],
 
 dnl Checks for Python 2.x, x >= 4.
 AC_DEFUN([OVS_CHECK_PYTHON],
-  [AC_ARG_VAR([PYTHON], [path to Python 2.x])
-   AC_CACHE_CHECK(
+  [AC_CACHE_CHECK(
      [for Python 2.x for x >= 4],
      [ovs_cv_python],
      [if test -n "$PYTHON"; then
@@ -260,4 +249,12 @@ else:
           done
         done
       fi])
-   PYTHON=$ovs_cv_python])
+   AC_SUBST([HAVE_PYTHON])
+   AM_MISSING_PROG([PYTHON], [python])
+   if test $ovs_cv_python != no; then
+     PYTHON=$ovs_cv_python
+     HAVE_PYTHON=yes
+   else
+     HAVE_PYTHON=no
+   fi
+   AM_CONDITIONAL([HAVE_PYTHON], [test "$HAVE_PYTHON" = yes])])
index 3c18977..0c99b49 100644 (file)
@@ -11,8 +11,6 @@ ofproto_libofproto_a_SOURCES = \
        ofproto/collectors.h \
        ofproto/discovery.c \
        ofproto/discovery.h \
-       ofproto/executer.c \
-       ofproto/executer.h \
        ofproto/fail-open.c \
        ofproto/fail-open.h \
        ofproto/in-band.c \
@@ -29,5 +27,3 @@ ofproto_libofproto_a_SOURCES = \
        ofproto/pinsched.h \
        ofproto/status.c \
        ofproto/status.h
-
-include ofproto/commands/automake.mk
diff --git a/ofproto/commands/automake.mk b/ofproto/commands/automake.mk
deleted file mode 100644 (file)
index 96d165f..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-commandsdir = ${pkgdatadir}/commands
-dist_commands_SCRIPTS = \
-       ofproto/commands/reboot
diff --git a/ofproto/commands/reboot b/ofproto/commands/reboot
deleted file mode 100755 (executable)
index 42fd10c..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-#! /bin/sh
-ovs-kill --force --signal=USR1 ovs-switchui.pid
-reboot
index 2868db5..ce3e7ce 100644 (file)
@@ -29,7 +29,7 @@
 #include "openflow/openflow.h"
 #include "packets.h"
 #include "status.h"
-#include "vconn-ssl.h"
+#include "stream-ssl.h"
 
 #define THIS_MODULE VLM_discovery
 #include "vlog.h"
@@ -102,7 +102,7 @@ discovery_create(const char *re, bool update_resolv_conf,
     char local_name[IF_NAMESIZE];
     int error;
 
-    d = xcalloc(1, sizeof *d);
+    d = xzalloc(sizeof *d);
 
     /* Controller regular expression. */
     error = discovery_set_accept_controller_re(d, re);
@@ -169,7 +169,7 @@ discovery_set_accept_controller_re(struct discovery *d, const char *re_)
     int error;
     char *re;
 
-    re = (!re_ ? xstrdup(vconn_ssl_is_configured() ? "^ssl:.*" : "^tcp:.*")
+    re = (!re_ ? xstrdup(stream_ssl_is_configured() ? "^ssl:.*" : "^tcp:.*")
           : re_[0] == '^' ? xstrdup(re_) : xasprintf("^%s", re_));
     regex = xmalloc(sizeof *regex);
     error = regcomp(regex, re, REG_NOSUB | REG_EXTENDED);
diff --git a/ofproto/executer.c b/ofproto/executer.c
deleted file mode 100644 (file)
index b78b0df..0000000
+++ /dev/null
@@ -1,514 +0,0 @@
-/*
- * Copyright (c) 2008, 2009 Nicira Networks.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-#include "executer.h"
-#include <errno.h>
-#include <fcntl.h>
-#include <fnmatch.h>
-#include <poll.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <string.h>
-#include <unistd.h>
-#include "dirs.h"
-#include "dynamic-string.h"
-#include "fatal-signal.h"
-#include "openflow/nicira-ext.h"
-#include "ofpbuf.h"
-#include "openflow/openflow.h"
-#include "poll-loop.h"
-#include "rconn.h"
-#include "socket-util.h"
-#include "util.h"
-#include "vconn.h"
-
-#define THIS_MODULE VLM_executer
-#include "vlog.h"
-
-#define MAX_CHILDREN 8
-
-struct child {
-    /* Information about child process. */
-    char *name;                 /* argv[0] passed to child. */
-    pid_t pid;                  /* Child's process ID. */
-
-    /* For sending a reply to the controller when the child dies. */
-    struct rconn *rconn;
-    uint32_t xid;               /* Transaction ID used by controller. */
-
-    /* We read up to MAX_OUTPUT bytes of output and send them back to the
-     * controller when the child dies. */
-#define MAX_OUTPUT 4096
-    int output_fd;              /* FD from which to read child's output. */
-    uint8_t *output;            /* Output data. */
-    size_t output_size;         /* Number of bytes of output data so far. */
-};
-
-struct executer {
-    /* Settings. */
-    char *command_acl;          /* Command white/blacklist, as shell globs. */
-    char *command_dir;          /* Directory that contains commands. */
-
-    /* Children. */
-    struct child children[MAX_CHILDREN];
-    size_t n_children;
-};
-
-/* File descriptors for waking up when a child dies. */
-static int signal_fds[2] = {-1, -1};
-
-static void send_child_status(struct rconn *, uint32_t xid, uint32_t status,
-                              const void *data, size_t size);
-static void send_child_message(struct rconn *, uint32_t xid, uint32_t status,
-                               const char *message);
-
-/* Returns true if 'cmd' is allowed by 'acl', which is a command-separated
- * access control list in the format described for --command-acl in
- * ovs-openflowd(8). */
-static bool
-executer_is_permitted(const char *acl_, const char *cmd)
-{
-    char *acl, *save_ptr, *pattern;
-    bool allowed, denied;
-
-    /* Verify that 'cmd' consists only of alphanumerics plus _ or -. */
-    if (cmd[strspn(cmd, "abcdefghijklmnopqrstuvwxyz"
-                   "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")] != '\0') {
-        VLOG_WARN("rejecting command name \"%s\" that contain forbidden "
-                  "characters", cmd);
-        return false;
-    }
-
-    /* Check 'cmd' against 'acl'. */
-    acl = xstrdup(acl_);
-    save_ptr = acl;
-    allowed = denied = false;
-    while ((pattern = strsep(&save_ptr, ",")) != NULL && !denied) {
-        if (pattern[0] != '!' && !fnmatch(pattern, cmd, 0)) {
-            allowed = true;
-        } else if (pattern[0] == '!' && !fnmatch(pattern + 1, cmd, 0)) {
-            denied = true;
-        }
-    }
-    free(acl);
-
-    /* Check the command white/blacklisted state. */
-    if (allowed && !denied) {
-        VLOG_INFO("permitting command execution: \"%s\" is whitelisted", cmd);
-    } else if (allowed && denied) {
-        VLOG_WARN("denying command execution: \"%s\" is both blacklisted "
-                  "and whitelisted", cmd);
-    } else if (!allowed) {
-        VLOG_WARN("denying command execution: \"%s\" is not whitelisted", cmd);
-    } else if (denied) {
-        VLOG_WARN("denying command execution: \"%s\" is blacklisted", cmd);
-    }
-    return allowed && !denied;
-}
-
-int
-executer_handle_request(struct executer *e, struct rconn *rconn,
-                        struct nicira_header *request)
-{
-    char **argv;
-    char *args;
-    char *exec_file = NULL;
-    int max_fds;
-    struct stat s;
-    size_t args_size;
-    size_t argc;
-    size_t i;
-    pid_t pid;
-    int output_fds[2];
-
-    /* Verify limit on children not exceeded.
-     * XXX should probably kill children when the connection drops? */
-    if (e->n_children >= MAX_CHILDREN) {
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "too many child processes");
-        return 0;
-    }
-
-    /* Copy argument buffer, adding a null terminator at the end.  Now every
-     * argument is null-terminated, instead of being merely null-delimited. */
-    args_size = ntohs(request->header.length) - sizeof *request;
-    args = xmemdup0((const void *) (request + 1), args_size);
-
-    /* Count arguments. */
-    argc = 0;
-    for (i = 0; i <= args_size; i++) {
-        argc += args[i] == '\0';
-    }
-
-    /* Set argv[*] to point to each argument. */
-    argv = xmalloc((argc + 1) * sizeof *argv);
-    argv[0] = args;
-    for (i = 1; i < argc; i++) {
-        argv[i] = strchr(argv[i - 1], '\0') + 1;
-    }
-    argv[argc] = NULL;
-
-    /* Check permissions. */
-    if (!executer_is_permitted(e->command_acl, argv[0])) {
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "command not allowed");
-        goto done;
-    }
-
-    /* Find the executable. */
-    exec_file = xasprintf("%s/%s", e->command_dir, argv[0]);
-    if (stat(exec_file, &s)) {
-        VLOG_WARN("failed to stat \"%s\": %s", exec_file, strerror(errno));
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "command not allowed");
-        goto done;
-    }
-    if (!S_ISREG(s.st_mode)) {
-        VLOG_WARN("\"%s\" is not a regular file", exec_file);
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "command not allowed");
-        goto done;
-    }
-    argv[0] = exec_file;
-
-    /* Arrange to capture output. */
-    if (pipe(output_fds)) {
-        VLOG_WARN("pipe failed: %s", strerror(errno));
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "internal error (pipe)");
-        goto done;
-    }
-
-    pid = fork();
-    if (!pid) {
-        /* Running in child.
-         * XXX should run in new process group so that we can signal all
-         * subprocesses at once?  Would also want to catch fatal signals and
-         * kill them at the same time though. */
-        fatal_signal_fork();
-        dup2(get_null_fd(), 0);
-        dup2(output_fds[1], 1);
-        dup2(get_null_fd(), 2);
-        max_fds = get_max_fds();
-        for (i = 3; i < max_fds; i++) {
-            close(i);
-        }
-        if (chdir(e->command_dir)) {
-            printf("could not change directory to \"%s\": %s",
-                   e->command_dir, strerror(errno));
-            exit(EXIT_FAILURE);
-        }
-        execv(argv[0], argv);
-        printf("failed to start \"%s\": %s\n", argv[0], strerror(errno));
-        exit(EXIT_FAILURE);
-    } else if (pid > 0) {
-        /* Running in parent. */
-        struct child *child;
-
-        VLOG_INFO("started \"%s\" subprocess", argv[0]);
-        send_child_status(rconn, request->header.xid, NXT_STATUS_STARTED,
-                          NULL, 0);
-        child = &e->children[e->n_children++];
-        child->name = xstrdup(argv[0]);
-        child->pid = pid;
-        child->rconn = rconn;
-        child->xid = request->header.xid;
-        child->output_fd = output_fds[0];
-        child->output = xmalloc(MAX_OUTPUT);
-        child->output_size = 0;
-        set_nonblocking(output_fds[0]);
-        close(output_fds[1]);
-    } else {
-        VLOG_WARN("fork failed: %s", strerror(errno));
-        send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR,
-                           "internal error (fork)");
-        close(output_fds[0]);
-        close(output_fds[1]);
-    }
-
-done:
-    free(exec_file);
-    free(args);
-    free(argv);
-    return 0;
-}
-
-static void
-send_child_status(struct rconn *rconn, uint32_t xid, uint32_t status,
-                  const void *data, size_t size)
-{
-    if (rconn) {
-        struct nx_command_reply *r;
-        struct ofpbuf *buffer;
-
-        r = make_openflow_xid(sizeof *r, OFPT_VENDOR, xid, &buffer);
-        r->nxh.vendor = htonl(NX_VENDOR_ID);
-        r->nxh.subtype = htonl(NXT_COMMAND_REPLY);
-        r->status = htonl(status);
-        ofpbuf_put(buffer, data, size);
-        update_openflow_length(buffer);
-        if (rconn_send(rconn, buffer, NULL)) {
-            ofpbuf_delete(buffer);
-        }
-    }
-}
-
-static void
-send_child_message(struct rconn *rconn, uint32_t xid, uint32_t status,
-                   const char *message)
-{
-    send_child_status(rconn, xid, status, message, strlen(message));
-}
-
-/* 'child' died with 'status' as its return code.  Deal with it. */
-static void
-child_terminated(struct child *child, int status)
-{
-    struct ds ds;
-    uint32_t ofp_status;
-
-    /* Log how it terminated. */
-    ds_init(&ds);
-    if (WIFEXITED(status)) {
-        ds_put_format(&ds, "normally with status %d", WEXITSTATUS(status));
-    } else if (WIFSIGNALED(status)) {
-        const char *name = NULL;
-#ifdef HAVE_STRSIGNAL
-        name = strsignal(WTERMSIG(status));
-#endif
-        ds_put_format(&ds, "by signal %d", WTERMSIG(status));
-        if (name) {
-            ds_put_format(&ds, " (%s)", name);
-        }
-    }
-    if (WCOREDUMP(status)) {
-        ds_put_cstr(&ds, " (core dumped)");
-    }
-    VLOG_INFO("child process \"%s\" with pid %ld terminated %s",
-              child->name, (long int) child->pid, ds_cstr(&ds));
-    ds_destroy(&ds);
-
-    /* Send a status message back to the controller that requested the
-     * command. */
-    if (WIFEXITED(status)) {
-        ofp_status = WEXITSTATUS(status) | NXT_STATUS_EXITED;
-    } else if (WIFSIGNALED(status)) {
-        ofp_status = WTERMSIG(status) | NXT_STATUS_SIGNALED;
-    } else {
-        ofp_status = NXT_STATUS_UNKNOWN;
-    }
-    if (WCOREDUMP(status)) {
-        ofp_status |= NXT_STATUS_COREDUMP;
-    }
-    send_child_status(child->rconn, child->xid, ofp_status,
-                      child->output, child->output_size);
-}
-
-/* Read output from 'child' and append it to its output buffer. */
-static void
-poll_child(struct child *child)
-{
-    ssize_t n;
-
-    if (child->output_fd < 0) {
-        return;
-    }
-
-    do {
-        n = read(child->output_fd, child->output + child->output_size,
-                 MAX_OUTPUT - child->output_size);
-    } while (n < 0 && errno == EINTR);
-    if (n > 0) {
-        child->output_size += n;
-        if (child->output_size < MAX_OUTPUT) {
-            return;
-        }
-    } else if (n < 0 && errno == EAGAIN) {
-        return;
-    }
-    close(child->output_fd);
-    child->output_fd = -1;
-}
-
-void
-executer_run(struct executer *e)
-{
-    char buffer[MAX_CHILDREN];
-    size_t i;
-
-    if (!e->n_children) {
-        return;
-    }
-
-    /* Read output from children. */
-    for (i = 0; i < e->n_children; i++) {
-        struct child *child = &e->children[i];
-        poll_child(child);
-    }
-
-    /* If SIGCHLD was received, reap dead children. */
-    if (read(signal_fds[0], buffer, sizeof buffer) <= 0) {
-        return;
-    }
-    for (;;) {
-        int status;
-        pid_t pid;
-
-        /* Get dead child in 'pid' and its return code in 'status'. */
-        pid = waitpid(WAIT_ANY, &status, WNOHANG);
-        if (pid < 0 && errno == EINTR) {
-            continue;
-        } else if (pid <= 0) {
-            return;
-        }
-
-        /* Find child with given 'pid' and drop it from the list. */
-        for (i = 0; i < e->n_children; i++) {
-            struct child *child = &e->children[i];
-            if (child->pid == pid) {
-                poll_child(child);
-                child_terminated(child, status);
-                free(child->name);
-                free(child->output);
-                *child = e->children[--e->n_children];
-                goto found;
-            }
-        }
-        VLOG_WARN("child with unknown pid %ld terminated", (long int) pid);
-    found:;
-    }
-
-}
-
-void
-executer_wait(struct executer *e)
-{
-    if (e->n_children) {
-        size_t i;
-
-        /* Wake up on SIGCHLD. */
-        poll_fd_wait(signal_fds[0], POLLIN);
-
-        /* Wake up when we get output from a child. */
-        for (i = 0; i < e->n_children; i++) {
-            struct child *child = &e->children[i];
-            if (child->output_fd >= 0) {
-                poll_fd_wait(child->output_fd, POLLIN);
-            }
-        }
-    }
-}
-
-void
-executer_rconn_closing(struct executer *e, struct rconn *rconn)
-{
-    size_t i;
-
-    /* If any of our children was connected to 'r', then disconnect it so we
-     * don't try to reference a dead connection when the process terminates
-     * later.
-     * XXX kill the children started by 'r'? */
-    for (i = 0; i < e->n_children; i++) {
-        if (e->children[i].rconn == rconn) {
-            e->children[i].rconn = NULL;
-        }
-    }
-}
-
-static void
-sigchld_handler(int signr UNUSED)
-{
-    write(signal_fds[1], "", 1);
-}
-
-int
-executer_create(const char *command_acl, const char *command_dir,
-                struct executer **executerp)
-{
-    struct executer *e;
-    struct sigaction sa;
-
-    *executerp = NULL;
-    if (signal_fds[0] == -1) {
-        /* Make sure we can get a fd for /dev/null. */
-        int null_fd = get_null_fd();
-        if (null_fd < 0) {
-            return -null_fd;
-        }
-
-        /* Create pipe for notifying us that SIGCHLD was invoked. */
-        if (pipe(signal_fds)) {
-            VLOG_ERR("pipe failed: %s", strerror(errno));
-            return errno;
-        }
-        set_nonblocking(signal_fds[0]);
-        set_nonblocking(signal_fds[1]);
-    }
-
-    /* Set up signal handler. */
-    memset(&sa, 0, sizeof sa);
-    sa.sa_handler = sigchld_handler;
-    sigemptyset(&sa.sa_mask);
-    sa.sa_flags = SA_NOCLDSTOP | SA_RESTART;
-    if (sigaction(SIGCHLD, &sa, NULL)) {
-        VLOG_ERR("sigaction(SIGCHLD) failed: %s", strerror(errno));
-        return errno;
-    }
-
-    e = xcalloc(1, sizeof *e);
-    e->command_acl = xstrdup(command_acl);
-    e->command_dir = (command_dir
-                      ? xstrdup(command_dir)
-                      : xasprintf("%s/commands", ovs_pkgdatadir));
-    e->n_children = 0;
-    *executerp = e;
-    return 0;
-}
-
-void
-executer_destroy(struct executer *e)
-{
-    if (e) {
-        size_t i;
-
-        free(e->command_acl);
-        free(e->command_dir);
-        for (i = 0; i < e->n_children; i++) {
-            struct child *child = &e->children[i];
-
-            free(child->name);
-            kill(child->pid, SIGHUP);
-            /* We don't own child->rconn. */
-            free(child->output);
-            free(child);
-        }
-        free(e);
-    }
-}
-
-void
-executer_set_acl(struct executer *e, const char *acl, const char *dir)
-{
-    free(e->command_acl);
-    e->command_acl = xstrdup(acl);
-    free(e->command_dir);
-    e->command_dir = (dir
-                      ? xstrdup(dir)
-                      : xasprintf("%s/commands", ovs_pkgdatadir));
-}
diff --git a/ofproto/executer.h b/ofproto/executer.h
deleted file mode 100644 (file)
index fbed85b..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (c) 2008, 2009 Nicira Networks.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef EXECUTER_H
-#define EXECUTER_H 1
-
-struct executer;
-struct nicira_header;
-struct rconn;
-
-int executer_create(const char *acl, const char *dir, struct executer **);
-void executer_set_acl(struct executer *, const char *acl, const char *dir);
-void executer_destroy(struct executer *);
-void executer_run(struct executer *);
-void executer_wait(struct executer *);
-void executer_rconn_closing(struct executer *, struct rconn *);
-int executer_handle_request(struct executer *, struct rconn *,
-                            struct nicira_header *);
-
-#endif /* executer.h */
index c39e5ca..857618f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -272,8 +272,8 @@ get_remote_mac(struct in_band *ib)
             || strcmp(netdev_get_name(ib->remote_netdev), next_hop_dev))
         {
             netdev_close(ib->remote_netdev);
-            retval = netdev_open(next_hop_dev, NETDEV_ETH_TYPE_NONE,
-                                 &ib->remote_netdev);
+
+            retval = netdev_open_default(next_hop_dev, &ib->remote_netdev);
             if (retval) {
                 VLOG_WARN_RL(&rl, "cannot open netdev %s (next hop "
                              "to controller "IP_FMT"): %s",
@@ -617,14 +617,14 @@ in_band_create(struct ofproto *ofproto, struct dpif *dpif,
         return error;
     }
 
-    error = netdev_open(local_name, NETDEV_ETH_TYPE_NONE, &local_netdev);
+    error = netdev_open_default(local_name, &local_netdev);
     if (error) {
         VLOG_ERR("failed to initialize in-band control: cannot open "
                  "datapath local port %s (%s)", local_name, strerror(error));
         return error;
     }
 
-    in_band = xcalloc(1, sizeof *in_band);
+    in_band = xzalloc(sizeof *in_band);
     in_band->ofproto = ofproto;
     in_band->controller = controller;
     in_band->ss_cat = switch_status_register(ss, "in-band",
index 7c77c64..34d571f 100644 (file)
@@ -20,7 +20,6 @@
 #include <errno.h>
 #include <stdlib.h>
 #include <unistd.h>
-#include "cfg.h"
 #include "collectors.h"
 #include "flow.h"
 #include "netflow.h"
@@ -38,8 +37,6 @@
 
 #define NETFLOW_V5_VERSION 5
 
-static const int ACTIVE_TIMEOUT_DEFAULT = 600;
-
 /* Every NetFlow v5 message contains the header that follows.  This is
  * followed by up to thirty records that describe a terminating flow.
  * We only send a single record per NetFlow message.
@@ -211,10 +208,10 @@ netflow_set_options(struct netflow *nf,
     collectors_create(&nf_options->collectors, 0, &nf->collectors);
 
     old_timeout = nf->active_timeout;
-    if (nf_options->active_timeout != -1) {
+    if (nf_options->active_timeout >= 0) {
         nf->active_timeout = nf_options->active_timeout;
     } else {
-        nf->active_timeout = ACTIVE_TIMEOUT_DEFAULT;
+        nf->active_timeout = NF_ACTIVE_TIMEOUT_DEFAULT;
     }
     nf->active_timeout *= 1000;
     if (old_timeout != nf->active_timeout) {
index cc7b960..7f48ddd 100644 (file)
@@ -20,6 +20,8 @@
 #include "flow.h"
 #include "svec.h"
 
+static const int NF_ACTIVE_TIMEOUT_DEFAULT = 600;
+
 struct ofexpired;
 
 struct netflow_options {
index b37db42..1b659d1 100644 (file)
@@ -212,7 +212,7 @@ sflow_choose_agent_address(const char *agent_device, const char *control_ip,
     if (agent_device) {
         struct netdev *netdev;
 
-        if (!netdev_open(agent_device, NETDEV_ETH_TYPE_NONE, &netdev)) {
+        if (!netdev_open_default(agent_device, &netdev)) {
             int error = netdev_get_in4(netdev, &in4, NULL);
             netdev_close(netdev);
             if (!error) {
@@ -319,7 +319,7 @@ ofproto_sflow_add_port(struct ofproto_sflow *os, uint16_t odp_port,
     ofproto_sflow_del_port(os, odp_port);
 
     /* Open network device. */
-    error = netdev_open(netdev_name, NETDEV_ETH_TYPE_NONE, &netdev);
+    error = netdev_open_default(netdev_name, &netdev);
     if (error) {
         VLOG_WARN_RL(&rl, "failed to open network device \"%s\": %s",
                      netdev_name, strerror(error));
index 43054fa..c44762c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,7 +27,6 @@
 #include "discovery.h"
 #include "dpif.h"
 #include "dynamic-string.h"
-#include "executer.h"
 #include "fail-open.h"
 #include "in-band.h"
 #include "mac-learning.h"
@@ -39,7 +38,6 @@
 #include "ofpbuf.h"
 #include "openflow/nicira-ext.h"
 #include "openflow/openflow.h"
-#include "openflow/openflow-mgmt.h"
 #include "openvswitch/datapath-protocol.h"
 #include "packets.h"
 #include "pinsched.h"
 #include "shash.h"
 #include "status.h"
 #include "stp.h"
+#include "stream-ssl.h"
 #include "svec.h"
 #include "tag.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "vconn.h"
-#include "vconn-ssl.h"
 #include "xtoxll.h"
 
 #define THIS_MODULE VLM_ofproto
@@ -174,7 +172,7 @@ struct ofconn {
 };
 
 static struct ofconn *ofconn_create(struct ofproto *, struct rconn *);
-static void ofconn_destroy(struct ofconn *, struct ofproto *);
+static void ofconn_destroy(struct ofconn *);
 static void ofconn_run(struct ofconn *, struct ofproto *);
 static void ofconn_wait(struct ofconn *);
 static void queue_tx(struct ofpbuf *msg, const struct ofconn *ofconn,
@@ -184,7 +182,6 @@ struct ofproto {
     /* Settings. */
     uint64_t datapath_id;       /* Datapath ID. */
     uint64_t fallback_dpid;     /* Datapath ID if no better choice found. */
-    uint64_t mgmt_id;           /* Management channel identifier. */
     char *manufacturer;         /* Manufacturer. */
     char *hardware;             /* Hardware. */
     char *software;             /* Software version. */
@@ -205,7 +202,6 @@ struct ofproto {
     struct discovery *discovery;
     struct fail_open *fail_open;
     struct pinsched *miss_sched, *action_sched;
-    struct executer *executer;
     struct netflow *netflow;
     struct ofproto_sflow *sflow;
 
@@ -259,7 +255,8 @@ static int init_ports(struct ofproto *);
 static void reinit_ports(struct ofproto *);
 
 int
-ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
+ofproto_create(const char *datapath, const char *datapath_type,
+               const struct ofhooks *ofhooks, void *aux,
                struct ofproto **ofprotop)
 {
     struct odp_stats stats;
@@ -270,7 +267,7 @@ ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
     *ofprotop = NULL;
 
     /* Connect to datapath and start listening for messages. */
-    error = dpif_open(datapath, &dpif);
+    error = dpif_open(datapath, datapath_type, &dpif);
     if (error) {
         VLOG_ERR("failed to open datapath %s: %s", datapath, strerror(error));
         return error;
@@ -293,7 +290,7 @@ ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
     dpif_recv_purge(dpif);
 
     /* Initialize settings. */
-    p = xcalloc(1, sizeof *p);
+    p = xzalloc(sizeof *p);
     p->fallback_dpid = pick_fallback_dpid();
     p->datapath_id = p->fallback_dpid;
     p->manufacturer = xstrdup("Nicira Networks, Inc.");
@@ -314,7 +311,6 @@ ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
     p->discovery = NULL;
     p->fail_open = NULL;
     p->miss_sched = p->action_sched = NULL;
-    p->executer = NULL;
     p->netflow = NULL;
     p->sflow = NULL;
 
@@ -349,13 +345,6 @@ ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux,
     p->ss_cat = switch_status_register(p->switch_status, "remote",
                                        rconn_status_cb, p->controller->rconn);
 
-    /* Almost done... */
-    error = init_ports(p);
-    if (error) {
-        ofproto_destroy(p);
-        return error;
-    }
-
     /* Pick final datapath ID. */
     p->datapath_id = pick_datapath_id(p);
     VLOG_INFO("using datapath ID %012"PRIx64, p->datapath_id);
@@ -375,12 +364,6 @@ ofproto_set_datapath_id(struct ofproto *p, uint64_t datapath_id)
     }
 }
 
-void
-ofproto_set_mgmt_id(struct ofproto *p, uint64_t mgmt_id)
-{
-    p->mgmt_id = mgmt_id;
-}
-
 void
 ofproto_set_probe_interval(struct ofproto *p, int probe_interval)
 {
@@ -537,7 +520,7 @@ int
 ofproto_set_netflow(struct ofproto *ofproto,
                     const struct netflow_options *nf_options)
 {
-    if (nf_options->collectors.n) {
+    if (nf_options && nf_options->collectors.n) {
         if (!ofproto->netflow) {
             ofproto->netflow = netflow_create();
         }
@@ -628,36 +611,12 @@ ofproto_set_stp(struct ofproto *ofproto UNUSED, bool enable_stp)
     }
 }
 
-int
-ofproto_set_remote_execution(struct ofproto *ofproto, const char *command_acl,
-                             const char *command_dir)
-{
-    if (command_acl) {
-        if (!ofproto->executer) {
-            return executer_create(command_acl, command_dir,
-                                   &ofproto->executer);
-        } else {
-            executer_set_acl(ofproto->executer, command_acl, command_dir);
-        }
-    } else {
-        executer_destroy(ofproto->executer);
-        ofproto->executer = NULL;
-    }
-    return 0;
-}
-
 uint64_t
 ofproto_get_datapath_id(const struct ofproto *ofproto)
 {
     return ofproto->datapath_id;
 }
 
-uint64_t
-ofproto_get_mgmt_id(const struct ofproto *ofproto)
-{
-    return ofproto->mgmt_id;
-}
-
 int
 ofproto_get_probe_interval(const struct ofproto *ofproto)
 {
@@ -725,7 +684,7 @@ ofproto_destroy(struct ofproto *p)
 
     LIST_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, node,
                         &p->all_conns) {
-        ofconn_destroy(ofconn, p);
+        ofconn_destroy(ofconn);
     }
 
     dpif_close(p->dpif);
@@ -741,7 +700,6 @@ ofproto_destroy(struct ofproto *p)
     fail_open_destroy(p->fail_open);
     pinsched_destroy(p->miss_sched);
     pinsched_destroy(p->action_sched);
-    executer_destroy(p->executer);
     netflow_destroy(p->netflow);
     ofproto_sflow_destroy(p->sflow);
 
@@ -791,6 +749,10 @@ ofproto_run1(struct ofproto *p)
     int error;
     int i;
 
+    if (shash_is_empty(&p->port_by_name)) {
+        init_ports(p);
+    }
+
     for (i = 0; i < 50; i++) {
         struct ofpbuf *buf;
         int error;
@@ -838,9 +800,6 @@ ofproto_run1(struct ofproto *p)
     }
     pinsched_run(p->miss_sched, send_packet_in_miss, p);
     pinsched_run(p->action_sched, send_packet_in_action, p);
-    if (p->executer) {
-        executer_run(p->executer);
-    }
 
     LIST_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, node,
                         &p->all_conns) {
@@ -952,9 +911,6 @@ ofproto_wait(struct ofproto *p)
     }
     pinsched_wait(p->miss_sched);
     pinsched_wait(p->action_sched);
-    if (p->executer) {
-        executer_wait(p->executer);
-    }
     if (p->sflow) {
         ofproto_sflow_wait(p->sflow);
     }
@@ -1134,13 +1090,19 @@ refresh_port_groups(struct ofproto *p)
 static struct ofport *
 make_ofport(const struct odp_port *odp_port)
 {
+    struct netdev_options netdev_options;
     enum netdev_flags flags;
     struct ofport *ofport;
     struct netdev *netdev;
     bool carrier;
     int error;
 
-    error = netdev_open(odp_port->devname, NETDEV_ETH_TYPE_NONE, &netdev);
+    memset(&netdev_options, 0, sizeof netdev_options);
+    netdev_options.name = odp_port->devname;
+    netdev_options.ethertype = NETDEV_ETH_TYPE_NONE;
+    netdev_options.may_open = true;
+
+    error = netdev_open(&netdev_options, &netdev);
     if (error) {
         VLOG_WARN_RL(&rl, "ignoring port %s (%"PRIu16") because netdev %s "
                      "cannot be opened (%s)",
@@ -1375,12 +1337,8 @@ ofconn_create(struct ofproto *p, struct rconn *rconn)
 }
 
 static void
-ofconn_destroy(struct ofconn *ofconn, struct ofproto *p)
+ofconn_destroy(struct ofconn *ofconn)
 {
-    if (p->executer) {
-        executer_rconn_closing(p->executer, ofconn->rconn);
-    }
-
     list_remove(&ofconn->node);
     rconn_destroy(ofconn->rconn);
     rconn_packet_counter_destroy(ofconn->packet_in_counter);
@@ -1413,7 +1371,7 @@ ofconn_run(struct ofconn *ofconn, struct ofproto *p)
     }
 
     if (ofconn != p->controller && !rconn_is_alive(ofconn->rconn)) {
-        ofconn_destroy(ofconn, p);
+        ofconn_destroy(ofconn);
     }
 }
 
@@ -1435,7 +1393,7 @@ rule_create(struct ofproto *ofproto, struct rule *super,
             const union ofp_action *actions, size_t n_actions,
             uint16_t idle_timeout, uint16_t hard_timeout)
 {
-    struct rule *rule = xcalloc(1, sizeof *rule);
+    struct rule *rule = xzalloc(sizeof *rule);
     rule->idle_timeout = idle_timeout;
     rule->hard_timeout = hard_timeout;
     rule->used = rule->created = time_msec();
@@ -2539,7 +2497,7 @@ query_stats(struct ofproto *p, struct rule *rule,
     byte_count = rule->byte_count;
 
     n_odp_flows = rule->cr.wc.wildcards ? list_size(&rule->list) : 1;
-    odp_flows = xcalloc(1, n_odp_flows * sizeof *odp_flows);
+    odp_flows = xzalloc(n_odp_flows * sizeof *odp_flows);
     if (rule->cr.wc.wildcards) {
         size_t i = 0;
         LIST_FOR_EACH (subrule, struct rule, list, &rule->list) {
@@ -2997,58 +2955,6 @@ handle_flow_mod(struct ofproto *p, struct ofconn *ofconn,
     }
 }
 
-static void
-send_capability_reply(struct ofproto *p, struct ofconn *ofconn, uint32_t xid)
-{
-    struct ofmp_capability_reply *ocr;
-    struct ofpbuf *b;
-    char capabilities[] = "com.nicira.mgmt.manager=false\n";
-
-    ocr = make_openflow_xid(sizeof(*ocr), OFPT_VENDOR, xid, &b);
-    ocr->header.header.vendor = htonl(NX_VENDOR_ID);
-    ocr->header.header.subtype = htonl(NXT_MGMT);
-    ocr->header.type = htons(OFMPT_CAPABILITY_REPLY);
-
-    ocr->format = htonl(OFMPCOF_SIMPLE);
-    ocr->mgmt_id = htonll(p->mgmt_id);
-
-    ofpbuf_put(b, capabilities, strlen(capabilities));
-
-    queue_tx(b, ofconn, ofconn->reply_counter);
-}
-
-static int
-handle_ofmp(struct ofproto *p, struct ofconn *ofconn, 
-            struct ofmp_header *ofmph)
-{
-    size_t msg_len = ntohs(ofmph->header.header.length);
-    if (msg_len < sizeof(*ofmph)) {
-        VLOG_WARN_RL(&rl, "dropping short managment message: %zu\n", msg_len);
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
-    }
-
-    if (ofmph->type == htons(OFMPT_CAPABILITY_REQUEST)) {
-        struct ofmp_capability_request *ofmpcr;
-
-        if (msg_len < sizeof(struct ofmp_capability_request)) {
-            VLOG_WARN_RL(&rl, "dropping short capability request: %zu\n",
-                    msg_len);
-            return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH);
-        }
-
-        ofmpcr = (struct ofmp_capability_request *)ofmph;
-        if (ofmpcr->format != htonl(OFMPCAF_SIMPLE)) {
-            /* xxx Find a better type than bad subtype */
-            return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE);
-        }
-
-        send_capability_reply(p, ofconn, ofmph->header.header.xid);
-        return 0;
-    } else {
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE);
-    }
-}
-
 static int
 handle_vendor(struct ofproto *p, struct ofconn *ofconn, void *msg)
 {
@@ -3070,21 +2976,6 @@ handle_vendor(struct ofproto *p, struct ofconn *ofconn, void *msg)
     case NXT_STATUS_REQUEST:
         return switch_status_handle_request(p->switch_status, ofconn->rconn,
                                             msg);
-
-    case NXT_ACT_SET_CONFIG:
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); /* XXX */
-
-    case NXT_ACT_GET_CONFIG:
-        return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); /* XXX */
-
-    case NXT_COMMAND_REQUEST:
-        if (p->executer) {
-            return executer_handle_request(p->executer, ofconn->rconn, msg);
-        }
-        break;
-
-    case NXT_MGMT:
-        return handle_ofmp(p, ofconn, msg);
     }
 
     return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE);
index 6377e51..a94c8b5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,7 +51,8 @@ struct ofproto_sflow_options {
     char *control_ip;
 };
 
-int ofproto_create(const char *datapath, const struct ofhooks *, void *aux,
+int ofproto_create(const char *datapath, const char *datapath_type,
+                   const struct ofhooks *, void *aux,
                    struct ofproto **ofprotop);
 void ofproto_destroy(struct ofproto *);
 int ofproto_run(struct ofproto *);
@@ -62,7 +63,6 @@ bool ofproto_is_alive(const struct ofproto *);
 
 /* Configuration. */
 void ofproto_set_datapath_id(struct ofproto *, uint64_t datapath_id);
-void ofproto_set_mgmt_id(struct ofproto *, uint64_t mgmt_id);
 void ofproto_set_probe_interval(struct ofproto *, int probe_interval);
 void ofproto_set_max_backoff(struct ofproto *, int max_backoff);
 void ofproto_set_desc(struct ofproto *,
@@ -81,12 +81,9 @@ void ofproto_set_sflow(struct ofproto *, const struct ofproto_sflow_options *);
 void ofproto_set_failure(struct ofproto *, bool fail_open);
 void ofproto_set_rate_limit(struct ofproto *, int rate_limit, int burst_limit);
 int ofproto_set_stp(struct ofproto *, bool enable_stp);
-int ofproto_set_remote_execution(struct ofproto *, const char *command_acl,
-                                 const char *command_dir);
 
 /* Configuration querying. */
 uint64_t ofproto_get_datapath_id(const struct ofproto *);
-uint64_t ofproto_get_mgmt_id(const struct ofproto *);
 int ofproto_get_probe_interval(const struct ofproto *);
 int ofproto_get_max_backoff(const struct ofproto *);
 bool ofproto_get_in_band(const struct ofproto *);
index 0afd22f..a4f5bfa 100644 (file)
@@ -227,7 +227,7 @@ pinsched_create(int rate_limit, int burst_limit, struct switch_status *ss)
 {
     struct pinsched *ps;
 
-    ps = xcalloc(1, sizeof *ps);
+    ps = xzalloc(sizeof *ps);
     port_array_init(&ps->queues);
     ps->n_queued = 0;
     ps->last_tx_port = PORT_ARRAY_SIZE;
index 450cc3b..3701aeb 100644 (file)
@@ -63,7 +63,7 @@ pktbuf_capacity(void)
 struct pktbuf *
 pktbuf_create(void)
 {
-    return xcalloc(1, sizeof *pktbuf_create());
+    return xzalloc(sizeof *pktbuf_create());
 }
 
 void
index b2cb935..1b13e65 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -130,7 +130,7 @@ static void
 config_status_cb(struct status_reply *sr, void *ofproto_)
 {
     const struct ofproto *ofproto = ofproto_;
-    uint64_t datapath_id, mgmt_id;
+    uint64_t datapath_id;
     struct svec listeners;
     int probe_interval, max_backoff;
     size_t i;
@@ -140,11 +140,6 @@ config_status_cb(struct status_reply *sr, void *ofproto_)
         status_reply_put(sr, "datapath-id=%"PRIx64, datapath_id);
     }
 
-    mgmt_id = ofproto_get_mgmt_id(ofproto);
-    if (mgmt_id) {
-        status_reply_put(sr, "mgmt-id=%"PRIx64, mgmt_id);
-    }
-
     svec_init(&listeners);
     ofproto_get_listeners(ofproto, &listeners);
     for (i = 0; i < listeners.n; i++) {
@@ -177,7 +172,7 @@ switch_status_cb(struct status_reply *sr, void *ss_)
 struct switch_status *
 switch_status_create(const struct ofproto *ofproto)
 {
-    struct switch_status *ss = xcalloc(1, sizeof *ss);
+    struct switch_status *ss = xzalloc(sizeof *ss);
     ss->booted = time_now();
     list_init(&ss->categories);
     ss->config_cat = switch_status_register(ss, "config", config_status_cb,
diff --git a/ovsdb/.gitignore b/ovsdb/.gitignore
new file mode 100644 (file)
index 0000000..d9c12ac
--- /dev/null
@@ -0,0 +1,7 @@
+/ovsdb-client
+/ovsdb-client.1
+/ovsdb-idlc
+/ovsdb-server
+/ovsdb-server.1
+/ovsdb-tool
+/ovsdb-tool.1
diff --git a/ovsdb/SPECS b/ovsdb/SPECS
new file mode 100644 (file)
index 0000000..7963a2a
--- /dev/null
@@ -0,0 +1,993 @@
+         ===================================================
+          Open vSwitch Configuration Database Specification
+         ===================================================
+
+Basic Notation
+--------------
+
+The descriptions below use the following shorthand notations for JSON
+values.  Additional notation is presented later.
+
+<string>
+
+    A JSON string.  Any Unicode string is allowed, as specified by RFC
+    4627.  Implementations may disallowed null bytes.
+
+<id>
+
+    A JSON string matching [a-zA-Z_][a-zA-Z0-9_]*.
+
+    <id>s that begin with _ are reserved to the implementation and may
+    not be used by the user.
+
+<boolean>
+
+    A JSON true or false value.
+
+<number>
+
+    A JSON number.
+
+<integer>
+
+    A JSON number with an integer value, within a certain range
+    (currently -2**63...+2**63-1).
+
+<value>
+
+    Any JSON value.
+
+Schema Format
+-------------
+
+An Open vSwitch configuration database consists of a set of tables,
+each of which has a number of columns and zero or more rows.  A schema
+is represented by <database-schema>, as described below.
+
+<database-schema>
+
+    A JSON object with the following members:
+
+        "name": <id>                            required
+        "comment": <string>                     optional
+        "tables": {<id>: <table-schema>, ...}   required
+
+    The "name" identifies the database as a whole.  The "comment"
+    optionally provides more information about the database.  The
+    value of "tables" is a JSON object whose names are table names and
+    whose values are <table-schema>s.
+
+<table-schema>
+
+    A JSON object with the following members:
+
+        "comment": <string>                       optional
+        "columns": {<id>: <column-schema>, ...}   required
+
+    The "comment" optionally provides information about this table for
+    a human reader.  The value of "columns" is a JSON object whose
+    names are column names and whose values are <column-schema>s.
+
+    Every table has the following columns whose definitions are not
+    included in the schema:
+
+        "_uuid": This column, which contains exactly one UUID value,
+        is initialized to a random value by the database engine when
+        it creates a row.  It is read-only, and its value never
+        changes during the lifetime of a row.
+
+        "_version": Like "_uuid", this column contains exactly one
+        UUID value, initialized to a random value by the database
+        engine when it creates a row, and it is read-only.  However,
+        its value changes to a new random value whenever any other
+        field in the row changes.  Furthermore, its value is
+        ephemeral: when the database is closed and reopened, or when
+        the database process is stopped and then started again, each
+        "_version" also changes to a new random value.
+
+<column-schema>
+
+    A JSON object with the following members:
+
+        "comment": <string>                       optional
+        "type": <type>                            required
+        "ephemeral": <boolean>                    optional
+
+    The "comment" optionally provides information about this column
+    for a human reader.  The "type" specifies the type of data stored
+    in this column.  If "ephemeral" is specified as true, then this
+    column's values are not guaranteed to be durable; they may be lost
+    when the database restarts.
+
+<type>
+
+    The type of a database column.  Either an <atomic-type> or a JSON
+    object that describes the type of a database column, with the
+    following members:
+
+        "key": <atomic-type>               required
+        "value": <atomic-type>             optional
+        "min": <integer>                   optional
+        "max": <integer> or "unlimited"    optional
+
+    If "min" or "max" is not specified, each defaults to 1.  If "max"
+    is specified as "unlimited", then there is no specified maximum
+    number of elements, although the implementation will enforce some
+    limit.  After considering defaults, "min" must be exactly 0 or
+    exactly 1, "max" must be at least 1, and "max" must be greater
+    than or equal to "min".
+
+    If "min" and "max" are both 1 and "value" is not specified, the
+    type is the scalar type specified by "key".
+
+    If "min" is not 1 or "max" is not 1, or both, and "value" is not
+    specified, the type is a set of scalar type "key".
+
+    If "value" is specified, the type is a map from type "key" to type
+    "value".
+
+<atomic-type>
+
+    One of the strings "integer", "real", "boolean", "string", or
+    "uuid", representing the specified scalar type.
+
+Wire Protocol
+-------------
+
+The database wire protocol is implemented in JSON-RPC 1.0.  We
+encourage use of JSON-RPC over stream connections instead of JSON-RPC
+over HTTP, for these reasons:
+
+    * JSON-RPC is a peer-to-peer protocol, but HTTP is a client-server
+      protocol, which is a poor match.  Thus, JSON-RPC over HTTP
+      requires the client to periodically poll the server to receive
+      server requests.
+
+    * HTTP is more complicated than stream connections and doesn't
+      provide any corresponding advantage.
+
+    * The JSON-RPC specification for HTTP transport is incomplete.
+
+The database wire protocol consists of the following JSON-RPC methods:
+
+get_schema
+..........
+
+Request object members:
+
+    "method": "get_schema"            required
+    "params": []                      required
+    "id": any JSON value except null  required
+
+Response object members:
+
+    "result": <database-schema>
+    "error": null
+    "id": same "id" as request
+
+This operation retrieves a <database-schema> that describes the
+hosted database.
+
+transact
+........
+
+Request object members:
+
+    "method": "transact"              required
+    "params": [<operation>*]          required
+    "id": any JSON value except null  required
+
+Response object members:
+
+    "result": [<object>*]
+    "error": null
+    "id": same "id" as request
+
+The "params" array for this method consists of zero or more JSON
+objects, each of which represents a single database operation.  The
+"Operations" section below describes the valid operations.
+
+The value of "id" must be unique among all in-flight transactions
+within the current JSON-RPC session.  Otherwise, the server may return
+a JSON-RPC error.
+
+The database server executes each of the specified operations in the
+specified order, except that if an operation fails, then the remaining
+operations are not executed.
+
+The set of operations is executed as a single atomic, consistent,
+isolated transaction.  The transaction is committed only if every
+operation succeeds.  Durability of the commit is not guaranteed unless
+the "commit" operation, with "durable" set to true, is included in the
+operation set (see below).
+
+Regardless of whether errors occur, the response is always a JSON-RPC
+response with null "error" and a "result" member that is an array with
+the same number of elements as "params".  Each element of the "result"
+array corresponds to the same element of the "params" array.  The
+"result" array elements may be interpreted as follows:
+
+    - A JSON object that does not contain an "error" member indicates
+      that the operation completed successfully.  The specific members
+      of the object are specified below in the descriptions of
+      individual operations.  Some operations do not produce any
+      results, in which case the object will have no members.
+
+    - A JSON object that contains an "error" member indicates that the
+      operation completed with an error.  The value of the "error"
+      member is a short string, specified in this document, that
+      broadly indicates the class of the error.  Besides the ones
+      listed for a specific operation, any operation may result in one
+      the following "error"s:
+
+      "error": "resources exhausted"
+
+          The operation or the transaction requires more resources
+          (memory, disk, CPU, etc.) than are currently available to
+          the database server.
+
+      "error": "syntax error"
+
+          The operation is not specified correctly: a required request
+          object member is missing, an unknown or unsupported request
+          object member is present, the operation attempts to act on a
+          table that does not exist, the operation modifies a
+          read-only table column, etc.
+
+      Database implementations may use "error" strings not specified
+      in this document to indicate errors that do not fit into any of
+      the specified categories.
+
+      Optionally, the object may include a "details" member, whose
+      value is a string that describes the error in more detail for
+      the benefit of a human user or administrator.  The object may
+      also have other members that describe the error in more detail.
+      This document does not specify the names or values of these
+      members.
+
+    - A JSON null value indicates that the operation was not attempted
+      because a prior operation failed.
+
+In general, "result" contains some number of successful results,
+possibly followed by an error, in turn followed by enough JSON null
+values to match the number of elements in "params".  There is one
+exception: if all of the operations succeed, but the results cannot be
+committed (e.g. due to I/O errors), then "result" will have one more
+element than "params", with the additional element describing the
+error.
+
+If "params" contains one or more "wait" operations, then the
+transaction may take an arbitrary amount of time to complete.  The
+database implementation must be capable of accepting, executing, and
+replying to other transactions and other JSON-RPC requests while a
+transaction or transactions containing "wait" operations are
+outstanding on the same or different JSON-RPC sessions.
+
+The section "Notation for the Wire Protocol" below describes
+additional notation for use with the wire protocol.  After that, the
+"Operations" section describes each operation.
+
+cancel
+......
+
+Request object members:
+
+    "method": "cancel"                              required
+    "params": [the "id" for an outstanding request] required
+    "id": null                                      required
+
+Response object members:
+
+    <no response>
+
+This JSON-RPC notification instructs the database server to
+immediately complete or cancel the "transact" request whose "id" is
+the same as the notification's "params" value.  
+
+If the "transact" request can be completed immediately, then the
+server sends a response in the form described for "transact", above.
+Otherwise, the server sends a JSON-RPC error response of the following
+form:
+
+    "result": null
+    "error": "canceled"
+    "id": the request "id" member
+
+The "cancel" notification itself has no reply.
+
+monitor
+.......
+
+Request object members:
+
+    "method": "monitor"                        required
+    "params": [<value>, <monitor-requests>]    required
+    "id": any JSON value except null           required
+
+<monitor-requests> is an object that maps from a table name to a
+<monitor-request>.
+
+Each <monitor-request> is an object with the following members:
+
+    "columns": [<column>*]            optional
+    "select": <monitor-select>        optional
+
+<monitor-select> is an object with the following members:
+
+    "initial": <boolean>              optional
+    "insert": <boolean>               optional
+    "delete": <boolean>               optional
+    "modify": <boolean>               optional
+
+Response object members:
+
+    "result": <table-updates>
+    "error": null
+    "id": same "id" as request
+
+This JSON-RPC request enables a client to replicate tables or subsets
+of tables.  Each <monitor-request> specifies a table to be replicated.
+The JSON-RPC response to the "monitor" includes the initial contents
+of each table.  Afterward, when changes to those tables are committed,
+the changes are automatically sent to the client using the "update"
+monitor notification.  This monitoring persists until the JSON-RPC
+session terminates or until the client sends a "monitor_cancel"
+JSON-RPC request.
+
+Each <monitor-request> describes how to monitor a table:
+
+    The circumstances in which an "update" notification is sent for a
+    row within the table are determined by <monitor-select>:
+
+        If "initial" is omitted or true, every row in the table is
+        sent as part of the reply to the "monitor" request.
+
+        If "insert" is omitted or true, "update" notifications are
+        sent for rows newly inserted into the table.
+
+        If "delete" is omitted or true, "update" notifications are
+        sent for rows deleted from the table.
+
+        If "modify" is omitted or true, "update" notifications are
+        sent whenever when a row in the table is modified.
+
+    The "columns" member specifies the columns whose values are
+    monitored.  If "columns" is omitted, all columns in the table,
+    except for "_uuid", are monitored.
+
+The "result" in the JSON-RPC response to the "monitor" request is a
+<table-updates> object (see below) that contains the contents of the
+tables for which "initial" rows are selected.  If no tables' initial
+contents are requested, then "result" is an empty object.
+
+update
+......
+
+Notification object members:
+
+    "method": "update"
+    "params": [<value>, <table-updates>]
+    "id": null
+
+The <value> in "params" is the same as the value passed as the <value>
+in "params" for the "monitor" request.
+
+<table-updates> is an object that maps from a table name to a
+<table-update>.
+
+A <table-update> is an object that maps from the row's UUID (as a
+36-byte string) to a <row-update> object.
+
+A <row-update> is an object with the following members:
+
+    "old": <row>         present for "delete" and "modify" updates
+    "new": <row>         present for "initial", "insert", and "modify" updates
+
+This JSON-RPC notification is sent from the server to the client to
+tell it about changes to a monitored table (or the initial state of a
+modified table).  Each table in which one or more rows has changed (or
+whose initial view is being presented) is represented in "updates".
+Each row that has changed (or whose initial view is being presented)
+is represented in its <table-update> as a member with its name taken
+from the row's _uuid member.  The corresponding value is a
+<row-update>:
+
+    The "old" member is present for "delete" and "modify" updates.
+    For "delete" updates, each monitored column is included.  For
+    "modify" updates, the prior value of each monitored column whose
+    value has changed is included (monitored columns that have not
+    changed are represented in "new").
+
+    The "new" member is present for "initial", "insert", and "modify"
+    updates.  For "initial" and "insert" updates, each monitored
+    column is included.  For "modify" updates, the new value of each
+    monitored column is included.
+
+monitor_cancel
+..............
+
+Request object members:
+
+    "method": "monitor_cancel"                              required
+    "params": [<value>]                                     required
+    "id": any JSON value except null                        required
+
+Response object members:
+
+    "result": {}
+    "error": null
+    "id": the request "id" member
+
+Cancels the ongoing table monitor request, identified by the <value>
+in "params" matching the <value> in "params" for an ongoing "monitor"
+request.  No more "update" messages will be sent for this table
+monitor.
+
+echo
+....
+
+Request object members:
+
+    "method": "echo"                                required
+    "params": JSON array with any contents          required
+    "id": <value>                                   required
+
+Response object members:
+
+    "result": same as "params"
+    "error": null
+    "id": the request "id" member
+
+Both the JSON-RPC client and the server must implement this request.
+
+This JSON-RPC request and response can be used to implement connection
+keepalives, by allowing the server to check that the client is still
+there or vice versa.
+
+
+Notation for the Wire Protocol
+------------------------------
+
+<table>
+
+    An <id> that names a table.
+
+<column>
+
+    An <id> that names a table column.
+
+<row>
+
+    A JSON object that describes a table row or a subset of a table
+    row.  Each member is the name of a table column paired with the
+    <value> of that column.
+
+<value>
+
+    A JSON value that represents the value of a column in a table row,
+    one of <atom>, a <set>, or a <map>.
+
+<atom>
+
+    A JSON value that represents a scalar value for a column, one of
+    <string>, <number>, <boolean>, <uuid>, <named-uuid>.
+
+<set>
+
+    A 2-element JSON array that represents a database set value.  The
+    first element of the array must be the string "set" and the second
+    element must be an array of zero or more <atom>s giving the values
+    in the set.  All of the <atom>s must have the same type.
+
+<map>
+
+    A 2-element JSON array that represents a database map value.  The
+    first element of the array must be the string "map" and the second
+    element must be an array of zero or more <pair>s giving the values
+    in the map.  All of the <pair>s must have the same key and value
+    types.
+
+    (JSON objects are not used to represent <map> because JSON only
+    allows string names in an object.)
+
+<pair>
+
+    A 2-element JSON array that represents a pair within a database
+    map.  The first element is an <atom> that represents the key, the
+    second element is an <atom> that represents the value.
+
+<uuid>
+
+    A 2-element JSON array that represents a UUID.  The first element
+    of the array must be the string "uuid" and the second element must
+    be a 36-character string giving the UUID in the format described
+    by RFC 4122.  For example, the following <uuid> represents the
+    UUID 550e8400-e29b-41d4-a716-446655440000:
+
+        ["uuid", "550e8400-e29b-41d4-a716-446655440000"]
+
+<named-uuid>
+
+    A 2-element JSON array that represents the UUID of a row inserted
+    in a previous "insert" operation within the same transaction.  The
+    first element of the array must be the string "named-uuid" and the
+    second element must be the string specified on this "insert"
+    operation's "uuid-name" or on a preceding "insert" within the same
+    transaction.  For example, if this or a previous "insert"
+    operation specified a "uuid-name" of "myrow", the following
+    <named-uuid> represents the UUID created by that operation:
+
+        ["named-uuid", "myrow"]
+
+    A <named-uuid> may be used anywhere a <uuid> is valid.
+
+<condition>
+
+    A 3-element JSON array of the form [<column>, <function>,
+    <value>] that represents a test on a column value.
+
+    Except as otherwise specified below, <value> must have the same
+    type as <column>.
+
+    The meaning depends on the type of <column>:
+
+        integer
+        real
+
+            <function> must be "<", "<=", "==", "!=", ">=", ">",
+            "includes", or "excludes".
+
+            The test is true if the column's value satisfies the
+            relation <function> <value>, e.g. if the column has value
+            1 and <value> is 2, the test is true if <function> is "<",
+            "<=" or "!=", but not otherwise.
+
+            "includes" is equivalent to "=="; "excludes" is equivalent
+            to "!=".
+
+        boolean
+        string
+        uuid
+
+            <function> must be "!=", "==", "includes", or "excludes".
+
+            If <function> is "==" or "includes", the test is true if
+            the column's value equals <value>.  If <function> is "!="
+            or "excludes", the test is inverted.
+
+        set
+        map
+
+            <function> must be "!=", "==", "includes", or "excludes".
+
+            If <function> is "==", the test is true if the column's
+            value contains exactly the same values (for sets) or pairs
+            (for maps).  If <function> is "!=", the test is inverted.
+
+            If <function> is "includes", the test is true if the
+            column's value contains all of the values (for sets) or
+            pairs (for maps) in <value>.  The column's value may also
+            contain other values or pairs.
+
+            If <function> is "excludes", the test is true if the
+            column's value does not contain any of the values (for
+            sets) or pairs (for maps) in <value>.  The column's value
+            may contain other values or pairs not in <value>.
+
+            If <function> is "includes" or "excludes", then the
+            required type of <value> is slightly relaxed, in that it
+            may have fewer than the minimum number of elements
+            specified by the column's type.  If <function> is
+            "excludes", then the required type is additionally relaxed
+            in that <value> may have more than the maximum number of
+            elements specified by the column's type.
+
+<function>
+
+    One of "<", "<=", "==", "!=", ">=", ">", "includes", "excludes".
+
+<mutation>
+
+    A 3-element JSON array of the form [<column>, <mutator>, <value>]
+    that represents a change to a column value.
+
+    Except as otherwise specified below, <value> must have the same
+    type as <column>.
+
+    The meaning depends on the type of <column>:
+
+        integer
+        real
+
+            <mutator> must be "+=", "-=", "*=", "/=" or (integer only)
+            "%=".  The value of <column> is changed to the sum,
+            difference, product, quotient, or remainder, respectively,
+            of <column> and <value>.
+
+        boolean
+        string
+        uuid
+
+            No valid <mutator>s are currently defined for these types.
+
+        set
+
+            Any <mutator> valid for the set's element type may be
+            applied to the set, in which case the mutation is applied
+            to each member of the set individually.  <value> must be a
+            scalar value of the same type as the set's element type.
+
+            If <mutator> is "insert", then each of the values in the
+            set in <value> is added to <column> if it is not already
+            present.  The required type of <value> is slightly
+            relaxed, in that it may have fewer than the minimum number
+            of elements specified by the column's type.
+
+            If <mutator> is "delete", then each of the values in the
+            set in <value> is removed from <column> if it is present
+            there.  The required type is slightly relaxed in that
+            <value> may have more or less than the maximum number of
+            elements specified by the column's type.
+
+        map
+
+            <mutator> must be "insert" or "delete".
+
+            If <mutator> is "insert", then each of the key-value pairs
+            in the map in <value> is added to <column> only if its key
+            is not already present.  The required type of <value> is
+            slightly relaxed, in that it may have fewer than the
+            minimum number of elements specified by the column's type.
+
+            If <mutator> is "delete", then <value> may have the same
+            type as <column> (a map type) or it may be a set whose
+            element type is the same as <column>'s key type:
+
+                - If <value> is a map, the mutation deletes each
+                  key-value pair in <column> whose key and value equal
+                  one of the key-value pairs in <value>.
+
+                - If <value> is a set, the mutation deletes each
+                  key-value pair in <column> whose key equals one of
+                  the values in <value>.
+
+            For "delete", <value> may have any number of elements,
+            regardless of restrictions on the number of elements in
+            <column>.
+
+<mutator>
+
+    One of "+=", "-=", "*=", "/=", "%=", "insert", "delete".
+
+Operations
+----------
+
+Each of the available operations is described below.
+
+insert
+......
+
+Request object members:
+
+    "op": "insert"          required
+    "table": <table>        required
+    "row": <row>            required
+    "uuid-name": <id>       optional
+
+Result object members:
+
+    "uuid": <uuid>
+
+Semantics:
+
+    Inserts "row" into "table".  If "row" does not specify values
+    for all the columns in "table", those columns receive default
+    values.
+
+    If "uuid-name" is not supplied, the new row receives a new,
+    randomly generated UUID.
+
+    If "uuid-name" is supplied, then it is an error if <id> has
+    previously appeared as the "uuid-name" in an "insert" operation.
+
+    If "uuid-name" is supplied and its <id> previously appeared as the
+    "uuid-name" in a "declare" operation, then the new row receives
+    the UUID associated with that "uuid-name".
+
+    If "uuid-name" is supplied and its <id> has not previously
+    appeared as the "uuid-name" in a "declare" operation, then the new
+    row also receives a new, randomly generated UUID.  This UUID is
+    also made available under that name to this operation and later
+    operations within the same transaction.
+
+    The UUID for the new row is returned as the "uuid" member of the
+    result.
+
+Errors:
+
+    "error": "duplicate uuid-name"
+
+        The same "uuid-name" appeared on an earlier "insert" operation
+        within this transaction.
+
+select
+......
+
+Request object members:
+
+    "op": "select"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "columns": [<column>*]        optional
+
+Result object members:
+
+    "rows": [<row>*]
+
+Semantics:
+
+    Searches "table" for rows that match all the conditions specified
+    in "where".  If "where" is an empty array, every row in "table" is
+    selected.
+
+    The "rows" member of the result is an array of objects.  Each
+    object corresponds to a matching row, with each column
+    specified in "columns" as a member, the column's name as the
+    member name and its value as the member value.  If "columns"
+    is not specified, all the table's columns are included.  If
+    two rows of the result have the same values for all included
+    columns, only one copy of that row is included in "rows".
+    Specifying "_uuid" within "columns" will avoid dropping
+    duplicates, since every row has a unique UUID.
+
+    The ordering of rows within "rows" is unspecified.
+
+update
+......
+
+Request object members:
+
+    "op": "update"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "row": <row>                  required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Updates rows in a table.
+
+    Searches "table" for rows that match all the conditions
+    specified in "where".  For each matching row, changes the
+    value of each column specified in "row" to the value for that
+    column specified in "row".
+
+    The "_uuid" and "_version" columns of a table may not be directly
+    updated with this operation.  Columns designated read-only in the 
+    schema also may not be updated.
+
+    The "count" member of the result specifies the number of rows
+    that matched.
+
+mutate
+......
+
+Request object members:
+
+    "op": "mutate"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+    "mutations": [<mutation>*]    required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Mutates rows in a table.
+
+    Searches "table" for rows that match all the conditions specified
+    in "where".  For each matching row, mutates its columns as
+    specified by each <mutation> in "mutations", in the order
+    specified.
+
+    The "_uuid" and "_version" columns of a table may not be directly
+    modified with this operation.  Columns designated read-only in the
+    schema also may not be updated.
+
+    The "count" member of the result specifies the number of rows
+    that matched.
+
+Errors:
+
+    "error": "domain error"
+
+        The result of the mutation is not mathematically defined,
+        e.g. division by zero.
+
+    "error": "range error"
+
+        The result of the mutation is not representable within the
+        database's format, e.g. an integer result outside the range
+        INT64_MIN...INT64_MAX or a real result outside the range
+        -DBL_MAX...DBL_MAX.
+
+    "error": "constraint violation"
+
+        The mutation caused the column's value to violate a
+        constraint, e.g. it caused a column to have more or fewer
+        values than are allowed or an arithmetic operation caused a
+        set or map to have duplicate elements.
+
+delete
+......
+
+Request object members:
+
+    "op": "delete"                required
+    "table": <table>              required
+    "where": [<condition>*]       required
+
+Result object members:
+
+    "count": <integer>
+
+Semantics:
+
+    Deletes all the rows from "table" that match all the conditions
+    specified in "where".
+
+    The "count" member of the result specifies the number of deleted
+    rows.
+
+wait
+....
+
+Request object members:
+
+    "op": "wait"                        required
+    "timeout": <integer>                optional
+    "table": <table>                    required
+    "where": [<condition>*]             required
+    "columns": [<column>*]              required
+    "until": "==" or "!="               required
+    "rows": [<row>*]                    required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    Waits until a condition becomes true.
+
+    If "until" is "==", checks whether the query on "table" specified
+    by "where" and "columns", which is evaluated in the same way as
+    specified for "select", returns the result set specified by
+    "rows".  If it does, then the operation completes successfully.
+    Otherwise, the entire transaction rolls back.  It is automatically
+    restarted later, after a change in the database makes it possible
+    for the operation to succeed.  The client will not receive a
+    response until the operation permanently succeeds or fails.
+    
+    If "until" is "!=", the sense of the test is negated.  That is, as
+    long as the query on "table" specified by "where" and "columns"
+    returns "rows", the transaction will be rolled back and restarted
+    later.
+
+    If "timeout" is specified, then the transaction aborts after the
+    specified number of milliseconds.  The transaction is guaranteed
+    to be attempted at least once before it aborts.  A "timeout" of 0
+    will abort the transaction on the first mismatch.
+
+Errors:
+
+    "error": "not supported"
+
+        One or more of the columns in this table do not support
+        triggers.  This error will not occur if "timeout" is 0.
+
+    "error": "timed out"
+
+        The "timeout" was reached before the transaction was able to
+        complete.
+
+commit
+......
+
+Request object members:
+
+    "op": "commit"                      required
+    "durable": <boolean>                required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    If "durable" is specified as true, then the transaction, if it
+    commits, will be stored durably (to disk) before the reply is sent
+    to the client.
+
+Errors:
+
+    "error": "not supported"
+
+        When "durable" is true, this database implementation does not
+        support durable commits.
+
+abort
+.....
+
+Request object members:
+
+    "op": "abort"                      required
+
+Result object members:
+
+    (never succeeds)
+
+Semantics:
+
+    Aborts the transaction with an error.  This may be useful for
+    testing.
+
+Errors:
+
+    "error": "aborted"
+
+        This operation always fails with this error.
+
+declare
+.......
+
+Request object members:
+
+    "op": "declare"                    required
+    "uuid-name": <id>                  required
+
+Result object members:
+
+    "uuid": <uuid>
+
+Semantics:
+
+    Predeclares a UUID named <id> that may be referenced in later
+    operations as ["named-uuid", <id>] or (at most once) in an
+    "insert" operation as "uuid-name".
+
+    It is an error if <id> has appeared as the "uuid-name" in a prior
+    "insert" or "declare" operation within this transaction.
+
+    The generated UUID is returned as the "uuid" member of the result.
+
+Errors:
+
+    "error": "duplicate uuid-name"
+
+        The same "uuid-name" appeared on an earlier "insert" or
+        "declare" operation within this transaction.
+
+comment
+.......
+
+
+Request object members:
+
+    "op": "comment"                    required
+    "comment": <string>                required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    Provides information to a database administrator on the purpose of
+    a transaction.  The OVSDB server, for example, adds comments in
+    transactions that modify the database to the database journal.
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
new file mode 100644 (file)
index 0000000..8605410
--- /dev/null
@@ -0,0 +1,113 @@
+# libovsdb
+noinst_LIBRARIES += ovsdb/libovsdb.a
+ovsdb_libovsdb_a_SOURCES = \
+       ovsdb/column.c \
+       ovsdb/column.h \
+       ovsdb/condition.c \
+       ovsdb/condition.h \
+       ovsdb/execution.c \
+       ovsdb/file.c \
+       ovsdb/file.h \
+       ovsdb/jsonrpc-server.c \
+       ovsdb/jsonrpc-server.h \
+       ovsdb/log.c \
+       ovsdb/log.h \
+       ovsdb/mutation.c \
+       ovsdb/mutation.h \
+       ovsdb/ovsdb-server.c \
+       ovsdb/ovsdb.c \
+       ovsdb/ovsdb.h \
+       ovsdb/query.c \
+       ovsdb/query.h \
+       ovsdb/row.c \
+       ovsdb/row.h \
+       ovsdb/table.c \
+       ovsdb/table.h \
+       ovsdb/trigger.c \
+       ovsdb/trigger.h \
+       ovsdb/transaction.c \
+       ovsdb/transaction.h
+EXTRA_DIST += \
+       ovsdb/remote-active.man \
+       ovsdb/remote-passive.man
+
+# ovsdb-tool
+bin_PROGRAMS += ovsdb/ovsdb-tool
+ovsdb_ovsdb_tool_SOURCES = ovsdb/ovsdb-tool.c
+ovsdb_ovsdb_tool_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a
+# ovsdb-tool.1
+man_MANS += ovsdb/ovsdb-tool.1
+DISTCLEANFILES += ovsdb/ovsdb-tool.1
+EXTRA_DIST += ovsdb/ovsdb-tool.1.in
+
+# ovsdb-client
+bin_PROGRAMS += ovsdb/ovsdb-client
+ovsdb_ovsdb_client_SOURCES = ovsdb/ovsdb-client.c
+ovsdb_ovsdb_client_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS)
+# ovsdb-client.1
+man_MANS += ovsdb/ovsdb-client.1
+DISTCLEANFILES += ovsdb/ovsdb-client.1
+EXTRA_DIST += ovsdb/ovsdb-client.1.in
+
+# ovsdb-server
+sbin_PROGRAMS += ovsdb/ovsdb-server
+ovsdb_ovsdb_server_SOURCES = ovsdb/ovsdb-server.c
+ovsdb_ovsdb_server_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS)
+# ovsdb-server.1
+man_MANS += ovsdb/ovsdb-server.1
+DISTCLEANFILES += ovsdb/ovsdb-server.1
+EXTRA_DIST += ovsdb/ovsdb-server.1.in
+
+# ovsdb-idlc
+EXTRA_DIST += \
+       ovsdb/simplejson/__init__.py \
+       ovsdb/simplejson/_speedups.c                            \
+       ovsdb/simplejson/decoder.py                             \
+       ovsdb/simplejson/encoder.py                             \
+       ovsdb/simplejson/scanner.py                             \
+       ovsdb/simplejson/tests/__init__.py                      \
+       ovsdb/simplejson/tests/test_check_circular.py           \
+       ovsdb/simplejson/tests/test_decode.py                   \
+       ovsdb/simplejson/tests/test_default.py                  \
+       ovsdb/simplejson/tests/test_dump.py                     \
+       ovsdb/simplejson/tests/test_encode_basestring_ascii.py  \
+       ovsdb/simplejson/tests/test_fail.py                     \
+       ovsdb/simplejson/tests/test_float.py                    \
+       ovsdb/simplejson/tests/test_indent.py                   \
+       ovsdb/simplejson/tests/test_pass1.py                    \
+       ovsdb/simplejson/tests/test_pass2.py                    \
+       ovsdb/simplejson/tests/test_pass3.py                    \
+       ovsdb/simplejson/tests/test_recursion.py                \
+       ovsdb/simplejson/tests/test_scanstring.py               \
+       ovsdb/simplejson/tests/test_separators.py               \
+       ovsdb/simplejson/tests/test_unicode.py                  \
+       ovsdb/simplejson/tool.py
+noinst_SCRIPTS += ovsdb/ovsdb-idlc
+EXTRA_DIST += \
+       ovsdb/ovsdb-idlc.in \
+       ovsdb/ovsdb-idlc.1
+DISTCLEANFILES += ovsdb/ovsdb-idlc
+SUFFIXES += .ovsidl .txt
+OVSDB_IDLC = $(PYTHON) $(srcdir)/ovsdb/ovsdb-idlc.in
+.ovsidl.c:
+       $(OVSDB_IDLC) c-idl-source $< > $@.tmp
+       mv $@.tmp $@
+.ovsidl.h:
+       $(OVSDB_IDLC) c-idl-header $< > $@.tmp
+       mv $@.tmp $@
+.ovsidl.txt:
+       $(OVSDB_IDLC) doc $< | fmt -s > $@.tmp
+       mv $@.tmp $@
+
+EXTRA_DIST += $(OVSIDL_BUILT)
+BUILT_SOURCES += $(OVSIDL_BUILT)
+
+# This must be done late: macros in targets are expanded when the
+# target line is read, so if this file were to be included before some
+# other file that added to OVSIDL_BUILT, then those files wouldn't get
+# the dependency.
+#
+# However, current versions of Automake seem to output all variable
+# assignments before any targets, so it doesn't seem to be a problem,
+# at least for now.
+$(OVSIDL_BUILT): ovsdb/ovsdb-idlc.in
diff --git a/ovsdb/column.c b/ovsdb/column.c
new file mode 100644 (file)
index 0000000..73dc9c2
--- /dev/null
@@ -0,0 +1,254 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb/column.h"
+
+#include <stdlib.h>
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "table.h"
+#include "util.h"
+
+struct ovsdb_column *
+ovsdb_column_create(const char *name, const char *comment,
+                    bool mutable, bool persistent,
+                    const struct ovsdb_type *type)
+{
+    struct ovsdb_column *column;
+
+    column = xzalloc(sizeof *column);
+    column->name = xstrdup(name);
+    column->comment = comment ? xstrdup(comment) : NULL;
+    column->mutable = mutable;
+    column->persistent = persistent;
+    column->type = *type;
+
+    return column;
+}
+
+void
+ovsdb_column_destroy(struct ovsdb_column *column)
+{
+    free(column->name);
+    free(column->comment);
+    free(column);
+}
+
+struct ovsdb_error *
+ovsdb_column_from_json(const struct json *json, const char *name,
+                       struct ovsdb_column **columnp)
+{
+    const struct json *comment, *mutable, *ephemeral, *type_json;
+    struct ovsdb_error *error;
+    struct ovsdb_type type;
+    struct ovsdb_parser parser;
+    bool persistent;
+
+    *columnp = NULL;
+
+    ovsdb_parser_init(&parser, json, "schema for column %s", name);
+    comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+    mutable = ovsdb_parser_member(&parser, "mutable",
+                                OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    ephemeral = ovsdb_parser_member(&parser, "ephemeral",
+                                    OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    type_json = ovsdb_parser_member(&parser, "type", OP_STRING | OP_OBJECT);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    error = ovsdb_type_from_json(&type, type_json);
+    if (error) {
+        return error;
+    }
+
+    persistent = ephemeral ? !json_boolean(ephemeral) : true;
+    *columnp = ovsdb_column_create(name,
+                                   comment ? json_string(comment) : NULL,
+                                   mutable ? json_boolean(mutable) : true,
+                                   persistent, &type);
+    return NULL;
+}
+
+struct json *
+ovsdb_column_to_json(const struct ovsdb_column *column)
+{
+    struct json *json = json_object_create();
+    if (column->comment) {
+        json_object_put_string(json, "comment", column->comment);
+    }
+    if (!column->mutable) {
+        json_object_put(json, "mutable", json_boolean_create(false));
+    }
+    if (!column->persistent) {
+        json_object_put(json, "ephemeral", json_boolean_create(true));
+    }
+    json_object_put(json, "type", ovsdb_type_to_json(&column->type));
+    return json;
+}
+\f
+void
+ovsdb_column_set_init(struct ovsdb_column_set *set)
+{
+    set->columns = NULL;
+    set->n_columns = set->allocated_columns = 0;
+}
+
+void
+ovsdb_column_set_destroy(struct ovsdb_column_set *set)
+{
+    free(set->columns);
+}
+
+void
+ovsdb_column_set_clone(struct ovsdb_column_set *new,
+                       const struct ovsdb_column_set *old)
+{
+    new->columns = xmemdup(old->columns,
+                           old->n_columns * sizeof *old->columns);
+    new->n_columns = new->allocated_columns = old->n_columns;
+}
+
+struct ovsdb_error *
+ovsdb_column_set_from_json(const struct json *json,
+                           const struct ovsdb_table *table,
+                           struct ovsdb_column_set *set)
+{
+    ovsdb_column_set_init(set);
+    if (!json) {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            ovsdb_column_set_add(set, column);
+        }
+
+        return NULL;
+    } else {
+        struct ovsdb_error *error = NULL;
+        size_t i;
+
+        if (json->type != JSON_ARRAY) {
+            goto error;
+        }
+
+        /* XXX this is O(n**2) */
+        for (i = 0; i < json->u.array.n; i++) {
+            struct ovsdb_column *column;
+            const char *s;
+
+            if (json->u.array.elems[i]->type != JSON_STRING) {
+                goto error;
+            }
+
+            s = json->u.array.elems[i]->u.string;
+            column = shash_find_data(&table->schema->columns, s);
+            if (!column) {
+                error = ovsdb_syntax_error(json, NULL, "%s is not a valid "
+                                           "column name", s);
+                goto error;
+            } else if (ovsdb_column_set_contains(set, column->index)) {
+                goto error;
+            }
+            ovsdb_column_set_add(set, column);
+        }
+        return NULL;
+
+    error:
+        ovsdb_column_set_destroy(set);
+        ovsdb_column_set_init(set);
+        if (!error) {
+            error = ovsdb_syntax_error(json, NULL, "array of distinct column "
+                                       "names expected");
+        }
+        return error;
+    }
+}
+
+struct json *
+ovsdb_column_set_to_json(const struct ovsdb_column_set *set)
+{
+    struct json *json;
+    size_t i;
+
+    json = json_array_create_empty();
+    for (i = 0; i < set->n_columns; i++) {
+        json_array_add(json, json_string_create(set->columns[i]->name));
+    }
+    return json;
+}
+
+void
+ovsdb_column_set_add(struct ovsdb_column_set *set,
+                     const struct ovsdb_column *column)
+{
+    if (set->n_columns >= set->allocated_columns) {
+        set->columns = x2nrealloc(set->columns, &set->allocated_columns,
+                                  sizeof *set->columns);
+    }
+    set->columns[set->n_columns++] = column;
+}
+
+void
+ovsdb_column_set_add_all(struct ovsdb_column_set *set,
+                         const struct ovsdb_table *table)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_column_set_add(set, column);
+    }
+}
+
+bool
+ovsdb_column_set_contains(const struct ovsdb_column_set *set,
+                          unsigned int column_index)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_columns; i++) {
+        if (set->columns[i]->index == column_index) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/* This comparison is sensitive to ordering of columns within a set, but that's
+ * good: the only existing caller wants to make sure that hash values are
+ * comparable, which is only true if column ordering is the same. */
+bool
+ovsdb_column_set_equals(const struct ovsdb_column_set *a,
+                        const struct ovsdb_column_set *b)
+{
+    size_t i;
+
+    if (a->n_columns != b->n_columns) {
+        return false;
+    }
+    for (i = 0; i < a->n_columns; i++) {
+        if (a->columns[i] != b->columns[i]) {
+            return false;
+        }
+    }
+    return true;
+}
diff --git a/ovsdb/column.h b/ovsdb/column.h
new file mode 100644 (file)
index 0000000..5fd39ae
--- /dev/null
@@ -0,0 +1,85 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_COLUMN_H
+#define OVSDB_COLUMN_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "ovsdb-types.h"
+
+struct ovsdb_table;
+
+/* A column or a column schema (currently there is no distinction). */
+struct ovsdb_column {
+    unsigned int index;
+    char *name;
+
+    char *comment;
+    bool mutable;
+    bool persistent;
+    struct ovsdb_type type;
+};
+
+/* A few columns appear in every table with standardized column indexes.
+ * These macros define those columns' indexes.
+ *
+ * Don't change these values, because ovsdb_query() depends on OVSDB_COL_UUID
+ * having value 0. */
+enum {
+    OVSDB_COL_UUID = 0,         /* UUID for the row. */
+    OVSDB_COL_VERSION = 1,      /* Version number for the row. */
+    OVSDB_N_STD_COLUMNS
+};
+
+struct ovsdb_column *ovsdb_column_create(
+    const char *name, const char *comment, bool mutable, bool persistent,
+    const struct ovsdb_type *);
+void ovsdb_column_destroy(struct ovsdb_column *);
+
+struct ovsdb_error *ovsdb_column_from_json(const struct json *,
+                                           const char *name,
+                                           struct ovsdb_column **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_column_to_json(const struct ovsdb_column *);
+\f
+/* An unordered set of distinct columns. */
+
+struct ovsdb_column_set {
+    const struct ovsdb_column **columns;
+    size_t n_columns, allocated_columns;
+};
+
+#define OVSDB_COLUMN_SET_INITIALIZER { NULL, 0, 0 }
+
+void ovsdb_column_set_init(struct ovsdb_column_set *);
+void ovsdb_column_set_destroy(struct ovsdb_column_set *);
+void ovsdb_column_set_clone(struct ovsdb_column_set *,
+                            const struct ovsdb_column_set *);
+struct ovsdb_error *ovsdb_column_set_from_json(const struct json *,
+                                               const struct ovsdb_table *,
+                                               struct ovsdb_column_set *);
+struct json *ovsdb_column_set_to_json(const struct ovsdb_column_set *);
+
+void ovsdb_column_set_add(struct ovsdb_column_set *,
+                          const struct ovsdb_column *);
+void ovsdb_column_set_add_all(struct ovsdb_column_set *,
+                              const struct ovsdb_table *);
+bool ovsdb_column_set_contains(const struct ovsdb_column_set *,
+                               unsigned int column_index);
+bool ovsdb_column_set_equals(const struct ovsdb_column_set *,
+                             const struct ovsdb_column_set *);
+
+#endif /* column.h */
diff --git a/ovsdb/condition.c b/ovsdb/condition.c
new file mode 100644 (file)
index 0000000..f3f4300
--- /dev/null
@@ -0,0 +1,283 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "condition.h"
+
+#include <limits.h>
+
+#include "column.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "row.h"
+#include "table.h"
+
+struct ovsdb_error *
+ovsdb_function_from_string(const char *name, enum ovsdb_function *function)
+{
+#define OVSDB_FUNCTION(ENUM, NAME)              \
+    if (!strcmp(name, NAME)) {                  \
+        *function = ENUM;                       \
+        return NULL;                            \
+    }
+    OVSDB_FUNCTIONS;
+#undef OVSDB_FUNCTION
+
+    return ovsdb_syntax_error(NULL, "unknown function",
+                              "No function named %s.", name);
+}
+
+const char *
+ovsdb_function_to_string(enum ovsdb_function function)
+{
+    switch (function) {
+#define OVSDB_FUNCTION(ENUM, NAME) case ENUM: return NAME;
+        OVSDB_FUNCTIONS;
+#undef OVSDB_FUNCTION
+    }
+
+    return NULL;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+ovsdb_clause_from_json(const struct ovsdb_table_schema *ts,
+                       const struct json *json,
+                       const struct ovsdb_symbol_table *symtab,
+                       struct ovsdb_clause *clause)
+{
+    const struct json_array *array;
+    struct ovsdb_error *error;
+    const char *function_name;
+    const char *column_name;
+    struct ovsdb_type type;
+
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 3
+        || json->u.array.elems[0]->type != JSON_STRING
+        || json->u.array.elems[1]->type != JSON_STRING) {
+        return ovsdb_syntax_error(json, NULL, "Parse error in condition.");
+    }
+    array = json_array(json);
+
+    column_name = json_string(array->elems[0]);
+    clause->column = ovsdb_table_schema_get_column(ts, column_name);
+    if (!clause->column) {
+        return ovsdb_syntax_error(json, "unknown column",
+                                  "No column %s in table %s.",
+                                  column_name, ts->name);
+    }
+    type = clause->column->type;
+
+    function_name = json_string(array->elems[1]);
+    error = ovsdb_function_from_string(function_name, &clause->function);
+    if (error) {
+        return error;
+    }
+
+    /* Type-check and relax restrictions on 'type' if appropriate.  */
+    switch (clause->function) {
+    case OVSDB_F_LT:
+    case OVSDB_F_LE:
+    case OVSDB_F_GT:
+    case OVSDB_F_GE:
+        /* XXX should we also allow these operators for types with n_min == 0,
+         * n_max == 1?  (They would always be "false" if the value was
+         * missing.) */
+        if (!ovsdb_type_is_scalar(&type)
+            || (type.key_type != OVSDB_TYPE_INTEGER
+                && type.key_type != OVSDB_TYPE_REAL)) {
+            char *s = ovsdb_type_to_english(&type);
+            error = ovsdb_syntax_error(
+                json, NULL, "Type mismatch: \"%s\" operator may not be "
+                "applied to column %s of type %s.",
+                ovsdb_function_to_string(clause->function),
+                clause->column->name, s);
+            free(s);
+            return error;
+        }
+        break;
+
+    case OVSDB_F_EQ:
+    case OVSDB_F_NE:
+        break;
+
+    case OVSDB_F_EXCLUDES:
+        if (!ovsdb_type_is_scalar(&type)) {
+            type.n_min = 0;
+            type.n_max = UINT_MAX;
+        }
+        break;
+
+    case OVSDB_F_INCLUDES:
+        if (!ovsdb_type_is_scalar(&type)) {
+            type.n_min = 0;
+        }
+        break;
+    }
+    return ovsdb_datum_from_json(&clause->arg, &type, array->elems[2], symtab);
+}
+
+static void
+ovsdb_clause_free(struct ovsdb_clause *clause)
+{
+    ovsdb_datum_destroy(&clause->arg, &clause->column->type);
+}
+
+static int
+compare_clauses_3way(const void *a_, const void *b_)
+{
+    const struct ovsdb_clause *a = a_;
+    const struct ovsdb_clause *b = b_;
+
+    if (a->function != b->function) {
+        /* Bring functions to the front based on the fraction of table rows
+         * that they are (heuristically) expected to leave in the query
+         * results.  Note that "enum ovsdb_function" is intentionally ordered
+         * to make this trivial. */
+        return a->function < b->function ? -1 : 1;
+    } else if (a->column->index != b->column->index) {
+        if (a->column->index < OVSDB_N_STD_COLUMNS
+            || b->column->index < OVSDB_N_STD_COLUMNS) {
+            /* Bring the standard columns and in particular the UUID column
+             * (since OVSDB_COL_UUID has value 0) to the front.  We have an
+             * index on the UUID column, so that makes our queries cheaper. */
+            return a->column->index < b->column->index ? -1 : 1;
+        } else {
+            /* Order clauses predictably to make testing easier. */
+            return strcmp(a->column->name, b->column->name);
+        }
+    } else {
+        return 0;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_condition_from_json(const struct ovsdb_table_schema *ts,
+                          const struct json *json,
+                          const struct ovsdb_symbol_table *symtab,
+                          struct ovsdb_condition *cnd)
+{
+    const struct json_array *array = json_array(json);
+    size_t i;
+
+    cnd->clauses = xmalloc(array->n * sizeof *cnd->clauses);
+    cnd->n_clauses = 0;
+    for (i = 0; i < array->n; i++) {
+        struct ovsdb_error *error;
+        error = ovsdb_clause_from_json(ts, array->elems[i], symtab,
+                                       &cnd->clauses[i]);
+        if (error) {
+            ovsdb_condition_destroy(cnd);
+            cnd->clauses = NULL;
+            cnd->n_clauses = 0;
+            return error;
+        }
+        cnd->n_clauses++;
+    }
+
+    /* A real database would have a query optimizer here. */
+    qsort(cnd->clauses, cnd->n_clauses, sizeof *cnd->clauses,
+          compare_clauses_3way);
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_clause_to_json(const struct ovsdb_clause *clause)
+{
+    return json_array_create_3(
+        json_string_create(clause->column->name),
+        json_string_create(ovsdb_function_to_string(clause->function)),
+        ovsdb_datum_to_json(&clause->arg, &clause->column->type));
+}
+
+struct json *
+ovsdb_condition_to_json(const struct ovsdb_condition *cnd)
+{
+    struct json **clauses;
+    size_t i;
+
+    clauses = xmalloc(cnd->n_clauses * sizeof *clauses);
+    for (i = 0; i < cnd->n_clauses; i++) {
+        clauses[i] = ovsdb_clause_to_json(&cnd->clauses[i]);
+    }
+    return json_array_create(clauses, cnd->n_clauses);
+}
+
+bool
+ovsdb_condition_evaluate(const struct ovsdb_row *row,
+                         const struct ovsdb_condition *cnd)
+{
+    size_t i;
+
+    for (i = 0; i < cnd->n_clauses; i++) {
+        const struct ovsdb_clause *c = &cnd->clauses[i];
+        const struct ovsdb_datum *field = &row->fields[c->column->index];
+        const struct ovsdb_datum *arg = &cnd->clauses[i].arg;
+        const struct ovsdb_type *type = &c->column->type;
+
+        if (ovsdb_type_is_scalar(type)) {
+            int cmp = ovsdb_atom_compare_3way(&field->keys[0], &arg->keys[0],
+                                              type->key_type);
+            switch (c->function) {
+            case OVSDB_F_LT:
+                return cmp < 0;
+            case OVSDB_F_LE:
+                return cmp <= 0;
+            case OVSDB_F_EQ:
+            case OVSDB_F_INCLUDES:
+                return cmp == 0;
+            case OVSDB_F_NE:
+            case OVSDB_F_EXCLUDES:
+                return cmp != 0;
+            case OVSDB_F_GE:
+                return cmp >= 0;
+            case OVSDB_F_GT:
+                return cmp > 0;
+            }
+        } else {
+            switch (c->function) {
+            case OVSDB_F_EQ:
+                return ovsdb_datum_equals(field, arg, type);
+            case OVSDB_F_NE:
+                return !ovsdb_datum_equals(field, arg, type);
+            case OVSDB_F_INCLUDES:
+                return ovsdb_datum_includes_all(arg, field, type);
+            case OVSDB_F_EXCLUDES:
+                return ovsdb_datum_excludes_all(arg, field, type);
+            case OVSDB_F_LT:
+            case OVSDB_F_LE:
+            case OVSDB_F_GE:
+            case OVSDB_F_GT:
+                NOT_REACHED();
+            }
+        }
+        NOT_REACHED();
+    }
+
+    return true;
+}
+
+void
+ovsdb_condition_destroy(struct ovsdb_condition *cnd)
+{
+    size_t i;
+
+    for (i = 0; i < cnd->n_clauses; i++) {
+        ovsdb_clause_free(&cnd->clauses[i]);
+    }
+    free(cnd->clauses);
+}
diff --git a/ovsdb/condition.h b/ovsdb/condition.h
new file mode 100644 (file)
index 0000000..8c422b9
--- /dev/null
@@ -0,0 +1,72 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_CONDITION_H
+#define OVSDB_CONDITION_H 1
+
+#include <stddef.h>
+#include "compiler.h"
+#include "ovsdb-data.h"
+
+struct json;
+struct ovsdb_table_schema;
+struct ovsdb_row;
+
+/* These list is ordered in ascending order of the fraction of tables row that
+ * they are (heuristically) expected to leave in query results. */
+#define OVSDB_FUNCTIONS                         \
+    OVSDB_FUNCTION(OVSDB_F_EQ, "==")                  \
+    OVSDB_FUNCTION(OVSDB_F_INCLUDES, "includes")      \
+    OVSDB_FUNCTION(OVSDB_F_LE, "<=")                  \
+    OVSDB_FUNCTION(OVSDB_F_LT, "<")                   \
+    OVSDB_FUNCTION(OVSDB_F_GE, ">=")                  \
+    OVSDB_FUNCTION(OVSDB_F_GT, ">")                   \
+    OVSDB_FUNCTION(OVSDB_F_EXCLUDES, "excludes")      \
+    OVSDB_FUNCTION(OVSDB_F_NE, "!=")
+
+enum ovsdb_function {
+#define OVSDB_FUNCTION(ENUM, NAME) ENUM,
+    OVSDB_FUNCTIONS
+#undef OVSDB_FUNCTION
+};
+
+struct ovsdb_error *ovsdb_function_from_string(const char *,
+                                               enum ovsdb_function *)
+    WARN_UNUSED_RESULT;
+const char *ovsdb_function_to_string(enum ovsdb_function);
+
+struct ovsdb_clause {
+    enum ovsdb_function function;
+    const struct ovsdb_column *column;
+    struct ovsdb_datum arg;
+};
+
+struct ovsdb_condition {
+    struct ovsdb_clause *clauses;
+    size_t n_clauses;
+};
+
+#define OVSDB_CONDITION_INITIALIZER { NULL, 0 }
+
+struct ovsdb_error *ovsdb_condition_from_json(
+    const struct ovsdb_table_schema *,
+    const struct json *, const struct ovsdb_symbol_table *,
+    struct ovsdb_condition *) WARN_UNUSED_RESULT;
+struct json *ovsdb_condition_to_json(const struct ovsdb_condition *);
+void ovsdb_condition_destroy(struct ovsdb_condition *);
+bool ovsdb_condition_evaluate(const struct ovsdb_row *,
+                              const struct ovsdb_condition *);
+
+#endif /* ovsdb/condition.h */
diff --git a/ovsdb/execution.c b/ovsdb/execution.c
new file mode 100644 (file)
index 0000000..514a278
--- /dev/null
@@ -0,0 +1,707 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <limits.h>
+
+#include "column.h"
+#include "condition.h"
+#include "file.h"
+#include "json.h"
+#include "mutation.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb.h"
+#include "query.h"
+#include "row.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+
+struct ovsdb_execution {
+    struct ovsdb *db;
+    struct ovsdb_txn *txn;
+    struct ovsdb_symbol_table *symtab;
+    bool durable;
+
+    /* Triggers. */
+    long long int elapsed_msec;
+    long long int timeout_msec;
+};
+
+typedef struct ovsdb_error *ovsdb_operation_executor(struct ovsdb_execution *,
+                                                     struct ovsdb_parser *,
+                                                     struct json *result);
+
+static ovsdb_operation_executor ovsdb_execute_insert;
+static ovsdb_operation_executor ovsdb_execute_select;
+static ovsdb_operation_executor ovsdb_execute_update;
+static ovsdb_operation_executor ovsdb_execute_mutate;
+static ovsdb_operation_executor ovsdb_execute_delete;
+static ovsdb_operation_executor ovsdb_execute_wait;
+static ovsdb_operation_executor ovsdb_execute_commit;
+static ovsdb_operation_executor ovsdb_execute_abort;
+static ovsdb_operation_executor ovsdb_execute_declare;
+static ovsdb_operation_executor ovsdb_execute_comment;
+
+static ovsdb_operation_executor *
+lookup_executor(const char *name)
+{
+    struct ovsdb_operation {
+        const char *name;
+        ovsdb_operation_executor *executor;
+    };
+
+    static const struct ovsdb_operation operations[] = {
+        { "insert", ovsdb_execute_insert },
+        { "select", ovsdb_execute_select },
+        { "update", ovsdb_execute_update },
+        { "mutate", ovsdb_execute_mutate },
+        { "delete", ovsdb_execute_delete },
+        { "wait", ovsdb_execute_wait },
+        { "commit", ovsdb_execute_commit },
+        { "abort", ovsdb_execute_abort },
+        { "declare", ovsdb_execute_declare },
+        { "comment", ovsdb_execute_comment },
+    };
+
+    size_t i;
+
+    for (i = 0; i < ARRAY_SIZE(operations); i++) {
+        const struct ovsdb_operation *c = &operations[i];
+        if (!strcmp(c->name, name)) {
+            return c->executor;
+        }
+    }
+    return NULL;
+}
+
+struct json *
+ovsdb_execute(struct ovsdb *db, const struct json *params,
+              long long int elapsed_msec, long long int *timeout_msec)
+{
+    struct ovsdb_execution x;
+    struct ovsdb_error *error;
+    struct json *results;
+    size_t n_operations;
+    size_t i;
+
+    if (params->type != JSON_ARRAY) {
+        struct ovsdb_error *error;
+
+        error = ovsdb_syntax_error(params, NULL, "array expected");
+        results = ovsdb_error_to_json(error);
+        ovsdb_error_destroy(error);
+        return results;
+    }
+
+    x.db = db;
+    x.txn = ovsdb_txn_create(db);
+    x.symtab = ovsdb_symbol_table_create();
+    x.durable = false;
+    x.elapsed_msec = elapsed_msec;
+    x.timeout_msec = LLONG_MAX;
+    results = NULL;
+
+    results = json_array_create_empty();
+    n_operations = params->u.array.n;
+    error = NULL;
+    for (i = 0; i < n_operations; i++) {
+        struct json *operation = params->u.array.elems[i];
+        struct ovsdb_error *parse_error;
+        struct ovsdb_parser parser;
+        struct json *result;
+        const struct json *op;
+
+        /* Parse and execute operation. */
+        ovsdb_parser_init(&parser, operation,
+                          "ovsdb operation %zu of %zu", i + 1, n_operations);
+        op = ovsdb_parser_member(&parser, "op", OP_ID);
+        result = json_object_create();
+        if (op) {
+            const char *op_name = json_string(op);
+            ovsdb_operation_executor *executor = lookup_executor(op_name);
+            if (executor) {
+                error = executor(&x, &parser, result);
+            } else {
+                ovsdb_parser_raise_error(&parser, "No operation \"%s\"",
+                                         op_name);
+            }
+        } else {
+            assert(ovsdb_parser_has_error(&parser));
+        }
+
+        /* A parse error overrides any other error.
+         * An error overrides any other result. */
+        parse_error = ovsdb_parser_finish(&parser);
+        if (parse_error) {
+            ovsdb_error_destroy(error);
+            error = parse_error;
+        }
+        if (error) {
+            json_destroy(result);
+            result = ovsdb_error_to_json(error);
+        }
+        if (error && !strcmp(ovsdb_error_get_tag(error), "not supported")
+            && timeout_msec) {
+            ovsdb_txn_abort(x.txn);
+            *timeout_msec = x.timeout_msec;
+
+            json_destroy(result);
+            json_destroy(results);
+            results = NULL;
+            goto exit;
+        }
+
+        /* Add result to array. */
+        json_array_add(results, result);
+        if (error) {
+            break;
+        }
+    }
+
+    if (!error) {
+        error = ovsdb_txn_commit(x.txn, x.durable);
+        if (error) {
+            json_array_add(results, ovsdb_error_to_json(error));
+        }
+    } else {
+        ovsdb_txn_abort(x.txn);
+    }
+
+    while (json_array(results)->n < n_operations) {
+        json_array_add(results, json_null_create());
+    }
+
+exit:
+    ovsdb_error_destroy(error);
+    ovsdb_symbol_table_destroy(x.symtab);
+
+    return results;
+}
+
+struct ovsdb_error *
+ovsdb_execute_commit(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result UNUSED)
+{
+    const struct json *durable;
+
+    durable = ovsdb_parser_member(parser, "durable", OP_BOOLEAN);
+    if (durable && json_boolean(durable)) {
+        x->durable = true;
+    }
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_execute_abort(struct ovsdb_execution *x UNUSED,
+                    struct ovsdb_parser *parser UNUSED,
+                    struct json *result UNUSED)
+{
+    return ovsdb_error("aborted", "aborted by request");
+}
+
+static struct ovsdb_table *
+parse_table(struct ovsdb_execution *x,
+            struct ovsdb_parser *parser, const char *member)
+{
+    struct ovsdb_table *table;
+    const char *table_name;
+    const struct json *json;
+
+    json = ovsdb_parser_member(parser, member, OP_ID);
+    if (!json) {
+        return NULL;
+    }
+    table_name = json_string(json);
+
+    table = shash_find_data(&x->db->tables, table_name);
+    if (!table) {
+        ovsdb_parser_raise_error(parser, "No table named %s.", table_name);
+    }
+    return table;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+parse_row(struct ovsdb_parser *parser, const char *member,
+          const struct ovsdb_table *table,
+          const struct ovsdb_symbol_table *symtab,
+          struct ovsdb_row **rowp, struct ovsdb_column_set *columns)
+{
+    struct ovsdb_error *error;
+    const struct json *json;
+    struct ovsdb_row *row;
+
+    *rowp = NULL;
+
+    if (!table) {
+        return OVSDB_BUG("null table");
+    }
+    json = ovsdb_parser_member(parser, member, OP_OBJECT);
+    if (!json) {
+        return OVSDB_BUG("null row member");
+    }
+
+    row = ovsdb_row_create(table);
+    error = ovsdb_row_from_json(row, json, symtab, columns);
+    if (error) {
+        ovsdb_row_destroy(row);
+        return error;
+    } else {
+        *rowp = row;
+        return NULL;
+    }
+}
+
+struct ovsdb_error *
+ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    struct ovsdb_row *row = NULL;
+    const struct json *uuid_name;
+    struct ovsdb_error *error;
+    struct uuid row_uuid;
+
+    table = parse_table(x, parser, "table");
+    uuid_name = ovsdb_parser_member(parser, "uuid-name", OP_ID | OP_OPTIONAL);
+    error = ovsdb_parser_get_error(parser);
+
+    if (uuid_name) {
+        struct ovsdb_symbol *symbol;
+
+        symbol = ovsdb_symbol_table_get(x->symtab, json_string(uuid_name));
+        if (symbol) {
+            if (symbol->used) {
+                return ovsdb_syntax_error(uuid_name, "duplicate uuid-name",
+                                          "This \"uuid-name\" appeared on an "
+                                          "earlier \"insert\" operation.");
+            }
+            row_uuid = symbol->uuid;
+            symbol->used = true;
+        } else {
+            uuid_generate(&row_uuid);
+            ovsdb_symbol_table_put(x->symtab, json_string(uuid_name),
+                                   &row_uuid, true);
+        }
+    } else {
+        uuid_generate(&row_uuid);
+    }
+
+    if (!error) {
+        error = parse_row(parser, "row", table, x->symtab, &row, NULL);
+    }
+    if (!error) {
+        *ovsdb_row_get_uuid_rw(row) = row_uuid;
+        ovsdb_txn_row_insert(x->txn, row);
+        json_object_put(result, "uuid",
+                        ovsdb_datum_to_json(&row->fields[OVSDB_COL_UUID],
+                                            &ovsdb_type_uuid));
+        row = NULL;
+    }
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_execute_select(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where, *columns_json, *sort_json;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_column_set sort = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    columns_json = ovsdb_parser_member(parser, "columns",
+                                       OP_ARRAY | OP_OPTIONAL);
+    sort_json = ovsdb_parser_member(parser, "sort", OP_ARRAY | OP_OPTIONAL);
+
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(columns_json, table, &columns);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(sort_json, table, &sort);
+    }
+    if (!error) {
+        struct ovsdb_row_set rows = OVSDB_ROW_SET_INITIALIZER;
+
+        ovsdb_query_distinct(table, &condition, &columns, &rows);
+        ovsdb_row_set_sort(&rows, &sort);
+        json_object_put(result, "rows",
+                        ovsdb_row_set_to_json(&rows, &columns));
+
+        ovsdb_row_set_destroy(&rows);
+    }
+
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_column_set_destroy(&sort);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct update_row_cbdata {
+    size_t n_matches;
+    struct ovsdb_txn *txn;
+    const struct ovsdb_row *row;
+    const struct ovsdb_column_set *columns;
+};
+
+static bool
+update_row_cb(const struct ovsdb_row *row, void *ur_)
+{
+    struct update_row_cbdata *ur = ur_;
+
+    ur->n_matches++;
+    if (!ovsdb_row_equal_columns(row, ur->row, ur->columns)) {
+        ovsdb_row_update_columns(ovsdb_txn_row_modify(ur->txn, row),
+                                 ur->row, ur->columns);
+    }
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_row *row = NULL;
+    struct update_row_cbdata ur;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = parse_row(parser, "row", table, x->symtab, &row, &columns);
+    }
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        ur.n_matches = 0;
+        ur.txn = x->txn;
+        ur.row = row;
+        ur.columns = &columns;
+        ovsdb_query(table, &condition, update_row_cb, &ur);
+        json_object_put(result, "count", json_integer_create(ur.n_matches));
+    }
+
+    ovsdb_row_destroy(row);
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct mutate_row_cbdata {
+    size_t n_matches;
+    struct ovsdb_txn *txn;
+    const struct ovsdb_mutation_set *mutations;
+};
+
+static bool
+mutate_row_cb(const struct ovsdb_row *row, void *mr_)
+{
+    struct mutate_row_cbdata *mr = mr_;
+
+    mr->n_matches++;
+    ovsdb_mutation_set_execute(ovsdb_txn_row_modify(mr->txn, row),
+                               mr->mutations);
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_mutate(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    const struct json *mutations_json;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_mutation_set mutations = OVSDB_MUTATION_SET_INITIALIZER;
+    struct ovsdb_row *row = NULL;
+    struct mutate_row_cbdata mr;
+    struct ovsdb_error *error;
+
+    table = parse_table(x, parser, "table");
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    mutations_json = ovsdb_parser_member(parser, "mutations", OP_ARRAY);
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_mutation_set_from_json(table->schema, mutations_json,
+                                             x->symtab, &mutations);
+    }
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        mr.n_matches = 0;
+        mr.txn = x->txn;
+        mr.mutations = &mutations;
+        ovsdb_query(table, &condition, mutate_row_cb, &mr);
+        json_object_put(result, "count", json_integer_create(mr.n_matches));
+    }
+
+    ovsdb_row_destroy(row);
+    ovsdb_mutation_set_destroy(&mutations);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct delete_row_cbdata {
+    size_t n_matches;
+    const struct ovsdb_table *table;
+    struct ovsdb_txn *txn;
+};
+
+static bool
+delete_row_cb(const struct ovsdb_row *row, void *dr_)
+{
+    struct delete_row_cbdata *dr = dr_;
+
+    dr->n_matches++;
+    ovsdb_txn_row_delete(dr->txn, row);
+
+    return true;
+}
+
+struct ovsdb_error *
+ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result)
+{
+    struct ovsdb_table *table;
+    const struct json *where;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_error *error;
+
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    table = parse_table(x, parser, "table");
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        struct delete_row_cbdata dr;
+
+        dr.n_matches = 0;
+        dr.table = table;
+        dr.txn = x->txn;
+        ovsdb_query(table, &condition, delete_row_cb, &dr);
+
+        json_object_put(result, "count", json_integer_create(dr.n_matches));
+    }
+
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+struct wait_auxdata {
+    struct ovsdb_row_hash *actual;
+    struct ovsdb_row_hash *expected;
+    bool *equal;
+};
+
+static bool
+ovsdb_execute_wait_query_cb(const struct ovsdb_row *row, void *aux_)
+{
+    struct wait_auxdata *aux = aux_;
+
+    if (ovsdb_row_hash_contains(aux->expected, row)) {
+        ovsdb_row_hash_insert(aux->actual, row);
+        return true;
+    } else {
+        /* The query row isn't in the expected result set, so the actual and
+         * expected results sets definitely differ and we can short-circuit the
+         * rest of the query. */
+        *aux->equal = false;
+        return false;
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_execute_wait(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                   struct json *result UNUSED)
+{
+    struct ovsdb_table *table;
+    const struct json *timeout, *where, *columns_json, *until, *rows;
+    struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER;
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_row_hash expected = OVSDB_ROW_HASH_INITIALIZER(expected);
+    struct ovsdb_row_hash actual = OVSDB_ROW_HASH_INITIALIZER(actual);
+    struct ovsdb_error *error;
+    struct wait_auxdata aux;
+    long long int timeout_msec = 0;
+    size_t i;
+
+    timeout = ovsdb_parser_member(parser, "timeout", OP_NUMBER | OP_OPTIONAL);
+    where = ovsdb_parser_member(parser, "where", OP_ARRAY);
+    columns_json = ovsdb_parser_member(parser, "columns",
+                                       OP_ARRAY | OP_OPTIONAL);
+    until = ovsdb_parser_member(parser, "until", OP_STRING);
+    rows = ovsdb_parser_member(parser, "rows", OP_ARRAY);
+    table = parse_table(x, parser, "table");
+    error = ovsdb_parser_get_error(parser);
+    if (!error) {
+        error = ovsdb_condition_from_json(table->schema, where, x->symtab,
+                                          &condition);
+    }
+    if (!error) {
+        error = ovsdb_column_set_from_json(columns_json, table, &columns);
+    }
+    if (!error) {
+        if (timeout) {
+            timeout_msec = MIN(LLONG_MAX, json_real(timeout));
+            if (timeout_msec < 0) {
+                error = ovsdb_syntax_error(timeout, NULL,
+                                           "timeout must be nonnegative");
+            } else if (timeout_msec < x->timeout_msec) {
+                x->timeout_msec = timeout_msec;
+            }
+        } else {
+            timeout_msec = LLONG_MAX;
+        }
+        if (strcmp(json_string(until), "==")
+            && strcmp(json_string(until), "!=")) {
+            error = ovsdb_syntax_error(until, NULL,
+                                       "\"until\" must be \"==\" or \"!=\"");
+        }
+    }
+    if (!error) {
+        /* Parse "rows" into 'expected'. */
+        ovsdb_row_hash_init(&expected, &columns);
+        for (i = 0; i < rows->u.array.n; i++) {
+            struct ovsdb_error *error;
+            struct ovsdb_row *row;
+
+            row = ovsdb_row_create(table);
+            error = ovsdb_row_from_json(row, rows->u.array.elems[i], x->symtab,
+                                        NULL);
+            if (error) {
+                break;
+            }
+
+            if (!ovsdb_row_hash_insert(&expected, row)) {
+                /* XXX Perhaps we should abort with an error or log a
+                 * warning. */
+                ovsdb_row_destroy(row);
+            }
+        }
+    }
+    if (!error) {
+        /* Execute query. */
+        bool equal = true;
+        ovsdb_row_hash_init(&actual, &columns);
+        aux.actual = &actual;
+        aux.expected = &expected;
+        aux.equal = &equal;
+        ovsdb_query(table, &condition, ovsdb_execute_wait_query_cb, &aux);
+        if (equal) {
+            /* We know that every row in 'actual' is also in 'expected'.  We
+             * also know that all of the rows in 'actual' are distinct and that
+             * all of the rows in 'expected' are distinct.  Therefore, if
+             * 'actual' and 'expected' have the same number of rows, then they
+             * have the same content. */
+            size_t n_actual = ovsdb_row_hash_count(&actual);
+            size_t n_expected = ovsdb_row_hash_count(&expected);
+            equal = n_actual == n_expected;
+        }
+        if (!strcmp(json_string(until), "==") != equal) {
+            if (timeout && x->elapsed_msec >= timeout_msec) {
+                if (x->elapsed_msec) {
+                    error = ovsdb_error("timed out",
+                                        "\"wait\" timed out after %lld ms",
+                                        x->elapsed_msec);
+                } else {
+                    error = ovsdb_error("timed out", "\"wait\" timed out");
+                }
+            } else {
+                /* ovsdb_execute() will change this, if triggers really are
+                 * supported. */
+                error = ovsdb_error("not supported", "triggers not supported");
+            }
+        }
+    }
+
+
+    ovsdb_row_hash_destroy(&expected, true);
+    ovsdb_row_hash_destroy(&actual, false);
+    ovsdb_column_set_destroy(&columns);
+    ovsdb_condition_destroy(&condition);
+
+    return error;
+}
+
+static struct ovsdb_error *
+ovsdb_execute_declare(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                      struct json *result)
+{
+    const struct json *uuid_name;
+    struct uuid uuid;
+
+    uuid_name = ovsdb_parser_member(parser, "uuid-name", OP_ID);
+    if (!uuid_name) {
+        return NULL;
+    }
+
+    if (ovsdb_symbol_table_get(x->symtab, json_string(uuid_name))) {
+        return ovsdb_syntax_error(uuid_name, "duplicate uuid-name",
+                                  "This \"uuid-name\" appeared on an "
+                                  "earlier \"declare\" or \"insert\" "
+                                  "operation.");
+    }
+
+    uuid_generate(&uuid);
+    ovsdb_symbol_table_put(x->symtab, json_string(uuid_name), &uuid, false);
+    json_object_put(result, "uuid", json_string_create_nocopy(
+                        xasprintf(UUID_FMT, UUID_ARGS(&uuid))));
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_execute_comment(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                      struct json *result UNUSED)
+{
+    const struct json *comment;
+
+    comment = ovsdb_parser_member(parser, "comment", OP_STRING);
+    if (!comment) {
+        return NULL;
+    }
+    ovsdb_txn_add_comment(x->txn, json_string(comment));
+
+    return NULL;
+}
diff --git a/ovsdb/file.c b/ovsdb/file.c
new file mode 100644 (file)
index 0000000..716ea89
--- /dev/null
@@ -0,0 +1,357 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "file.h"
+
+#include <assert.h>
+#include <fcntl.h>
+
+#include "column.h"
+#include "log.h"
+#include "json.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "row.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+#include "uuid.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_file
+#include "vlog.h"
+
+static struct ovsdb_error *ovsdb_file_txn_from_json(struct ovsdb *,
+                                                    const struct json *,
+                                                    struct ovsdb_txn **);
+static void ovsdb_file_replica_create(struct ovsdb *, struct ovsdb_log *);
+
+struct ovsdb_error *
+ovsdb_file_open(const char *file_name, bool read_only, struct ovsdb **dbp)
+{
+    struct ovsdb_schema *schema;
+    struct ovsdb_error *error;
+    struct ovsdb_log *log;
+    struct json *json;
+    struct ovsdb *db;
+
+    error = ovsdb_log_open(file_name, read_only ? O_RDONLY : O_RDWR, &log);
+    if (error) {
+        return error;
+    }
+
+    error = ovsdb_log_read(log, &json);
+    if (error) {
+        return error;
+    } else if (!json) {
+        return ovsdb_io_error(EOF, "%s: database file contains no schema",
+                              file_name);
+    }
+
+    error = ovsdb_schema_from_json(json, &schema);
+    if (error) {
+        json_destroy(json);
+        return ovsdb_wrap_error(error,
+                                "failed to parse \"%s\" as ovsdb schema",
+                                file_name);
+    }
+    json_destroy(json);
+
+    db = ovsdb_create(schema);
+    while ((error = ovsdb_log_read(log, &json)) == NULL && json) {
+        struct ovsdb_txn *txn;
+
+        error = ovsdb_file_txn_from_json(db, json, &txn);
+        json_destroy(json);
+        if (error) {
+            break;
+        }
+
+        ovsdb_txn_commit(txn, false);
+    }
+    if (error) {
+        char *msg = ovsdb_error_to_string(error);
+        VLOG_WARN("%s", msg);
+        free(msg);
+
+        ovsdb_error_destroy(error);
+    }
+
+    if (!read_only) {
+        ovsdb_file_replica_create(db, log);
+    } else {
+        ovsdb_log_close(log);
+    }
+
+    *dbp = db;
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_file_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
+                             const struct uuid *row_uuid, struct json *json)
+{
+    const struct ovsdb_row *row = ovsdb_table_get_row(table, row_uuid);
+    if (json->type == JSON_NULL) {
+        if (!row) {
+            return ovsdb_syntax_error(NULL, NULL, "transaction deletes "
+                                      "row "UUID_FMT" that does not exist",
+                                      UUID_ARGS(row_uuid));
+        }
+        ovsdb_txn_row_delete(txn, row);
+        return NULL;
+    } else if (row) {
+        return ovsdb_row_from_json(ovsdb_txn_row_modify(txn, row),
+                                   json, NULL, NULL);
+    } else {
+        struct ovsdb_error *error;
+        struct ovsdb_row *new;
+
+        new = ovsdb_row_create(table);
+        *ovsdb_row_get_uuid_rw(new) = *row_uuid;
+        error = ovsdb_row_from_json(new, json, NULL, NULL);
+        if (error) {
+            ovsdb_row_destroy(new);
+        }
+
+        ovsdb_txn_row_insert(txn, new);
+
+        return error;
+    }
+}
+
+static struct ovsdb_error *
+ovsdb_file_txn_table_from_json(struct ovsdb_txn *txn,
+                               struct ovsdb_table *table, struct json *json)
+{
+    struct shash_node *node;
+
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "object expected");
+    }
+
+    SHASH_FOR_EACH (node, json->u.object) {
+        const char *uuid_string = node->name;
+        struct json *txn_row_json = node->data;
+        struct ovsdb_error *error;
+        struct uuid row_uuid;
+
+        if (!uuid_from_string(&row_uuid, uuid_string)) {
+            return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
+                                      uuid_string);
+        }
+
+        error = ovsdb_file_txn_row_from_json(txn, table, &row_uuid,
+                                             txn_row_json);
+        if (error) {
+            return error;
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_file_txn_from_json(struct ovsdb *db, const struct json *json,
+                         struct ovsdb_txn **txnp)
+{
+    struct ovsdb_error *error;
+    struct shash_node *node;
+    struct ovsdb_txn *txn;
+
+    *txnp = NULL;
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "object expected");
+    }
+
+    txn = ovsdb_txn_create(db);
+    SHASH_FOR_EACH (node, json->u.object) {
+        const char *table_name = node->name;
+        struct json *txn_table_json = node->data;
+        struct ovsdb_table *table;
+
+        table = shash_find_data(&db->tables, table_name);
+        if (!table) {
+            if (!strcmp(table_name, "_date")
+                || !strcmp(table_name, "_comment")) {
+                continue;
+            }
+
+            error = ovsdb_syntax_error(json, "unknown table",
+                                       "No table named %s.", table_name);
+            goto error;
+        }
+
+        error = ovsdb_file_txn_table_from_json(txn, table, txn_table_json);
+        if (error) {
+            goto error;
+        }
+    }
+    *txnp = txn;
+    return NULL;
+
+error:
+    ovsdb_txn_abort(txn);
+    return error;
+}
+\f
+/* Replica implementation. */
+
+struct ovsdb_file_replica {
+    struct ovsdb_replica replica;
+    struct ovsdb_log *log;
+};
+
+static const struct ovsdb_replica_class ovsdb_file_replica_class;
+
+static void
+ovsdb_file_replica_create(struct ovsdb *db, struct ovsdb_log *log)
+{
+    struct ovsdb_file_replica *r = xmalloc(sizeof *r);
+    ovsdb_replica_init(&r->replica, &ovsdb_file_replica_class);
+    r->log = log;
+    ovsdb_add_replica(db, &r->replica);
+
+}
+
+static struct ovsdb_file_replica *
+ovsdb_file_replica_cast(struct ovsdb_replica *replica)
+{
+    assert(replica->class == &ovsdb_file_replica_class);
+    return CONTAINER_OF(replica, struct ovsdb_file_replica, replica);
+}
+
+struct ovsdb_file_replica_aux {
+    struct json *json;          /* JSON for the whole transaction. */
+    struct json *table_json;    /* JSON for 'table''s transaction. */
+    struct ovsdb_table *table;  /* Table described in 'table_json'.  */
+};
+
+static bool
+ovsdb_file_replica_change_cb(const struct ovsdb_row *old,
+                             const struct ovsdb_row *new,
+                             void *aux_)
+{
+    struct ovsdb_file_replica_aux *aux = aux_;
+    struct json *row;
+
+    if (!new) {
+        row = json_null_create();
+    } else {
+        struct shash_node *node;
+
+        row = NULL;
+        SHASH_FOR_EACH (node, &new->table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            const struct ovsdb_type *type = &column->type;
+            unsigned int idx = column->index;
+
+            if (idx != OVSDB_COL_UUID && column->persistent
+                && (old
+                    ? !ovsdb_datum_equals(&old->fields[idx], &new->fields[idx],
+                                          type)
+                    : !ovsdb_datum_is_default(&new->fields[idx], type)))
+            {
+                if (!row) {
+                    row = json_object_create();
+                }
+                json_object_put(row, column->name,
+                                ovsdb_datum_to_json(&new->fields[idx], type));
+            }
+        }
+    }
+
+    if (row) {
+        struct ovsdb_table *table = new ? new->table : old->table;
+        char uuid[UUID_LEN + 1];
+
+        if (table != aux->table) {
+            /* Create JSON object for transaction overall. */
+            if (!aux->json) {
+                aux->json = json_object_create();
+            }
+
+            /* Create JSON object for transaction on this table. */
+            aux->table_json = json_object_create();
+            aux->table = table;
+            json_object_put(aux->json, table->schema->name, aux->table_json);
+        }
+
+        /* Add row to transaction for this table. */
+        snprintf(uuid, sizeof uuid,
+                 UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old)));
+        json_object_put(aux->table_json, uuid, row);
+    }
+
+    return true;
+}
+
+static struct ovsdb_error *
+ovsdb_file_replica_commit(struct ovsdb_replica *r_,
+                          const struct ovsdb_txn *txn, bool durable)
+{
+    struct ovsdb_file_replica *r = ovsdb_file_replica_cast(r_);
+    struct ovsdb_file_replica_aux aux;
+    struct ovsdb_error *error;
+    const char *comment;
+
+    aux.json = NULL;
+    aux.table_json = NULL;
+    aux.table = NULL;
+    ovsdb_txn_for_each_change(txn, ovsdb_file_replica_change_cb, &aux);
+
+    if (!aux.json) {
+        /* Nothing to commit. */
+        return NULL;
+    }
+
+    comment = ovsdb_txn_get_comment(txn);
+    if (comment) {
+        json_object_put_string(aux.json, "_comment", comment);
+    }
+
+    json_object_put(aux.json, "_date", json_integer_create(time_now()));
+
+    error = ovsdb_log_write(r->log, aux.json);
+    json_destroy(aux.json);
+    if (error) {
+        return ovsdb_wrap_error(error, "writing transaction failed");
+    }
+
+    if (durable) {
+        error = ovsdb_log_commit(r->log);
+        if (error) {
+            return ovsdb_wrap_error(error, "committing transaction failed");
+        }
+    }
+
+    return NULL;
+}
+
+static void
+ovsdb_file_replica_destroy(struct ovsdb_replica *r_)
+{
+    struct ovsdb_file_replica *r = ovsdb_file_replica_cast(r_);
+
+    ovsdb_log_close(r->log);
+    free(r);
+}
+
+static const struct ovsdb_replica_class ovsdb_file_replica_class = {
+    ovsdb_file_replica_commit,
+    ovsdb_file_replica_destroy
+};
similarity index 68%
rename from vswitchd/mgmt.h
rename to ovsdb/file.h
index f05c916..2a27477 100644 (file)
  * limitations under the License.
  */
 
-#ifndef VSWITCHD_MGMT_H
-#define VSWITCHD_MGMT_H 1
+#ifndef OVSDB_FILE_H
+#define OVSDB_FILE_H 1
 
-void mgmt_init(void);
-void mgmt_reconfigure(void);
-bool mgmt_run(void);
-void mgmt_wait(void);
-uint64_t mgmt_get_mgmt_id(void);
+#include <stdbool.h>
+#include "compiler.h"
 
-#endif /* mgmt.h */
+struct ovsdb;
+
+struct ovsdb_error *ovsdb_file_open(const char *file_name, bool read_only,
+                                    struct ovsdb **)
+    WARN_UNUSED_RESULT;
+
+#endif /* ovsdb/file.h */
diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
new file mode 100644 (file)
index 0000000..936dd1d
--- /dev/null
@@ -0,0 +1,924 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc-server.h"
+
+#include <assert.h>
+#include <errno.h>
+
+#include "column.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb.h"
+#include "reconnect.h"
+#include "row.h"
+#include "stream.h"
+#include "table.h"
+#include "timeval.h"
+#include "transaction.h"
+#include "trigger.h"
+
+#define THIS_MODULE VLM_ovsdb_jsonrpc_server
+#include "vlog.h"
+
+struct ovsdb_jsonrpc_remote;
+struct ovsdb_jsonrpc_session;
+
+/* Message rate-limiting. */
+struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+/* Sessions. */
+static struct ovsdb_jsonrpc_session *ovsdb_jsonrpc_session_create(
+    struct ovsdb_jsonrpc_remote *, struct jsonrpc_session *);
+static void ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *);
+static void ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *);
+static void ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *);
+
+/* Triggers. */
+static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *,
+                                         struct json *id, struct json *params);
+static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find(
+    struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash);
+static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *);
+static void ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_trigger_complete_done(
+    struct ovsdb_jsonrpc_session *);
+
+/* Monitors. */
+static struct json *ovsdb_jsonrpc_monitor_create(
+    struct ovsdb_jsonrpc_session *, struct json *params);
+static struct jsonrpc_msg *ovsdb_jsonrpc_monitor_cancel(
+    struct ovsdb_jsonrpc_session *,
+    struct json_array *params,
+    const struct json *request_id);
+static void ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *);
+\f
+/* JSON-RPC database server. */
+
+struct ovsdb_jsonrpc_server {
+    struct ovsdb *db;
+    unsigned int n_sessions, max_sessions;
+    struct shash remotes;      /* Contains "struct ovsdb_jsonrpc_remote *"s. */
+};
+
+/* A configured remote.  This is either a passive stream listener plus a list
+ * of the currently connected sessions, or a list of exactly one active
+ * session. */
+struct ovsdb_jsonrpc_remote {
+    struct ovsdb_jsonrpc_server *server;
+    struct pstream *listener;   /* Listener, if passive. */
+    struct list sessions;       /* List of "struct ovsdb_jsonrpc_session"s. */
+};
+
+static void ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *,
+                                            const char *name);
+static void ovsdb_jsonrpc_server_del_remote(struct shash_node *);
+
+struct ovsdb_jsonrpc_server *
+ovsdb_jsonrpc_server_create(struct ovsdb *db)
+{
+    struct ovsdb_jsonrpc_server *server = xzalloc(sizeof *server);
+    server->db = db;
+    server->max_sessions = 64;
+    shash_init(&server->remotes);
+    return server;
+}
+
+void
+ovsdb_jsonrpc_server_destroy(struct ovsdb_jsonrpc_server *svr)
+{
+    struct shash_node *node, *next;
+
+    SHASH_FOR_EACH_SAFE (node, next, &svr->remotes) {
+        ovsdb_jsonrpc_server_del_remote(node);
+    }
+    shash_destroy(&svr->remotes);
+    free(svr);
+}
+
+/* Sets 'svr''s current set of remotes to the names in 'new_remotes'.  The data
+ * values in 'new_remotes' are ignored.
+ *
+ * A remote is an active or passive stream connection method, e.g. "pssl:" or
+ * "tcp:1.2.3.4". */
+void
+ovsdb_jsonrpc_server_set_remotes(struct ovsdb_jsonrpc_server *svr,
+                                 const struct shash *new_remotes)
+{
+    struct shash_node *node, *next;
+
+    SHASH_FOR_EACH_SAFE (node, next, &svr->remotes) {
+        if (!shash_find(new_remotes, node->name)) {
+            ovsdb_jsonrpc_server_del_remote(node);
+        }
+    }
+    SHASH_FOR_EACH (node, new_remotes) {
+        if (!shash_find(&svr->remotes, node->name)) {
+            ovsdb_jsonrpc_server_add_remote(svr, node->name);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *svr,
+                                const char *name)
+{
+    struct ovsdb_jsonrpc_remote *remote;
+    struct pstream *listener;
+    int error;
+
+    error = pstream_open(name, &listener);
+    if (error && error != EAFNOSUPPORT) {
+        VLOG_ERR_RL(&rl, "%s: listen failed: %s", name, strerror(error));
+        return;
+    }
+
+    remote = xmalloc(sizeof *remote);
+    remote->server = svr;
+    remote->listener = listener;
+    list_init(&remote->sessions);
+    shash_add(&svr->remotes, name, remote);
+
+    if (!listener) {
+        ovsdb_jsonrpc_session_create(remote, jsonrpc_session_open(name));
+    }
+}
+
+static void
+ovsdb_jsonrpc_server_del_remote(struct shash_node *node)
+{
+    struct ovsdb_jsonrpc_remote *remote = node->data;
+
+    ovsdb_jsonrpc_session_close_all(remote);
+    pstream_close(remote->listener);
+    shash_delete(&remote->server->remotes, node);
+    free(remote);
+}
+
+void
+ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &svr->remotes) {
+        struct ovsdb_jsonrpc_remote *remote = node->data;
+
+        if (remote->listener && svr->n_sessions < svr->max_sessions) {
+            struct stream *stream;
+            int error;
+
+            error = pstream_accept(remote->listener, &stream);
+            if (!error) {
+                struct jsonrpc_session *js;
+                js = jsonrpc_session_open_unreliably(jsonrpc_open(stream));
+                ovsdb_jsonrpc_session_create(remote, js);
+            } else if (error != EAGAIN) {
+                VLOG_WARN_RL(&rl, "%s: accept failed: %s",
+                             pstream_get_name(remote->listener),
+                             strerror(error));
+            }
+        }
+
+        ovsdb_jsonrpc_session_run_all(remote);
+    }
+}
+
+void
+ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *svr)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &svr->remotes) {
+        struct ovsdb_jsonrpc_remote *remote = node->data;
+
+        if (remote->listener && svr->n_sessions < svr->max_sessions) {
+            pstream_wait(remote->listener);
+        }
+
+        ovsdb_jsonrpc_session_wait_all(remote);
+    }
+}
+\f
+/* JSON-RPC database server session. */
+
+struct ovsdb_jsonrpc_session {
+    struct ovsdb_jsonrpc_remote *remote;
+    struct list node;           /* Element in remote's sessions list. */
+
+    /* Triggers. */
+    struct hmap triggers;       /* Hmap of "struct ovsdb_jsonrpc_trigger"s. */
+    struct list completions;    /* Completed triggers. */
+
+    /* Monitors. */
+    struct hmap monitors;       /* Hmap of "struct ovsdb_jsonrpc_monitor"s. */
+
+    /* Network connectivity. */
+    struct jsonrpc_session *js;  /* JSON-RPC session. */
+    unsigned int js_seqno;       /* Last jsonrpc_session_get_seqno() value. */
+};
+
+static void ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *);
+static int ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *,
+                                             struct jsonrpc_msg *);
+static void ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *,
+                                             struct jsonrpc_msg *);
+
+static struct ovsdb_jsonrpc_session *
+ovsdb_jsonrpc_session_create(struct ovsdb_jsonrpc_remote *remote,
+                             struct jsonrpc_session *js)
+{
+    struct ovsdb_jsonrpc_session *s;
+
+    s = xzalloc(sizeof *s);
+    s->remote = remote;
+    list_push_back(&remote->sessions, &s->node);
+    hmap_init(&s->triggers);
+    hmap_init(&s->monitors);
+    list_init(&s->completions);
+    s->js = js;
+    s->js_seqno = jsonrpc_session_get_seqno(js);
+
+    remote->server->n_sessions++;
+
+    return s;
+}
+
+static void
+ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s)
+{
+    ovsdb_jsonrpc_monitor_remove_all(s);
+    jsonrpc_session_close(s->js);
+    list_remove(&s->node);
+    s->remote->server->n_sessions--;
+    free(s);
+}
+
+static int
+ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *s)
+{
+    jsonrpc_session_run(s->js);
+    if (s->js_seqno != jsonrpc_session_get_seqno(s->js)) {
+        s->js_seqno = jsonrpc_session_get_seqno(s->js);
+        ovsdb_jsonrpc_trigger_complete_all(s);
+        ovsdb_jsonrpc_monitor_remove_all(s);
+    }
+
+    ovsdb_jsonrpc_trigger_complete_done(s);
+
+    if (!jsonrpc_session_get_backlog(s->js)) {
+        struct jsonrpc_msg *msg = jsonrpc_session_recv(s->js);
+        if (msg) {
+            if (msg->type == JSONRPC_REQUEST) {
+                ovsdb_jsonrpc_session_got_request(s, msg);
+            } else if (msg->type == JSONRPC_NOTIFY) {
+                ovsdb_jsonrpc_session_got_notify(s, msg);
+            } else {
+                VLOG_WARN("%s: received unexpected %s message",
+                          jsonrpc_session_get_name(s->js),
+                          jsonrpc_msg_type_to_string(msg->type));
+                jsonrpc_session_force_reconnect(s->js);
+                jsonrpc_msg_destroy(msg);
+            }
+        }
+    }
+    return jsonrpc_session_is_alive(s->js) ? 0 : ETIMEDOUT;
+}
+
+static void
+ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *remote)
+{
+    struct ovsdb_jsonrpc_session *s, *next;
+
+    LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node,
+                        &remote->sessions) {
+        int error = ovsdb_jsonrpc_session_run(s);
+        if (error) {
+            ovsdb_jsonrpc_session_close(s);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *s)
+{
+    jsonrpc_session_wait(s->js);
+    if (!jsonrpc_session_get_backlog(s->js)) {
+        jsonrpc_session_recv_wait(s->js);
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *remote)
+{
+    struct ovsdb_jsonrpc_session *s;
+
+    LIST_FOR_EACH (s, struct ovsdb_jsonrpc_session, node, &remote->sessions) {
+        ovsdb_jsonrpc_session_wait(s);
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *remote)
+{
+    struct ovsdb_jsonrpc_session *s, *next;
+
+    LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node,
+                        &remote->sessions) {
+        ovsdb_jsonrpc_session_close(s);
+    }
+}
+
+static struct jsonrpc_msg *
+execute_transaction(struct ovsdb_jsonrpc_session *s,
+                    struct jsonrpc_msg *request)
+{
+    ovsdb_jsonrpc_trigger_create(s, request->id, request->params);
+    request->id = NULL;
+    request->params = NULL;
+    jsonrpc_msg_destroy(request);
+    return NULL;
+}
+
+static void
+ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s,
+                                  struct jsonrpc_msg *request)
+{
+    struct jsonrpc_msg *reply;
+
+    if (!strcmp(request->method, "transact")) {
+        reply = execute_transaction(s, request);
+    } else if (!strcmp(request->method, "monitor")) {
+        reply = jsonrpc_create_reply(
+            ovsdb_jsonrpc_monitor_create(s, request->params), request->id);
+    } else if (!strcmp(request->method, "monitor_cancel")) {
+        reply = ovsdb_jsonrpc_monitor_cancel(s, json_array(request->params),
+                                             request->id);
+    } else if (!strcmp(request->method, "get_schema")) {
+        reply = jsonrpc_create_reply(
+            ovsdb_schema_to_json(s->remote->server->db->schema), request->id);
+    } else if (!strcmp(request->method, "echo")) {
+        reply = jsonrpc_create_reply(json_clone(request->params), request->id);
+    } else {
+        reply = jsonrpc_create_error(json_string_create("unknown method"),
+                                     request->id);
+    }
+
+    if (reply) {
+        jsonrpc_msg_destroy(request);
+        jsonrpc_session_send(s->js, reply);
+    }
+}
+
+static void
+execute_cancel(struct ovsdb_jsonrpc_session *s, struct jsonrpc_msg *request)
+{
+    if (json_array(request->params)->n == 1) {
+        struct ovsdb_jsonrpc_trigger *t;
+        struct json *id;
+
+        id = request->params->u.array.elems[0];
+        t = ovsdb_jsonrpc_trigger_find(s, id, json_hash(id, 0));
+        if (t) {
+            ovsdb_jsonrpc_trigger_complete(t);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *s,
+                                 struct jsonrpc_msg *request)
+{
+    if (!strcmp(request->method, "cancel")) {
+        execute_cancel(s, request);
+    }
+    jsonrpc_msg_destroy(request);
+}
+\f
+/* JSON-RPC database server triggers.
+ *
+ * (Every transaction is treated as a trigger even if it doesn't actually have
+ * any "wait" operations.) */
+
+struct ovsdb_jsonrpc_trigger {
+    struct ovsdb_trigger trigger;
+    struct ovsdb_jsonrpc_session *session;
+    struct hmap_node hmap_node; /* In session's "triggers" hmap. */
+    struct json *id;
+};
+
+static void
+ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *s,
+                             struct json *id, struct json *params)
+{
+    struct ovsdb_jsonrpc_trigger *t;
+    size_t hash;
+
+    /* Check for duplicate ID. */
+    hash = json_hash(id, 0);
+    t = ovsdb_jsonrpc_trigger_find(s, id, hash);
+    if (t) {
+        struct jsonrpc_msg *msg;
+
+        msg = jsonrpc_create_error(json_string_create("duplicate request ID"),
+                                   id);
+        jsonrpc_session_send(s->js, msg);
+        json_destroy(id);
+        json_destroy(params);
+        return;
+    }
+
+    /* Insert into trigger table. */
+    t = xmalloc(sizeof *t);
+    ovsdb_trigger_init(s->remote->server->db,
+                       &t->trigger, params, &s->completions,
+                       time_msec());
+    t->session = s;
+    t->id = id;
+    hmap_insert(&s->triggers, &t->hmap_node, hash);
+
+    /* Complete early if possible. */
+    if (ovsdb_trigger_is_complete(&t->trigger)) {
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+}
+
+static struct ovsdb_jsonrpc_trigger *
+ovsdb_jsonrpc_trigger_find(struct ovsdb_jsonrpc_session *s,
+                           const struct json *id, size_t hash)
+{
+    struct ovsdb_jsonrpc_trigger *t;
+
+    HMAP_FOR_EACH_WITH_HASH (t, struct ovsdb_jsonrpc_trigger, hmap_node, hash,
+                             &s->triggers) {
+        if (json_equal(t->id, id)) {
+            return t;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t)
+{
+    struct ovsdb_jsonrpc_session *s = t->session;
+
+    if (jsonrpc_session_is_connected(s->js)) {
+        struct jsonrpc_msg *reply;
+        struct json *result;
+
+        result = ovsdb_trigger_steal_result(&t->trigger);
+        if (result) {
+            reply = jsonrpc_create_reply(result, t->id);
+        } else {
+            reply = jsonrpc_create_error(json_string_create("canceled"),
+                                         t->id);
+        }
+        jsonrpc_session_send(s->js, reply);
+    }
+
+    json_destroy(t->id);
+    ovsdb_trigger_destroy(&t->trigger);
+    hmap_remove(&s->triggers, &t->hmap_node);
+    free(t);
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *s)
+{
+    struct ovsdb_jsonrpc_trigger *t, *next;
+    HMAP_FOR_EACH_SAFE (t, next, struct ovsdb_jsonrpc_trigger, hmap_node,
+                        &s->triggers) {
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+}
+
+static void
+ovsdb_jsonrpc_trigger_complete_done(struct ovsdb_jsonrpc_session *s)
+{
+    while (!list_is_empty(&s->completions)) {
+        struct ovsdb_jsonrpc_trigger *t
+            = CONTAINER_OF(s->completions.next,
+                           struct ovsdb_jsonrpc_trigger, trigger.node);
+        ovsdb_jsonrpc_trigger_complete(t);
+    }
+}
+\f
+/* JSON-RPC database table monitors. */
+
+enum ovsdb_jsonrpc_monitor_selection {
+    OJMS_INITIAL = 1 << 0,      /* All rows when monitor is created. */
+    OJMS_INSERT = 1 << 1,       /* New rows. */
+    OJMS_DELETE = 1 << 2,       /* Deleted rows. */
+    OJMS_MODIFY = 1 << 3        /* Modified rows. */
+};
+
+struct ovsdb_jsonrpc_monitor_table {
+    const struct ovsdb_table *table;
+    enum ovsdb_jsonrpc_monitor_selection select;
+    struct ovsdb_column_set columns;
+};
+
+struct ovsdb_jsonrpc_monitor {
+    struct ovsdb_replica replica;
+    struct ovsdb_jsonrpc_session *session;
+    struct hmap_node node;      /* In ovsdb_jsonrpc_session's "monitors". */
+
+    struct json *monitor_id;
+    struct shash tables;     /* Holds "struct ovsdb_jsonrpc_monitor_table"s. */
+};
+
+static const struct ovsdb_replica_class ovsdb_jsonrpc_replica_class;
+
+struct ovsdb_jsonrpc_monitor *ovsdb_jsonrpc_monitor_find(
+    struct ovsdb_jsonrpc_session *, const struct json *monitor_id);
+static void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *);
+static struct json *ovsdb_jsonrpc_monitor_get_initial(
+    const struct ovsdb_jsonrpc_monitor *);
+
+static bool
+parse_bool(struct ovsdb_parser *parser, const char *name, bool default_value)
+{
+    const struct json *json;
+
+    json = ovsdb_parser_member(parser, name, OP_BOOLEAN | OP_OPTIONAL);
+    return json ? json_boolean(json) : default_value;
+}
+
+struct ovsdb_jsonrpc_monitor *
+ovsdb_jsonrpc_monitor_find(struct ovsdb_jsonrpc_session *s,
+                           const struct json *monitor_id)
+{
+    struct ovsdb_jsonrpc_monitor *m;
+
+    HMAP_FOR_EACH_WITH_HASH (m, struct ovsdb_jsonrpc_monitor, node,
+                             json_hash(monitor_id, 0), &s->monitors) {
+        if (json_equal(m->monitor_id, monitor_id)) {
+            return m;
+        }
+    }
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s,
+                             struct json *params)
+{
+    struct ovsdb_jsonrpc_monitor *m = NULL;
+    struct json *monitor_id, *monitor_requests;
+    struct ovsdb_error *error = NULL;
+    struct shash_node *node;
+    struct json *json;
+
+    if (json_array(params)->n != 2) {
+        error = ovsdb_syntax_error(params, NULL, "invalid parameters");
+        goto error;
+    }
+    monitor_id = params->u.array.elems[0];
+    monitor_requests = params->u.array.elems[1];
+    if (monitor_requests->type != JSON_OBJECT) {
+        error = ovsdb_syntax_error(monitor_requests, NULL,
+                                   "monitor-requests must be object");
+        goto error;
+    }
+
+    if (ovsdb_jsonrpc_monitor_find(s, monitor_id)) {
+        error = ovsdb_syntax_error(monitor_id, NULL, "duplicate monitor ID");
+        goto error;
+    }
+
+    m = xzalloc(sizeof *m);
+    ovsdb_replica_init(&m->replica, &ovsdb_jsonrpc_replica_class);
+    ovsdb_add_replica(s->remote->server->db, &m->replica);
+    m->session = s;
+    hmap_insert(&s->monitors, &m->node, json_hash(monitor_id, 0));
+    m->monitor_id = json_clone(monitor_id);
+    shash_init(&m->tables);
+
+    SHASH_FOR_EACH (node, json_object(monitor_requests)) {
+        const struct ovsdb_table *table;
+        struct ovsdb_jsonrpc_monitor_table *mt;
+        const struct json *columns_json, *select_json;
+        struct ovsdb_parser parser;
+
+        table = ovsdb_get_table(s->remote->server->db, node->name);
+        if (!table) {
+            error = ovsdb_syntax_error(NULL, NULL,
+                                       "no table named %s", node->name);
+            goto error;
+        }
+
+        mt = xzalloc(sizeof *mt);
+        mt->table = table;
+        mt->select = OJMS_INITIAL | OJMS_INSERT | OJMS_DELETE | OJMS_MODIFY;
+        ovsdb_column_set_init(&mt->columns);
+        shash_add(&m->tables, table->schema->name, mt);
+
+        ovsdb_parser_init(&parser, node->data, "table %s", node->name);
+        columns_json = ovsdb_parser_member(&parser, "columns",
+                                           OP_ARRAY | OP_OPTIONAL);
+        select_json = ovsdb_parser_member(&parser, "select",
+                                          OP_OBJECT | OP_OPTIONAL);
+        error = ovsdb_parser_finish(&parser);
+        if (error) {
+            goto error;
+        }
+
+        if (columns_json) {
+            error = ovsdb_column_set_from_json(columns_json, table,
+                                               &mt->columns);
+            if (error) {
+                goto error;
+            }
+        } else {
+            struct shash_node *node;
+
+            SHASH_FOR_EACH (node, &table->schema->columns) {
+                const struct ovsdb_column *column = node->data;
+                if (column->index != OVSDB_COL_UUID) {
+                    ovsdb_column_set_add(&mt->columns, column);
+                }
+            }
+        }
+
+        if (select_json) {
+            mt->select = 0;
+            ovsdb_parser_init(&parser, select_json, "table %s select",
+                              table->schema->name);
+            if (parse_bool(&parser, "initial", true)) {
+                mt->select |= OJMS_INITIAL;
+            }
+            if (parse_bool(&parser, "insert", true)) {
+                mt->select |= OJMS_INSERT;
+            }
+            if (parse_bool(&parser, "delete", true)) {
+                mt->select |= OJMS_DELETE;
+            }
+            if (parse_bool(&parser, "modify", true)) {
+                mt->select |= OJMS_MODIFY;
+            }
+            error = ovsdb_parser_finish(&parser);
+            if (error) {
+                goto error;
+            }
+        }
+    }
+
+    return ovsdb_jsonrpc_monitor_get_initial(m);
+
+error:
+    if (m) {
+        ovsdb_remove_replica(s->remote->server->db, &m->replica);
+    }
+
+    json = ovsdb_error_to_json(error);
+    ovsdb_error_destroy(error);
+    return json;
+}
+
+static struct jsonrpc_msg *
+ovsdb_jsonrpc_monitor_cancel(struct ovsdb_jsonrpc_session *s,
+                             struct json_array *params,
+                             const struct json *request_id)
+{
+    if (params->n != 1) {
+        return jsonrpc_create_error(json_string_create("invalid parameters"),
+                                    request_id);
+    } else {
+        struct ovsdb_jsonrpc_monitor *m;
+
+        m = ovsdb_jsonrpc_monitor_find(s, params->elems[0]);
+        if (!m) {
+            return jsonrpc_create_error(json_string_create("unknown monitor"),
+                                        request_id);
+        } else {
+            ovsdb_remove_replica(s->remote->server->db, &m->replica);
+            return jsonrpc_create_reply(json_object_create(), request_id);
+        }
+    }
+}
+
+static void
+ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *s)
+{
+    struct ovsdb_jsonrpc_monitor *m, *next;
+
+    HMAP_FOR_EACH_SAFE (m, next,
+                        struct ovsdb_jsonrpc_monitor, node, &s->monitors) {
+        ovsdb_remove_replica(s->remote->server->db, &m->replica);
+    }
+}
+
+static struct ovsdb_jsonrpc_monitor *
+ovsdb_jsonrpc_monitor_cast(struct ovsdb_replica *replica)
+{
+    assert(replica->class == &ovsdb_jsonrpc_replica_class);
+    return CONTAINER_OF(replica, struct ovsdb_jsonrpc_monitor, replica);
+}
+
+struct ovsdb_jsonrpc_monitor_aux {
+    bool initial;               /* Sending initial contents of table? */
+    const struct ovsdb_jsonrpc_monitor *monitor;
+    struct json *json;          /* JSON for the whole transaction. */
+
+    /* Current table.  */
+    struct ovsdb_jsonrpc_monitor_table *mt;
+    struct json *table_json;    /* JSON for table's transaction. */
+};
+
+static bool
+ovsdb_jsonrpc_monitor_change_cb(const struct ovsdb_row *old,
+                                const struct ovsdb_row *new,
+                                void *aux_)
+{
+    struct ovsdb_jsonrpc_monitor_aux *aux = aux_;
+    const struct ovsdb_jsonrpc_monitor *m = aux->monitor;
+    struct ovsdb_table *table = new ? new->table : old->table;
+    enum ovsdb_jsonrpc_monitor_selection type;
+    struct json *old_json, *new_json;
+    struct json *row_json;
+    char uuid[UUID_LEN + 1];
+    int n_changed;
+    size_t i;
+
+    if (!aux->mt || table != aux->mt->table) {
+        aux->mt = shash_find_data(&m->tables, table->schema->name);
+        aux->table_json = NULL;
+        if (!aux->mt) {
+            /* We don't care about rows in this table at all.  Tell the caller
+             * to skip it.  */
+            return false;
+        }
+    }
+
+    type = (aux->initial ? OJMS_INITIAL
+            : !old ? OJMS_INSERT
+            : !new ? OJMS_DELETE
+            : OJMS_MODIFY);
+    if (!(aux->mt->select & type)) {
+        /* We don't care about this type of change (but do want to be called
+         * back for changes to other rows in the same table). */
+        return true;
+    }
+
+    old_json = new_json = NULL;
+    n_changed = 0;
+    for (i = 0; i < aux->mt->columns.n_columns; i++) {
+        const struct ovsdb_column *column = aux->mt->columns.columns[i];
+        unsigned int idx = column->index;
+        bool changed = false;
+
+        if (type == OJMS_MODIFY) {
+            changed = !ovsdb_datum_equals(&old->fields[idx],
+                                          &new->fields[idx], &column->type);
+            n_changed += changed;
+        }
+        if (changed || type == OJMS_DELETE) {
+            if (!old_json) {
+                old_json = json_object_create();
+            }
+            json_object_put(old_json, column->name,
+                            ovsdb_datum_to_json(&old->fields[idx],
+                                                &column->type));
+        }
+        if (type & (OJMS_INITIAL | OJMS_INSERT | OJMS_MODIFY)) {
+            if (!new_json) {
+                new_json = json_object_create();
+            }
+            json_object_put(new_json, column->name,
+                            ovsdb_datum_to_json(&new->fields[idx],
+                                                &column->type));
+        }
+    }
+    if ((type == OJMS_MODIFY && !n_changed) || (!old_json && !new_json)) {
+        /* No reportable changes. */
+        json_destroy(old_json);
+        json_destroy(new_json);
+        return true;
+    }
+
+    /* Create JSON object for transaction overall. */
+    if (!aux->json) {
+        aux->json = json_object_create();
+    }
+
+    /* Create JSON object for transaction on this table. */
+    if (!aux->table_json) {
+        aux->table_json = json_object_create();
+        json_object_put(aux->json, aux->mt->table->schema->name,
+                        aux->table_json);
+    }
+
+    /* Create JSON object for transaction on this row. */
+    row_json = json_object_create();
+    if (old_json) {
+        json_object_put(row_json, "old", old_json);
+    }
+    if (new_json) {
+        json_object_put(row_json, "new", new_json);
+    }
+
+    /* Add JSON row to JSON table. */
+    snprintf(uuid, sizeof uuid,
+             UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old)));
+    json_object_put(aux->table_json, uuid, row_json);
+
+    return true;
+}
+
+static void
+ovsdb_jsonrpc_monitor_init_aux(struct ovsdb_jsonrpc_monitor_aux *aux,
+                               const struct ovsdb_jsonrpc_monitor *m,
+                               bool initial)
+{
+    aux->initial = initial;
+    aux->monitor = m;
+    aux->json = NULL;
+    aux->mt = NULL;
+    aux->table_json = NULL;
+}
+
+static struct ovsdb_error *
+ovsdb_jsonrpc_monitor_commit(struct ovsdb_replica *replica,
+                             const struct ovsdb_txn *txn, bool durable UNUSED)
+{
+    struct ovsdb_jsonrpc_monitor *m = ovsdb_jsonrpc_monitor_cast(replica);
+    struct ovsdb_jsonrpc_monitor_aux aux;
+
+    ovsdb_jsonrpc_monitor_init_aux(&aux, m, false);
+    ovsdb_txn_for_each_change(txn, ovsdb_jsonrpc_monitor_change_cb, &aux);
+    if (aux.json) {
+        struct jsonrpc_msg *msg;
+        struct json *params;
+
+        params = json_array_create_2(json_clone(aux.monitor->monitor_id),
+                                     aux.json);
+        msg = jsonrpc_create_notify("update", params);
+        jsonrpc_session_send(aux.monitor->session->js, msg);
+    }
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_jsonrpc_monitor_get_initial(const struct ovsdb_jsonrpc_monitor *m)
+{
+    struct ovsdb_jsonrpc_monitor_aux aux;
+    struct shash_node *node;
+
+    ovsdb_jsonrpc_monitor_init_aux(&aux, m, true);
+    SHASH_FOR_EACH (node, &m->tables) {
+        struct ovsdb_jsonrpc_monitor_table *mt = node->data;
+
+        if (mt->select & OJMS_INITIAL) {
+            struct ovsdb_row *row;
+
+            HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node,
+                           &mt->table->rows) {
+                ovsdb_jsonrpc_monitor_change_cb(NULL, row, &aux);
+            }
+        }
+    }
+    return aux.json ? aux.json : json_object_create();
+}
+
+static void
+ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *replica)
+{
+    struct ovsdb_jsonrpc_monitor *m = ovsdb_jsonrpc_monitor_cast(replica);
+    struct shash_node *node;
+
+    json_destroy(m->monitor_id);
+    SHASH_FOR_EACH (node, &m->tables) {
+        struct ovsdb_jsonrpc_monitor_table *mt = node->data;
+        ovsdb_column_set_destroy(&mt->columns);
+        free(mt);
+    }
+    shash_destroy(&m->tables);
+    hmap_remove(&m->session->monitors, &m->node);
+    free(m);
+}
+
+static const struct ovsdb_replica_class ovsdb_jsonrpc_replica_class = {
+    ovsdb_jsonrpc_monitor_commit,
+    ovsdb_jsonrpc_monitor_destroy
+};
diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h
new file mode 100644 (file)
index 0000000..6c4acd7
--- /dev/null
@@ -0,0 +1,31 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_JSONRPC_SERVER_H
+#define OVSDB_JSONRPC_SERVER_H 1
+
+struct ovsdb;
+struct shash;
+
+struct ovsdb_jsonrpc_server *ovsdb_jsonrpc_server_create(struct ovsdb *);
+void ovsdb_jsonrpc_server_destroy(struct ovsdb_jsonrpc_server *);
+
+void ovsdb_jsonrpc_server_set_remotes(struct ovsdb_jsonrpc_server *,
+                                      const struct shash *);
+
+void ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *);
+void ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *);
+
+#endif /* ovsdb/jsonrpc-server.h */
diff --git a/ovsdb/log.c b/ovsdb/log.c
new file mode 100644 (file)
index 0000000..c8dac10
--- /dev/null
@@ -0,0 +1,363 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "log.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "json.h"
+#include "lockfile.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "sha1.h"
+#include "transaction.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_log
+#include "vlog.h"
+
+enum ovsdb_log_mode {
+    OVSDB_LOG_READ,
+    OVSDB_LOG_WRITE
+};
+
+struct ovsdb_log {
+    off_t offset;
+    char *name;
+    struct lockfile *lockfile;
+    FILE *stream;
+    struct ovsdb_error *read_error;
+    struct ovsdb_error *write_error;
+    enum ovsdb_log_mode mode;
+};
+
+struct ovsdb_error *
+ovsdb_log_open(const char *name, int flags, struct ovsdb_log **filep)
+{
+    struct lockfile *lockfile;
+    struct ovsdb_error *error;
+    struct ovsdb_log *file;
+    struct stat s;
+    FILE *stream;
+    int accmode;
+    int fd;
+
+    *filep = NULL;
+
+    accmode = flags & O_ACCMODE;
+    if (accmode == O_RDWR || accmode == O_WRONLY) {
+        int retval = lockfile_lock(name, 0, &lockfile);
+        if (retval) {
+            error = ovsdb_io_error(retval, "%s: failed to lock lockfile",
+                                   name);
+            goto error;
+        }
+    } else {
+        lockfile = NULL;
+    }
+
+    fd = open(name, flags, 0666);
+    if (fd < 0) {
+        const char *op = flags & O_CREAT && flags & O_EXCL ? "create" : "open";
+        error = ovsdb_io_error(errno, "%s: %s failed", op, name);
+        goto error_unlock;
+    }
+
+    if (!fstat(fd, &s) && s.st_size == 0) {
+        /* It's (probably) a new file so fsync() its parent directory to ensure
+         * that its directory entry is committed to disk. */
+        char *dir = dir_name(name);
+        int dirfd = open(dir, O_RDONLY);
+        if (dirfd >= 0) {
+            if (fsync(dirfd) && errno != EINVAL) {
+                VLOG_ERR("%s: fsync failed (%s)", dir, strerror(errno));
+            }
+            close(dirfd);
+        } else {
+            VLOG_ERR("%s: open failed (%s)", dir, strerror(errno));
+        }
+        free(dir);
+    }
+
+    stream = fdopen(fd, (accmode == O_RDONLY ? "rb"
+                         : accmode == O_WRONLY ? "wb"
+                         : "w+b"));
+    if (!stream) {
+        error = ovsdb_io_error(errno, "%s: fdopen failed", name);
+        goto error_close;
+    }
+
+    file = xmalloc(sizeof *file);
+    file->name = xstrdup(name);
+    file->lockfile = lockfile;
+    file->stream = stream;
+    file->offset = 0;
+    file->read_error = NULL;
+    file->write_error = NULL;
+    file->mode = OVSDB_LOG_READ;
+    *filep = file;
+    return NULL;
+
+error_close:
+    close(fd);
+error_unlock:
+    lockfile_unlock(lockfile);
+error:
+    return error;
+}
+
+void
+ovsdb_log_close(struct ovsdb_log *file)
+{
+    if (file) {
+        free(file->name);
+        fclose(file->stream);
+        lockfile_unlock(file->lockfile);
+        ovsdb_error_destroy(file->read_error);
+        ovsdb_error_destroy(file->write_error);
+        free(file);
+    }
+}
+
+static const char magic[] = "OVSDB JSON ";
+
+static bool
+parse_header(char *header, unsigned long int *length,
+             uint8_t sha1[SHA1_DIGEST_SIZE])
+{
+    char *p;
+
+    /* 'header' must consist of a magic string... */
+    if (strncmp(header, magic, strlen(magic))) {
+        return false;
+    }
+
+    /* ...followed by a length in bytes... */
+    *length = strtoul(header + strlen(magic), &p, 10);
+    if (!*length || *length == ULONG_MAX || *p != ' ') {
+        return false;
+    }
+    p++;
+
+    /* ...followed by a SHA-1 hash... */
+    if (!sha1_from_hex(sha1, p)) {
+        return false;
+    }
+    p += SHA1_HEX_DIGEST_LEN;
+
+    /* ...and ended by a new-line. */
+    if (*p != '\n') {
+        return false;
+    }
+
+    return true;
+}
+
+struct ovsdb_log_read_cbdata {
+    char input[4096];
+    struct ovsdb_log *file;
+    int error;
+    unsigned long length;
+};
+
+static struct ovsdb_error *
+parse_body(struct ovsdb_log *file, off_t offset, unsigned long int length,
+           uint8_t sha1[SHA1_DIGEST_SIZE], struct json **jsonp)
+{
+    unsigned long int bytes_left;
+    struct json_parser *parser;
+    struct sha1_ctx ctx;
+
+    sha1_init(&ctx);
+    parser = json_parser_create(JSPF_TRAILER);
+
+    bytes_left = length;
+    while (length > 0) {
+        char input[BUFSIZ];
+        int chunk;
+
+        chunk = MIN(length, sizeof input);
+        if (fread(input, 1, chunk, file->stream) != chunk) {
+            json_parser_abort(parser);
+            return ovsdb_io_error(ferror(file->stream) ? errno : EOF,
+                                  "%s: error reading %lu bytes "
+                                  "starting at offset %lld", file->name,
+                                  length, (long long int) offset);
+        }
+        sha1_update(&ctx, input, chunk);
+        json_parser_feed(parser, input, chunk);
+        length -= chunk;
+    }
+
+    sha1_final(&ctx, sha1);
+    *jsonp = json_parser_finish(parser);
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_log_read(struct ovsdb_log *file, struct json **jsonp)
+{
+    uint8_t expected_sha1[SHA1_DIGEST_SIZE];
+    uint8_t actual_sha1[SHA1_DIGEST_SIZE];
+    struct ovsdb_error *error;
+    off_t data_offset;
+    unsigned long data_length;
+    struct json *json;
+    char header[128];
+
+    *jsonp = json = NULL;
+
+    if (file->read_error) {
+        return ovsdb_error_clone(file->read_error);
+    } else if (file->mode == OVSDB_LOG_WRITE) {
+        return OVSDB_BUG("reading file in write mode");
+    }
+
+    if (!fgets(header, sizeof header, file->stream)) {
+        if (feof(file->stream)) {
+            error = NULL;
+        } else {
+            error = ovsdb_io_error(errno, "%s: read failed", file->name);
+        }
+        goto error;
+    }
+
+    if (!parse_header(header, &data_length, expected_sha1)) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: parse error at offset "
+                                   "%lld in header line \"%.*s\"",
+                                   file->name, (long long int) file->offset,
+                                   (int) strcspn(header, "\n"), header);
+        goto error;
+    }
+
+    data_offset = file->offset + strlen(header);
+    error = parse_body(file, data_offset, data_length, actual_sha1, &json);
+    if (error) {
+        goto error;
+    }
+
+    if (memcmp(expected_sha1, actual_sha1, SHA1_DIGEST_SIZE)) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
+                                   "offset %lld have SHA-1 hash "SHA1_FMT" "
+                                   "but should have hash "SHA1_FMT,
+                                   file->name, data_length,
+                                   (long long int) data_offset,
+                                   SHA1_ARGS(actual_sha1),
+                                   SHA1_ARGS(expected_sha1));
+        goto error;
+    }
+
+    if (json->type == JSON_STRING) {
+        error = ovsdb_syntax_error(NULL, NULL, "%s: %lu bytes starting at "
+                                   "offset %lld are not valid JSON (%s)",
+                                   file->name, data_length,
+                                   (long long int) data_offset,
+                                   json->u.string);
+        goto error;
+    }
+
+    file->offset = data_offset + data_length;
+    *jsonp = json;
+    return 0;
+
+error:
+    file->read_error = ovsdb_error_clone(error);
+    json_destroy(json);
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_log_write(struct ovsdb_log *file, struct json *json)
+{
+    uint8_t sha1[SHA1_DIGEST_SIZE];
+    struct ovsdb_error *error;
+    char *json_string;
+    char header[128];
+    size_t length;
+
+    json_string = NULL;
+
+    if (file->write_error) {
+        return ovsdb_error_clone(file->write_error);
+    } else if (file->mode == OVSDB_LOG_READ) {
+        file->mode = OVSDB_LOG_WRITE;
+        if (fseeko(file->stream, file->offset, SEEK_SET)) {
+            error = ovsdb_io_error(errno, "%s: cannot seek to offset %lld",
+                                   file->name, (long long int) file->offset);
+            goto error;
+        }
+        if (ftruncate(fileno(file->stream), file->offset)) {
+            error = ovsdb_io_error(errno, "%s: cannot truncate to length %lld",
+                                   file->name, (long long int) file->offset);
+            goto error;
+        }
+    }
+
+    if (json->type != JSON_OBJECT && json->type != JSON_ARRAY) {
+        error = OVSDB_BUG("bad JSON type");
+        goto error;
+    }
+
+    /* Compose content.  Add a new-line (replacing the null terminator) to make
+     * the file easier to read, even though it has no semantic value.  */
+    json_string = json_to_string(json, 0);
+    length = strlen(json_string) + 1;
+    json_string[length - 1] = '\n';
+
+    /* Compose header. */
+    sha1_bytes(json_string, length, sha1);
+    snprintf(header, sizeof header, "%s%zu "SHA1_FMT"\n",
+             magic, length, SHA1_ARGS(sha1));
+
+    /* Write. */
+    if (fwrite(header, strlen(header), 1, file->stream) != 1
+        || fwrite(json_string, length, 1, file->stream) != 1
+        || fflush(file->stream))
+    {
+        error = ovsdb_io_error(errno, "%s: write failed", file->name);
+
+        /* Remove any partially written data, ignoring errors since there is
+         * nothing further we can do. */
+        ignore(ftruncate(fileno(file->stream), file->offset));
+
+        goto error;
+    }
+
+    file->offset += strlen(header) + length;
+    free(json_string);
+    return 0;
+
+error:
+    file->write_error = ovsdb_error_clone(error);
+    free(json_string);
+    return error;
+}
+
+struct ovsdb_error *
+ovsdb_log_commit(struct ovsdb_log *file)
+{
+    if (fsync(fileno(file->stream))) {
+        return ovsdb_io_error(errno, "%s: fsync failed", file->name);
+    }
+    return 0;
+}
+
diff --git a/ovsdb/log.h b/ovsdb/log.h
new file mode 100644 (file)
index 0000000..2daa635
--- /dev/null
@@ -0,0 +1,36 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_LOG_H
+#define OVSDB_LOG_H 1
+
+#include <sys/types.h>
+#include "compiler.h"
+
+struct json;
+struct ovsdb_log;
+
+struct ovsdb_error *ovsdb_log_open(const char *name, int flags,
+                                   struct ovsdb_log **) WARN_UNUSED_RESULT;
+void ovsdb_log_close(struct ovsdb_log *);
+
+struct ovsdb_error *ovsdb_log_read(struct ovsdb_log *, struct json **)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_log_write(struct ovsdb_log *, struct json *)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_log_commit(struct ovsdb_log *)
+    WARN_UNUSED_RESULT;
+
+#endif /* ovsdb/log.h */
diff --git a/ovsdb/mutation.c b/ovsdb/mutation.c
new file mode 100644 (file)
index 0000000..53e46f9
--- /dev/null
@@ -0,0 +1,462 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "mutation.h"
+
+#include <float.h>
+#include <limits.h>
+
+#include "column.h"
+#include "ovsdb-error.h"
+#include "json.h"
+#include "row.h"
+#include "table.h"
+
+enum mutate_error {
+    ME_OK,
+    ME_DOM,
+    ME_RANGE,
+    ME_COUNT,
+    ME_DUP
+};
+
+struct ovsdb_error *
+ovsdb_mutator_from_string(const char *name, enum ovsdb_mutator *mutator)
+{
+#define OVSDB_MUTATOR(ENUM, NAME)               \
+    if (!strcmp(name, NAME)) {                  \
+        *mutator = ENUM;                        \
+        return NULL;                            \
+    }
+    OVSDB_MUTATORS;
+#undef OVSDB_MUTATOR
+
+    return ovsdb_syntax_error(NULL, "unknown mutator",
+                              "No mutator named %s.", name);
+}
+
+const char *
+ovsdb_mutator_to_string(enum ovsdb_mutator mutator)
+{
+    switch (mutator) {
+#define OVSDB_MUTATOR(ENUM, NAME) case ENUM: return NAME;
+        OVSDB_MUTATORS;
+#undef OVSDB_MUTATOR
+    }
+
+    return NULL;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+type_mismatch(const struct ovsdb_mutation *m, const struct json *json)
+{
+    struct ovsdb_error *error;
+    char *s;
+
+    s = ovsdb_type_to_english(&m->column->type);
+    error = ovsdb_syntax_error(
+        json, NULL, "Type mismatch: \"%s\" operator may not be "
+        "applied to column %s of type %s.",
+        ovsdb_mutator_to_string(m->mutator), m->column->name, s);
+    free(s);
+
+    return error;
+}
+
+static WARN_UNUSED_RESULT struct ovsdb_error *
+ovsdb_mutation_from_json(const struct ovsdb_table_schema *ts,
+                         const struct json *json,
+                         const struct ovsdb_symbol_table *symtab,
+                         struct ovsdb_mutation *m)
+{
+    const struct json_array *array;
+    struct ovsdb_error *error;
+    const char *mutator_name;
+    const char *column_name;
+
+    if (json->type != JSON_ARRAY
+        || json->u.array.n != 3
+        || json->u.array.elems[0]->type != JSON_STRING
+        || json->u.array.elems[1]->type != JSON_STRING) {
+        return ovsdb_syntax_error(json, NULL, "Parse error in mutation.");
+    }
+    array = json_array(json);
+
+    column_name = json_string(array->elems[0]);
+    m->column = ovsdb_table_schema_get_column(ts, column_name);
+    if (!m->column) {
+        return ovsdb_syntax_error(json, "unknown column",
+                                  "No column %s in table %s.",
+                                  column_name, ts->name);
+    }
+    m->type = m->column->type;
+
+    mutator_name = json_string(array->elems[1]);
+    error = ovsdb_mutator_from_string(mutator_name, &m->mutator);
+    if (error) {
+        return error;
+    }
+
+    /* Type-check and relax restrictions on 'type' if appropriate.  */
+    switch (m->mutator) {
+    case OVSDB_M_ADD:
+    case OVSDB_M_SUB:
+    case OVSDB_M_MUL:
+    case OVSDB_M_DIV:
+    case OVSDB_M_MOD:
+        if ((!ovsdb_type_is_scalar(&m->type) && !ovsdb_type_is_set(&m->type))
+            || (m->type.key_type != OVSDB_TYPE_INTEGER
+                && m->type.key_type != OVSDB_TYPE_REAL)
+            || (m->mutator == OVSDB_M_MOD
+                && m->type.key_type == OVSDB_TYPE_REAL)) {
+            return type_mismatch(m, json);
+        }
+        m->type.n_min = m->type.n_max = 1;
+        return ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
+                                     symtab);
+
+    case OVSDB_M_INSERT:
+    case OVSDB_M_DELETE:
+        if (!ovsdb_type_is_set(&m->type) && !ovsdb_type_is_map(&m->type)) {
+            return type_mismatch(m, json);
+        }
+        m->type.n_min = 0;
+        if (m->mutator == OVSDB_M_DELETE) {
+            m->type.n_max = UINT_MAX;
+        }
+        error = ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
+                                      symtab);
+        if (error && ovsdb_type_is_map(&m->type)
+            && m->mutator == OVSDB_M_DELETE) {
+            ovsdb_error_destroy(error);
+            m->type.value_type = OVSDB_TYPE_VOID;
+            error = ovsdb_datum_from_json(&m->arg, &m->type, array->elems[2],
+                                          symtab);
+        }
+        return error;
+    }
+
+    NOT_REACHED();
+}
+
+static void
+ovsdb_mutation_free(struct ovsdb_mutation *m)
+{
+    ovsdb_datum_destroy(&m->arg, &m->type);
+}
+
+struct ovsdb_error *
+ovsdb_mutation_set_from_json(const struct ovsdb_table_schema *ts,
+                             const struct json *json,
+                             const struct ovsdb_symbol_table *symtab,
+                             struct ovsdb_mutation_set *set)
+{
+    const struct json_array *array = json_array(json);
+    size_t i;
+
+    set->mutations = xmalloc(array->n * sizeof *set->mutations);
+    set->n_mutations = 0;
+    for (i = 0; i < array->n; i++) {
+        struct ovsdb_error *error;
+        error = ovsdb_mutation_from_json(ts, array->elems[i], symtab,
+                                         &set->mutations[i]);
+        if (error) {
+            ovsdb_mutation_set_destroy(set);
+            set->mutations = NULL;
+            set->n_mutations = 0;
+            return error;
+        }
+        set->n_mutations++;
+    }
+
+    return NULL;
+}
+
+static struct json *
+ovsdb_mutation_to_json(const struct ovsdb_mutation *m)
+{
+    return json_array_create_3(
+        json_string_create(m->column->name),
+        json_string_create(ovsdb_mutator_to_string(m->mutator)),
+        ovsdb_datum_to_json(&m->arg, &m->type));
+}
+
+struct json *
+ovsdb_mutation_set_to_json(const struct ovsdb_mutation_set *set)
+{
+    struct json **mutations;
+    size_t i;
+
+    mutations = xmalloc(set->n_mutations * sizeof *mutations);
+    for (i = 0; i < set->n_mutations; i++) {
+        mutations[i] = ovsdb_mutation_to_json(&set->mutations[i]);
+    }
+    return json_array_create(mutations, set->n_mutations);
+}
+
+void
+ovsdb_mutation_set_destroy(struct ovsdb_mutation_set *set)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_mutations; i++) {
+        ovsdb_mutation_free(&set->mutations[i]);
+    }
+    free(set->mutations);
+}
+
+static int
+add_int(int64_t *x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 27. */
+    int64_t z = ~(*x ^ y) & INT64_MIN;
+    if ((~(*x ^ y) & ~(((*x ^ z) + y) ^ y)) >> 63) {
+        return ME_RANGE;
+    } else {
+        *x += y;
+        return 0;
+    }
+}
+
+static int
+sub_int(int64_t *x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 27. */
+    int64_t z = (*x ^ y) & INT64_MIN;
+    if (((*x ^ y) & (((*x ^ z) - y) ^ y)) >> 63) {
+        return ME_RANGE;
+    } else {
+        *x -= y;
+        return 0;
+    }
+}
+
+static int
+mul_int(int64_t *x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 30. */
+    if (*x > 0
+        ? (y > 0
+           ? *x >= INT64_MAX / y
+           : y  < INT64_MIN / *x)
+        : (y > 0
+           ? *x < INT64_MIN / y
+           : *x != 0 && y < INT64_MAX / y)) {
+        return ME_RANGE;
+    } else {
+        *x *= y;
+        return 0;
+    }
+}
+
+static int
+check_int_div(int64_t x, int64_t y)
+{
+    /* Check for overflow.  See _Hacker's Delight_ pp. 32. */
+    if (!y) {
+        return ME_DOM;
+    } else if (x == INT64_MIN && y == -1) {
+        return ME_RANGE;
+    } else {
+        return 0;
+    }
+}
+
+static int
+div_int(int64_t *x, int64_t y)
+{
+    int error = check_int_div(*x, y);
+    if (!error) {
+        *x /= y;
+    }
+    return error;
+}
+
+static int
+mod_int(int64_t *x, int64_t y)
+{
+    int error = check_int_div(*x, y);
+    if (!error) {
+        *x %= y;
+    }
+    return error;
+}
+
+static int
+check_real_range(double x)
+{
+    return x >= -DBL_MAX && x <= DBL_MAX ? 0 : ME_RANGE;
+}
+
+static int
+add_double(double *x, double y)
+{
+    *x += y;
+    return 0;
+}
+
+static int
+sub_double(double *x, double y)
+{
+    *x -= y;
+    return 0;
+}
+
+static int
+mul_double(double *x, double y)
+{
+    *x *= y;
+    return 0;
+}
+
+static int
+div_double(double *x, double y)
+{
+    if (y == 0) {
+        return ME_DOM;
+    } else {
+        *x /= y;
+        return 0;
+    }
+}
+
+static int
+mutate_scalar(const struct ovsdb_type *dst_type, struct ovsdb_datum *dst,
+              const union ovsdb_atom *arg,
+              int (*mutate_integer)(int64_t *x, int64_t y),
+              int (*mutate_real)(double *x, double y))
+{
+    struct ovsdb_error *error;
+    unsigned int i;
+
+    if (dst_type->key_type == OVSDB_TYPE_INTEGER) {
+        int64_t y = arg->integer;
+        for (i = 0; i < dst->n; i++) {
+            int error = mutate_integer(&dst->keys[i].integer, y);
+            if (error) {
+                return error;
+            }
+        }
+    } else if (dst_type->key_type == OVSDB_TYPE_REAL) {
+        double y = arg->real;
+        for (i = 0; i < dst->n; i++) {
+            double *x = &dst->keys[i].real;
+            int error = mutate_real(x, y);
+            if (!error) {
+                error = check_real_range(*x);
+            }
+            if (error) {
+                return error;
+            }
+        }
+    } else {
+        NOT_REACHED();
+    }
+
+    error = ovsdb_datum_sort(dst, dst_type);
+    if (error) {
+        ovsdb_error_destroy(error);
+        return ME_DUP;
+    }
+    return 0;
+}
+
+struct ovsdb_error *
+ovsdb_mutation_set_execute(struct ovsdb_row *row,
+                           const struct ovsdb_mutation_set *set)
+{
+    size_t i;
+
+    for (i = 0; i < set->n_mutations; i++) {
+        const struct ovsdb_mutation *m = &set->mutations[i];
+        struct ovsdb_datum *dst = &row->fields[m->column->index];
+        const struct ovsdb_type *dst_type = &m->column->type;
+        const struct ovsdb_datum *arg = &set->mutations[i].arg;
+        const struct ovsdb_type *arg_type = &m->type;
+        int error;
+
+        switch (m->mutator) {
+        case OVSDB_M_ADD:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0],
+                                  add_int, add_double);
+            break;
+
+        case OVSDB_M_SUB:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0],
+                                  sub_int, sub_double);
+            break;
+
+        case OVSDB_M_MUL:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0],
+                                  mul_int, mul_double);
+            break;
+
+        case OVSDB_M_DIV:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0],
+                                  div_int, div_double);
+            break;
+
+        case OVSDB_M_MOD:
+            error = mutate_scalar(dst_type, dst, &arg->keys[0],
+                                  mod_int, NULL);
+            break;
+
+        case OVSDB_M_INSERT:
+            ovsdb_datum_union(dst, arg, dst_type, false);
+            error = ovsdb_datum_conforms_to_type(dst, dst_type) ? 0 : ME_COUNT;
+            break;
+
+        case OVSDB_M_DELETE:
+            ovsdb_datum_subtract(dst, dst_type, arg, arg_type);
+            error = ovsdb_datum_conforms_to_type(dst, dst_type) ? 0 : ME_COUNT;
+            break;
+        }
+
+        switch (error) {
+        case 0:
+            break;
+
+        case ME_DOM:
+            return ovsdb_error("domain error", "Division by zero.");
+
+        case ME_RANGE:
+            return ovsdb_error("range error",
+                               "Result of \"%s\" operation is out of range.",
+                               ovsdb_mutator_to_string(m->mutator));
+
+        case ME_DUP:
+            return ovsdb_error("constraint violation",
+                               "Result of \"%s\" operation contains "
+                               "duplicates.",
+                               ovsdb_mutator_to_string(m->mutator));
+
+        case ME_COUNT: {
+            char *s = ovsdb_type_to_english(dst_type);
+            struct ovsdb_error *e = ovsdb_error(
+                "constaint violation",
+                "Attempted to store %u elements in %s.", dst->n, s);
+            free(s);
+            return e;
+        }
+
+        default:
+            return OVSDB_BUG("unexpected errno");
+        }
+    }
+
+    return NULL;
+}
diff --git a/ovsdb/mutation.h b/ovsdb/mutation.h
new file mode 100644 (file)
index 0000000..d466e28
--- /dev/null
@@ -0,0 +1,72 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_MUTATION_H
+#define OVSDB_MUTATION_H 1
+
+#include <stddef.h>
+#include "compiler.h"
+#include "ovsdb-data.h"
+
+struct json;
+struct ovsdb_table_schema;
+struct ovsdb_row;
+
+/* These list is ordered in ascending order of the fraction of tables row that
+ * they are (heuristically) expected to leave in query results. */
+#define OVSDB_MUTATORS                              \
+    OVSDB_MUTATOR(OVSDB_M_ADD, "+=")                \
+    OVSDB_MUTATOR(OVSDB_M_SUB, "-=")                \
+    OVSDB_MUTATOR(OVSDB_M_MUL, "*=")                \
+    OVSDB_MUTATOR(OVSDB_M_DIV, "/=")                \
+    OVSDB_MUTATOR(OVSDB_M_MOD, "%=")                \
+    OVSDB_MUTATOR(OVSDB_M_INSERT, "insert")         \
+    OVSDB_MUTATOR(OVSDB_M_DELETE, "delete")
+
+enum ovsdb_mutator {
+#define OVSDB_MUTATOR(ENUM, NAME) ENUM,
+    OVSDB_MUTATORS
+#undef OVSDB_MUTATOR
+};
+
+struct ovsdb_error *ovsdb_mutator_from_string(const char *,
+                                              enum ovsdb_mutator *)
+    WARN_UNUSED_RESULT;
+const char *ovsdb_mutator_to_string(enum ovsdb_mutator);
+
+struct ovsdb_mutation {
+    enum ovsdb_mutator mutator;
+    const struct ovsdb_column *column;
+    struct ovsdb_datum arg;
+    struct ovsdb_type type;
+};
+
+struct ovsdb_mutation_set {
+    struct ovsdb_mutation *mutations;
+    size_t n_mutations;
+};
+
+#define OVSDB_MUTATION_SET_INITIALIZER { NULL, 0 }
+
+struct ovsdb_error *ovsdb_mutation_set_from_json(
+    const struct ovsdb_table_schema *,
+    const struct json *, const struct ovsdb_symbol_table *,
+    struct ovsdb_mutation_set *) WARN_UNUSED_RESULT;
+struct json *ovsdb_mutation_set_to_json(const struct ovsdb_mutation_set *);
+void ovsdb_mutation_set_destroy(struct ovsdb_mutation_set *);
+struct ovsdb_error *ovsdb_mutation_set_execute(
+    struct ovsdb_row *, const struct ovsdb_mutation_set *);
+
+#endif /* ovsdb/mutation.h */
diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in
new file mode 100644 (file)
index 0000000..716fcc2
--- /dev/null
@@ -0,0 +1,140 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.\" -*- nroff -*-
+.TH ovsdb\-client 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-client
+.
+.SH NAME
+ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
+.
+.SH SYNOPSIS
+\fBovsdb\-client \fR[\fIoptions\fR] \fBget-schema\fI server\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBlist-tables\fI server\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBlist-columns\fI server \fR[\fItable\fR]
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBtransact\fI server transaction\fR
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor\fI server table\fR
+[\fIcolumn\fR[\fB,\fIcolumn\fR]...]
+[\fIselect\fR[\fB,\fIselect\fR]...]
+.br
+\fBovsdb\-client help\fR
+.IP "Output formatting options:"
+[\fB--format=\fIformat\fR]
+[\fB--wide\fR]
+[\fB--no-heading\fR]
+.so lib/daemon-syn.man
+.so lib/vlog-syn.man
+.so lib/ssl-syn.man
+.so lib/ssl-bootstrap-syn.man
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovsdb\-client\fR program is a command-line client for
+interacting with a running \fBovsdb\-server\fR process.  For each
+command, the \fIserver\fR to connect to must be specified in one of
+the following forms:
+.RS
+.so ovsdb/remote-active.man
+.so ovsdb/remote-passive.man
+.RE
+.
+.SS "Commands"
+The following commands are implemented:
+.IP "\fBget-schema\fI server\fR"
+Connects to \fIserver\fR, retrieves the database schema, and prints it
+in JSON format.
+.
+.IP "\fBlist-tables\fI server\fR"
+Connects to \fIserver\fR, retrieves the database schema, and prints
+a table listing the names and comments (if any) on each table within
+the database.
+.
+.IP "\fBlist-columns\fI server \fR[\fItable\fR]"
+Connects to \fIserver\fR, retrieves the database schema, and prints
+a table listing the names, type, and comment (if any) on each column.  If
+\fItable\fR is specified, only columns in that table are listed;
+otherwise, the tables include columns in all tables.
+.
+.IP "\fBtransact\fI server transaction\fR"
+Connects to \fIserver\fR, sends it the specified \fItransaction\fR,
+which must be a JSON array containing one or more valid OVSDB
+operations, and prints the received reply on stdout.
+.
+.IP "\fBmonitor\fI server table\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...] [\fIselect\fR[\fB,\fIselect\fR]...]"
+Connects to \fIserver\fR and monitors the contents of \fItable\fR.  By
+default, the initial contents of \fItable\fR are printed, followed by
+each change as it occurs.  If at least one \fIcolumn\fR is specified,
+only those columns are monitored.  If at least one \fIselect\fR is
+specified, they are interpreted as follows:
+.RS
+.IP "\fBinitial\fR"
+Print the initial contents of the specified columns.
+.IP "\fBinsert\fR"
+Print newly inserted rows.
+.IP "\fBdelete\fR"
+Print deleted rows.
+.IP "\fBmodify\fR"
+Print old and new values of modified rows.
+.RE
+.IP
+If \fB--detach\fR is used with \fBmonitor\fR, then \fBovsdb\-client\fR
+detaches after it has successfully received and printed the initial
+contents of \fItable\fR.
+.SH OPTIONS
+.SS "Output Formatting Options"
+Much of the output from \fBovsdb\-client\fR is in the form of tables.
+The following options controlling output formatting:
+.
+.IP "\fB-f \fIformat\fR"
+.IQ "\fB--format=\fIformat\fR"
+Sets the basic type of output formatting.  The following types of
+\fIformat\fR are available:
+.RS
+.IP "\fBtable\fR (default)"
+Text-based tables with aligned columns.
+.IP "\fBhtml\fR"
+HTML tables.
+.IP "\fBcvs\fR"
+Comma-separated values as defined in RFC 4180.
+.RE
+.
+.IP "\fB--wide\fR"
+In \fBtable\fR output (the default), when standard output is a
+terminal device, by default lines are truncated at a width of 79
+characters.  Specifying this option prevents line truncation.
+.
+.IP "\fB--no-heading\fR"
+This option suppresses the heading row that otherwise appears in the
+first row of table output.
+.
+.IP "\fB--pretty\fR"
+By default, JSON in output is printed as compactly as possible.  This
+option causes JSON in output to be printed in a more readable
+fashion.  Members of objects and elements of arrays are printed one
+per line, with indentation.
+.IP
+This option does not affect JSON in tables, which is always printed
+compactly.
+.
+.SS "Daemon Options"
+The daemon options apply only to the \fBmonitor\fR command.  With any
+other command, they have no effect.
+.so lib/daemon.man
+.SS "Logging Options"
+.so lib/vlog.man
+.so lib/ssl.man
+.so lib/ssl-bootstrap.man
+.SS "Other Options"
+.so lib/common.man
+.SH "SEE ALSO"
+.
+\fBovsdb\-server\fR(1),
+\fBovsdb\-client\fR(1),
+and the OVSDB specification.
diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c
new file mode 100644 (file)
index 0000000..e7d3b50
--- /dev/null
@@ -0,0 +1,884 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "command-line.h"
+#include "column.h"
+#include "compiler.h"
+#include "daemon.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "stream.h"
+#include "stream-ssl.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_client
+
+/* --format: Output formatting. */
+static enum {
+    FMT_TABLE,                  /* Textual table. */
+    FMT_HTML,                   /* HTML table. */
+    FMT_CSV                     /* Comma-separated lines. */
+} output_format;
+
+/* --wide: For --format=table, the maximum output width. */
+static int output_width;
+
+/* --no-headings: Whether table output should include headings. */
+static int output_headings = true;
+
+/* --pretty: Flags to pass to json_to_string(). */
+static int json_flags = JSSF_SORT;
+
+static const struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    signal(SIGPIPE, SIG_IGN);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    enum {
+        OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1
+    };
+    static struct option long_options[] = {
+        {"wide", no_argument, &output_width, INT_MAX},
+        {"format", required_argument, 0, 'f'},
+           {"no-headings", no_argument, &output_headings, 0},
+        {"pretty", no_argument, &json_flags, JSSF_PRETTY | JSSF_SORT},
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {"version", no_argument, 0, 'V'},
+        DAEMON_LONG_OPTIONS,
+#ifdef HAVE_OPENSSL
+        {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
+        STREAM_SSL_LONG_OPTIONS
+#endif
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    output_width = isatty(fileno(stdout)) ? 79 : INT_MAX;
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'f':
+            if (!strcmp(optarg, "table")) {
+                output_format = FMT_TABLE;
+            } else if (!strcmp(optarg, "html")) {
+                output_format = FMT_HTML;
+            } else if (!strcmp(optarg, "csv")) {
+                output_format = FMT_CSV;
+            } else {
+                ovs_fatal(0, "unknown output format \"%s\"", optarg);
+            }
+            break;
+
+        case 'w':
+            output_width = INT_MAX;
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        DAEMON_OPTION_HANDLERS
+
+#ifdef HAVE_OPENSSL
+        STREAM_SSL_OPTION_HANDLERS
+
+        case OPT_BOOTSTRAP_CA_CERT:
+            stream_ssl_set_ca_cert_file(optarg, true);
+            break;
+#endif
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        case 0:
+            /* getopt_long() already set the value for us. */
+            break;
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database JSON-RPC client\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+           "\nValid commands are:\n"
+           "\n  get-schema SERVER\n"
+           "    retrieve schema from SERVER\n"
+           "\n  list-tables SERVER\n"
+           "    list SERVER's tables\n"
+           "\n  list-columns SERVER [TABLE]\n"
+           "    list columns in TABLE (or all tables) on SERVER\n"
+           "\n  transact SERVER TRANSACTION\n"
+           "    run TRANSACTION (a JSON array of operations) on SERVER\n"
+           "    and print the results as JSON on stdout\n"
+           "\n  monitor SERVER TABLE [COLUMN,...] [SELECT,...]\n"
+           "    monitor contents of (COLUMNs in) TABLE on SERVER\n"
+           "    Valid SELECTs are: initial, insert, delete, modify\n",
+           program_name, program_name);
+    stream_usage("SERVER", true, true, true);
+    printf("\nOutput formatting options:\n"
+           "  -f, --format=FORMAT         set output formatting to FORMAT\n"
+           "                              (\"table\", \"html\", or \"csv\"\n"
+           "  --wide                      don't limit TTY lines to 79 bytes\n"
+           "  --no-headings               omit table heading row\n"
+           "  --pretty                    pretty-print JSON in output");
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help                  display this help message\n"
+           "  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static struct jsonrpc *
+open_jsonrpc(const char *server)
+{
+    struct stream *stream;
+    int error;
+
+    error = stream_open_block(server, &stream);
+    if (error == EAFNOSUPPORT) {
+        struct pstream *pstream;
+
+        error = pstream_open(server, &pstream);
+        if (error) {
+            ovs_fatal(error, "failed to connect or listen to \"%s\"", server);
+        }
+
+        VLOG_INFO("%s: waiting for connection...", server);
+        error = pstream_accept_block(pstream, &stream);
+        if (error) {
+            ovs_fatal(error, "failed to accept connection on \"%s\"", server);
+        }
+
+        pstream_close(pstream);
+    } else if (error) {
+        ovs_fatal(error, "failed to connect to \"%s\"", server);
+    }
+
+    return jsonrpc_open(stream);
+}
+
+static void
+print_json(struct json *json)
+{
+    char *string = json_to_string(json, json_flags);
+    fputs(string, stdout);
+    free(string);
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    print_json(json);
+    json_destroy(json);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+}
+
+static struct ovsdb_schema *
+fetch_schema_from_rpc(struct jsonrpc *rpc)
+{
+    struct jsonrpc_msg *request, *reply;
+    struct ovsdb_schema *schema;
+    int error;
+
+    request = jsonrpc_create_request("get_schema", json_array_create_empty(),
+                                     NULL);
+    error = jsonrpc_transact_block(rpc, request, &reply);
+    if (error) {
+        ovs_fatal(error, "transaction failed");
+    }
+    check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema));
+    jsonrpc_msg_destroy(reply);
+
+    return schema;
+}
+
+static struct ovsdb_schema *
+fetch_schema(const char *server)
+{
+    struct ovsdb_schema *schema;
+    struct jsonrpc *rpc;
+
+    rpc = open_jsonrpc(server);
+    schema = fetch_schema_from_rpc(rpc);
+    jsonrpc_close(rpc);
+
+    return schema;
+}
+\f
+struct column {
+    char *heading;
+    int width;
+};
+
+struct table {
+    char **cells;
+    struct column *columns;
+    size_t n_columns, allocated_columns;
+    size_t n_rows, allocated_rows;
+    size_t current_column;
+};
+
+static void
+table_init(struct table *table)
+{
+    memset(table, 0, sizeof *table);
+}
+
+static void
+table_destroy(struct table *table)
+{
+    size_t i;
+
+    for (i = 0; i < table->n_columns; i++) {
+        free(table->columns[i].heading);
+    }
+    free(table->columns);
+
+    for (i = 0; i < table->n_columns * table->n_rows; i++) {
+        free(table->cells[i]);
+    }
+    free(table->cells);
+}
+
+static void
+table_add_column(struct table *table, const char *heading, ...)
+    PRINTF_FORMAT(2, 3);
+
+static void
+table_add_column(struct table *table, const char *heading, ...)
+{
+    struct column *column;
+    va_list args;
+
+    assert(!table->n_rows);
+    if (table->n_columns >= table->allocated_columns) {
+        table->columns = x2nrealloc(table->columns, &table->allocated_columns,
+                                    sizeof *table->columns);
+    }
+    column = &table->columns[table->n_columns++];
+
+    va_start(args, heading);
+    column->heading = xvasprintf(heading, args);
+    column->width = strlen(column->heading);
+    va_end(args);
+}
+
+static char **
+table_cell__(const struct table *table, size_t row, size_t column)
+{
+    return &table->cells[column + row * table->n_columns];
+}
+
+static void
+table_add_row(struct table *table)
+{
+    size_t x, y;
+
+    if (table->n_rows >= table->allocated_rows) {
+        table->cells = x2nrealloc(table->cells, &table->allocated_rows,
+                                  table->n_columns * sizeof *table->cells);
+    }
+
+    y = table->n_rows++;
+    table->current_column = 0;
+    for (x = 0; x < table->n_columns; x++) {
+        *table_cell__(table, y, x) = NULL;
+    }
+}
+
+static void
+table_add_cell_nocopy(struct table *table, char *s)
+{
+    size_t x, y;
+    int length;
+
+    assert(table->n_rows > 0);
+    assert(table->current_column < table->n_columns);
+
+    x = table->current_column++;
+    y = table->n_rows - 1;
+    *table_cell__(table, y, x) = s;
+
+    length = strlen(s);
+    if (length > table->columns[x].width) {
+        table->columns[x].width = length;
+    }
+}
+
+static void
+table_add_cell(struct table *table, const char *format, ...)
+{
+    va_list args;
+
+    va_start(args, format);
+    table_add_cell_nocopy(table, xvasprintf(format, args));
+    va_end(args);
+}
+
+static void
+table_print_table_line__(struct ds *line, size_t max_width)
+{
+    ds_truncate(line, max_width);
+    puts(ds_cstr(line));
+    ds_clear(line);
+}
+
+static void
+table_print_table__(const struct table *table)
+{
+    struct ds line = DS_EMPTY_INITIALIZER;
+    size_t x, y;
+
+    if (output_headings) {
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            if (x) {
+                ds_put_char(&line, ' ');
+            }
+            ds_put_format(&line, "%-*s", column->width, column->heading);
+        }
+        table_print_table_line__(&line, output_width);
+
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            int i;
+
+            if (x) {
+                ds_put_char(&line, ' ');
+            }
+            for (i = 0; i < column->width; i++) {
+                ds_put_char(&line, '-');
+            }
+        }
+        table_print_table_line__(&line, output_width);
+    }
+
+    for (y = 0; y < table->n_rows; y++) {
+        for (x = 0; x < table->n_columns; x++) {
+            const char *cell = *table_cell__(table, y, x);
+            if (x) {
+                ds_put_char(&line, ' ');
+            }
+            ds_put_format(&line, "%-*s", table->columns[x].width, cell);
+        }
+        table_print_table_line__(&line, output_width);
+    }
+
+    ds_destroy(&line);
+}
+
+static void
+table_print_html_cell__(const char *element, const char *content)
+{
+    const char *p;
+
+    printf("    <%s>", element);
+    for (p = content; *p != '\0'; p++) {
+        switch (*p) {
+        case '&':
+            fputs("&amp;", stdout);
+            break;
+        case '<':
+            fputs("&lt;", stdout);
+            break;
+        case '>':
+            fputs("&gt;", stdout);
+            break;
+        default:
+            putchar(*p);
+            break;
+        }
+    }
+    printf("</%s>\n", element);
+}
+
+static void
+table_print_html__(const struct table *table)
+{
+    size_t x, y;
+
+    fputs("<table>\n", stdout);
+
+    if (output_headings) {
+        fputs("  <tr>\n", stdout);
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            table_print_html_cell__("th", column->heading);
+        }
+        fputs("  </tr>\n", stdout);
+    }
+
+    for (y = 0; y < table->n_rows; y++) {
+        fputs("  <tr>\n", stdout);
+        for (x = 0; x < table->n_columns; x++) {
+            table_print_html_cell__("td", *table_cell__(table, y, x));
+        }
+        fputs("  </tr>\n", stdout);
+    }
+
+    fputs("</table>\n", stdout);
+}
+
+static void
+table_print_csv_cell__(const char *content)
+{
+    const char *p;
+
+    if (!strpbrk(content, "\n\",")) {
+        fputs(content, stdout);
+    } else {
+        putchar('"');
+        for (p = content; *p != '\0'; p++) {
+            switch (*p) {
+            case '"':
+                fputs("\"\"", stdout);
+                break;
+            default:
+                putchar(*p);
+                break;
+            }
+        }
+        putchar('"');
+    }
+}
+
+static void
+table_print_csv__(const struct table *table)
+{
+    size_t x, y;
+
+    if (output_headings) {
+        for (x = 0; x < table->n_columns; x++) {
+            const struct column *column = &table->columns[x];
+            if (x) {
+                putchar(',');
+            }
+            table_print_csv_cell__(column->heading);
+        }
+        putchar('\n');
+    }
+
+    for (y = 0; y < table->n_rows; y++) {
+        for (x = 0; x < table->n_columns; x++) {
+            if (x) {
+                putchar(',');
+            }
+            table_print_csv_cell__(*table_cell__(table, y, x));
+        }
+        putchar('\n');
+    }
+}
+
+static void
+table_print(const struct table *table)
+{
+    switch (output_format) {
+    case FMT_TABLE:
+        table_print_table__(table);
+        break;
+
+    case FMT_HTML:
+        table_print_html__(table);
+        break;
+
+    case FMT_CSV:
+        table_print_csv__(table);
+        break;
+    }
+}
+\f
+static void
+do_get_schema(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema = fetch_schema(argv[1]);
+    print_and_free_json(ovsdb_schema_to_json(schema));
+    ovsdb_schema_destroy(schema);
+}
+
+static void
+do_list_tables(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct shash_node *node;
+    struct table t;
+
+    schema = fetch_schema(argv[1]);
+    table_init(&t);
+    table_add_column(&t, "Table");
+    table_add_column(&t, "Comment");
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *ts = node->data;
+
+        table_add_row(&t);
+        table_add_cell(&t, ts->name);
+        if (ts->comment) {
+            table_add_cell(&t, ts->comment);
+        }
+    }
+    ovsdb_schema_destroy(schema);
+    table_print(&t);
+}
+
+static void
+do_list_columns(int argc UNUSED, char *argv[])
+{
+    const char *table_name = argv[2];
+    struct ovsdb_schema *schema;
+    struct shash_node *table_node;
+    struct table t;
+
+    schema = fetch_schema(argv[1]);
+    table_init(&t);
+    if (!table_name) {
+        table_add_column(&t, "Table");
+    }
+    table_add_column(&t, "Column");
+    table_add_column(&t, "Type");
+    table_add_column(&t, "Comment");
+    SHASH_FOR_EACH (table_node, &schema->tables) {
+        struct ovsdb_table_schema *ts = table_node->data;
+
+        if (!table_name || !strcmp(table_name, ts->name)) {
+            struct shash_node *column_node;
+
+            SHASH_FOR_EACH (column_node, &ts->columns) {
+                struct ovsdb_column *column = column_node->data;
+                struct json *type = ovsdb_type_to_json(&column->type);
+
+                table_add_row(&t);
+                if (!table_name) {
+                    table_add_cell(&t, ts->name);
+                }
+                table_add_cell(&t, column->name);
+                table_add_cell_nocopy(&t, json_to_string(type, JSSF_SORT));
+                if (column->comment) {
+                    table_add_cell(&t, column->comment);
+                }
+
+                json_destroy(type);
+            }
+        }
+    }
+    ovsdb_schema_destroy(schema);
+    table_print(&t);
+}
+
+static void
+do_transact(int argc UNUSED, char *argv[])
+{
+    struct jsonrpc_msg *request, *reply;
+    struct json *transaction;
+    struct jsonrpc *rpc;
+    int error;
+
+    transaction = parse_json(argv[2]);
+
+    rpc = open_jsonrpc(argv[1]);
+    request = jsonrpc_create_request("transact", transaction, NULL);
+    error = jsonrpc_transact_block(rpc, request, &reply);
+    if (error) {
+        ovs_fatal(error, "transaction failed");
+    }
+    if (reply->error) {
+        ovs_fatal(error, "transaction returned error: %s",
+                  json_to_string(reply->error, json_flags));
+    }
+    print_json(reply->result);
+    putchar('\n');
+    jsonrpc_msg_destroy(reply);
+    jsonrpc_close(rpc);
+}
+
+static void
+monitor_print_row(struct json *row, const char *type, const char *uuid,
+                  const struct ovsdb_column_set *columns, struct table *t)
+{
+    size_t i;
+
+    if (!row) {
+        ovs_error(0, "missing %s row", type);
+        return;
+    } else if (row->type != JSON_OBJECT) {
+        ovs_error(0, "<row> is not object");
+        return;
+    }
+
+    table_add_row(t);
+    table_add_cell(t, uuid);
+    table_add_cell(t, type);
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        struct json *value = shash_find_data(json_object(row), column->name);
+        if (value) {
+            table_add_cell_nocopy(t, json_to_string(value, JSSF_SORT));
+        } else {
+            table_add_cell(t, "");
+        }
+    }
+}
+
+static void
+monitor_print(struct json *table_updates,
+              const struct ovsdb_table_schema *table,
+              const struct ovsdb_column_set *columns, bool initial)
+{
+    struct json *table_update;
+    struct shash_node *node;
+    struct table t;
+    size_t i;
+
+    table_init(&t);
+
+    if (table_updates->type != JSON_OBJECT) {
+        ovs_error(0, "<table-updates> is not object");
+        return;
+    }
+    table_update = shash_find_data(json_object(table_updates), table->name);
+    if (!table_update) {
+        return;
+    }
+    if (table_update->type != JSON_OBJECT) {
+        ovs_error(0, "<table-update> is not object");
+        return;
+    }
+
+    table_add_column(&t, "row");
+    table_add_column(&t, "action");
+    for (i = 0; i < columns->n_columns; i++) {
+        table_add_column(&t, "%s", columns->columns[i]->name);
+    }
+    SHASH_FOR_EACH (node, json_object(table_update)) {
+        struct json *row_update = node->data;
+        struct json *old, *new;
+
+        if (row_update->type != JSON_OBJECT) {
+            ovs_error(0, "<row-update> is not object");
+            continue;
+        }
+        old = shash_find_data(json_object(row_update), "old");
+        new = shash_find_data(json_object(row_update), "new");
+        if (initial) {
+            monitor_print_row(new, "initial", node->name, columns, &t);
+        } else if (!old) {
+            monitor_print_row(new, "insert", node->name, columns, &t);
+        } else if (!new) {
+            monitor_print_row(old, "delete", node->name, columns, &t);
+        } else {
+            monitor_print_row(old, "old", node->name, columns, &t);
+            monitor_print_row(new, "new", "", columns, &t);
+        }
+    }
+    table_print(&t);
+    table_destroy(&t);
+}
+
+static void
+do_monitor(int argc, char *argv[])
+{
+    struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER;
+    struct ovsdb_table_schema *table;
+    struct ovsdb_schema *schema;
+    struct jsonrpc_msg *request;
+    struct jsonrpc *rpc;
+    struct json *select, *monitor, *monitor_request, *monitor_requests,
+        *request_id;
+
+    rpc = open_jsonrpc(argv[1]);
+
+    schema = fetch_schema_from_rpc(rpc);
+    table = shash_find_data(&schema->tables, argv[2]);
+    if (!table) {
+        ovs_fatal(0, "%s: no table named \"%s\"", argv[1], argv[2]);
+    }
+
+    if (argc >= 4 && *argv[3] != '\0') {
+        char *save_ptr = NULL;
+        char *token;
+
+        for (token = strtok_r(argv[3], ",", &save_ptr); token != NULL;
+             token = strtok_r(NULL, ",", &save_ptr)) {
+            const struct ovsdb_column *column;
+            column = ovsdb_table_schema_get_column(table, token);
+            if (!column) {
+                ovs_fatal(0, "%s: table \"%s\" does not have a "
+                          "column named \"%s\"", argv[1], argv[2], token);
+            }
+            ovsdb_column_set_add(&columns, column);
+        }
+    } else {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->columns) {
+            const struct ovsdb_column *column = node->data;
+            if (column->index != OVSDB_COL_UUID) {
+                ovsdb_column_set_add(&columns, column);
+            }
+        }
+    }
+
+    if (argc >= 5 && *argv[4] != '\0') {
+        char *save_ptr = NULL;
+        char *token;
+
+        select = json_object_create();
+        for (token = strtok_r(argv[4], ",", &save_ptr); token != NULL;
+             token = strtok_r(NULL, ",", &save_ptr)) {
+            json_object_put(select, token, json_boolean_create(true));
+        }
+    } else {
+        select = NULL;
+    }
+
+    monitor_request = json_object_create();
+    json_object_put(monitor_request,
+                    "columns", ovsdb_column_set_to_json(&columns));
+    if (select) {
+        json_object_put(monitor_request, "select", select);
+    }
+
+    monitor_requests = json_object_create();
+    json_object_put(monitor_requests, argv[2], monitor_request);
+
+    monitor = json_array_create_2(json_null_create(), monitor_requests);
+    request = jsonrpc_create_request("monitor", monitor, NULL);
+    request_id = json_clone(request->id);
+    jsonrpc_send(rpc, request);
+    for (;;) {
+        struct jsonrpc_msg *msg;
+        int error;
+
+        error = jsonrpc_recv_block(rpc, &msg);
+        if (error) {
+            ovsdb_schema_destroy(schema);
+            ovs_fatal(error, "%s: receive failed", argv[1]);
+        }
+
+        if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
+            jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params),
+                                                   msg->id));
+        } else if (msg->type == JSONRPC_REPLY
+                   && json_equal(msg->id, request_id)) {
+            monitor_print(msg->result, table, &columns, true);
+            fflush(stdout);
+            if (get_detach()) {
+                /* daemonize() closes the standard file descriptors.  We output
+                 * to stdout, so we need to save and restore STDOUT_FILENO. */
+                int fd = dup(STDOUT_FILENO);
+                daemonize();
+                dup2(fd, STDOUT_FILENO);
+                close(fd);
+            }
+        } else if (msg->type == JSONRPC_NOTIFY
+                   && !strcmp(msg->method, "update")) {
+            struct json *params = msg->params;
+            if (params->type == JSON_ARRAY
+                && params->u.array.n == 2
+                && params->u.array.elems[0]->type == JSON_NULL) {
+                monitor_print(params->u.array.elems[1],
+                              table, &columns, false);
+                fflush(stdout);
+            }
+        }
+        jsonrpc_msg_destroy(msg);
+    }
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+    usage();
+}
+
+static const struct command all_commands[] = {
+    { "get-schema", 1, 1, do_get_schema },
+    { "list-tables", 1, 1, do_list_tables },
+    { "list-columns", 1, 2, do_list_columns },
+    { "transact", 2, 2, do_transact },
+    { "monitor", 2, 4, do_monitor },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/ovsdb/ovsdb-idlc.1 b/ovsdb/ovsdb-idlc.1
new file mode 100644 (file)
index 0000000..1a7e804
--- /dev/null
@@ -0,0 +1,84 @@
+.\" -*- nroff -*-
+.TH ovsdb\-idlc 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-idlc
+.
+.SH NAME
+ovsdb\-idlc \- Open vSwitch IDL (Interface Definition Language) compiler
+.
+.SH SYNOPSIS
+\fBovsdb\-idlc \fBannotate\fI schema annotations\fR 
+.br
+\fBovsdb\-idlc \fBc\-idl\-header\fI idl\fR
+.br
+\fBovsdb\-idlc \fBc\-idl\-source\fI idl\fR
+.br
+\fBovsdb\-idlc --help\fR
+.br
+\fBovsdb\-idlc --version\fR
+.
+.SH DESCRIPTION
+The \fBovsdb\-idlc\fR program is a command-line tool for translating
+Open vSwitch database interface definition language (IDL) schemas into
+other formats.  It is used while building Open vSwitch, not at
+installation or configuration time.  Thus, it is not normally
+installed as part of Open vSwitch.
+.
+.PP
+The \fIidl\fR files used as input for most \fBovsdb\-idlc\fR commands
+have the same format as the OVSDB schemas, specified in the OVSDB
+specification, with a few additions:
+.
+.IP "\fB""\fBidlPrefix\fR"" member of <database-schema>"
+This member, which is required, specifies a string that is prefixed to
+top-level names in C bindings.  It should probably end in an
+underscore.
+.
+.IP "\fB""\fBidlHeader\fR"" member of <database-schema>"
+This member, which is required, specifies the name of the IDL header.
+It will be output on an \fB#include\fR line in the source file
+generated by the C bindings.  It should include the bracketing
+\fB""\fR or \fB<>\fR.
+.
+.IP "\fB""\fBkeyRefTable\fR"" member of <type>"
+A <type> whose \fBkey\fR is \fB"uuid"\fR may have an additional member
+named \fB"keyRefTable"\fR, whose value is a table name.  This
+expresses the constraint that keys of the given <type> are UUIDs that
+reference rows in the named table.  This allows the IDL to supply a
+structure pointer in place of a raw UUID in its marshalled version of
+the given type.
+.
+.IP "\fB""valueRefTable""\fR member of <type>"
+Analogous to \fB"keyRefTable"\fR in meaning and effect, except that it
+applies to the \fB"value"\fR member of the <type>.
+.SS "Commands"
+.IP "\fBannotate\fI schema annotations\fR"
+Reads \fIschema\fR, which should be a file in JSON format (ordinarily
+an OVSDB schema file), then reads and executes the Python syntax
+fragment in \fIannotations\fR.  The Python syntax fragment is passed
+the JSON object as a local variable named \fBs\fR.  It may modify this
+data in any way.  After the Python code returns, the object as
+modified is re-serialized as JSON on standard output.
+.
+.IP "\fBc\-idl\-header\fI idl\fR"
+Reads \fIidl\fR and prints on standard output a C header file that
+defines a structure for each table defined by the schema.
+.
+.IP "\fBc\-idl\-source\fI idl\fR"
+Reads \fIidl\fR and prints on standard output a C source file that
+implements C bindings for the database defined by the schema.
+.
+.IP "\fBdoc\fI idl\fR"
+Reads \fIidl\fR and prints on standard output a text file that
+documents the schema.  The output may have very long lines, so it
+makes sense to pipe it through, e.g. \fBfmt \-s\fR.
+.
+.SS "Options"
+.so lib/common.man
+.
+.SH "BUGS"
+\fBovsdb\-idlc\fR is more lenient about the format of OVSDB schemas
+than other OVSDB tools.  It may successfully parse schemas that, e.g.,
+\fBovsdb\-tool\fR rejects.
+.
+.SH "SEE ALSO"
+The OVSDB specification.
diff --git a/ovsdb/ovsdb-idlc.in b/ovsdb/ovsdb-idlc.in
new file mode 100755 (executable)
index 0000000..24387b8
--- /dev/null
@@ -0,0 +1,767 @@
+#! @PYTHON@
+
+import getopt
+import os
+import re
+import sys
+
+sys.path.insert(0, "@abs_top_srcdir@/ovsdb")
+import simplejson as json
+
+argv0 = sys.argv[0]
+
+class Error(Exception):
+    def __init__(self, msg):
+        Exception.__init__(self)
+        self.msg = msg
+
+def getMember(json, name, validTypes, description, default=None):
+    if name in json:
+        member = json[name]
+        if type(member) not in validTypes:
+            raise Error("%s: type mismatch for '%s' member"
+                        % (description, name))
+        return member
+    return default
+
+def mustGetMember(json, name, expectedType, description):
+    member = getMember(json, name, expectedType, description)
+    if member == None:
+        raise Error("%s: missing '%s' member" % (description, name))
+    return member
+
+class DbSchema:
+    def __init__(self, name, comment, tables, idlPrefix, idlHeader):
+        self.name = name
+        self.comment = comment
+        self.tables = tables
+        self.idlPrefix = idlPrefix
+        self.idlHeader = idlHeader
+
+    @staticmethod
+    def fromJson(json):
+        name = mustGetMember(json, 'name', [unicode], 'database')
+        comment = getMember(json, 'comment', [unicode], 'database')
+        tablesJson = mustGetMember(json, 'tables', [dict], 'database')
+        tables = {}
+        for tableName, tableJson in tablesJson.iteritems():
+            tables[tableName] = TableSchema.fromJson(tableJson,
+                                                     "%s table" % tableName)
+        idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database')
+        idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database')
+        return DbSchema(name, comment, tables, idlPrefix, idlHeader)
+
+    def toJson(self):
+        d = {"name": self.name,
+             "tables": {}}
+        for name, table in self.tables.iteritems():
+            d["tables"][name] = table.toJson()
+        if self.comment != None:
+            d["comment"] = self.comment
+        return d
+
+class TableSchema:
+    def __init__(self, comment, columns):
+        self.comment = comment
+        self.columns = columns
+
+    @staticmethod
+    def fromJson(json, description):
+        comment = getMember(json, 'comment', [unicode], description)
+        columnsJson = mustGetMember(json, 'columns', [dict], description)
+        columns = {}
+        for name, json in columnsJson.iteritems():
+            columns[name] = ColumnSchema.fromJson(
+                json, "column %s in %s" % (name, description))
+        return TableSchema(comment, columns)
+
+    def toJson(self):
+        d = {"columns": {}}
+        for name, column in self.columns.iteritems():
+            d["columns"][name] = column.toJson()
+        if self.comment != None:
+            d["comment"] = self.comment
+        return d
+
+class ColumnSchema:
+    def __init__(self, comment, type, persistent):
+        self.comment = comment
+        self.type = type
+        self.persistent = persistent
+
+    @staticmethod
+    def fromJson(json, description):
+        comment = getMember(json, 'comment', [unicode], description)
+        type = Type.fromJson(mustGetMember(json, 'type', [dict, unicode],
+                                           description),
+                             'type of %s' % description)
+        ephemeral = getMember(json, 'ephemeral', [bool], description)
+        persistent = ephemeral != True
+        return ColumnSchema(comment, type, persistent)
+
+    def toJson(self):
+        d = {"type": self.type.toJson()}
+        if self.persistent == False:
+            d["ephemeral"] = True
+        if self.comment != None:
+            d["comment"] = self.comment
+        return d
+
+class Type:
+    def __init__(self, key, keyRefTable=None, value=None, valueRefTable=None,
+                 min=1, max=1):
+        self.key = key
+        self.keyRefTable = keyRefTable
+        self.value = value
+        self.valueRefTable = valueRefTable
+        self.min = min
+        self.max = max
+    
+    @staticmethod
+    def fromJson(json, description):
+        if type(json) == unicode:
+            return Type(json)
+        else:
+            key = mustGetMember(json, 'key', [unicode], description)
+            keyRefTable = getMember(json, 'keyRefTable', [unicode], description)
+            value = getMember(json, 'value', [unicode], description)
+            valueRefTable = getMember(json, 'valueRefTable', [unicode], description)
+            min = getMember(json, 'min', [int], description, 1)
+            max = getMember(json, 'max', [int, unicode], description, 1)
+            return Type(key, keyRefTable, value, valueRefTable, min, max)
+
+    def toJson(self):
+        if self.value == None and self.min == 1 and self.max == 1:
+            return self.key
+        else:
+            d = {"key": self.key}
+            if self.value != None:
+                d["value"] = self.value
+            if self.min != 1:
+                d["min"] = self.min
+            if self.max != 1:
+                d["max"] = self.max
+            return d
+
+    def isScalar(self):
+        return self.min == 1 and self.max == 1 and not self.value
+
+    def isOptional(self):
+        return self.min == 0 and self.max == 1
+
+    def toEnglish(self):
+        keyName = atomicTypeToEnglish(self.key, self.keyRefTable)
+        if self.value:
+            valueName = atomicTypeToEnglish(self.value, self.valueRefTable)
+
+        if self.isScalar():
+            return atomicTypeToEnglish(self.key, self.keyRefTable)
+        elif self.isOptional():
+            if self.value:
+                return "optional %s-%s pair" % (keyName, valueName)
+            else:
+                return "optional %s" % keyName
+        else:
+            if self.max == "unlimited":
+                if self.min:
+                    quantity = "%d or more " % self.min
+                else:
+                    quantity = ""
+            elif self.min:
+                quantity = "%d to %d " % (self.min, self.max)
+            else:
+                quantity = "up to %d " % self.max
+
+            if self.value:
+                return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
+            else:
+                return "set of %s%s" % (quantity, keyName)
+                
+
+def atomicTypeToEnglish(base, refTable):
+    if base == 'uuid' and refTable:
+        return refTable
+    else:
+        return base
+
+def parseSchema(filename):
+    return DbSchema.fromJson(json.load(open(filename, "r")))
+
+def annotateSchema(schemaFile, annotationFile):
+    schemaJson = json.load(open(schemaFile, "r"))
+    execfile(annotationFile, globals(), {"s": schemaJson})
+    json.dump(schemaJson, sys.stdout)
+
+def cBaseType(prefix, type, refTable=None):
+    if type == 'uuid' and refTable:
+        return "struct %s%s *" % (prefix, refTable.lower())
+    else:
+        return {'integer': 'int64_t ',
+                'real': 'double ',
+                'uuid': 'struct uuid ',
+                'boolean': 'bool ',
+                'string': 'char *'}[type]
+
+def cCopyType(indent, dst, src, type, refTable=None):
+    args = {'indent': indent,
+            'dst': dst,
+            'src': src}
+    if type == 'uuid' and refTable:
+        return ("%(indent)s%(dst)s = %(src)s->header_.uuid;") % args
+    elif type == 'string':
+        return "%(indent)s%(dst)s = xstrdup(%(src)s);" % args
+    else:
+        return "%(indent)s%(dst)s = %(src)s;" % args
+
+def typeIsOptionalPointer(type):
+    return (type.min == 0 and type.max == 1 and not type.value
+            and (type.key == 'string'
+                 or (type.key == 'uuid' and type.keyRefTable)))
+
+def cDeclComment(type):
+    if type.min == 1 and type.max == 1 and type.key == "string":
+        return "\t/* Always nonnull. */"
+    else:
+        return ""
+
+def cInitDefault(var, type, refTable, isOptional):
+    if type == 'uuid' and refTable:
+        return "%s = NULL;" % var
+    elif type == 'string' and not isOptional:
+        return "%s = \"\";" % var
+    else:
+        return {'integer': '%s = 0;',
+                'real': '%s = 0.0;',
+                'uuid': 'uuid_zero(&%s);',
+                'boolean': '%s = false;',
+                'string': '%s = NULL;'}[type] % var
+
+def constify(cType, const):
+    if (const
+        and cType.endswith('*') and not cType.endswith('**')
+        and (cType.startswith('struct uuid') or cType.startswith('char'))):
+        return 'const %s' % cType
+    else:
+        return cType
+
+def cMembers(prefix, columnName, column, const):
+    type = column.type
+    if type.min == 1 and type.max == 1:
+        singleton = True
+        pointer = ''
+    else:
+        singleton = False
+        if typeIsOptionalPointer(type):
+            pointer = ''
+        else:
+            pointer = '*'
+
+    if type.value:
+        key = {'name': "key_%s" % columnName,
+               'type': constify(cBaseType(prefix, type.key, type.keyRefTable) + pointer, const),
+               'comment': ''}
+        value = {'name': "value_%s" % columnName,
+                 'type': constify(cBaseType(prefix, type.value, type.valueRefTable) + pointer, const),
+                 'comment': ''}
+        members = [key, value]
+    else:
+        m = {'name': columnName,
+             'type': constify(cBaseType(prefix, type.key, type.keyRefTable) + pointer, const),
+             'comment': cDeclComment(type)}
+        members = [m]
+
+    if not singleton and not typeIsOptionalPointer(type):
+        members.append({'name': 'n_%s' % columnName,
+                        'type': 'size_t ',
+                        'comment': ''})
+    return members
+
+def printCIDLHeader(schemaFile):
+    schema = parseSchema(schemaFile)
+    prefix = schema.idlPrefix
+    print '''\
+/* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */
+
+#ifndef %(prefix)sIDL_HEADER
+#define %(prefix)sIDL_HEADER 1
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include "ovsdb-idl-provider.h"
+#include "uuid.h"''' % {'prefix': prefix.upper()}
+
+    for tableName, table in schema.tables.iteritems():
+        structName = "%s%s" % (prefix, tableName.lower())
+
+        print "\f"
+        print "/* %s table. */" % tableName
+        print "struct %s {" % structName
+        print "\tstruct ovsdb_idl_row header_;"
+        for columnName, column in table.columns.iteritems():
+            print "\n\t/* %s column. */" % columnName
+            for member in cMembers(prefix, columnName, column, False):
+                print "\t%(type)s%(name)s;%(comment)s" % member
+        print "};"
+
+        # Column indexes.
+        printEnum(["%s_COL_%s" % (structName.upper(), columnName.upper())
+                   for columnName in table.columns]
+                  + ["%s_N_COLUMNS" % structName.upper()])
+
+        print
+        for columnName in table.columns:
+            print "#define %(s)s_col_%(c)s (%(s)s_columns[%(S)s_COL_%(C)s])" % {
+                's': structName,
+                'S': structName.upper(),
+                'c': columnName,
+                'C': columnName.upper()}
+
+        print "\nextern struct ovsdb_idl_column %s_columns[%s_N_COLUMNS];" % (structName, structName.upper())
+
+        print '''
+const struct %(s)s *%(s)s_first(const struct ovsdb_idl *);
+const struct %(s)s *%(s)s_next(const struct %(s)s *);
+#define %(S)s_FOR_EACH(ROW, IDL) for ((ROW) = %(s)s_first(IDL); (ROW); (ROW) = %(s)s_next(ROW))
+
+void %(s)s_delete(const struct %(s)s *);
+struct %(s)s *%(s)s_insert(struct ovsdb_idl_txn *);
+''' % {'s': structName, 'S': structName.upper()}
+
+        for columnName, column in table.columns.iteritems():
+            print 'void %(s)s_verify_%(c)s(const struct %(s)s *);' % {'s': structName, 'c': columnName}
+
+        print
+        for columnName, column in table.columns.iteritems():
+
+            print 'void %(s)s_set_%(c)s(const struct %(s)s *,' % {'s': structName, 'c': columnName},
+            args = ['%(type)s%(name)s' % member for member
+                    in cMembers(prefix, columnName, column, True)]
+            print '%s);' % ', '.join(args)
+
+    # Table indexes.
+    printEnum(["%sTABLE_%s" % (prefix.upper(), tableName.upper()) for tableName in schema.tables] + ["%sN_TABLES" % prefix.upper()])
+    print
+    for tableName in schema.tables:
+        print "#define %(p)stable_%(t)s (%(p)stable_classes[%(P)sTABLE_%(T)s])" % {
+            'p': prefix,
+            'P': prefix.upper(),
+            't': tableName.lower(),
+            'T': tableName.upper()}
+    print "\nextern struct ovsdb_idl_table_class %stable_classes[%sN_TABLES];" % (prefix, prefix.upper())
+
+    print "\nextern struct ovsdb_idl_class %sidl_class;" % prefix
+    print "\n#endif /* %(prefix)sIDL_HEADER */" % {'prefix': prefix.upper()}
+
+def printEnum(members):
+    if len(members) == 0:
+        return
+
+    print "\nenum {";
+    for member in members[:-1]:
+        print "    %s," % member
+    print "    %s" % members[-1]
+    print "};"
+
+def printCIDLSource(schemaFile):
+    schema = parseSchema(schemaFile)
+    prefix = schema.idlPrefix
+    print '''\
+/* Generated automatically -- do not modify!    -*- buffer-read-only: t -*- */
+
+#include <config.h>
+#include %s
+#include <limits.h>
+#include "ovsdb-data.h"''' % schema.idlHeader
+
+    # Cast functions.
+    for tableName, table in schema.tables.iteritems():
+        structName = "%s%s" % (prefix, tableName.lower())
+        print '''
+static struct %(s)s *
+%(s)s_cast(const struct ovsdb_idl_row *row)
+{
+    return row ? CONTAINER_OF(row, struct %(s)s, header_) : NULL;
+}\
+''' % {'s': structName}
+
+
+    for tableName, table in schema.tables.iteritems():
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "\f"
+        if table.comment != None:
+            print "/* %s table (%s). */" % (tableName, table.comment)
+        else:
+            print "/* %s table. */" % (tableName)
+
+        # Parse functions.
+        for columnName, column in table.columns.iteritems():
+            print '''
+static void
+%(s)s_parse_%(c)s(struct ovsdb_idl_row *row_, const struct ovsdb_datum *datum)
+{
+    struct %(s)s *row = %(s)s_cast(row_);''' % {'s': structName,
+                                                'c': columnName}
+
+            type = column.type
+            refKey = type.key == "uuid" and type.keyRefTable
+            refValue = type.value == "uuid" and type.valueRefTable
+            if type.value:
+                keyVar = "row->key_%s" % columnName
+                valueVar = "row->value_%s" % columnName
+            else:
+                keyVar = "row->%s" % columnName
+                valueVar = None
+
+            if ((type.min == 1 and type.max == 1) or
+                typeIsOptionalPointer(type)):
+                print
+                print "    if (datum->n >= 1) {"
+                if not refKey:
+                    print "        %s = datum->keys[0].%s;" % (keyVar, type.key)
+                else:
+                    print "        %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[0].uuid));" % (keyVar, prefix, type.keyRefTable.lower(), prefix, prefix.upper(), type.keyRefTable.upper())
+
+                if valueVar:
+                    if refValue:
+                        print "        %s = datum->values[0].%s;" % (valueVar, type.value)
+                    else:
+                        print "        %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[0].uuid));" % (valueVar, prefix, type.valueRefTable.lower(), prefix, prefix.upper(), type.valueRefTable.upper())
+                print "    } else {"
+                print "        %s" % cInitDefault(keyVar, type.key, type.keyRefTable, type.min == 0)
+                if valueVar:
+                    print "        %s" % cInitDefault(valueVar, type.value, type.valueRefTable, type.min == 0)
+                print "    }"
+            else:
+                if type.max != 'unlimited':
+                    print "    size_t n = MIN(%d, datum->n);" % type.max
+                    nMax = "n"
+                else:
+                    nMax = "datum->n"
+                print "    size_t i;"
+                print
+                print "    %s = NULL;" % keyVar
+                if valueVar:
+                    print "    %s = NULL;" % valueVar
+                print "    row->n_%s = 0;" % columnName
+                print "    for (i = 0; i < %s; i++) {" % nMax
+                refs = []
+                if refKey:
+                    print "        struct %s%s *keyRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[i].uuid));" % (prefix, type.keyRefTable.lower(), prefix, type.keyRefTable.lower(), prefix, prefix.upper(), type.keyRefTable.upper())
+                    keySrc = "keyRow"
+                    refs.append('keyRow')
+                else:
+                    keySrc = "datum->keys[i].%s" % type.key
+                if refValue:
+                    print "        struct %s%s *valueRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[i].uuid));" % (prefix, type.valueRefTable.lower(), prefix, type.valueRefTable.lower(), prefix, prefix.upper(), type.valueRefTable.upper())
+                    valueSrc = "valueRow"
+                    refs.append('valueRow')
+                elif valueVar:
+                    valueSrc = "datum->values[i].%s" % type.value
+                if refs:
+                    print "        if (%s) {" % ' && '.join(refs)
+                    indent = "            "
+                else:
+                    indent = "        "
+                print "%sif (!row->n_%s) {" % (indent, columnName)
+                print "%s    %s = xmalloc(%s * sizeof *%s);" % (indent, keyVar, nMax, keyVar)
+                if valueVar:
+                    print "%s    %s = xmalloc(%s * sizeof %s);" % (indent, valueVar, nMax, valueVar)
+                print "%s}" % indent
+                print "%s%s[row->n_%s] = %s;" % (indent, keyVar, columnName, keySrc)
+                if valueVar:
+                    print "%s%s[row->n_%s] = %s;" % (indent, valueVar, columnName, valueSrc)
+                print "%srow->n_%s++;" % (indent, columnName)
+                if refs:
+                    print "        }"
+                print "    }"
+            print "}"
+
+        # Unparse functions.
+        for columnName, column in table.columns.iteritems():
+            type = column.type
+            if (type.min != 1 or type.max != 1) and not typeIsOptionalPointer(type):
+                print '''
+static void
+%(s)s_unparse_%(c)s(struct ovsdb_idl_row *row_)
+{
+    struct %(s)s *row = %(s)s_cast(row_);
+''' % {'s': structName, 'c': columnName}
+                if type.value:
+                    keyVar = "row->key_%s" % columnName
+                    valueVar = "row->value_%s" % columnName
+                else:
+                    keyVar = "row->%s" % columnName
+                    valueVar = None
+                print "    free(%s);" % keyVar
+                if valueVar:
+                    print "    free(%s);" % valueVar
+                print '}'
+            else:
+                print '''
+static void
+%(s)s_unparse_%(c)s(struct ovsdb_idl_row *row UNUSED)
+{
+    /* Nothing to do. */
+}''' % {'s': structName, 'c': columnName}
+        # First, next functions.
+        print '''
+const struct %(s)s *
+%(s)s_first(const struct ovsdb_idl *idl)
+{
+    return %(s)s_cast(ovsdb_idl_first_row(idl, &%(p)stable_classes[%(P)sTABLE_%(T)s]));
+}
+
+const struct %(s)s *
+%(s)s_next(const struct %(s)s *row)
+{
+    return %(s)s_cast(ovsdb_idl_next_row(&row->header_));
+}''' % {'s': structName,
+        'p': prefix,
+        'P': prefix.upper(),
+        'T': tableName.upper()}
+
+        print '''
+void
+%(s)s_delete(const struct %(s)s *row)
+{
+    ovsdb_idl_txn_delete(&row->header_);
+}
+
+struct %(s)s *
+%(s)s_insert(struct ovsdb_idl_txn *txn)
+{
+    return %(s)s_cast(ovsdb_idl_txn_insert(txn, &%(p)stable_classes[%(P)sTABLE_%(T)s]));
+}
+''' % {'s': structName,
+       'p': prefix,
+       'P': prefix.upper(),
+       'T': tableName.upper()}
+
+        # Verify functions.
+        for columnName, column in table.columns.iteritems():
+            print '''
+void
+%(s)s_verify_%(c)s(const struct %(s)s *row)
+{
+    ovsdb_idl_txn_verify(&row->header_, &%(s)s_columns[%(S)s_COL_%(C)s]);
+}''' % {'s': structName,
+        'S': structName.upper(),
+        'c': columnName,
+        'C': columnName.upper()}
+
+        # Set functions.
+        for columnName, column in table.columns.iteritems():
+            type = column.type
+            print '\nvoid'
+            members = cMembers(prefix, columnName, column, True)
+            keyVar = members[0]['name']
+            nVar = None
+            valueVar = None
+            if type.value:
+                valueVar = members[1]['name']
+                if len(members) > 2:
+                    nVar = members[2]['name']
+            else:
+                if len(members) > 1:
+                    nVar = members[1]['name']
+            print '%(s)s_set_%(c)s(const struct %(s)s *row, %(args)s)' % \
+                {'s': structName, 'c': columnName,
+                 'args': ', '.join(['%(type)s%(name)s' % m for m in members])}
+            print "{"
+            print "    struct ovsdb_datum datum;"
+            if type.min == 1 and type.max == 1:
+                print
+                print "    datum.n = 1;"
+                print "    datum.keys = xmalloc(sizeof *datum.keys);"
+                print cCopyType("    ", "datum.keys[0].%s" % type.key, keyVar, type.key, type.keyRefTable)
+                if type.value:
+                    print "    datum.values = xmalloc(sizeof *datum.values);"
+                    print cCopyType("    ", "datum.values[0].%s" % type.value, valueVar, type.value, type.valueRefTable)
+                else:
+                    print "    datum.values = NULL;"
+            elif typeIsOptionalPointer(type):
+                print
+                print "    if (%s) {" % keyVar
+                print "        datum.n = 1;"
+                print "        datum.keys = xmalloc(sizeof *datum.keys);"
+                print cCopyType("        ", "datum.keys[0].%s" % type.key, keyVar, type.key, type.keyRefTable)
+                print "    } else {"
+                print "        datum.n = 0;"
+                print "        datum.keys = NULL;"
+                print "    }"
+                print "    datum.values = NULL;"
+            else:
+                print "    size_t i;"
+                print
+                print "    datum.n = %s;" % nVar
+                print "    datum.keys = xmalloc(%s * sizeof *datum.keys);" % nVar
+                if type.value:
+                    print "    datum.values = xmalloc(%s * sizeof *datum.values);" % nVar
+                else:
+                    print "    datum.values = NULL;"
+                print "    for (i = 0; i < %s; i++) {" % nVar
+                print cCopyType("        ", "datum.keys[i].%s" % type.key, "%s[i]" % keyVar, type.key, type.keyRefTable)
+                if type.value:
+                    print cCopyType("        ", "datum.values[i].%s" % type.value, "%s[i]" % valueVar, type.value, type.valueRefTable)
+                print "    }"
+            print "    ovsdb_idl_txn_write(&row->header_, &%(s)s_columns[%(S)s_COL_%(C)s], &datum);" \
+                % {'s': structName,
+                   'S': structName.upper(),
+                   'C': columnName.upper()}
+            print "}"
+
+        # Table columns.
+        print "\nstruct ovsdb_idl_column %s_columns[%s_N_COLUMNS] = {" % (
+            structName, structName.upper())
+        for columnName, column in table.columns.iteritems():
+            type = column.type
+            
+            if type.value:
+                valueTypeName = type.value.upper()
+            else:
+                valueTypeName = "VOID"
+            if type.max == "unlimited":
+                max = "UINT_MAX"
+            else:
+                max = type.max
+            print """\
+    {"%(c)s",
+     {OVSDB_TYPE_%(kt)s, OVSDB_TYPE_%(vt)s, %(min)s, %(max)s},
+     %(s)s_parse_%(c)s,
+     %(s)s_unparse_%(c)s},""" % {'c': columnName,
+                                 's': structName,
+                                 'kt': type.key.upper(),
+                                 'vt': valueTypeName,
+                                 'min': type.min,
+                                 'max': max}
+        print "};"
+
+    # Table classes.
+    print "\f"
+    print "struct ovsdb_idl_table_class %stable_classes[%sN_TABLES] = {" % (prefix, prefix.upper())
+    for tableName, table in schema.tables.iteritems():
+        structName = "%s%s" % (prefix, tableName.lower())
+        print "    {\"%s\"," % tableName
+        print "     %s_columns, ARRAY_SIZE(%s_columns)," % (
+            structName, structName)
+        print "     sizeof(struct %s)}," % structName
+    print "};"
+
+    # IDL class.
+    print "\nstruct ovsdb_idl_class %sidl_class = {" % prefix
+    print "    %stable_classes, ARRAY_SIZE(%stable_classes)" % (prefix, prefix)
+    print "};"
+
+def ovsdb_escape(string):
+    def escape(match):
+        c = match.group(0)
+        if c == '\0':
+            raise Error("strings may not contain null bytes")
+        elif c == '\\':
+            return '\\\\'
+        elif c == '\n':
+            return '\\n'
+        elif c == '\r':
+            return '\\r'
+        elif c == '\t':
+            return '\\t'
+        elif c == '\b':
+            return '\\b'
+        elif c == '\a':
+            return '\\a'
+        else:
+            return '\\x%02x' % ord(c)
+    return re.sub(r'["\\\000-\037]', escape, string)
+
+def printDoc(schemaFile):
+    schema = parseSchema(schemaFile)
+    print schema.name
+    if schema.comment:
+        print schema.comment
+
+    for tableName, table in sorted(schema.tables.iteritems()):
+        title = "%s table" % tableName
+        print
+        print title
+        print '-' * len(title)
+        if table.comment:
+            print table.comment
+
+        for columnName, column in sorted(table.columns.iteritems()):
+            print
+            print "%s (%s)" % (columnName, column.type.toEnglish())
+            if column.comment:
+                print "\t%s" % column.comment
+
+def usage():
+    print """\
+%(argv0)s: ovsdb schema compiler
+usage: %(argv0)s [OPTIONS] COMMAND ARG...
+
+The following commands are supported:
+  annotate SCHEMA ANNOTATIONS print SCHEMA combined with ANNOTATIONS
+  c-idl-header IDL            print C header file for IDL
+  c-idl-source IDL            print C source file for IDL implementation
+  doc IDL                     print schema documentation
+
+The following options are also available:
+  -h, --help                  display this help message
+  -V, --version               display version information\
+""" % {'argv0': argv0}
+    sys.exit(0)
+
+if __name__ == "__main__":
+    try:
+        try:
+            options, args = getopt.gnu_getopt(sys.argv[1:], 'C:hV',
+                                              ['directory',
+                                               'help',
+                                               'version'])
+        except getopt.GetoptError, geo:
+            sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
+            sys.exit(1)
+            
+        for key, value in options:
+            if key in ['-h', '--help']:
+                usage()
+            elif key in ['-V', '--version']:
+                print "ovsdb-idlc (Open vSwitch) @VERSION@"
+            elif key in ['-C', '--directory']:
+                os.chdir(value)
+            else:
+                sys.exit(0)
+            
+        optKeys = [key for key, value in options]
+
+        if not args:
+            sys.stderr.write("%s: missing command argument "
+                             "(use --help for help)\n" % argv0)
+            sys.exit(1)
+
+        commands = {"annotate": (annotateSchema, 2),
+                    "c-idl-header": (printCIDLHeader, 1),
+                    "c-idl-source": (printCIDLSource, 1),
+                    "doc": (printDoc, 1)}
+
+        if not args[0] in commands:
+            sys.stderr.write("%s: unknown command \"%s\" "
+                             "(use --help for help)\n" % (argv0, args[0]))
+            sys.exit(1)
+
+        func, n_args = commands[args[0]]
+        if len(args) - 1 != n_args:
+            sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
+                             "provided\n"
+                             % (argv0, args[0], n_args, len(args) - 1))
+            sys.exit(1)
+
+        func(*args[1:])
+    except Error, e:
+        sys.stderr.write("%s: %s\n" % (argv0, e.msg))
+        sys.exit(1)
+
+# Local variables:
+# mode: python
+# End:
diff --git a/ovsdb/ovsdb-server.1.in b/ovsdb/ovsdb-server.1.in
new file mode 100644 (file)
index 0000000..4c6cedd
--- /dev/null
@@ -0,0 +1,50 @@
+.\" -*- nroff -*-
+.TH ovsdb\-server 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-server
+.
+.SH NAME
+ovsdb\-server \- Open vSwitch database server
+.
+.SH SYNOPSIS
+\fBovsdb\-server\fR
+\fIdatabase\fR
+[\fB--remote=\fIremote\fR]\&...
+.so lib/daemon-syn.man
+.so lib/vlog-syn.man
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovsdb\-server\fR program provides RPC interfaces to an Open
+vSwitch database (OVSDB).  It supports JSON-RPC client connections
+over active or passive TCP/IP or Unix domain sockets.
+.PP
+The name of the OVSDB file must be specified on the command line as
+\fIdatabase\fR, which must already have been created and initialized
+using, for example, \fBovsdb\-tool create\fR.
+.
+.SH OPTIONS
+.
+.IP "\fB\-\-remote=\fIremote\fR"
+Adds \fIremote\fR as a connection method used by \fBovsdb\-server\fR.
+\fIremote\fR must take one of the following forms:
+.
+.RS
+.so ovsdb/remote-passive.man
+.so ovsdb/remote-active.man
+.RE
+.
+.SS "Daemon Options"
+.so lib/daemon.man
+.SS "Logging Options"
+.so lib/vlog.man
+.so lib/ssl.man
+.SS "Other Options"
+.so lib/common.man
+.SH "RUNTIME MANAGEMENT COMMANDS"
+\fBovs\-appctl\fR(8) can send commands to a running
+\fBovsdb\-server\fR process.  The currently supported commands are
+described below.
+.so lib/vlog-unixctl.man
+.SH "SEE ALSO"
+.
+.BR ovsdb\-tool (1).
diff --git a/ovsdb/ovsdb-server.c b/ovsdb/ovsdb-server.c
new file mode 100644 (file)
index 0000000..ab7e6c3
--- /dev/null
@@ -0,0 +1,308 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include "column.h"
+#include "command-line.h"
+#include "daemon.h"
+#include "file.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "jsonrpc-server.h"
+#include "leak-checker.h"
+#include "list.h"
+#include "ovsdb-data.h"
+#include "ovsdb-types.h"
+#include "ovsdb-error.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "row.h"
+#include "stream-ssl.h"
+#include "stream.h"
+#include "svec.h"
+#include "table.h"
+#include "timeval.h"
+#include "trigger.h"
+#include "util.h"
+#include "unixctl.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_server
+
+static unixctl_cb_func ovsdb_server_exit;
+
+static void parse_options(int argc, char *argv[], char **file_namep,
+                          struct shash *remotes, char **unixctl_pathp);
+static void usage(void) NO_RETURN;
+
+static void set_remotes(struct ovsdb_jsonrpc_server *jsonrpc,
+                        const struct ovsdb *db, struct shash *remotes);
+
+int
+main(int argc, char *argv[])
+{
+    char *unixctl_path = NULL;
+    struct unixctl_server *unixctl;
+    struct ovsdb_jsonrpc_server *jsonrpc;
+    struct shash remotes;
+    struct ovsdb_error *error;
+    struct ovsdb *db;
+    char *file_name;
+    bool exiting;
+    int retval;
+
+    proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    signal(SIGPIPE, SIG_IGN);
+    process_init();
+
+    parse_options(argc, argv, &file_name, &remotes, &unixctl_path);
+
+    die_if_already_running();
+    daemonize_start();
+
+    error = ovsdb_file_open(file_name, false, &db);
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+
+    jsonrpc = ovsdb_jsonrpc_server_create(db);
+    set_remotes(jsonrpc, db, &remotes);
+
+    retval = unixctl_server_create(unixctl_path, &unixctl);
+    if (retval) {
+        exit(EXIT_FAILURE);
+    }
+
+    daemonize_complete();
+
+    unixctl_command_register("exit", ovsdb_server_exit, &exiting);
+
+    exiting = false;
+    while (!exiting) {
+        set_remotes(jsonrpc, db, &remotes);
+        ovsdb_jsonrpc_server_run(jsonrpc);
+        unixctl_server_run(unixctl);
+        ovsdb_trigger_run(db, time_msec());
+
+        ovsdb_jsonrpc_server_wait(jsonrpc);
+        unixctl_server_wait(unixctl);
+        ovsdb_trigger_wait(db, time_msec());
+        poll_block();
+    }
+    ovsdb_jsonrpc_server_destroy(jsonrpc);
+    ovsdb_destroy(db);
+    shash_destroy(&remotes);
+    unixctl_server_destroy(unixctl);
+
+    return 0;
+}
+
+static void
+query_db_remotes(const char *name_, const struct ovsdb *db,
+                 struct shash *remotes)
+{
+    char *name, *db_prefix, *table_name, *column_name;
+    const struct ovsdb_column *column;
+    const struct ovsdb_table *table;
+    const struct ovsdb_row *row;
+    char *save_ptr = NULL;
+
+    name = xstrdup(name_);
+    db_prefix = strtok_r(name, ":", &save_ptr);
+    table_name = strtok_r(NULL, ",", &save_ptr);
+    column_name = strtok_r(NULL, ",", &save_ptr);
+    if (!table_name || !column_name) {
+        ovs_fatal(0, "remote \"%s\": invalid syntax", name_);
+    }
+
+    table = ovsdb_get_table(db, table_name);
+    if (!table) {
+        ovs_fatal(0, "remote \"%s\": no table named %s", name_, table_name);
+    }
+
+    column = ovsdb_table_schema_get_column(table->schema, column_name);
+    if (!column) {
+        ovs_fatal(0, "remote \"%s\": table \"%s\" has no column \"%s\"",
+                  name_, table_name, column_name);
+    }
+
+    if (column->type.key_type != OVSDB_TYPE_STRING
+        || column->type.value_type != OVSDB_TYPE_VOID) {
+        ovs_fatal(0, "remote \"%s\": type of table \"%s\" column \"%s\" is "
+                  "not string or set of strings",
+                  name_, table_name, column_name);
+    }
+
+    HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node, &table->rows) {
+        const struct ovsdb_datum *datum;
+        size_t i;
+
+        datum = &row->fields[column->index];
+        for (i = 0; i < datum->n; i++) {
+            shash_add_once(remotes, datum->keys[i].string, NULL);
+        }
+    }
+
+    free(name);
+}
+
+static void
+set_remotes(struct ovsdb_jsonrpc_server *jsonrpc,
+            const struct ovsdb *db, struct shash *remotes)
+{
+    struct shash resolved_remotes;
+    struct shash_node *node;
+
+    shash_init(&resolved_remotes);
+    SHASH_FOR_EACH (node, remotes) {
+        const char *name = node->name;
+
+        if (!strncmp(name, "db:", 3)) {
+            query_db_remotes(name, db, &resolved_remotes);
+        } else {
+            shash_add_once(&resolved_remotes, name, NULL);
+        }
+    }
+    ovsdb_jsonrpc_server_set_remotes(jsonrpc, &resolved_remotes);
+    shash_destroy(&resolved_remotes);
+}
+
+
+static void
+ovsdb_server_exit(struct unixctl_conn *conn, const char *args UNUSED,
+                  void *exiting_)
+{
+    bool *exiting = exiting_;
+    *exiting = true;
+    unixctl_command_reply(conn, 200, NULL);
+}
+
+static void
+parse_options(int argc, char *argv[], char **file_namep,
+              struct shash *remotes, char **unixctl_pathp)
+{
+    enum {
+        OPT_DUMMY = UCHAR_MAX + 1,
+        OPT_REMOTE,
+        OPT_UNIXCTL,
+        OPT_BOOTSTRAP_CA_CERT,
+        VLOG_OPTION_ENUMS,
+        LEAK_CHECKER_OPTION_ENUMS
+    };
+    static struct option long_options[] = {
+        {"remote",      required_argument, 0, OPT_REMOTE},
+        {"unixctl",     required_argument, 0, OPT_UNIXCTL},
+        {"help",        no_argument, 0, 'h'},
+        {"version",     no_argument, 0, 'V'},
+        DAEMON_LONG_OPTIONS,
+        VLOG_LONG_OPTIONS,
+        LEAK_CHECKER_LONG_OPTIONS,
+#ifdef HAVE_OPENSSL
+        {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
+        STREAM_SSL_LONG_OPTIONS
+#endif
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    shash_init(remotes);
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_REMOTE:
+            shash_add_once(remotes, optarg, NULL);
+            break;
+
+        case OPT_UNIXCTL:
+            *unixctl_pathp = optarg;
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        VLOG_OPTION_HANDLERS
+        DAEMON_OPTION_HANDLERS
+        LEAK_CHECKER_OPTION_HANDLERS
+
+#ifdef HAVE_OPENSSL
+        STREAM_SSL_OPTION_HANDLERS
+
+        case OPT_BOOTSTRAP_CA_CERT:
+            stream_ssl_set_ca_cert_file(optarg, true);
+            break;
+#endif
+
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+
+    argc -= optind;
+    argv += optind;
+
+    if (argc > 1) {
+        ovs_fatal(0, "database file is only non-option argument; "
+                "use --help for usage");
+    } else if (argc < 1) {
+        ovs_fatal(0, "missing database file argument; use --help for usage");
+    }
+
+    *file_namep = argv[0];
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database server\n"
+           "usage: %s [OPTIONS] DATABASE\n"
+           "where DATABASE is a database file in ovsdb format.\n",
+           program_name, program_name);
+    printf("\nJSON-RPC options (may be specified any number of times):\n"
+           "  --remote=REMOTE         connect or listen to REMOTE\n");
+    stream_usage("JSON-RPC", true, true, true);
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help              display this help message\n"
+           "  -V, --version           display version information\n");
+    leak_checker_usage();
+    exit(EXIT_SUCCESS);
+}
diff --git a/ovsdb/ovsdb-tool.1.in b/ovsdb/ovsdb-tool.1.in
new file mode 100644 (file)
index 0000000..04e6296
--- /dev/null
@@ -0,0 +1,88 @@
+.\" -*- nroff -*-
+.de IQ
+.  br
+.  ns
+.  IP "\\$1"
+..
+.\" -*- nroff -*-
+.TH ovsdb\-tool 1 "November 2009" "Open vSwitch" "Open vSwitch Manual"
+.ds PN ovsdb\-tool
+.
+.SH NAME
+ovsdb\-tool \- Open vSwitch database management utility
+.
+.SH SYNOPSIS
+\fBovsdb\-tool \fR[\fIoptions\fR] \fBcreate\fI db schema\fR
+.br
+\fBovsdb\-tool \fR[\fIoptions\fR] \fBquery\fI db transaction\fR
+.br
+\fBovsdb\-tool \fR[\fIoptions\fR] \fBtransact\fI db transaction\fR
+.br
+\fBovsdb\-tool \fR[\fIoptions\fR] [\fB-m\fR | \fB--more\fR]... \fBshow\-log\fI db\fR
+.br
+\fBovsdb\-tool help\fR
+.so lib/vlog-syn.man
+.so lib/common-syn.man
+.
+.SH DESCRIPTION
+The \fBovsdb\-tool\fR program is a command-line tool for managing Open
+vSwitch database (OVSDB) files.  It does not interact directly with
+running Open vSwitch database servers (instead, use
+\fBovsdb\-client\fR).
+.
+.SS "Basic Commands"
+.IP "\fBcreate\fI db schema\fR"
+Reads an OVSDB schema from the file named \fIschema\fR and creates a
+new OVSDB database file named \fIdb\fR using that schema.  The new
+database is initially empty.  This command will not overwrite an
+existing \fIdb\fR.
+.IP
+\fIschema\fR must contain an OVSDB schema in JSON format.  Refer to
+the OVSDB specification for details.
+.
+.IP "\fBquery\fI db transaction\fR"
+Opens \fIdb\fR, executes \fItransaction\fR on it, and prints the
+results.  The \fItransaction\fR must be a JSON array in the format of
+the \fBparams\fR array for the JSON-RPC \fBtransact\fR method, as
+described in the OVSDB specification.
+.IP
+The \fIdb\fR is opened for read-only access, so this command may
+safely run concurrently with other database activity, including
+\fBovsdb-server\fR and other database writers.  The \fItransaction\fR
+may specify database modifications, but these will have no effect on
+\fIdb\fR.
+.
+.IP "\fBtransact\fI db transaction\fR"
+Opens \fIdb\fR, executes \fItransaction\fR on it, prints the results,
+and commits any changes to \fIdb\fR.  The \fItransaction\fR must be a
+JSON array in the format of the \fBparams\fR array for the JSON-RPC
+\fBtransact\fR method, as described in the OVSDB specification.
+.IP
+The \fIdb\fR is opened and locked for read/write access, so this
+command will fail if the database is opened for writing by any other
+process, including \fBovsdb-server\fR(1).  Use \fBovsdb\-client\fR(1),
+instead, to write to a database that is served by
+\fBovsdb-server\fR(1).
+.
+.IP "\fBshow-log\fI db\fR"
+Prints a summary of the records in \fBdb\fR's log, including the time
+and date at which each database change occurred and any associated
+comment.  This may be useful for debugging.
+.PP
+To increase the verbosity of output, add \fB-m\fR (or \fB--more\fR)
+one or more times to the command line.  With one \fB-m\fR,
+\fBshow\-log\fR prints a summary of the records added, deleted, or
+modified by each transaction.  With two \fB-m\fRs, \fBshow\-log\fR
+also prints the values of the columns modified by each change to a
+record.
+.
+.SH OPTIONS
+.SS "Logging Options"
+.so lib/vlog.man
+.SS "Other Options"
+.so lib/common.man
+.SH "SEE ALSO"
+.
+\fBovsdb\-server\fR(1),
+\fBovsdb\-client\fR(1),
+and the OVSDB specification.
diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
new file mode 100644 (file)
index 0000000..1f2a5ef
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "file.h"
+#include "log.h"
+#include "json.h"
+#include "ovsdb.h"
+#include "ovsdb-error.h"
+#include "table.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_tool
+
+/* -m, --more: Verbosity level for "show-log" command output. */
+static int show_log_verbosity;
+
+static const struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    signal(SIGPIPE, SIG_IGN);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"more", no_argument, 0, 'm'},
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {"version", no_argument, 0, 'V'},
+        {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 'm':
+            show_log_verbosity++;
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database management utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+           "  create DB SCHEMA   create DB with the given SCHEMA\n"
+           "  compact DB [DST]   compact DB in-place (or to DST)\n"
+           "  extract-schema DB  print DB's schema on stdout\n"
+           "  query DB TRNS      execute read-only transaction on DB\n"
+           "  transact DB TRNS   execute read/write transaction on DB\n"
+           "  show-log DB        prints information about DB's log entries\n",
+           program_name, program_name);
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -m, --more                  increase show-log verbosity\n"
+           "  -h, --help                  display this help message\n"
+           "  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+    }
+}
+\f
+static void
+do_create(int argc UNUSED, char *argv[])
+{
+    const char *db_file_name = argv[1];
+    const char *schema_file_name = argv[2];
+    struct ovsdb_schema *schema;
+    struct ovsdb_log *log;
+    struct json *json;
+
+    /* Read schema from file and convert to JSON. */
+    check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema));
+    json = ovsdb_schema_to_json(schema);
+    ovsdb_schema_destroy(schema);
+
+    /* Create database file. */
+    check_ovsdb_error(ovsdb_log_open(db_file_name, O_RDWR | O_CREAT | O_EXCL,
+                                     &log));
+    check_ovsdb_error(ovsdb_log_write(log, json));
+    check_ovsdb_error(ovsdb_log_commit(log));
+    ovsdb_log_close(log);
+
+    json_destroy(json);
+}
+
+static void
+transact(bool read_only, const char *db_file_name, const char *transaction)
+{
+    struct json *request, *result;
+    struct ovsdb *db;
+
+    check_ovsdb_error(ovsdb_file_open(db_file_name, read_only, &db));
+
+    request = parse_json(transaction);
+    result = ovsdb_execute(db, request, 0, NULL);
+    json_destroy(request);
+
+    print_and_free_json(result);
+    ovsdb_destroy(db);
+}
+
+static void
+do_query(int argc UNUSED, char *argv[])
+{
+    transact(true, argv[1], argv[2]);
+}
+
+static void
+do_transact(int argc UNUSED, char *argv[])
+{
+    transact(false, argv[1], argv[2]);
+}
+
+static void
+print_db_changes(struct shash *tables, struct shash *names)
+{
+    struct shash_node *n1;
+
+    SHASH_FOR_EACH (n1, tables) {
+        const char *table = n1->name;
+        struct json *rows = n1->data;
+        struct shash_node *n2;
+
+        if (n1->name[0] == '_' || rows->type != JSON_OBJECT) {
+            continue;
+        }
+
+        SHASH_FOR_EACH (n2, json_object(rows)) {
+            const char *row_uuid = n2->name;
+            struct json *columns = n2->data;
+            struct shash_node *n3;
+            char *old_name, *new_name;
+            bool free_new_name = false;
+
+            old_name = new_name = shash_find_data(names, row_uuid);
+            if (columns->type == JSON_OBJECT) {
+                struct json *new_name_json;
+
+                new_name_json = shash_find_data(json_object(columns), "name");
+                if (new_name_json) {
+                    new_name = json_to_string(new_name_json, JSSF_SORT);
+                    free_new_name = true;
+                }
+            }
+
+            printf("\ttable %s", table);
+
+            if (!old_name) {
+                if (new_name) {
+                    printf(" insert row %s:\n", new_name);
+                } else {
+                    printf(" insert row %.8s:\n", row_uuid);
+                }
+            } else {
+                printf(" row %s:\n", old_name);
+            }
+
+            if (columns->type == JSON_OBJECT) {
+                if (show_log_verbosity > 1) {
+                    SHASH_FOR_EACH (n3, json_object(columns)) {
+                        const char *column = n3->name;
+                        struct json *value = n3->data;
+                        char *value_string;
+
+                        value_string = json_to_string(value, JSSF_SORT);
+                        printf("\t\t%s=%s\n", column, value_string);
+                        free(value_string);
+                    }
+                }
+                if (!old_name
+                    || (new_name != old_name && strcmp(old_name, new_name))) {
+                    if (old_name) {
+                        shash_delete(names, shash_find(names, row_uuid));
+                        free(old_name);
+                    }
+                    shash_add(names, row_uuid, (new_name
+                                                ? xstrdup(new_name)
+                                                : xmemdup0(row_uuid, 8)));
+                }
+            } else if (columns->type == JSON_NULL) {
+                printf("\t\tdelete row\n");
+                shash_delete(names, shash_find(names, row_uuid));
+                free(old_name);
+            }
+
+            if (free_new_name) {
+                free(new_name);
+            }
+        }
+    }
+}
+
+static void
+do_show_log(int argc UNUSED, char *argv[])
+{
+    const char *db_file_name = argv[1];
+    struct shash names;
+    struct ovsdb_log *log;
+    unsigned int i;
+
+    check_ovsdb_error(ovsdb_log_open(db_file_name, O_RDONLY, &log));
+    shash_init(&names);
+    for (i = 0; ; i++) {
+        struct json *json;
+
+        check_ovsdb_error(ovsdb_log_read(log, &json));
+        if (!json) {
+            break;
+        }
+
+        printf("record %u:", i);
+        if (json->type == JSON_OBJECT) {
+            struct json *date, *comment;
+
+            date = shash_find_data(json_object(json), "_date");
+            if (date && date->type == JSON_INTEGER) {
+                time_t t = json_integer(date);
+                char s[128];
+
+                strftime(s, sizeof s, "%Y-%m-%d %H:%M:%S", localtime(&t));
+                printf(" %s", s);
+            }
+
+            comment = shash_find_data(json_object(json), "_comment");
+            if (comment && comment->type == JSON_STRING) {
+                printf(" \"%s\"", json_string(comment));
+            }
+
+            if (i > 0 && show_log_verbosity > 0) {
+                putchar('\n');
+                print_db_changes(json_object(json), &names);
+            }
+        }
+        json_destroy(json);
+        putchar('\n');
+    }
+
+    /* XXX free 'names'. */
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+    usage();
+}
+
+static const struct command all_commands[] = {
+    { "create", 2, 2, do_create },
+    { "query", 2, 2, do_query },
+    { "transact", 2, 2, do_transact },
+    { "show-log", 1, 1, do_show_log },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c
new file mode 100644 (file)
index 0000000..e95d23c
--- /dev/null
@@ -0,0 +1,227 @@
+/* Copyright (c) 2009, 2010 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "ovsdb.h"
+
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "table.h"
+#include "transaction.h"
+
+struct ovsdb_schema *
+ovsdb_schema_create(const char *name, const char *comment)
+{
+    struct ovsdb_schema *schema;
+
+    schema = xzalloc(sizeof *schema);
+    schema->name = xstrdup(name);
+    schema->comment = comment ? xstrdup(comment) : NULL;
+    shash_init(&schema->tables);
+
+    return schema;
+}
+
+void
+ovsdb_schema_destroy(struct ovsdb_schema *schema)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &schema->tables) {
+        ovsdb_table_schema_destroy(node->data);
+    }
+    shash_destroy(&schema->tables);
+    free(schema->comment);
+    free(schema->name);
+    free(schema);
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap)
+{
+    struct ovsdb_schema *schema;
+    struct ovsdb_error *error;
+    struct json *json;
+
+    *schemap = NULL;
+    json = json_from_file(file_name);
+    if (json->type == JSON_STRING) {
+        error = ovsdb_error("failed to read schema",
+                           "\"%s\" could not be read as JSON (%s)",
+                           file_name, json_string(json));
+        json_destroy(json);
+        return error;
+    }
+
+    error = ovsdb_schema_from_json(json, &schema);
+    json_destroy(json);
+    if (error) {
+        return ovsdb_wrap_error(error,
+                                "failed to parse \"%s\" as ovsdb schema",
+                                file_name);
+    }
+
+    *schemap = schema;
+    return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap)
+{
+    struct ovsdb_schema *schema;
+    const struct json *name, *comment, *tables;
+    struct ovsdb_error *error;
+    struct shash_node *node;
+    struct ovsdb_parser parser;
+
+    *schemap = NULL;
+
+    ovsdb_parser_init(&parser, json, "database schema");
+    name = ovsdb_parser_member(&parser, "name", OP_ID);
+    comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+    tables = ovsdb_parser_member(&parser, "tables", OP_OBJECT);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    schema = ovsdb_schema_create(json_string(name),
+                                 comment ? json_string(comment) : NULL);
+    SHASH_FOR_EACH (node, json_object(tables)) {
+        struct ovsdb_table_schema *table;
+
+        if (node->name[0] == '_') {
+            error = ovsdb_syntax_error(json, NULL, "names beginning with "
+                                       "\"_\" are reserved");
+        } else if (!ovsdb_parser_is_id(node->name)) {
+            error = ovsdb_syntax_error(json, NULL, "name must be a valid id");
+        } else {
+            error = ovsdb_table_schema_from_json(node->data, node->name,
+                                                 &table);
+        }
+        if (error) {
+            ovsdb_schema_destroy(schema);
+            return error;
+        }
+
+        shash_add(&schema->tables, table->name, table);
+    }
+    *schemap = schema;
+    return 0;
+}
+
+struct json *
+ovsdb_schema_to_json(const struct ovsdb_schema *schema)
+{
+    struct json *json, *tables;
+    struct shash_node *node;
+
+    json = json_object_create();
+    json_object_put_string(json, "name", schema->name);
+    if (schema->comment) {
+        json_object_put_string(json, "comment", schema->comment);
+    }
+
+    tables = json_object_create();
+
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *table = node->data;
+        json_object_put(tables, table->name,
+                        ovsdb_table_schema_to_json(table));
+    }
+    json_object_put(json, "tables", tables);
+
+    return json;
+}
+\f
+struct ovsdb *
+ovsdb_create(struct ovsdb_schema *schema)
+{
+    struct shash_node *node;
+    struct ovsdb *db;
+
+    db = xmalloc(sizeof *db);
+    db->schema = schema;
+    list_init(&db->replicas);
+    list_init(&db->triggers);
+    db->run_triggers = false;
+
+    shash_init(&db->tables);
+    SHASH_FOR_EACH (node, &schema->tables) {
+        struct ovsdb_table_schema *ts = node->data;
+        shash_add(&db->tables, node->name, ovsdb_table_create(ts));
+    }
+
+    return db;
+}
+
+void
+ovsdb_destroy(struct ovsdb *db)
+{
+    if (db) {
+        struct shash_node *node;
+
+        /* Remove all the replicas. */
+        while (!list_is_empty(&db->replicas)) {
+            struct ovsdb_replica *r
+                = CONTAINER_OF(list_pop_back(&db->replicas),
+                               struct ovsdb_replica, node);
+            ovsdb_remove_replica(db, r);
+        }
+
+        /* Delete all the tables.  This also deletes their schemas. */
+        SHASH_FOR_EACH (node, &db->tables) {
+            struct ovsdb_table *table = node->data;
+            ovsdb_table_destroy(table);
+        }
+        shash_destroy(&db->tables);
+
+        /* The schemas, but not the table that points to them, were deleted in
+         * the previous step, so we need to clear out the table.  We can't
+         * destroy the table, because ovsdb_schema_destroy() will do that. */
+        shash_clear(&db->schema->tables);
+
+        ovsdb_schema_destroy(db->schema);
+        free(db);
+    }
+}
+
+struct ovsdb_table *
+ovsdb_get_table(const struct ovsdb *db, const char *name)
+{
+    return shash_find_data(&db->tables, name);
+}
+\f
+void
+ovsdb_replica_init(struct ovsdb_replica *r,
+                   const struct ovsdb_replica_class *class)
+{
+    r->class = class;
+}
+
+void
+ovsdb_add_replica(struct ovsdb *db, struct ovsdb_replica *r)
+{
+    list_push_back(&db->replicas, &r->node);
+}
+
+void
+ovsdb_remove_replica(struct ovsdb *db UNUSED, struct ovsdb_replica *r)
+{
+    list_remove(&r->node);
+    (r->class->destroy)(r);
+}
diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h
new file mode 100644 (file)
index 0000000..24ebd9c
--- /dev/null
@@ -0,0 +1,91 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_OVSDB_H
+#define OVSDB_OVSDB_H 1
+
+#include "compiler.h"
+#include "hmap.h"
+#include "list.h"
+#include "shash.h"
+
+struct json;
+struct ovsdb_log;
+struct ovsdb_txn;
+struct uuid;
+
+/* Database schema. */
+struct ovsdb_schema {
+    char *name;
+    char *comment;
+    struct shash tables;        /* Contains "struct ovsdb_table_schema *"s. */
+};
+
+struct ovsdb_schema *ovsdb_schema_create(const char *name,
+                                         const char *comment);
+void ovsdb_schema_destroy(struct ovsdb_schema *);
+
+struct ovsdb_error *ovsdb_schema_from_file(const char *file_name,
+                                           struct ovsdb_schema **)
+    WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_schema_from_json(struct json *,
+                                           struct ovsdb_schema **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_schema_to_json(const struct ovsdb_schema *);
+\f
+/* Database. */
+struct ovsdb {
+    struct ovsdb_schema *schema;
+    struct list replicas;       /* Contains "struct ovsdb_replica"s. */
+    struct shash tables;        /* Contains "struct ovsdb_table *"s. */
+
+    /* Triggers. */
+    struct list triggers;       /* Contains "struct ovsdb_trigger"s. */
+    bool run_triggers;
+};
+
+struct ovsdb *ovsdb_create(struct ovsdb_schema *);
+void ovsdb_destroy(struct ovsdb *);
+
+struct ovsdb_error *ovsdb_from_json(const struct json *, struct ovsdb **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_to_json(const struct ovsdb *);
+
+struct ovsdb_table *ovsdb_get_table(const struct ovsdb *, const char *);
+
+struct json *ovsdb_execute(struct ovsdb *, const struct json *params,
+                           long long int elapsed_msec,
+                           long long int *timeout_msec);
+\f
+/* Database replication. */
+
+struct ovsdb_replica {
+    struct list node;           /* Element in "struct ovsdb" replicas list. */
+    const struct ovsdb_replica_class *class;
+};
+
+struct ovsdb_replica_class {
+    struct ovsdb_error *(*commit)(struct ovsdb_replica *,
+                                  const struct ovsdb_txn *, bool durable);
+    void (*destroy)(struct ovsdb_replica *);
+};
+
+void ovsdb_replica_init(struct ovsdb_replica *,
+                        const struct ovsdb_replica_class *);
+
+void ovsdb_add_replica(struct ovsdb *, struct ovsdb_replica *);
+void ovsdb_remove_replica(struct ovsdb *, struct ovsdb_replica *);
+
+#endif /* ovsdb/ovsdb.h */
diff --git a/ovsdb/query.c b/ovsdb/query.c
new file mode 100644 (file)
index 0000000..878ac5b
--- /dev/null
@@ -0,0 +1,99 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "query.h"
+
+#include "column.h"
+#include "condition.h"
+#include "row.h"
+#include "table.h"
+
+void
+ovsdb_query(struct ovsdb_table *table, const struct ovsdb_condition *cnd,
+            bool (*output_row)(const struct ovsdb_row *, void *aux), void *aux)
+{
+    if (cnd->n_clauses > 0
+        && cnd->clauses[0].column->index == OVSDB_COL_UUID
+        && cnd->clauses[0].function == OVSDB_F_EQ) {
+        /* Optimize the case where the query has a clause of the form "uuid ==
+         * <some-uuid>", since we have an index on UUID. */
+        const struct ovsdb_row *row;
+
+        row = ovsdb_table_get_row(table, &cnd->clauses[0].arg.keys[0].uuid);
+        if (row && row->table == table && ovsdb_condition_evaluate(row, cnd)) {
+            output_row(row, aux);
+        }
+    } else {
+        /* Linear scan. */
+        const struct ovsdb_row *row, *next;
+
+        HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node,
+                            &table->rows) {
+            if (ovsdb_condition_evaluate(row, cnd) && !output_row(row, aux)) {
+                break;
+            }
+        }
+    }
+}
+
+static bool
+query_row_set_cb(const struct ovsdb_row *row, void *results_)
+{
+    struct ovsdb_row_set *results = results_;
+    ovsdb_row_set_add_row(results, row);
+    return true;
+}
+
+void
+ovsdb_query_row_set(struct ovsdb_table *table,
+                    const struct ovsdb_condition *condition,
+                    struct ovsdb_row_set *results)
+{
+    ovsdb_query(table, condition, query_row_set_cb, results);
+}
+
+static bool
+query_distinct_cb(const struct ovsdb_row *row, void *hash_)
+{
+    struct ovsdb_row_hash *hash = hash_;
+    ovsdb_row_hash_insert(hash, row);
+    return true;
+}
+
+void
+ovsdb_query_distinct(struct ovsdb_table *table,
+                     const struct ovsdb_condition *condition,
+                     const struct ovsdb_column_set *columns,
+                     struct ovsdb_row_set *results)
+{
+    if (!columns || ovsdb_column_set_contains(columns, OVSDB_COL_UUID)) {
+        /* All the result rows are guaranteed to be distinct anyway. */
+        return ovsdb_query_row_set(table, condition, results);
+    } else {
+        /* Use hash table to drop duplicates. */
+        struct ovsdb_row_hash_node *node;
+        struct ovsdb_row_hash hash;
+
+        ovsdb_row_hash_init(&hash, columns);
+        ovsdb_query(table, condition, query_distinct_cb, &hash);
+        HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node,
+                       &hash.rows) {
+            ovsdb_row_set_add_row(results, node->row);
+        }
+        ovsdb_row_hash_destroy(&hash, false);
+    }
+}
diff --git a/ovsdb/query.h b/ovsdb/query.h
new file mode 100644 (file)
index 0000000..f5cfe2e
--- /dev/null
@@ -0,0 +1,37 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_QUERY_H
+#define OVSDB_QUERY_H 1
+
+#include <stdbool.h>
+
+struct ovsdb_column_set;
+struct ovsdb_condition;
+struct ovsdb_row;
+struct ovsdb_row_set;
+struct ovsdb_table;
+struct ovsdb_txn;
+
+void ovsdb_query(struct ovsdb_table *, const struct ovsdb_condition *,
+                 bool (*output_row)(const struct ovsdb_row *, void *aux),
+                 void *aux);
+void ovsdb_query_row_set(struct ovsdb_table *, const struct ovsdb_condition *,
+                         struct ovsdb_row_set *);
+void ovsdb_query_distinct(struct ovsdb_table *, const struct ovsdb_condition *,
+                          const struct ovsdb_column_set *,
+                          struct ovsdb_row_set *);
+
+#endif /* ovsdb/query.h */
diff --git a/ovsdb/remote-active.man b/ovsdb/remote-active.man
new file mode 100644 (file)
index 0000000..a934cf0
--- /dev/null
@@ -0,0 +1,11 @@
+.IP "\fBssl:\fIip\fB:\fIport\fR"
+The specified SSL \fIport\fR on the host at the given \fIip\fR, which
+must be expressed as an IP address (not a DNS name).  The
+\fB\-\-private\-key\fR, \fB\-\-certificate\fR, and \fB\-\-ca\-cert\fR
+options are mandatory when this form is used.
+.
+.IP "\fBtcp:\fIip\fB:\fIport\fR"
+Connect to the given TCP \fIport\fR on \fIip\fR.
+.
+.IP "\fBunix:\fIfile\fR"
+Connect to the Unix domain server socket named \fIfile\fR.
diff --git a/ovsdb/remote-passive.man b/ovsdb/remote-passive.man
new file mode 100644 (file)
index 0000000..b313e92
--- /dev/null
@@ -0,0 +1,16 @@
+.IP "\fBpssl:\fIport\fR[\fB:\fIip\fR]"
+Listen on the given SSL \fIport\fR for a connection.  By default,
+\fB\*(PN\fR listens for connections to any local IP address, but
+specifying \fIip\fR limits connections to those from the given
+\fIip\fR.  The \fB\-\-private\-key\fR, \fB\-\-certificate\fR, and
+\fB\-\-ca\-cert\fR options are mandatory when this form is used.
+.
+.IP "\fBptcp:\fIport\fR[\fB:\fIip\fR]"
+Listen on the given TCP \fIport\fR for a connection.  By default,
+\fB\*(PN\fR listens for connections to any local IP address, but
+\fIip\fR may be specified to listen only for connections to the given
+\fIip\fR.
+.
+.IP "\fBpunix:\fIfile\fR"
+Listen on the Unix domain server socket named \fIfile\fR for a
+connection.
diff --git a/ovsdb/row.c b/ovsdb/row.c
new file mode 100644 (file)
index 0000000..1b81942
--- /dev/null
@@ -0,0 +1,386 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "row.h"
+
+#include <assert.h>
+#include <stddef.h>
+
+#include "json.h"
+#include "ovsdb-error.h"
+#include "shash.h"
+#include "sort.h"
+#include "table.h"
+
+static struct ovsdb_row *
+allocate_row(const struct ovsdb_table *table)
+{
+    size_t n_fields = shash_count(&table->schema->columns);
+    size_t row_size = (offsetof(struct ovsdb_row, fields)
+                       + sizeof(struct ovsdb_datum) * n_fields);
+    struct ovsdb_row *row = xmalloc(row_size);
+    row->table = (struct ovsdb_table *) table;
+    row->txn_row = NULL;
+    return row;
+}
+
+struct ovsdb_row *
+ovsdb_row_create(const struct ovsdb_table *table)
+{
+    struct shash_node *node;
+    struct ovsdb_row *row;
+
+    row = allocate_row(table);
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_datum_init_default(&row->fields[column->index], &column->type);
+    }
+    return row;
+}
+
+struct ovsdb_row *
+ovsdb_row_clone(const struct ovsdb_row *old)
+{
+    const struct ovsdb_table *table = old->table;
+    const struct shash_node *node;
+    struct ovsdb_row *new;
+
+    new = allocate_row(table);
+    SHASH_FOR_EACH (node, &table->schema->columns) {
+        const struct ovsdb_column *column = node->data;
+        ovsdb_datum_clone(&new->fields[column->index],
+                          &old->fields[column->index],
+                          &column->type);
+    }
+    return new;
+}
+
+/* The caller is responsible for ensuring that 'row' has been removed from its
+ * table and that it is not participating in a transaction. */
+void
+ovsdb_row_destroy(struct ovsdb_row *row)
+{
+    if (row) {
+        const struct ovsdb_table *table = row->table;
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &table->schema->columns) {
+            const struct ovsdb_column *column = node->data;
+            ovsdb_datum_destroy(&row->fields[column->index], &column->type);
+        }
+        free(row);
+    }
+}
+
+uint32_t
+ovsdb_row_hash_columns(const struct ovsdb_row *row,
+                       const struct ovsdb_column_set *columns,
+                       uint32_t basis)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        basis = ovsdb_datum_hash(&row->fields[column->index], &column->type,
+                                 basis);
+    }
+
+    return basis;
+}
+
+int
+ovsdb_row_compare_columns_3way(const struct ovsdb_row *a,
+                               const struct ovsdb_row *b,
+                               const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        int cmp = ovsdb_datum_compare_3way(&a->fields[column->index],
+                                           &b->fields[column->index],
+                                           &column->type);
+        if (cmp) {
+            return cmp;
+        }
+    }
+
+    return 0;
+}
+
+bool
+ovsdb_row_equal_columns(const struct ovsdb_row *a,
+                        const struct ovsdb_row *b,
+                        const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        if (!ovsdb_datum_equals(&a->fields[column->index],
+                                &b->fields[column->index],
+                                &column->type)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+void
+ovsdb_row_update_columns(struct ovsdb_row *dst,
+                         const struct ovsdb_row *src,
+                         const struct ovsdb_column_set *columns)
+{
+    size_t i;
+
+    for (i = 0; i < columns->n_columns; i++) {
+        const struct ovsdb_column *column = columns->columns[i];
+        ovsdb_datum_destroy(&dst->fields[column->index], &column->type);
+        ovsdb_datum_clone(&dst->fields[column->index],
+                          &src->fields[column->index],
+                          &column->type);
+    }
+}
+
+struct ovsdb_error *
+ovsdb_row_from_json(struct ovsdb_row *row, const struct json *json,
+                    const struct ovsdb_symbol_table *symtab,
+                    struct ovsdb_column_set *included)
+{
+    struct ovsdb_table_schema *schema = row->table->schema;
+    struct ovsdb_error *error;
+    struct shash_node *node;
+
+    if (json->type != JSON_OBJECT) {
+        return ovsdb_syntax_error(json, NULL, "row must be JSON object");
+    }
+
+    SHASH_FOR_EACH (node, json_object(json)) {
+        const char *column_name = node->name;
+        const struct ovsdb_column *column;
+        struct ovsdb_datum datum;
+
+        column = ovsdb_table_schema_get_column(schema, column_name);
+        if (!column) {
+            return ovsdb_syntax_error(json, "unknown column",
+                                      "No column %s in table %s.",
+                                      column_name, schema->name);
+        }
+
+        error = ovsdb_datum_from_json(&datum, &column->type, node->data,
+                                      symtab);
+        if (error) {
+            return error;
+        }
+        ovsdb_datum_swap(&row->fields[column->index], &datum);
+        ovsdb_datum_destroy(&datum, &column->type);
+        if (included) {
+            ovsdb_column_set_add(included, column);
+        }
+    }
+
+    return NULL;
+}
+
+static void
+put_json_column(struct json *object, const struct ovsdb_row *row,
+                const struct ovsdb_column *column)
+{
+    json_object_put(object, column->name,
+                    ovsdb_datum_to_json(&row->fields[column->index],
+                                        &column->type));
+}
+
+struct json *
+ovsdb_row_to_json(const struct ovsdb_row *row,
+                  const struct ovsdb_column_set *columns)
+{
+    struct json *json;
+    size_t i;
+
+    json = json_object_create();
+    for (i = 0; i < columns->n_columns; i++) {
+        put_json_column(json, row, columns->columns[i]);
+    }
+    return json;
+}
+\f
+void
+ovsdb_row_set_init(struct ovsdb_row_set *set)
+{
+    set->rows = NULL;
+    set->n_rows = set->allocated_rows = 0;
+}
+
+void
+ovsdb_row_set_destroy(struct ovsdb_row_set *set)
+{
+    free(set->rows);
+}
+
+void
+ovsdb_row_set_add_row(struct ovsdb_row_set *set, const struct ovsdb_row *row)
+{
+    if (set->n_rows >= set->allocated_rows) {
+        set->rows = x2nrealloc(set->rows, &set->allocated_rows,
+                               sizeof *set->rows);
+    }
+    set->rows[set->n_rows++] = row;
+}
+
+struct json *
+ovsdb_row_set_to_json(const struct ovsdb_row_set *rows,
+                      const struct ovsdb_column_set *columns)
+{
+    struct json **json_rows;
+    size_t i;
+
+    json_rows = xmalloc(rows->n_rows * sizeof *json_rows);
+    for (i = 0; i < rows->n_rows; i++) {
+        json_rows[i] = ovsdb_row_to_json(rows->rows[i], columns);
+    }
+    return json_array_create(json_rows, rows->n_rows);
+}
+
+struct ovsdb_row_set_sort_cbdata {
+    struct ovsdb_row_set *set;
+    const struct ovsdb_column_set *columns;
+};
+
+static int
+ovsdb_row_set_sort_compare_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_;
+    return ovsdb_row_compare_columns_3way(cbdata->set->rows[a],
+                                          cbdata->set->rows[b],
+                                          cbdata->columns);
+}
+
+static void
+ovsdb_row_set_sort_swap_cb(size_t a, size_t b, void *cbdata_)
+{
+    struct ovsdb_row_set_sort_cbdata *cbdata = cbdata_;
+    const struct ovsdb_row *tmp = cbdata->set->rows[a];
+    cbdata->set->rows[a] = cbdata->set->rows[b];
+    cbdata->set->rows[b] = tmp;
+}
+
+void
+ovsdb_row_set_sort(struct ovsdb_row_set *set,
+                   const struct ovsdb_column_set *columns)
+{
+    if (columns && columns->n_columns && set->n_rows > 1) {
+        struct ovsdb_row_set_sort_cbdata cbdata;
+        cbdata.set = set;
+        cbdata.columns = columns;
+        sort(set->n_rows,
+             ovsdb_row_set_sort_compare_cb,
+             ovsdb_row_set_sort_swap_cb,
+             &cbdata);
+    }
+}
+\f
+void
+ovsdb_row_hash_init(struct ovsdb_row_hash *rh,
+                    const struct ovsdb_column_set *columns)
+{
+    hmap_init(&rh->rows);
+    ovsdb_column_set_clone(&rh->columns, columns);
+}
+
+void
+ovsdb_row_hash_destroy(struct ovsdb_row_hash *rh, bool destroy_rows)
+{
+    struct ovsdb_row_hash_node *node, *next;
+
+    HMAP_FOR_EACH_SAFE (node, next, struct ovsdb_row_hash_node, hmap_node,
+                        &rh->rows) {
+        hmap_remove(&rh->rows, &node->hmap_node);
+        if (destroy_rows) {
+            ovsdb_row_destroy((struct ovsdb_row *) node->row);
+        }
+        free(node);
+    }
+    hmap_destroy(&rh->rows);
+    ovsdb_column_set_destroy(&rh->columns);
+}
+
+size_t
+ovsdb_row_hash_count(const struct ovsdb_row_hash *rh)
+{
+    return hmap_count(&rh->rows);
+}
+
+bool
+ovsdb_row_hash_contains(const struct ovsdb_row_hash *rh,
+                        const struct ovsdb_row *row)
+{
+    size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0);
+    return ovsdb_row_hash_contains__(rh, row, hash);
+}
+
+/* Returns true if every row in 'b' has an equal row in 'a'. */
+bool
+ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *a,
+                            const struct ovsdb_row_hash *b)
+{
+    struct ovsdb_row_hash_node *node;
+
+    assert(ovsdb_column_set_equals(&a->columns, &b->columns));
+    HMAP_FOR_EACH (node, struct ovsdb_row_hash_node, hmap_node, &b->rows) {
+        if (!ovsdb_row_hash_contains__(a, node->row, node->hmap_node.hash)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool
+ovsdb_row_hash_insert(struct ovsdb_row_hash *rh, const struct ovsdb_row *row)
+{
+    size_t hash = ovsdb_row_hash_columns(row, &rh->columns, 0);
+    return ovsdb_row_hash_insert__(rh, row, hash);
+}
+
+bool
+ovsdb_row_hash_contains__(const struct ovsdb_row_hash *rh,
+                          const struct ovsdb_row *row, size_t hash)
+{
+    struct ovsdb_row_hash_node *node;
+    HMAP_FOR_EACH_WITH_HASH (node, struct ovsdb_row_hash_node, hmap_node,
+                             hash, &rh->rows) {
+        if (ovsdb_row_equal_columns(row, node->row, &rh->columns)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool
+ovsdb_row_hash_insert__(struct ovsdb_row_hash *rh, const struct ovsdb_row *row,
+                        size_t hash)
+{
+    if (!ovsdb_row_hash_contains__(rh, row, hash)) {
+        struct ovsdb_row_hash_node *node = xmalloc(sizeof *node);
+        node->row = row;
+        hmap_insert(&rh->rows, &node->hmap_node, hash);
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/ovsdb/row.h b/ovsdb/row.h
new file mode 100644 (file)
index 0000000..55c4f14
--- /dev/null
@@ -0,0 +1,139 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_ROW_H
+#define OVSDB_ROW_H 1
+
+#include <stddef.h>
+#include <stdint.h>
+#include "column.h"
+#include "hmap.h"
+#include "ovsdb-data.h"
+
+struct ovsdb_column_set;
+
+/* A row in a database table. */
+struct ovsdb_row {
+    struct ovsdb_table *table;  /* Table to which this belongs. */
+    struct hmap_node hmap_node; /* Element in ovsdb_table's 'rows' hmap. */
+    struct ovsdb_txn_row *txn_row; /* Transaction that row is in, if any. */
+    struct ovsdb_datum fields[];
+};
+
+struct ovsdb_row *ovsdb_row_create(const struct ovsdb_table *);
+struct ovsdb_row *ovsdb_row_clone(const struct ovsdb_row *);
+void ovsdb_row_destroy(struct ovsdb_row *);
+
+uint32_t ovsdb_row_hash_columns(const struct ovsdb_row *,
+                                const struct ovsdb_column_set *,
+                                uint32_t basis);
+bool ovsdb_row_equal_columns(const struct ovsdb_row *,
+                             const struct ovsdb_row *,
+                             const struct ovsdb_column_set *);
+int ovsdb_row_compare_columns_3way(const struct ovsdb_row *,
+                                   const struct ovsdb_row *,
+                                   const struct ovsdb_column_set *);
+void ovsdb_row_update_columns(struct ovsdb_row *, const struct ovsdb_row *,
+                              const struct ovsdb_column_set *);
+
+struct ovsdb_error *ovsdb_row_from_json(struct ovsdb_row *,
+                                        const struct json *,
+                                        const struct ovsdb_symbol_table *,
+                                        struct ovsdb_column_set *included)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_row_to_json(const struct ovsdb_row *,
+                               const struct ovsdb_column_set *include);
+
+static inline const struct uuid *
+ovsdb_row_get_uuid(const struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_UUID].keys[0].uuid;
+}
+
+static inline struct uuid *
+ovsdb_row_get_uuid_rw(struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_UUID].keys[0].uuid;
+}
+
+static inline const struct uuid *
+ovsdb_row_get_version(const struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_VERSION].keys[0].uuid;
+}
+
+static inline struct uuid *
+ovsdb_row_get_version_rw(struct ovsdb_row *row)
+{
+    return &row->fields[OVSDB_COL_VERSION].keys[0].uuid;
+}
+
+static inline uint32_t
+ovsdb_row_hash(const struct ovsdb_row *row)
+{
+    return uuid_hash(ovsdb_row_get_uuid(row));
+}
+\f
+/* An unordered collection of rows. */
+struct ovsdb_row_set {
+    const struct ovsdb_row **rows;
+    size_t n_rows, allocated_rows;
+};
+
+#define OVSDB_ROW_SET_INITIALIZER { NULL, 0, 0 }
+
+void ovsdb_row_set_init(struct ovsdb_row_set *);
+void ovsdb_row_set_destroy(struct ovsdb_row_set *);
+void ovsdb_row_set_add_row(struct ovsdb_row_set *, const struct ovsdb_row *);
+
+struct json *ovsdb_row_set_to_json(const struct ovsdb_row_set *,
+                                   const struct ovsdb_column_set *);
+
+void ovsdb_row_set_sort(struct ovsdb_row_set *,
+                        const struct ovsdb_column_set *);
+\f
+/* A hash table of rows.  A specified set of columns is used for hashing and
+ * comparing rows.
+ *
+ * The row hash doesn't necessarily own its rows.  They may be owned by, for
+ * example, an ovsdb_table. */
+struct ovsdb_row_hash {
+    struct hmap rows;
+    struct ovsdb_column_set columns;
+};
+
+#define OVSDB_ROW_HASH_INITIALIZER(RH) \
+    { HMAP_INITIALIZER(&(RH).rows), OVSDB_COLUMN_SET_INITIALIZER }
+
+struct ovsdb_row_hash_node {
+    struct hmap_node hmap_node;
+    const struct ovsdb_row *row;
+};
+
+void ovsdb_row_hash_init(struct ovsdb_row_hash *,
+                         const struct ovsdb_column_set *);
+void ovsdb_row_hash_destroy(struct ovsdb_row_hash *, bool destroy_rows);
+size_t ovsdb_row_hash_count(const struct ovsdb_row_hash *);
+bool ovsdb_row_hash_contains(const struct ovsdb_row_hash *,
+                             const struct ovsdb_row *);
+bool ovsdb_row_hash_contains_all(const struct ovsdb_row_hash *,
+                                 const struct ovsdb_row_hash *);
+bool ovsdb_row_hash_insert(struct ovsdb_row_hash *, const struct ovsdb_row *);
+bool ovsdb_row_hash_contains__(const struct ovsdb_row_hash *,
+                               const struct ovsdb_row *, size_t hash);
+bool ovsdb_row_hash_insert__(struct ovsdb_row_hash *,
+                             const struct ovsdb_row *, size_t hash);
+
+#endif /* ovsdb/row.h */
diff --git a/ovsdb/simplejson/__init__.py b/ovsdb/simplejson/__init__.py
new file mode 100644 (file)
index 0000000..d5b4d39
--- /dev/null
@@ -0,0 +1,318 @@
+r"""JSON (JavaScript Object Notation) <http://json.org> is a subset of
+JavaScript syntax (ECMA-262 3rd edition) used as a lightweight data
+interchange format.
+
+:mod:`simplejson` exposes an API familiar to users of the standard library
+:mod:`marshal` and :mod:`pickle` modules. It is the externally maintained
+version of the :mod:`json` library contained in Python 2.6, but maintains
+compatibility with Python 2.4 and Python 2.5 and (currently) has
+significant performance advantages, even without using the optional C
+extension for speedups.
+
+Encoding basic Python object hierarchies::
+
+    >>> import simplejson as json
+    >>> json.dumps(['foo', {'bar': ('baz', None, 1.0, 2)}])
+    '["foo", {"bar": ["baz", null, 1.0, 2]}]'
+    >>> print json.dumps("\"foo\bar")
+    "\"foo\bar"
+    >>> print json.dumps(u'\u1234')
+    "\u1234"
+    >>> print json.dumps('\\')
+    "\\"
+    >>> print json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)
+    {"a": 0, "b": 0, "c": 0}
+    >>> from StringIO import StringIO
+    >>> io = StringIO()
+    >>> json.dump(['streaming API'], io)
+    >>> io.getvalue()
+    '["streaming API"]'
+
+Compact encoding::
+
+    >>> import simplejson as json
+    >>> json.dumps([1,2,3,{'4': 5, '6': 7}], separators=(',',':'))
+    '[1,2,3,{"4":5,"6":7}]'
+
+Pretty printing::
+
+    >>> import simplejson as json
+    >>> s = json.dumps({'4': 5, '6': 7}, sort_keys=True, indent=4)
+    >>> print '\n'.join([l.rstrip() for l in  s.splitlines()])
+    {
+        "4": 5,
+        "6": 7
+    }
+
+Decoding JSON::
+
+    >>> import simplejson as json
+    >>> obj = [u'foo', {u'bar': [u'baz', None, 1.0, 2]}]
+    >>> json.loads('["foo", {"bar":["baz", null, 1.0, 2]}]') == obj
+    True
+    >>> json.loads('"\\"foo\\bar"') == u'"foo\x08ar'
+    True
+    >>> from StringIO import StringIO
+    >>> io = StringIO('["streaming API"]')
+    >>> json.load(io)[0] == 'streaming API'
+    True
+
+Specializing JSON object decoding::
+
+    >>> import simplejson as json
+    >>> def as_complex(dct):
+    ...     if '__complex__' in dct:
+    ...         return complex(dct['real'], dct['imag'])
+    ...     return dct
+    ...
+    >>> json.loads('{"__complex__": true, "real": 1, "imag": 2}',
+    ...     object_hook=as_complex)
+    (1+2j)
+    >>> import decimal
+    >>> json.loads('1.1', parse_float=decimal.Decimal) == decimal.Decimal('1.1')
+    True
+
+Specializing JSON object encoding::
+
+    >>> import simplejson as json
+    >>> def encode_complex(obj):
+    ...     if isinstance(obj, complex):
+    ...         return [obj.real, obj.imag]
+    ...     raise TypeError(repr(o) + " is not JSON serializable")
+    ...
+    >>> json.dumps(2 + 1j, default=encode_complex)
+    '[2.0, 1.0]'
+    >>> json.JSONEncoder(default=encode_complex).encode(2 + 1j)
+    '[2.0, 1.0]'
+    >>> ''.join(json.JSONEncoder(default=encode_complex).iterencode(2 + 1j))
+    '[2.0, 1.0]'
+
+
+Using simplejson.tool from the shell to validate and pretty-print::
+
+    $ echo '{"json":"obj"}' | python -m simplejson.tool
+    {
+        "json": "obj"
+    }
+    $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+    Expecting property name: line 1 column 2 (char 2)
+"""
+__version__ = '2.0.9'
+__all__ = [
+    'dump', 'dumps', 'load', 'loads',
+    'JSONDecoder', 'JSONEncoder',
+]
+
+__author__ = 'Bob Ippolito <bob@redivi.com>'
+
+from decoder import JSONDecoder
+from encoder import JSONEncoder
+
+_default_encoder = JSONEncoder(
+    skipkeys=False,
+    ensure_ascii=True,
+    check_circular=True,
+    allow_nan=True,
+    indent=None,
+    separators=None,
+    encoding='utf-8',
+    default=None,
+)
+
+def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True,
+        allow_nan=True, cls=None, indent=None, separators=None,
+        encoding='utf-8', default=None, **kw):
+    """Serialize ``obj`` as a JSON formatted stream to ``fp`` (a
+    ``.write()``-supporting file-like object).
+
+    If ``skipkeys`` is true then ``dict`` keys that are not basic types
+    (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+    will be skipped instead of raising a ``TypeError``.
+
+    If ``ensure_ascii`` is false, then the some chunks written to ``fp``
+    may be ``unicode`` instances, subject to normal Python ``str`` to
+    ``unicode`` coercion rules. Unless ``fp.write()`` explicitly
+    understands ``unicode`` (as in ``codecs.getwriter()``) this is likely
+    to cause an error.
+
+    If ``check_circular`` is false, then the circular reference check
+    for container types will be skipped and a circular reference will
+    result in an ``OverflowError`` (or worse).
+
+    If ``allow_nan`` is false, then it will be a ``ValueError`` to
+    serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``)
+    in strict compliance of the JSON specification, instead of using the
+    JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+    If ``indent`` is a non-negative integer, then JSON array elements and object
+    members will be pretty-printed with that indent level. An indent level
+    of 0 will only insert newlines. ``None`` is the most compact representation.
+
+    If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+    then it will be used instead of the default ``(', ', ': ')`` separators.
+    ``(',', ':')`` is the most compact JSON representation.
+
+    ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+    ``default(obj)`` is a function that should return a serializable version
+    of obj or raise TypeError. The default simply raises TypeError.
+
+    To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+    ``.default()`` method to serialize additional types), specify it with
+    the ``cls`` kwarg.
+
+    """
+    # cached encoder
+    if (not skipkeys and ensure_ascii and
+        check_circular and allow_nan and
+        cls is None and indent is None and separators is None and
+        encoding == 'utf-8' and default is None and not kw):
+        iterable = _default_encoder.iterencode(obj)
+    else:
+        if cls is None:
+            cls = JSONEncoder
+        iterable = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+            check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+            separators=separators, encoding=encoding,
+            default=default, **kw).iterencode(obj)
+    # could accelerate with writelines in some versions of Python, at
+    # a debuggability cost
+    for chunk in iterable:
+        fp.write(chunk)
+
+
+def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True,
+        allow_nan=True, cls=None, indent=None, separators=None,
+        encoding='utf-8', default=None, **kw):
+    """Serialize ``obj`` to a JSON formatted ``str``.
+
+    If ``skipkeys`` is false then ``dict`` keys that are not basic types
+    (``str``, ``unicode``, ``int``, ``long``, ``float``, ``bool``, ``None``)
+    will be skipped instead of raising a ``TypeError``.
+
+    If ``ensure_ascii`` is false, then the return value will be a
+    ``unicode`` instance subject to normal Python ``str`` to ``unicode``
+    coercion rules instead of being escaped to an ASCII ``str``.
+
+    If ``check_circular`` is false, then the circular reference check
+    for container types will be skipped and a circular reference will
+    result in an ``OverflowError`` (or worse).
+
+    If ``allow_nan`` is false, then it will be a ``ValueError`` to
+    serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
+    strict compliance of the JSON specification, instead of using the
+    JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
+
+    If ``indent`` is a non-negative integer, then JSON array elements and
+    object members will be pretty-printed with that indent level. An indent
+    level of 0 will only insert newlines. ``None`` is the most compact
+    representation.
+
+    If ``separators`` is an ``(item_separator, dict_separator)`` tuple
+    then it will be used instead of the default ``(', ', ': ')`` separators.
+    ``(',', ':')`` is the most compact JSON representation.
+
+    ``encoding`` is the character encoding for str instances, default is UTF-8.
+
+    ``default(obj)`` is a function that should return a serializable version
+    of obj or raise TypeError. The default simply raises TypeError.
+
+    To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
+    ``.default()`` method to serialize additional types), specify it with
+    the ``cls`` kwarg.
+
+    """
+    # cached encoder
+    if (not skipkeys and ensure_ascii and
+        check_circular and allow_nan and
+        cls is None and indent is None and separators is None and
+        encoding == 'utf-8' and default is None and not kw):
+        return _default_encoder.encode(obj)
+    if cls is None:
+        cls = JSONEncoder
+    return cls(
+        skipkeys=skipkeys, ensure_ascii=ensure_ascii,
+        check_circular=check_circular, allow_nan=allow_nan, indent=indent,
+        separators=separators, encoding=encoding, default=default,
+        **kw).encode(obj)
+
+
+_default_decoder = JSONDecoder(encoding=None, object_hook=None)
+
+
+def load(fp, encoding=None, cls=None, object_hook=None, parse_float=None,
+        parse_int=None, parse_constant=None, **kw):
+    """Deserialize ``fp`` (a ``.read()``-supporting file-like object containing
+    a JSON document) to a Python object.
+
+    If the contents of ``fp`` is encoded with an ASCII based encoding other
+    than utf-8 (e.g. latin-1), then an appropriate ``encoding`` name must
+    be specified. Encodings that are not ASCII based (such as UCS-2) are
+    not allowed, and should be wrapped with
+    ``codecs.getreader(fp)(encoding)``, or simply decoded to a ``unicode``
+    object and passed to ``loads()``
+
+    ``object_hook`` is an optional function that will be called with the
+    result of any object literal decode (a ``dict``). The return value of
+    ``object_hook`` will be used instead of the ``dict``. This feature
+    can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+    To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+    kwarg.
+
+    """
+    return loads(fp.read(),
+        encoding=encoding, cls=cls, object_hook=object_hook,
+        parse_float=parse_float, parse_int=parse_int,
+        parse_constant=parse_constant, **kw)
+
+
+def loads(s, encoding=None, cls=None, object_hook=None, parse_float=None,
+        parse_int=None, parse_constant=None, **kw):
+    """Deserialize ``s`` (a ``str`` or ``unicode`` instance containing a JSON
+    document) to a Python object.
+
+    If ``s`` is a ``str`` instance and is encoded with an ASCII based encoding
+    other than utf-8 (e.g. latin-1) then an appropriate ``encoding`` name
+    must be specified. Encodings that are not ASCII based (such as UCS-2)
+    are not allowed and should be decoded to ``unicode`` first.
+
+    ``object_hook`` is an optional function that will be called with the
+    result of any object literal decode (a ``dict``). The return value of
+    ``object_hook`` will be used instead of the ``dict``. This feature
+    can be used to implement custom decoders (e.g. JSON-RPC class hinting).
+
+    ``parse_float``, if specified, will be called with the string
+    of every JSON float to be decoded. By default this is equivalent to
+    float(num_str). This can be used to use another datatype or parser
+    for JSON floats (e.g. decimal.Decimal).
+
+    ``parse_int``, if specified, will be called with the string
+    of every JSON int to be decoded. By default this is equivalent to
+    int(num_str). This can be used to use another datatype or parser
+    for JSON integers (e.g. float).
+
+    ``parse_constant``, if specified, will be called with one of the
+    following strings: -Infinity, Infinity, NaN, null, true, false.
+    This can be used to raise an exception if invalid JSON numbers
+    are encountered.
+
+    To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
+    kwarg.
+
+    """
+    if (cls is None and encoding is None and object_hook is None and
+            parse_int is None and parse_float is None and
+            parse_constant is None and not kw):
+        return _default_decoder.decode(s)
+    if cls is None:
+        cls = JSONDecoder
+    if object_hook is not None:
+        kw['object_hook'] = object_hook
+    if parse_float is not None:
+        kw['parse_float'] = parse_float
+    if parse_int is not None:
+        kw['parse_int'] = parse_int
+    if parse_constant is not None:
+        kw['parse_constant'] = parse_constant
+    return cls(encoding=encoding, **kw).decode(s)
diff --git a/ovsdb/simplejson/_speedups.c b/ovsdb/simplejson/_speedups.c
new file mode 100644 (file)
index 0000000..23b5f4a
--- /dev/null
@@ -0,0 +1,2329 @@
+#include "Python.h"
+#include "structmember.h"
+#if PY_VERSION_HEX < 0x02060000 && !defined(Py_TYPE)
+#define Py_TYPE(ob)     (((PyObject*)(ob))->ob_type)
+#endif
+#if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
+typedef int Py_ssize_t;
+#define PY_SSIZE_T_MAX INT_MAX
+#define PY_SSIZE_T_MIN INT_MIN
+#define PyInt_FromSsize_t PyInt_FromLong
+#define PyInt_AsSsize_t PyInt_AsLong
+#endif
+#ifndef Py_IS_FINITE
+#define Py_IS_FINITE(X) (!Py_IS_INFINITY(X) && !Py_IS_NAN(X))
+#endif
+
+#ifdef __GNUC__
+#define UNUSED __attribute__((__unused__))
+#else
+#define UNUSED
+#endif
+
+#define DEFAULT_ENCODING "utf-8"
+
+#define PyScanner_Check(op) PyObject_TypeCheck(op, &PyScannerType)
+#define PyScanner_CheckExact(op) (Py_TYPE(op) == &PyScannerType)
+#define PyEncoder_Check(op) PyObject_TypeCheck(op, &PyEncoderType)
+#define PyEncoder_CheckExact(op) (Py_TYPE(op) == &PyEncoderType)
+
+static PyTypeObject PyScannerType;
+static PyTypeObject PyEncoderType;
+
+typedef struct _PyScannerObject {
+    PyObject_HEAD
+    PyObject *encoding;
+    PyObject *strict;
+    PyObject *object_hook;
+    PyObject *parse_float;
+    PyObject *parse_int;
+    PyObject *parse_constant;
+} PyScannerObject;
+
+static PyMemberDef scanner_members[] = {
+    {"encoding", T_OBJECT, offsetof(PyScannerObject, encoding), READONLY, "encoding"},
+    {"strict", T_OBJECT, offsetof(PyScannerObject, strict), READONLY, "strict"},
+    {"object_hook", T_OBJECT, offsetof(PyScannerObject, object_hook), READONLY, "object_hook"},
+    {"parse_float", T_OBJECT, offsetof(PyScannerObject, parse_float), READONLY, "parse_float"},
+    {"parse_int", T_OBJECT, offsetof(PyScannerObject, parse_int), READONLY, "parse_int"},
+    {"parse_constant", T_OBJECT, offsetof(PyScannerObject, parse_constant), READONLY, "parse_constant"},
+    {NULL}
+};
+
+typedef struct _PyEncoderObject {
+    PyObject_HEAD
+    PyObject *markers;
+    PyObject *defaultfn;
+    PyObject *encoder;
+    PyObject *indent;
+    PyObject *key_separator;
+    PyObject *item_separator;
+    PyObject *sort_keys;
+    PyObject *skipkeys;
+    int fast_encode;
+    int allow_nan;
+} PyEncoderObject;
+
+static PyMemberDef encoder_members[] = {
+    {"markers", T_OBJECT, offsetof(PyEncoderObject, markers), READONLY, "markers"},
+    {"default", T_OBJECT, offsetof(PyEncoderObject, defaultfn), READONLY, "default"},
+    {"encoder", T_OBJECT, offsetof(PyEncoderObject, encoder), READONLY, "encoder"},
+    {"indent", T_OBJECT, offsetof(PyEncoderObject, indent), READONLY, "indent"},
+    {"key_separator", T_OBJECT, offsetof(PyEncoderObject, key_separator), READONLY, "key_separator"},
+    {"item_separator", T_OBJECT, offsetof(PyEncoderObject, item_separator), READONLY, "item_separator"},
+    {"sort_keys", T_OBJECT, offsetof(PyEncoderObject, sort_keys), READONLY, "sort_keys"},
+    {"skipkeys", T_OBJECT, offsetof(PyEncoderObject, skipkeys), READONLY, "skipkeys"},
+    {NULL}
+};
+
+static Py_ssize_t
+ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars);
+static PyObject *
+ascii_escape_unicode(PyObject *pystr);
+static PyObject *
+ascii_escape_str(PyObject *pystr);
+static PyObject *
+py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr);
+void init_speedups(void);
+static PyObject *
+scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+static PyObject *
+scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr);
+static PyObject *
+_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx);
+static PyObject *
+scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static int
+scanner_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void
+scanner_dealloc(PyObject *self);
+static int
+scanner_clear(PyObject *self);
+static PyObject *
+encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
+static int
+encoder_init(PyObject *self, PyObject *args, PyObject *kwds);
+static void
+encoder_dealloc(PyObject *self);
+static int
+encoder_clear(PyObject *self);
+static int
+encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level);
+static int
+encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level);
+static int
+encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level);
+static PyObject *
+_encoded_const(PyObject *const);
+static void
+raise_errmsg(char *msg, PyObject *s, Py_ssize_t end);
+static PyObject *
+encoder_encode_string(PyEncoderObject *s, PyObject *obj);
+static int
+_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr);
+static PyObject *
+_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr);
+static PyObject *
+encoder_encode_float(PyEncoderObject *s, PyObject *obj);
+
+#define S_CHAR(c) (c >= ' ' && c <= '~' && c != '\\' && c != '"')
+#define IS_WHITESPACE(c) (((c) == ' ') || ((c) == '\t') || ((c) == '\n') || ((c) == '\r'))
+
+#define MIN_EXPANSION 6
+#ifdef Py_UNICODE_WIDE
+#define MAX_EXPANSION (2 * MIN_EXPANSION)
+#else
+#define MAX_EXPANSION MIN_EXPANSION
+#endif
+
+static int
+_convertPyInt_AsSsize_t(PyObject *o, Py_ssize_t *size_ptr)
+{
+    /* PyObject to Py_ssize_t converter */
+    *size_ptr = PyInt_AsSsize_t(o);
+    if (*size_ptr == -1 && PyErr_Occurred());
+        return 1;
+    return 0;
+}
+
+static PyObject *
+_convertPyInt_FromSsize_t(Py_ssize_t *size_ptr)
+{
+    /* Py_ssize_t to PyObject converter */
+    return PyInt_FromSsize_t(*size_ptr);
+}
+
+static Py_ssize_t
+ascii_escape_char(Py_UNICODE c, char *output, Py_ssize_t chars)
+{
+    /* Escape unicode code point c to ASCII escape sequences
+    in char *output. output must have at least 12 bytes unused to
+    accommodate an escaped surrogate pair "\uXXXX\uXXXX" */
+    output[chars++] = '\\';
+    switch (c) {
+        case '\\': output[chars++] = (char)c; break;
+        case '"': output[chars++] = (char)c; break;
+        case '\b': output[chars++] = 'b'; break;
+        case '\f': output[chars++] = 'f'; break;
+        case '\n': output[chars++] = 'n'; break;
+        case '\r': output[chars++] = 'r'; break;
+        case '\t': output[chars++] = 't'; break;
+        default:
+#ifdef Py_UNICODE_WIDE
+            if (c >= 0x10000) {
+                /* UTF-16 surrogate pair */
+                Py_UNICODE v = c - 0x10000;
+                c = 0xd800 | ((v >> 10) & 0x3ff);
+                output[chars++] = 'u';
+                output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c >>  8) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c >>  4) & 0xf];
+                output[chars++] = "0123456789abcdef"[(c      ) & 0xf];
+                c = 0xdc00 | (v & 0x3ff);
+                output[chars++] = '\\';
+            }
+#endif
+            output[chars++] = 'u';
+            output[chars++] = "0123456789abcdef"[(c >> 12) & 0xf];
+            output[chars++] = "0123456789abcdef"[(c >>  8) & 0xf];
+            output[chars++] = "0123456789abcdef"[(c >>  4) & 0xf];
+            output[chars++] = "0123456789abcdef"[(c      ) & 0xf];
+    }
+    return chars;
+}
+
+static PyObject *
+ascii_escape_unicode(PyObject *pystr)
+{
+    /* Take a PyUnicode pystr and return a new ASCII-only escaped PyString */
+    Py_ssize_t i;
+    Py_ssize_t input_chars;
+    Py_ssize_t output_size;
+    Py_ssize_t max_output_size;
+    Py_ssize_t chars;
+    PyObject *rval;
+    char *output;
+    Py_UNICODE *input_unicode;
+
+    input_chars = PyUnicode_GET_SIZE(pystr);
+    input_unicode = PyUnicode_AS_UNICODE(pystr);
+
+    /* One char input can be up to 6 chars output, estimate 4 of these */
+    output_size = 2 + (MIN_EXPANSION * 4) + input_chars;
+    max_output_size = 2 + (input_chars * MAX_EXPANSION);
+    rval = PyString_FromStringAndSize(NULL, output_size);
+    if (rval == NULL) {
+        return NULL;
+    }
+    output = PyString_AS_STRING(rval);
+    chars = 0;
+    output[chars++] = '"';
+    for (i = 0; i < input_chars; i++) {
+        Py_UNICODE c = input_unicode[i];
+        if (S_CHAR(c)) {
+            output[chars++] = (char)c;
+        }
+        else {
+            chars = ascii_escape_char(c, output, chars);
+        }
+        if (output_size - chars < (1 + MAX_EXPANSION)) {
+            /* There's more than four, so let's resize by a lot */
+            Py_ssize_t new_output_size = output_size * 2;
+            /* This is an upper bound */
+            if (new_output_size > max_output_size) {
+                new_output_size = max_output_size;
+            }
+            /* Make sure that the output size changed before resizing */
+            if (new_output_size != output_size) {
+                output_size = new_output_size;
+                if (_PyString_Resize(&rval, output_size) == -1) {
+                    return NULL;
+                }
+                output = PyString_AS_STRING(rval);
+            }
+        }
+    }
+    output[chars++] = '"';
+    if (_PyString_Resize(&rval, chars) == -1) {
+        return NULL;
+    }
+    return rval;
+}
+
+static PyObject *
+ascii_escape_str(PyObject *pystr)
+{
+    /* Take a PyString pystr and return a new ASCII-only escaped PyString */
+    Py_ssize_t i;
+    Py_ssize_t input_chars;
+    Py_ssize_t output_size;
+    Py_ssize_t chars;
+    PyObject *rval;
+    char *output;
+    char *input_str;
+
+    input_chars = PyString_GET_SIZE(pystr);
+    input_str = PyString_AS_STRING(pystr);
+
+    /* Fast path for a string that's already ASCII */
+    for (i = 0; i < input_chars; i++) {
+        Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i];
+        if (!S_CHAR(c)) {
+            /* If we have to escape something, scan the string for unicode */
+            Py_ssize_t j;
+            for (j = i; j < input_chars; j++) {
+                c = (Py_UNICODE)(unsigned char)input_str[j];
+                if (c > 0x7f) {
+                    /* We hit a non-ASCII character, bail to unicode mode */
+                    PyObject *uni;
+                    uni = PyUnicode_DecodeUTF8(input_str, input_chars, "strict");
+                    if (uni == NULL) {
+                        return NULL;
+                    }
+                    rval = ascii_escape_unicode(uni);
+                    Py_DECREF(uni);
+                    return rval;
+                }
+            }
+            break;
+        }
+    }
+
+    if (i == input_chars) {
+        /* Input is already ASCII */
+        output_size = 2 + input_chars;
+    }
+    else {
+        /* One char input can be up to 6 chars output, estimate 4 of these */
+        output_size = 2 + (MIN_EXPANSION * 4) + input_chars;
+    }
+    rval = PyString_FromStringAndSize(NULL, output_size);
+    if (rval == NULL) {
+        return NULL;
+    }
+    output = PyString_AS_STRING(rval);
+    output[0] = '"';
+
+    /* We know that everything up to i is ASCII already */
+    chars = i + 1;
+    memcpy(&output[1], input_str, i);
+
+    for (; i < input_chars; i++) {
+        Py_UNICODE c = (Py_UNICODE)(unsigned char)input_str[i];
+        if (S_CHAR(c)) {
+            output[chars++] = (char)c;
+        }
+        else {
+            chars = ascii_escape_char(c, output, chars);
+        }
+        /* An ASCII char can't possibly expand to a surrogate! */
+        if (output_size - chars < (1 + MIN_EXPANSION)) {
+            /* There's more than four, so let's resize by a lot */
+            output_size *= 2;
+            if (output_size > 2 + (input_chars * MIN_EXPANSION)) {
+                output_size = 2 + (input_chars * MIN_EXPANSION);
+            }
+            if (_PyString_Resize(&rval, output_size) == -1) {
+                return NULL;
+            }
+            output = PyString_AS_STRING(rval);
+        }
+    }
+    output[chars++] = '"';
+    if (_PyString_Resize(&rval, chars) == -1) {
+        return NULL;
+    }
+    return rval;
+}
+
+static void
+raise_errmsg(char *msg, PyObject *s, Py_ssize_t end)
+{
+    /* Use the Python function simplejson.decoder.errmsg to raise a nice
+    looking ValueError exception */
+    static PyObject *errmsg_fn = NULL;
+    PyObject *pymsg;
+    if (errmsg_fn == NULL) {
+        PyObject *decoder = PyImport_ImportModule("simplejson.decoder");
+        if (decoder == NULL)
+            return;
+        errmsg_fn = PyObject_GetAttrString(decoder, "errmsg");
+        Py_DECREF(decoder);
+        if (errmsg_fn == NULL)
+            return;
+    }
+    pymsg = PyObject_CallFunction(errmsg_fn, "(zOO&)", msg, s, _convertPyInt_FromSsize_t, &end);
+    if (pymsg) {
+        PyErr_SetObject(PyExc_ValueError, pymsg);
+        Py_DECREF(pymsg);
+    }
+}
+
+static PyObject *
+join_list_unicode(PyObject *lst)
+{
+    /* return u''.join(lst) */
+    static PyObject *joinfn = NULL;
+    if (joinfn == NULL) {
+        PyObject *ustr = PyUnicode_FromUnicode(NULL, 0);
+        if (ustr == NULL)
+            return NULL;
+
+        joinfn = PyObject_GetAttrString(ustr, "join");
+        Py_DECREF(ustr);
+        if (joinfn == NULL)
+            return NULL;
+    }
+    return PyObject_CallFunctionObjArgs(joinfn, lst, NULL);
+}
+
+static PyObject *
+join_list_string(PyObject *lst)
+{
+    /* return ''.join(lst) */
+    static PyObject *joinfn = NULL;
+    if (joinfn == NULL) {
+        PyObject *ustr = PyString_FromStringAndSize(NULL, 0);
+        if (ustr == NULL)
+            return NULL;
+
+        joinfn = PyObject_GetAttrString(ustr, "join");
+        Py_DECREF(ustr);
+        if (joinfn == NULL)
+            return NULL;
+    }
+    return PyObject_CallFunctionObjArgs(joinfn, lst, NULL);
+}
+
+static PyObject *
+_build_rval_index_tuple(PyObject *rval, Py_ssize_t idx) {
+    /* return (rval, idx) tuple, stealing reference to rval */
+    PyObject *tpl;
+    PyObject *pyidx;
+    /*
+    steal a reference to rval, returns (rval, idx)
+    */
+    if (rval == NULL) {
+        return NULL;
+    }
+    pyidx = PyInt_FromSsize_t(idx);
+    if (pyidx == NULL) {
+        Py_DECREF(rval);
+        return NULL;
+    }
+    tpl = PyTuple_New(2);
+    if (tpl == NULL) {
+        Py_DECREF(pyidx);
+        Py_DECREF(rval);
+        return NULL;
+    }
+    PyTuple_SET_ITEM(tpl, 0, rval);
+    PyTuple_SET_ITEM(tpl, 1, pyidx);
+    return tpl;
+}
+
+static PyObject *
+scanstring_str(PyObject *pystr, Py_ssize_t end, char *encoding, int strict, Py_ssize_t *next_end_ptr)
+{
+    /* Read the JSON string from PyString pystr.
+    end is the index of the first character after the quote.
+    encoding is the encoding of pystr (must be an ASCII superset)
+    if strict is zero then literal control characters are allowed
+    *next_end_ptr is a return-by-reference index of the character
+        after the end quote
+
+    Return value is a new PyString (if ASCII-only) or PyUnicode
+    */
+    PyObject *rval;
+    Py_ssize_t len = PyString_GET_SIZE(pystr);
+    Py_ssize_t begin = end - 1;
+    Py_ssize_t next = begin;
+    int has_unicode = 0;
+    char *buf = PyString_AS_STRING(pystr);
+    PyObject *chunks = PyList_New(0);
+    if (chunks == NULL) {
+        goto bail;
+    }
+    if (end < 0 || len <= end) {
+        PyErr_SetString(PyExc_ValueError, "end is out of bounds");
+        goto bail;
+    }
+    while (1) {
+        /* Find the end of the string or the next escape */
+        Py_UNICODE c = 0;
+        PyObject *chunk = NULL;
+        for (next = end; next < len; next++) {
+            c = (unsigned char)buf[next];
+            if (c == '"' || c == '\\') {
+                break;
+            }
+            else if (strict && c <= 0x1f) {
+                raise_errmsg("Invalid control character at", pystr, next);
+                goto bail;
+            }
+            else if (c > 0x7f) {
+                has_unicode = 1;
+            }
+        }
+        if (!(c == '"' || c == '\\')) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        /* Pick up this chunk if it's not zero length */
+        if (next != end) {
+            PyObject *strchunk = PyString_FromStringAndSize(&buf[end], next - end);
+            if (strchunk == NULL) {
+                goto bail;
+            }
+            if (has_unicode) {
+                chunk = PyUnicode_FromEncodedObject(strchunk, encoding, NULL);
+                Py_DECREF(strchunk);
+                if (chunk == NULL) {
+                    goto bail;
+                }
+            }
+            else {
+                chunk = strchunk;
+            }
+            if (PyList_Append(chunks, chunk)) {
+                Py_DECREF(chunk);
+                goto bail;
+            }
+            Py_DECREF(chunk);
+        }
+        next++;
+        if (c == '"') {
+            end = next;
+            break;
+        }
+        if (next == len) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        c = buf[next];
+        if (c != 'u') {
+            /* Non-unicode backslash escapes */
+            end = next + 1;
+            switch (c) {
+                case '"': break;
+                case '\\': break;
+                case '/': break;
+                case 'b': c = '\b'; break;
+                case 'f': c = '\f'; break;
+                case 'n': c = '\n'; break;
+                case 'r': c = '\r'; break;
+                case 't': c = '\t'; break;
+                default: c = 0;
+            }
+            if (c == 0) {
+                raise_errmsg("Invalid \\escape", pystr, end - 2);
+                goto bail;
+            }
+        }
+        else {
+            c = 0;
+            next++;
+            end = next + 4;
+            if (end >= len) {
+                raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1);
+                goto bail;
+            }
+            /* Decode 4 hex digits */
+            for (; next < end; next++) {
+                Py_UNICODE digit = buf[next];
+                c <<= 4;
+                switch (digit) {
+                    case '0': case '1': case '2': case '3': case '4':
+                    case '5': case '6': case '7': case '8': case '9':
+                        c |= (digit - '0'); break;
+                    case 'a': case 'b': case 'c': case 'd': case 'e':
+                    case 'f':
+                        c |= (digit - 'a' + 10); break;
+                    case 'A': case 'B': case 'C': case 'D': case 'E':
+                    case 'F':
+                        c |= (digit - 'A' + 10); break;
+                    default:
+                        raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                        goto bail;
+                }
+            }
+#ifdef Py_UNICODE_WIDE
+            /* Surrogate pair */
+            if ((c & 0xfc00) == 0xd800) {
+                Py_UNICODE c2 = 0;
+                if (end + 6 >= len) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                if (buf[next++] != '\\' || buf[next++] != 'u') {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                end += 6;
+                /* Decode 4 hex digits */
+                for (; next < end; next++) {
+                    c2 <<= 4;
+                    Py_UNICODE digit = buf[next];
+                    switch (digit) {
+                        case '0': case '1': case '2': case '3': case '4':
+                        case '5': case '6': case '7': case '8': case '9':
+                            c2 |= (digit - '0'); break;
+                        case 'a': case 'b': case 'c': case 'd': case 'e':
+                        case 'f':
+                            c2 |= (digit - 'a' + 10); break;
+                        case 'A': case 'B': case 'C': case 'D': case 'E':
+                        case 'F':
+                            c2 |= (digit - 'A' + 10); break;
+                        default:
+                            raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                            goto bail;
+                    }
+                }
+                if ((c2 & 0xfc00) != 0xdc00) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00));
+            }
+            else if ((c & 0xfc00) == 0xdc00) {
+                raise_errmsg("Unpaired low surrogate", pystr, end - 5);
+                goto bail;
+            }
+#endif
+        }
+        if (c > 0x7f) {
+            has_unicode = 1;
+        }
+        if (has_unicode) {
+            chunk = PyUnicode_FromUnicode(&c, 1);
+            if (chunk == NULL) {
+                goto bail;
+            }
+        }
+        else {
+            char c_char = Py_CHARMASK(c);
+            chunk = PyString_FromStringAndSize(&c_char, 1);
+            if (chunk == NULL) {
+                goto bail;
+            }
+        }
+        if (PyList_Append(chunks, chunk)) {
+            Py_DECREF(chunk);
+            goto bail;
+        }
+        Py_DECREF(chunk);
+    }
+
+    rval = join_list_string(chunks);
+    if (rval == NULL) {
+        goto bail;
+    }
+    Py_CLEAR(chunks);
+    *next_end_ptr = end;
+    return rval;
+bail:
+    *next_end_ptr = -1;
+    Py_XDECREF(chunks);
+    return NULL;
+}
+
+
+static PyObject *
+scanstring_unicode(PyObject *pystr, Py_ssize_t end, int strict, Py_ssize_t *next_end_ptr)
+{
+    /* Read the JSON string from PyUnicode pystr.
+    end is the index of the first character after the quote.
+    if strict is zero then literal control characters are allowed
+    *next_end_ptr is a return-by-reference index of the character
+        after the end quote
+
+    Return value is a new PyUnicode
+    */
+    PyObject *rval;
+    Py_ssize_t len = PyUnicode_GET_SIZE(pystr);
+    Py_ssize_t begin = end - 1;
+    Py_ssize_t next = begin;
+    const Py_UNICODE *buf = PyUnicode_AS_UNICODE(pystr);
+    PyObject *chunks = PyList_New(0);
+    if (chunks == NULL) {
+        goto bail;
+    }
+    if (end < 0 || len <= end) {
+        PyErr_SetString(PyExc_ValueError, "end is out of bounds");
+        goto bail;
+    }
+    while (1) {
+        /* Find the end of the string or the next escape */
+        Py_UNICODE c = 0;
+        PyObject *chunk = NULL;
+        for (next = end; next < len; next++) {
+            c = buf[next];
+            if (c == '"' || c == '\\') {
+                break;
+            }
+            else if (strict && c <= 0x1f) {
+                raise_errmsg("Invalid control character at", pystr, next);
+                goto bail;
+            }
+        }
+        if (!(c == '"' || c == '\\')) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        /* Pick up this chunk if it's not zero length */
+        if (next != end) {
+            chunk = PyUnicode_FromUnicode(&buf[end], next - end);
+            if (chunk == NULL) {
+                goto bail;
+            }
+            if (PyList_Append(chunks, chunk)) {
+                Py_DECREF(chunk);
+                goto bail;
+            }
+            Py_DECREF(chunk);
+        }
+        next++;
+        if (c == '"') {
+            end = next;
+            break;
+        }
+        if (next == len) {
+            raise_errmsg("Unterminated string starting at", pystr, begin);
+            goto bail;
+        }
+        c = buf[next];
+        if (c != 'u') {
+            /* Non-unicode backslash escapes */
+            end = next + 1;
+            switch (c) {
+                case '"': break;
+                case '\\': break;
+                case '/': break;
+                case 'b': c = '\b'; break;
+                case 'f': c = '\f'; break;
+                case 'n': c = '\n'; break;
+                case 'r': c = '\r'; break;
+                case 't': c = '\t'; break;
+                default: c = 0;
+            }
+            if (c == 0) {
+                raise_errmsg("Invalid \\escape", pystr, end - 2);
+                goto bail;
+            }
+        }
+        else {
+            c = 0;
+            next++;
+            end = next + 4;
+            if (end >= len) {
+                raise_errmsg("Invalid \\uXXXX escape", pystr, next - 1);
+                goto bail;
+            }
+            /* Decode 4 hex digits */
+            for (; next < end; next++) {
+                Py_UNICODE digit = buf[next];
+                c <<= 4;
+                switch (digit) {
+                    case '0': case '1': case '2': case '3': case '4':
+                    case '5': case '6': case '7': case '8': case '9':
+                        c |= (digit - '0'); break;
+                    case 'a': case 'b': case 'c': case 'd': case 'e':
+                    case 'f':
+                        c |= (digit - 'a' + 10); break;
+                    case 'A': case 'B': case 'C': case 'D': case 'E':
+                    case 'F':
+                        c |= (digit - 'A' + 10); break;
+                    default:
+                        raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                        goto bail;
+                }
+            }
+#ifdef Py_UNICODE_WIDE
+            /* Surrogate pair */
+            if ((c & 0xfc00) == 0xd800) {
+                Py_UNICODE c2 = 0;
+                if (end + 6 >= len) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                if (buf[next++] != '\\' || buf[next++] != 'u') {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                end += 6;
+                /* Decode 4 hex digits */
+                for (; next < end; next++) {
+                    c2 <<= 4;
+                    Py_UNICODE digit = buf[next];
+                    switch (digit) {
+                        case '0': case '1': case '2': case '3': case '4':
+                        case '5': case '6': case '7': case '8': case '9':
+                            c2 |= (digit - '0'); break;
+                        case 'a': case 'b': case 'c': case 'd': case 'e':
+                        case 'f':
+                            c2 |= (digit - 'a' + 10); break;
+                        case 'A': case 'B': case 'C': case 'D': case 'E':
+                        case 'F':
+                            c2 |= (digit - 'A' + 10); break;
+                        default:
+                            raise_errmsg("Invalid \\uXXXX escape", pystr, end - 5);
+                            goto bail;
+                    }
+                }
+                if ((c2 & 0xfc00) != 0xdc00) {
+                    raise_errmsg("Unpaired high surrogate", pystr, end - 5);
+                    goto bail;
+                }
+                c = 0x10000 + (((c - 0xd800) << 10) | (c2 - 0xdc00));
+            }
+            else if ((c & 0xfc00) == 0xdc00) {
+                raise_errmsg("Unpaired low surrogate", pystr, end - 5);
+                goto bail;
+            }
+#endif
+        }
+        chunk = PyUnicode_FromUnicode(&c, 1);
+        if (chunk == NULL) {
+            goto bail;
+        }
+        if (PyList_Append(chunks, chunk)) {
+            Py_DECREF(chunk);
+            goto bail;
+        }
+        Py_DECREF(chunk);
+    }
+
+    rval = join_list_unicode(chunks);
+    if (rval == NULL) {
+        goto bail;
+    }
+    Py_DECREF(chunks);
+    *next_end_ptr = end;
+    return rval;
+bail:
+    *next_end_ptr = -1;
+    Py_XDECREF(chunks);
+    return NULL;
+}
+
+PyDoc_STRVAR(pydoc_scanstring,
+    "scanstring(basestring, end, encoding, strict=True) -> (str, end)\n"
+    "\n"
+    "Scan the string s for a JSON string. End is the index of the\n"
+    "character in s after the quote that started the JSON string.\n"
+    "Unescapes all valid JSON string escape sequences and raises ValueError\n"
+    "on attempt to decode an invalid string. If strict is False then literal\n"
+    "control characters are allowed in the string.\n"
+    "\n"
+    "Returns a tuple of the decoded string and the index of the character in s\n"
+    "after the end quote."
+);
+
+static PyObject *
+py_scanstring(PyObject* self UNUSED, PyObject *args)
+{
+    PyObject *pystr;
+    PyObject *rval;
+    Py_ssize_t end;
+    Py_ssize_t next_end = -1;
+    char *encoding = NULL;
+    int strict = 1;
+    if (!PyArg_ParseTuple(args, "OO&|zi:scanstring", &pystr, _convertPyInt_AsSsize_t, &end, &encoding, &strict)) {
+        return NULL;
+    }
+    if (encoding == NULL) {
+        encoding = DEFAULT_ENCODING;
+    }
+    if (PyString_Check(pystr)) {
+        rval = scanstring_str(pystr, end, encoding, strict, &next_end);
+    }
+    else if (PyUnicode_Check(pystr)) {
+        rval = scanstring_unicode(pystr, end, strict, &next_end);
+    }
+    else {
+        PyErr_Format(PyExc_TypeError,
+                     "first argument must be a string, not %.80s",
+                     Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+    return _build_rval_index_tuple(rval, next_end);
+}
+
+PyDoc_STRVAR(pydoc_encode_basestring_ascii,
+    "encode_basestring_ascii(basestring) -> str\n"
+    "\n"
+    "Return an ASCII-only JSON representation of a Python string"
+);
+
+static PyObject *
+py_encode_basestring_ascii(PyObject* self UNUSED, PyObject *pystr)
+{
+    /* Return an ASCII-only JSON representation of a Python string */
+    /* METH_O */
+    if (PyString_Check(pystr)) {
+        return ascii_escape_str(pystr);
+    }
+    else if (PyUnicode_Check(pystr)) {
+        return ascii_escape_unicode(pystr);
+    }
+    else {
+        PyErr_Format(PyExc_TypeError,
+                     "first argument must be a string, not %.80s",
+                     Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+}
+
+static void
+scanner_dealloc(PyObject *self)
+{
+    /* Deallocate scanner object */
+    scanner_clear(self);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static int
+scanner_traverse(PyObject *self, visitproc visit, void *arg)
+{
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    Py_VISIT(s->encoding);
+    Py_VISIT(s->strict);
+    Py_VISIT(s->object_hook);
+    Py_VISIT(s->parse_float);
+    Py_VISIT(s->parse_int);
+    Py_VISIT(s->parse_constant);
+    return 0;
+}
+
+static int
+scanner_clear(PyObject *self)
+{
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    Py_CLEAR(s->encoding);
+    Py_CLEAR(s->strict);
+    Py_CLEAR(s->object_hook);
+    Py_CLEAR(s->parse_float);
+    Py_CLEAR(s->parse_int);
+    Py_CLEAR(s->parse_constant);
+    return 0;
+}
+
+static PyObject *
+_parse_object_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON object from PyString pystr.
+    idx is the index of the first character after the opening curly brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing curly brace.
+
+    Returns a new PyObject (usually a dict, but object_hook can change that)
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    PyObject *rval = PyDict_New();
+    PyObject *key = NULL;
+    PyObject *val = NULL;
+    char *encoding = PyString_AS_STRING(s->encoding);
+    int strict = PyObject_IsTrue(s->strict);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after { */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the object is non-empty */
+    if (idx <= end_idx && str[idx] != '}') {
+        while (idx <= end_idx) {
+            /* read key */
+            if (str[idx] != '"') {
+                raise_errmsg("Expecting property name", pystr, idx);
+                goto bail;
+            }
+            key = scanstring_str(pystr, idx + 1, encoding, strict, &next_idx);
+            if (key == NULL)
+                goto bail;
+            idx = next_idx;
+
+            /* skip whitespace between key and : delimiter, read :, skip whitespace */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+            if (idx > end_idx || str[idx] != ':') {
+                raise_errmsg("Expecting : delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* read any JSON data type */
+            val = scan_once_str(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyDict_SetItem(rval, key, val) == -1)
+                goto bail;
+
+            Py_CLEAR(key);
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace before } or , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the object is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == '}') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , delimiter */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+    /* verify that idx < end_idx, str[idx] should be '}' */
+    if (idx > end_idx || str[idx] != '}') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+    /* if object_hook is not None: rval = object_hook(rval) */
+    if (s->object_hook != Py_None) {
+        val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL);
+        if (val == NULL)
+            goto bail;
+        Py_DECREF(rval);
+        rval = val;
+        val = NULL;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(key);
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_object_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON object from PyUnicode pystr.
+    idx is the index of the first character after the opening curly brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing curly brace.
+
+    Returns a new PyObject (usually a dict, but object_hook can change that)
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyDict_New();
+    PyObject *key = NULL;
+    int strict = PyObject_IsTrue(s->strict);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after { */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the object is non-empty */
+    if (idx <= end_idx && str[idx] != '}') {
+        while (idx <= end_idx) {
+            /* read key */
+            if (str[idx] != '"') {
+                raise_errmsg("Expecting property name", pystr, idx);
+                goto bail;
+            }
+            key = scanstring_unicode(pystr, idx + 1, strict, &next_idx);
+            if (key == NULL)
+                goto bail;
+            idx = next_idx;
+
+            /* skip whitespace between key and : delimiter, read :, skip whitespace */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+            if (idx > end_idx || str[idx] != ':') {
+                raise_errmsg("Expecting : delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* read any JSON term */
+            val = scan_once_unicode(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyDict_SetItem(rval, key, val) == -1)
+                goto bail;
+
+            Py_CLEAR(key);
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace before } or , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the object is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == '}') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , delimiter */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be '}' */
+    if (idx > end_idx || str[idx] != '}') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+
+    /* if object_hook is not None: rval = object_hook(rval) */
+    if (s->object_hook != Py_None) {
+        val = PyObject_CallFunctionObjArgs(s->object_hook, rval, NULL);
+        if (val == NULL)
+            goto bail;
+        Py_DECREF(rval);
+        rval = val;
+        val = NULL;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(key);
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_array_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON array from PyString pystr.
+    idx is the index of the first character after the opening brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing brace.
+
+    Returns a new PyList
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyList_New(0);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after [ */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the array is non-empty */
+    if (idx <= end_idx && str[idx] != ']') {
+        while (idx <= end_idx) {
+
+            /* read any JSON term and de-tuplefy the (rval, idx) */
+            val = scan_once_str(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyList_Append(rval, val) == -1)
+                goto bail;
+
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace between term and , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the array is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == ']') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be ']' */
+    if (idx > end_idx || str[idx] != ']') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_array_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON array from PyString pystr.
+    idx is the index of the first character after the opening brace.
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the closing brace.
+
+    Returns a new PyList
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1;
+    PyObject *val = NULL;
+    PyObject *rval = PyList_New(0);
+    Py_ssize_t next_idx;
+    if (rval == NULL)
+        return NULL;
+
+    /* skip whitespace after [ */
+    while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+    /* only loop if the array is non-empty */
+    if (idx <= end_idx && str[idx] != ']') {
+        while (idx <= end_idx) {
+
+            /* read any JSON term  */
+            val = scan_once_unicode(s, pystr, idx, &next_idx);
+            if (val == NULL)
+                goto bail;
+
+            if (PyList_Append(rval, val) == -1)
+                goto bail;
+
+            Py_CLEAR(val);
+            idx = next_idx;
+
+            /* skip whitespace between term and , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+
+            /* bail if the array is closed or we didn't get the , delimiter */
+            if (idx > end_idx) break;
+            if (str[idx] == ']') {
+                break;
+            }
+            else if (str[idx] != ',') {
+                raise_errmsg("Expecting , delimiter", pystr, idx);
+                goto bail;
+            }
+            idx++;
+
+            /* skip whitespace after , */
+            while (idx <= end_idx && IS_WHITESPACE(str[idx])) idx++;
+        }
+    }
+
+    /* verify that idx < end_idx, str[idx] should be ']' */
+    if (idx > end_idx || str[idx] != ']') {
+        raise_errmsg("Expecting object", pystr, end_idx);
+        goto bail;
+    }
+    *next_idx_ptr = idx + 1;
+    return rval;
+bail:
+    Py_XDECREF(val);
+    Py_DECREF(rval);
+    return NULL;
+}
+
+static PyObject *
+_parse_constant(PyScannerObject *s, char *constant, Py_ssize_t idx, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON constant from PyString pystr.
+    constant is the constant string that was found
+        ("NaN", "Infinity", "-Infinity").
+    idx is the index of the first character of the constant
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the constant.
+
+    Returns the result of parse_constant
+    */
+    PyObject *cstr;
+    PyObject *rval;
+    /* constant is "NaN", "Infinity", or "-Infinity" */
+    cstr = PyString_InternFromString(constant);
+    if (cstr == NULL)
+        return NULL;
+
+    /* rval = parse_constant(constant) */
+    rval = PyObject_CallFunctionObjArgs(s->parse_constant, cstr, NULL);
+    idx += PyString_GET_SIZE(cstr);
+    Py_DECREF(cstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+static PyObject *
+_match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON number from PyString pystr.
+    idx is the index of the first character of the number
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of that number:
+        PyInt, PyLong, or PyFloat.
+        May return other types if parse_int or parse_float are set
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t end_idx = PyString_GET_SIZE(pystr) - 1;
+    Py_ssize_t idx = start;
+    int is_float = 0;
+    PyObject *rval;
+    PyObject *numstr;
+
+    /* read a sign if it's there, make sure it's not the end of the string */
+    if (str[idx] == '-') {
+        idx++;
+        if (idx > end_idx) {
+            PyErr_SetNone(PyExc_StopIteration);
+            return NULL;
+        }
+    }
+
+    /* read as many integer digits as we find as long as it doesn't start with 0 */
+    if (str[idx] >= '1' && str[idx] <= '9') {
+        idx++;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+    /* if it starts with 0 we only expect one integer digit */
+    else if (str[idx] == '0') {
+        idx++;
+    }
+    /* no integer digits, error */
+    else {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+
+    /* if the next char is '.' followed by a digit then read all float digits */
+    if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') {
+        is_float = 1;
+        idx += 2;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+
+    /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */
+    if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) {
+
+        /* save the index of the 'e' or 'E' just in case we need to backtrack */
+        Py_ssize_t e_start = idx;
+        idx++;
+
+        /* read an exponent sign if present */
+        if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++;
+
+        /* read all digits */
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+
+        /* if we got a digit, then parse as float. if not, backtrack */
+        if (str[idx - 1] >= '0' && str[idx - 1] <= '9') {
+            is_float = 1;
+        }
+        else {
+            idx = e_start;
+        }
+    }
+
+    /* copy the section we determined to be a number */
+    numstr = PyString_FromStringAndSize(&str[start], idx - start);
+    if (numstr == NULL)
+        return NULL;
+    if (is_float) {
+        /* parse as a float using a fast path if available, otherwise call user defined method */
+        if (s->parse_float != (PyObject *)&PyFloat_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL);
+        }
+        else {
+            rval = PyFloat_FromDouble(PyOS_ascii_atof(PyString_AS_STRING(numstr)));
+        }
+    }
+    else {
+        /* parse as an int using a fast path if available, otherwise call user defined method */
+        if (s->parse_int != (PyObject *)&PyInt_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL);
+        }
+        else {
+            rval = PyInt_FromString(PyString_AS_STRING(numstr), NULL, 10);
+        }
+    }
+    Py_DECREF(numstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+static PyObject *
+_match_number_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssize_t *next_idx_ptr) {
+    /* Read a JSON number from PyUnicode pystr.
+    idx is the index of the first character of the number
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of that number:
+        PyInt, PyLong, or PyFloat.
+        May return other types if parse_int or parse_float are set
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t end_idx = PyUnicode_GET_SIZE(pystr) - 1;
+    Py_ssize_t idx = start;
+    int is_float = 0;
+    PyObject *rval;
+    PyObject *numstr;
+
+    /* read a sign if it's there, make sure it's not the end of the string */
+    if (str[idx] == '-') {
+        idx++;
+        if (idx > end_idx) {
+            PyErr_SetNone(PyExc_StopIteration);
+            return NULL;
+        }
+    }
+
+    /* read as many integer digits as we find as long as it doesn't start with 0 */
+    if (str[idx] >= '1' && str[idx] <= '9') {
+        idx++;
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+    /* if it starts with 0 we only expect one integer digit */
+    else if (str[idx] == '0') {
+        idx++;
+    }
+    /* no integer digits, error */
+    else {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+
+    /* if the next char is '.' followed by a digit then read all float digits */
+    if (idx < end_idx && str[idx] == '.' && str[idx + 1] >= '0' && str[idx + 1] <= '9') {
+        is_float = 1;
+        idx += 2;
+        while (idx < end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+    }
+
+    /* if the next char is 'e' or 'E' then maybe read the exponent (or backtrack) */
+    if (idx < end_idx && (str[idx] == 'e' || str[idx] == 'E')) {
+        Py_ssize_t e_start = idx;
+        idx++;
+
+        /* read an exponent sign if present */
+        if (idx < end_idx && (str[idx] == '-' || str[idx] == '+')) idx++;
+
+        /* read all digits */
+        while (idx <= end_idx && str[idx] >= '0' && str[idx] <= '9') idx++;
+
+        /* if we got a digit, then parse as float. if not, backtrack */
+        if (str[idx - 1] >= '0' && str[idx - 1] <= '9') {
+            is_float = 1;
+        }
+        else {
+            idx = e_start;
+        }
+    }
+
+    /* copy the section we determined to be a number */
+    numstr = PyUnicode_FromUnicode(&str[start], idx - start);
+    if (numstr == NULL)
+        return NULL;
+    if (is_float) {
+        /* parse as a float using a fast path if available, otherwise call user defined method */
+        if (s->parse_float != (PyObject *)&PyFloat_Type) {
+            rval = PyObject_CallFunctionObjArgs(s->parse_float, numstr, NULL);
+        }
+        else {
+            rval = PyFloat_FromString(numstr, NULL);
+        }
+    }
+    else {
+        /* no fast path for unicode -> int, just call */
+        rval = PyObject_CallFunctionObjArgs(s->parse_int, numstr, NULL);
+    }
+    Py_DECREF(numstr);
+    *next_idx_ptr = idx;
+    return rval;
+}
+
+static PyObject *
+scan_once_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read one JSON term (of any kind) from PyString pystr.
+    idx is the index of the first character of the term
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of the term.
+    */
+    char *str = PyString_AS_STRING(pystr);
+    Py_ssize_t length = PyString_GET_SIZE(pystr);
+    if (idx >= length) {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+    switch (str[idx]) {
+        case '"':
+            /* string */
+            return scanstring_str(pystr, idx + 1,
+                PyString_AS_STRING(s->encoding),
+                PyObject_IsTrue(s->strict),
+                next_idx_ptr);
+        case '{':
+            /* object */
+            return _parse_object_str(s, pystr, idx + 1, next_idx_ptr);
+        case '[':
+            /* array */
+            return _parse_array_str(s, pystr, idx + 1, next_idx_ptr);
+        case 'n':
+            /* null */
+            if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') {
+                Py_INCREF(Py_None);
+                *next_idx_ptr = idx + 4;
+                return Py_None;
+            }
+            break;
+        case 't':
+            /* true */
+            if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') {
+                Py_INCREF(Py_True);
+                *next_idx_ptr = idx + 4;
+                return Py_True;
+            }
+            break;
+        case 'f':
+            /* false */
+            if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') {
+                Py_INCREF(Py_False);
+                *next_idx_ptr = idx + 5;
+                return Py_False;
+            }
+            break;
+        case 'N':
+            /* NaN */
+            if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') {
+                return _parse_constant(s, "NaN", idx, next_idx_ptr);
+            }
+            break;
+        case 'I':
+            /* Infinity */
+            if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') {
+                return _parse_constant(s, "Infinity", idx, next_idx_ptr);
+            }
+            break;
+        case '-':
+            /* -Infinity */
+            if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') {
+                return _parse_constant(s, "-Infinity", idx, next_idx_ptr);
+            }
+            break;
+    }
+    /* Didn't find a string, object, array, or named constant. Look for a number. */
+    return _match_number_str(s, pystr, idx, next_idx_ptr);
+}
+
+static PyObject *
+scan_once_unicode(PyScannerObject *s, PyObject *pystr, Py_ssize_t idx, Py_ssize_t *next_idx_ptr)
+{
+    /* Read one JSON term (of any kind) from PyUnicode pystr.
+    idx is the index of the first character of the term
+    *next_idx_ptr is a return-by-reference index to the first character after
+        the number.
+
+    Returns a new PyObject representation of the term.
+    */
+    Py_UNICODE *str = PyUnicode_AS_UNICODE(pystr);
+    Py_ssize_t length = PyUnicode_GET_SIZE(pystr);
+    if (idx >= length) {
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+    switch (str[idx]) {
+        case '"':
+            /* string */
+            return scanstring_unicode(pystr, idx + 1,
+                PyObject_IsTrue(s->strict),
+                next_idx_ptr);
+        case '{':
+            /* object */
+            return _parse_object_unicode(s, pystr, idx + 1, next_idx_ptr);
+        case '[':
+            /* array */
+            return _parse_array_unicode(s, pystr, idx + 1, next_idx_ptr);
+        case 'n':
+            /* null */
+            if ((idx + 3 < length) && str[idx + 1] == 'u' && str[idx + 2] == 'l' && str[idx + 3] == 'l') {
+                Py_INCREF(Py_None);
+                *next_idx_ptr = idx + 4;
+                return Py_None;
+            }
+            break;
+        case 't':
+            /* true */
+            if ((idx + 3 < length) && str[idx + 1] == 'r' && str[idx + 2] == 'u' && str[idx + 3] == 'e') {
+                Py_INCREF(Py_True);
+                *next_idx_ptr = idx + 4;
+                return Py_True;
+            }
+            break;
+        case 'f':
+            /* false */
+            if ((idx + 4 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'l' && str[idx + 3] == 's' && str[idx + 4] == 'e') {
+                Py_INCREF(Py_False);
+                *next_idx_ptr = idx + 5;
+                return Py_False;
+            }
+            break;
+        case 'N':
+            /* NaN */
+            if ((idx + 2 < length) && str[idx + 1] == 'a' && str[idx + 2] == 'N') {
+                return _parse_constant(s, "NaN", idx, next_idx_ptr);
+            }
+            break;
+        case 'I':
+            /* Infinity */
+            if ((idx + 7 < length) && str[idx + 1] == 'n' && str[idx + 2] == 'f' && str[idx + 3] == 'i' && str[idx + 4] == 'n' && str[idx + 5] == 'i' && str[idx + 6] == 't' && str[idx + 7] == 'y') {
+                return _parse_constant(s, "Infinity", idx, next_idx_ptr);
+            }
+            break;
+        case '-':
+            /* -Infinity */
+            if ((idx + 8 < length) && str[idx + 1] == 'I' && str[idx + 2] == 'n' && str[idx + 3] == 'f' && str[idx + 4] == 'i' && str[idx + 5] == 'n' && str[idx + 6] == 'i' && str[idx + 7] == 't' && str[idx + 8] == 'y') {
+                return _parse_constant(s, "-Infinity", idx, next_idx_ptr);
+            }
+            break;
+    }
+    /* Didn't find a string, object, array, or named constant. Look for a number. */
+    return _match_number_unicode(s, pystr, idx, next_idx_ptr);
+}
+
+static PyObject *
+scanner_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Python callable interface to scan_once_{str,unicode} */
+    PyObject *pystr;
+    PyObject *rval;
+    Py_ssize_t idx;
+    Py_ssize_t next_idx = -1;
+    static char *kwlist[] = {"string", "idx", NULL};
+    PyScannerObject *s;
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:scan_once", kwlist, &pystr, _convertPyInt_AsSsize_t, &idx))
+        return NULL;
+
+    if (PyString_Check(pystr)) {
+        rval = scan_once_str(s, pystr, idx, &next_idx);
+    }
+    else if (PyUnicode_Check(pystr)) {
+        rval = scan_once_unicode(s, pystr, idx, &next_idx);
+    }
+    else {
+        PyErr_Format(PyExc_TypeError,
+                 "first argument must be a string, not %.80s",
+                 Py_TYPE(pystr)->tp_name);
+        return NULL;
+    }
+    return _build_rval_index_tuple(rval, next_idx);
+}
+
+static PyObject *
+scanner_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyScannerObject *s;
+    s = (PyScannerObject *)type->tp_alloc(type, 0);
+    if (s != NULL) {
+        s->encoding = NULL;
+        s->strict = NULL;
+        s->object_hook = NULL;
+        s->parse_float = NULL;
+        s->parse_int = NULL;
+        s->parse_constant = NULL;
+    }
+    return (PyObject *)s;
+}
+
+static int
+scanner_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Initialize Scanner object */
+    PyObject *ctx;
+    static char *kwlist[] = {"context", NULL};
+    PyScannerObject *s;
+
+    assert(PyScanner_Check(self));
+    s = (PyScannerObject *)self;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O:make_scanner", kwlist, &ctx))
+        return -1;
+
+    /* PyString_AS_STRING is used on encoding */
+    s->encoding = PyObject_GetAttrString(ctx, "encoding");
+    if (s->encoding == Py_None) {
+        Py_DECREF(Py_None);
+        s->encoding = PyString_InternFromString(DEFAULT_ENCODING);
+    }
+    else if (PyUnicode_Check(s->encoding)) {
+        PyObject *tmp = PyUnicode_AsEncodedString(s->encoding, NULL, NULL);
+        Py_DECREF(s->encoding);
+        s->encoding = tmp;
+    }
+    if (s->encoding == NULL || !PyString_Check(s->encoding))
+        goto bail;
+
+    /* All of these will fail "gracefully" so we don't need to verify them */
+    s->strict = PyObject_GetAttrString(ctx, "strict");
+    if (s->strict == NULL)
+        goto bail;
+    s->object_hook = PyObject_GetAttrString(ctx, "object_hook");
+    if (s->object_hook == NULL)
+        goto bail;
+    s->parse_float = PyObject_GetAttrString(ctx, "parse_float");
+    if (s->parse_float == NULL)
+        goto bail;
+    s->parse_int = PyObject_GetAttrString(ctx, "parse_int");
+    if (s->parse_int == NULL)
+        goto bail;
+    s->parse_constant = PyObject_GetAttrString(ctx, "parse_constant");
+    if (s->parse_constant == NULL)
+        goto bail;
+
+    return 0;
+
+bail:
+    Py_CLEAR(s->encoding);
+    Py_CLEAR(s->strict);
+    Py_CLEAR(s->object_hook);
+    Py_CLEAR(s->parse_float);
+    Py_CLEAR(s->parse_int);
+    Py_CLEAR(s->parse_constant);
+    return -1;
+}
+
+PyDoc_STRVAR(scanner_doc, "JSON scanner object");
+
+static
+PyTypeObject PyScannerType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                    /* tp_internal */
+    "simplejson._speedups.Scanner",       /* tp_name */
+    sizeof(PyScannerObject), /* tp_basicsize */
+    0,                    /* tp_itemsize */
+    scanner_dealloc, /* tp_dealloc */
+    0,                    /* tp_print */
+    0,                    /* tp_getattr */
+    0,                    /* tp_setattr */
+    0,                    /* tp_compare */
+    0,                    /* tp_repr */
+    0,                    /* tp_as_number */
+    0,                    /* tp_as_sequence */
+    0,                    /* tp_as_mapping */
+    0,                    /* tp_hash */
+    scanner_call,         /* tp_call */
+    0,                    /* tp_str */
+    0,/* PyObject_GenericGetAttr, */                    /* tp_getattro */
+    0,/* PyObject_GenericSetAttr, */                    /* tp_setattro */
+    0,                    /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,   /* tp_flags */
+    scanner_doc,          /* tp_doc */
+    scanner_traverse,                    /* tp_traverse */
+    scanner_clear,                    /* tp_clear */
+    0,                    /* tp_richcompare */
+    0,                    /* tp_weaklistoffset */
+    0,                    /* tp_iter */
+    0,                    /* tp_iternext */
+    0,                    /* tp_methods */
+    scanner_members,                    /* tp_members */
+    0,                    /* tp_getset */
+    0,                    /* tp_base */
+    0,                    /* tp_dict */
+    0,                    /* tp_descr_get */
+    0,                    /* tp_descr_set */
+    0,                    /* tp_dictoffset */
+    scanner_init,                    /* tp_init */
+    0,/* PyType_GenericAlloc, */        /* tp_alloc */
+    scanner_new,          /* tp_new */
+    0,/* PyObject_GC_Del, */              /* tp_free */
+};
+
+static PyObject *
+encoder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+    PyEncoderObject *s;
+    s = (PyEncoderObject *)type->tp_alloc(type, 0);
+    if (s != NULL) {
+        s->markers = NULL;
+        s->defaultfn = NULL;
+        s->encoder = NULL;
+        s->indent = NULL;
+        s->key_separator = NULL;
+        s->item_separator = NULL;
+        s->sort_keys = NULL;
+        s->skipkeys = NULL;
+    }
+    return (PyObject *)s;
+}
+
+static int
+encoder_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* initialize Encoder object */
+    static char *kwlist[] = {"markers", "default", "encoder", "indent", "key_separator", "item_separator", "sort_keys", "skipkeys", "allow_nan", NULL};
+
+    PyEncoderObject *s;
+    PyObject *allow_nan;
+
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOOOOOOOO:make_encoder", kwlist,
+        &s->markers, &s->defaultfn, &s->encoder, &s->indent, &s->key_separator, &s->item_separator, &s->sort_keys, &s->skipkeys, &allow_nan))
+        return -1;
+
+    Py_INCREF(s->markers);
+    Py_INCREF(s->defaultfn);
+    Py_INCREF(s->encoder);
+    Py_INCREF(s->indent);
+    Py_INCREF(s->key_separator);
+    Py_INCREF(s->item_separator);
+    Py_INCREF(s->sort_keys);
+    Py_INCREF(s->skipkeys);
+    s->fast_encode = (PyCFunction_Check(s->encoder) && PyCFunction_GetFunction(s->encoder) == (PyCFunction)py_encode_basestring_ascii);
+    s->allow_nan = PyObject_IsTrue(allow_nan);
+    return 0;
+}
+
+static PyObject *
+encoder_call(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    /* Python callable interface to encode_listencode_obj */
+    static char *kwlist[] = {"obj", "_current_indent_level", NULL};
+    PyObject *obj;
+    PyObject *rval;
+    Py_ssize_t indent_level;
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO&:_iterencode", kwlist,
+        &obj, _convertPyInt_AsSsize_t, &indent_level))
+        return NULL;
+    rval = PyList_New(0);
+    if (rval == NULL)
+        return NULL;
+    if (encoder_listencode_obj(s, rval, obj, indent_level)) {
+        Py_DECREF(rval);
+        return NULL;
+    }
+    return rval;
+}
+
+static PyObject *
+_encoded_const(PyObject *obj)
+{
+    /* Return the JSON string representation of None, True, False */
+    if (obj == Py_None) {
+        static PyObject *s_null = NULL;
+        if (s_null == NULL) {
+            s_null = PyString_InternFromString("null");
+        }
+        Py_INCREF(s_null);
+        return s_null;
+    }
+    else if (obj == Py_True) {
+        static PyObject *s_true = NULL;
+        if (s_true == NULL) {
+            s_true = PyString_InternFromString("true");
+        }
+        Py_INCREF(s_true);
+        return s_true;
+    }
+    else if (obj == Py_False) {
+        static PyObject *s_false = NULL;
+        if (s_false == NULL) {
+            s_false = PyString_InternFromString("false");
+        }
+        Py_INCREF(s_false);
+        return s_false;
+    }
+    else {
+        PyErr_SetString(PyExc_ValueError, "not a const");
+        return NULL;
+    }
+}
+
+static PyObject *
+encoder_encode_float(PyEncoderObject *s, PyObject *obj)
+{
+    /* Return the JSON representation of a PyFloat */
+    double i = PyFloat_AS_DOUBLE(obj);
+    if (!Py_IS_FINITE(i)) {
+        if (!s->allow_nan) {
+            PyErr_SetString(PyExc_ValueError, "Out of range float values are not JSON compliant");
+            return NULL;
+        }
+        if (i > 0) {
+            return PyString_FromString("Infinity");
+        }
+        else if (i < 0) {
+            return PyString_FromString("-Infinity");
+        }
+        else {
+            return PyString_FromString("NaN");
+        }
+    }
+    /* Use a better float format here? */
+    return PyObject_Repr(obj);
+}
+
+static PyObject *
+encoder_encode_string(PyEncoderObject *s, PyObject *obj)
+{
+    /* Return the JSON representation of a string */
+    if (s->fast_encode)
+        return py_encode_basestring_ascii(NULL, obj);
+    else
+        return PyObject_CallFunctionObjArgs(s->encoder, obj, NULL);
+}
+
+static int
+_steal_list_append(PyObject *lst, PyObject *stolen)
+{
+    /* Append stolen and then decrement its reference count */
+    int rval = PyList_Append(lst, stolen);
+    Py_DECREF(stolen);
+    return rval;
+}
+
+static int
+encoder_listencode_obj(PyEncoderObject *s, PyObject *rval, PyObject *obj, Py_ssize_t indent_level)
+{
+    /* Encode Python object obj to a JSON term, rval is a PyList */
+    PyObject *newobj;
+    int rv;
+
+    if (obj == Py_None || obj == Py_True || obj == Py_False) {
+        PyObject *cstr = _encoded_const(obj);
+        if (cstr == NULL)
+            return -1;
+        return _steal_list_append(rval, cstr);
+    }
+    else if (PyString_Check(obj) || PyUnicode_Check(obj))
+    {
+        PyObject *encoded = encoder_encode_string(s, obj);
+        if (encoded == NULL)
+            return -1;
+        return _steal_list_append(rval, encoded);
+    }
+    else if (PyInt_Check(obj) || PyLong_Check(obj)) {
+        PyObject *encoded = PyObject_Str(obj);
+        if (encoded == NULL)
+            return -1;
+        return _steal_list_append(rval, encoded);
+    }
+    else if (PyFloat_Check(obj)) {
+        PyObject *encoded = encoder_encode_float(s, obj);
+        if (encoded == NULL)
+            return -1;
+        return _steal_list_append(rval, encoded);
+    }
+    else if (PyList_Check(obj) || PyTuple_Check(obj)) {
+        return encoder_listencode_list(s, rval, obj, indent_level);
+    }
+    else if (PyDict_Check(obj)) {
+        return encoder_listencode_dict(s, rval, obj, indent_level);
+    }
+    else {
+        PyObject *ident = NULL;
+        if (s->markers != Py_None) {
+            int has_key;
+            ident = PyLong_FromVoidPtr(obj);
+            if (ident == NULL)
+                return -1;
+            has_key = PyDict_Contains(s->markers, ident);
+            if (has_key) {
+                if (has_key != -1)
+                    PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+                Py_DECREF(ident);
+                return -1;
+            }
+            if (PyDict_SetItem(s->markers, ident, obj)) {
+                Py_DECREF(ident);
+                return -1;
+            }
+        }
+        newobj = PyObject_CallFunctionObjArgs(s->defaultfn, obj, NULL);
+        if (newobj == NULL) {
+            Py_XDECREF(ident);
+            return -1;
+        }
+        rv = encoder_listencode_obj(s, rval, newobj, indent_level);
+        Py_DECREF(newobj);
+        if (rv) {
+            Py_XDECREF(ident);
+            return -1;
+        }
+        if (ident != NULL) {
+            if (PyDict_DelItem(s->markers, ident)) {
+                Py_XDECREF(ident);
+                return -1;
+            }
+            Py_XDECREF(ident);
+        }
+        return rv;
+    }
+}
+
+static int
+encoder_listencode_dict(PyEncoderObject *s, PyObject *rval, PyObject *dct, Py_ssize_t indent_level)
+{
+    /* Encode Python dict dct a JSON term, rval is a PyList */
+    static PyObject *open_dict = NULL;
+    static PyObject *close_dict = NULL;
+    static PyObject *empty_dict = NULL;
+    PyObject *kstr = NULL;
+    PyObject *ident = NULL;
+    PyObject *key, *value;
+    Py_ssize_t pos;
+    int skipkeys;
+    Py_ssize_t idx;
+
+    if (open_dict == NULL || close_dict == NULL || empty_dict == NULL) {
+        open_dict = PyString_InternFromString("{");
+        close_dict = PyString_InternFromString("}");
+        empty_dict = PyString_InternFromString("{}");
+        if (open_dict == NULL || close_dict == NULL || empty_dict == NULL)
+            return -1;
+    }
+    if (PyDict_Size(dct) == 0)
+        return PyList_Append(rval, empty_dict);
+
+    if (s->markers != Py_None) {
+        int has_key;
+        ident = PyLong_FromVoidPtr(dct);
+        if (ident == NULL)
+            goto bail;
+        has_key = PyDict_Contains(s->markers, ident);
+        if (has_key) {
+            if (has_key != -1)
+                PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+            goto bail;
+        }
+        if (PyDict_SetItem(s->markers, ident, dct)) {
+            goto bail;
+        }
+    }
+
+    if (PyList_Append(rval, open_dict))
+        goto bail;
+
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level += 1;
+        /*
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        */
+    }
+
+    /* TODO: C speedup not implemented for sort_keys */
+
+    pos = 0;
+    skipkeys = PyObject_IsTrue(s->skipkeys);
+    idx = 0;
+    while (PyDict_Next(dct, &pos, &key, &value)) {
+        PyObject *encoded;
+
+        if (PyString_Check(key) || PyUnicode_Check(key)) {
+            Py_INCREF(key);
+            kstr = key;
+        }
+        else if (PyFloat_Check(key)) {
+            kstr = encoder_encode_float(s, key);
+            if (kstr == NULL)
+                goto bail;
+        }
+        else if (PyInt_Check(key) || PyLong_Check(key)) {
+            kstr = PyObject_Str(key);
+            if (kstr == NULL)
+                goto bail;
+        }
+        else if (key == Py_True || key == Py_False || key == Py_None) {
+            kstr = _encoded_const(key);
+            if (kstr == NULL)
+                goto bail;
+        }
+        else if (skipkeys) {
+            continue;
+        }
+        else {
+            /* TODO: include repr of key */
+            PyErr_SetString(PyExc_ValueError, "keys must be a string");
+            goto bail;
+        }
+
+        if (idx) {
+            if (PyList_Append(rval, s->item_separator))
+                goto bail;
+        }
+
+        encoded = encoder_encode_string(s, kstr);
+        Py_CLEAR(kstr);
+        if (encoded == NULL)
+            goto bail;
+        if (PyList_Append(rval, encoded)) {
+            Py_DECREF(encoded);
+            goto bail;
+        }
+        Py_DECREF(encoded);
+        if (PyList_Append(rval, s->key_separator))
+            goto bail;
+        if (encoder_listencode_obj(s, rval, value, indent_level))
+            goto bail;
+        idx += 1;
+    }
+    if (ident != NULL) {
+        if (PyDict_DelItem(s->markers, ident))
+            goto bail;
+        Py_CLEAR(ident);
+    }
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level -= 1;
+        /*
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        */
+    }
+    if (PyList_Append(rval, close_dict))
+        goto bail;
+    return 0;
+
+bail:
+    Py_XDECREF(kstr);
+    Py_XDECREF(ident);
+    return -1;
+}
+
+
+static int
+encoder_listencode_list(PyEncoderObject *s, PyObject *rval, PyObject *seq, Py_ssize_t indent_level)
+{
+    /* Encode Python list seq to a JSON term, rval is a PyList */
+    static PyObject *open_array = NULL;
+    static PyObject *close_array = NULL;
+    static PyObject *empty_array = NULL;
+    PyObject *ident = NULL;
+    PyObject *s_fast = NULL;
+    Py_ssize_t num_items;
+    PyObject **seq_items;
+    Py_ssize_t i;
+
+    if (open_array == NULL || close_array == NULL || empty_array == NULL) {
+        open_array = PyString_InternFromString("[");
+        close_array = PyString_InternFromString("]");
+        empty_array = PyString_InternFromString("[]");
+        if (open_array == NULL || close_array == NULL || empty_array == NULL)
+            return -1;
+    }
+    ident = NULL;
+    s_fast = PySequence_Fast(seq, "_iterencode_list needs a sequence");
+    if (s_fast == NULL)
+        return -1;
+    num_items = PySequence_Fast_GET_SIZE(s_fast);
+    if (num_items == 0) {
+        Py_DECREF(s_fast);
+        return PyList_Append(rval, empty_array);
+    }
+
+    if (s->markers != Py_None) {
+        int has_key;
+        ident = PyLong_FromVoidPtr(seq);
+        if (ident == NULL)
+            goto bail;
+        has_key = PyDict_Contains(s->markers, ident);
+        if (has_key) {
+            if (has_key != -1)
+                PyErr_SetString(PyExc_ValueError, "Circular reference detected");
+            goto bail;
+        }
+        if (PyDict_SetItem(s->markers, ident, seq)) {
+            goto bail;
+        }
+    }
+
+    seq_items = PySequence_Fast_ITEMS(s_fast);
+    if (PyList_Append(rval, open_array))
+        goto bail;
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level += 1;
+        /*
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        */
+    }
+    for (i = 0; i < num_items; i++) {
+        PyObject *obj = seq_items[i];
+        if (i) {
+            if (PyList_Append(rval, s->item_separator))
+                goto bail;
+        }
+        if (encoder_listencode_obj(s, rval, obj, indent_level))
+            goto bail;
+    }
+    if (ident != NULL) {
+        if (PyDict_DelItem(s->markers, ident))
+            goto bail;
+        Py_CLEAR(ident);
+    }
+    if (s->indent != Py_None) {
+        /* TODO: DOES NOT RUN */
+        indent_level -= 1;
+        /*
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        */
+    }
+    if (PyList_Append(rval, close_array))
+        goto bail;
+    Py_DECREF(s_fast);
+    return 0;
+
+bail:
+    Py_XDECREF(ident);
+    Py_DECREF(s_fast);
+    return -1;
+}
+
+static void
+encoder_dealloc(PyObject *self)
+{
+    /* Deallocate Encoder */
+    encoder_clear(self);
+    Py_TYPE(self)->tp_free(self);
+}
+
+static int
+encoder_traverse(PyObject *self, visitproc visit, void *arg)
+{
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    Py_VISIT(s->markers);
+    Py_VISIT(s->defaultfn);
+    Py_VISIT(s->encoder);
+    Py_VISIT(s->indent);
+    Py_VISIT(s->key_separator);
+    Py_VISIT(s->item_separator);
+    Py_VISIT(s->sort_keys);
+    Py_VISIT(s->skipkeys);
+    return 0;
+}
+
+static int
+encoder_clear(PyObject *self)
+{
+    /* Deallocate Encoder */
+    PyEncoderObject *s;
+    assert(PyEncoder_Check(self));
+    s = (PyEncoderObject *)self;
+    Py_CLEAR(s->markers);
+    Py_CLEAR(s->defaultfn);
+    Py_CLEAR(s->encoder);
+    Py_CLEAR(s->indent);
+    Py_CLEAR(s->key_separator);
+    Py_CLEAR(s->item_separator);
+    Py_CLEAR(s->sort_keys);
+    Py_CLEAR(s->skipkeys);
+    return 0;
+}
+
+PyDoc_STRVAR(encoder_doc, "_iterencode(obj, _current_indent_level) -> iterable");
+
+static
+PyTypeObject PyEncoderType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                    /* tp_internal */
+    "simplejson._speedups.Encoder",       /* tp_name */
+    sizeof(PyEncoderObject), /* tp_basicsize */
+    0,                    /* tp_itemsize */
+    encoder_dealloc, /* tp_dealloc */
+    0,                    /* tp_print */
+    0,                    /* tp_getattr */
+    0,                    /* tp_setattr */
+    0,                    /* tp_compare */
+    0,                    /* tp_repr */
+    0,                    /* tp_as_number */
+    0,                    /* tp_as_sequence */
+    0,                    /* tp_as_mapping */
+    0,                    /* tp_hash */
+    encoder_call,         /* tp_call */
+    0,                    /* tp_str */
+    0,                    /* tp_getattro */
+    0,                    /* tp_setattro */
+    0,                    /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,   /* tp_flags */
+    encoder_doc,          /* tp_doc */
+    encoder_traverse,     /* tp_traverse */
+    encoder_clear,        /* tp_clear */
+    0,                    /* tp_richcompare */
+    0,                    /* tp_weaklistoffset */
+    0,                    /* tp_iter */
+    0,                    /* tp_iternext */
+    0,                    /* tp_methods */
+    encoder_members,      /* tp_members */
+    0,                    /* tp_getset */
+    0,                    /* tp_base */
+    0,                    /* tp_dict */
+    0,                    /* tp_descr_get */
+    0,                    /* tp_descr_set */
+    0,                    /* tp_dictoffset */
+    encoder_init,         /* tp_init */
+    0,                    /* tp_alloc */
+    encoder_new,          /* tp_new */
+    0,                    /* tp_free */
+};
+
+static PyMethodDef speedups_methods[] = {
+    {"encode_basestring_ascii",
+        (PyCFunction)py_encode_basestring_ascii,
+        METH_O,
+        pydoc_encode_basestring_ascii},
+    {"scanstring",
+        (PyCFunction)py_scanstring,
+        METH_VARARGS,
+        pydoc_scanstring},
+    {NULL, NULL, 0, NULL}
+};
+
+PyDoc_STRVAR(module_doc,
+"simplejson speedups\n");
+
+void
+init_speedups(void)
+{
+    PyObject *m;
+    PyScannerType.tp_new = PyType_GenericNew;
+    if (PyType_Ready(&PyScannerType) < 0)
+        return;
+    PyEncoderType.tp_new = PyType_GenericNew;
+    if (PyType_Ready(&PyEncoderType) < 0)
+        return;
+    m = Py_InitModule3("_speedups", speedups_methods, module_doc);
+    Py_INCREF((PyObject*)&PyScannerType);
+    PyModule_AddObject(m, "make_scanner", (PyObject*)&PyScannerType);
+    Py_INCREF((PyObject*)&PyEncoderType);
+    PyModule_AddObject(m, "make_encoder", (PyObject*)&PyEncoderType);
+}
diff --git a/ovsdb/simplejson/decoder.py b/ovsdb/simplejson/decoder.py
new file mode 100644 (file)
index 0000000..b769ea4
--- /dev/null
@@ -0,0 +1,354 @@
+"""Implementation of JSONDecoder
+"""
+import re
+import sys
+import struct
+
+from simplejson.scanner import make_scanner
+try:
+    from simplejson._speedups import scanstring as c_scanstring
+except ImportError:
+    c_scanstring = None
+
+__all__ = ['JSONDecoder']
+
+FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL
+
+def _floatconstants():
+    _BYTES = '7FF80000000000007FF0000000000000'.decode('hex')
+    if sys.byteorder != 'big':
+        _BYTES = _BYTES[:8][::-1] + _BYTES[8:][::-1]
+    nan, inf = struct.unpack('dd', _BYTES)
+    return nan, inf, -inf
+
+NaN, PosInf, NegInf = _floatconstants()
+
+
+def linecol(doc, pos):
+    lineno = doc.count('\n', 0, pos) + 1
+    if lineno == 1:
+        colno = pos
+    else:
+        colno = pos - doc.rindex('\n', 0, pos)
+    return lineno, colno
+
+
+def errmsg(msg, doc, pos, end=None):
+    # Note that this function is called from _speedups
+    lineno, colno = linecol(doc, pos)
+    if end is None:
+        #fmt = '{0}: line {1} column {2} (char {3})'
+        #return fmt.format(msg, lineno, colno, pos)
+        fmt = '%s: line %d column %d (char %d)'
+        return fmt % (msg, lineno, colno, pos)
+    endlineno, endcolno = linecol(doc, end)
+    #fmt = '{0}: line {1} column {2} - line {3} column {4} (char {5} - {6})'
+    #return fmt.format(msg, lineno, colno, endlineno, endcolno, pos, end)
+    fmt = '%s: line %d column %d - line %d column %d (char %d - %d)'
+    return fmt % (msg, lineno, colno, endlineno, endcolno, pos, end)
+
+
+_CONSTANTS = {
+    '-Infinity': NegInf,
+    'Infinity': PosInf,
+    'NaN': NaN,
+}
+
+STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS)
+BACKSLASH = {
+    '"': u'"', '\\': u'\\', '/': u'/',
+    'b': u'\b', 'f': u'\f', 'n': u'\n', 'r': u'\r', 't': u'\t',
+}
+
+DEFAULT_ENCODING = "utf-8"
+
+def py_scanstring(s, end, encoding=None, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match):
+    """Scan the string s for a JSON string. End is the index of the
+    character in s after the quote that started the JSON string.
+    Unescapes all valid JSON string escape sequences and raises ValueError
+    on attempt to decode an invalid string. If strict is False then literal
+    control characters are allowed in the string.
+    
+    Returns a tuple of the decoded string and the index of the character in s
+    after the end quote."""
+    if encoding is None:
+        encoding = DEFAULT_ENCODING
+    chunks = []
+    _append = chunks.append
+    begin = end - 1
+    while 1:
+        chunk = _m(s, end)
+        if chunk is None:
+            raise ValueError(
+                errmsg("Unterminated string starting at", s, begin))
+        end = chunk.end()
+        content, terminator = chunk.groups()
+        # Content is contains zero or more unescaped string characters
+        if content:
+            if not isinstance(content, unicode):
+                content = unicode(content, encoding)
+            _append(content)
+        # Terminator is the end of string, a literal control character,
+        # or a backslash denoting that an escape sequence follows
+        if terminator == '"':
+            break
+        elif terminator != '\\':
+            if strict:
+                msg = "Invalid control character %r at" % (terminator,)
+                #msg = "Invalid control character {0!r} at".format(terminator)
+                raise ValueError(errmsg(msg, s, end))
+            else:
+                _append(terminator)
+                continue
+        try:
+            esc = s[end]
+        except IndexError:
+            raise ValueError(
+                errmsg("Unterminated string starting at", s, begin))
+        # If not a unicode escape sequence, must be in the lookup table
+        if esc != 'u':
+            try:
+                char = _b[esc]
+            except KeyError:
+                msg = "Invalid \\escape: " + repr(esc)
+                raise ValueError(errmsg(msg, s, end))
+            end += 1
+        else:
+            # Unicode escape sequence
+            esc = s[end + 1:end + 5]
+            next_end = end + 5
+            if len(esc) != 4:
+                msg = "Invalid \\uXXXX escape"
+                raise ValueError(errmsg(msg, s, end))
+            uni = int(esc, 16)
+            # Check for surrogate pair on UCS-4 systems
+            if 0xd800 <= uni <= 0xdbff and sys.maxunicode > 65535:
+                msg = "Invalid \\uXXXX\\uXXXX surrogate pair"
+                if not s[end + 5:end + 7] == '\\u':
+                    raise ValueError(errmsg(msg, s, end))
+                esc2 = s[end + 7:end + 11]
+                if len(esc2) != 4:
+                    raise ValueError(errmsg(msg, s, end))
+                uni2 = int(esc2, 16)
+                uni = 0x10000 + (((uni - 0xd800) << 10) | (uni2 - 0xdc00))
+                next_end += 6
+            char = unichr(uni)
+            end = next_end
+        # Append the unescaped character
+        _append(char)
+    return u''.join(chunks), end
+
+
+# Use speedup if available
+scanstring = c_scanstring or py_scanstring
+
+WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS)
+WHITESPACE_STR = ' \t\n\r'
+
+def JSONObject((s, end), encoding, strict, scan_once, object_hook, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+    pairs = {}
+    # Use a slice to prevent IndexError from being raised, the following
+    # check will raise a more specific ValueError if the string is empty
+    nextchar = s[end:end + 1]
+    # Normally we expect nextchar == '"'
+    if nextchar != '"':
+        if nextchar in _ws:
+            end = _w(s, end).end()
+            nextchar = s[end:end + 1]
+        # Trivial empty object
+        if nextchar == '}':
+            return pairs, end + 1
+        elif nextchar != '"':
+            raise ValueError(errmsg("Expecting property name", s, end))
+    end += 1
+    while True:
+        key, end = scanstring(s, end, encoding, strict)
+
+        # To skip some function call overhead we optimize the fast paths where
+        # the JSON key separator is ": " or just ":".
+        if s[end:end + 1] != ':':
+            end = _w(s, end).end()
+            if s[end:end + 1] != ':':
+                raise ValueError(errmsg("Expecting : delimiter", s, end))
+
+        end += 1
+
+        try:
+            if s[end] in _ws:
+                end += 1
+                if s[end] in _ws:
+                    end = _w(s, end + 1).end()
+        except IndexError:
+            pass
+
+        try:
+            value, end = scan_once(s, end)
+        except StopIteration:
+            raise ValueError(errmsg("Expecting object", s, end))
+        pairs[key] = value
+
+        try:
+            nextchar = s[end]
+            if nextchar in _ws:
+                end = _w(s, end + 1).end()
+                nextchar = s[end]
+        except IndexError:
+            nextchar = ''
+        end += 1
+
+        if nextchar == '}':
+            break
+        elif nextchar != ',':
+            raise ValueError(errmsg("Expecting , delimiter", s, end - 1))
+
+        try:
+            nextchar = s[end]
+            if nextchar in _ws:
+                end += 1
+                nextchar = s[end]
+                if nextchar in _ws:
+                    end = _w(s, end + 1).end()
+                    nextchar = s[end]
+        except IndexError:
+            nextchar = ''
+
+        end += 1
+        if nextchar != '"':
+            raise ValueError(errmsg("Expecting property name", s, end - 1))
+
+    if object_hook is not None:
+        pairs = object_hook(pairs)
+    return pairs, end
+
+def JSONArray((s, end), scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+    values = []
+    nextchar = s[end:end + 1]
+    if nextchar in _ws:
+        end = _w(s, end + 1).end()
+        nextchar = s[end:end + 1]
+    # Look-ahead for trivial empty array
+    if nextchar == ']':
+        return values, end + 1
+    _append = values.append
+    while True:
+        try:
+            value, end = scan_once(s, end)
+        except StopIteration:
+            raise ValueError(errmsg("Expecting object", s, end))
+        _append(value)
+        nextchar = s[end:end + 1]
+        if nextchar in _ws:
+            end = _w(s, end + 1).end()
+            nextchar = s[end:end + 1]
+        end += 1
+        if nextchar == ']':
+            break
+        elif nextchar != ',':
+            raise ValueError(errmsg("Expecting , delimiter", s, end))
+
+        try:
+            if s[end] in _ws:
+                end += 1
+                if s[end] in _ws:
+                    end = _w(s, end + 1).end()
+        except IndexError:
+            pass
+
+    return values, end
+
+class JSONDecoder(object):
+    """Simple JSON <http://json.org> decoder
+
+    Performs the following translations in decoding by default:
+
+    +---------------+-------------------+
+    | JSON          | Python            |
+    +===============+===================+
+    | object        | dict              |
+    +---------------+-------------------+
+    | array         | list              |
+    +---------------+-------------------+
+    | string        | unicode           |
+    +---------------+-------------------+
+    | number (int)  | int, long         |
+    +---------------+-------------------+
+    | number (real) | float             |
+    +---------------+-------------------+
+    | true          | True              |
+    +---------------+-------------------+
+    | false         | False             |
+    +---------------+-------------------+
+    | null          | None              |
+    +---------------+-------------------+
+
+    It also understands ``NaN``, ``Infinity``, and ``-Infinity`` as
+    their corresponding ``float`` values, which is outside the JSON spec.
+
+    """
+
+    def __init__(self, encoding=None, object_hook=None, parse_float=None,
+            parse_int=None, parse_constant=None, strict=True):
+        """``encoding`` determines the encoding used to interpret any ``str``
+        objects decoded by this instance (utf-8 by default).  It has no
+        effect when decoding ``unicode`` objects.
+
+        Note that currently only encodings that are a superset of ASCII work,
+        strings of other encodings should be passed in as ``unicode``.
+
+        ``object_hook``, if specified, will be called with the result
+        of every JSON object decoded and its return value will be used in
+        place of the given ``dict``.  This can be used to provide custom
+        deserializations (e.g. to support JSON-RPC class hinting).
+
+        ``parse_float``, if specified, will be called with the string
+        of every JSON float to be decoded. By default this is equivalent to
+        float(num_str). This can be used to use another datatype or parser
+        for JSON floats (e.g. decimal.Decimal).
+
+        ``parse_int``, if specified, will be called with the string
+        of every JSON int to be decoded. By default this is equivalent to
+        int(num_str). This can be used to use another datatype or parser
+        for JSON integers (e.g. float).
+
+        ``parse_constant``, if specified, will be called with one of the
+        following strings: -Infinity, Infinity, NaN.
+        This can be used to raise an exception if invalid JSON numbers
+        are encountered.
+
+        """
+        self.encoding = encoding
+        self.object_hook = object_hook
+        self.parse_float = parse_float or float
+        self.parse_int = parse_int or int
+        self.parse_constant = parse_constant or _CONSTANTS.__getitem__
+        self.strict = strict
+        self.parse_object = JSONObject
+        self.parse_array = JSONArray
+        self.parse_string = scanstring
+        self.scan_once = make_scanner(self)
+
+    def decode(self, s, _w=WHITESPACE.match):
+        """Return the Python representation of ``s`` (a ``str`` or ``unicode``
+        instance containing a JSON document)
+
+        """
+        obj, end = self.raw_decode(s, idx=_w(s, 0).end())
+        end = _w(s, end).end()
+        if end != len(s):
+            raise ValueError(errmsg("Extra data", s, end, len(s)))
+        return obj
+
+    def raw_decode(self, s, idx=0):
+        """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` beginning
+        with a JSON document) and return a 2-tuple of the Python
+        representation and the index in ``s`` where the document ended.
+
+        This can be used to decode a JSON document from a string that may
+        have extraneous data at the end.
+
+        """
+        try:
+            obj, end = self.scan_once(s, idx)
+        except StopIteration:
+            raise ValueError("No JSON object could be decoded")
+        return obj, end
diff --git a/ovsdb/simplejson/encoder.py b/ovsdb/simplejson/encoder.py
new file mode 100644 (file)
index 0000000..cf58290
--- /dev/null
@@ -0,0 +1,440 @@
+"""Implementation of JSONEncoder
+"""
+import re
+
+try:
+    from simplejson._speedups import encode_basestring_ascii as c_encode_basestring_ascii
+except ImportError:
+    c_encode_basestring_ascii = None
+try:
+    from simplejson._speedups import make_encoder as c_make_encoder
+except ImportError:
+    c_make_encoder = None
+
+ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
+ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
+HAS_UTF8 = re.compile(r'[\x80-\xff]')
+ESCAPE_DCT = {
+    '\\': '\\\\',
+    '"': '\\"',
+    '\b': '\\b',
+    '\f': '\\f',
+    '\n': '\\n',
+    '\r': '\\r',
+    '\t': '\\t',
+}
+for i in range(0x20):
+    #ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
+    ESCAPE_DCT.setdefault(chr(i), '\\u%04x' % (i,))
+
+# Assume this produces an infinity on all machines (probably not guaranteed)
+INFINITY = float('1e66666')
+FLOAT_REPR = repr
+
+def encode_basestring(s):
+    """Return a JSON representation of a Python string
+
+    """
+    def replace(match):
+        return ESCAPE_DCT[match.group(0)]
+    return '"' + ESCAPE.sub(replace, s) + '"'
+
+
+def py_encode_basestring_ascii(s):
+    """Return an ASCII-only JSON representation of a Python string
+
+    """
+    if isinstance(s, str) and HAS_UTF8.search(s) is not None:
+        s = s.decode('utf-8')
+    def replace(match):
+        s = match.group(0)
+        try:
+            return ESCAPE_DCT[s]
+        except KeyError:
+            n = ord(s)
+            if n < 0x10000:
+                #return '\\u{0:04x}'.format(n)
+                return '\\u%04x' % (n,)
+            else:
+                # surrogate pair
+                n -= 0x10000
+                s1 = 0xd800 | ((n >> 10) & 0x3ff)
+                s2 = 0xdc00 | (n & 0x3ff)
+                #return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
+                return '\\u%04x\\u%04x' % (s1, s2)
+    return '"' + str(ESCAPE_ASCII.sub(replace, s)) + '"'
+
+
+encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii
+
+class JSONEncoder(object):
+    """Extensible JSON <http://json.org> encoder for Python data structures.
+
+    Supports the following objects and types by default:
+
+    +-------------------+---------------+
+    | Python            | JSON          |
+    +===================+===============+
+    | dict              | object        |
+    +-------------------+---------------+
+    | list, tuple       | array         |
+    +-------------------+---------------+
+    | str, unicode      | string        |
+    +-------------------+---------------+
+    | int, long, float  | number        |
+    +-------------------+---------------+
+    | True              | true          |
+    +-------------------+---------------+
+    | False             | false         |
+    +-------------------+---------------+
+    | None              | null          |
+    +-------------------+---------------+
+
+    To extend this to recognize other objects, subclass and implement a
+    ``.default()`` method with another method that returns a serializable
+    object for ``o`` if possible, otherwise it should call the superclass
+    implementation (to raise ``TypeError``).
+
+    """
+    item_separator = ', '
+    key_separator = ': '
+    def __init__(self, skipkeys=False, ensure_ascii=True,
+            check_circular=True, allow_nan=True, sort_keys=False,
+            indent=None, separators=None, encoding='utf-8', default=None):
+        """Constructor for JSONEncoder, with sensible defaults.
+
+        If skipkeys is false, then it is a TypeError to attempt
+        encoding of keys that are not str, int, long, float or None.  If
+        skipkeys is True, such items are simply skipped.
+
+        If ensure_ascii is true, the output is guaranteed to be str
+        objects with all incoming unicode characters escaped.  If
+        ensure_ascii is false, the output will be unicode object.
+
+        If check_circular is true, then lists, dicts, and custom encoded
+        objects will be checked for circular references during encoding to
+        prevent an infinite recursion (which would cause an OverflowError).
+        Otherwise, no such check takes place.
+
+        If allow_nan is true, then NaN, Infinity, and -Infinity will be
+        encoded as such.  This behavior is not JSON specification compliant,
+        but is consistent with most JavaScript based encoders and decoders.
+        Otherwise, it will be a ValueError to encode such floats.
+
+        If sort_keys is true, then the output of dictionaries will be
+        sorted by key; this is useful for regression tests to ensure
+        that JSON serializations can be compared on a day-to-day basis.
+
+        If indent is a non-negative integer, then JSON array
+        elements and object members will be pretty-printed with that
+        indent level.  An indent level of 0 will only insert newlines.
+        None is the most compact representation.
+
+        If specified, separators should be a (item_separator, key_separator)
+        tuple.  The default is (', ', ': ').  To get the most compact JSON
+        representation you should specify (',', ':') to eliminate whitespace.
+
+        If specified, default is a function that gets called for objects
+        that can't otherwise be serialized.  It should return a JSON encodable
+        version of the object or raise a ``TypeError``.
+
+        If encoding is not None, then all input strings will be
+        transformed into unicode using that encoding prior to JSON-encoding.
+        The default is UTF-8.
+
+        """
+
+        self.skipkeys = skipkeys
+        self.ensure_ascii = ensure_ascii
+        self.check_circular = check_circular
+        self.allow_nan = allow_nan
+        self.sort_keys = sort_keys
+        self.indent = indent
+        if separators is not None:
+            self.item_separator, self.key_separator = separators
+        if default is not None:
+            self.default = default
+        self.encoding = encoding
+
+    def default(self, o):
+        """Implement this method in a subclass such that it returns
+        a serializable object for ``o``, or calls the base implementation
+        (to raise a ``TypeError``).
+
+        For example, to support arbitrary iterators, you could
+        implement default like this::
+
+            def default(self, o):
+                try:
+                    iterable = iter(o)
+                except TypeError:
+                    pass
+                else:
+                    return list(iterable)
+                return JSONEncoder.default(self, o)
+
+        """
+        raise TypeError(repr(o) + " is not JSON serializable")
+
+    def encode(self, o):
+        """Return a JSON string representation of a Python data structure.
+
+        >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
+        '{"foo": ["bar", "baz"]}'
+
+        """
+        # This is for extremely simple cases and benchmarks.
+        if isinstance(o, basestring):
+            if isinstance(o, str):
+                _encoding = self.encoding
+                if (_encoding is not None
+                        and not (_encoding == 'utf-8')):
+                    o = o.decode(_encoding)
+            if self.ensure_ascii:
+                return encode_basestring_ascii(o)
+            else:
+                return encode_basestring(o)
+        # This doesn't pass the iterator directly to ''.join() because the
+        # exceptions aren't as detailed.  The list call should be roughly
+        # equivalent to the PySequence_Fast that ''.join() would do.
+        chunks = self.iterencode(o, _one_shot=True)
+        if not isinstance(chunks, (list, tuple)):
+            chunks = list(chunks)
+        return ''.join(chunks)
+
+    def iterencode(self, o, _one_shot=False):
+        """Encode the given object and yield each string
+        representation as available.
+
+        For example::
+
+            for chunk in JSONEncoder().iterencode(bigobject):
+                mysocket.write(chunk)
+
+        """
+        if self.check_circular:
+            markers = {}
+        else:
+            markers = None
+        if self.ensure_ascii:
+            _encoder = encode_basestring_ascii
+        else:
+            _encoder = encode_basestring
+        if self.encoding != 'utf-8':
+            def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
+                if isinstance(o, str):
+                    o = o.decode(_encoding)
+                return _orig_encoder(o)
+
+        def floatstr(o, allow_nan=self.allow_nan, _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):
+            # Check for specials.  Note that this type of test is processor- and/or
+            # platform-specific, so do tests which don't depend on the internals.
+
+            if o != o:
+                text = 'NaN'
+            elif o == _inf:
+                text = 'Infinity'
+            elif o == _neginf:
+                text = '-Infinity'
+            else:
+                return _repr(o)
+
+            if not allow_nan:
+                raise ValueError(
+                    "Out of range float values are not JSON compliant: " +
+                    repr(o))
+
+            return text
+
+
+        if _one_shot and c_make_encoder is not None and not self.indent and not self.sort_keys:
+            _iterencode = c_make_encoder(
+                markers, self.default, _encoder, self.indent,
+                self.key_separator, self.item_separator, self.sort_keys,
+                self.skipkeys, self.allow_nan)
+        else:
+            _iterencode = _make_iterencode(
+                markers, self.default, _encoder, self.indent, floatstr,
+                self.key_separator, self.item_separator, self.sort_keys,
+                self.skipkeys, _one_shot)
+        return _iterencode(o, 0)
+
+def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, _key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
+        ## HACK: hand-optimized bytecode; turn globals into locals
+        False=False,
+        True=True,
+        ValueError=ValueError,
+        basestring=basestring,
+        dict=dict,
+        float=float,
+        id=id,
+        int=int,
+        isinstance=isinstance,
+        list=list,
+        long=long,
+        str=str,
+        tuple=tuple,
+    ):
+
+    def _iterencode_list(lst, _current_indent_level):
+        if not lst:
+            yield '[]'
+            return
+        if markers is not None:
+            markerid = id(lst)
+            if markerid in markers:
+                raise ValueError("Circular reference detected")
+            markers[markerid] = lst
+        buf = '['
+        if _indent is not None:
+            _current_indent_level += 1
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            separator = _item_separator + newline_indent
+            buf += newline_indent
+        else:
+            newline_indent = None
+            separator = _item_separator
+        first = True
+        for value in lst:
+            if first:
+                first = False
+            else:
+                buf = separator
+            if isinstance(value, basestring):
+                yield buf + _encoder(value)
+            elif value is None:
+                yield buf + 'null'
+            elif value is True:
+                yield buf + 'true'
+            elif value is False:
+                yield buf + 'false'
+            elif isinstance(value, (int, long)):
+                yield buf + str(value)
+            elif isinstance(value, float):
+                yield buf + _floatstr(value)
+            else:
+                yield buf
+                if isinstance(value, (list, tuple)):
+                    chunks = _iterencode_list(value, _current_indent_level)
+                elif isinstance(value, dict):
+                    chunks = _iterencode_dict(value, _current_indent_level)
+                else:
+                    chunks = _iterencode(value, _current_indent_level)
+                for chunk in chunks:
+                    yield chunk
+        if newline_indent is not None:
+            _current_indent_level -= 1
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        yield ']'
+        if markers is not None:
+            del markers[markerid]
+
+    def _iterencode_dict(dct, _current_indent_level):
+        if not dct:
+            yield '{}'
+            return
+        if markers is not None:
+            markerid = id(dct)
+            if markerid in markers:
+                raise ValueError("Circular reference detected")
+            markers[markerid] = dct
+        yield '{'
+        if _indent is not None:
+            _current_indent_level += 1
+            newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+            item_separator = _item_separator + newline_indent
+            yield newline_indent
+        else:
+            newline_indent = None
+            item_separator = _item_separator
+        first = True
+        if _sort_keys:
+            items = dct.items()
+            items.sort(key=lambda kv: kv[0])
+        else:
+            items = dct.iteritems()
+        for key, value in items:
+            if isinstance(key, basestring):
+                pass
+            # JavaScript is weakly typed for these, so it makes sense to
+            # also allow them.  Many encoders seem to do something like this.
+            elif isinstance(key, float):
+                key = _floatstr(key)
+            elif key is True:
+                key = 'true'
+            elif key is False:
+                key = 'false'
+            elif key is None:
+                key = 'null'
+            elif isinstance(key, (int, long)):
+                key = str(key)
+            elif _skipkeys:
+                continue
+            else:
+                raise TypeError("key " + repr(key) + " is not a string")
+            if first:
+                first = False
+            else:
+                yield item_separator
+            yield _encoder(key)
+            yield _key_separator
+            if isinstance(value, basestring):
+                yield _encoder(value)
+            elif value is None:
+                yield 'null'
+            elif value is True:
+                yield 'true'
+            elif value is False:
+                yield 'false'
+            elif isinstance(value, (int, long)):
+                yield str(value)
+            elif isinstance(value, float):
+                yield _floatstr(value)
+            else:
+                if isinstance(value, (list, tuple)):
+                    chunks = _iterencode_list(value, _current_indent_level)
+                elif isinstance(value, dict):
+                    chunks = _iterencode_dict(value, _current_indent_level)
+                else:
+                    chunks = _iterencode(value, _current_indent_level)
+                for chunk in chunks:
+                    yield chunk
+        if newline_indent is not None:
+            _current_indent_level -= 1
+            yield '\n' + (' ' * (_indent * _current_indent_level))
+        yield '}'
+        if markers is not None:
+            del markers[markerid]
+
+    def _iterencode(o, _current_indent_level):
+        if isinstance(o, basestring):
+            yield _encoder(o)
+        elif o is None:
+            yield 'null'
+        elif o is True:
+            yield 'true'
+        elif o is False:
+            yield 'false'
+        elif isinstance(o, (int, long)):
+            yield str(o)
+        elif isinstance(o, float):
+            yield _floatstr(o)
+        elif isinstance(o, (list, tuple)):
+            for chunk in _iterencode_list(o, _current_indent_level):
+                yield chunk
+        elif isinstance(o, dict):
+            for chunk in _iterencode_dict(o, _current_indent_level):
+                yield chunk
+        else:
+            if markers is not None:
+                markerid = id(o)
+                if markerid in markers:
+                    raise ValueError("Circular reference detected")
+                markers[markerid] = o
+            o = _default(o)
+            for chunk in _iterencode(o, _current_indent_level):
+                yield chunk
+            if markers is not None:
+                del markers[markerid]
+
+    return _iterencode
diff --git a/ovsdb/simplejson/scanner.py b/ovsdb/simplejson/scanner.py
new file mode 100644 (file)
index 0000000..adbc6ec
--- /dev/null
@@ -0,0 +1,65 @@
+"""JSON token scanner
+"""
+import re
+try:
+    from simplejson._speedups import make_scanner as c_make_scanner
+except ImportError:
+    c_make_scanner = None
+
+__all__ = ['make_scanner']
+
+NUMBER_RE = re.compile(
+    r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
+    (re.VERBOSE | re.MULTILINE | re.DOTALL))
+
+def py_make_scanner(context):
+    parse_object = context.parse_object
+    parse_array = context.parse_array
+    parse_string = context.parse_string
+    match_number = NUMBER_RE.match
+    encoding = context.encoding
+    strict = context.strict
+    parse_float = context.parse_float
+    parse_int = context.parse_int
+    parse_constant = context.parse_constant
+    object_hook = context.object_hook
+
+    def _scan_once(string, idx):
+        try:
+            nextchar = string[idx]
+        except IndexError:
+            raise StopIteration
+
+        if nextchar == '"':
+            return parse_string(string, idx + 1, encoding, strict)
+        elif nextchar == '{':
+            return parse_object((string, idx + 1), encoding, strict, _scan_once, object_hook)
+        elif nextchar == '[':
+            return parse_array((string, idx + 1), _scan_once)
+        elif nextchar == 'n' and string[idx:idx + 4] == 'null':
+            return None, idx + 4
+        elif nextchar == 't' and string[idx:idx + 4] == 'true':
+            return True, idx + 4
+        elif nextchar == 'f' and string[idx:idx + 5] == 'false':
+            return False, idx + 5
+
+        m = match_number(string, idx)
+        if m is not None:
+            integer, frac, exp = m.groups()
+            if frac or exp:
+                res = parse_float(integer + (frac or '') + (exp or ''))
+            else:
+                res = parse_int(integer)
+            return res, m.end()
+        elif nextchar == 'N' and string[idx:idx + 3] == 'NaN':
+            return parse_constant('NaN'), idx + 3
+        elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity':
+            return parse_constant('Infinity'), idx + 8
+        elif nextchar == '-' and string[idx:idx + 9] == '-Infinity':
+            return parse_constant('-Infinity'), idx + 9
+        else:
+            raise StopIteration
+
+    return _scan_once
+
+make_scanner = c_make_scanner or py_make_scanner
diff --git a/ovsdb/simplejson/tests/__init__.py b/ovsdb/simplejson/tests/__init__.py
new file mode 100644 (file)
index 0000000..17c9796
--- /dev/null
@@ -0,0 +1,23 @@
+import unittest
+import doctest
+
+def additional_tests():
+    import simplejson
+    import simplejson.encoder
+    import simplejson.decoder
+    suite = unittest.TestSuite()
+    for mod in (simplejson, simplejson.encoder, simplejson.decoder):
+        suite.addTest(doctest.DocTestSuite(mod))
+    suite.addTest(doctest.DocFileSuite('../../index.rst'))
+    return suite
+
+def main():
+    suite = additional_tests()
+    runner = unittest.TextTestRunner()
+    runner.run(suite)
+
+if __name__ == '__main__':
+    import os
+    import sys
+    sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
+    main()
diff --git a/ovsdb/simplejson/tests/test_check_circular.py b/ovsdb/simplejson/tests/test_check_circular.py
new file mode 100644 (file)
index 0000000..af6463d
--- /dev/null
@@ -0,0 +1,30 @@
+from unittest import TestCase
+import simplejson as json
+
+def default_iterable(obj):
+    return list(obj)
+
+class TestCheckCircular(TestCase):
+    def test_circular_dict(self):
+        dct = {}
+        dct['a'] = dct
+        self.assertRaises(ValueError, json.dumps, dct)
+
+    def test_circular_list(self):
+        lst = []
+        lst.append(lst)
+        self.assertRaises(ValueError, json.dumps, lst)
+
+    def test_circular_composite(self):
+        dct2 = {}
+        dct2['a'] = []
+        dct2['a'].append(dct2)
+        self.assertRaises(ValueError, json.dumps, dct2)
+
+    def test_circular_default(self):
+        json.dumps([set()], default=default_iterable)
+        self.assertRaises(TypeError, json.dumps, [set()])
+
+    def test_circular_off_default(self):
+        json.dumps([set()], default=default_iterable, check_circular=False)
+        self.assertRaises(TypeError, json.dumps, [set()], check_circular=False)
diff --git a/ovsdb/simplejson/tests/test_decode.py b/ovsdb/simplejson/tests/test_decode.py
new file mode 100644 (file)
index 0000000..1cd701d
--- /dev/null
@@ -0,0 +1,22 @@
+import decimal
+from unittest import TestCase
+
+import simplejson as json
+
+class TestDecode(TestCase):
+    def test_decimal(self):
+        rval = json.loads('1.1', parse_float=decimal.Decimal)
+        self.assert_(isinstance(rval, decimal.Decimal))
+        self.assertEquals(rval, decimal.Decimal('1.1'))
+
+    def test_float(self):
+        rval = json.loads('1', parse_int=float)
+        self.assert_(isinstance(rval, float))
+        self.assertEquals(rval, 1.0)
+
+    def test_decoder_optimizations(self):
+        # Several optimizations were made that skip over calls to
+        # the whitespace regex, so this test is designed to try and
+        # exercise the uncommon cases. The array cases are already covered.
+        rval = json.loads('{   "key"    :    "value"    ,  "k":"v"    }')
+        self.assertEquals(rval, {"key":"value", "k":"v"})
diff --git a/ovsdb/simplejson/tests/test_default.py b/ovsdb/simplejson/tests/test_default.py
new file mode 100644 (file)
index 0000000..139e42b
--- /dev/null
@@ -0,0 +1,9 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class TestDefault(TestCase):
+    def test_default(self):
+        self.assertEquals(
+            json.dumps(type, default=repr),
+            json.dumps(repr(type)))
diff --git a/ovsdb/simplejson/tests/test_dump.py b/ovsdb/simplejson/tests/test_dump.py
new file mode 100644 (file)
index 0000000..4de37cf
--- /dev/null
@@ -0,0 +1,21 @@
+from unittest import TestCase
+from cStringIO import StringIO
+
+import simplejson as json
+
+class TestDump(TestCase):
+    def test_dump(self):
+        sio = StringIO()
+        json.dump({}, sio)
+        self.assertEquals(sio.getvalue(), '{}')
+
+    def test_dumps(self):
+        self.assertEquals(json.dumps({}), '{}')
+
+    def test_encode_truefalse(self):
+        self.assertEquals(json.dumps(
+                 {True: False, False: True}, sort_keys=True),
+                 '{"false": true, "true": false}')
+        self.assertEquals(json.dumps(
+                {2: 3.0, 4.0: 5L, False: 1, 6L: True, "7": 0}, sort_keys=True),
+                '{"false": 1, "2": 3.0, "4.0": 5, "6": true, "7": 0}')
diff --git a/ovsdb/simplejson/tests/test_encode_basestring_ascii.py b/ovsdb/simplejson/tests/test_encode_basestring_ascii.py
new file mode 100644 (file)
index 0000000..7128495
--- /dev/null
@@ -0,0 +1,38 @@
+from unittest import TestCase
+
+import simplejson.encoder
+
+CASES = [
+    (u'/\\"\ucafe\ubabe\uab98\ufcde\ubcda\uef4a\x08\x0c\n\r\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?', '"/\\\\\\"\\ucafe\\ubabe\\uab98\\ufcde\\ubcda\\uef4a\\b\\f\\n\\r\\t`1~!@#$%^&*()_+-=[]{}|;:\',./<>?"'),
+    (u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
+    (u'controls', '"controls"'),
+    (u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
+    (u'{"object with 1 member":["array with 1 element"]}', '"{\\"object with 1 member\\":[\\"array with 1 element\\"]}"'),
+    (u' s p a c e d ', '" s p a c e d "'),
+    (u'\U0001d120', '"\\ud834\\udd20"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    ('\xce\xb1\xce\xa9', '"\\u03b1\\u03a9"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    ('\xce\xb1\xce\xa9', '"\\u03b1\\u03a9"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    (u'\u03b1\u03a9', '"\\u03b1\\u03a9"'),
+    (u"`1~!@#$%^&*()_+-={':[,]}|;.</>?", '"`1~!@#$%^&*()_+-={\':[,]}|;.</>?"'),
+    (u'\x08\x0c\n\r\t', '"\\b\\f\\n\\r\\t"'),
+    (u'\u0123\u4567\u89ab\ucdef\uabcd\uef4a', '"\\u0123\\u4567\\u89ab\\ucdef\\uabcd\\uef4a"'),
+]
+
+class TestEncodeBaseStringAscii(TestCase):
+    def test_py_encode_basestring_ascii(self):
+        self._test_encode_basestring_ascii(simplejson.encoder.py_encode_basestring_ascii)
+
+    def test_c_encode_basestring_ascii(self):
+        if not simplejson.encoder.c_encode_basestring_ascii:
+            return
+        self._test_encode_basestring_ascii(simplejson.encoder.c_encode_basestring_ascii)
+
+    def _test_encode_basestring_ascii(self, encode_basestring_ascii):
+        fname = encode_basestring_ascii.__name__
+        for input_string, expect in CASES:
+            result = encode_basestring_ascii(input_string)
+            self.assertEquals(result, expect,
+                '%r != %r for %s(%r)' % (result, expect, fname, input_string))
diff --git a/ovsdb/simplejson/tests/test_fail.py b/ovsdb/simplejson/tests/test_fail.py
new file mode 100644 (file)
index 0000000..002eea0
--- /dev/null
@@ -0,0 +1,76 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# Fri Dec 30 18:57:26 2005
+JSONDOCS = [
+    # http://json.org/JSON_checker/test/fail1.json
+    '"A JSON payload should be an object or array, not a string."',
+    # http://json.org/JSON_checker/test/fail2.json
+    '["Unclosed array"',
+    # http://json.org/JSON_checker/test/fail3.json
+    '{unquoted_key: "keys must be quoted}',
+    # http://json.org/JSON_checker/test/fail4.json
+    '["extra comma",]',
+    # http://json.org/JSON_checker/test/fail5.json
+    '["double extra comma",,]',
+    # http://json.org/JSON_checker/test/fail6.json
+    '[   , "<-- missing value"]',
+    # http://json.org/JSON_checker/test/fail7.json
+    '["Comma after the close"],',
+    # http://json.org/JSON_checker/test/fail8.json
+    '["Extra close"]]',
+    # http://json.org/JSON_checker/test/fail9.json
+    '{"Extra comma": true,}',
+    # http://json.org/JSON_checker/test/fail10.json
+    '{"Extra value after close": true} "misplaced quoted value"',
+    # http://json.org/JSON_checker/test/fail11.json
+    '{"Illegal expression": 1 + 2}',
+    # http://json.org/JSON_checker/test/fail12.json
+    '{"Illegal invocation": alert()}',
+    # http://json.org/JSON_checker/test/fail13.json
+    '{"Numbers cannot have leading zeroes": 013}',
+    # http://json.org/JSON_checker/test/fail14.json
+    '{"Numbers cannot be hex": 0x14}',
+    # http://json.org/JSON_checker/test/fail15.json
+    '["Illegal backslash escape: \\x15"]',
+    # http://json.org/JSON_checker/test/fail16.json
+    '["Illegal backslash escape: \\\'"]',
+    # http://json.org/JSON_checker/test/fail17.json
+    '["Illegal backslash escape: \\017"]',
+    # http://json.org/JSON_checker/test/fail18.json
+    '[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]',
+    # http://json.org/JSON_checker/test/fail19.json
+    '{"Missing colon" null}',
+    # http://json.org/JSON_checker/test/fail20.json
+    '{"Double colon":: null}',
+    # http://json.org/JSON_checker/test/fail21.json
+    '{"Comma instead of colon", null}',
+    # http://json.org/JSON_checker/test/fail22.json
+    '["Colon instead of comma": false]',
+    # http://json.org/JSON_checker/test/fail23.json
+    '["Bad value", truth]',
+    # http://json.org/JSON_checker/test/fail24.json
+    "['single quote']",
+    # http://code.google.com/p/simplejson/issues/detail?id=3
+    u'["A\u001FZ control characters in string"]',
+]
+
+SKIPS = {
+    1: "why not have a string payload?",
+    18: "spec doesn't specify any nesting limitations",
+}
+
+class TestFail(TestCase):
+    def test_failures(self):
+        for idx, doc in enumerate(JSONDOCS):
+            idx = idx + 1
+            if idx in SKIPS:
+                json.loads(doc)
+                continue
+            try:
+                json.loads(doc)
+            except ValueError:
+                pass
+            else:
+                self.fail("Expected failure for fail%d.json: %r" % (idx, doc))
diff --git a/ovsdb/simplejson/tests/test_float.py b/ovsdb/simplejson/tests/test_float.py
new file mode 100644 (file)
index 0000000..1a2b98a
--- /dev/null
@@ -0,0 +1,15 @@
+import math
+from unittest import TestCase
+
+import simplejson as json
+
+class TestFloat(TestCase):
+    def test_floats(self):
+        for num in [1617161771.7650001, math.pi, math.pi**100, math.pi**-100, 3.1]:
+            self.assertEquals(float(json.dumps(num)), num)
+            self.assertEquals(json.loads(json.dumps(num)), num)
+
+    def test_ints(self):
+        for num in [1, 1L, 1<<32, 1<<64]:
+            self.assertEquals(json.dumps(num), str(num))
+            self.assertEquals(int(json.dumps(num)), num)
diff --git a/ovsdb/simplejson/tests/test_indent.py b/ovsdb/simplejson/tests/test_indent.py
new file mode 100644 (file)
index 0000000..66e19b9
--- /dev/null
@@ -0,0 +1,41 @@
+from unittest import TestCase
+
+import simplejson as json
+import textwrap
+
+class TestIndent(TestCase):
+    def test_indent(self):
+        h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh', 'i-vhbjkhnth',
+             {'nifty': 87}, {'field': 'yes', 'morefield': False} ]
+
+        expect = textwrap.dedent("""\
+        [
+          [
+            "blorpie"
+          ],
+          [
+            "whoops"
+          ],
+          [],
+          "d-shtaeou",
+          "d-nthiouh",
+          "i-vhbjkhnth",
+          {
+            "nifty": 87
+          },
+          {
+            "field": "yes",
+            "morefield": false
+          }
+        ]""")
+
+
+        d1 = json.dumps(h)
+        d2 = json.dumps(h, indent=2, sort_keys=True, separators=(',', ': '))
+
+        h1 = json.loads(d1)
+        h2 = json.loads(d2)
+
+        self.assertEquals(h1, h)
+        self.assertEquals(h2, h)
+        self.assertEquals(d2, expect)
diff --git a/ovsdb/simplejson/tests/test_pass1.py b/ovsdb/simplejson/tests/test_pass1.py
new file mode 100644 (file)
index 0000000..c3d6302
--- /dev/null
@@ -0,0 +1,76 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass1.json
+JSON = r'''
+[
+    "JSON Test Pattern pass1",
+    {"object with 1 member":["array with 1 element"]},
+    {},
+    [],
+    -42,
+    true,
+    false,
+    null,
+    {
+        "integer": 1234567890,
+        "real": -9876.543210,
+        "e": 0.123456789e-12,
+        "E": 1.234567890E+34,
+        "":  23456789012E666,
+        "zero": 0,
+        "one": 1,
+        "space": " ",
+        "quote": "\"",
+        "backslash": "\\",
+        "controls": "\b\f\n\r\t",
+        "slash": "/ & \/",
+        "alpha": "abcdefghijklmnopqrstuvwyz",
+        "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
+        "digit": "0123456789",
+        "special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
+        "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
+        "true": true,
+        "false": false,
+        "null": null,
+        "array":[  ],
+        "object":{  },
+        "address": "50 St. James Street",
+        "url": "http://www.JSON.org/",
+        "comment": "// /* <!-- --",
+        "# -- --> */": " ",
+        " s p a c e d " :[1,2 , 3
+
+,
+
+4 , 5        ,          6           ,7        ],
+        "compact": [1,2,3,4,5,6,7],
+        "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
+        "quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
+        "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
+: "A key can be any string"
+    },
+    0.5 ,98.6
+,
+99.44
+,
+
+1066
+
+
+,"rosebud"]
+'''
+
+class TestPass1(TestCase):
+    def test_parse(self):
+        # test in/out equivalence and parsing
+        res = json.loads(JSON)
+        out = json.dumps(res)
+        self.assertEquals(res, json.loads(out))
+        try:
+            json.dumps(res, allow_nan=False)
+        except ValueError:
+            pass
+        else:
+            self.fail("23456789012E666 should be out of range")
diff --git a/ovsdb/simplejson/tests/test_pass2.py b/ovsdb/simplejson/tests/test_pass2.py
new file mode 100644 (file)
index 0000000..de4ee00
--- /dev/null
@@ -0,0 +1,14 @@
+from unittest import TestCase
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass2.json
+JSON = r'''
+[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]
+'''
+
+class TestPass2(TestCase):
+    def test_parse(self):
+        # test in/out equivalence and parsing
+        res = json.loads(JSON)
+        out = json.dumps(res)
+        self.assertEquals(res, json.loads(out))
diff --git a/ovsdb/simplejson/tests/test_pass3.py b/ovsdb/simplejson/tests/test_pass3.py
new file mode 100644 (file)
index 0000000..f591aba
--- /dev/null
@@ -0,0 +1,20 @@
+from unittest import TestCase
+
+import simplejson as json
+
+# from http://json.org/JSON_checker/test/pass3.json
+JSON = r'''
+{
+    "JSON Test Pattern pass3": {
+        "The outermost value": "must be an object or array.",
+        "In this test": "It is an object."
+    }
+}
+'''
+
+class TestPass3(TestCase):
+    def test_parse(self):
+        # test in/out equivalence and parsing
+        res = json.loads(JSON)
+        out = json.dumps(res)
+        self.assertEquals(res, json.loads(out))
diff --git a/ovsdb/simplejson/tests/test_recursion.py b/ovsdb/simplejson/tests/test_recursion.py
new file mode 100644 (file)
index 0000000..97422a6
--- /dev/null
@@ -0,0 +1,67 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class JSONTestObject:
+    pass
+
+
+class RecursiveJSONEncoder(json.JSONEncoder):
+    recurse = False
+    def default(self, o):
+        if o is JSONTestObject:
+            if self.recurse:
+                return [JSONTestObject]
+            else:
+                return 'JSONTestObject'
+        return json.JSONEncoder.default(o)
+
+
+class TestRecursion(TestCase):
+    def test_listrecursion(self):
+        x = []
+        x.append(x)
+        try:
+            json.dumps(x)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on list recursion")
+        x = []
+        y = [x]
+        x.append(y)
+        try:
+            json.dumps(x)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on alternating list recursion")
+        y = []
+        x = [y, y]
+        # ensure that the marker is cleared
+        json.dumps(x)
+
+    def test_dictrecursion(self):
+        x = {}
+        x["test"] = x
+        try:
+            json.dumps(x)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on dict recursion")
+        x = {}
+        y = {"a": x, "b": x}
+        # ensure that the marker is cleared
+        json.dumps(x)
+
+    def test_defaultrecursion(self):
+        enc = RecursiveJSONEncoder()
+        self.assertEquals(enc.encode(JSONTestObject), '"JSONTestObject"')
+        enc.recurse = True
+        try:
+            enc.encode(JSONTestObject)
+        except ValueError:
+            pass
+        else:
+            self.fail("didn't raise ValueError on default recursion")
diff --git a/ovsdb/simplejson/tests/test_scanstring.py b/ovsdb/simplejson/tests/test_scanstring.py
new file mode 100644 (file)
index 0000000..b08dec7
--- /dev/null
@@ -0,0 +1,111 @@
+import sys
+import decimal
+from unittest import TestCase
+
+import simplejson as json
+import simplejson.decoder
+
+class TestScanString(TestCase):
+    def test_py_scanstring(self):
+        self._test_scanstring(simplejson.decoder.py_scanstring)
+
+    def test_c_scanstring(self):
+        if not simplejson.decoder.c_scanstring:
+            return
+        self._test_scanstring(simplejson.decoder.c_scanstring)
+
+    def _test_scanstring(self, scanstring):
+        self.assertEquals(
+            scanstring('"z\\ud834\\udd20x"', 1, None, True),
+            (u'z\U0001d120x', 16))
+
+        if sys.maxunicode == 65535:
+            self.assertEquals(
+                scanstring(u'"z\U0001d120x"', 1, None, True),
+                (u'z\U0001d120x', 6))
+        else:
+            self.assertEquals(
+                scanstring(u'"z\U0001d120x"', 1, None, True),
+                (u'z\U0001d120x', 5))
+
+        self.assertEquals(
+            scanstring('"\\u007b"', 1, None, True),
+            (u'{', 8))
+
+        self.assertEquals(
+            scanstring('"A JSON payload should be an object or array, not a string."', 1, None, True),
+            (u'A JSON payload should be an object or array, not a string.', 60))
+
+        self.assertEquals(
+            scanstring('["Unclosed array"', 2, None, True),
+            (u'Unclosed array', 17))
+
+        self.assertEquals(
+            scanstring('["extra comma",]', 2, None, True),
+            (u'extra comma', 14))
+
+        self.assertEquals(
+            scanstring('["double extra comma",,]', 2, None, True),
+            (u'double extra comma', 21))
+
+        self.assertEquals(
+            scanstring('["Comma after the close"],', 2, None, True),
+            (u'Comma after the close', 24))
+
+        self.assertEquals(
+            scanstring('["Extra close"]]', 2, None, True),
+            (u'Extra close', 14))
+
+        self.assertEquals(
+            scanstring('{"Extra comma": true,}', 2, None, True),
+            (u'Extra comma', 14))
+
+        self.assertEquals(
+            scanstring('{"Extra value after close": true} "misplaced quoted value"', 2, None, True),
+            (u'Extra value after close', 26))
+
+        self.assertEquals(
+            scanstring('{"Illegal expression": 1 + 2}', 2, None, True),
+            (u'Illegal expression', 21))
+
+        self.assertEquals(
+            scanstring('{"Illegal invocation": alert()}', 2, None, True),
+            (u'Illegal invocation', 21))
+
+        self.assertEquals(
+            scanstring('{"Numbers cannot have leading zeroes": 013}', 2, None, True),
+            (u'Numbers cannot have leading zeroes', 37))
+
+        self.assertEquals(
+            scanstring('{"Numbers cannot be hex": 0x14}', 2, None, True),
+            (u'Numbers cannot be hex', 24))
+
+        self.assertEquals(
+            scanstring('[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]', 21, None, True),
+            (u'Too deep', 30))
+
+        self.assertEquals(
+            scanstring('{"Missing colon" null}', 2, None, True),
+            (u'Missing colon', 16))
+
+        self.assertEquals(
+            scanstring('{"Double colon":: null}', 2, None, True),
+            (u'Double colon', 15))
+
+        self.assertEquals(
+            scanstring('{"Comma instead of colon", null}', 2, None, True),
+            (u'Comma instead of colon', 25))
+
+        self.assertEquals(
+            scanstring('["Colon instead of comma": false]', 2, None, True),
+            (u'Colon instead of comma', 25))
+
+        self.assertEquals(
+            scanstring('["Bad value", truth]', 2, None, True),
+            (u'Bad value', 12))
+
+    def test_issue3623(self):
+        self.assertRaises(ValueError, json.decoder.scanstring, "xxx", 1,
+                          "xxx")
+        self.assertRaises(UnicodeDecodeError,
+                          json.encoder.encode_basestring_ascii, "xx\xff")
diff --git a/ovsdb/simplejson/tests/test_separators.py b/ovsdb/simplejson/tests/test_separators.py
new file mode 100644 (file)
index 0000000..8fa0dac
--- /dev/null
@@ -0,0 +1,42 @@
+import textwrap
+from unittest import TestCase
+
+import simplejson as json
+
+
+class TestSeparators(TestCase):
+    def test_separators(self):
+        h = [['blorpie'], ['whoops'], [], 'd-shtaeou', 'd-nthiouh', 'i-vhbjkhnth',
+             {'nifty': 87}, {'field': 'yes', 'morefield': False} ]
+
+        expect = textwrap.dedent("""\
+        [
+          [
+            "blorpie"
+          ] ,
+          [
+            "whoops"
+          ] ,
+          [] ,
+          "d-shtaeou" ,
+          "d-nthiouh" ,
+          "i-vhbjkhnth" ,
+          {
+            "nifty" : 87
+          } ,
+          {
+            "field" : "yes" ,
+            "morefield" : false
+          }
+        ]""")
+
+
+        d1 = json.dumps(h)
+        d2 = json.dumps(h, indent=2, sort_keys=True, separators=(' ,', ' : '))
+
+        h1 = json.loads(d1)
+        h2 = json.loads(d2)
+
+        self.assertEquals(h1, h)
+        self.assertEquals(h2, h)
+        self.assertEquals(d2, expect)
diff --git a/ovsdb/simplejson/tests/test_unicode.py b/ovsdb/simplejson/tests/test_unicode.py
new file mode 100644 (file)
index 0000000..6f4384a
--- /dev/null
@@ -0,0 +1,64 @@
+from unittest import TestCase
+
+import simplejson as json
+
+class TestUnicode(TestCase):
+    def test_encoding1(self):
+        encoder = json.JSONEncoder(encoding='utf-8')
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        s = u.encode('utf-8')
+        ju = encoder.encode(u)
+        js = encoder.encode(s)
+        self.assertEquals(ju, js)
+
+    def test_encoding2(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        s = u.encode('utf-8')
+        ju = json.dumps(u, encoding='utf-8')
+        js = json.dumps(s, encoding='utf-8')
+        self.assertEquals(ju, js)
+
+    def test_encoding3(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps(u)
+        self.assertEquals(j, '"\\u03b1\\u03a9"')
+
+    def test_encoding4(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps([u])
+        self.assertEquals(j, '["\\u03b1\\u03a9"]')
+
+    def test_encoding5(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps(u, ensure_ascii=False)
+        self.assertEquals(j, u'"%s"' % (u,))
+
+    def test_encoding6(self):
+        u = u'\N{GREEK SMALL LETTER ALPHA}\N{GREEK CAPITAL LETTER OMEGA}'
+        j = json.dumps([u], ensure_ascii=False)
+        self.assertEquals(j, u'["%s"]' % (u,))
+
+    def test_big_unicode_encode(self):
+        u = u'\U0001d120'
+        self.assertEquals(json.dumps(u), '"\\ud834\\udd20"')
+        self.assertEquals(json.dumps(u, ensure_ascii=False), u'"\U0001d120"')
+
+    def test_big_unicode_decode(self):
+        u = u'z\U0001d120x'
+        self.assertEquals(json.loads('"' + u + '"'), u)
+        self.assertEquals(json.loads('"z\\ud834\\udd20x"'), u)
+
+    def test_unicode_decode(self):
+        for i in range(0, 0xd7ff):
+            u = unichr(i)
+            s = '"\\u%04x"' % (i,)
+            self.assertEquals(json.loads(s), u)
+
+    def test_default_encoding(self):
+        self.assertEquals(json.loads(u'{"a": "\xe9"}'.encode('utf-8')),
+            {'a': u'\xe9'})
+
+    def test_unicode_preservation(self):
+        self.assertEquals(type(json.loads(u'""')), unicode)
+        self.assertEquals(type(json.loads(u'"a"')), unicode)
+        self.assertEquals(type(json.loads(u'["a"]')[0]), unicode)
\ No newline at end of file
diff --git a/ovsdb/simplejson/tool.py b/ovsdb/simplejson/tool.py
new file mode 100644 (file)
index 0000000..9044331
--- /dev/null
@@ -0,0 +1,37 @@
+r"""Command-line tool to validate and pretty-print JSON
+
+Usage::
+
+    $ echo '{"json":"obj"}' | python -m simplejson.tool
+    {
+        "json": "obj"
+    }
+    $ echo '{ 1.2:3.4}' | python -m simplejson.tool
+    Expecting property name: line 1 column 2 (char 2)
+
+"""
+import sys
+import simplejson
+
+def main():
+    if len(sys.argv) == 1:
+        infile = sys.stdin
+        outfile = sys.stdout
+    elif len(sys.argv) == 2:
+        infile = open(sys.argv[1], 'rb')
+        outfile = sys.stdout
+    elif len(sys.argv) == 3:
+        infile = open(sys.argv[1], 'rb')
+        outfile = open(sys.argv[2], 'wb')
+    else:
+        raise SystemExit(sys.argv[0] + " [infile [outfile]]")
+    try:
+        obj = simplejson.load(infile)
+    except ValueError, e:
+        raise SystemExit(e)
+    simplejson.dump(obj, outfile, sort_keys=True, indent=4)
+    outfile.write('\n')
+
+
+if __name__ == '__main__':
+    main()
diff --git a/ovsdb/table.c b/ovsdb/table.c
new file mode 100644 (file)
index 0000000..b520580
--- /dev/null
@@ -0,0 +1,230 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "table.h"
+
+#include <assert.h>
+
+#include "json.h"
+#include "column.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "ovsdb-types.h"
+#include "row.h"
+
+static void
+add_column(struct ovsdb_table_schema *ts, struct ovsdb_column *column)
+{
+    assert(!shash_find(&ts->columns, column->name));
+    column->index = shash_count(&ts->columns);
+    shash_add(&ts->columns, column->name, column);
+}
+
+struct ovsdb_table_schema *
+ovsdb_table_schema_create(const char *name, const char *comment, bool mutable)
+{
+    struct ovsdb_column *uuid, *version;
+    struct ovsdb_table_schema *ts;
+
+    ts = xzalloc(sizeof *ts);
+    ts->name = xstrdup(name);
+    ts->comment = comment ? xstrdup(comment) : NULL;
+    ts->mutable = mutable;
+    shash_init(&ts->columns);
+
+    uuid = ovsdb_column_create(
+        "_uuid", "Unique identifier for this row.",
+        false, true, &ovsdb_type_uuid);
+    add_column(ts, uuid);
+    assert(uuid->index == OVSDB_COL_UUID);
+
+    version = ovsdb_column_create(
+        "_version", "Unique identifier for this version of this row.",
+        false, false, &ovsdb_type_uuid);
+    add_column(ts, version);
+    assert(version->index == OVSDB_COL_VERSION);
+
+    return ts;
+}
+
+void
+ovsdb_table_schema_destroy(struct ovsdb_table_schema *ts)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &ts->columns) {
+        ovsdb_column_destroy(node->data);
+    }
+    shash_destroy(&ts->columns);
+    free(ts->comment);
+    free(ts->name);
+    free(ts);
+}
+
+struct ovsdb_error *
+ovsdb_table_schema_from_json(const struct json *json, const char *name,
+                             struct ovsdb_table_schema **tsp)
+{
+    struct ovsdb_table_schema *ts;
+    const struct json *comment, *columns, *mutable;
+    struct shash_node *node;
+    struct ovsdb_parser parser;
+    struct ovsdb_error *error;
+
+    *tsp = NULL;
+
+    ovsdb_parser_init(&parser, json, "table schema for table %s", name);
+    comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL);
+    columns = ovsdb_parser_member(&parser, "columns", OP_OBJECT);
+    mutable = ovsdb_parser_member(&parser, "mutable",
+                                  OP_TRUE | OP_FALSE | OP_OPTIONAL);
+    error = ovsdb_parser_finish(&parser);
+    if (error) {
+        return error;
+    }
+
+    if (shash_is_empty(json_object(columns))) {
+        return ovsdb_syntax_error(json, NULL,
+                                  "table must have at least one column");
+    }
+
+    ts = ovsdb_table_schema_create(name,
+                                   comment ? json_string(comment) : NULL,
+                                   mutable ? json_boolean(mutable) : true);
+    SHASH_FOR_EACH (node, json_object(columns)) {
+        struct ovsdb_column *column;
+
+        if (node->name[0] == '_') {
+            error = ovsdb_syntax_error(json, NULL, "names beginning with "
+                                       "\"_\" are reserved");
+        } else if (!ovsdb_parser_is_id(node->name)) {
+            error = ovsdb_syntax_error(json, NULL, "name must be a valid id");
+        } else {
+            error = ovsdb_column_from_json(node->data, node->name, &column);
+        }
+        if (error) {
+            ovsdb_table_schema_destroy(ts);
+            return error;
+        }
+
+        add_column(ts, column);
+    }
+    *tsp = ts;
+    return 0;
+}
+
+struct json *
+ovsdb_table_schema_to_json(const struct ovsdb_table_schema *ts)
+{
+    struct json *json, *columns;
+    struct shash_node *node;
+
+    json = json_object_create();
+    if (ts->comment) {
+        json_object_put_string(json, "comment", ts->comment);
+    }
+    if (!ts->mutable) {
+        json_object_put(json, "mutable", json_boolean_create(false));
+    }
+
+    columns = json_object_create();
+
+    SHASH_FOR_EACH (node, &ts->columns) {
+        struct ovsdb_column *column = node->data;
+        if (node->name[0] != '_') {
+            json_object_put(columns, column->name,
+                            ovsdb_column_to_json(column));
+        }
+    }
+    json_object_put(json, "columns", columns);
+
+    return json;
+}
+
+const struct ovsdb_column *
+ovsdb_table_schema_get_column(const struct ovsdb_table_schema *ts,
+                              const char *name)
+{
+    return shash_find_data(&ts->columns, name);
+}
+\f
+struct ovsdb_table *
+ovsdb_table_create(struct ovsdb_table_schema *ts)
+{
+    struct ovsdb_table *table;
+
+    table = xmalloc(sizeof *table);
+    table->schema = ts;
+    hmap_init(&table->rows);
+
+    return table;
+}
+
+void
+ovsdb_table_destroy(struct ovsdb_table *table)
+{
+    if (table) {
+        struct ovsdb_row *row, *next;
+
+        HMAP_FOR_EACH_SAFE (row, next, struct ovsdb_row, hmap_node,
+                            &table->rows) {
+            ovsdb_row_destroy(row);
+        }
+        hmap_destroy(&table->rows);
+
+        ovsdb_table_schema_destroy(table->schema);
+        free(table);
+    }
+}
+
+static const struct ovsdb_row *
+ovsdb_table_get_row__(const struct ovsdb_table *table, const struct uuid *uuid,
+                      size_t hash)
+{
+    struct ovsdb_row *row;
+
+    HMAP_FOR_EACH_WITH_HASH (row, struct ovsdb_row, hmap_node, hash,
+                             &table->rows) {
+        if (uuid_equals(ovsdb_row_get_uuid(row), uuid)) {
+            return row;
+        }
+    }
+
+    return NULL;
+}
+
+const struct ovsdb_row *
+ovsdb_table_get_row(const struct ovsdb_table *table, const struct uuid *uuid)
+{
+    return ovsdb_table_get_row__(table, uuid, uuid_hash(uuid));
+}
+
+/* This is probably not the function you want.  Use ovsdb_txn_row_modify()
+ * instead. */
+bool
+ovsdb_table_put_row(struct ovsdb_table *table, struct ovsdb_row *row)
+{
+    const struct uuid *uuid = ovsdb_row_get_uuid(row);
+    size_t hash = uuid_hash(uuid);
+
+    if (!ovsdb_table_get_row__(table, uuid, hash)) {
+        hmap_insert(&table->rows, &row->hmap_node, hash);
+        return true;
+    } else {
+        return false;
+    }
+}
diff --git a/ovsdb/table.h b/ovsdb/table.h
new file mode 100644 (file)
index 0000000..9e36c91
--- /dev/null
@@ -0,0 +1,63 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TABLE_H
+#define OVSDB_TABLE_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+#include "hmap.h"
+#include "shash.h"
+
+struct json;
+struct uuid;
+
+/* Schema for a database table. */
+struct ovsdb_table_schema {
+    char *name;
+    char *comment;
+    bool mutable;
+    struct shash columns;       /* Contains "struct ovsdb_column *"s. */
+};
+
+struct ovsdb_table_schema *ovsdb_table_schema_create(const char *name,
+                                                     const char *comment,
+                                                     bool mutable);
+void ovsdb_table_schema_destroy(struct ovsdb_table_schema *);
+
+struct ovsdb_error *ovsdb_table_schema_from_json(const struct json *,
+                                                 const char *name,
+                                                 struct ovsdb_table_schema **)
+    WARN_UNUSED_RESULT;
+struct json *ovsdb_table_schema_to_json(const struct ovsdb_table_schema *);
+
+const struct ovsdb_column *ovsdb_table_schema_get_column(
+    const struct ovsdb_table_schema *, const char *name);
+\f
+/* Database table. */
+
+struct ovsdb_table {
+    struct ovsdb_table_schema *schema;
+    struct hmap rows;           /* Contains "struct ovsdb_row"s. */
+};
+
+struct ovsdb_table *ovsdb_table_create(struct ovsdb_table_schema *);
+void ovsdb_table_destroy(struct ovsdb_table *);
+
+const struct ovsdb_row *ovsdb_table_get_row(const struct ovsdb_table *,
+                                            const struct uuid *);
+bool ovsdb_table_put_row(struct ovsdb_table *, struct ovsdb_row *);
+
+#endif /* ovsdb/table.h */
diff --git a/ovsdb/transaction.c b/ovsdb/transaction.c
new file mode 100644 (file)
index 0000000..15b87da
--- /dev/null
@@ -0,0 +1,306 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "transaction.h"
+
+#include <assert.h>
+
+#include "dynamic-string.h"
+#include "hash.h"
+#include "hmap.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb.h"
+#include "row.h"
+#include "table.h"
+#include "uuid.h"
+
+struct ovsdb_txn {
+    struct ovsdb *db;
+    struct hmap txn_tables;     /* Contains "struct ovsdb_txn_table"s. */
+    struct ds comment;
+};
+
+/* A table modified by a transaction. */
+struct ovsdb_txn_table {
+    struct hmap_node hmap_node; /* Element in ovsdb_txn's txn_tables hmap. */
+    struct ovsdb_table *table;
+    struct hmap txn_rows;       /* Contains "struct ovsdb_txn_row"s. */
+};
+
+/* A row modified by the transaction:
+ *
+ *      - A row added by a transaction will have null 'old' and non-null 'new'.
+ *
+ *      - A row deleted by a transaction will have non-null 'old' and null
+ *        'new'.
+ *
+ *      - A row modified by a transaction will have non-null 'old' and 'new'.
+ *
+ *      - 'old' and 'new' both null is invalid.  It would indicate that a row
+ *        was added then deleted within a single transaction, but we instead
+ *        handle that case by deleting the txn_row entirely.
+ */
+struct ovsdb_txn_row {
+    struct hmap_node hmap_node; /* In ovsdb_txn_table's txn_rows hmap. */
+    struct ovsdb_row *old;      /* The old row. */
+    struct ovsdb_row *new;      /* The new row. */
+};
+
+struct ovsdb_txn *
+ovsdb_txn_create(struct ovsdb *db)
+{
+    struct ovsdb_txn *txn = xmalloc(sizeof *txn);
+    txn->db = db;
+    hmap_init(&txn->txn_tables);
+    ds_init(&txn->comment);
+    return txn;
+}
+
+static void
+ovsdb_txn_destroy(struct ovsdb_txn *txn, void (*cb)(struct ovsdb_txn_row *))
+{
+    struct ovsdb_txn_table *txn_table, *next_txn_table;
+
+    HMAP_FOR_EACH_SAFE (txn_table, next_txn_table,
+                        struct ovsdb_txn_table, hmap_node, &txn->txn_tables)
+    {
+        struct ovsdb_txn_row *txn_row, *next_txn_row;
+
+        HMAP_FOR_EACH_SAFE (txn_row, next_txn_row,
+                            struct ovsdb_txn_row, hmap_node,
+                            &txn_table->txn_rows)
+        {
+            if (txn_row->old) {
+                txn_row->old->txn_row = NULL;
+            }
+            if (txn_row->new) {
+                txn_row->new->txn_row = NULL;
+            }
+            cb(txn_row);
+            free(txn_row);
+        }
+
+        hmap_destroy(&txn_table->txn_rows);
+        free(txn_table);
+    }
+    hmap_destroy(&txn->txn_tables);
+    ds_destroy(&txn->comment);
+    free(txn);
+}
+
+static void
+ovsdb_txn_row_abort(struct ovsdb_txn_row *txn_row)
+{
+    struct ovsdb_row *old = txn_row->old;
+    struct ovsdb_row *new = txn_row->new;
+
+    if (!old) {
+        hmap_remove(&new->table->rows, &new->hmap_node);
+    } else if (!new) {
+        hmap_insert(&old->table->rows, &old->hmap_node, ovsdb_row_hash(old));
+    } else {
+        hmap_replace(&new->table->rows, &new->hmap_node, &old->hmap_node);
+    }
+    ovsdb_row_destroy(new);
+}
+
+void
+ovsdb_txn_abort(struct ovsdb_txn *txn)
+{
+    ovsdb_txn_destroy(txn, ovsdb_txn_row_abort);
+}
+
+static void
+ovsdb_txn_row_commit(struct ovsdb_txn_row *txn_row)
+{
+    ovsdb_row_destroy(txn_row->old);
+}
+
+struct ovsdb_error *
+ovsdb_txn_commit(struct ovsdb_txn *txn, bool durable)
+{
+    struct ovsdb_replica *replica;
+    struct ovsdb_error *error;
+
+    LIST_FOR_EACH (replica, struct ovsdb_replica, node, &txn->db->replicas) {
+        error = (replica->class->commit)(replica, txn, durable);
+        if (error) {
+            /* We don't support two-phase commit so only the first replica is
+             * allowed to report an error. */
+            assert(&replica->node == txn->db->replicas.next);
+
+            ovsdb_txn_abort(txn);
+            return error;
+        }
+    }
+
+    txn->db->run_triggers = true;
+    ovsdb_txn_destroy(txn, ovsdb_txn_row_commit);
+    return NULL;
+}
+
+void
+ovsdb_txn_for_each_change(const struct ovsdb_txn *txn,
+                          ovsdb_txn_row_cb_func *cb, void *aux)
+{
+    struct ovsdb_txn_table *t;
+    struct ovsdb_txn_row *r;
+
+    HMAP_FOR_EACH (t, struct ovsdb_txn_table, hmap_node, &txn->txn_tables) {
+        HMAP_FOR_EACH (r, struct ovsdb_txn_row, hmap_node, &t->txn_rows) {
+            if (!cb(r->old, r->new, aux)) {
+                break;
+            }
+        }
+   }
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_get_txn_table__(struct ovsdb_txn *txn,
+                          const struct ovsdb_table *table,
+                          uint32_t hash)
+{
+    struct ovsdb_txn_table *txn_table;
+
+    HMAP_FOR_EACH_IN_BUCKET (txn_table, struct ovsdb_txn_table, hmap_node,
+                             hash, &txn->txn_tables) {
+        if (txn_table->table == table) {
+            return txn_table;
+        }
+    }
+
+    return NULL;
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_get_txn_table(struct ovsdb_txn *txn, const struct ovsdb_table *table)
+{
+    return ovsdb_txn_get_txn_table__(txn, table, hash_pointer(table, 0));
+}
+
+static struct ovsdb_txn_table *
+ovsdb_txn_create_txn_table(struct ovsdb_txn *txn,
+                           struct ovsdb_table *table)
+{
+    uint32_t hash = hash_pointer(table, 0);
+    struct ovsdb_txn_table *txn_table;
+
+    txn_table = ovsdb_txn_get_txn_table__(txn, table, hash);
+    if (!txn_table) {
+        txn_table = xmalloc(sizeof *txn_table);
+        txn_table->table = table;
+        hmap_init(&txn_table->txn_rows);
+        hmap_insert(&txn->txn_tables, &txn_table->hmap_node, hash);
+    }
+    return txn_table;
+}
+
+static struct ovsdb_txn_row *
+ovsdb_txn_row_create(struct ovsdb_txn_table *txn_table,
+                     const struct ovsdb_row *old, struct ovsdb_row *new)
+{
+    uint32_t hash = ovsdb_row_hash(old ? old : new);
+    struct ovsdb_txn_row *txn_row;
+
+    txn_row = xmalloc(sizeof *txn_row);
+    txn_row->old = (struct ovsdb_row *) old;
+    txn_row->new = new;
+    hmap_insert(&txn_table->txn_rows, &txn_row->hmap_node, hash);
+
+    return txn_row;
+}
+
+struct ovsdb_row *
+ovsdb_txn_row_modify(struct ovsdb_txn *txn, const struct ovsdb_row *ro_row_)
+{
+    struct ovsdb_row *ro_row = (struct ovsdb_row *) ro_row_;
+
+    if (ro_row->txn_row) {
+        assert(ro_row == ro_row->txn_row->new);
+        return ro_row;
+    } else {
+        struct ovsdb_table *table = ro_row->table;
+        struct ovsdb_txn_table *txn_table;
+        struct ovsdb_row *rw_row;
+
+        txn_table = ovsdb_txn_create_txn_table(txn, table);
+        rw_row = ovsdb_row_clone(ro_row);
+        uuid_generate(ovsdb_row_get_version_rw(rw_row));
+        rw_row->txn_row = ovsdb_txn_row_create(txn_table, ro_row, rw_row);
+        hmap_replace(&table->rows, &ro_row->hmap_node, &rw_row->hmap_node);
+
+        return rw_row;
+    }
+}
+
+void
+ovsdb_txn_row_insert(struct ovsdb_txn *txn, struct ovsdb_row *row)
+{
+    uint32_t hash = ovsdb_row_hash(row);
+    struct ovsdb_table *table = row->table;
+    struct ovsdb_txn_table *txn_table;
+
+    uuid_generate(ovsdb_row_get_version_rw(row));
+
+    txn_table = ovsdb_txn_create_txn_table(txn, table);
+    row->txn_row = ovsdb_txn_row_create(txn_table, NULL, row);
+    hmap_insert(&table->rows, &row->hmap_node, hash);
+}
+
+/* 'row' must be assumed destroyed upon return; the caller must not reference
+ * it again. */
+void
+ovsdb_txn_row_delete(struct ovsdb_txn *txn, const struct ovsdb_row *row_)
+{
+    struct ovsdb_row *row = (struct ovsdb_row *) row_;
+    struct ovsdb_table *table = row->table;
+    struct ovsdb_txn_row *txn_row = row->txn_row;
+    struct ovsdb_txn_table *txn_table;
+
+    hmap_remove(&table->rows, &row->hmap_node);
+
+    if (!txn_row) {
+        txn_table = ovsdb_txn_create_txn_table(txn, table);
+        row->txn_row = ovsdb_txn_row_create(txn_table, row, NULL);
+    } else {
+        assert(txn_row->new == row);
+        if (txn_row->old) {
+            txn_row->new = NULL;
+        } else {
+            txn_table = ovsdb_txn_get_txn_table(txn, table);
+            hmap_remove(&txn_table->txn_rows, &txn_row->hmap_node);
+            free(txn_row);
+        }
+        ovsdb_row_destroy(row);
+    }
+}
+
+void
+ovsdb_txn_add_comment(struct ovsdb_txn *txn, const char *s)
+{
+    if (txn->comment.length) {
+        ds_put_char(&txn->comment, '\n');
+    }
+    ds_put_cstr(&txn->comment, s);
+}
+
+const char *
+ovsdb_txn_get_comment(const struct ovsdb_txn *txn)
+{
+    return txn->comment.length ? ds_cstr_ro(&txn->comment) : NULL;
+}
diff --git a/ovsdb/transaction.h b/ovsdb/transaction.h
new file mode 100644 (file)
index 0000000..1c54ec3
--- /dev/null
@@ -0,0 +1,45 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TRANSACTION_H
+#define OVSDB_TRANSACTION_H 1
+
+#include <stdbool.h>
+#include "compiler.h"
+
+struct json;
+struct ovsdb;
+struct ovsdb_table;
+struct uuid;
+
+struct ovsdb_txn *ovsdb_txn_create(struct ovsdb *);
+void ovsdb_txn_abort(struct ovsdb_txn *);
+struct ovsdb_error *ovsdb_txn_commit(struct ovsdb_txn *, bool durable);
+
+struct ovsdb_row *ovsdb_txn_row_modify(struct ovsdb_txn *,
+                                       const struct ovsdb_row *);
+void ovsdb_txn_row_insert(struct ovsdb_txn *, struct ovsdb_row *);
+void ovsdb_txn_row_delete(struct ovsdb_txn *, const struct ovsdb_row *);
+
+typedef bool ovsdb_txn_row_cb_func(const struct ovsdb_row *old,
+                                   const struct ovsdb_row *new,
+                                   void *aux);
+void ovsdb_txn_for_each_change(const struct ovsdb_txn *,
+                               ovsdb_txn_row_cb_func *, void *aux);
+
+void ovsdb_txn_add_comment(struct ovsdb_txn *, const char *);
+const char *ovsdb_txn_get_comment(const struct ovsdb_txn *);
+
+#endif /* ovsdb/transaction.h */
diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c
new file mode 100644 (file)
index 0000000..1ecfdca
--- /dev/null
@@ -0,0 +1,129 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "trigger.h"
+
+#include <assert.h>
+#include <limits.h>
+
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "poll-loop.h"
+
+static bool ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *,
+                              long long int now);
+static void ovsdb_trigger_complete(struct ovsdb_trigger *);
+
+void
+ovsdb_trigger_init(struct ovsdb *db, struct ovsdb_trigger *trigger,
+                   struct json *request, struct list *completion,
+                   long long int now)
+{
+    list_push_back(&db->triggers, &trigger->node);
+    trigger->completion = completion;
+    trigger->request = request;
+    trigger->result = NULL;
+    trigger->created = now;
+    trigger->timeout_msec = LLONG_MAX;
+    ovsdb_trigger_try(db, trigger, now);
+}
+
+void
+ovsdb_trigger_destroy(struct ovsdb_trigger *trigger)
+{
+    list_remove(&trigger->node);
+    json_destroy(trigger->request);
+    json_destroy(trigger->result);
+}
+
+bool
+ovsdb_trigger_is_complete(const struct ovsdb_trigger *trigger)
+{
+    return trigger->result != NULL;
+}
+
+struct json *
+ovsdb_trigger_steal_result(struct ovsdb_trigger *trigger)
+{
+    struct json *result = trigger->result;
+    trigger->result = NULL;
+    return result;
+}
+
+void
+ovsdb_trigger_run(struct ovsdb *db, long long int now)
+{
+    struct ovsdb_trigger *t, *next;
+    bool run_triggers;
+
+    run_triggers = db->run_triggers;
+    db->run_triggers = false;
+    LIST_FOR_EACH_SAFE (t, next, struct ovsdb_trigger, node, &db->triggers) {
+        if (run_triggers || now - t->created >= t->timeout_msec) {
+            ovsdb_trigger_try(db, t, now);
+        }
+    }
+}
+
+void
+ovsdb_trigger_wait(struct ovsdb *db, long long int now)
+{
+    if (db->run_triggers) {
+        poll_immediate_wake();
+    } else {
+        long long int deadline = LLONG_MAX;
+        struct ovsdb_trigger *t;
+
+        LIST_FOR_EACH (t, struct ovsdb_trigger, node, &db->triggers) {
+            if (t->created < LLONG_MAX - t->timeout_msec) {
+                long long int t_deadline = t->created + t->timeout_msec;
+                if (deadline > t_deadline) {
+                    deadline = t_deadline;
+                    if (now >= deadline) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (deadline < LLONG_MAX) {
+            poll_timer_wait(MIN(deadline - now, INT_MAX));
+        }
+    }
+}
+
+static bool
+ovsdb_trigger_try(struct ovsdb *db, struct ovsdb_trigger *t, long long int now)
+{
+    t->result = ovsdb_execute(db, t->request, now - t->created,
+                              &t->timeout_msec);
+    if (t->result) {
+        ovsdb_trigger_complete(t);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+static void
+ovsdb_trigger_complete(struct ovsdb_trigger *t)
+{
+    assert(t->result != NULL);
+    list_remove(&t->node);
+    list_push_back(t->completion, &t->node);
+}
diff --git a/ovsdb/trigger.h b/ovsdb/trigger.h
new file mode 100644 (file)
index 0000000..521b150
--- /dev/null
@@ -0,0 +1,44 @@
+/* Copyright (c) 2009 Nicira Networks
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVSDB_TRIGGER_H
+#define OVSDB_TRIGGER_H 1
+
+#include "list.h"
+
+struct ovsdb;
+
+struct ovsdb_trigger {
+    struct list node;           /* !result: in struct ovsdb "triggers" list;
+                                 * result: in completion list. */
+    struct list *completion;    /* Completion list. */
+    struct json *request;       /* Database request. */
+    struct json *result;        /* Result (null if none yet). */
+    long long int created;      /* Time created. */
+    long long int timeout_msec; /* Max wait duration. */
+};
+
+void ovsdb_trigger_init(struct ovsdb *, struct ovsdb_trigger *,
+                        struct json *request, struct list *completion,
+                        long long int now);
+void ovsdb_trigger_destroy(struct ovsdb_trigger *);
+
+bool ovsdb_trigger_is_complete(const struct ovsdb_trigger *);
+struct json *ovsdb_trigger_steal_result(struct ovsdb_trigger *);
+
+void ovsdb_trigger_run(struct ovsdb *, long long int now);
+void ovsdb_trigger_wait(struct ovsdb *, long long int now);
+
+#endif /* ovsdb/trigger.h */
index 706aa14..c987014 100644 (file)
@@ -1,11 +1,28 @@
 /Makefile
 /Makefile.in
+/atconfig
+/atlocal
+/idltest.c
+/idltest.h
+/idltest.ovsidl
+/test-aes128
 /test-classifier
+/test-csum
 /test-dhcp-client
+/test-dir_name
 /test-flows
 /test-hash
 /test-hmap
+/test-json
+/test-jsonrpc
 /test-list
+/test-lockfile
+/test-ovsdb
+/test-reconnect
+/test-timeval
+/test-sha1
 /test-stp
 /test-type-props
+/test-uuid
+/test-vconn
 /testsuite
diff --git a/tests/aes128.at b/tests/aes128.at
new file mode 100644 (file)
index 0000000..4818f5c
--- /dev/null
@@ -0,0 +1,146 @@
+AT_BANNER([AES-128 unit tests])
+
+m4_define([AES128_CHECK], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([aes128])
+   AT_CHECK([test-aes128 $2 $3], [0], [$4
+], [])
+   AT_CLEANUP])
+
+AES128_CHECK(
+  [wikipedia test vector 1],
+  [00010203050607080a0b0c0d0f101112],
+  [506812a45f08c889b97f5980038b8359],
+  [d8f532538289ef7d06b506a4fd5be9c9])
+
+AES128_CHECK(
+  [wikipedia test vector 2],
+  [95A8EE8E89979B9EFDCBC6EB9797528D],
+  [4ec137a426dabf8aa0beb8bc0c2b89d6],
+  [d9b65d1232ba0199cdbd487b2a1fd646])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 0],
+  [10a58869d74be5a374cf867cfb473859],
+  [00000000000000000000000000000000],
+  [6d251e6944b051e04eaa6fb4dbf78465])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 1],
+  [caea65cdbb75e9169ecd22ebe6e54675],
+  [00000000000000000000000000000000],
+  [6e29201190152df4ee058139def610bb])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 2],
+  [a2e2fa9baf7d20822ca9f0542f764a41],
+  [00000000000000000000000000000000],
+  [c3b44b95d9d2f25670eee9a0de099fa3])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 3],
+  [b6364ac4e1de1e285eaf144a2415f7a0],
+  [00000000000000000000000000000000],
+  [5d9b05578fc944b3cf1ccf0e746cd581])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 4],
+  [64cf9c7abc50b888af65f49d521944b2],
+  [00000000000000000000000000000000],
+  [f7efc89d5dba578104016ce5ad659c05])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 5],
+  [47d6742eefcc0465dc96355e851b64d9],
+  [00000000000000000000000000000000],
+  [0306194f666d183624aa230a8b264ae7])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 6],
+  [3eb39790678c56bee34bbcdeccf6cdb5],
+  [00000000000000000000000000000000],
+  [858075d536d79ccee571f7d7204b1f67])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 7],
+  [64110a924f0743d500ccadae72c13427],
+  [00000000000000000000000000000000],
+  [35870c6a57e9e92314bcb8087cde72ce])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 8],
+  [18d8126516f8a12ab1a36d9f04d68e51],
+  [00000000000000000000000000000000],
+  [6c68e9be5ec41e22c825b7c7affb4363])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 9],
+  [f530357968578480b398a3c251cd1093],
+  [00000000000000000000000000000000],
+  [f5df39990fc688f1b07224cc03e86cea])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 10],
+  [da84367f325d42d601b4326964802e8e],
+  [00000000000000000000000000000000],
+  [bba071bcb470f8f6586e5d3add18bc66])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 11],
+  [e37b1c6aa2846f6fdb413f238b089f23],
+  [00000000000000000000000000000000],
+  [43c9f7e62f5d288bb27aa40ef8fe1ea8])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 12],
+  [6c002b682483e0cabcc731c253be5674],
+  [00000000000000000000000000000000],
+  [3580d19cff44f1014a7c966a69059de5])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 13],
+  [143ae8ed6555aba96110ab58893a8ae1],
+  [00000000000000000000000000000000],
+  [806da864dd29d48deafbe764f8202aef])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 14],
+  [b69418a85332240dc82492353956ae0c],
+  [00000000000000000000000000000000],
+  [a303d940ded8f0baff6f75414cac5243])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 15],
+  [71b5c08a1993e1362e4d0ce9b22b78d5],
+  [00000000000000000000000000000000],
+  [c2dabd117f8a3ecabfbb11d12194d9d0])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 16],
+  [e234cdca2606b81f29408d5f6da21206],
+  [00000000000000000000000000000000],
+  [fff60a4740086b3b9c56195b98d91a7b])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 17],
+  [13237c49074a3da078dc1d828bb78c6f],
+  [00000000000000000000000000000000],
+  [8146a08e2357f0caa30ca8c94d1a0544])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 18],
+  [3071a2a48fe6cbd04f1a129098e308f8],
+  [00000000000000000000000000000000],
+  [4b98e06d356deb07ebb824e5713f7be3])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 19],
+  [90f42ec0f68385f2ffc5dfc03a654dce],
+  [00000000000000000000000000000000],
+  [7a20a53d460fc9ce0423a7a0764c6cf2])
+
+AES128_CHECK(
+  [NIST KAT ECBKeySbox128e vector 20],
+  [febd9a24d8b65c1c787d50a4ed3619a9],
+  [00000000000000000000000000000000],
+  [f4a70d8af877f9b02b4c40df57d45b17])
index d0ca704..8ac4f67 100644 (file)
@@ -1,3 +1,5 @@
 # -*- shell-script -*-
+HAVE_OPENSSL='@HAVE_OPENSSL@'
+HAVE_PYTHON='@HAVE_PYTHON@'
 PERL='@PERL@'
-LCOV='@LCOV@'
+PYTHON='@PYTHON@'
index bd8a605..f862c33 100644 (file)
@@ -6,17 +6,143 @@ EXTRA_DIST += \
        $(srcdir)/tests/testsuite
 TESTSUITE_AT = \
        tests/testsuite.at \
-       tests/lcov-pre.at \
+       tests/ovsdb-macros.at \
        tests/library.at \
+       tests/check-structs.at \
+       tests/daemon.at \
+       tests/vconn.at \
+       tests/dir_name.at \
+       tests/aes128.at \
+       tests/uuid.at \
+       tests/json.at \
+       tests/jsonrpc.at \
+       tests/timeval.at \
+       tests/lockfile.at \
+       tests/reconnect.at \
+       tests/ovsdb.at \
+       tests/ovsdb-log.at \
+       tests/ovsdb-types.at \
+       tests/ovsdb-data.at \
+       tests/ovsdb-column.at \
+       tests/ovsdb-table.at \
+       tests/ovsdb-row.at \
+       tests/ovsdb-condition.at \
+       tests/ovsdb-mutation.at \
+       tests/ovsdb-query.at \
+       tests/ovsdb-transaction.at \
+       tests/ovsdb-execution.at \
+       tests/ovsdb-trigger.at \
+       tests/ovsdb-file.at \
+       tests/ovsdb-server.at \
+       tests/ovsdb-monitor.at \
+       tests/ovsdb-idl.at \
        tests/stp.at \
-       tests/ovs-vsctl.at \
-       tests/lcov-post.at
+       tests/ovs-vsctl.at
 TESTSUITE = $(srcdir)/tests/testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal $(TESTSUITE)
 
+AUTOTEST_PATH = utilities:vswitchd:ovsdb:tests
+
 check-local: tests/atconfig tests/atlocal $(TESTSUITE)
-       $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH='utilities:vswitchd:tests' $(TESTSUITEFLAGS)
+       $(SHELL) '$(TESTSUITE)' -C tests AUTOTEST_PATH=$(AUTOTEST_PATH) $(TESTSUITEFLAGS)
+\f
+# lcov support
+
+lcov_wrappers = \
+       tests/lcov/ovs-appctl \
+       tests/lcov/ovs-vsctl \
+       tests/lcov/ovsdb-client \
+       tests/lcov/ovsdb-server \
+       tests/lcov/ovsdb-tool \
+       tests/lcov/test-aes128 \
+       tests/lcov/test-classifier \
+       tests/lcov/test-csum \
+       tests/lcov/test-dhcp-client \
+       tests/lcov/test-dir_name \
+       tests/lcov/test-flows \
+       tests/lcov/test-hash \
+       tests/lcov/test-hmap \
+       tests/lcov/test-json \
+       tests/lcov/test-jsonrpc \
+       tests/lcov/test-list \
+       tests/lcov/test-lockfile \
+       tests/lcov/test-ovsdb \
+       tests/lcov/test-reconnect \
+       tests/lcov/test-sha1 \
+       tests/lcov/test-stp \
+       tests/lcov/test-timeval \
+       tests/lcov/test-type-props \
+       tests/lcov/test-uuid \
+       tests/lcov/test-vconn
+
+$(lcov_wrappers): tests/lcov-wrapper.in
+       @test -d tests/lcov || mkdir tests/lcov
+       sed -e 's,[@]abs_top_builddir[@],$(abs_top_builddir),' \
+           -e 's,[@]wrap_program[@],$@,' \
+               $(top_srcdir)/tests/lcov-wrapper.in > $@.tmp
+       chmod +x $@.tmp
+       mv $@.tmp $@
+CLEANFILES += $(lcov_wrappers)
+EXTRA_DIST += tests/lcov-wrapper.in
+
+LCOV = lcov -b $(abs_top_builddir) -d $(abs_top_builddir) -q
+check-lcov: all tests/atconfig tests/atlocal $(TESTSUITE) $(lcov_wrappers)
+       rm -fr tests/coverage.html tests/coverage.info
+       $(LCOV) -c -i -o - > tests/coverage.info
+       $(SHELL) '$(TESTSUITE)' -C tests CHECK_LCOV=true AUTOTEST_PATH='tests/lcov:$(AUTOTEST_PATH)' $(TESTSUITEFLAGS); \
+               rc=$$?; \
+               echo "Producing coverage.html..."; \
+               cd tests && genhtml -q -o coverage.html coverage.info; \
+               exit $$rc
+\f
+# valgrind support
+
+valgrind_wrappers = \
+       tests/valgrind/ovs-appctl \
+       tests/valgrind/ovs-vsctl \
+       tests/valgrind/ovsdb-client \
+       tests/valgrind/ovsdb-server \
+       tests/valgrind/ovsdb-tool \
+       tests/valgrind/test-aes128 \
+       tests/valgrind/test-classifier \
+       tests/valgrind/test-csum \
+       tests/valgrind/test-dhcp-client \
+       tests/valgrind/test-dir_name \
+       tests/valgrind/test-flows \
+       tests/valgrind/test-hash \
+       tests/valgrind/test-hmap \
+       tests/valgrind/test-json \
+       tests/valgrind/test-jsonrpc \
+       tests/valgrind/test-list \
+       tests/valgrind/test-lockfile \
+       tests/valgrind/test-ovsdb \
+       tests/valgrind/test-reconnect \
+       tests/valgrind/test-sha1 \
+       tests/valgrind/test-stp \
+       tests/valgrind/test-timeval \
+       tests/valgrind/test-type-props \
+       tests/valgrind/test-uuid \
+       tests/valgrind/test-vconn
 
+$(valgrind_wrappers): tests/valgrind-wrapper.in
+       @test -d tests/valgrind || mkdir tests/valgrind
+       sed -e 's,[@]wrap_program[@],$@,' \
+               $(top_srcdir)/tests/valgrind-wrapper.in > $@.tmp
+       chmod +x $@.tmp
+       mv $@.tmp $@
+CLEANFILES += $(valgrind_wrappers)
+EXTRA_DIST += tests/valgrind-wrapper.in
+
+VALGRIND = valgrind --log-file=valgrind.%p --leak-check=full \
+       --suppressions=$(abs_top_srcdir)/tests/openssl.supp --num-callers=20
+EXTRA_DIST += tests/openssl.supp
+check-valgrind: all tests/atconfig tests/atlocal $(TESTSUITE) $(valgrind_wrappers)
+       $(SHELL) '$(TESTSUITE)' -C tests CHECK_VALGRIND=true VALGRIND='$(VALGRIND)' AUTOTEST_PATH='tests/valgrind:$(AUTOTEST_PATH)' -d $(TESTSUITEFLAGS)
+       @echo
+       @echo '----------------------------------------------------------------------'
+       @echo 'Valgrind output can be found in tests/testsuite.dir/*/valgrind.*'
+       @echo '----------------------------------------------------------------------'
+\f
 clean-local:
        test ! -f '$(TESTSUITE)' || $(SHELL) '$(TESTSUITE)' -C tests --clean
 
@@ -30,13 +156,17 @@ $(TESTSUITE): package.m4 $(TESTSUITE_AT)
 $(srcdir)/package.m4: $(top_srcdir)/configure.ac
        :;{ \
          echo '# Signature of the current package.' && \
-         echo 'm4_define([AT_PACKAGE_NAME],      [@PACKAGE_NAME@])' && \
-         echo 'm4_define([AT_PACKAGE_TARNAME],   [@PACKAGE_TARNAME@])' && \
-         echo 'm4_define([AT_PACKAGE_VERSION],   [@PACKAGE_VERSION@])' && \
-         echo 'm4_define([AT_PACKAGE_STRING],    [@PACKAGE_STRING@])' && \
-         echo 'm4_define([AT_PACKAGE_BUGREPORT], [@PACKAGE_BUGREPORT@])'; \
+         echo 'm4_define([AT_PACKAGE_NAME],      [$(PACKAGE_NAME)])' && \
+         echo 'm4_define([AT_PACKAGE_TARNAME],   [$(PACKAGE_TARNAME)])' && \
+         echo 'm4_define([AT_PACKAGE_VERSION],   [$(PACKAGE_VERSION)])' && \
+         echo 'm4_define([AT_PACKAGE_STRING],    [$(PACKAGE_STRING)])' && \
+         echo 'm4_define([AT_PACKAGE_BUGREPORT], [$(PACKAGE_BUGREPORT)])'; \
        } >'$(srcdir)/package.m4'
 
+noinst_PROGRAMS += tests/test-aes128
+tests_test_aes128_SOURCES = tests/test-aes128.c
+tests_test_aes128_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-classifier
 tests_test_classifier_SOURCES = tests/test-classifier.c
 tests_test_classifier_LDADD = lib/libopenvswitch.a
@@ -45,6 +175,10 @@ noinst_PROGRAMS += tests/test-csum
 tests_test_csum_SOURCES = tests/test-csum.c
 tests_test_csum_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-dir_name
+tests_test_dir_name_SOURCES = tests/test-dir_name.c
+tests_test_dir_name_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-flows
 tests_test_flows_SOURCES = tests/test-flows.c
 tests_test_flows_LDADD = lib/libopenvswitch.a
@@ -58,14 +192,52 @@ noinst_PROGRAMS += tests/test-hmap
 tests_test_hmap_SOURCES = tests/test-hmap.c
 tests_test_hmap_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-json
+tests_test_json_SOURCES = tests/test-json.c
+tests_test_json_LDADD = lib/libopenvswitch.a
+
+noinst_PROGRAMS += tests/test-jsonrpc
+tests_test_jsonrpc_SOURCES = tests/test-jsonrpc.c
+tests_test_jsonrpc_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+
 noinst_PROGRAMS += tests/test-list
 tests_test_list_SOURCES = tests/test-list.c
 tests_test_list_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-lockfile
+tests_test_lockfile_SOURCES = tests/test-lockfile.c
+tests_test_lockfile_LDADD = lib/libopenvswitch.a
+
+noinst_PROGRAMS += tests/test-ovsdb
+tests_test_ovsdb_SOURCES = \
+       tests/test-ovsdb.c \
+       tests/idltest.c \
+       tests/idltest.h
+EXTRA_DIST += tests/uuidfilt.pl
+tests_test_ovsdb_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a $(SSL_LIBS)
+
+# idltest schema and IDL
+OVSIDL_BUILT +=        tests/idltest.c tests/idltest.h tests/idltest.ovsidl
+IDLTEST_IDL_FILES = tests/idltest.ovsschema tests/idltest.ann
+EXTRA_DIST += $(IDLTEST_IDL_FILES)
+tests/idltest.ovsidl: $(IDLTEST_IDL_FILES)
+       $(OVSDB_IDLC) -C $(srcdir) annotate $(IDLTEST_IDL_FILES) > $@.tmp
+       mv $@.tmp $@
+
+tests/idltest.c: tests/idltest.h
+
+noinst_PROGRAMS += tests/test-reconnect
+tests_test_reconnect_SOURCES = tests/test-reconnect.c
+tests_test_reconnect_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-sha1
 tests_test_sha1_SOURCES = tests/test-sha1.c
 tests_test_sha1_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-timeval
+tests_test_timeval_SOURCES = tests/test-timeval.c
+tests_test_timeval_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-strtok_r
 tests_test_strtok_r_SOURCES = tests/test-strtok_r.c
 
@@ -74,13 +246,24 @@ tests_test_type_props_SOURCES = tests/test-type-props.c
 
 noinst_PROGRAMS += tests/test-dhcp-client
 tests_test_dhcp_client_SOURCES = tests/test-dhcp-client.c
-tests_test_dhcp_client_LDADD = lib/libopenvswitch.a $(FAULT_LIBS)
+tests_test_dhcp_client_LDADD = lib/libopenvswitch.a
 
 noinst_PROGRAMS += tests/test-stp
 tests_test_stp_SOURCES = tests/test-stp.c
 tests_test_stp_LDADD = lib/libopenvswitch.a
 
+noinst_PROGRAMS += tests/test-uuid
+tests_test_uuid_SOURCES = tests/test-uuid.c
+tests_test_uuid_LDADD = lib/libopenvswitch.a
+
 noinst_PROGRAMS += tests/test-vconn
 tests_test_vconn_SOURCES = tests/test-vconn.c
 tests_test_vconn_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
-
+EXTRA_DIST += \
+       tests/testpki-cacert.pem \
+       tests/testpki-cert.pem \
+       tests/testpki-cert2.pem \
+       tests/testpki-privkey.pem \
+       tests/testpki-privkey2.pem \
+       tests/testpki-req.pem \
+       tests/testpki-req2.pem
diff --git a/tests/check-structs.at b/tests/check-structs.at
new file mode 100644 (file)
index 0000000..52e92ec
--- /dev/null
@@ -0,0 +1,41 @@
+AT_BANNER([struct alignment checker unit tests])
+
+m4_define([check_structs], [$top_srcdir/build-aux/check-structs])
+m4_define([RUN_STRUCT_CHECKER], 
+  [AT_SKIP_IF([test $HAVE_PYTHON = no])
+   AT_DATA([test.h], [$1
+])
+   AT_CHECK_UNQUOTED([$PYTHON check_structs test.h], [$2], [$3], [$4])])
+
+AT_SETUP([check struct tail padding])
+RUN_STRUCT_CHECKER(
+[struct xyz {
+    uint16_t x;
+};], 
+  [1], [], 
+  [test.h:3: warning: struct xyz needs 2 bytes of tail padding
+])
+AT_CLEANUP
+
+AT_SETUP([check struct internal alignment])
+RUN_STRUCT_CHECKER(
+[struct xyzzy {
+    uint16_t x;
+    uint32_t y;
+};], 
+  [1], [], 
+  [test.h:3: warning: struct xyzzy member y is 2 bytes short of 4-byte alignment
+])
+AT_CLEANUP
+
+AT_SETUP([check struct declared size])
+RUN_STRUCT_CHECKER(
+[struct wibble {
+    uint64_t z;
+};
+OFP_ASSERT(sizeof(struct wibble) == 12);
+], 
+  [1], [], 
+  [test.h:4: warning: struct wibble is 8 bytes long but declared as 12
+])
+AT_CLEANUP
diff --git a/tests/daemon.at b/tests/daemon.at
new file mode 100644 (file)
index 0000000..d2b0180
--- /dev/null
@@ -0,0 +1,169 @@
+AT_BANNER([daemon unit tests])
+
+AT_SETUP([daemon])
+AT_SKIP_IF([test "$CHECK_LCOV" = true]) # lcov wrapper make pids differ
+OVSDB_INIT([db])
+AT_CAPTURE_FILE([pid])
+AT_CAPTURE_FILE([expected])
+# Start the daemon and wait for the pidfile to get created
+# and that its contents are the correct pid.
+AT_CHECK([ovsdb-server --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db& echo $! > expected], [0], [ignore], [ignore])
+OVS_WAIT_UNTIL([test -s pid], [kill `cat expected`])
+AT_CHECK(
+  [pid=`cat pid` && expected=`cat expected` && test "$pid" = "$expected"],
+  [0], [], [], [kill `cat expected`])
+AT_CHECK([kill -0 `cat pid`], [0], [], [], [kill `cat expected`])
+# Kill the daemon and make sure that the pidfile gets deleted.
+kill `cat expected`
+OVS_WAIT_WHILE([kill -0 `cat expected`])
+AT_CHECK([test ! -e pid])
+AT_CLEANUP
+
+AT_SETUP([daemon --monitor])
+AT_SKIP_IF([test "$CHECK_LCOV" = true]) # lcov wrapper make pids differ
+OVSDB_INIT([db])
+AT_CAPTURE_FILE([pid])
+AT_CAPTURE_FILE([parent])
+AT_CAPTURE_FILE([parentpid])
+AT_CAPTURE_FILE([newpid])
+# Start the daemon and wait for the pidfile to get created.
+AT_CHECK([ovsdb-server --monitor --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db& echo $! > parent], [0], [ignore], [ignore])
+OVS_WAIT_UNTIL([test -s pid], [kill `cat parent`])
+# Check that the pidfile names a running process,
+# and that the parent process of that process is our child process.
+AT_CHECK([kill -0 `cat pid`], [0], [], [], [kill `cat parent`])
+AT_CHECK([ps -o ppid= -p `cat pid` > parentpid],
+  [0], [], [], [kill `cat parent`])
+AT_CHECK(
+  [parentpid=`cat parentpid` && 
+   parent=`cat parent` && 
+   test $parentpid = $parent],
+  [0], [], [], [kill `cat parent`])
+# Kill the daemon process, making it look like a segfault,
+# and wait for a new child process to get spawned.
+AT_CHECK([cp pid oldpid], [0], [], [], [kill `cat parent`])
+AT_CHECK([kill -SEGV `cat pid`], [0], [], [ignore], [kill `cat parent`])
+OVS_WAIT_WHILE([kill -0 `cat oldpid`], [kill `cat parent`])
+OVS_WAIT_UNTIL([test -s pid && test `cat pid` != `cat oldpid`],
+  [kill `cat parent`])
+AT_CHECK([cp pid newpid], [0], [], [], [kill `cat parent`])
+# Check that the pidfile names a running process,
+# and that the parent process of that process is our child process.
+AT_CHECK([ps -o ppid= -p `cat pid` > parentpid],
+  [0], [], [], [kill `cat parent`])
+AT_CHECK(
+  [parentpid=`cat parentpid` && 
+   parent=`cat parent` && 
+   test $parentpid = $parent],
+  [0], [], [], [kill `cat parent`])
+# Kill the daemon process with SIGTERM, and wait for the daemon
+# and the monitor processes to go away and the pidfile to get deleted.
+AT_CHECK([kill `cat pid`], [0], [], [ignore], [kill `cat parent`])
+OVS_WAIT_WHILE([kill -0 `cat parent` || kill -0 `cat newpid` || test -e pid],
+  [kill `cat parent`])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach])
+AT_SKIP_IF([test "$CHECK_LCOV" = true]) # lcov wrapper make pids differ
+AT_CAPTURE_FILE([pid])
+OVSDB_INIT([db])
+# Start the daemon and make sure that the pidfile exists immediately.
+# We don't wait for the pidfile to get created because the daemon is
+# supposed to do so before the parent exits.
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+AT_CHECK([test -s pid])
+AT_CHECK([kill -0 `cat pid`])
+# Kill the daemon and make sure that the pidfile gets deleted.
+cp pid saved-pid
+kill `cat pid`
+OVS_WAIT_WHILE([kill -0 `cat saved-pid`])
+AT_CHECK([test ! -e pid])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach --monitor])
+AT_SKIP_IF([test "$CHECK_LCOV" = true]) # lcov wrapper make pids differ
+m4_define([CHECK], 
+  [AT_CHECK([$1], [$2], [$3], [$4], [kill `cat daemon monitor`])])
+OVSDB_INIT([db])
+AT_CAPTURE_FILE([daemon])
+AT_CAPTURE_FILE([olddaemon])
+AT_CAPTURE_FILE([newdaemon])
+AT_CAPTURE_FILE([monitor])
+AT_CAPTURE_FILE([newmonitor])
+AT_CAPTURE_FILE([init])
+# Start the daemon and make sure that the pidfile exists immediately.
+# We don't wait for the pidfile to get created because the daemon is
+# supposed to do so before the parent exits.
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/daemon --monitor --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+AT_CHECK([test -s daemon])
+# Check that the pidfile names a running process,
+# and that the parent process of that process is a running process,
+# and that the parent process of that process is init.
+CHECK([kill -0 `cat daemon`])
+CHECK([ps -o ppid= -p `cat daemon` > monitor])
+CHECK([kill -0 `cat monitor`])
+CHECK([ps -o ppid= -p `cat monitor` > init])
+CHECK([test `cat init` = 1])
+# Kill the daemon process, making it look like a segfault,
+# and wait for a new daemon process to get spawned.
+CHECK([cp daemon olddaemon])
+CHECK([kill -SEGV `cat daemon`], [0], [ignore], [ignore])
+OVS_WAIT_WHILE([kill -0 `cat olddaemon`], [kill `cat olddaemon daemon`])
+OVS_WAIT_UNTIL([test -s daemon && test `cat daemon` != `cat olddaemon`],
+  [kill `cat olddaemon daemon`])
+CHECK([cp daemon newdaemon])
+# Check that the pidfile names a running process,
+# and that the parent process of that process is our child process.
+CHECK([kill -0 `cat daemon`])
+CHECK([diff olddaemon newdaemon], [1], [ignore])
+CHECK([ps -o ppid= -p `cat daemon` > newmonitor])
+CHECK([diff monitor newmonitor])
+CHECK([kill -0 `cat newmonitor`])
+CHECK([ps -o ppid= -p `cat newmonitor` > init])
+CHECK([test `cat init` = 1])
+# Kill the daemon process with SIGTERM, and wait for the daemon
+# and the monitor processes to go away and the pidfile to get deleted.
+CHECK([kill `cat daemon`], [0], [], [ignore])
+OVS_WAIT_WHILE(
+  [kill -0 `cat monitor` || kill -0 `cat newdaemon` || test -e daemon],
+  [kill `cat monitor newdaemon`])
+m4_undefine([CHECK])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach startup errors])
+AT_CAPTURE_FILE([pid])
+OVSDB_INIT([db])
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --unixctl=$PWD/nonexistent/unixctl db], [1], [], [stderr])
+AT_CHECK([grep 'ovsdb-server: could not initialize control socket' stderr],
+  [0], [ignore], [])
+AT_CHECK([test ! -s pid])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach --monitor startup errors])
+AT_CAPTURE_FILE([pid])
+OVSDB_INIT([db])
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --monitor --unixctl=$PWD/nonexistent/unixctl db], [1], [], [stderr])
+AT_CHECK([grep 'ovsdb-server: could not initialize control socket' stderr],
+  [0], [ignore], [])
+AT_CHECK([test ! -s pid])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach closes standard fds])
+AT_CAPTURE_FILE([pid])
+OVSDB_INIT([db])
+AT_CHECK([(yes; echo $? > status) | ovsdb-server --detach --pidfile=$PWD/pid --unixctl=$PWD/unixctl db], [0], [], [stderr])
+AT_CHECK([kill `cat pid`])
+AT_CHECK([test -s status])
+AT_CHECK([kill -l `cat status`], [0], [PIPE
+])
+AT_CLEANUP
+
+AT_SETUP([daemon --detach --monitor closes standard fds])
+AT_CAPTURE_FILE([pid])
+OVSDB_INIT([db])
+AT_CHECK([(yes; echo $? > status) | ovsdb-server --detach --monitor --pidfile=$PWD/pid --unixctl=$PWD/unixctl db], [0], [], [stderr])
+AT_CHECK([kill `cat pid`])
+AT_CHECK([test -s status])
+AT_CHECK([kill -l `cat status`], [0], [PIPE
+])
+AT_CLEANUP
diff --git a/tests/dir_name.at b/tests/dir_name.at
new file mode 100644 (file)
index 0000000..062e422
--- /dev/null
@@ -0,0 +1,25 @@
+AT_BANNER([test dir_name function])
+
+m4_define([CHECK_DIR_NAME],
+  [AT_SETUP([dir_name("$1") returns "$2"])
+   AT_KEYWORDS([dir_name])
+   AT_CHECK([test-dir_name "AS_ESCAPE($1)"], [0], [$2
+])
+   AT_CLEANUP])
+
+# These are the test cases given in POSIX for dirname().
+CHECK_DIR_NAME([/usr/lib], [/usr])
+CHECK_DIR_NAME([/usr/], [/])
+CHECK_DIR_NAME([usr], [.])
+CHECK_DIR_NAME([/], [/])
+CHECK_DIR_NAME([.], [.])
+CHECK_DIR_NAME([..], [.])
+CHECK_DIR_NAME([//], [//])      # / is also allowed
+CHECK_DIR_NAME([//foo], [//])   # / is also allowed
+CHECK_DIR_NAME([], [.])
+
+# Additional test cases.
+CHECK_DIR_NAME([dir/file], [dir])
+CHECK_DIR_NAME([dir/file/], [dir])
+CHECK_DIR_NAME([dir/file//], [dir])
+CHECK_DIR_NAME([///foo], [/])
diff --git a/tests/idltest.ann b/tests/idltest.ann
new file mode 100644 (file)
index 0000000..2ffd1af
--- /dev/null
@@ -0,0 +1,13 @@
+# -*- python -*-
+
+# This code, when invoked by "ovsdb-idlc annotate" (by the build
+# process), annotates idltest.ovsschema with additional data that give
+# the ovsdb-idl engine information about the types involved, so that
+# it can generate more programmer-friendly data structures.
+
+s["idlPrefix"] = "idltest_"
+s["idlHeader"] = "\"tests/idltest.h\""
+s["tables"]["link1"]["columns"]["k"]["type"]["keyRefTable"] = "link1"
+s["tables"]["link1"]["columns"]["ka"]["type"]["keyRefTable"] = "link1"
+s["tables"]["link1"]["columns"]["l2"]["type"]["keyRefTable"] = "link2"
+s["tables"]["link2"]["columns"]["l1"]["type"]["keyRefTable"] = "link1"
diff --git a/tests/idltest.ovsschema b/tests/idltest.ovsschema
new file mode 100644 (file)
index 0000000..239a343
--- /dev/null
@@ -0,0 +1,25 @@
+{"name": "idltest",
+ "tables": {
+   "simple": {
+     "columns": {
+       "i": {"type": "integer"},
+       "r": {"type": "real"},
+       "b": {"type": "boolean"},
+       "s": {"type": "string"},
+       "u": {"type": "uuid"},
+       "ia": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+       "ra": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+       "ba": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+       "sa": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+       "ua": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}},
+   "link1": {
+     "columns": {
+       "i": {"type": "integer"},
+       "k": {"type": {"key": "uuid"}},
+       "ka": {"type": {"key": "uuid",
+                       "min": 0, "max": "unlimited"}},
+       "l2": {"type": {"key": "uuid", "min": 0, "max": 1}}}},
+   "link2": {
+     "columns": {
+       "i": {"type": "integer"},
+       "l1": {"type": {"key": "uuid", "min": 0, "max": 1}}}}}}
diff --git a/tests/json.at b/tests/json.at
new file mode 100644 (file)
index 0000000..0449e03
--- /dev/null
@@ -0,0 +1,312 @@
+m4_define([JSON_CHECK_POSITIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([json positive])
+   AT_CHECK([printf %s "AS_ESCAPE([$2])" > input])
+   AT_CAPTURE_FILE([input])
+   AT_CHECK([test-json $4 input], [0], [stdout], [])
+   AT_CHECK([cat stdout], [0], [$3
+])
+   AT_CLEANUP])
+
+m4_define([JSON_CHECK_NEGATIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([json negative])
+   AT_CHECK([printf %s "AS_ESCAPE([$2])" > input])
+   AT_CAPTURE_FILE([input])
+   AT_CHECK([test-json $4 input], [1], [stdout], [])
+   AT_CHECK([[sed 's/^error: [^:]*:/error:/' < stdout]], [0], [$3
+])
+   AT_CLEANUP])
+
+AT_BANNER([JSON -- arrays])
+
+JSON_CHECK_POSITIVE([empty array], [[ [   ] ]], [[[]]])
+JSON_CHECK_POSITIVE([single-element array], [[ [ 1 ] ]], [[[1]]])
+JSON_CHECK_POSITIVE([2-element array], [[ [ 1, 2 ] ]], [[[1,2]]])
+JSON_CHECK_POSITIVE([many-element array],
+                    [[ [ 1, 2, 3, 4, 5 ] ]],
+                    [[[1,2,3,4,5]]])
+JSON_CHECK_NEGATIVE([missing comma], [[ [ 1, 2, 3 4, 5 ] ]],
+                    [error: syntax error expecting '@:>@' or ','])
+JSON_CHECK_NEGATIVE([trailing comma not allowed], 
+                    [[[1,2,]]], [error: syntax error expecting value])
+JSON_CHECK_NEGATIVE([doubled comma not allowed], 
+                    [[[1,,2]]], [error: syntax error expecting value])
+
+AT_BANNER([JSON -- strings])
+
+JSON_CHECK_POSITIVE([empty string], [[[ "" ]]], [[[""]]])
+JSON_CHECK_POSITIVE([1-character strings], 
+                    [[[ "a", "b", "c" ]]],
+                    [[["a","b","c"]]])
+JSON_CHECK_POSITIVE([escape sequences], 
+  [[[ " \" \\ \/ \b \f \n \r \t" ]]],
+  [[[" \" \\ / \b \f \n \r \t"]]])
+JSON_CHECK_POSITIVE([Unicode escape sequences], 
+  [[[ " \u0022 \u005c \u002F \u0008 \u000c \u000A \u000d \u0009" ]]],
+  [[[" \" \\ / \b \f \n \r \t"]]])
+JSON_CHECK_POSITIVE([surrogate pairs],
+  [[["\ud834\udd1e"]]],
+  [[["𝄞"]]])
+JSON_CHECK_NEGATIVE([a string by itself is not valid JSON], ["xxx"],
+                    [error: syntax error at beginning of input])
+JSON_CHECK_NEGATIVE([end of line in quoted string],
+                    [[["xxx
+"]]],
+                    [error: U+000A must be escaped in quoted string])
+JSON_CHECK_NEGATIVE([formfeed in quoted string],
+                    [[["xxx\f"]]],
+                    [error: U+000C must be escaped in quoted string])
+JSON_CHECK_NEGATIVE([bad escape in quoted string],
+                    [[["\x12"]]],
+                    [error: bad escape \x])
+JSON_CHECK_NEGATIVE([\u must be followed by 4 hex digits (1)],
+                    [[["\u1x"]]],
+                    [error: quoted string ends within \u escape])
+JSON_CHECK_NEGATIVE([\u must be followed by 4 hex digits (2)],
+                    [[["\u1xyz"]]],
+                    [error: malformed \u escape])
+JSON_CHECK_NEGATIVE([isolated leading surrogate not allowed],
+                    [[["\ud834xxx"]]],
+                    [error: malformed escaped surrogate pair])
+JSON_CHECK_NEGATIVE([surrogatess must paired properly],
+                    [[["\ud834\u1234"]]],
+                    [error: second half of escaped surrogate pair is not trailing surrogate])
+JSON_CHECK_NEGATIVE([null bytes not allowed], 
+                    [[["\u0000"]]], 
+                    [error: null bytes not supported in quoted strings])
+
+AT_SETUP([end of input in quoted string])
+AT_KEYWORDS([json negative])
+AT_CHECK([printf '\"xxx' | test-json -], [1],
+  [error: line 0, column 4, byte 4: unexpected end of input in quoted string
+])
+AT_CLEANUP
+
+AT_BANNER([JSON -- objects])
+
+JSON_CHECK_POSITIVE([empty object], [[{ }]], [[{}]])
+JSON_CHECK_POSITIVE([simple object],
+                    [[{"b": 2, "a": 1, "c": 3}]],
+                    [[{"a":1,"b":2,"c":3}]])
+JSON_CHECK_NEGATIVE([bad value], [[{"a": }, "b": 2]], 
+                    [error: syntax error expecting value])
+JSON_CHECK_NEGATIVE([missing colon], [[{"b": 2, "a" 1, "c": 3}]],
+                    [error: syntax error parsing object expecting ':'])
+JSON_CHECK_NEGATIVE([missing comma], [[{"b": 2 "a" 1, "c": 3}]],
+                    [error: syntax error expecting '}' or ','])
+JSON_CHECK_NEGATIVE([trailing comma not allowed],
+                    [[{"b": 2, "a": 1, "c": 3, }]],
+                    [[error: syntax error parsing object expecting string]])
+JSON_CHECK_NEGATIVE([doubled comma not allowed],
+                    [[{"b": 2, "a": 1,, "c": 3}]],
+                    [[error: syntax error parsing object expecting string]])
+JSON_CHECK_NEGATIVE([names must be strings],
+                    [[{1: 2}]],
+                    [[error: syntax error parsing object expecting string]])
+
+AT_BANNER([JSON -- literal names])
+
+JSON_CHECK_POSITIVE([null], [[[ null ]]], [[[null]]])
+JSON_CHECK_POSITIVE([false], [[[ false ]]], [[[false]]])
+JSON_CHECK_POSITIVE([true], [[[ true ]]], [[[true]]])
+JSON_CHECK_NEGATIVE([a literal by itself is not valid JSON], [null],
+                    [error: syntax error at beginning of input])
+JSON_CHECK_NEGATIVE([nullify is invalid], [[[ nullify ]]], 
+                    [error: invalid keyword 'nullify'])
+JSON_CHECK_NEGATIVE([nubs is invalid], [[[ nubs ]]],
+                    [error: invalid keyword 'nubs'])
+JSON_CHECK_NEGATIVE([xxx is invalid], [[[ xxx ]]], 
+                    [error: invalid keyword 'xxx'])
+
+AT_BANNER([JSON -- numbers])
+
+JSON_CHECK_POSITIVE(
+  [integers expressed as reals],
+  [[[1.0000000000,
+     2.00000000000000000000000000000000000,
+     2e5,
+     2.1234e4,
+     2.1230e3,
+     0e-10000,
+     0e10000]]],
+  [[[1,2,200000,21234,2123,0,0]]])
+JSON_CHECK_POSITIVE(
+  [large integers], 
+  [[[9223372036854775807, -9223372036854775808]]],
+  [[[9223372036854775807,-9223372036854775808]]])
+JSON_CHECK_POSITIVE(
+  [large integers expressed as reals], 
+  [[[9223372036854775807.0, -9223372036854775808.0,
+     92233720.36854775807e11, -9.223372036854775808e18]]],
+  [[[9223372036854775807,-9223372036854775808,9223372036854775807,-9223372036854775808]]])
+# It seems likely that the following test will fail on some system that
+# rounds slightly differently in arithmetic or in printf, but I'd like
+# to keep it this way until we run into such a system.
+JSON_CHECK_POSITIVE(
+  [large integers that overflow to reals], 
+  [[[9223372036854775807000, -92233720368547758080000]]],
+  [[[9.22337203685478e+21,-9.22337203685478e+22]]])
+
+JSON_CHECK_POSITIVE(
+  [negative zero],
+  [[[-0, -0.0, 1e-9999, -1e-9999]]],
+  [[[0,0,0,0]]])
+
+JSON_CHECK_POSITIVE(
+  [reals], 
+  [[[0.0, 1.0, 2.0, 3.0, 3.5, 81.250]]],
+  [[[0,1,2,3,3.5,81.25]]])
+JSON_CHECK_POSITIVE(
+  [scientific notation],
+  [[[1e3, 1E3, 2.5E2, 1e+3, 125e-3, 3.125e-2, 3125e-05, 1.525878906e-5]]],
+  [[[1000,1000,250,1000,0.125,0.03125,0.03125,1.525878906e-05]]])
+# It seems likely that the following test will fail on some system that
+# rounds slightly differently in arithmetic or in printf, but I'd like
+# to keep it this way until we run into such a system.
+JSON_CHECK_POSITIVE(
+  [+/- DBL_MAX],
+  [[[1.7976931348623157e+308, -1.7976931348623157e+308]]],
+  [[[1.79769313486232e+308,-1.79769313486232e+308]]])
+
+JSON_CHECK_POSITIVE(
+  [negative reals], 
+  [[[-0, -1.0, -2.0, -3.0, -3.5, -8.1250]]],
+  [[[0,-1,-2,-3,-3.5,-8.125]]])
+JSON_CHECK_POSITIVE(
+  [negative scientific notation],
+  [[[-1e3, -1E3, -2.5E2, -1e+3, -125e-3, -3.125e-2, -3125e-05, -1.525878906e-5]]],
+  [[[-1000,-1000,-250,-1000,-0.125,-0.03125,-0.03125,-1.525878906e-05]]])
+JSON_CHECK_POSITIVE(
+  [1e-9999 underflows to 0],
+  [[[1e-9999]]],
+  [[[0]]])
+JSON_CHECK_NEGATIVE([a number by itself is not valid JSON], [1],
+                    [error: syntax error at beginning of input])
+JSON_CHECK_NEGATIVE(
+  [leading zeros not allowed],
+  [[[0123]]],
+  [error: leading zeros not allowed])
+JSON_CHECK_NEGATIVE(
+  [1e9999 is too big],
+  [[[1e9999]]],
+  [error: number outside valid range])
+JSON_CHECK_NEGATIVE(
+  [exponent bigger than INT_MAX],
+  [[[1e9999999999999999999]]],
+  [error: exponent outside valid range])
+JSON_CHECK_NEGATIVE(
+  [decimal point must be followed by digit],
+  [[[1.]]],
+  [error: decimal point must be followed by digit])
+JSON_CHECK_NEGATIVE(
+  [exponent must contain at least one digit (1)],
+  [[[1e]]],
+  [error: exponent must contain at least one digit])
+JSON_CHECK_NEGATIVE(
+  [exponent must contain at least one digit (2)],
+  [[[1e+]]],
+  [error: exponent must contain at least one digit])
+JSON_CHECK_NEGATIVE(
+  [exponent must contain at least one digit (3)],
+  [[[1e-]]],
+  [error: exponent must contain at least one digit])
+
+AT_BANNER([JSON -- RFC 4627 examples])
+
+JSON_CHECK_POSITIVE([RFC 4267 object example],
+[[{
+   "Image": {
+       "Width":  800,
+       "Height": 600,
+       "Title":  "View from 15th Floor",
+       "Thumbnail": {
+           "Url":    "http://www.example.com/image/481989943",
+           "Height": 125,
+           "Width":  "100"
+       },
+       "IDs": [116, 943, 234, 38793]
+     }
+}]],
+[[{"Image":{"Height":600,"IDs":[116,943,234,38793],"Thumbnail":{"Height":125,"Url":"http://www.example.com/image/481989943","Width":"100"},"Title":"View from 15th Floor","Width":800}}]])
+
+JSON_CHECK_POSITIVE([RFC 4267 array example],
+[[[
+   {
+      "precision": "zip",
+      "Latitude":  37.7668,
+      "Longitude": -122.3959,
+      "Address":   "",
+      "City":      "SAN FRANCISCO",
+      "State":     "CA",
+      "Zip":       "94107",
+      "Country":   "US"
+   },
+   {
+      "precision": "zip",
+      "Latitude":  37.371991,
+      "Longitude": -122.026020,
+      "Address":   "",
+      "City":      "SUNNYVALE",
+      "State":     "CA",
+      "Zip":       "94085",
+      "Country":   "US"
+   }
+]]],
+[[[{"Address":"","City":"SAN FRANCISCO","Country":"US","Latitude":37.7668,"Longitude":-122.3959,"State":"CA","Zip":"94107","precision":"zip"},{"Address":"","City":"SUNNYVALE","Country":"US","Latitude":37.371991,"Longitude":-122.02602,"State":"CA","Zip":"94085","precision":"zip"}]]])
+
+AT_BANNER([JSON -- pathological cases])
+
+JSON_CHECK_NEGATIVE([trailing garbage], [[[1]null]],
+                    [error: trailing garbage at end of input])
+JSON_CHECK_NEGATIVE([formfeeds are not valid white space],
+                    [[[\f]]], [error: invalid character U+000c])
+JSON_CHECK_NEGATIVE([';' is not a valid token],
+                    [;], [error: invalid character ';'])
+JSON_CHECK_NEGATIVE([arrays nesting too deep],
+                    [m4_for([i], [0], [1002], [1], [@<:@])dnl
+                     m4_for([i], [0], [1002], [1], [@:>@])],
+                    [error: input exceeds maximum nesting depth 1000])
+JSON_CHECK_NEGATIVE([objects nesting too deep],
+                    [m4_for([i], [0], [1002], [1], [{"x":])dnl
+                     m4_for([i], [0], [1002], [1], [}])],
+                    [error: input exceeds maximum nesting depth 1000])
+
+AT_SETUP([input may not be empty])
+AT_KEYWORDS([json negative])
+AT_CHECK([test-json /dev/null], [1], [error: line 0, column 0, byte 0: empty input stream
+])
+AT_CLEANUP
+
+AT_BANNER([JSON -- multiple inputs])
+
+JSON_CHECK_POSITIVE([multiple adjacent objects], [[{}{}{}]], [[{}
+{}
+{}]],
+  [--multiple])
+
+JSON_CHECK_POSITIVE([multiple space-separated objects], [[{}  {}  {}]], [[{}
+{}
+{}]],
+  [--multiple])
+
+JSON_CHECK_POSITIVE([multiple objects on separate lines], [[{}
+{}
+{}]], [[{}
+{}
+{}]],
+  [--multiple])
+
+JSON_CHECK_POSITIVE([multiple objects and arrays], [[{}[]{}[]]], [[{}
+[]
+{}
+[]]],
+  [--multiple])
+
+JSON_CHECK_NEGATIVE([garbage between multiple objects], [[{}x{}]], [[{}
+error: invalid keyword 'x'
+{}]], [--multiple])
+
+JSON_CHECK_NEGATIVE([garbage after multiple objects], [[{}{}x]], [[{}
+{}
+error: invalid keyword 'x']], [--multiple])
diff --git a/tests/jsonrpc.at b/tests/jsonrpc.at
new file mode 100644 (file)
index 0000000..83410f9
--- /dev/null
@@ -0,0 +1,45 @@
+AT_BANNER([JSON-RPC])
+
+AT_SETUP([JSON-RPC request and successful reply])
+AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket])
+AT_CHECK([test -s pid])
+AT_CHECK([kill -0 `cat pid`])
+AT_CHECK(
+  [[test-jsonrpc request unix:socket echo '[{"a": "b", "x": null}]']], [0],
+  [[{"error":null,"id":0,"result":[{"a":"b","x":null}]}
+]], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK([kill `cat pid`])
+AT_CLEANUP
+
+AT_SETUP([JSON-RPC request and error reply])
+AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket])
+AT_CHECK([test -s pid])
+AT_CHECK([kill -0 `cat pid`])
+AT_CHECK(
+  [[test-jsonrpc request unix:socket bad-request '[]']], [0],
+  [[{"error":{"error":"unknown method"},"id":0,"result":null}
+]], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK([kill `cat pid`])
+AT_CLEANUP
+
+AT_SETUP([JSON-RPC notification])
+AT_CHECK([test-jsonrpc --detach --pidfile=$PWD/pid listen punix:socket])
+AT_CHECK([test -s pid])
+# When a daemon dies it deletes its pidfile, so make a copy.
+AT_CHECK([cp pid pid2])
+AT_CHECK([kill -0 `cat pid2`])
+AT_CHECK([[test-jsonrpc notify unix:socket shutdown '[]']], [0], [], 
+  [ignore], [kill `cat pid2`])
+AT_CHECK(
+  [pid=`cat pid2`
+   # First try a quick sleep, so that the test completes very quickly
+   # in the normal case.  POSIX doesn't require fractional times to
+   # work, so this might not work.
+   sleep 0.1; if kill -0 $pid; then :; else echo success; exit 0; fi
+   # Then wait up to 2 seconds.
+   sleep 1; if kill -0 $pid; then :; else echo success; exit 0; fi
+   sleep 1; if kill -0 $pid; then :; else echo success; exit 0; fi
+   echo failure; exit 1], [0], [success
+], [ignore])
+AT_CHECK([test ! -e pid])
+AT_CLEANUP
diff --git a/tests/lcov-post.at b/tests/lcov-post.at
deleted file mode 100644 (file)
index 957cdfa..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-AT_BANNER([code coverage])
-
-AT_SETUP([generate coverage.html with lcov])
-AT_CHECK([$LCOV || exit 77])
-AT_CHECK([cd $abs_builddir && genhtml -o coverage.html coverage.info], [0], [ignore], [ignore])
-AT_CLEANUP
diff --git a/tests/lcov-pre.at b/tests/lcov-pre.at
deleted file mode 100644 (file)
index 1ecfeb1..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-AT_BANNER([code coverage])
-
-m4_define([_OVS_RUN_LCOV], [test $LCOV = false || lcov -b $abs_top_builddir -d $abs_top_builddir $1])
-
-AT_SETUP([initialize lcov])
-AT_CHECK([rm -fr $abs_builddir/coverage.html])
-AT_CHECK([rm -f $abs_builddir/coverage.info])
-AT_CHECK([$LCOV || exit 77])
-AT_CHECK([_OVS_RUN_LCOV([-c -i -o - > $abs_builddir/coverage.info])], [0], [ignore], [ignore])
-AT_CLEANUP
-
-# OVS_CHECK_LCOV(COMMAND, [STATUS = `0'], [STDOUT = `'], [STDERR = `'], 
-#                [RUN-IF-FAIL], [RUN-IF-PASS])
-#
-# This macro is equivalent to AT_CHECK, except that COMMAND should be a single
-# shell command that invokes a program whose code coverage is to be measured
-# (if configure was invoked with --coverage).  
-m4_define([OVS_CHECK_LCOV],
-    [AT_CHECK([_OVS_RUN_LCOV([-z])], [0], [ignore], [ignore])
-     AT_CHECK($@)
-     AT_CHECK([_OVS_RUN_LCOV([-c -o - >> $abs_builddir/coverage.info])], [0], [ignore], [ignore])])
diff --git a/tests/lcov-wrapper.in b/tests/lcov-wrapper.in
new file mode 100755 (executable)
index 0000000..1981cda
--- /dev/null
@@ -0,0 +1,58 @@
+#! /bin/sh
+
+abs_top_builddir='@abs_top_builddir@'
+wrap_program=`basename '@wrap_program@'`
+
+# Strip the first directory from $PATH that contains $wrap_program,
+# so that below we run the real $wrap_program, not ourselves.
+not_found=true
+new_path=
+first=true
+save_IFS=$IFS
+IFS=:
+for dir in $PATH; do
+    IFS=$save_IFS
+    if $not_found && test -x "$dir/$wrap_program"; then
+        not_found=false
+    else
+        if $first; then
+            first=false
+            new_path=$dir
+        else
+            new_path=$new_path:$dir
+        fi
+    fi
+done
+IFS=$save_IFS
+if $not_found; then
+    echo "$0: error: cannot find $wrap_program in \$PATH" >&2
+    exit 1
+fi
+PATH=$new_path
+export PATH
+
+# XXX Probably want some kind of synchronization here to deal with
+# programs running in parallel.
+LCOV="lcov -b $abs_top_builddir -d $abs_top_builddir -q"
+$LCOV -z
+
+# Run the subprocess and propagate signals to it.
+for signal in 1 2 3 5 15; do
+    trap "kill -$signal \$!     # Propagate signal
+          trap - $signal        # Reset signal to default
+          wait                  # Wait for child to die
+          kill -$signal $$      # Kill ourselves with same signal
+          exit 1                # Exit in case 'kill' failed" $signal
+done
+$wrap_program 0<&0 "$@" &       # 0<&0 prevents shell from closing stdin
+exec 0</dev/null                # Don't hold stdin open unnecessarily
+wait $!; rc=$?
+
+# Run lcov, but only if some .gcda files were produced, since lcov
+# complains otherwise.
+for file in `find "$abs_top_builddir" -name '*.gcda'`; do
+    $LCOV -c -o - >> "$abs_top_builddir/tests/coverage.info"
+    break
+done
+
+exit $rc
index a9a5bea..0e408f0 100644 (file)
@@ -2,40 +2,37 @@ AT_BANNER([library unit tests])
 
 AT_SETUP([test flow extractor])
 AT_CHECK([$PERL `which flowgen.pl` >/dev/null 3>flows 4>pcap])
-OVS_CHECK_LCOV([test-flows <flows 3<pcap], [0], [checked 247 packets, 0 errors
+AT_CHECK([test-flows <flows 3<pcap], [0], [checked 247 packets, 0 errors
 ])
 AT_CLEANUP
 
 AT_SETUP([test TCP/IP checksumming])
-OVS_CHECK_LCOV([test-csum], [0], [ignore])
+AT_CHECK([test-csum], [0], [ignore])
 AT_CLEANUP
 
 AT_SETUP([test flow classifier])
-OVS_CHECK_LCOV([test-classifier], [0], [ignore])
+AT_KEYWORDS([slow])
+AT_CHECK([test-classifier], [0], [ignore])
 AT_CLEANUP
 
 AT_SETUP([test hash functions])
-OVS_CHECK_LCOV([test-hash], [0], [ignore])
+AT_CHECK([test-hash], [0], [ignore])
 AT_CLEANUP
 
 AT_SETUP([test hash map])
-OVS_CHECK_LCOV([test-hmap], [0], [ignore])
+AT_CHECK([test-hmap], [0], [ignore])
 AT_CLEANUP
 
 AT_SETUP([test linked lists])
-OVS_CHECK_LCOV([test-list], [0], [ignore])
+AT_CHECK([test-list], [0], [ignore])
 AT_CLEANUP
 
 AT_SETUP([test SHA-1])
-OVS_CHECK_LCOV([test-sha1], [0], [ignore])
+AT_CHECK([test-sha1], [0], [ignore])
 AT_CLEANUP
 
 AT_SETUP([test type properties])
-OVS_CHECK_LCOV([test-type-props], [0], [ignore])
-AT_CLEANUP
-
-AT_SETUP([test vconn library])
-OVS_CHECK_LCOV([test-vconn], [0], [ignore])
+AT_CHECK([test-type-props], [0], [ignore])
 AT_CLEANUP
 
 AT_SETUP([test strtok_r bug fix])
diff --git a/tests/lockfile.at b/tests/lockfile.at
new file mode 100644 (file)
index 0000000..9cc95a8
--- /dev/null
@@ -0,0 +1,20 @@
+AT_BANNER([lockfile unit tests])
+
+m4_define([CHECK_LOCKFILE],
+  [AT_SETUP([m4_translit([$1], [_], [ ])])
+   AT_KEYWORDS([lockfile])
+   AT_CHECK([test-lockfile $1], [0], [$1: success (m4_if(
+     [$2], [1], [$2 child], [$2 children]))
+])
+   AT_CLEANUP])
+
+CHECK_LOCKFILE([lock_and_unlock], [0])
+CHECK_LOCKFILE([lock_and_unlock_twice], [0])
+CHECK_LOCKFILE([lock_blocks_same_process], [0])
+CHECK_LOCKFILE([lock_blocks_same_process_twice], [0])
+CHECK_LOCKFILE([lock_blocks_other_process], [1])
+CHECK_LOCKFILE([lock_twice_blocks_other_process], [1])
+CHECK_LOCKFILE([lock_and_unlock_allows_other_process], [1])
+CHECK_LOCKFILE([lock_timeout_gets_the_lock], [1])
+CHECK_LOCKFILE([lock_timeout_runs_out], [1])
+CHECK_LOCKFILE([lock_multiple], [0])
diff --git a/tests/openssl.supp b/tests/openssl.supp
new file mode 100644 (file)
index 0000000..eb461f8
--- /dev/null
@@ -0,0 +1,13 @@
+# suppress OpenSSL errors from valgrind
+
+{
+   BN_mod_inverse
+   Memcheck:Cond
+   fun:BN_mod_inverse
+}
+
+{
+   BN_div
+   Memcheck:Cond
+   fun:BN_div
+}
index 85812c4..0d35d17 100644 (file)
@@ -1,20 +1,32 @@
+dnl OVS_VSCTL_SETUP
+dnl
+dnl Creates an empty database in the current directory and then starts
+dnl an ovsdb-server on it for ovs-vsctl to connect to.
+m4_define([OVS_VSCTL_SETUP],
+  [OVSDB_INIT([db])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db >/dev/null 2>&1], [0], [ignore], [ignore])])
+
+dnl OVS_VSCTL_CLEANUP
+dnl
+dnl Kills off the database server.
+m4_define([OVS_VSCTL_CLEANUP], [OVSDB_SERVER_SHUTDOWN])
+
 dnl RUN_OVS_VSCTL(COMMAND, ...)
 dnl
-dnl Executes each ovs-vsctl COMMAND on a file named "conf" in the
-dnl current directory.  Creates "conf" if it does not already exist.
+dnl Executes each ovs-vsctl COMMAND.
 m4_define([RUN_OVS_VSCTL],
-  [: >> conf
-m4_foreach([command], [$@], [ovs-vsctl --no-reload --config=conf command
+  [m4_foreach([command], [$@], [ovs-vsctl --no-wait -vreconnect:ANY:emer --db=unix:socket -- command
+])])
+m4_define([RUN_OVS_VSCTL_ONELINE],
+  [m4_foreach([command], [$@], [ovs-vsctl --no-wait -vreconnect:ANY:emer --db=unix:socket --oneline -- command
 ])])
 
 dnl RUN_OVS_VSCTL_TOGETHER(COMMAND, ...)
 dnl
-dnl Executes each ovs-vsctl COMMAND on a file named "conf" in the
-dnl current directory, in a single run of ovs-vsctl.  Creates "conf" if it
-dnl does not already exist.
+dnl Executes each ovs-vsctl COMMAND in a single run of ovs-vsctl.
 m4_define([RUN_OVS_VSCTL_TOGETHER],
-  [: >> conf
-   ovs-vsctl --no-reload --config=conf m4_join([ -- ], $@)])
+  [ovs-vsctl --no-wait -vreconnect:ANY:emer --db=unix:socket --oneline dnl
+m4_foreach([command], [$@], [ -- command])])
 
 dnl CHECK_BRIDGES([BRIDGE, PARENT, VLAN], ...)
 dnl
@@ -23,43 +35,49 @@ dnl which must be in alphabetical order.  Also checks that each BRIDGE has the
 dnl specified PARENT and is on the given VLAN.
 m4_define([_CHECK_BRIDGE],
   [AT_CHECK([RUN_OVS_VSCTL([br-to-parent $1])], [0], [$2
-])
+], [], [OVS_VSCTL_CLEANUP])
 
    # Check br-to-vlan, without --oneline.
    AT_CHECK([RUN_OVS_VSCTL([br-to-vlan $1])], [0], [$3
-])
+], [], [OVS_VSCTL_CLEANUP])
    # Check br-to-vlan, with --oneline.
    # (This particular test is interesting with --oneline because it returns
    # an integer instead of a string and that can cause type mismatches inside
    # python if not done carefully.)
-   AT_CHECK([RUN_OVS_VSCTL([--oneline br-to-vlan $1])], [0], [$3
-])
+   AT_CHECK([RUN_OVS_VSCTL_ONELINE([br-to-vlan $1])], [0], [$3
+], [], [OVS_VSCTL_CLEANUP])
 
    # Check multiple queries in a single run.
    AT_CHECK([RUN_OVS_VSCTL_TOGETHER([br-to-parent $1], [br-to-vlan $1])], [0],
 [$2
 $3
-])])
+], [], [OVS_VSCTL_CLEANUP])])
 m4_define([CHECK_BRIDGES],
   [dnl Check that the bridges appear on list-br, without --oneline.
    AT_CHECK(
      [RUN_OVS_VSCTL([list-br])],
      [0],
      [m4_foreach([brinfo], [$@], [m4_car(brinfo)
-])])
+])],
+     [],
+     [OVS_VSCTL_CLEANUP])
 
    dnl Check that the bridges appear on list-br, with --oneline.
    AT_CHECK(
-     [RUN_OVS_VSCTL([--oneline list-br])],
+     [RUN_OVS_VSCTL_ONELINE([list-br])],
      [0],
      [m4_join([\n], m4_foreach([brinfo], [$@], [m4_car(brinfo),]))
-])
+],
+     [],
+     [OVS_VSCTL_CLEANUP])
 
    dnl Check that each bridge exists according to br-exists and that
    dnl a bridge that should not exist does not.
    m4_foreach([brinfo], [$@], 
-              [AT_CHECK([RUN_OVS_VSCTL([br-exists m4_car(brinfo)])])])
-   AT_CHECK([RUN_OVS_VSCTL([br-exists nonexistent])], [2])
+              [AT_CHECK([RUN_OVS_VSCTL([br-exists m4_car(brinfo)])], [0], [],
+                        [], [OVS_VSCTL_CLEANUP])])
+   AT_CHECK([RUN_OVS_VSCTL([br-exists nonexistent])], [2], [], [],
+            [OVS_VSCTL_CLEANUP])
 
    dnl Check that each bridge has the expected parent and VLAN.
    m4_map([_CHECK_BRIDGE], [$@])])
@@ -76,20 +94,26 @@ m4_define([CHECK_PORTS],
      [RUN_OVS_VSCTL([list-ports $1])],
      [0],
      [m4_foreach([port], m4_cdr($@), [port
-])])
+])],
+     [],
+     [OVS_VSCTL_CLEANUP])
 
    dnl Check ports with --oneline.
    AT_CHECK(
-     [RUN_OVS_VSCTL([--oneline list-ports $1])],
+     [RUN_OVS_VSCTL_ONELINE([list-ports $1])],
      [0],
      [m4_join([\n], m4_shift($@))
-])
-   AT_CHECK([RUN_OVS_VSCTL([port-to-br $1])], [1], [], [ovs-vsctl: no port named $1
-])
+],
+     [],
+     [OVS_VSCTL_CLEANUP])
+   AT_CHECK([RUN_OVS_VSCTL([port-to-br $1])], [1], [],
+            [ovs-vsctl: no port named $1
+],
+            [OVS_VSCTL_CLEANUP])
    m4_foreach(
      [port], m4_cdr($@), 
      [AT_CHECK([RUN_OVS_VSCTL([[port-to-br] port])], [0], [$1
-])])])
+], [], [OVS_VSCTL_CLEANUP])])])
 
 dnl CHECK_IFACES(BRIDGE, IFACE[, IFACE...])
 dnl
@@ -102,249 +126,501 @@ m4_define([CHECK_IFACES],
      [RUN_OVS_VSCTL([list-ifaces $1])],
      [0],
      [m4_foreach([iface], m4_cdr($@), [iface
-])])
-   AT_CHECK([RUN_OVS_VSCTL([iface-to-br $1])], [1], [], [ovs-vsctl: no interface named $1
-])
+])],
+     [],
+     [OVS_VSCTL_CLEANUP])
+   AT_CHECK([RUN_OVS_VSCTL([iface-to-br $1])], [1], [],
+            [ovs-vsctl: no interface named $1
+],
+            [OVS_VSCTL_CLEANUP])
    m4_foreach(
      [iface], m4_cdr($@), 
      [AT_CHECK([RUN_OVS_VSCTL([[iface-to-br] iface])], [0], [$1
-])])])
+],
+               [], [OVS_VSCTL_CLEANUP])])])
 
 dnl ----------------------------------------------------------------------
 AT_BANNER([ovs-vsctl unit tests -- real bridges])
 
 AT_SETUP([add-br a])
 AT_KEYWORDS([ovs-vsctl])
-AT_CHECK([RUN_OVS_VSCTL([add-br a])])
-AT_CHECK([cat conf], [0], [dnl
-bridge.a.port=a
-])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([add-br a])], [0], [], [], [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES([a, a, 0])
 CHECK_PORTS([a])
 CHECK_IFACES([a])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([add-br a, add-br a])
 AT_KEYWORDS([ovs-vsctl])
-AT_CHECK([RUN_OVS_VSCTL([add-br a])])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([add-br a])], [0], [], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([add-br a])], [1], [],
   [ovs-vsctl: cannot create a bridge named a because a bridge named a already exists
-])
+], [OVS_VSCTL_CLEANUP])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([add-br a, add-br b])
 AT_KEYWORDS([ovs-vsctl])
-AT_CHECK([RUN_OVS_VSCTL([add-br a], [add-br b])])
-AT_CHECK([cat conf], [0], [dnl
-bridge.a.port=a
-bridge.b.port=b
-])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([add-br a], [add-br b])], [0], [], [],
+         [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES([a, a, 0], [b, b, 0])
 CHECK_PORTS([a])
 CHECK_IFACES([a])
 CHECK_PORTS([b])
 CHECK_IFACES([b])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([add-br a, add-br b, del-br a])
 AT_KEYWORDS([ovs-vsctl])
-AT_CHECK([RUN_OVS_VSCTL([add-br a], [add-br b], [del-br a])])
-AT_CHECK([cat conf], [0], [dnl
-bridge.b.port=b
-])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([add-br a], [add-br b], [del-br a])], [0], [], [],
+         [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES([b, b, 0])
 CHECK_PORTS([b])
 CHECK_IFACES([b])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([add-br a, add-port a a1, add-port a a2])
 AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
 AT_CHECK([RUN_OVS_VSCTL(
-   [add-br a], 
+   [add-br a],
+   [--if-exists del-br b],
    [add-port a a1],
-   [add-port a a2])])
-AT_CHECK([cat conf], [0],
-  [bridge.a.port=a
-bridge.a.port=a1
-bridge.a.port=a2
-])
+   [add-port a a2])], [0], [], [], [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES([a, a, 0])
 CHECK_PORTS([a], [a1], [a2])
 CHECK_IFACES([a], [a1], [a2])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([add-br a, add-port a a1, add-port a a1])
 AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
 AT_CHECK([RUN_OVS_VSCTL(
    [add-br a], 
-   [add-port a a1])])
-AT_CHECK([cat conf], [0],
-  [bridge.a.port=a
-bridge.a.port=a1
-])
+   [add-port a a1])], [0], [], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([add-port a a1])], [1], [],
   [ovs-vsctl: cannot create a port named a1 because a port named a1 already exists on bridge a
-])
+], [OVS_VSCTL_CLEANUP])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([add-br a b, add-port a a1, add-port b b1, del-br a])
 AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
 AT_CHECK([RUN_OVS_VSCTL_TOGETHER(
    [add-br a], 
    [add-br b], 
    [add-port a a1],
    [add-port b b1],
-   [del-br a])])
-AT_CHECK([cat conf], [0],
-  [bridge.b.port=b
-bridge.b.port=b1
-])
+   [--if-exists del-port b b2],
+   [del-br a])], [0], [
+
+
+
+
+
+], [], [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES([b, b, 0])
 CHECK_PORTS([b], [b1])
 CHECK_IFACES([b], [b1])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([add-br a, add-bond a bond0 a1 a2 a3])
 AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
 AT_CHECK([RUN_OVS_VSCTL(
    [add-br a], 
-   [add-bond a bond0 a1 a2 a3])])
-AT_CHECK([cat conf], [0], [dnl
-bonding.bond0.slave=a1
-bonding.bond0.slave=a2
-bonding.bond0.slave=a3
-bridge.a.port=a
-bridge.a.port=bond0
-])
+   [add-bond a bond0 a1 a2 a3])], [0], [], [], [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES([a, a, 0])
 CHECK_PORTS([a], [bond0])
 CHECK_IFACES([a], [a1], [a2], [a3])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([add-br a b, add-port a a1, add-port b b1, del-port a a1])
 AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
 AT_CHECK([RUN_OVS_VSCTL(
   [add-br a], 
   [add-br b], 
   [add-port a a1],
   [add-port b b1],
-  [del-port a a1])])
-AT_CHECK([cat conf], [0], [dnl
-bridge.a.port=a
-bridge.b.port=b
-bridge.b.port=b1
-])
+  [del-port a a1])], [0], [], [], [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES([a, a, 0], [b, b, 0])
 CHECK_PORTS([a])
 CHECK_IFACES([a])
 CHECK_PORTS([b], [b1])
 CHECK_IFACES([b], [b1])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([add-br a, add-bond a bond0 a1 a2 a3, del-port bond0])
 AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
 AT_CHECK([RUN_OVS_VSCTL_TOGETHER(
   [add-br a], 
   [add-bond a bond0 a1 a2 a3],
-  [del-port bond0])])
-AT_CHECK([cat conf], [0], [dnl
-bridge.a.port=a
-])
+  [del-port bond0])], [0], [
+
+
+], [], [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES([a, a, 0])
 CHECK_PORTS([a])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([external IDs])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL_TOGETHER(
+  [add-br a], 
+  [add-port a a1],
+  [add-bond a bond0 a2 a3],
+  [br-set-external-id a key0 value0],
+  [set port a1 external-ids:key1=value1],
+  [set interface a2 external-ids:key2=value2],
+  [set interface a2 external-ids:key3=value3],
+  [set interface a3 external-ids:key4=value4],
+  [br-get-external-id a],
+  [br-get-external-id a key0],
+  [br-get-external-id a key1],
+  [br-set-external-id a key0 othervalue],
+  [br-get-external-id a],
+  [br-set-external-id a key0],
+  [br-get-external-id a],
+  [get port a1 external-ids],
+  [get interface a2 external-ids],
+  [get interface a3 external-ids])], [0], [
+
+
+
+
+
+
+
+key0=value0
+value0
+
+
+key0=othervalue
+
+
+{"key1"="value1"}
+{"key2"="value2", "key3"="value3"}
+{"key4"="value4"}
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL_TOGETHER(
+  [br-get-external-id a],
+  [get port a1 external-ids],
+  [get interface a2 external-ids],
+  [get interface a3 external-ids])], [0],
+[
+{"key1"="value1"}
+{"key2"="value2", "key3"="value3"}
+{"key4"="value4"}
+], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([a, a, 0])
+CHECK_PORTS([a], [a1], [bond0])
+CHECK_IFACES([a], [a1], [a2], [a3])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 dnl ----------------------------------------------------------------------
 AT_BANNER([ovs-vsctl unit tests -- fake bridges])
 
-m4_define([SIMPLE_FAKE_CONF], [dnl
-bridge.xenbr0.port=eth0
-bridge.xenbr0.port=eth0.9
-bridge.xenbr0.port=xapi1
-bridge.xenbr0.port=xenbr0
-iface.xapi1.fake-bridge=true
-iface.xapi1.internal=true
-vlan.eth0.9.tag=9
-vlan.xapi1.tag=9
-])
+m4_define([OVS_VSCTL_SETUP_SIMPLE_FAKE_CONF],
+  [AT_CHECK(
+     [RUN_OVS_VSCTL(
+        [add-br xenbr0],
+        [add-port xenbr0 eth0],
+        [add-br xapi1 xenbr0 9],
+        [add-port xapi1 eth0.9])],
+     [0], [], [], [OVS_VSCTL_CLEANUP])])
 
 AT_SETUP([simple fake bridge])
 AT_KEYWORDS([ovs-vsctl fake-bridge])
-AT_CHECK([RUN_OVS_VSCTL(
-  [add-br xenbr0],
-  [add-port xenbr0 eth0],
-  [add-br xapi1 xenbr0 9],
-  [add-port xapi1 eth0.9])])
-AT_CHECK([cat conf], [0], [SIMPLE_FAKE_CONF])
-CHECK_BRIDGES([xenbr0, xenbr0, 0], [xapi1, xenbr0, 9])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_SIMPLE_FAKE_CONF
+CHECK_BRIDGES([xapi1, xenbr0, 9], [xenbr0, xenbr0, 0])
 CHECK_PORTS([xenbr0], [eth0])
 CHECK_IFACES([xenbr0], [eth0])
 CHECK_PORTS([xapi1], [eth0.9])
 CHECK_IFACES([xapi1], [eth0.9])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([simple fake bridge + del-br fake bridge])
 AT_KEYWORDS([ovs-vsctl fake-bridge])
-AT_DATA([conf], [SIMPLE_FAKE_CONF])
-AT_CHECK([RUN_OVS_VSCTL([del-br xapi1])])
-AT_CHECK([cat conf], [0], [dnl
-bridge.xenbr0.port=eth0
-bridge.xenbr0.port=xenbr0
-])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_SIMPLE_FAKE_CONF
+AT_CHECK([RUN_OVS_VSCTL([del-br xapi1])], [0], [], [], [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES([xenbr0, xenbr0, 0])
 CHECK_PORTS([xenbr0], [eth0])
 CHECK_IFACES([xenbr0], [eth0])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([simple fake bridge + del-br real bridge])
 AT_KEYWORDS([ovs-vsctl fake-bridge])
-AT_DATA([conf], [SIMPLE_FAKE_CONF])
-AT_CHECK([RUN_OVS_VSCTL([del-br xenbr0])])
-AT_CHECK([cat conf], [0], [])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_SIMPLE_FAKE_CONF
+AT_CHECK([RUN_OVS_VSCTL([del-br xenbr0])], [0], [], [], [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
-m4_define([BOND_FAKE_CONF], [dnl
-bonding.bond0.slave=eth0
-bonding.bond0.slave=eth1
-bridge.xapi1.port=bond0
-bridge.xapi1.port=bond0.11
-bridge.xapi1.port=xapi1
-bridge.xapi1.port=xapi2
-iface.xapi2.fake-bridge=true
-iface.xapi2.internal=true
-vlan.bond0.11.tag=11
-vlan.xapi2.tag=11
-])
+AT_SETUP([simple fake bridge + external IDs])
+AT_KEYWORDS([ovs-vsctl fake-bridge])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_SIMPLE_FAKE_CONF
+AT_CHECK([RUN_OVS_VSCTL_TOGETHER(
+  [br-set-external-id xenbr0 key0 value0],
+  [br-set-external-id xapi1 key1 value1],
+  [br-get-external-id xenbr0],
+  [br-get-external-id xenbr0 key0],
+  [br-get-external-id xapi1],
+  [br-get-external-id xapi1 key1])], [0], [
+
+key0=value0
+value0
+key1=value1
+value1
+], [], [OVS_VSCTL_CLEANUP])
+CHECK_BRIDGES([xapi1, xenbr0, 9], [xenbr0, xenbr0, 0])
+CHECK_PORTS([xenbr0], [eth0])
+CHECK_IFACES([xenbr0], [eth0])
+CHECK_PORTS([xapi1], [eth0.9])
+CHECK_IFACES([xapi1], [eth0.9])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+m4_define([OVS_VSCTL_SETUP_BOND_FAKE_CONF],
+  [AT_CHECK(
+     [RUN_OVS_VSCTL(
+        [add-br xapi1],
+        [add-bond xapi1 bond0 eth0 eth1],
+        [add-br xapi2 xapi1 11],
+        [add-port xapi2 bond0.11])],
+     [0], [], [], [OVS_VSCTL_CLEANUP])])
 
 AT_SETUP([fake bridge on bond])
 AT_KEYWORDS([ovs-vsctl fake-bridge])
-AT_CHECK([RUN_OVS_VSCTL(
-  [add-br xapi1],
-  [add-bond xapi1 bond0 eth0 eth1],
-  [add-br xapi2 xapi1 11],
-  [add-port xapi2 bond0.11])])
-AT_CHECK([cat conf], [0], [BOND_FAKE_CONF])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_BOND_FAKE_CONF
 CHECK_BRIDGES([xapi1, xapi1, 0], [xapi2, xapi1, 11])
 CHECK_PORTS([xapi1], [bond0])
 CHECK_IFACES([xapi1], [eth0], [eth1])
 CHECK_PORTS([xapi2], [bond0.11])
 CHECK_IFACES([xapi2], [bond0.11])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([fake bridge on bond + del-br fake bridge])
 AT_KEYWORDS([ovs-vsctl fake-bridge])
-AT_DATA([conf], [BOND_FAKE_CONF])
-AT_CHECK([RUN_OVS_VSCTL([--oneline del-br xapi2])], [0], [
-])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_BOND_FAKE_CONF
+AT_CHECK([RUN_OVS_VSCTL_ONELINE([del-br xapi2])], [0], [
+], [], [OVS_VSCTL_CLEANUP])
 CHECK_BRIDGES([xapi1, xapi1, 0])
 CHECK_PORTS([xapi1], [bond0])
 CHECK_IFACES([xapi1], [eth0], [eth1])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
 
 AT_SETUP([fake bridge on bond + del-br real bridge])
 AT_KEYWORDS([ovs-vsctl fake-bridge])
-AT_DATA([conf], [BOND_FAKE_CONF])
+OVS_VSCTL_SETUP
+OVS_VSCTL_SETUP_BOND_FAKE_CONF
 AT_CHECK([RUN_OVS_VSCTL([del-br xapi1])])
 CHECK_BRIDGES
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+dnl ----------------------------------------------------------------------
+AT_BANNER([ovs-vsctl unit tests -- database commands])
+
+AT_SETUP([database commands -- positive checks])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([--force create b name=br0])], 
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+cp stdout out1
+AT_CHECK([RUN_OVS_VSCTL([list b])], 
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+cp stdout out2
+AT_CHECK([perl $srcdir/uuidfilt.pl out1 out2], [0], 
+  [[<0>
+_uuid                (RO): <0>
+controller           (RO): []
+datapath_id          (RO): []
+datapath_type        (RO): ""
+external_ids         (RW): {}
+flood_vlans          (RW): []
+mirrors              (RO): []
+name                 (RO): "br0"
+netflow              (RO): []
+other_config         (RW): {}
+ports                (RO): []
+]], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK(
+  [RUN_OVS_VSCTL(
+    [set bridge br0 \
+      'other_config:datapath_id="0123456789ab"' \
+      'other_config:hwaddr="00:11:22:33:44:55"' \
+      'external-ids={"xs-network-uuids"="9c45f225-a7cf-439d-976d-83db6271fda1"}' -- \
+     add bridge br0 external_ids '"xs-network-names"="local; remote; cloud"'])], 
+  [0], [], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL_ONELINE([get bridge br0 other_config external-ids])], 
+  [0], [{datapath_id="0123456789ab", hwaddr="00:11:22:33:44:55"}\n{xs-network-names="local; remote; cloud", xs-network-uuids="9c45f225-a7cf-439d-976d-83db6271fda1"}
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get bridge br0 other_config:hwaddr -- --if-exists get bridge br0 other-config:nonexistent])], 
+  [0], ["00:11:22:33:44:55"
+
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([remove br br0 other_config hwaddr 'datapath_id=""' -- get br br0 other_config])], 
+  [0], [{datapath_id="0123456789ab"}
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([remove br br0 other_config 'datapath_id="0123456789ab"' -- get br br0 other_config])], 
+  [0], [{}
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([clear br br0 external-ids -- get br br0 external_ids])], 
+  [0], [{}
+], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--force destroy b br0])], 
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([list b])], 
+  [0], [], [], [OVS_VSCTL_CLEANUP])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+AT_SETUP([database commands -- negative checks])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([--force create b name=br0])], 
+  [0], [ignore], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([add-br br1])], 
+  [0], [ignore], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set-controller br1 tcp:127.0.0.1])], 
+  [0], [ignore], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--force create n targets='"1.2.3.4:567"'])], 
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+cp stdout netflow-uuid
+AT_CHECK([RUN_OVS_VSCTL([list n `cat netflow-uuid`])],
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([perl $srcdir/uuidfilt.pl netflow-uuid stdout], [0], 
+  [[<0>
+_uuid                (RO): <0>
+active_timeout       (RW): 0
+add_id_to_interface  (RW): false
+engine_id            (RW): []
+engine_type          (RW): []
+targets              (RW): ["1.2.3.4:567"]
+]], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK([RUN_OVS_VSCTL([list interx x])], 
+  [1], [], [ovs-vsctl: unknown table "interx"
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([list b x])], 
+  [1], [], [ovs-vsctl: no row "x" in table Bridge
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([list b br])], 
+  [1], [], [ovs-vsctl: multiple rows in Bridge match "br"
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 d])], 
+  [1], [], [ovs-vsctl: Bridge contains more than one column whose name matches "d"
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 x])], 
+  [1], [], [ovs-vsctl: Bridge does not contain a column whose name matches "x"
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 :y=z])], 
+  [1], [], [ovs-vsctl: :y=z: missing column name
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 datapath_id:y=z])], 
+  [1], [], [ovs-vsctl: datapath_id:y=z: value not accepted here
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 datapath_id::])], 
+  [1], [], [ovs-vsctl: datapath_id::: trailing garbage ":" in argument
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 datapath_id:x])], 
+  [1], [], [ovs-vsctl: cannot specify key to get for non-map column datapath_id
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([get b br0 external_ids:x])], 
+  [1], [], [ovs-vsctl: no key "x" in Bridge record "br0" column external_ids
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=-1])], 
+  [1], [], [ovs-vsctl: -1 is outside the valid range 1 to 4095 (inclusive)
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set b br0 flood_vlans=4096])], 
+  [1], [], [ovs-vsctl: 4096 is outside the valid range 1 to 4095 (inclusive)
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set b br0 datapath_id=4096])], 
+  [1], [], [ovs-vsctl: datapath_id=4096: cannot modify read-only column datapath_id in table Bridge
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set c br1 'connection-mode=xyz'])], 
+  [1], [], [ovs-vsctl: xyz is not valid (it does not match ^in-band|out-of-band$)
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([set c br1 connection-mode:x=y])], 
+  [1], [], [ovs-vsctl: cannot specify key to set for non-map column connection_mode
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([add b br1 datapath_id x=y])], 
+  [1], [], [ovs-vsctl: cannot modify read-only column datapath_id in table Bridge
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--force add b br1 datapath_id x y])], 
+  [1], [], [ovs-vsctl: "add" operation would put 2 values in column datapath_id of table Bridge but the maximum number is 1
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([remove b br1 datapath_id x=y])], 
+  [1], [], [ovs-vsctl: cannot modify read-only column datapath_id in table Bridge
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([remove n `cat netflow-uuid` targets '"1.2.3.4:567"'])], 
+  [1], [], [ovs-vsctl: "remove" operation would put 0 values in column targets of table NetFlow but the minimun number is 1
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([clear bri br0 netflow])], 
+  [1], [], [ovs-vsctl: cannot modify read-only column netflow in table Bridge
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([clear n `cat netflow-uuid` targets])], 
+  [1], [], [ovs-vsctl: "clear" operation cannot be applied to column targets of table NetFlow, which is not allowed to be empty
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([create b name=br2])], 
+  [1], [], [ovs-vsctl: "create" requires --force
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([destroy b br0])], 
+  [1], [], [ovs-vsctl: "destroy" requires --force
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([RUN_OVS_VSCTL([--force destroy b br2])], 
+  [1], [], [ovs-vsctl: no row "br2" in table Bridge
+], [OVS_VSCTL_CLEANUP])
+OVS_VSCTL_CLEANUP
+AT_CLEANUP
+
+dnl This test really shows a bug -- "create" followed by "list" in
+dnl the same execution shows the wrong UUID on the "list" command.
+dnl The bug is documented in ovs-vsctl.8.
+AT_SETUP([created row UUID is wrong in same execution])
+AT_KEYWORDS([ovs-vsctl])
+OVS_VSCTL_SETUP
+AT_CHECK([RUN_OVS_VSCTL([--force create Bridge name=br0 -- list b])], 
+  [0], [stdout], [], [OVS_VSCTL_CLEANUP])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], 
+  [[<0>
+_uuid                (RO): <1>
+controller           (RO): []
+datapath_id          (RO): []
+datapath_type        (RO): ""
+external_ids         (RW): {}
+flood_vlans          (RW): []
+mirrors              (RO): []
+name                 (RO): "br0"
+netflow              (RO): []
+other_config         (RW): {}
+ports                (RO): []
+]], [ignore], [test ! -e pid || kill `cat pid`])
+OVS_VSCTL_CLEANUP
 AT_CLEANUP
diff --git a/tests/ovsdb-column.at b/tests/ovsdb-column.at
new file mode 100644 (file)
index 0000000..03cd8dc
--- /dev/null
@@ -0,0 +1,18 @@
+AT_BANNER([OVSDB -- columns])
+
+OVSDB_CHECK_POSITIVE([ordinary column],
+  [[parse-column mycol '{"type": "integer"}']],
+  [[{"type":"integer"}]])
+
+OVSDB_CHECK_POSITIVE([immutable column],
+  [[parse-column mycol '{"type": "real", "mutable": false}']],
+  [[{"mutable":false,"type":"real"}]])
+
+OVSDB_CHECK_POSITIVE([ephemeral column],
+  [[parse-column mycol '{"type": "uuid", "ephemeral": true}']],
+  [[{"ephemeral":true,"type":"uuid"}]])
+
+OVSDB_CHECK_POSITIVE([column with comment],
+  [[parse-column mycol '{"type": "boolean",
+                         "comment": "extra information about this column"}']],
+  [[{"comment":"extra information about this column","type":"boolean"}]])
diff --git a/tests/ovsdb-condition.at b/tests/ovsdb-condition.at
new file mode 100644 (file)
index 0000000..a94f1dc
--- /dev/null
@@ -0,0 +1,558 @@
+AT_BANNER([OVSDB -- conditions])
+
+OVSDB_CHECK_POSITIVE([null condition],
+  [[parse-conditions \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '[]']],
+  [[[]]])
+
+OVSDB_CHECK_POSITIVE([conditions on scalars],
+  [[parse-conditions \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["i", "==", 0]]' \
+    '[["i", "!=", 1]]' \
+    '[["i", "<", 2]]' \
+    '[["i", "<=", 3]]' \
+    '[["i", ">", 4]]' \
+    '[["i", ">=", 5]]' \
+    '[["i", "includes", 6]]' \
+    '[["i", "excludes", 7]]' \
+    '[["r", "==", 0.5]]' \
+    '[["r", "!=", 1.5]]' \
+    '[["r", "<", 2.5]]' \
+    '[["r", "<=", 3.5]]' \
+    '[["r", ">", 4.5]]' \
+    '[["r", ">=", 5.5]]' \
+    '[["r", "includes", 6.5]]' \
+    '[["r", "excludes", 7.5]]' \
+    '[["b", "==", true]]' \
+    '[["b", "!=", false]]' \
+    '[["b", "includes", false]]' \
+    '[["b", "excludes", true]]' \
+    '[["s", "==", "a"]]' \
+    '[["s", "!=", "b"]]' \
+    '[["s", "includes", "c"]]' \
+    '[["s", "excludes", "d"]]' \
+    '[["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \
+    '[["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "includes", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \
+    '[["u", "excludes", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']],
+  [[[["i","==",0]]
+[["i","!=",1]]
+[["i","<",2]]
+[["i","<=",3]]
+[["i",">",4]]
+[["i",">=",5]]
+[["i","includes",6]]
+[["i","excludes",7]]
+[["r","==",0.5]]
+[["r","!=",1.5]]
+[["r","<",2.5]]
+[["r","<=",3.5]]
+[["r",">",4.5]]
+[["r",">=",5.5]]
+[["r","includes",6.5]]
+[["r","excludes",7.5]]
+[["b","==",true]]
+[["b","!=",false]]
+[["b","includes",false]]
+[["b","excludes",true]]
+[["s","==","a"]]
+[["s","!=","b"]]
+[["s","includes","c"]]
+[["s","excludes","d"]]
+[["u","==",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]
+[["u","!=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]]
+[["u","includes",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]
+[["u","excludes",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]]]],
+  [condition])
+
+AT_SETUP([disallowed conditions on scalars])
+AT_KEYWORDS([ovsdb negative condition])
+AT_CHECK([[test-ovsdb parse-conditions \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["b", ">", true]]' \
+    '[["b", ">=", false]]' \
+    '[["b", "<", false]]' \
+    '[["b", "<=", false]]' \
+    '[["s", ">", "a"]]' \
+    '[["s", ">=", "b"]]' \
+    '[["s", "<", "c"]]' \
+    '[["s", "<=", "d"]]' \
+    '[["u", ">", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]' \
+    '[["u", ">=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "<", ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]' \
+    '[["u", "<=", ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]']],
+  [1], [],
+  [[test-ovsdb: syntax "["b",">",true]": syntax error: Type mismatch: ">" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b",">=",false]": syntax error: Type mismatch: ">=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","<",false]": syntax error: Type mismatch: "<" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","<=",false]": syntax error: Type mismatch: "<=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["s",">","a"]": syntax error: Type mismatch: ">" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s",">=","b"]": syntax error: Type mismatch: ">=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","<","c"]": syntax error: Type mismatch: "<" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","<=","d"]": syntax error: Type mismatch: "<=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["u",">",["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]": syntax error: Type mismatch: ">" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u",">=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: ">=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","<",["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]": syntax error: Type mismatch: "<" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","<=",["uuid","62315898-64e0-40b9-b26f-ff74225303e6"]]": syntax error: Type mismatch: "<=" operator may not be applied to column u of type uuid.
+]])
+AT_CLEANUP
+
+OVSDB_CHECK_POSITIVE([conditions on sets],
+  [[parse-conditions \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '[["i", "==", ["set", []]]]' \
+    '[["i", "!=", ["set", [1]]]]' \
+    '[["i", "includes", ["set", [1, 2]]]]' \
+    '[["i", "excludes", ["set", [1, 2, 3]]]]' \
+    '[["r", "==", ["set", []]]]' \
+    '[["r", "!=", ["set", [1.5]]]]' \
+    '[["r", "includes", ["set", [1.5, 2.5]]]]' \
+    '[["r", "excludes", ["set", [1.5, 2.5, 3.5]]]]' \
+    '[["b", "==", ["set", [true]]]]' \
+    '[["b", "!=", ["set", [false]]]]' \
+    '[["b", "includes", ["set", [false]]]]' \
+    '[["b", "excludes", ["set", [true, false]]]]' \
+    '[["s", "==", ["set", ["a"]]]]' \
+    '[["s", "!=", ["set", ["a", "b"]]]]' \
+    '[["s", "includes", ["set", ["c"]]]]' \
+    '[["s", "excludes", ["set", ["c", "d"]]]]' \
+    '[["u", "==", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]' \
+    '[["u", "==", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]]]' \
+    '[["u", "includes", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"],
+                ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]]' \
+    '[["u", "excludes", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"],
+                ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],
+                ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]]]]]' \
+]],
+  [[[["i","==",["set",[]]]]
+[["i","!=",["set",[1]]]]
+[["i","includes",["set",[1,2]]]]
+[["i","excludes",["set",[1,2,3]]]]
+[["r","==",["set",[]]]]
+[["r","!=",["set",[1.5]]]]
+[["r","includes",["set",[1.5,2.5]]]]
+[["r","excludes",["set",[1.5,2.5,3.5]]]]
+[["b","==",["set",[true]]]]
+[["b","!=",["set",[false]]]]
+[["b","includes",["set",[false]]]]
+[["b","excludes",["set",[false,true]]]]
+[["s","==",["set",["a"]]]]
+[["s","!=",["set",["a","b"]]]]
+[["s","includes",["set",["c"]]]]
+[["s","excludes",["set",["c","d"]]]]
+[["u","==",["set",[["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","==",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","includes",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","excludes",["set",[["uuid","62315898-64e0-40b9-b26f-ff74225303e6"],["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]]],
+  [condition])
+
+OVSDB_CHECK_POSITIVE([condition sorting],
+  [[parse-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[["i", "excludes", 7],
+      ["i", "!=", 8],
+      ["i", "==", 1],
+      ["i", "includes", 2],
+      ["i", "<=", 3],
+      ["i", "<", 4],
+      ["i", ">", 6],
+      ["i", ">=", 5],
+      ["_uuid", "==", ["uuid", "d50e85c6-8ae7-4b16-b69e-4395928bd9be"]]]']],
+  [[[["_uuid","==",["uuid","d50e85c6-8ae7-4b16-b69e-4395928bd9be"]],["i","==",1],["i","includes",2],["i","<=",3],["i","<",4],["i",">=",5],["i",">",6],["i","excludes",7],["i","!=",8]]]])
+
+OVSDB_CHECK_POSITIVE([evaluating null condition],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [condition  0: TTT])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on integers],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[["i", "<", 1]],
+      [["i", "<=", 1]],
+      [["i", "==", 1]],
+      [["i", "!=", 1]],
+      [["i", ">=", 1]],
+      [["i", ">", 1]],
+      [["i", "includes", 1]],
+      [["i", "excludes", 1]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [condition  0: T--
+condition  1: TT-
+condition  2: -T-
+condition  3: T-T
+condition  4: -TT
+condition  5: --T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on reals],
+  [[evaluate-conditions \
+    '{"columns": {"r": {"type": "real"}}}' \
+    '[[["r", "<", 5.0]],
+      [["r", "<=", 5.0]],
+      [["r", "==", 5.0]],
+      [["r", "!=", 5.0]],
+      [["r", ">=", 5.0]],
+      [["r", ">", 5.0]],
+      [["r", "includes", 5.0]],
+      [["r", "excludes", 5.0]]]' \
+    '[{"r": 0},
+      {"r": 5.0},
+      {"r": 5.1}']]],
+  [condition  0: T--
+condition  1: TT-
+condition  2: -T-
+condition  3: T-T
+condition  4: -TT
+condition  5: --T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on booleans],
+  [[evaluate-conditions \
+    '{"columns": {"b": {"type": "boolean"}}}' \
+    '[[["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", true]],
+      [["b", "excludes", true]],
+      [["b", "==", false]],
+      [["b", "!=", false]],
+      [["b", "includes", false]],
+      [["b", "excludes", false]]]' \
+    '[{"b": true},
+      {"b": false}']]],
+  [condition  0: T-
+condition  1: -T
+condition  2: T-
+condition  3: -T
+condition  4: -T
+condition  5: T-
+condition  6: -T
+condition  7: T-], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on strings],
+  [[evaluate-conditions \
+    '{"columns": {"s": {"type": "string"}}}' \
+    '[[["s", "==", ""]],
+      [["s", "!=", ""]],
+      [["s", "includes", ""]],
+      [["s", "excludes", ""]],
+      [["s", "==", "foo"]],
+      [["s", "!=", "foo"]],
+      [["s", "includes", "foo"]],
+      [["s", "excludes", "foo"]]]' \
+    '[{"s": ""},
+      {"s": "foo"},
+      {"s": "xxx"}']]],
+  [condition  0: T--
+condition  1: -TT
+condition  2: T--
+condition  3: -TT
+condition  4: -T-
+condition  5: T-T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on UUIDs],
+  [[evaluate-conditions \
+    '{"columns": {"u": {"type": "uuid"}}}' \
+    '[[["u", "==", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "!=", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "includes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "excludes", ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]]],
+      [["u", "==", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "!=", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "includes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]],
+      [["u", "excludes", ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]]]]' \
+    '[{"u": ["uuid", "8a1dbdb8-416f-4ce9-affa-3332691714b6"]},
+      {"u": ["uuid", "06151f9d-62d6-4f59-8504-e9765107faa9"]},
+      {"u": ["uuid", "00000000-0000-0000-0000-000000000000"]}']]],
+  [condition  0: T--
+condition  1: -TT
+condition  2: T--
+condition  3: -TT
+condition  4: -T-
+condition  5: T-T
+condition  6: -T-
+condition  7: T-T], [condition])
+
+OVSDB_CHECK_POSITIVE([evaluating conditions on sets],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[[["i", "==", ["set", []]]],
+      [["i", "==", ["set", [0]]]],
+      [["i", "==", ["set", [1]]]],
+      [["i", "==", ["set", [0, 1]]]],
+      [["i", "==", ["set", [2]]]],
+      [["i", "==", ["set", [2, 0]]]],
+      [["i", "==", ["set", [2, 1]]]],
+      [["i", "==", ["set", [2, 1, 0]]]],
+      [["i", "!=", ["set", []]]],
+      [["i", "!=", ["set", [0]]]],
+      [["i", "!=", ["set", [1]]]],
+      [["i", "!=", ["set", [0, 1]]]],
+      [["i", "!=", ["set", [2]]]],
+      [["i", "!=", ["set", [2, 0]]]],
+      [["i", "!=", ["set", [2, 1]]]],
+      [["i", "!=", ["set", [2, 1, 0]]]],
+      [["i", "includes", ["set", []]]],
+      [["i", "includes", ["set", [0]]]],
+      [["i", "includes", ["set", [1]]]],
+      [["i", "includes", ["set", [0, 1]]]],
+      [["i", "includes", ["set", [2]]]],
+      [["i", "includes", ["set", [2, 0]]]],
+      [["i", "includes", ["set", [2, 1]]]],
+      [["i", "includes", ["set", [2, 1, 0]]]],
+      [["i", "excludes", ["set", []]]],
+      [["i", "excludes", ["set", [0]]]],
+      [["i", "excludes", ["set", [1]]]],
+      [["i", "excludes", ["set", [0, 1]]]],
+      [["i", "excludes", ["set", [2]]]],
+      [["i", "excludes", ["set", [2, 0]]]],
+      [["i", "excludes", ["set", [2, 1]]]],
+      [["i", "excludes", ["set", [2, 1, 0]]]]]' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [1]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [2]]},
+      {"i": ["set", [2, 0]]},
+      {"i": ["set", [2, 1]]},
+      {"i": ["set", [2, 1, 0]]}]']],
+  [dnl
+condition  0: T---- ---
+condition  1: -T--- ---
+condition  2: --T-- ---
+condition  3: ---T- ---
+condition  4: ----T ---
+condition  5: ----- T--
+condition  6: ----- -T-
+condition  7: ----- --T
+condition  8: -TTTT TTT
+condition  9: T-TTT TTT
+condition 10: TT-TT TTT
+condition 11: TTT-T TTT
+condition 12: TTTT- TTT
+condition 13: TTTTT -TT
+condition 14: TTTTT T-T
+condition 15: TTTTT TT-
+condition 16: TTTTT TTT
+condition 17: -T-T- T-T
+condition 18: --TT- -TT
+condition 19: ---T- --T
+condition 20: ----T TTT
+condition 21: ----- T-T
+condition 22: ----- -TT
+condition 23: ----- --T
+condition 24: TTTTT TTT
+condition 25: T-T-T -T-
+condition 26: TT--T T--
+condition 27: T---T ---
+condition 28: TTTT- ---
+condition 29: T-T-- ---
+condition 30: TT--- ---
+condition 31: T---- ---], [condition])
+
+# This is the same as the "set" test except that it adds values,
+# all of which always match.
+OVSDB_CHECK_POSITIVE([evaluating conditions on maps (1)],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[[["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]}]']],
+  [dnl
+condition  0: T---- ---
+condition  1: -T--- ---
+condition  2: --T-- ---
+condition  3: ---T- ---
+condition  4: ----T ---
+condition  5: ----- T--
+condition  6: ----- -T-
+condition  7: ----- --T
+condition  8: -TTTT TTT
+condition  9: T-TTT TTT
+condition 10: TT-TT TTT
+condition 11: TTT-T TTT
+condition 12: TTTT- TTT
+condition 13: TTTTT -TT
+condition 14: TTTTT T-T
+condition 15: TTTTT TT-
+condition 16: TTTTT TTT
+condition 17: -T-T- T-T
+condition 18: --TT- -TT
+condition 19: ---T- --T
+condition 20: ----T TTT
+condition 21: ----- T-T
+condition 22: ----- -TT
+condition 23: ----- --T
+condition 24: TTTTT TTT
+condition 25: T-T-T -T-
+condition 26: TT--T T--
+condition 27: T---T ---
+condition 28: TTTT- ---
+condition 29: T-T-- ---
+condition 30: TT--- ---
+condition 31: T---- ---], [condition])
+
+# This is the same as the "set" test except that it adds values,
+# and those values don't always match.
+OVSDB_CHECK_POSITIVE([evaluating conditions on maps (2)],
+  [[evaluate-conditions \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[[["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[0, false]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[1, true]]]},
+
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[0, true], [1, true]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, false]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+
+      {"i": ["map", [[2, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, false]]]}]']],
+  [dnl
+condition  0: T---- ----- -----
+condition  1: -T--- ----- -----
+condition  2: ---T- ----- -----
+condition  3: ----- T---- -----
+condition  4: ----- --T-- -----
+condition  5: ----- ----T -----
+condition  6: ----- ----- -T---
+condition  7: ----- ----- ---T-
+condition  8: -TTTT TTTTT TTTTT
+condition  9: T-TTT TTTTT TTTTT
+condition 10: TTT-T TTTTT TTTTT
+condition 11: TTTTT -TTTT TTTTT
+condition 12: TTTTT TT-TT TTTTT
+condition 13: TTTTT TTTT- TTTTT
+condition 14: TTTTT TTTTT T-TTT
+condition 15: TTTTT TTTTT TTT-T
+condition 16: TTTTT TTTTT TTTTT
+condition 17: -T--- TT--T T--T-
+condition 18: ---T- T---- -T-TT
+condition 19: ----- T---- ---T-
+condition 20: ----- --T-T -TTTT
+condition 21: ----- ----T ---T-
+condition 22: ----- ----- -T-TT
+condition 23: ----- ----- ---T-
+condition 24: TTTTT TTTTT TTTTT
+condition 25: T-TTT --TT- -TT-T
+condition 26: TTT-T -TTTT T-T--
+condition 27: T-T-T --TT- --T--
+condition 28: TTTTT TT-T- T----
+condition 29: T-TTT ---T- -----
+condition 30: TTT-T -T-T- T----
+condition 31: T-T-T ---T- -----], [condition])
diff --git a/tests/ovsdb-data.at b/tests/ovsdb-data.at
new file mode 100644 (file)
index 0000000..e868477
--- /dev/null
@@ -0,0 +1,473 @@
+AT_BANNER([OVSDB -- atoms])
+
+OVSDB_CHECK_POSITIVE([integer atom from JSON], 
+  [[parse-atoms '["integer"]' \
+    '[0]' \
+    '[-1]' \
+    '[1e3]' \
+    '[9223372036854775807]' \
+    '[-9223372036854775808]' ]], 
+  [0
+-1
+1000
+9223372036854775807
+-9223372036854775808])
+
+OVSDB_CHECK_POSITIVE([integer atom from string], 
+  [[parse-atom-strings -- '["integer"]' \
+    '0' \
+    '-1' \
+    '+1000' \
+    '9223372036854775807' \
+    '-9223372036854775808' ]], 
+  [0
+-1
+1000
+9223372036854775807
+-9223372036854775808])
+
+OVSDB_CHECK_POSITIVE([real atom from JSON], 
+  [[parse-atoms '["real"]' \
+    '[0]' \
+    '[0.0]' \
+    '[-0.0]' \
+    '[-1.25]' \
+    '[1e3]' \
+    '[1e37]' \
+    '[0.00390625]' ]], 
+  [0
+0
+0
+-1.25
+1000
+1e+37
+0.00390625])
+
+OVSDB_CHECK_POSITIVE([real atom from string], 
+  [[parse-atom-strings -- '["real"]' \
+    '0' \
+    '0.0' \
+    '-0.0' \
+    '-1.25' \
+    '1e3' \
+    '1e37' \
+    '0.00390625' ]], 
+  [0
+0
+0
+-1.25
+1000
+1e+37
+0.00390625])
+
+OVSDB_CHECK_POSITIVE([boolean atom from JSON],
+  [[parse-atoms '["boolean"]' '[true]' '[false]' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([boolean atom from string],
+  [[parse-atom-strings '["boolean"]' 'true' 'false' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([string atom from JSON],
+  [[parse-atoms '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']],
+  [""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+OVSDB_CHECK_POSITIVE([string atom from string],
+  [[parse-atom-strings '["string"]' \
+    'unquoted' \
+    '"quoted-string"' \
+    '"needs quotes"' \
+    '""' \
+    '"true"' \
+    '"\"\\\/\b\f\n\r\t"']],
+  [unquoted
+quoted-string
+"needs quotes"
+""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+OVSDB_CHECK_POSITIVE([uuid atom from JSON],
+  [[parse-atoms '["uuid"]' '["uuid", "550e8400-e29b-41d4-a716-446655440000"]']],
+  [[["uuid","550e8400-e29b-41d4-a716-446655440000"]]])
+
+OVSDB_CHECK_POSITIVE([uuid atom from string],
+  [[parse-atom-strings '["uuid"]' '550e8400-e29b-41d4-a716-446655440000']],
+  [550e8400-e29b-41d4-a716-446655440000])
+
+OVSDB_CHECK_POSITIVE([integer atom sorting],
+  [[sort-atoms '["integer"]' '[55,0,-1,2,1]']],
+  [[[-1,0,1,2,55]]])
+
+OVSDB_CHECK_POSITIVE([real atom sorting],
+  [[sort-atoms '["real"]' '[1.25,1.23,0.0,-0.0,-1e99]']],
+  [[[-1e+99,0,0,1.23,1.25]]])
+
+OVSDB_CHECK_POSITIVE([boolean atom sorting],
+  [[sort-atoms '["boolean"]' '[true,false,true,false,false]']],
+  [[[false,false,false,true,true]]])
+
+OVSDB_CHECK_POSITIVE([string atom sorting],
+  [[sort-atoms '["string"]' '["abd","abc","\b","xxx"]']],
+  [[["\b","abc","abd","xxx"]]])
+
+OVSDB_CHECK_POSITIVE([uuid atom sorting],
+  [[sort-atoms '["uuid"]' '[
+    ["uuid", "00000000-0000-0000-0000-000000000001"],
+    ["uuid", "00000000-1000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-1000-0000-000000000000"],
+    ["uuid", "00010000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000000100"],
+    ["uuid", "00000000-0000-0000-0000-000100000000"],
+    ["uuid", "00000000-0000-0010-0000-000000000000"],
+    ["uuid", "00000100-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0001-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000001000000"],
+    ["uuid", "01000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000001000"],
+    ["uuid", "00000000-0000-0000-0000-000010000000"],
+    ["uuid", "00000000-0000-0000-0000-010000000000"],
+    ["uuid", "00000000-0000-0100-0000-000000000000"],
+    ["uuid", "10000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000000010"],
+    ["uuid", "00000000-0100-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0100-000000000000"],
+    ["uuid", "00000000-0000-0000-0001-000000000000"],
+    ["uuid", "00000010-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0000-0000-0010-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000010000"],
+    ["uuid", "00000000-0000-0000-1000-000000000000"],
+    ["uuid", "00000000-0000-0000-0000-100000000000"],
+    ["uuid", "00000000-0000-0000-0000-001000000000"],
+    ["uuid", "00000000-0000-0000-0000-000000100000"],
+    ["uuid", "00000000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0010-0000-0000-000000000000"],
+    ["uuid", "00100000-0000-0000-0000-000000000000"],
+    ["uuid", "00000000-0001-0000-0000-000000000000"],
+    ["uuid", "00000001-0000-0000-0000-000000000000"],
+    ["uuid", "00001000-0000-0000-0000-000000000000"]]']],
+  [[[["uuid","00000000-0000-0000-0000-000000000000"],["uuid","00000000-0000-0000-0000-000000000001"],["uuid","00000000-0000-0000-0000-000000000010"],["uuid","00000000-0000-0000-0000-000000000100"],["uuid","00000000-0000-0000-0000-000000001000"],["uuid","00000000-0000-0000-0000-000000010000"],["uuid","00000000-0000-0000-0000-000000100000"],["uuid","00000000-0000-0000-0000-000001000000"],["uuid","00000000-0000-0000-0000-000010000000"],["uuid","00000000-0000-0000-0000-000100000000"],["uuid","00000000-0000-0000-0000-001000000000"],["uuid","00000000-0000-0000-0000-010000000000"],["uuid","00000000-0000-0000-0000-100000000000"],["uuid","00000000-0000-0000-0001-000000000000"],["uuid","00000000-0000-0000-0010-000000000000"],["uuid","00000000-0000-0000-0100-000000000000"],["uuid","00000000-0000-0000-1000-000000000000"],["uuid","00000000-0000-0001-0000-000000000000"],["uuid","00000000-0000-0010-0000-000000000000"],["uuid","00000000-0000-0100-0000-000000000000"],["uuid","00000000-0000-1000-0000-000000000000"],["uuid","00000000-0001-0000-0000-000000000000"],["uuid","00000000-0010-0000-0000-000000000000"],["uuid","00000000-0100-0000-0000-000000000000"],["uuid","00000000-1000-0000-0000-000000000000"],["uuid","00000001-0000-0000-0000-000000000000"],["uuid","00000010-0000-0000-0000-000000000000"],["uuid","00000100-0000-0000-0000-000000000000"],["uuid","00001000-0000-0000-0000-000000000000"],["uuid","00010000-0000-0000-0000-000000000000"],["uuid","00100000-0000-0000-0000-000000000000"],["uuid","01000000-0000-0000-0000-000000000000"],["uuid","10000000-0000-0000-0000-000000000000"]]]])
+
+OVSDB_CHECK_NEGATIVE([real not acceptable integer JSON atom],
+  [[parse-atoms '["integer"]' '[0.5]' ]],
+  [expected integer])
+
+OVSDB_CHECK_NEGATIVE([real not acceptable integer string atom],
+  [[parse-atom-strings '["integer"]' '0.5' ]],
+  ["0.5" is not a valid integer])
+
+OVSDB_CHECK_NEGATIVE([string "true" not acceptable boolean JSON atom],
+  [[parse-atoms '["boolean"]' '["true"]' ]],
+  [expected boolean])
+
+OVSDB_CHECK_NEGATIVE([string "true" not acceptable boolean string atom],
+  [[parse-atom-strings '["boolean"]' '"true"' ]],
+  [""true"" is not a valid boolean (use "true" or "false")])
+
+OVSDB_CHECK_NEGATIVE([integer not acceptable string JSON atom],
+  [[parse-atoms '["string"]' '[1]']],
+  [expected string])
+
+OVSDB_CHECK_NEGATIVE([uuid atom must be expressed as JSON array],
+  [[parse-atoms '["uuid"]' '["550e8400-e29b-41d4-a716-446655440000"]']],
+  [[expected ["uuid", <string>]]])
+
+OVSDB_CHECK_NEGATIVE([empty string atom must be quoted],
+  [[parse-atom-strings '["string"]' '']],
+  [An empty string is not valid as input; use "" to represent the empty string])
+
+OVSDB_CHECK_NEGATIVE([quotes must be balanced],
+  [parse-atom-strings '[["string"]]' '"asdf'],
+  ["asdf: missing quote at end of quoted string])
+
+OVSDB_CHECK_NEGATIVE([uuids must be valid],
+  [parse-atom-strings '[["uuid"]]' '1234-5678'],
+  ["1234-5678" is not a valid UUID])
+
+AT_BANNER([OSVDB -- simple data])
+
+OVSDB_CHECK_POSITIVE([integer JSON datum],
+  [[parse-data '["integer"]' '[0]' '[1]' '[-1]']],
+  [0
+1
+-1])
+
+OVSDB_CHECK_POSITIVE([integer string datum],
+  [[parse-data-strings -- '["integer"]' '0' '1' '-1' '+1']],
+  [0
+1
+-1
+1])
+
+OVSDB_CHECK_POSITIVE([real JSON datum], 
+  [[parse-data '["real"]' '[0]' '[1.0]' '[-1.25]']],
+  [0
+1
+-1.25])
+
+OVSDB_CHECK_POSITIVE([real string datum], 
+  [[parse-data-strings -- '["real"]' '0' '1.0' '-1.25']],
+  [0
+1
+-1.25])
+
+OVSDB_CHECK_POSITIVE([boolean JSON datum],
+  [[parse-data '["boolean"]' '[true]' '[false]' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([boolean string datum],
+  [[parse-data-strings '["boolean"]' 'true' 'false' ]],
+  [true
+false])
+
+OVSDB_CHECK_POSITIVE([string JSON datum],
+  [[parse-data '["string"]' '[""]' '["true"]' '["\"\\\/\b\f\n\r\t"]']],
+  [""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+OVSDB_CHECK_POSITIVE([string string datum],
+  [[parse-data-strings '["string"]' '"x"' '""' '"true"' '"\"\\\/\b\f\n\r\t"']],
+  [x
+""
+"true"
+"\"\\/\b\f\n\r\t"])
+
+AT_BANNER([OVSDB -- set data])
+
+OVSDB_CHECK_POSITIVE([JSON optional boolean],
+  [[parse-data '{"key": "boolean", "min": 0}' \
+    '["set", [true]]' \
+    '["set", [false]]' \
+    '["set", []]']], 
+  [[["set",[true]]
+["set",[false]]
+["set",[]]]],
+  [set])
+
+OVSDB_CHECK_POSITIVE([string optional boolean],
+  [[parse-data-strings '{"key": "boolean", "min": 0}' \
+    'true' \
+    'false' \
+    '[]']], 
+  [[true
+false
+[]]],
+  [set])
+
+OVSDB_CHECK_POSITIVE([JSON set of 0 or more integers],
+  [[parse-data '{"key": "integer", "min": 0, "max": "unlimited"}' \
+    '["set", [0]]' \
+    '["set", [0, 1]]' \
+    '["set", [0, 1, 2]]' \
+    '["set", [0, 1, 2, 3, 4, 5]]' \
+    '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8]]' \
+    '["set", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]']],
+  [[["set",[0]]
+["set",[0,1]]
+["set",[0,1,2]]
+["set",[0,1,2,3,4,5]]
+["set",[0,1,2,3,4,5,6,7,8]]
+["set",[0,1,2,3,4,5,6,7,8,9,10]]]])
+
+OVSDB_CHECK_POSITIVE([string set of 0 or more integers],
+  [[parse-data-strings '{"key": "integer", "min": 0, "max": "unlimited"}' \
+    '0' \
+    '0,1' \
+    '0, 1, 2' \
+    '[0, 1,2, 3, 4, 5]' \
+    '0, 1,2, 3,4, 5, 6, 7, 8' \
+    '[0, 1, 2, 3, 4,5, 6,7, 8, 9, 10]']],
+  [[[0]
+[0, 1]
+[0, 1, 2]
+[0, 1, 2, 3, 4, 5]
+[0, 1, 2, 3, 4, 5, 6, 7, 8]
+[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]])
+
+OVSDB_CHECK_POSITIVE([JSON set of 1 to 3 uuids],
+  [[parse-data '{"key": "uuid", "min": 1, "max": 3}' \
+    '["set", [["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]' \
+    '["set", [["uuid", "c5051240-30ff-43ed-b4b9-93cf3f050813"],
+              ["uuid", "90558331-09af-4d2f-a572-509cad2e9088"],
+              ["uuid", "550e8400-e29b-41d4-a716-446655440000"]]]']],
+  [[["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"]]]
+["set",[["uuid","550e8400-e29b-41d4-a716-446655440000"],["uuid","90558331-09af-4d2f-a572-509cad2e9088"],["uuid","c5051240-30ff-43ed-b4b9-93cf3f050813"]]]]])
+
+OVSDB_CHECK_POSITIVE([string set of 1 to 3 uuids],
+  [[parse-data-strings '{"key": "uuid", "min": 1, "max": 3}' \
+    '[550e8400-e29b-41d4-a716-446655440000]' \
+    '[c5051240-30ff-43ed-b4b9-93cf3f050813,
+      90558331-09af-4d2f-a572-509cad2e9088,
+      550e8400-e29b-41d4-a716-446655440000]']],
+  [[[550e8400-e29b-41d4-a716-446655440000]
+[550e8400-e29b-41d4-a716-446655440000, 90558331-09af-4d2f-a572-509cad2e9088, c5051240-30ff-43ed-b4b9-93cf3f050813]]])
+
+OVSDB_CHECK_POSITIVE([JSON set of 0 to 3 strings],
+  [[parse-data '{"key": "string", "min": 0, "max": 3}' \
+    '["set", []]' \
+    '["set", ["a relatively long string"]]' \
+    '["set", ["short string", "a relatively long string"]]' \
+    '["set", ["zzz", "short string", "a relatively long string"]]']],
+  [[["set",[]]
+["set",["a relatively long string"]]
+["set",["a relatively long string","short string"]]
+["set",["a relatively long string","short string","zzz"]]]])
+
+OVSDB_CHECK_POSITIVE([string set of 0 to 3 strings],
+  [[parse-data-strings '{"key": "string", "min": 0, "max": 3}' \
+    '[]' \
+    '"a relatively long string"' \
+    '["short string", "a relatively long string"]' \
+    '"zzz","short string","a relatively long string"']],
+  [[[]
+["a relatively long string"]
+["a relatively long string", "short string"]
+["a relatively long string", "short string", zzz]]])
+
+OVSDB_CHECK_NEGATIVE([duplicate boolean not allowed in JSON set],
+  [[parse-data '{"key": "boolean", "max": 5}' '["set", [true, true]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate boolean not allowed in string set],
+  [[parse-data-strings '{"key": "boolean", "max": 5}' 'true, true']],
+  [set contains duplicate value])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer not allowed in JSON set],
+  [[parse-data '{"key": "integer", "max": 5}' '["set", [1, 2, 3, 1]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer not allowed in string set],
+  [[parse-data-strings '{"key": "integer", "max": 5}' '[1, 2, 3, 1]']],
+  [set contains duplicate value])
+
+OVSDB_CHECK_NEGATIVE([duplicate real not allowed in JSON set],
+  [[parse-data '{"key": "real", "max": 5}' '["set", [0.0, -0.0]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate real not allowed in string set],
+  [[parse-data-strings '{"key": "real", "max": 5}' '0.0, -0.0']],
+  [set contains duplicate value])
+
+OVSDB_CHECK_NEGATIVE([duplicate string not allowed in JSON set],
+  [[parse-data '{"key": "string", "max": 5}' '["set", ["asdf", "ASDF", "asdf"]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate string not allowed in string set],
+  [[parse-data-strings '{"key": "string", "max": 5}' 'asdf, ASDF, "asdf"']],
+  [set contains duplicate value])
+
+OVSDB_CHECK_NEGATIVE([duplicate uuid not allowed in JSON set],
+  [[parse-data '{"key": "uuid", "max": 5}' \
+    '["set", [["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"],
+              ["uuid", "355ad037-f1da-40aa-b47c-ff9c7e8c6a38"],
+              ["uuid", "7ef21525-0088-4a28-a418-5518413e43ea"]]]']],
+  [ovsdb error: set contains duplicate])
+
+OVSDB_CHECK_NEGATIVE([duplicate uuid not allowed in string set],
+  [[parse-data-strings '{"key": "uuid", "max": 5}' \
+    '7ef21525-0088-4a28-a418-5518413e43ea,
+     355ad037-f1da-40aa-b47c-ff9c7e8c6a38,
+     7ef21525-0088-4a28-a418-5518413e43ea']],
+  [set contains duplicate value])
+
+AT_BANNER([OVSDB -- map data])
+
+OVSDB_CHECK_POSITIVE([JSON map of 1 integer to boolean],
+  [[parse-data '{"key": "integer", "value": "boolean"}' \
+    '["map", [[1, true]]]']],
+  [[["map",[[1,true]]]]])
+
+OVSDB_CHECK_POSITIVE([string map of 1 integer to boolean],
+  [[parse-data-strings '{"key": "integer", "value": "boolean"}' \
+    '1=true']],
+  [[1=true]])
+
+OVSDB_CHECK_POSITIVE([JSON map of at least 1 integer to boolean],
+  [[parse-data '{"key": "integer", "value": "boolean", "max": "unlimited"}' \
+    '["map", [[1, true]]]' \
+    '["map", [[0, true], [1, false], [2, true], [3, true], [4, true]]]' \
+    '["map", [[3, false], [0, true], [4, false]]]']],
+  [[["map",[[1,true]]]
+["map",[[0,true],[1,false],[2,true],[3,true],[4,true]]]
+["map",[[0,true],[3,false],[4,false]]]]])
+
+OVSDB_CHECK_POSITIVE([string map of at least 1 integer to boolean],
+  [[parse-data-strings '{"key": "integer", "value": "boolean", "max": "unlimited"}' \
+    '1=true' \
+    '0=true 1=false 2=true, 3=true 4=true,' \
+    '3=false,0=true ,4=false']],
+  [[{1=true}
+{0=true, 1=false, 2=true, 3=true, 4=true}
+{0=true, 3=false, 4=false}]])
+
+OVSDB_CHECK_POSITIVE([JSON map of 1 boolean to integer],
+ [[parse-data '{"key": "boolean", "value": "integer"}' \
+   '["map", [[true, 1]]]']],
+ [[["map",[[true,1]]]]])
+
+OVSDB_CHECK_POSITIVE([string map of 1 boolean to integer],
+ [[parse-data-strings '{"key": "boolean", "value": "integer"}' \
+   'true=1']],
+ [[true=1]])
+
+OVSDB_CHECK_POSITIVE([JSON map of 1 uuid to real],
+  [[parse-data '{"key": "uuid", "value": "real", "min": 1, "max": 5}' \
+    '["map", [[["uuid", "cad8542b-6ee1-486b-971b-7dcbf6e14979"], 1.0],
+             [["uuid", "6b94b968-2702-4f64-9457-314a34d69b8c"], 2.0],
+             [["uuid", "d2c4a168-24de-47eb-a8a3-c1abfc814979"], 3.0],
+             [["uuid", "25bfa475-d072-4f60-8be1-00f48643e9cb"], 4.0],
+             [["uuid", "1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"], 5.0]]]']],
+  [[["map",[[["uuid","1c92b8ca-d5e4-4628-a85d-1dc2d099a99a"],5],[["uuid","25bfa475-d072-4f60-8be1-00f48643e9cb"],4],[["uuid","6b94b968-2702-4f64-9457-314a34d69b8c"],2],[["uuid","cad8542b-6ee1-486b-971b-7dcbf6e14979"],1],[["uuid","d2c4a168-24de-47eb-a8a3-c1abfc814979"],3]]]]])
+
+OVSDB_CHECK_POSITIVE([string map of 1 uuid to real],
+  [[parse-data-strings '{"key": "uuid", "value": "real", "min": 1, "max": 5}' \
+    'cad8542b-6ee1-486b-971b-7dcbf6e14979=1.0,
+     6b94b968-2702-4f64-9457-314a34d69b8c=2.0,
+     d2c4a168-24de-47eb-a8a3-c1abfc814979=3.0,
+     25bfa475-d072-4f60-8be1-00f48643e9cb=4.0,
+     1c92b8ca-d5e4-4628-a85d-1dc2d099a99a=5.0']],
+  [[{1c92b8ca-d5e4-4628-a85d-1dc2d099a99a=5, 25bfa475-d072-4f60-8be1-00f48643e9cb=4, 6b94b968-2702-4f64-9457-314a34d69b8c=2, cad8542b-6ee1-486b-971b-7dcbf6e14979=1, d2c4a168-24de-47eb-a8a3-c1abfc814979=3}]])
+
+OVSDB_CHECK_POSITIVE([JSON map of 10 string to string],
+  [[parse-data '{"key": "string", "value": "string", "min": 1, "max": 10}' \
+    '["map", [["2 gills", "1 chopin"],
+             ["2 chopins", "1 pint"],
+             ["2 pints", "1 quart"],
+             ["2 quarts", "1 pottle"],
+             ["2 pottles", "1 gallon"],
+             ["2 gallons", "1 peck"],
+             ["2 pecks", "1 demibushel"],
+             ["2 demibushel", "1 firkin"],
+             ["2 firkins", "1 kilderkin"],
+             ["2 kilderkins", "1 barrel"]]]']],
+   [[["map",[["2 chopins","1 pint"],["2 demibushel","1 firkin"],["2 firkins","1 kilderkin"],["2 gallons","1 peck"],["2 gills","1 chopin"],["2 kilderkins","1 barrel"],["2 pecks","1 demibushel"],["2 pints","1 quart"],["2 pottles","1 gallon"],["2 quarts","1 pottle"]]]]])
+
+OVSDB_CHECK_POSITIVE([string map of 10 string to string],
+  [[parse-data-strings '{"key": "string", "value": "string", "min": 1, "max": 10}' \
+    '{"2 gills"="1 chopin",
+      "2 chopins"= "1 pint",
+      "2 pints"= "1 quart",
+      "2 quarts"= "1 pottle",
+      "2 pottles"= "1 gallon",
+      "2 gallons"= "1 peck",
+      "2 pecks"= "1 demibushel",
+      "2 demibushel"= "1 firkin",
+      "2 firkins"= "1 kilderkin",
+      "2 kilderkins"= "1 barrel"}']],
+   [[{"2 chopins"="1 pint", "2 demibushel"="1 firkin", "2 firkins"="1 kilderkin", "2 gallons"="1 peck", "2 gills"="1 chopin", "2 kilderkins"="1 barrel", "2 pecks"="1 demibushel", "2 pints"="1 quart", "2 pottles"="1 gallon", "2 quarts"="1 pottle"}]])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer key not allowed in JSON map],
+  [[parse-data '{"key": "integer", "value": "boolean", "max": 5}' \
+    '["map", [[1, true], [2, false], [1, false]]]']],
+  [ovsdb error: map contains duplicate key])
+
+OVSDB_CHECK_NEGATIVE([duplicate integer key not allowed in string map],
+  [[parse-data-strings '{"key": "integer", "value": "boolean", "max": 5}' \
+    '1=true 2=false 1=false']],
+  [map contains duplicate key])
diff --git a/tests/ovsdb-execution.at b/tests/ovsdb-execution.at
new file mode 100644 (file)
index 0000000..1113f94
--- /dev/null
@@ -0,0 +1,357 @@
+AT_BANNER([OVSDB -- execution])
+
+m4_define([ORDINAL_SCHEMA],
+  [[{"name": "mydb",
+     "tables": {
+       "ordinals": {
+         "columns": {
+           "number": {"type": "integer"},
+           "name": {"type": "string"}}}}}]])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Runs "test-ovsdb execute" with the given SCHEMA and each of the
+# TRANSACTIONS (which should be a quoted list of quoted strings).
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb execute execution positive $5])
+   AT_CHECK([test-ovsdb execute '$2' m4_foreach([txn], [$3], [ 'txn'])],
+     [0], [stdout], [])
+   AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$4])
+   AT_CLEANUP])
+
+m4_define([EXECUTION_EXAMPLES], [
+OVSDB_CHECK_EXECUTION([insert row, query table],
+  [ORDINAL_SCHEMA], 
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": []}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, query by value],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]]}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": [["name", "==", "one"]]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, query by named-uuid],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "first"]]]},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "second"]]]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0}]},{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, update rows by value],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "update",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]],
+       "row": {"name": "nought"}}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"nought","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, mutate rows],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "mutate",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]],
+       "mutations": [["number", "+=", 2]]}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<2>"],"name":"one","number":1},{"_uuid":["uuid","<0>"],"_version":["uuid","<3>"],"name":"zero","number":2}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete by named-uuid],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "first"]]]},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name","number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":1},{"rows":[{"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete rows by value],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "delete",
+       "table": "ordinals",
+       "where": [["name", "==", "zero"]]}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": []}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":1}]
+[{"rows":[{"_uuid":["uuid","<1>"],"_version":["uuid","<2>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete by (non-matching) value],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "first"}]]],
+   [[[{"op": "delete",
+       "table": "ordinals",
+       "where": [["name", "==", "nought"]]}]]],
+   [[[{"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "sort": ["number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]}]
+[{"uuid":["uuid","<1>"]}]
+[{"count":0}]
+[{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<2>"],"name":"zero","number":0},{"_uuid":["uuid","<1>"],"_version":["uuid","<3>"],"name":"one","number":1}]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert rows, delete all],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"},
+       "uuid-name": "first"},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"},
+       "uuid-name": "second"},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": []},
+      {"op": "select",
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name","number"]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"count":2},{"rows":[]}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table, commit],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "select",
+       "table": "ordinals",
+       "where": []},
+      {"op": "commit",
+       "durable": false}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([insert row, query table, commit durably],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "select",
+       "table": "ordinals",
+       "where": []},
+      {"op": "commit",
+       "durable": true}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"rows":[{"_uuid":["uuid","<0>"],"_version":["uuid","<1>"],"name":"zero","number":0}]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with correct rows],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with extra row],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([equality wait with missing row],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with correct rows],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out","error":"timed out"}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with extra row],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+
+OVSDB_CHECK_EXECUTION([inequality wait with missing row],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 0,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "!=",
+       "rows": [{"name": "one", "number": 1}]}]]]],
+  [[[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{}]
+]])
+])
+
+EXECUTION_EXAMPLES
diff --git a/tests/ovsdb-file.at b/tests/ovsdb-file.at
new file mode 100644 (file)
index 0000000..d08b6de
--- /dev/null
@@ -0,0 +1,48 @@
+AT_BANNER([OVSDB -- file storage])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA and runs each of the
+# TRANSACTIONS (which should be a quoted list of quoted strings)
+# against it with ovsdb-tool one at a time.  
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb file positive $5])
+   AT_DATA([schema], [$2
+])
+   touch .db.~lock~
+   AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   m4_foreach([txn], [$3], 
+     [AT_CHECK([ovsdb-tool transact db 'txn'], [0], [stdout], [ignore])
+cat stdout >> output
+])
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$4])
+   AT_CLEANUP])
+
+EXECUTION_EXAMPLES
+
+AT_SETUP([transaction comments])
+AT_KEYWORDS([ovsdb file positive])
+AT_DATA([schema], [ORDINAL_SCHEMA
+])
+touch .db.~lock~
+AT_CHECK([ovsdb-tool create db schema], [0], [], [ignore])
+AT_CHECK([[ovsdb-tool transact db '
+    [{"op": "insert",
+      "table": "ordinals",
+      "row": {"name": "five", "number": 5}},
+     {"op": "comment",
+      "comment": "add row for 5"}]']], [0], [stdout], [ignore])
+AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0],
+ [[[{"uuid":["uuid","<0>"]},{}]
+]])
+AT_CHECK([grep -q "add row for 5" db])
+AT_CLEANUP
diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at
new file mode 100644 (file)
index 0000000..dcf3efc
--- /dev/null
@@ -0,0 +1,322 @@
+AT_BANNER([OVSDB -- interface description language (IDL)])
+
+# OVSDB_CHECK_IDL(TITLE, [PRE-IDL-TXN], TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with a schema derived from idltest.ovsidl, runs
+# each PRE-IDL-TXN (if any), starts an ovsdb-server on that database,
+# and runs "test-ovsdb idl" passing each of the TRANSACTIONS along.
+#
+# Checks that the overall output is OUTPUT.  Before comparison, the
+# output is sorted (using "sort") and UUIDs in the output are replaced
+# by markers of the form <N> where N is a number.  The first unique
+# UUID is replaced by <0>, the next by <1>, and so on.  If a given
+# UUID appears more than once it is always replaced by the same
+# marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_IDL], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server idl positive $5])
+   AT_CHECK([ovsdb-tool create db $abs_srcdir/idltest.ovsschema],
+                  [0], [stdout], [ignore])
+   AT_CHECK([ovsdb-server '-vPATTERN:console:ovsdb-server|%c|%m' --detach --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+   m4_if([$2], [], [],
+     [AT_CHECK([ovsdb-client transact unix:socket $2], [0], [ignore], [ignore], [kill `cat pid`])])
+   AT_CHECK([test-ovsdb '-vPATTERN:console:test-ovsdb|%c|%m' -vjsonrpc -t10 idl unix:socket $3], 
+            [0], [stdout], [ignore], [kill `cat pid`])
+   AT_CHECK([sort stdout | perl $srcdir/uuidfilt.pl], [0], [$4], [],
+            [kill `cat pid`])
+   OVSDB_SERVER_SHUTDOWN
+   AT_CLEANUP])
+
+OVSDB_CHECK_IDL([simple idl, initially empty, no ops],
+  [],
+  [],
+  [000: empty
+001: done
+])
+
+OVSDB_CHECK_IDL([simple idl, initially empty, various ops],
+  [],
+  [['[{"op": "insert",
+       "table": "simple",
+       "row": {"i": 1,
+               "r": 2.0,
+               "b": true,
+               "s": "mystring",
+               "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"],
+               "ia": ["set", [1, 2, 3]],
+               "ra": ["set", [-0.5]],
+               "ba": ["set", [true, false]],
+               "sa": ["set", ["abc", "def"]], 
+               "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"],
+                              ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}},
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]' \
+    '[{"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"b": true}}]' \
+    '[{"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"r": 123.5}}]' \
+    '[{"op": "insert",
+       "table": "simple",
+       "row": {"i": -1,
+               "r": 125,
+               "b": false,
+               "s": "",
+               "ia": ["set", [1]],
+               "ra": ["set", [1.5]],
+               "ba": ["set", [false]],
+               "sa": ["set", []], 
+               "ua": ["set", []]}}]' \
+    '[{"op": "update",
+       "table": "simple",
+       "where": [["i", "<", 1]],
+       "row": {"s": "newstring"}}]' \
+    '[{"op": "delete",
+       "table": "simple",
+       "where": [["i", "==", 0]]}]' \
+    'reconnect']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
+002: i=0 r=0 b=false s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+002: i=1 r=2 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+003: {"error":null,"result":[{"count":2}]}
+004: i=0 r=0 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+004: i=1 r=2 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+005: {"error":null,"result":[{"count":2}]}
+006: i=0 r=123.5 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+006: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+007: {"error":null,"result":[{"uuid":["uuid","<6>"]}]}
+008: i=-1 r=125 b=false s= u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+008: i=0 r=123.5 b=true s= u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+008: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+009: {"error":null,"result":[{"count":2}]}
+010: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+010: i=0 r=123.5 b=true s=newstring u=<2> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+010: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+011: {"error":null,"result":[{"count":1}]}
+012: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+012: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+013: reconnect
+014: i=-1 r=125 b=false s=newstring u=<2> ia=[1] ra=[1.5] ba=[false] sa=[] ua=[] uuid=<6>
+014: i=1 r=123.5 b=true s=mystring u=<3> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<4> <5>] uuid=<0>
+015: done
+]])
+
+OVSDB_CHECK_IDL([simple idl, initially populated],
+  [['[{"op": "insert",
+       "table": "simple",
+       "row": {"i": 1,
+               "r": 2.0,
+               "b": true,
+               "s": "mystring",
+               "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"],
+               "ia": ["set", [1, 2, 3]],
+               "ra": ["set", [-0.5]],
+               "ba": ["set", [true, false]],
+               "sa": ["set", ["abc", "def"]], 
+               "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"],
+                              ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}},
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]']],
+  [['[{"op": "update",
+       "table": "simple",
+       "where": [],
+       "row": {"b": true}}]']],
+  [[000: i=0 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+000: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+001: {"error":null,"result":[{"count":2}]}
+002: i=0 r=0 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+002: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+003: done
+]])
+
+OVSDB_CHECK_IDL([simple idl, writing via IDL],
+  [['[{"op": "insert",
+       "table": "simple",
+       "row": {"i": 1,
+               "r": 2.0,
+               "b": true,
+               "s": "mystring",
+               "u": ["uuid", "84f5c8f5-ac76-4dbc-a24f-8860eb407fc1"],
+               "ia": ["set", [1, 2, 3]],
+               "ra": ["set", [-0.5]],
+               "ba": ["set", [true, false]],
+               "sa": ["set", ["abc", "def"]], 
+               "ua": ["set", [["uuid", "69443985-7806-45e2-b35f-574a04e720f9"],
+                              ["uuid", "aad11ef0-816a-4b01-93e6-03b8b4256b98"]]]}},
+      {"op": "insert",
+       "table": "simple",
+       "row": {}}]']],
+  [['set 0 b 1, set 1 r 3.5' \
+    'insert 2, delete 1']],
+  [[000: i=0 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+000: i=1 r=2 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+001: commit, status=success
+002: i=0 r=0 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+002: i=1 r=3.5 b=true s=mystring u=<2> ia=[1 2 3] ra=[-0.5] ba=[false true] sa=[abc def] ua=[<3> <4>] uuid=<5>
+003: commit, status=success
+004: i=0 r=0 b=true s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+004: i=2 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<6>
+005: done
+]])
+
+OVSDB_CHECK_IDL([simple idl, increment operation],
+  [['[{"op": "insert",
+       "table": "simple",
+       "row": {}}]']],
+  [['set 0 r 2.0, increment simple i']],
+  [[000: i=0 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+001: commit, status=success, increment=1
+002: i=1 r=2 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1>
+003: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, consistent ops],
+  [],
+  [['[{"op": "insert",
+       "table": "link1",
+       "row": {"i": 0, "k": ["named-uuid", "self"]},
+       "uuid-name": "self"}]' \
+    '[{"op": "declare",
+       "uuid-name": "row1"},
+      {"op": "declare",
+       "uuid-name": "row2"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 1, "k": ["named-uuid", "row2"]},
+       "uuid-name": "row1"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 2, "k": ["named-uuid", "row1"]},
+       "uuid-name": "row2"}]' \
+    '[{"op": "update",
+       "table": "link1",
+       "where": [["i", "==", 1]],
+       "row": {"k": ["uuid", "#1#"]}}]' \
+    '[{"op": "update",
+       "table": "link1",
+       "where": [],
+       "row": {"k": ["uuid", "#0#"]}}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
+002: i=0 k=0 ka=[] l2= uuid=<0>
+003: {"error":null,"result":[{"uuid":"<1>"},{"uuid":"<2>"},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]}]}
+004: i=0 k=0 ka=[] l2= uuid=<0>
+004: i=1 k=2 ka=[] l2= uuid=<1>
+004: i=2 k=1 ka=[] l2= uuid=<2>
+005: {"error":null,"result":[{"count":1}]}
+006: i=0 k=0 ka=[] l2= uuid=<0>
+006: i=1 k=1 ka=[] l2= uuid=<1>
+006: i=2 k=1 ka=[] l2= uuid=<2>
+007: {"error":null,"result":[{"count":3}]}
+008: i=0 k=0 ka=[] l2= uuid=<0>
+008: i=1 k=0 ka=[] l2= uuid=<1>
+008: i=2 k=0 ka=[] l2= uuid=<2>
+009: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, inconsistent ops],
+  [],
+  [['[{"op": "insert",
+       "table": "link1",
+       "row": {"i": 0, "k": ["uuid", "cf197cc5-c8c9-42f5-82d5-c71a9f2cb96b"]}}]' \
+     '[{"op": "update",
+       "table": "link1",
+       "where": [],
+       "row": {"k": ["uuid", "#0#"]}}]' \
+     '[{"op": "update",
+       "table": "link1",
+       "where": [],
+       "row": {"k": ["uuid", "c2fca39a-e69a-42a4-9c56-5eca85839ce9"]}}]' \
+     '[{"op": "insert",
+       "table": "link1",
+       "row": {"i": 1, "k": ["uuid", "52d752a3-b062-4668-9446-d2e0d4a14703"]}}]' \
+     '[{"op": "update",
+       "table": "link1",
+       "where": [],
+       "row": {"k": ["uuid", "#1#"]}}]' \
+]],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]}]}
+002: i=0 k= ka=[] l2= uuid=<0>
+003: {"error":null,"result":[{"count":1}]}
+004: i=0 k=0 ka=[] l2= uuid=<0>
+005: {"error":null,"result":[{"count":1}]}
+006: i=0 k= ka=[] l2= uuid=<0>
+007: {"error":null,"result":[{"uuid":["uuid","<1>"]}]}
+008: i=0 k= ka=[] l2= uuid=<0>
+008: i=1 k= ka=[] l2= uuid=<1>
+009: {"error":null,"result":[{"count":2}]}
+010: i=0 k=1 ka=[] l2= uuid=<0>
+010: i=1 k=1 ka=[] l2= uuid=<1>
+011: done
+]])
+
+OVSDB_CHECK_IDL([self-linking idl, sets],
+  [],
+  [['[{"op": "insert",
+       "table": "link1",
+       "row": {"i": 0, "ka": ["set", [["named-uuid", "i0"]]]},
+       "uuid-name": "i0"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 1, "ka": ["set", [["named-uuid", "i1"]]]},
+       "uuid-name": "i1"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 2, "ka": ["set", [["named-uuid", "i2"]]]},
+       "uuid-name": "i2"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 3, "ka": ["set", [["named-uuid", "i3"]]]},
+       "uuid-name": "i3"}]' \
+    '[{"op": "update",
+       "table": "link1",
+       "where": [],
+       "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "#1#"], ["uuid", "#2#"], ["uuid", "#3#"]]]}}]' \
+    '[{"op": "update",
+       "table": "link1",
+       "where": [],
+       "row": {"ka": ["set", [["uuid", "#0#"], ["uuid", "88702e78-845b-4a6e-ad08-cf68922ae84a"], ["uuid", "#2#"], ["uuid", "1ac2b12e-b767-4805-a55d-43976e40c465"]]]}}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"uuid":["uuid","<2>"]},{"uuid":["uuid","<3>"]}]}
+002: i=0 k= ka=[0] l2= uuid=<0>
+002: i=1 k= ka=[1] l2= uuid=<1>
+002: i=2 k= ka=[2] l2= uuid=<2>
+002: i=3 k= ka=[3] l2= uuid=<3>
+003: {"error":null,"result":[{"count":4}]}
+004: i=0 k= ka=[0 1 2 3] l2= uuid=<0>
+004: i=1 k= ka=[0 1 2 3] l2= uuid=<1>
+004: i=2 k= ka=[0 1 2 3] l2= uuid=<2>
+004: i=3 k= ka=[0 1 2 3] l2= uuid=<3>
+005: {"error":null,"result":[{"count":4}]}
+006: i=0 k= ka=[0 2] l2= uuid=<0>
+006: i=1 k= ka=[0 2] l2= uuid=<1>
+006: i=2 k= ka=[0 2] l2= uuid=<2>
+006: i=3 k= ka=[0 2] l2= uuid=<3>
+007: done
+]])
+
+OVSDB_CHECK_IDL([external-linking idl, consistent ops],
+  [],
+  [['[{"op": "insert",
+       "table": "link2",
+       "row": {"i": 0},
+       "uuid-name": "row0"},
+      {"op": "insert",
+       "table": "link1",
+       "row": {"i": 1, "l2": ["set", [["named-uuid", "row0"]]]},
+       "uuid-name": "row1"}]']],
+  [[000: empty
+001: {"error":null,"result":[{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]}
+002: i=0 l1= uuid=<0>
+002: i=1 k= ka=[] l2=0 uuid=<1>
+003: done
+]])
diff --git a/tests/ovsdb-log.at b/tests/ovsdb-log.at
new file mode 100644 (file)
index 0000000..8aeb33b
--- /dev/null
@@ -0,0 +1,282 @@
+AT_BANNER([OVSDB -- logging])
+
+AT_SETUP([create empty, reread])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([log])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_CREAT|O_RDWR'], [0], 
+  [file: open successful
+], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read], [0], 
+  [file: open successful
+file: read: end of log
+], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([check that O_EXCL works])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[1]']], [0], 
+  [[file: open successful
+file: write:[1] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read], [0], 
+  [[file: open successful
+file: read: [1]
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb -vlockfile:console:emer log-io file 'O_CREAT|O_RDWR|O_EXCL' read], [1], 
+  [], [test-ovsdb: I/O error: create: file failed (File exists)
+])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write one, reread, append])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_RDWR' read read read 'write:["append"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: write:["append"] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: ["append"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, reread one, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_RDWR' read 'write:["more data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: write:["more data"] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: ["more data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, add corrupted data, read])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([echo 'xxx' >> file])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: parse error at offset 174 in header line "xxx"
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, add corrupted data, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([echo 'xxx' >> file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_RDWR' read read read read 'write:[3]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: parse error at offset 174 in header line "xxx"
+file: write:[3] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: [3]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, corrupt some data, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[sed 's/\[2]/[3]/' < file > file.tmp]])
+AT_CHECK([mv file.tmp file])
+AT_CHECK([[grep -c '\[3]' file]], [0], [1
+])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_RDWR' read read read 'write:["longer data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read failed: syntax error: file: 4 bytes starting at offset 170 have SHA-1 hash 5c031e5c0d3a9338cc127ebe40bb2748b6a67e78 but should have hash 98f55556e7ffd432381b56a19bd485b3e6446442
+file: write:["longer data"] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: ["longer data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write, truncate file, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[sed 's/\[2]/2/' < file > file.tmp]])
+AT_CHECK([mv file.tmp file])
+AT_CHECK([[grep -c '^2$' file]], [0], [1
+])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_RDWR' read read read 'write:["longer data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read failed: I/O error: file: error reading 4 bytes starting at offset 170 (unexpected end of file)
+file: write:["longer data"] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: ["longer data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
+
+AT_SETUP([write bad JSON, read, overwrite])
+AT_KEYWORDS([ovsdb log])
+AT_CAPTURE_FILE([file])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_CREAT|O_RDWR' 'write:[0]' 'write:[1]' 'write:[2]']], [0], 
+  [[file: open successful
+file: write:[0] successful
+file: write:[1] successful
+file: write:[2] successful
+]], [ignore])
+AT_CHECK([[printf '%s\n%s\n' 'OVSDB JSON 5 d910b02871075d3156ec8675dfc95b7d5d640aa6' 'null' >> file]])
+AT_CHECK(
+  [[test-ovsdb log-io file 'O_RDWR' read read read read 'write:["replacement data"]']], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read failed: syntax error: file: 5 bytes starting at offset 228 are not valid JSON (line 1, column 0, byte 5: syntax error at beginning of input)
+file: write:["replacement data"] successful
+]], [ignore])
+AT_CHECK(
+  [test-ovsdb log-io file 'O_RDONLY' read read read read read], [0], 
+  [[file: open successful
+file: read: [0]
+file: read: [1]
+file: read: [2]
+file: read: ["replacement data"]
+file: read: end of log
+]], [ignore])
+AT_CHECK([test -f .file.~lock~])
+AT_CLEANUP
diff --git a/tests/ovsdb-macros.at b/tests/ovsdb-macros.at
new file mode 100644 (file)
index 0000000..ebe0d9d
--- /dev/null
@@ -0,0 +1,13 @@
+dnl OVSDB_INIT([$1])
+dnl
+dnl Creates an empty database named $1.
+m4_define([OVSDB_INIT],
+  [AT_CHECK(
+     [ovsdb-tool create $1 $abs_top_srcdir/vswitchd/vswitch.ovsschema],
+     [0], [stdout], [ignore])
+   AT_CHECK(
+     [[ovsdb-tool transact $1 \
+        '[{"op": "insert",
+           "table": "Open_vSwitch",
+           "row": {}}]']],
+     [0], [ignore], [ignore])])
diff --git a/tests/ovsdb-monitor.at b/tests/ovsdb-monitor.at
new file mode 100644 (file)
index 0000000..5ebf122
--- /dev/null
@@ -0,0 +1,162 @@
+AT_BANNER([OVSDB -- ovsdb-server monitors])
+
+# OVSDB_CHECK_MONITOR(TITLE, SCHEMA, [PRE-MONITOR-TXN], MONITOR-ARGS,
+#                     TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA, starts an ovsdb-server on
+# that database, and runs each of the TRANSACTIONS (which should be a
+# quoted list of quoted strings) against it with ovsdb-client one at a
+# time.
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_MONITOR], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server monitor positive $7])
+   AT_DATA([schema], [$2
+])
+   AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   m4_foreach([txn], [$3],
+     [AT_CHECK([ovsdb-tool transact db 'txn'], [0], [ignore], [ignore])])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/server-pid --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+   AT_CHECK([ovsdb-client --detach --pidfile=$PWD/client-pid monitor --format=csv unix:socket $4 > output], 
+            [0], [ignore], [ignore], [kill `cat server-pid`])
+   m4_foreach([txn], [$5],
+     [AT_CHECK([ovsdb-client transact unix:socket 'txn'], [0],
+                     [ignore], [ignore], [kill `cat server-pid client-pid`])])
+   AT_CHECK([ovsdb-client transact unix:socket '[[]]'], [0],
+            [ignore], [ignore], [kill `cat server-pid client-pid`])
+   AT_CHECK([ovs-appctl -t $PWD/unixctl -e exit], [0], [ignore], [ignore])
+   OVS_WAIT_UNTIL([test ! -e server-pid && test ! -e client-pid])
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$6], [ignore])
+   AT_CLEANUP])
+
+OVSDB_CHECK_MONITOR([monitor insert into empty table],
+  [ORDINAL_SCHEMA],
+  [],
+  [ordinals],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]]],
+  [[row,action,name,number,_version
+<0>,insert,"""zero""",0,"[""uuid"",""<1>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor insert into populated table],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<2>,insert,"""zero""",0,"[""uuid"",""<3>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor delete],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "==", 10]]}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<0>,delete,"""ten""",10,"[""uuid"",""<1>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor row update],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "update",
+       "table": "ordinals",
+       "where": [["number", "==", 10]],
+       "row": {"name": "five plus five"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<0>,old,"""ten""",,"[""uuid"",""<1>""]"
+,new,"""five plus five""",10,"[""uuid"",""<2>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor no-op row updates],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "update",
+       "table": "ordinals",
+       "where": [["number", "==", 10]],
+       "row": {"number": 10, "name": "ten"}}]]],
+   [[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 9, "name": "nine"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<2>,insert,"""nine""",9,"[""uuid"",""<3>""]"
+]])
+
+OVSDB_CHECK_MONITOR([monitor insert-and-update transaction],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 9, "name": "nine"},
+       "uuid-name": "nine"},
+      {"op": "update",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "nine"]]],
+       "row": {"name": "three squared"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<2>,insert,"""three squared""",9,"[""uuid"",""<3>""]"
+]])
+
+
+OVSDB_CHECK_MONITOR([monitor insert-update-and-delete transaction],
+  [ORDINAL_SCHEMA],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 10, "name": "ten"}}]]]],
+  [ordinals],
+  [[[[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 9, "name": "nine"},
+       "uuid-name": "nine"},
+      {"op": "update",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "nine"]]],
+       "row": {"name": "three squared"}},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["_uuid", "==", ["named-uuid", "nine"]]]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 7, "name": "seven"}}]]]],
+  [[row,action,name,number,_version
+<0>,initial,"""ten""",10,"[""uuid"",""<1>""]"
+row,action,name,number,_version
+<2>,insert,"""seven""",7,"[""uuid"",""<3>""]"
+]])
+
diff --git a/tests/ovsdb-mutation.at b/tests/ovsdb-mutation.at
new file mode 100644 (file)
index 0000000..b10aff1
--- /dev/null
@@ -0,0 +1,700 @@
+AT_BANNER([OVSDB -- mutations])
+
+OVSDB_CHECK_POSITIVE([null mutation],
+  [[parse-mutations \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '[]']],
+  [[[]]])
+
+OVSDB_CHECK_POSITIVE([mutations on scalars],
+  [[parse-mutations \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["i", "+=", 0]]' \
+    '[["i", "-=", 1]]' \
+    '[["i", "*=", 2]]' \
+    '[["i", "/=", 3]]' \
+    '[["i", "%=", 4]]' \
+    '[["r", "+=", 0.5]]' \
+    '[["r", "-=", 1.5]]' \
+    '[["r", "*=", 2.5]]' \
+    '[["r", "/=", 3.5]]']],
+  [[[["i","+=",0]]
+[["i","-=",1]]
+[["i","*=",2]]
+[["i","/=",3]]
+[["i","%=",4]]
+[["r","+=",0.5]]
+[["r","-=",1.5]]
+[["r","*=",2.5]]
+[["r","/=",3.5]]]],
+  [mutation])
+
+AT_SETUP([disallowed mutations on scalars])
+AT_KEYWORDS([ovsdb negative mutation])
+AT_CHECK([[test-ovsdb parse-mutations \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[["i", "xxx", 1]]' \
+    '[["i", "insert", 1]]' \
+    '[["i", "delete", 2]]' \
+    '[["r", "%=", 0.5]]' \
+    '[["r", "insert", 1.5]]' \
+    '[["r", "delete", 2.5]]' \
+    '[["b", "+=", true]]' \
+    '[["b", "-=", false]]' \
+    '[["b", "*=", true]]' \
+    '[["b", "/=", false]]' \
+    '[["b", "%=", true]]' \
+    '[["b", "insert", false]]' \
+    '[["b", "delete", true]]' \
+    '[["s", "+=", "a"]]' \
+    '[["s", "-=", "b"]]' \
+    '[["s", "*=", "c"]]' \
+    '[["s", "/=", "d"]]' \
+    '[["s", "%=", "e"]]' \
+    '[["s", "insert", "f"]]' \
+    '[["s", "delete", "g"]]' \
+    '[["u", "+=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "-=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "*=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "/=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "insert", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]' \
+    '[["u", "delete", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]']],
+  [1], [],
+  [[test-ovsdb: unknown mutator: No mutator named xxx.
+test-ovsdb: syntax "["i","insert",1]": syntax error: Type mismatch: "insert" operator may not be applied to column i of type integer.
+test-ovsdb: syntax "["i","delete",2]": syntax error: Type mismatch: "delete" operator may not be applied to column i of type integer.
+test-ovsdb: syntax "["r","%=",0.5]": syntax error: Type mismatch: "%=" operator may not be applied to column r of type real.
+test-ovsdb: syntax "["r","insert",1.5]": syntax error: Type mismatch: "insert" operator may not be applied to column r of type real.
+test-ovsdb: syntax "["r","delete",2.5]": syntax error: Type mismatch: "delete" operator may not be applied to column r of type real.
+test-ovsdb: syntax "["b","+=",true]": syntax error: Type mismatch: "+=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","-=",false]": syntax error: Type mismatch: "-=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","*=",true]": syntax error: Type mismatch: "*=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","/=",false]": syntax error: Type mismatch: "/=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","%=",true]": syntax error: Type mismatch: "%=" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","insert",false]": syntax error: Type mismatch: "insert" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["b","delete",true]": syntax error: Type mismatch: "delete" operator may not be applied to column b of type boolean.
+test-ovsdb: syntax "["s","+=","a"]": syntax error: Type mismatch: "+=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","-=","b"]": syntax error: Type mismatch: "-=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","*=","c"]": syntax error: Type mismatch: "*=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","/=","d"]": syntax error: Type mismatch: "/=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","%=","e"]": syntax error: Type mismatch: "%=" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","insert","f"]": syntax error: Type mismatch: "insert" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["s","delete","g"]": syntax error: Type mismatch: "delete" operator may not be applied to column s of type string.
+test-ovsdb: syntax "["u","+=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "+=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","-=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "-=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","*=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "*=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","/=",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "/=" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","insert",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "insert" operator may not be applied to column u of type uuid.
+test-ovsdb: syntax "["u","delete",["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"]]": syntax error: Type mismatch: "delete" operator may not be applied to column u of type uuid.
+]])
+AT_CLEANUP
+
+OVSDB_CHECK_POSITIVE([mutations on sets],
+  [[parse-mutations \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '[["i", "+=", 1]]' \
+    '[["i", "-=", 2]]' \
+    '[["i", "*=", 3]]' \
+    '[["i", "/=", 4]]' \
+    '[["i", "%=", 5]]' \
+    '[["i", "insert", ["set", [1, 2]]]]' \
+    '[["i", "delete", ["set", [1, 2, 3]]]]' \
+    '[["r", "+=", 1]]' \
+    '[["r", "-=", 2]]' \
+    '[["r", "*=", 3]]' \
+    '[["r", "/=", 4]]' \
+    '[["r", "insert", ["set", [1, 2]]]]' \
+    '[["r", "delete", ["set", [1, 2, 3]]]]' \
+    '[["b", "insert", ["set", [true]]]]' \
+    '[["b", "delete", ["set", [false]]]]' \
+    '[["s", "insert", ["set", ["a"]]]]' \
+    '[["s", "delete", ["set", ["a", "b"]]]]' \
+    '[["u", "insert", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]' \
+    '[["u", "delete", 
+       ["set", [["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"],
+                ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]]]]' \
+]],
+  [[[["i","+=",1]]
+[["i","-=",2]]
+[["i","*=",3]]
+[["i","/=",4]]
+[["i","%=",5]]
+[["i","insert",["set",[1,2]]]]
+[["i","delete",["set",[1,2,3]]]]
+[["r","+=",1]]
+[["r","-=",2]]
+[["r","*=",3]]
+[["r","/=",4]]
+[["r","insert",["set",[1,2]]]]
+[["r","delete",["set",[1,2,3]]]]
+[["b","insert",["set",[true]]]]
+[["b","delete",["set",[false]]]]
+[["s","insert",["set",["a"]]]]
+[["s","delete",["set",["a","b"]]]]
+[["u","insert",["set",[["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]
+[["u","delete",["set",[["uuid","9179ca6d-6d65-400a-b455-3ad92783a099"],["uuid","b10d28f7-af18-4a67-9e78-2a6394516c59"]]]]]]],
+  [mutation])
+
+OVSDB_CHECK_POSITIVE([executing null mutation],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [mutation  0:
+row 0: no change
+row 1: no change
+row 2: no change
+])
+
+OVSDB_CHECK_POSITIVE([executing mutations on integers],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[["i", "+=", 1]],
+      [["i", "-=", 2]],
+      [["i", "*=", 3]],
+      [["i", "/=", 4]],
+      [["i", "%=", 2]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": 2}']]],
+  [mutation  0:
+row 0: {"i":1}
+row 1: {"i":2}
+row 2: {"i":3}
+
+mutation  1:
+row 0: {"i":-2}
+row 1: {"i":-1}
+row 2: {"i":0}
+
+mutation  2:
+row 0: no change
+row 1: {"i":3}
+row 2: {"i":6}
+
+mutation  3:
+row 0: no change
+row 1: {"i":0}
+row 2: {"i":0}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"i":0}
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([integer overflow detection],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[[["i", "+=", 9223372036854775807]],
+      [["i", "+=", -9223372036854775808]],
+      [["i", "-=", -9223372036854775808]],
+      [["i", "-=", 9223372036854775807]],
+      [["i", "*=", 3037000500]],
+      [["i", "/=", -1]],
+      [["i", "/=", 0]]]' \
+    '[{"i": 0},
+      {"i": 1},
+      {"i": -1},
+      {"i": 9223372036854775807},
+      {"i": -9223372036854775808},
+      {"i": 3037000500},
+      {"i": -3037000500}']]],
+  [mutation  0:
+row 0: {"i":9223372036854775807}
+row 1: range error: Result of "+=" operation is out of range.
+row 2: {"i":9223372036854775806}
+row 3: range error: Result of "+=" operation is out of range.
+row 4: {"i":-1}
+row 5: range error: Result of "+=" operation is out of range.
+row 6: {"i":9223372033817775307}
+
+mutation  1:
+row 0: {"i":-9223372036854775808}
+row 1: {"i":-9223372036854775807}
+row 2: range error: Result of "+=" operation is out of range.
+row 3: {"i":-1}
+row 4: range error: Result of "+=" operation is out of range.
+row 5: {"i":-9223372033817775308}
+row 6: range error: Result of "+=" operation is out of range.
+
+mutation  2:
+row 0: range error: Result of "-=" operation is out of range.
+row 1: range error: Result of "-=" operation is out of range.
+row 2: {"i":9223372036854775807}
+row 3: range error: Result of "-=" operation is out of range.
+row 4: {"i":0}
+row 5: range error: Result of "-=" operation is out of range.
+row 6: {"i":9223372033817775308}
+
+mutation  3:
+row 0: {"i":-9223372036854775807}
+row 1: {"i":-9223372036854775806}
+row 2: {"i":-9223372036854775808}
+row 3: {"i":0}
+row 4: range error: Result of "-=" operation is out of range.
+row 5: {"i":-9223372033817775307}
+row 6: range error: Result of "-=" operation is out of range.
+
+mutation  4:
+row 0: no change
+row 1: {"i":3037000500}
+row 2: {"i":-3037000500}
+row 3: range error: Result of "*=" operation is out of range.
+row 4: range error: Result of "*=" operation is out of range.
+row 5: range error: Result of "*=" operation is out of range.
+row 6: range error: Result of "*=" operation is out of range.
+
+mutation  5:
+row 0: no change
+row 1: {"i":-1}
+row 2: {"i":1}
+row 3: {"i":-9223372036854775807}
+row 4: range error: Result of "/=" operation is out of range.
+row 5: {"i":-3037000500}
+row 6: {"i":3037000500}
+
+mutation  6:
+row 0: domain error: Division by zero.
+row 1: domain error: Division by zero.
+row 2: domain error: Division by zero.
+row 3: domain error: Division by zero.
+row 4: domain error: Division by zero.
+row 5: domain error: Division by zero.
+row 6: domain error: Division by zero.
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on reals],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": "real"}}}' \
+    '[[["r", "+=", 0.5]],
+      [["r", "-=", 1.5]],
+      [["r", "*=", 2.5]],
+      [["r", "/=", 4]]]' \
+    '[{"r": 0},
+      {"r": -2.5},
+      {"r": 1.25}']]],
+  [mutation  0:
+row 0: {"r":0.5}
+row 1: {"r":-2}
+row 2: {"r":1.75}
+
+mutation  1:
+row 0: {"r":-1.5}
+row 1: {"r":-4}
+row 2: {"r":-0.25}
+
+mutation  2:
+row 0: no change
+row 1: {"r":-6.25}
+row 2: {"r":3.125}
+
+mutation  3:
+row 0: no change
+row 1: {"r":-0.625}
+row 2: {"r":0.3125}
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([real overflow detection],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": "real"}}}' \
+    '[[["r", "+=", 1.7976931348623157e+308]],
+      [["r", "-=", 1.7976931348623157e+308]],
+      [["r", "*=", 2]],
+      [["r", "/=", 4]],
+      [["r", "/=", 0.5]],
+      [["r", "/=", 0]]]' \
+    '[{"r": 0},
+      {"r": 1.7976931348623157e+308},
+      {"r": -1.7976931348623157e+308}']]],
+  [mutation  0:
+row 0: {"r":1.79769313486232e+308}
+row 1: range error: Result of "+=" operation is out of range.
+row 2: {"r":0}
+
+mutation  1:
+row 0: {"r":-1.79769313486232e+308}
+row 1: {"r":0}
+row 2: range error: Result of "-=" operation is out of range.
+
+mutation  2:
+row 0: no change
+row 1: range error: Result of "*=" operation is out of range.
+row 2: range error: Result of "*=" operation is out of range.
+
+mutation  3:
+row 0: no change
+row 1: {"r":4.49423283715579e+307}
+row 2: {"r":-4.49423283715579e+307}
+
+mutation  4:
+row 0: no change
+row 1: range error: Result of "/=" operation is out of range.
+row 2: range error: Result of "/=" operation is out of range.
+
+mutation  5:
+row 0: domain error: Division by zero.
+row 1: domain error: Division by zero.
+row 2: domain error: Division by zero.
+], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on integer sets],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[[["i", "+=", 1]],
+      [["i", "-=", 2]],
+      [["i", "*=", 3]],
+      [["i", "/=", 4]],
+      [["i", "%=", 2]],
+      [["i", "insert", ["set", [1]]]],
+      [["i", "insert", ["set", [2, 3]]]],
+      [["i", "delete", ["set", [1]]]],
+      [["i", "delete", ["set", [2, 3]]]]]' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [0, 1, 2]]}']]],
+  [[mutation  0:
+row 0: no change
+row 1: {"i":["set",[1]]}
+row 2: {"i":["set",[1,2]]}
+row 3: {"i":["set",[1,2,3]]}
+
+mutation  1:
+row 0: no change
+row 1: {"i":["set",[-2]]}
+row 2: {"i":["set",[-2,-1]]}
+row 3: {"i":["set",[-2,-1,0]]}
+
+mutation  2:
+row 0: no change
+row 1: no change
+row 2: {"i":["set",[0,3]]}
+row 3: {"i":["set",[0,3,6]]}
+
+mutation  3:
+row 0: no change
+row 1: no change
+row 2: constraint violation: Result of "/=" operation contains duplicates.
+row 3: constraint violation: Result of "/=" operation contains duplicates.
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: constraint violation: Result of "%=" operation contains duplicates.
+
+mutation  5:
+row 0: {"i":["set",[1]]}
+row 1: {"i":["set",[0,1]]}
+row 2: no change
+row 3: no change
+
+mutation  6:
+row 0: {"i":["set",[2,3]]}
+row 1: {"i":["set",[0,2,3]]}
+row 2: {"i":["set",[0,1,2,3]]}
+row 3: {"i":["set",[0,1,2,3]]}
+
+mutation  7:
+row 0: no change
+row 1: no change
+row 2: {"i":["set",[0]]}
+row 3: {"i":["set",[0,2]]}
+
+mutation  8:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: {"i":["set",[0,1]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on real sets],
+  [[execute-mutations \
+    '{"columns": {"r": {"type": {"key": "real", "min": 0, "max": "unlimited"}}}}' \
+    '[[["r", "+=", 0.5]],
+      [["r", "-=", 1.5]],
+      [["r", "*=", 2.5]],
+      [["r", "/=", 4]],
+      [["r", "*=", 0]],
+      [["r", "insert", ["set", [1.5]]]],
+      [["r", "insert", ["set", [3]]]],
+      [["r", "delete", ["set", [1.5, 3.5]]]],
+      [["r", "delete", ["set", [0.5, 1.5, 2.5]]]]]' \
+    '[{"r": ["set", []]},
+      {"r": ["set", [0.5]]},
+      {"r": ["set", [0.5, 1.5]]},
+      {"r": ["set", [0.5, 1.5, 2.5]]}']]],
+  [[mutation  0:
+row 0: no change
+row 1: {"r":["set",[1]]}
+row 2: {"r":["set",[1,2]]}
+row 3: {"r":["set",[1,2,3]]}
+
+mutation  1:
+row 0: no change
+row 1: {"r":["set",[-1]]}
+row 2: {"r":["set",[-1,0]]}
+row 3: {"r":["set",[-1,0,1]]}
+
+mutation  2:
+row 0: no change
+row 1: {"r":["set",[1.25]]}
+row 2: {"r":["set",[1.25,3.75]]}
+row 3: {"r":["set",[1.25,3.75,6.25]]}
+
+mutation  3:
+row 0: no change
+row 1: {"r":["set",[0.125]]}
+row 2: {"r":["set",[0.125,0.375]]}
+row 3: {"r":["set",[0.125,0.375,0.625]]}
+
+mutation  4:
+row 0: no change
+row 1: {"r":["set",[0]]}
+row 2: constraint violation: Result of "*=" operation contains duplicates.
+row 3: constraint violation: Result of "*=" operation contains duplicates.
+
+mutation  5:
+row 0: {"r":["set",[1.5]]}
+row 1: {"r":["set",[0.5,1.5]]}
+row 2: no change
+row 3: no change
+
+mutation  6:
+row 0: {"r":["set",[3]]}
+row 1: {"r":["set",[0.5,3]]}
+row 2: {"r":["set",[0.5,1.5,3]]}
+row 3: {"r":["set",[0.5,1.5,2.5,3]]}
+
+mutation  7:
+row 0: no change
+row 1: no change
+row 2: {"r":["set",[0.5]]}
+row 3: {"r":["set",[0.5,2.5]]}
+
+mutation  8:
+row 0: no change
+row 1: {"r":["set",[]]}
+row 2: {"r":["set",[]]}
+row 3: {"r":["set",[]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on boolean sets],
+  [[execute-mutations \
+    '{"columns": {"b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}}}}' \
+    '[[["b", "insert", ["set", [false]]]],
+      [["b", "insert", ["set", [true]]]],
+      [["b", "insert", ["set", [false, true]]]],
+      [["b", "delete", ["set", [false]]]],
+      [["b", "delete", ["set", [true]]]],
+      [["b", "delete", ["set", [true, false]]]]]' \
+    '[{"b": ["set", []]},
+      {"b": ["set", [false]]},
+      {"b": ["set", [true]]},
+      {"b": ["set", [false, true]]}']]],
+  [[mutation  0:
+row 0: {"b":["set",[false]]}
+row 1: no change
+row 2: {"b":["set",[false,true]]}
+row 3: no change
+
+mutation  1:
+row 0: {"b":["set",[true]]}
+row 1: {"b":["set",[false,true]]}
+row 2: no change
+row 3: no change
+
+mutation  2:
+row 0: {"b":["set",[false,true]]}
+row 1: {"b":["set",[false,true]]}
+row 2: {"b":["set",[false,true]]}
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: {"b":["set",[]]}
+row 2: no change
+row 3: {"b":["set",[true]]}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"b":["set",[]]}
+row 3: {"b":["set",[false]]}
+
+mutation  5:
+row 0: no change
+row 1: {"b":["set",[]]}
+row 2: {"b":["set",[]]}
+row 3: {"b":["set",[]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on string sets],
+  [[execute-mutations \
+    '{"columns": {"s": {"type": {"key": "string", "min": 0, "max": "unlimited"}}}}' \
+    '[[["s", "insert", ["set", ["a"]]]],
+      [["s", "insert", ["set", ["b"]]]],
+      [["s", "insert", ["set", ["c", "d"]]]],
+      [["s", "delete", ["set", ["a"]]]],
+      [["s", "delete", ["set", ["b"]]]],
+      [["s", "delete", ["set", ["c", "d"]]]]]' \
+    '[{"s": ["set", []]},
+      {"s": ["set", ["a"]]},
+      {"s": ["set", ["a", "b"]]},
+      {"s": ["set", ["a", "b", "c", "d"]]}']]],
+  [[mutation  0:
+row 0: {"s":["set",["a"]]}
+row 1: no change
+row 2: no change
+row 3: no change
+
+mutation  1:
+row 0: {"s":["set",["b"]]}
+row 1: {"s":["set",["a","b"]]}
+row 2: no change
+row 3: no change
+
+mutation  2:
+row 0: {"s":["set",["c","d"]]}
+row 1: {"s":["set",["a","c","d"]]}
+row 2: {"s":["set",["a","b","c","d"]]}
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: {"s":["set",[]]}
+row 2: {"s":["set",["b"]]}
+row 3: {"s":["set",["b","c","d"]]}
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"s":["set",["a"]]}
+row 3: {"s":["set",["a","c","d"]]}
+
+mutation  5:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: {"s":["set",["a","b"]]}
+]], [mutation])
+
+OVSDB_CHECK_POSITIVE([executing mutations on uuid sets],
+  [[execute-mutations \
+    '{"columns": {"u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '[[["u", "insert", ["set", [["uuid", "ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]]],
+      [["u", "insert", ["set", [["uuid", "a60fe7ff-317b-4568-9106-892b37445313"]]]]],
+      [["u", "insert", ["set", [["uuid", "2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]]],
+      [["u", "delete", ["set", [["uuid", "ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]]],
+      [["u", "delete", ["set", [["uuid", "a60fe7ff-317b-4568-9106-892b37445313"]]]]],
+      [["u", "delete", ["set", [["uuid", "2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]]]]' \
+    '[{"u": ["set", []]},
+      {"u": ["set", [["uuid", "ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]},
+      {"u": ["set", [["uuid", "a60fe7ff-317b-4568-9106-892b37445313"]]]},
+      {"u": ["set", [["uuid", "2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]}']]],
+  [[mutation  0:
+row 0: {"u":["set",[["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 1: no change
+row 2: {"u":["set",[["uuid","a60fe7ff-317b-4568-9106-892b37445313"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 3: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+
+mutation  1:
+row 0: {"u":["set",[["uuid","a60fe7ff-317b-4568-9106-892b37445313"]]]}
+row 1: {"u":["set",[["uuid","a60fe7ff-317b-4568-9106-892b37445313"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 2: no change
+row 3: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","a60fe7ff-317b-4568-9106-892b37445313"]]]}
+
+mutation  2:
+row 0: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"]]]}
+row 1: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","ddd9e79d-7782-414c-8b22-1046c60b6ec2"]]]}
+row 2: {"u":["set",[["uuid","2607d30e-e652-4927-9fec-8bbf1b60c7e9"],["uuid","a60fe7ff-317b-4568-9106-892b37445313"]]]}
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: {"u":["set",[]]}
+row 2: no change
+row 3: no change
+
+mutation  4:
+row 0: no change
+row 1: no change
+row 2: {"u":["set",[]]}
+row 3: no change
+
+mutation  5:
+row 0: no change
+row 1: no change
+row 2: no change
+row 3: {"u":["set",[]]}
+]], [mutation])
+
+
+OVSDB_CHECK_POSITIVE([executing mutations on integer maps],
+  [[execute-mutations \
+    '{"columns": {"i": {"type": {"key": "integer", "value": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[[["i", "insert", ["map", [[1, 2]]]]],
+      [["i", "insert", ["map", [[2, 4], [3, 5]]]]],
+      [["i", "delete", ["map", [[1, 2]]]]],
+      [["i", "delete", ["map", [[2, 3]]]]],
+      [["i", "delete", ["set", [1]]]],
+      [["i", "delete", ["set", [2, 3]]]]]' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[1, 2]]]},
+      {"i": ["map", [[1, 3], [2, 3]]]},
+      {"i": ["map", [[3, 5]]]}']]],
+  [[mutation  0:
+row 0: {"i":["map",[[1,2]]]}
+row 1: no change
+row 2: no change
+row 3: {"i":["map",[[1,2],[3,5]]]}
+
+mutation  1:
+row 0: {"i":["map",[[2,4],[3,5]]]}
+row 1: {"i":["map",[[1,2],[2,4],[3,5]]]}
+row 2: {"i":["map",[[1,3],[2,3],[3,5]]]}
+row 3: {"i":["map",[[2,4],[3,5]]]}
+
+mutation  2:
+row 0: no change
+row 1: {"i":["map",[]]}
+row 2: no change
+row 3: no change
+
+mutation  3:
+row 0: no change
+row 1: no change
+row 2: {"i":["map",[[1,3]]]}
+row 3: no change
+
+mutation  4:
+row 0: no change
+row 1: {"i":["map",[]]}
+row 2: {"i":["map",[[2,3]]]}
+row 3: no change
+
+mutation  5:
+row 0: no change
+row 1: no change
+row 2: {"i":["map",[[1,3]]]}
+row 3: {"i":["map",[]]}
+]], [mutation])
diff --git a/tests/ovsdb-query.at b/tests/ovsdb-query.at
new file mode 100644 (file)
index 0000000..2c2b648
--- /dev/null
@@ -0,0 +1,543 @@
+AT_BANNER([OVSDB -- queries])
+
+OVSDB_CHECK_POSITIVE([queries on scalars],
+  [[query \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]']],
+  [dnl
+query  0: 11111
+query  1: 1----
+query  2: 1-111
+query  3: 11---
+query  4: 1111-
+query  5: ---11
+query  6: ----1
+query  7: ---1-
+query  8: 11-11
+query  9: 1----
+query 10: 1-111
+query 11: 11---
+query 12: 1111-
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: 11111
+query 17: 1-1-1
+query 18: -1-1-
+query 19: -1-1-
+query 20: -1-1-
+query 21: 1----
+query 22: 1-111
+query 23: --1--
+query 24: 111-1
+query 25: 1----
+query 26: 1-111
+query 27: --1--],
+  [query])
+
+OVSDB_CHECK_POSITIVE([queries on sets],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}}}}' \
+    '[{"i": ["set", []]},
+      {"i": ["set", [0]]},
+      {"i": ["set", [1]]},
+      {"i": ["set", [0, 1]]},
+      {"i": ["set", [2]]},
+      {"i": ["set", [2, 0]]},
+      {"i": ["set", [2, 1]]},
+      {"i": ["set", [2, 1, 0]]}]' \
+    '[[],
+      [["i", "==", ["set", []]]],
+      [["i", "==", ["set", [0]]]],
+      [["i", "==", ["set", [1]]]],
+      [["i", "==", ["set", [0, 1]]]],
+      [["i", "==", ["set", [2]]]],
+      [["i", "==", ["set", [2, 0]]]],
+      [["i", "==", ["set", [2, 1]]]],
+      [["i", "==", ["set", [2, 1, 0]]]],
+      [["i", "!=", ["set", []]]],
+      [["i", "!=", ["set", [0]]]],
+      [["i", "!=", ["set", [1]]]],
+      [["i", "!=", ["set", [0, 1]]]],
+      [["i", "!=", ["set", [2]]]],
+      [["i", "!=", ["set", [2, 0]]]],
+      [["i", "!=", ["set", [2, 1]]]],
+      [["i", "!=", ["set", [2, 1, 0]]]],
+      [["i", "includes", ["set", []]]],
+      [["i", "includes", ["set", [0]]]],
+      [["i", "includes", ["set", [1]]]],
+      [["i", "includes", ["set", [0, 1]]]],
+      [["i", "includes", ["set", [2]]]],
+      [["i", "includes", ["set", [2, 0]]]],
+      [["i", "includes", ["set", [2, 1]]]],
+      [["i", "includes", ["set", [2, 1, 0]]]],
+      [["i", "excludes", ["set", []]]],
+      [["i", "excludes", ["set", [0]]]],
+      [["i", "excludes", ["set", [1]]]],
+      [["i", "excludes", ["set", [0, 1]]]],
+      [["i", "excludes", ["set", [2]]]],
+      [["i", "excludes", ["set", [2, 0]]]],
+      [["i", "excludes", ["set", [2, 1]]]],
+      [["i", "excludes", ["set", [2, 1, 0]]]]]']],
+  [dnl
+query  0: 11111 111
+query  1: 1---- ---
+query  2: -1--- ---
+query  3: --1-- ---
+query  4: ---1- ---
+query  5: ----1 ---
+query  6: ----- 1--
+query  7: ----- -1-
+query  8: ----- --1
+query  9: -1111 111
+query 10: 1-111 111
+query 11: 11-11 111
+query 12: 111-1 111
+query 13: 1111- 111
+query 14: 11111 -11
+query 15: 11111 1-1
+query 16: 11111 11-
+query 17: 11111 111
+query 18: -1-1- 1-1
+query 19: --11- -11
+query 20: ---1- --1
+query 21: ----1 111
+query 22: ----- 1-1
+query 23: ----- -11
+query 24: ----- --1
+query 25: 11111 111
+query 26: 1-1-1 -1-
+query 27: 11--1 1--
+query 28: 1---1 ---
+query 29: 1111- ---
+query 30: 1-1-- ---
+query 31: 11--- ---
+query 32: 1---- ---], [query])
+
+# This is the same as the "set" test except that it adds values,
+# all of which always match.
+OVSDB_CHECK_POSITIVE([queries on maps (1)],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]}]' \
+    '[[],
+      [["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']],
+  [dnl
+query  0: 11111 111
+query  1: 1---- ---
+query  2: -1--- ---
+query  3: --1-- ---
+query  4: ---1- ---
+query  5: ----1 ---
+query  6: ----- 1--
+query  7: ----- -1-
+query  8: ----- --1
+query  9: -1111 111
+query 10: 1-111 111
+query 11: 11-11 111
+query 12: 111-1 111
+query 13: 1111- 111
+query 14: 11111 -11
+query 15: 11111 1-1
+query 16: 11111 11-
+query 17: 11111 111
+query 18: -1-1- 1-1
+query 19: --11- -11
+query 20: ---1- --1
+query 21: ----1 111
+query 22: ----- 1-1
+query 23: ----- -11
+query 24: ----- --1
+query 25: 11111 111
+query 26: 1-1-1 -1-
+query 27: 11--1 1--
+query 28: 1---1 ---
+query 29: 1111- ---
+query 30: 1-1-- ---
+query 31: 11--- ---
+query 32: 1---- ---], [query])
+
+# This is the same as the "set" test except that it adds values,
+# and those values don't always match.
+OVSDB_CHECK_POSITIVE([queries on maps (2)],
+  [[query \
+    '{"columns": {"i": {"type": {"key": "integer",
+                                 "value": "boolean",
+                                 "min": 0,
+                                 "max": "unlimited"}}}}' \
+    '[{"i": ["map", []]},
+      {"i": ["map", [[0, true]]]},
+      {"i": ["map", [[0, false]]]},
+      {"i": ["map", [[1, false]]]},
+      {"i": ["map", [[1, true]]]},
+
+      {"i": ["map", [[0, true], [1, false]]]},
+      {"i": ["map", [[0, true], [1, true]]]},
+      {"i": ["map", [[2, true]]]},
+      {"i": ["map", [[2, false]]]},
+      {"i": ["map", [[2, true], [0, true]]]},
+
+      {"i": ["map", [[2, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false]]]},
+      {"i": ["map", [[2, true], [1, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, true]]]},
+      {"i": ["map", [[2, true], [1, false], [0, false]]]}]' \
+    '[[],
+      [["i", "==", ["map", []]]],
+      [["i", "==", ["map", [[0, true]]]]],
+      [["i", "==", ["map", [[1, false]]]]],
+      [["i", "==", ["map", [[0, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true]]]]],
+      [["i", "==", ["map", [[2, true], [0, true]]]]],
+      [["i", "==", ["map", [[2, true], [1, false]]]]],
+      [["i", "==", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "!=", ["map", []]]],
+      [["i", "!=", ["map", [[0, true]]]]],
+      [["i", "!=", ["map", [[1, false]]]]],
+      [["i", "!=", ["map", [[0, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true]]]]],
+      [["i", "!=", ["map", [[2, true], [0, true]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false]]]]],
+      [["i", "!=", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "includes", ["map", []]]],
+      [["i", "includes", ["map", [[0, true]]]]],
+      [["i", "includes", ["map", [[1, false]]]]],
+      [["i", "includes", ["map", [[0, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true]]]]],
+      [["i", "includes", ["map", [[2, true], [0, true]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false]]]]],
+      [["i", "includes", ["map", [[2, true], [1, false], [0, true]]]]],
+      [["i", "excludes", ["map", []]]],
+      [["i", "excludes", ["map", [[0, true]]]]],
+      [["i", "excludes", ["map", [[1, false]]]]],
+      [["i", "excludes", ["map", [[0, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [0, true]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false]]]]],
+      [["i", "excludes", ["map", [[2, true], [1, false], [0, true]]]]]]']],
+  [dnl
+query  0: 11111 11111 11111
+query  1: 1---- ----- -----
+query  2: -1--- ----- -----
+query  3: ---1- ----- -----
+query  4: ----- 1---- -----
+query  5: ----- --1-- -----
+query  6: ----- ----1 -----
+query  7: ----- ----- -1---
+query  8: ----- ----- ---1-
+query  9: -1111 11111 11111
+query 10: 1-111 11111 11111
+query 11: 111-1 11111 11111
+query 12: 11111 -1111 11111
+query 13: 11111 11-11 11111
+query 14: 11111 1111- 11111
+query 15: 11111 11111 1-111
+query 16: 11111 11111 111-1
+query 17: 11111 11111 11111
+query 18: -1--- 11--1 1--1-
+query 19: ---1- 1---- -1-11
+query 20: ----- 1---- ---1-
+query 21: ----- --1-1 -1111
+query 22: ----- ----1 ---1-
+query 23: ----- ----- -1-11
+query 24: ----- ----- ---1-
+query 25: 11111 11111 11111
+query 26: 1-111 --11- -11-1
+query 27: 111-1 -1111 1-1--
+query 28: 1-1-1 --11- --1--
+query 29: 11111 11-1- 1----
+query 30: 1-111 ---1- -----
+query 31: 111-1 -1-1- 1----
+query 32: 1-1-1 ---1- -----], [query])
+
+OVSDB_CHECK_POSITIVE([UUID-distinct queries on scalars],
+  [[query-distinct \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \
+    '["_uuid"]']],
+  [dnl
+query  0: abcde
+query  1: a----
+query  2: a-cde
+query  3: ab---
+query  4: abcd-
+query  5: ---de
+query  6: ----e
+query  7: ---d-
+query  8: ab-de
+query  9: a----
+query 10: a-cde
+query 11: ab---
+query 12: abcd-
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: abcde
+query 17: a-c-e
+query 18: -b-d-
+query 19: -b-d-
+query 20: -b-d-
+query 21: a----
+query 22: a-cde
+query 23: --c--
+query 24: abc-e
+query 25: a----
+query 26: a-cde
+query 27: --c--],
+  [query])
+
+OVSDB_CHECK_POSITIVE([Boolean-distinct queries on scalars],
+  [[query-distinct \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+    '[{"i": 0,
+       "r": 0.5,
+       "b": true,
+       "s": "a",
+       "u": ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]},
+      {"i": 1,
+       "r": 1.5,
+       "b": false,
+       "s": "b",
+       "u": ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]},
+      {"i": 2,
+       "r": 2.5,
+       "b": true,
+       "s": "c",
+       "u": ["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]},
+      {"i": 3,
+       "r": 3.5,
+       "b": false,
+       "s": "d",
+       "u": ["uuid", "62315898-64e0-40b9-b26f-ff74225303e6"]},
+      {"i": 4,
+       "r": 4.5,
+       "b": true,
+       "s": "e",
+       "u": ["uuid", "4a5127e2-0256-4a72-a7dc-6246213967c7"]}]' \
+    '[[],
+      [["i", "==", 0]],
+      [["i", "!=", 1]],
+      [["i", "<", 2]],
+      [["i", "<=", 3]],
+      [["i", ">", 2]],
+      [["i", ">=", 4]],
+      [["i", "includes", 3]],
+      [["i", "excludes", 2]],
+      [["r", "==", 0.5]],
+      [["r", "!=", 1.5]],
+      [["r", "<", 2.5]],
+      [["r", "<=", 3.5]],
+      [["r", ">", 4.5]],
+      [["r", ">=", 5.5]],
+      [["r", "includes", 1]],
+      [["r", "excludes", 3]],
+      [["b", "==", true]],
+      [["b", "!=", true]],
+      [["b", "includes", false]],
+      [["b", "excludes", true]],
+      [["s", "==", "a"]],
+      [["s", "!=", "b"]],
+      [["s", "includes", "c"]],
+      [["s", "excludes", "d"]],
+      [["u", "==", ["uuid", "b10d28f7-af18-4a67-9e78-2a6394516c59"]]],
+      [["u", "!=", ["uuid", "9179ca6d-6d65-400a-b455-3ad92783a099"]]],
+      [["u", "includes",["uuid", "ad0fa355-8b84-4a36-a4b5-b2c1bfd91758"]]]]' \
+    '["b"]']],
+  [dnl
+query  0: ababa
+query  1: a-a-a
+query  2: ababa
+query  3: ababa
+query  4: ababa
+query  5: ababa
+query  6: a-a-a
+query  7: -b-b-
+query  8: ababa
+query  9: a-a-a
+query 10: ababa
+query 11: ababa
+query 12: ababa
+query 13: -----
+query 14: -----
+query 15: -----
+query 16: ababa
+query 17: a-a-a
+query 18: -b-b-
+query 19: -b-b-
+query 20: -b-b-
+query 21: a-a-a
+query 22: ababa
+query 23: a-a-a
+query 24: ababa
+query 25: a-a-a
+query 26: ababa
+query 27: a-a-a],
+  [query])
+
+OVSDB_CHECK_NEGATIVE([parse colunn set containing bad name],
+  [[query-distinct \
+    '{"columns": {"i": {"type": "integer"}}}' \
+    '[{"i": 0}]' \
+    '[[]]' \
+    '["i", "bad"]']],
+  [bad is not a valid column name])
diff --git a/tests/ovsdb-row.at b/tests/ovsdb-row.at
new file mode 100644 (file)
index 0000000..e631f6f
--- /dev/null
@@ -0,0 +1,277 @@
+AT_BANNER([OVSDB -- rows])
+
+# Autoconf 2.63 has a bug that causes the double-quotes below to be
+# lost, so that the following tests fail, so we mark them as XFAIL for
+# Autoconf < 2.64.
+
+m4_define([RESERVED_COLUMNS], [["_uuid":["uuid","00000000-0000-0000-0000-000000000000"],"_version":["uuid","00000000-0000-0000-0000-000000000000"]]])
+
+OVSDB_CHECK_POSITIVE([row with one string column],
+  [[parse-rows \
+    '{"columns": {"name": {"type": "string"}}}' \
+    '{"name": "value"}' \
+    '{"name": ""}' \
+    '{"name": "longer string with spaces"}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"name":"value"}
+name
+{RESERVED_COLUMNS,"name":""}
+name
+{RESERVED_COLUMNS,"name":"longer string with spaces"}
+name
+{RESERVED_COLUMNS,"name":""}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one integer column],
+  [[parse-rows \
+    '{"columns": {"count": {"type": "integer"}}}' \
+    '{"count": 1}' \
+    '{"count": -1}' \
+    '{"count": 2e10}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"count":1}
+count
+{RESERVED_COLUMNS,"count":-1}
+count
+{RESERVED_COLUMNS,"count":20000000000}
+count
+{RESERVED_COLUMNS,"count":0}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one real column],
+  [[parse-rows \
+    '{"columns": {"cost": {"type": "real"}}}' \
+    '{"cost": 1.0}' \
+    '{"cost": -2.0}' \
+    '{"cost": 123000}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"cost":1}
+cost
+{RESERVED_COLUMNS,"cost":-2}
+cost
+{RESERVED_COLUMNS,"cost":123000}
+cost
+{RESERVED_COLUMNS,"cost":0}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one boolean column],
+  [[parse-rows \
+    '{"columns": {"feasible": {"type": "boolean"}}}' \
+    '{"feasible": true}' \
+    '{"feasible": false}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"feasible":true}
+feasible
+{RESERVED_COLUMNS,"feasible":false}
+feasible
+{RESERVED_COLUMNS,"feasible":false}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with one uuid column],
+  [[parse-rows \
+    '{"columns": {"ref": {"type": "uuid"}}}' \
+    '{"ref": ["uuid", "f707423d-bf5b-48b5-b6c0-797c900ba4b6"]}' \
+    '{"ref": ["uuid", "33583cc5-d2f4-43de-b1ca-8aac14071b51"]}' \
+    '{}']],
+  [{RESERVED_COLUMNS,"ref":[["uuid","f707423d-bf5b-48b5-b6c0-797c900ba4b6"]]}
+ref
+{RESERVED_COLUMNS,"ref":[["uuid","33583cc5-d2f4-43de-b1ca-8aac14071b51"]]}
+ref
+{RESERVED_COLUMNS,"ref":[["uuid","00000000-0000-0000-0000-000000000000"]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with set of 1 to 2 elements],
+  [[parse-rows \
+    '{"columns": {"myset": {"type": {"key": "integer", "min": 1, "max": 2}}}}' \
+    '{}']],
+  [{RESERVED_COLUMNS,["myset":["set",[0]]]}
+<none>])
+
+OVSDB_CHECK_POSITIVE([row with map of 1 to 2 elements],
+  [[parse-rows \
+    '{"columns": {"mymap": {"type": {"key": "integer", "value": "uuid", "min": 1, "max": 2}}}}' \
+    '{}']],
+  [{RESERVED_COLUMNS,["mymap":["map",[[0,["uuid","00000000-0000-0000-0000-000000000000"]]]]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row with several columns],
+  [[parse-rows \
+    '{"columns":
+        {"vswitch": {"type": "uuid"},
+         "name": {"type": "string"},
+         "datapath_id": {"type": {"key": "string", "min": 0}},
+         "hwaddr": {"type": "string"},
+         "mirrors": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "netflows": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "controller": {"type": {"key": "uuid", "min": 0}},
+         "listeners": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+         "snoops": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '{"vswitch": ["uuid", "1a5c7280-0d4c-4e34-9ec7-c772339f7774"],
+      "name": "br0",
+      "datapath_id": ["set", ["000ae4256bb0"]],
+      "hwaddr": "00:0a:e4:25:6b:b0"}' \
+    '{}']],
+ [{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",["000ae4256bb0"]],"hwaddr":"00:0a:e4:25:6b:b0","listeners":["set",[]],"mirrors":["set",[]],"name":"br0","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","1a5c7280-0d4c-4e34-9ec7-c772339f7774"]]}
+datapath_id, hwaddr, name, vswitch
+{RESERVED_COLUMNS,["controller":["set",[]],"datapath_id":["set",[]],"hwaddr":"","listeners":["set",[]],"mirrors":["set",[]],"name":"","netflows":["set",[]],"snoops":["set",[]],"vswitch":["uuid","00000000-0000-0000-0000-000000000000"]]}
+<none>], [], [2.64])
+
+OVSDB_CHECK_POSITIVE([row hashing (scalars)],
+  [[compare-rows \
+    '{"columns":
+        {"i": {"type": "integer"},
+         "r": {"type": "real"},
+         "b": {"type": "boolean"},
+        "s": {"type": "string"},
+         "u": {"type": "uuid"}}}' \
+     '["null", {}]' \
+     '["i1", {"i": 1}]' \
+     '["i2", {"i": 2}]' \
+     '["i4", {"i": 4}]' \
+     '["i8", {"i": 8}]' \
+     '["i16", {"i": 16}]' \
+     '["i32", {"i": 32}]' \
+     '["i64", {"i": 64}]' \
+     '["i128", {"i": 128}]' \
+     '["i256", {"i": 256}]' \
+     '["null2", {"r": -0}]' \
+     '["r123", {"r": 123}]' \
+     '["r0.0625", {"r": 0.0625}]' \
+     '["r0.125", {"r": 0.125}]' \
+     '["r0.25", {"r": 0.25}]' \
+     '["r0.5", {"r": 0.5}]' \
+     '["r1", {"r": 1}]' \
+     '["r2", {"r": 2}]' \
+     '["r4", {"r": 4}]' \
+     '["r8", {"r": 8}]' \
+     '["r16", {"r": 16}]' \
+     '["r32", {"r": 32}]' \
+     '["null3", {"b": false}]' \
+     '["b1", {"b": true}]' \
+     '["null4", {"s": ""}]' \
+     '["s0", {"s": "a"}]' \
+     '["s1", {"s": "b"}]' \
+     '["s2", {"s": "c"}]' \
+     '["s3", {"s": "d"}]' \
+     '["s4", {"s": "e"}]' \
+     '["s5", {"s": "f"}]' \
+     '["s6", {"s": "g"}]' \
+     '["s7", {"s": "h"}]' \
+     '["s8", {"s": "i"}]' \
+     '["s9", {"s": "j"}]' \
+     '["null5", {"u": ["uuid","00000000-0000-0000-0000-000000000000"]}]' \
+     '["u1", {"u": ["uuid","10000000-0000-0000-0000-000000000000"]}]' \
+     '["u2", {"u": ["uuid","01000000-0000-0000-0000-000000000000"]}]' \
+     '["u3", {"u": ["uuid","00100000-0000-0000-0000-000000000000"]}]' \
+     '["u4", {"u": ["uuid","00010000-0000-0000-0000-000000000000"]}]' \
+     '["u5", {"u": ["uuid","00001000-0000-0000-0000-000000000000"]}]' \
+     '["u6", {"u": ["uuid","00000100-0000-0000-0000-000000000000"]}]' \
+     '["u7", {"u": ["uuid","00000010-0000-0000-0000-000000000000"]}]' \
+     '["u8", {"u": ["uuid","00000001-0000-0000-0000-000000000000"]}]' \
+     '["null6", {"u": ["uuid","00000000-c6db-4d22-970f-b41fabd20c4b"]}]']],
+  [[null == null2
+null == null3
+null == null4
+null == null5
+hash(null) == hash(null6)
+null2 == null3
+null2 == null4
+null2 == null5
+hash(null2) == hash(null6)
+null3 == null4
+null3 == null5
+hash(null3) == hash(null6)
+null4 == null5
+hash(null4) == hash(null6)
+hash(null5) == hash(null6)]])
+
+OVSDB_CHECK_POSITIVE([row hashing (sets)],
+  [[compare-rows \
+    '{"columns":
+        {"i": {"type": {"key": "integer", "min": 0, "max": "unlimited"}},
+         "r": {"type": {"key": "real", "min": 0, "max": "unlimited"}},
+         "b": {"type": {"key": "boolean", "min": 0, "max": "unlimited"}},
+        "s": {"type": {"key": "string", "min": 0, "max": "unlimited"}},
+         "u": {"type": {"key": "uuid", "min": 0, "max": "unlimited"}}}}' \
+    '["null0", {"i": ["set", []]}]' \
+    '["i0", {"i": ["set", [0]]}]' \
+    '["i01", {"i": ["set", [0, 1]]}]' \
+    '["i012", {"i": ["set", [0, 1, 2]]}]' \
+    '["i021", {"i": ["set", [0, 2, 1]]}]' \
+    '["i201", {"i": ["set", [2, 0, 1]]}]' \
+    '["i102", {"i": ["set", [1, 0, 2]]}]' \
+    '["i120", {"i": ["set", [1, 2, 0]]}]' \
+    '["i210", {"i": ["set", [2, 1, 0]]}]' \
+    '["r0", {"r": ["set", [0]]}]' \
+    '["r01", {"r": ["set", [0, 1]]}]' \
+    '["r012", {"r": ["set", [0, 1, 2]]}]' \
+    '["r201", {"r": ["set", [2, 0, 1]]}]' \
+    '["null1", {"b": ["set", []]}]' \
+    '["b0", {"b": ["set", [false]]}]' \
+    '["b1", {"b": ["set", [true]]}]' \
+    '["b01", {"b": ["set", [false, true]]}]' \
+    '["b10", {"b": ["set", [true, false]]}]' \
+    '["null2", {"s": ["set", []]}]' \
+    '["sa", {"s": ["set", ["a"]]}]' \
+    '["sb", {"s": ["set", ["b"]]}]' \
+    '["sab", {"s": ["set", ["a", "b"]]}]' \
+    '["sba", {"s": ["set", ["b", "a"]]}]']],
+  [[null0 == null1
+null0 == null2
+i012 == i021
+i012 == i201
+i012 == i102
+i012 == i120
+i012 == i210
+i021 == i201
+i021 == i102
+i021 == i120
+i021 == i210
+i201 == i102
+i201 == i120
+i201 == i210
+i102 == i120
+i102 == i210
+i120 == i210
+r012 == r201
+null1 == null2
+b01 == b10
+sab == sba]])
+
+OVSDB_CHECK_POSITIVE([row hashing (maps)],
+  [[compare-rows \
+    '{"columns":
+        {"ii": {"type": {"key": "integer", "value": "integer", 
+                         "min": 0, "max": "unlimited"}},
+         "rr": {"type": {"key": "real", "value": "real",
+                         "min": 0, "max": "unlimited"}},
+         "bb": {"type": {"key": "boolean", "value": "boolean",
+                         "min": 0, "max": "unlimited"}},
+        "ss": {"type": {"key": "string", "value": "string",
+                         "min": 0, "max": "unlimited"}}}}' \
+    '["null", {}]' \
+    '["ii0", {"ii": ["map", [[0, 0]]]}]' \
+    '["ii1", {"ii": ["map", [[0, 1]]]}]' \
+    '["ii00", {"ii": ["map", [[0, 0], [1, 0]]]}]' \
+    '["ii01", {"ii": ["map", [[0, 0], [1, 1]]]}]' \
+    '["ii10", {"ii": ["map", [[0, 1], [1, 0]]]}]' \
+    '["ii11", {"ii": ["map", [[0, 1], [1, 1]]]}]' \
+    '["rr0", {"rr": ["map", [[0, 0]]]}]' \
+    '["rr0", {"rr": ["map", [[0, 1]]]}]' \
+    '["rr00", {"rr": ["map", [[0, 0], [1, 0]]]}]' \
+    '["rr01", {"rr": ["map", [[0, 0], [1, 1]]]}]' \
+    '["rr10", {"rr": ["map", [[0, 1], [1, 0]]]}]' \
+    '["rr11", {"rr": ["map", [[0, 1], [1, 1]]]}]' \
+    '["bb0", {"bb": ["map", [[false, false]]]}]' \
+    '["bb1", {"bb": ["map", [[false, true]]]}]' \
+    '["bb00", {"bb": ["map", [[false, false], [true, false]]]}]' \
+    '["bb01", {"bb": ["map", [[false, false], [true, true]]]}]' \
+    '["bb10", {"bb": ["map", [[false, true], [true, false]]]}]' \
+    '["bb11", {"bb": ["map", [[false, true], [true, true]]]}]' \
+    '["ss0", {"ss": ["map", [["a", "a"]]]}]' \
+    '["ss1", {"ss": ["map", [["a", "b"]]]}]' \
+    '["ss00", {"ss": ["map", [["a", "a"], ["b", "a"]]]}]' \
+    '["ss01", {"ss": ["map", [["a", "a"], ["b", "b"]]]}]' \
+    '["ss10", {"ss": ["map", [["a", "b"], ["b", "a"]]]}]' \
+    '["ss11", {"ss": ["map", [["a", "b"], ["b", "b"]]]}]'; echo
+]], [[]])
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
new file mode 100644 (file)
index 0000000..0b02189
--- /dev/null
@@ -0,0 +1,111 @@
+AT_BANNER([OVSDB -- ovsdb-server transactions (Unix sockets)])
+
+m4_define([OVSDB_SERVER_SHUTDOWN], 
+  [cp pid savepid
+   AT_CHECK([ovs-appctl -t $PWD/unixctl -e exit], [0], [ignore], [ignore])
+   OVS_WAIT_WHILE([kill -0 `cat savepid`], [kill `cat savepid`])])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA, starts an ovsdb-server on
+# that database, and runs each of the TRANSACTIONS (which should be a
+# quoted list of quoted strings) against it with ovsdb-client one at a
+# time.
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server positive $5])
+   AT_DATA([schema], [$2
+])
+   AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --remote=punix:socket --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+   m4_foreach([txn], [$3], 
+     [AT_CHECK([ovsdb-client transact unix:socket 'txn'], [0], [stdout], [ignore],
+     [test ! -e pid || kill `cat pid`])
+cat stdout >> output
+])
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$4], [ignore],
+            [test ! -e pid || kill `cat pid`])
+   OVSDB_SERVER_SHUTDOWN
+   AT_CLEANUP])
+
+EXECUTION_EXAMPLES
+\f
+AT_SETUP([--remote=db: implementation])
+AT_KEYWORDS([ovsdb server positive])
+AT_DATA([schema],
+  [[{"name": "mydb",
+     "tables": {
+       "Manager": {
+         "columns": {
+           "manager": {"type": "string"}}}}}
+]])
+AT_CHECK([ovsdb-tool create db schema], [0], [ignore], [ignore])
+AT_CHECK(
+  [[ovsdb-tool transact db \
+     '[{"op": "insert",
+        "table": "Manager",
+        "row": {"manager": "punix:socket"}}]']], [0], [ignore], [ignore])
+AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --remote=db:Manager,manager --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+AT_CHECK(
+  [[ovsdb-client transact unix:socket \
+     '[{"op": "select",
+        "table": "Manager",
+        "where": [],
+        "columns": ["manager"]}]']], 
+  [0], [stdout], [ignore], [test ! -e pid || kill `cat pid`])
+AT_CHECK(
+  [perl $srcdir/uuidfilt.pl stdout], 
+  [0], 
+  [[[{"rows":[{"manager":"punix:socket"}]}]
+]], 
+  [ignore], 
+  [test ! -e pid || kill `cat pid`])
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+\f
+AT_BANNER([OVSDB -- ovsdb-server transactions (SSL sockets)])
+
+# OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])
+#
+# Creates a database with the given SCHEMA, starts an ovsdb-server on
+# that database, and runs each of the TRANSACTIONS (which should be a
+# quoted list of quoted strings) against it with ovsdb-client one at a
+# time.
+#
+# Checks that the overall output is OUTPUT, but UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.
+m4_define([OVSDB_CHECK_EXECUTION], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb server positive ssl $5])
+   AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+   AT_SKIP_IF([test "x$RANDOM" = x])
+   AT_DATA([schema], [$2
+])
+   SSL_PORT=`expr 32767 + \( $RANDOM % 32767 \)`
+   PKIDIR=$abs_top_srcdir/tests
+   AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+   AT_CHECK([ovsdb-server --detach --pidfile=$PWD/pid --private-key=$PKIDIR/testpki-privkey2.pem --certificate=$PKIDIR/testpki-cert2.pem --ca-cert=$PKIDIR/testpki-cacert.pem --remote=pssl:$SSL_PORT:127.0.0.1 --unixctl=$PWD/unixctl db], [0], [ignore], [ignore])
+   m4_foreach([txn], [$3], 
+     [AT_CHECK([ovsdb-client --private-key=$PKIDIR/testpki-privkey.pem --certificate=$PKIDIR/testpki-cert.pem --ca-cert=$PKIDIR/testpki-cacert.pem transact ssl:127.0.0.1:$SSL_PORT 'txn'], [0], [stdout], [ignore],
+     [test ! -e pid || kill `cat pid`])
+cat stdout >> output
+])
+   AT_CHECK([perl $srcdir/uuidfilt.pl output], [0], [$4], [ignore],
+            [test ! -e pid || kill `cat pid`])
+   OVSDB_SERVER_SHUTDOWN
+   AT_CLEANUP])
+
+EXECUTION_EXAMPLES
diff --git a/tests/ovsdb-table.at b/tests/ovsdb-table.at
new file mode 100644 (file)
index 0000000..ebc5992
--- /dev/null
@@ -0,0 +1,31 @@
+AT_BANNER([OVSDB -- tables])
+
+OVSDB_CHECK_POSITIVE([table with one column],
+  [[parse-table mytable '{"columns": {"name": {"type": "string"}}}']],
+  [[{"columns":{"name":{"type":"string"}}}]])
+
+OVSDB_CHECK_POSITIVE([immutable table with one column],
+  [[parse-table mytable \
+    '{"columns": {"name": {"type": "string"}},
+      "mutable": false}']],
+  [[{"columns":{"name":{"type":"string"}},"mutable":false}]])
+
+OVSDB_CHECK_POSITIVE([table with comment],
+  [[parse-table mytable \
+    '{"columns": {"name": {"type": "string"}},
+      "comment": "description of table"}']],
+  [[{"columns":{"name":{"type":"string"}},"comment":"description of table"}]])
+
+OVSDB_CHECK_NEGATIVE([column names may not begin with _],
+  [[parse-table mytable \
+    '{"columns": {"_column": {"type": "integer"}}}']],
+  [[names beginning with "_" are reserved]],
+  [table])
+
+OVSDB_CHECK_NEGATIVE([table must have at least one column (1)],
+  [[parse-table mytable '{}']],
+  [[Parsing table schema for table mytable failed: Required 'columns' member is missing.]])
+
+OVSDB_CHECK_NEGATIVE([table must have at least one column (2)],
+  [[parse-table mytable '{"columns": {}}']],
+  [[table must have at least one column]])
diff --git a/tests/ovsdb-transaction.at b/tests/ovsdb-transaction.at
new file mode 100644 (file)
index 0000000..8abc5d3
--- /dev/null
@@ -0,0 +1,410 @@
+AT_BANNER([OVSDB -- transactions])
+
+OVSDB_CHECK_POSITIVE([empty table, empty transaction],
+  [[transact \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+print:
+commit:
+print:
+abort:
+print:])
+
+OVSDB_CHECK_POSITIVE([nonempty table, empty transaction],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+print:
+1: i=2, j=3
+2: i=2, j=3
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3])
+
+OVSDB_CHECK_POSITIVE([insert, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "1", "2"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 1 2:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "1", "2"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 1 2:
+print:
+1: i=2, j=3
+2: i=2, j=3
+3: i=1, j=2
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "2", "5", "-1"]' \
+    '["modify", "1", "-1", "4"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 2 5 -1:
+modify 1 -1 4:
+print:
+1: i=2, j=4
+2: i=5, j=3
+commit:
+print:
+1: i=2, j=4
+2: i=5, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "2", "5", "-1"]' \
+    '["modify", "1", "-1", "4"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 2 5 -1:
+modify 1 -1 4:
+print:
+1: i=2, j=4
+2: i=5, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+delete 1:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+delete 1:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "1", "5", "6"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 1 5 6:
+delete 1:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([modify, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["modify", "1", "5", "6"]' \
+    '["delete", "1"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+modify 1 5 6:
+delete 1:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+delete 3:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+delete 3:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+
+OVSDB_CHECK_POSITIVE([insert, modify, delete, commit],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["modify", "3", "7", "8"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["commit"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+modify 3 7 8:
+delete 3:
+print:
+2: i=2, j=3
+commit:
+print:
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([insert, modify, delete, abort],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["insert", "2", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["insert", "3", "5", "6"]' \
+    '["delete", "1"]' \
+    '["modify", "3", "7", "8"]' \
+    '["delete", "3"]' \
+    '["print"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+insert 2 2 3:
+commit:
+print:
+1: i=2, j=3
+2: i=2, j=3
+insert 3 5 6:
+delete 1:
+modify 3 7 8:
+delete 3:
+print:
+2: i=2, j=3
+abort:
+print:
+1: i=2, j=3
+2: i=2, j=3],
+  [transaction])
+
+OVSDB_CHECK_POSITIVE([deletes are aborted cleanly],
+  [[transact \
+    '["insert", "1", "2", "3"]' \
+    '["commit"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["abort"]' \
+    '["print"]' \
+    '["delete", "1"]' \
+    '["abort"]' \
+    '["print"]']],
+  [dnl
+insert 1 2 3:
+commit:
+print:
+1: i=2, j=3
+delete 1:
+abort:
+print:
+1: i=2, j=3
+delete 1:
+abort:
+print:
+1: i=2, j=3],
+  [transaction])
+
diff --git a/tests/ovsdb-trigger.at b/tests/ovsdb-trigger.at
new file mode 100644 (file)
index 0000000..97330fb
--- /dev/null
@@ -0,0 +1,173 @@
+AT_BANNER([OVSDB -- triggers])
+
+# This is like OVSDB_CHECK_POSITIVE, except that UUIDs in the output
+# are replaced by markers of the form <N> where N is a number.  The
+# first unique UUID is replaced by <0>, the next by <1>, and so on.
+# If a given UUID appears more than once it is always replaced by the
+# same marker.
+m4_define([OVSDB_CHECK_TRIGGER], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb execute execution trigger positive $4])
+   AT_CHECK([test-ovsdb trigger $2], [0], [stdout], [])
+   AT_CHECK([perl $srcdir/uuidfilt.pl stdout], [0], [$3])
+   AT_CLEANUP])
+
+OVSDB_CHECK_TRIGGER([trigger fires immediately],
+  ['ORDINAL_SCHEMA' [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1}]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{},{"uuid":["uuid","<2>"]}]
+]])
+
+OVSDB_CHECK_TRIGGER([trigger times out],
+  ['ORDINAL_SCHEMA' [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]' \
+    '["advance", 10]']],
+  [[t=0: new trigger 0
+t=10: trigger 0 (delayed): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]},{"details":"\"wait\" timed out after 10 ms","error":"timed out"}]
+]])
+
+OVSDB_CHECK_TRIGGER([trigger fires after delay],
+  ['ORDINAL_SCHEMA' [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]}]' \
+    '["advance", 5]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 1 (delayed): [{}]
+]])
+
+OVSDB_CHECK_TRIGGER([delayed trigger modifies database],
+  ['ORDINAL_SCHEMA' [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "<", 2]]}]' \
+    '["advance", 5]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]' \
+    '["advance", 5]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": []}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=10: trigger 2 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 1 (delayed): [{},{"count":2}]
+t=15: trigger 3 (immediate): [{"rows":[{"_uuid":["uuid","<2>"],"_version":["uuid","<3>"],"name":"two","number":2}]}]
+]])
+
+OVSDB_CHECK_TRIGGER([one delayed trigger wakes up another],
+  ['ORDINAL_SCHEMA' [\
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}}]' \
+    '["advance", 5]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "==", 2]]},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 3, "name": "three"}}]' \
+    '[{"op": "wait",
+       "timeout": 10,
+       "table": "ordinals",
+       "where": [],
+       "columns": ["name", "number"],
+       "until": "==",
+       "rows": [{"name": "zero", "number": 0},
+                {"name": "one", "number": 1},
+                {"name": "two", "number": 2}]},
+      {"op": "delete",
+       "table": "ordinals",
+       "where": [["number", "<", 2]]}]' \
+    '["advance", 5]' \
+    '[{"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]' \
+    '["advance", 5]' \
+    '[{"op": "select",
+       "table": "ordinals",
+       "where": []}]']],
+  [[t=0: trigger 0 (immediate): [{"uuid":["uuid","<0>"]},{"uuid":["uuid","<1>"]}]
+t=5: new trigger 1
+t=5: new trigger 2
+t=10: trigger 3 (immediate): [{"uuid":["uuid","<2>"]}]
+t=10: trigger 2 (delayed): [{},{"count":2}]
+t=15: trigger 1 (delayed): [{},{"count":1},{"uuid":["uuid","<3>"]}]
+t=15: trigger 4 (immediate): [{"rows":[{"_uuid":["uuid","<3>"],"_version":["uuid","<4>"],"name":"three","number":3}]}]
+]])
+
diff --git a/tests/ovsdb-types.at b/tests/ovsdb-types.at
new file mode 100644 (file)
index 0000000..ebaffff
--- /dev/null
@@ -0,0 +1,93 @@
+AT_BANNER([OVSDB -- atomic types])
+
+OVSDB_CHECK_POSITIVE([integer], 
+  [[parse-atomic-type '["integer"]' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([real], 
+  [[parse-atomic-type '["real"]' ]], ["real"])
+OVSDB_CHECK_POSITIVE([boolean], 
+  [[parse-atomic-type '["boolean"]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([string], 
+  [[parse-atomic-type '["string"]' ]], ["string"])
+OVSDB_CHECK_POSITIVE([uuid], 
+  [[parse-atomic-type '["uuid"]' ]], ["uuid"])
+OVSDB_CHECK_NEGATIVE([void is not a valid atomic-type],
+  [[parse-atomic-type '["void"]' ]], ["void" is not an atomic-type])
+
+AT_BANNER([OVSDB -- simple types])
+
+OVSDB_CHECK_POSITIVE([simple integer], 
+  [[parse-type '["integer"]' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([simple real], 
+  [[parse-type '["real"]' ]], ["real"])
+OVSDB_CHECK_POSITIVE([simple boolean], 
+  [[parse-type '["boolean"]' ]], ["boolean"])
+OVSDB_CHECK_POSITIVE([simple string], 
+  [[parse-type '["string"]' ]], ["string"])
+OVSDB_CHECK_POSITIVE([simple uuid], 
+  [[parse-type '["uuid"]' ]], ["uuid"])
+OVSDB_CHECK_POSITIVE([integer in object],
+  [[parse-type '{"key": "integer"}' ]], ["integer"])
+OVSDB_CHECK_POSITIVE([real in object with explicit min and max],
+  [[parse-type '{"key": "real", "min": 1, "max": 1}' ]], ["real"])
+
+OVSDB_CHECK_NEGATIVE([key type is required],
+  [[parse-type '{}' ]], [Required 'key' member is missing.])
+OVSDB_CHECK_NEGATIVE([void is not a valid type],
+  [[parse-type '["void"]' ]], ["void" is not an atomic-type])
+
+AT_BANNER([OVSDB -- set types])
+
+OVSDB_CHECK_POSITIVE([optional boolean],
+  [[parse-type '{"key": "boolean", "min": 0}' ]], 
+  [[{"key":"boolean","min":0}]],
+  [set])
+OVSDB_CHECK_POSITIVE([set of 1 to 3 uuids],
+  [[parse-type '{"key": "uuid", "min": 1, "max": 3}' ]], 
+  [[{"key":"uuid","max":3}]])
+OVSDB_CHECK_POSITIVE([set of 0 to 3 strings],
+  [[parse-type '{"key": "string", "min": 0, "max": 3}' ]], 
+  [[{"key":"string","max":3,"min":0}]])
+OVSDB_CHECK_POSITIVE([set of 0 or more integers],
+  [[parse-type '{"key": "integer", "min": 0, "max": "unlimited"}']],
+  [[{"key":"integer","max":"unlimited","min":0}]])
+OVSDB_CHECK_POSITIVE([set of 1 or more reals],
+  [[parse-type '{"key": "real", "min": 1, "max": "unlimited"}']],
+  [[{"key":"real","max":"unlimited"}]])
+
+OVSDB_CHECK_NEGATIVE([set max cannot be less than min],
+  [[parse-type '{"key": "real", "min": 5, "max": 3}' ]],
+  [ovsdb type fails constraint checks])
+OVSDB_CHECK_NEGATIVE([set max cannot be negative],
+  [[parse-type '{"key": "real", "max": -1}' ]],
+  [bad min or max value])
+OVSDB_CHECK_NEGATIVE([set min cannot be negative],
+  [[parse-type '{"key": "real", "min": -1}' ]],
+  [bad min or max value])
+OVSDB_CHECK_NEGATIVE([set min cannot be greater than one],
+  [[parse-type '{"key": "real", "min": 10, "max": "unlimited"}']],
+  [ovsdb type fails constraint checks])
+
+AT_BANNER([OVSDB -- map types])
+
+OVSDB_CHECK_POSITIVE([map of 1 integer to boolean],
+ [[parse-type '{"key": "integer", "value": "boolean"}' ]],
+ [[{"key":"integer","value":"boolean"}]])
+OVSDB_CHECK_POSITIVE([map of 1 boolean to integer, explicit min and max],
+ [[parse-type '{"key": "boolean", "value": "integer", "min": 1, "max": 1}' ]],
+ [[{"key":"boolean","value":"integer"}]])
+OVSDB_CHECK_POSITIVE([map of 1 to 5 uuid to real],
+ [[parse-type '{"key": "uuid", "value": "real", "min": 1, "max": 5}' ]],
+ [[{"key":"uuid","max":5,"value":"real"}]])
+OVSDB_CHECK_POSITIVE([map of 0 to 10 string to uuid],
+ [[parse-type '{"key": "string", "value": "uuid", "min": 0, "max": 10}' ]],
+ [[{"key":"string","max":10,"min":0,"value":"uuid"}]])
+OVSDB_CHECK_POSITIVE([map of 1 to 20 real to string],
+ [[parse-type '{"key": "real", "value": "string", "min": 1, "max": 20}' ]],
+ [[{"key":"real","max":20,"value":"string"}]])
+OVSDB_CHECK_POSITIVE([map of 0 or more string to real],
+ [[parse-type '{"key": "string", "value": "real", "min": 0, "max": "unlimited"}' ]],
+ [[{"key":"string","max":"unlimited","min":0,"value":"real"}]])
+
+OVSDB_CHECK_NEGATIVE([map key type is required],
+ [[parse-type '{"value": "integer"}' ]],
+ [Required 'key' member is missing.])
diff --git a/tests/ovsdb.at b/tests/ovsdb.at
new file mode 100644 (file)
index 0000000..d10bedd
--- /dev/null
@@ -0,0 +1,53 @@
+# OVSDB_CHECK_POSITIVE(TITLE, TEST-OVSDB-ARGS, OUTPUT, [KEYWORDS], [PREREQ])
+#
+# Runs "test-ovsdb TEST-OVSDB-ARGS" and checks that it exits with
+# status 0 and prints OUTPUT on stdout.
+#
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.  If
+# PREREQ is specified then the test is skipped if the Autoconf version
+# is less than PREREQ.
+m4_define([OVSDB_CHECK_POSITIVE], 
+  [AT_SETUP([$1])
+   m4_if([$5], [], [], 
+         [AT_XFAIL_IF([m4_version_prereq([$5], [false], [true])])])
+   AT_KEYWORDS([ovsdb positive $4])
+   AT_CHECK([test-ovsdb $2], [0], [$3
+], [])
+   AT_CLEANUP])
+
+# OVSDB_CHECK_NEGATIVE(TITLE, TEST-OVSDB-ARGS, OUTPUT, [KEYWORDS], [PREREQ])
+#
+# Runs "test-ovsdb TEST-OVSDB-ARGS" and checks that it exits with
+# status 1 and that its output on stdout contains substring OUTPUT.
+# TITLE is provided to AT_SETUP and KEYWORDS to AT_KEYWORDS.  
+m4_define([OVSDB_CHECK_NEGATIVE], 
+  [AT_SETUP([$1])
+   AT_KEYWORDS([ovsdb negative $4])
+   AT_CHECK([test-ovsdb $2], [1], [], [stderr])
+   m4_assert(m4_len([$3]))
+   AT_CHECK(
+     [if grep -F -e "AS_ESCAPE([$3])" stderr
+      then
+        :
+      else
+        exit 99
+      fi], 
+            [0], [ignore], [ignore])
+   AT_CLEANUP])
+
+m4_include([tests/ovsdb-log.at])
+m4_include([tests/ovsdb-types.at])
+m4_include([tests/ovsdb-data.at])
+m4_include([tests/ovsdb-column.at])
+m4_include([tests/ovsdb-table.at])
+m4_include([tests/ovsdb-row.at])
+m4_include([tests/ovsdb-condition.at])
+m4_include([tests/ovsdb-mutation.at])
+m4_include([tests/ovsdb-query.at])
+m4_include([tests/ovsdb-transaction.at])
+m4_include([tests/ovsdb-execution.at])
+m4_include([tests/ovsdb-trigger.at])
+m4_include([tests/ovsdb-file.at])
+m4_include([tests/ovsdb-server.at])
+m4_include([tests/ovsdb-monitor.at])
+m4_include([tests/ovsdb-idl.at])
diff --git a/tests/reconnect.at b/tests/reconnect.at
new file mode 100644 (file)
index 0000000..7c1cc6a
--- /dev/null
@@ -0,0 +1,1111 @@
+AT_BANNER([reconnect library])
+
+######################################################################
+AT_SETUP([nothing happens if not enabled])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [run
+timeout
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+run
+timeout
+  no timeout
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([quick connect, idle disconnect])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# Connection succeeds.
+run
+connected
+
+# Send inactivity probe.
+timeout
+run
+
+# Idle timeout kills connection.
+timeout
+run
+disconnected
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# Connection succeeds.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Send inactivity probe.
+timeout
+  advance 5000 ms
+
+### t=6000 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+
+# Idle timeout kills connection.
+timeout
+  advance 5000 ms
+
+### t=11000 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([slow connect, idle disconnect])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# Start connecting.
+run
+connecting
+
+# Connect after 500 ms.
+advance 500
+run
+connected
+
+# Send inactivity probe.
+timeout
+run
+
+# Idle timeout kills connection.
+timeout
+run
+disconnected
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# Start connecting.
+run
+  should connect
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (0 ms backoff)
+
+# Connect after 500 ms.
+advance 500
+
+### t=1500 ###
+  in CONNECT_IN_PROGRESS for 500 ms (0 ms backoff)
+run
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  created 1000, last received 1000, last connected 1500
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Send inactivity probe.
+timeout
+  advance 5000 ms
+
+### t=6500 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+
+# Idle timeout kills connection.
+timeout
+  advance 5000 ms
+
+### t=11500 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([connect backs off])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt fails after 2000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 4000 ms.
+timeout
+run
+
+# Third connection attempt fails after 4000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 8000 ms.
+timeout
+run
+
+# Third connection attempt fails after 8000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 8000 ms.
+timeout
+run
+
+# Fourth connection attempt fails after 8000 ms.
+connecting
+timeout
+run
+connect-failed
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (0 ms backoff)
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt fails after 2000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (2000 ms backoff)
+timeout
+  advance 2000 ms
+
+### t=8000 ###
+  in CONNECT_IN_PROGRESS for 2000 ms (2000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (4000 ms backoff)
+  0 successful connections out of 3 attempts, seqno 0
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=12000 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+run
+  should connect
+
+# Third connection attempt fails after 4000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (4000 ms backoff)
+timeout
+  advance 4000 ms
+
+### t=16000 ###
+  in CONNECT_IN_PROGRESS for 4000 ms (4000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (8000 ms backoff)
+  0 successful connections out of 4 attempts, seqno 0
+
+# Back off for 8000 ms.
+timeout
+  advance 8000 ms
+
+### t=24000 ###
+  in BACKOFF for 8000 ms (8000 ms backoff)
+run
+  should connect
+
+# Third connection attempt fails after 8000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (8000 ms backoff)
+timeout
+  advance 8000 ms
+
+### t=32000 ###
+  in CONNECT_IN_PROGRESS for 8000 ms (8000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (8000 ms backoff)
+  0 successful connections out of 5 attempts, seqno 0
+
+# Back off for 8000 ms.
+timeout
+  advance 8000 ms
+
+### t=40000 ###
+  in BACKOFF for 8000 ms (8000 ms backoff)
+run
+  should connect
+
+# Fourth connection attempt fails after 8000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (8000 ms backoff)
+timeout
+  advance 8000 ms
+
+### t=48000 ###
+  in CONNECT_IN_PROGRESS for 8000 ms (8000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (8000 ms backoff)
+  0 successful connections out of 6 attempts, seqno 0
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([connections with no data preserve backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connect, then idle timeout kills connection.
+run
+connected
+timeout
+run
+timeout
+run
+disconnected
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connect, then idle timeout kills connection.
+run
+connected
+timeout
+run
+timeout
+run
+disconnected
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connect, then idle timeout kills connection.
+run
+connected
+timeout
+run
+timeout
+run
+disconnected
+
+# Back off for 4000 ms.
+timeout
+], [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connect, then idle timeout kills connection.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+timeout
+  advance 5000 ms
+
+### t=6000 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+timeout
+  advance 5000 ms
+
+### t=11000 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=12000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connect, then idle timeout kills connection.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (1000 ms backoff)
+  created 1000, last received 1000, last connected 12000
+  2 successful connections out of 2 attempts, seqno 3
+  connected (0 ms), total 10000 ms connected
+timeout
+  advance 5000 ms
+
+### t=17000 ###
+  in ACTIVE for 5000 ms (1000 ms backoff)
+  connected (5000 ms), total 15000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (1000 ms backoff)
+timeout
+  advance 5000 ms
+
+### t=22000 ###
+  in IDLE for 5000 ms (1000 ms backoff)
+  connected (10000 ms), total 20000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (2000 ms backoff)
+  2 successful connections out of 2 attempts, seqno 4
+  not connected (0 ms), total 20000 ms connected
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=24000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connect, then idle timeout kills connection.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 24000
+  3 successful connections out of 3 attempts, seqno 5
+  connected (0 ms), total 20000 ms connected
+timeout
+  advance 5000 ms
+
+### t=29000 ###
+  in ACTIVE for 5000 ms (2000 ms backoff)
+  connected (5000 ms), total 25000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (2000 ms backoff)
+timeout
+  advance 5000 ms
+
+### t=34000 ###
+  in IDLE for 5000 ms (2000 ms backoff)
+  connected (10000 ms), total 30000 ms connected
+run
+  should disconnect
+disconnected
+  in BACKOFF for 0 ms (4000 ms backoff)
+  3 successful connections out of 3 attempts, seqno 6
+  not connected (0 ms), total 30000 ms connected
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=38000 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([brief connection preserves backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+advance 500
+run
+connected
+
+# Connection drops after another 250 ms.
+advance 250
+disconnected
+run
+
+# Back off for 4000 ms.
+timeout
+run
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (0 ms backoff)
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (2000 ms backoff)
+advance 500
+
+### t=6500 ###
+  in CONNECT_IN_PROGRESS for 500 ms (2000 ms backoff)
+run
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 6500
+  1 successful connections out of 3 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Connection drops after another 250 ms.
+advance 250
+
+### t=6750 ###
+  in ACTIVE for 250 ms (2000 ms backoff)
+  connected (250 ms), total 250 ms connected
+disconnected
+  in BACKOFF for 0 ms (4000 ms backoff)
+  1 successful connections out of 3 attempts, seqno 2
+  not connected (0 ms), total 250 ms connected
+run
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=10750 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+run
+  should connect
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([brief connection with data preserves backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+advance 500
+run
+connected
+
+# Connection receives 3 chunks of data spaced 250 ms apart.
+advance 250
+run
+received
+advance 250
+run
+received
+advance 250
+run
+received
+
+# Connection drops.
+disconnected
+run
+
+# Back off for 4000 ms.
+timeout
+run
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (0 ms backoff)
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (2000 ms backoff)
+advance 500
+
+### t=6500 ###
+  in CONNECT_IN_PROGRESS for 500 ms (2000 ms backoff)
+run
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 6500
+  1 successful connections out of 3 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Connection receives 3 chunks of data spaced 250 ms apart.
+advance 250
+
+### t=6750 ###
+  in ACTIVE for 250 ms (2000 ms backoff)
+  connected (250 ms), total 250 ms connected
+run
+received
+  created 1000, last received 6750, last connected 6500
+advance 250
+
+### t=7000 ###
+  in ACTIVE for 500 ms (2000 ms backoff)
+  connected (500 ms), total 500 ms connected
+run
+received
+  created 1000, last received 7000, last connected 6500
+advance 250
+
+### t=7250 ###
+  in ACTIVE for 750 ms (2000 ms backoff)
+  connected (750 ms), total 750 ms connected
+run
+received
+  created 1000, last received 7250, last connected 6500
+
+# Connection drops.
+disconnected
+  in BACKOFF for 0 ms (4000 ms backoff)
+  1 successful connections out of 3 attempts, seqno 2
+  not connected (0 ms), total 750 ms connected
+run
+
+# Back off for 4000 ms.
+timeout
+  advance 4000 ms
+
+### t=11250 ###
+  in BACKOFF for 4000 ms (4000 ms backoff)
+run
+  should connect
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([long connection resets backoff])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# First connection attempt fails after 1000 ms.
+run
+connecting
+run
+timeout
+run
+connect-failed
+
+# Back off for 1000 ms.
+timeout
+run
+
+# Second connection attempt fails after 1000 ms.
+connecting
+timeout
+run
+connect-failed
+
+# Back off for 2000 ms.
+timeout
+run
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+advance 500
+run
+connected
+
+# Connection receives 3 chunks of data spaced 2000 ms apart.
+advance 2000
+run
+received
+advance 2000
+run
+received
+advance 2000
+run
+received
+
+# Connection drops.
+disconnected
+run
+
+# Back off for 1000 ms.
+timeout
+run
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# First connection attempt fails after 1000 ms.
+run
+  should connect
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (0 ms backoff)
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (0 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=3000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+
+# Second connection attempt fails after 1000 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (1000 ms backoff)
+timeout
+  advance 1000 ms
+
+### t=4000 ###
+  in CONNECT_IN_PROGRESS for 1000 ms (1000 ms backoff)
+run
+  should disconnect
+connect-failed
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+timeout
+  advance 2000 ms
+
+### t=6000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+run
+  should connect
+
+# Third connection attempt succeeds after 500 ms.
+connecting
+  in CONNECT_IN_PROGRESS for 0 ms (2000 ms backoff)
+advance 500
+
+### t=6500 ###
+  in CONNECT_IN_PROGRESS for 500 ms (2000 ms backoff)
+run
+connected
+  in ACTIVE for 0 ms (2000 ms backoff)
+  created 1000, last received 1000, last connected 6500
+  1 successful connections out of 3 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Connection receives 3 chunks of data spaced 2000 ms apart.
+advance 2000
+
+### t=8500 ###
+  in ACTIVE for 2000 ms (2000 ms backoff)
+  connected (2000 ms), total 2000 ms connected
+run
+received
+  created 1000, last received 8500, last connected 6500
+advance 2000
+
+### t=10500 ###
+  in ACTIVE for 4000 ms (2000 ms backoff)
+  connected (4000 ms), total 4000 ms connected
+run
+received
+  created 1000, last received 10500, last connected 6500
+advance 2000
+
+### t=12500 ###
+  in ACTIVE for 6000 ms (2000 ms backoff)
+  connected (6000 ms), total 6000 ms connected
+run
+received
+  created 1000, last received 12500, last connected 6500
+
+# Connection drops.
+disconnected
+  in BACKOFF for 0 ms (1000 ms backoff)
+  1 successful connections out of 3 attempts, seqno 2
+  not connected (0 ms), total 6000 ms connected
+run
+
+# Back off for 1000 ms.
+timeout
+  advance 1000 ms
+
+### t=13500 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+run
+  should connect
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([connection attempt fails quickly])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [enable
+
+# Connection fails quickly.
+run
+connect-failed ECONNREFUSED
+
+# Back off for 1000 ms.
+run
+timeout
+
+# Connection fails quickly again.
+run
+connect-failed ECONNREFUSED
+
+# Back off for 2000 ms.
+run
+timeout
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+
+# Connection fails quickly.
+run
+  should connect
+connect-failed ECONNREFUSED
+  in BACKOFF for 0 ms (1000 ms backoff)
+  0 successful connections out of 1 attempts, seqno 0
+
+# Back off for 1000 ms.
+run
+timeout
+  advance 1000 ms
+
+### t=2000 ###
+  in BACKOFF for 1000 ms (1000 ms backoff)
+
+# Connection fails quickly again.
+run
+  should connect
+connect-failed ECONNREFUSED
+  in BACKOFF for 0 ms (2000 ms backoff)
+  0 successful connections out of 2 attempts, seqno 0
+
+# Back off for 2000 ms.
+run
+timeout
+  advance 2000 ms
+
+### t=4000 ###
+  in BACKOFF for 2000 ms (2000 ms backoff)
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([max-tries of 1 honored])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [set-max-tries 1
+enable
+
+# Connection succeeds.
+run
+connected
+
+# Send inactivity probe.
+timeout
+run
+
+# Idle timeout kills connection.
+timeout
+run
+disconnected
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+set-max-tries 1
+  1 tries left
+enable
+  in BACKOFF for 0 ms (0 ms backoff)
+  0 tries left
+
+# Connection succeeds.
+run
+  should connect
+connected
+  in ACTIVE for 0 ms (0 ms backoff)
+  1 successful connections out of 1 attempts, seqno 1
+  connected (0 ms), total 0 ms connected
+
+# Send inactivity probe.
+timeout
+  advance 5000 ms
+
+### t=6000 ###
+  in ACTIVE for 5000 ms (0 ms backoff)
+  connected (5000 ms), total 5000 ms connected
+run
+  should send probe
+  in IDLE for 0 ms (0 ms backoff)
+
+# Idle timeout kills connection.
+timeout
+  advance 5000 ms
+
+### t=11000 ###
+  in IDLE for 5000 ms (0 ms backoff)
+  connected (10000 ms), total 10000 ms connected
+run
+  should disconnect
+disconnected
+  in VOID for 0 ms (1000 ms backoff)
+  1 successful connections out of 1 attempts, seqno 2
+  not connected (0 ms), total 10000 ms connected
+])
+AT_CLEANUP
+
+######################################################################
+AT_SETUP([max-tries of 0 honored])
+AT_KEYWORDS([reconnect])
+AT_DATA([input], [set-max-tries 0
+enable
+run
+timeout
+])
+AT_CHECK([test-reconnect < input], [0], 
+  [### t=1000 ###
+set-max-tries 0
+  0 tries left
+enable
+run
+timeout
+  no timeout
+])
+AT_CLEANUP
index 6e2bcf9..4e25af7 100644 (file)
@@ -15,7 +15,7 @@ check 2 = F:10 B
 check 3 = F:5 F
 check 4 = F:5 B
 ])
-OVS_CHECK_LCOV([test-stp test-stp-ieee802.1d-1998])
+AT_CHECK([test-stp test-stp-ieee802.1d-1998])
 AT_CLEANUP
 
 AT_SETUP([STP example from IEEE 802.1D-2004 figures 17.4 and 17.5])
@@ -52,7 +52,7 @@ check 5 = F:20 B F F
 check 6 = F:20 B F F
 check 7 = F:20 B F B
 ])
-OVS_CHECK_LCOV([test-stp test-stp-ieee802.1d-2004-fig17.4])
+AT_CHECK([test-stp test-stp-ieee802.1d-2004-fig17.4])
 AT_CLEANUP
 
 AT_SETUP([STP example from IEEE 802.1D-2004 figure 17.6])
@@ -72,7 +72,7 @@ check 3 = F:30 F B
 check 4 = F:20 F F
 check 5 = F:10 F F
 ])
-OVS_CHECK_LCOV([test-stp test-stp-ieee802.1d-2004-fig17.6])
+AT_CHECK([test-stp test-stp-ieee802.1d-2004-fig17.6])
 AT_CLEANUP
 
 AT_SETUP([STP example from IEEE 802.1D-2004 figure 17.7])
@@ -95,7 +95,7 @@ check 0 = root
 check 1 = F F:10 F F F F F F
 check 2 = F:20 D F F F F F F
 ])
-OVS_CHECK_LCOV([test-stp test-stp-ieee802.1d-2004-fig17.7])
+AT_CHECK([test-stp test-stp-ieee802.1d-2004-fig17.7])
 AT_CLEANUP
 
 AT_SETUP([STP.io.1.1: Link Failure])
@@ -128,7 +128,7 @@ run 1000
 check 0 = root
 check 1 = D D F:10
 ])
-OVS_CHECK_LCOV([test-stp test-stp-iol-io-1.1])
+AT_CHECK([test-stp test-stp-iol-io-1.1])
 AT_CLEANUP
 
 AT_SETUP([STP.io.1.2: Repeated Network])
@@ -148,7 +148,7 @@ run 1000
 check 0 = rootid:0x111 F B
 check 1 = rootid:0x111 B F:10
 ])
-OVS_CHECK_LCOV([test-stp test-stp-iol-io-1.2])
+AT_CHECK([test-stp test-stp-iol-io-1.2])
 AT_CLEANUP
 
 AT_SETUP([STP.io.1.4: Network Initialization])
@@ -168,7 +168,7 @@ check 1 = F:10 F F
 check 2 = F:10 B F
 check 3 = F:10 B B
 ])
-OVS_CHECK_LCOV([test-stp test-stp-iol-io-1.4])
+AT_CHECK([test-stp test-stp-iol-io-1.4])
 AT_CLEANUP
 
 AT_SETUP([STP.io.1.5: Topology Change])
@@ -215,7 +215,7 @@ check 1 = F:10 B F F
 check 2 = B F:10 F F
 check 3 = B F:20 B B
 ])
-OVS_CHECK_LCOV([test-stp test-stp-iol-io-1.5])
+AT_CHECK([test-stp test-stp-iol-io-1.5])
 AT_CLEANUP
 
 AT_SETUP([STP.op.1.1 and STP.op.1.2])
@@ -229,7 +229,7 @@ AT_DATA([test-stp-iol-op-1.1],
 bridge 0 0x123 =
 check 0 = root
 ])
-OVS_CHECK_LCOV([test-stp test-stp-iol-op-1.1])
+AT_CHECK([test-stp test-stp-iol-op-1.1])
 AT_CLEANUP
 
 AT_SETUP([STP.op.1.4: All Ports Initialized to Designated Ports])
@@ -244,7 +244,7 @@ check 0 = Li Li Li Li Li Li
 run 1000
 check 0 = F F F F F F
 ])
-OVS_CHECK_LCOV([test-stp test-stp-iol-op-1.4])
+AT_CHECK([test-stp test-stp-iol-op-1.4])
 AT_CLEANUP
 
 AT_SETUP([STP.op.3.1: Root Bridge Selection: Root ID Values])
@@ -262,7 +262,7 @@ run 1000
 check 0 = rootid:0x111 root
 check 1 = rootid:0x111 F:10
 ])
-OVS_CHECK_LCOV([test-stp test-stp-iol-op-3.1])
+AT_CHECK([test-stp test-stp-iol-op-3.1])
 AT_CLEANUP
 
 AT_SETUP([STP.op.3.3: Root Bridge Selection: Bridge ID Values])
@@ -280,7 +280,7 @@ check 0 = rootid:0x333^0x6000 root
 check 1 = rootid:0x333^0x6000 F:20
 check 2 = rootid:0x333^0x6000 F:10 F
 ])
-OVS_CHECK_LCOV([test-stp test-stp-iol-op-3.3])
+AT_CHECK([test-stp test-stp-iol-op-3.3])
 AT_CLEANUP
 
 AT_SETUP([STP.op.3.3: Root Bridge Selection: Bridge ID Values])
@@ -298,6 +298,6 @@ check 0 = rootid:0x333^0x6000 root
 check 1 = rootid:0x333^0x6000 F:20
 check 2 = rootid:0x333^0x6000 F:10 F
 ])
-OVS_CHECK_LCOV([test-stp test-stp-iol-op-3.4])
+AT_CHECK([test-stp test-stp-iol-op-3.4])
 AT_CLEANUP
 
diff --git a/tests/test-aes128.c b/tests/test-aes128.c
new file mode 100644 (file)
index 0000000..8a6c28c
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <ctype.h>
+#include "aes128.h"
+#include "util.h"
+
+static void
+hex_to_uint8(const char *input, uint8_t *output, size_t n)
+{
+    size_t i;
+
+    if (strlen(input) != n * 2) {
+        goto error;
+    }
+    for (i = 0; i < n; i++) {
+        unsigned char hi = input[i * 2];
+        unsigned char lo = input[i * 2 + 1];
+
+        if (!isxdigit(hi) || !isxdigit(lo)) {
+            goto error;
+        }
+        output[i] = (hexit_value(hi) << 4) + hexit_value(lo);
+    }
+    return;
+
+error:
+    ovs_fatal(0, "\"%s\" is not exactly %zu hex digits", input, n * 2);
+}
+
+int
+main(int argc, char *argv[])
+{
+    struct aes128 aes;
+    uint8_t plaintext[16];
+    uint8_t ciphertext[16];
+    uint8_t key[16];
+    size_t i;
+
+    if (argc != 3) {
+        ovs_fatal(0, "usage: %s KEY PLAINTEXT, where KEY and PLAINTEXT each "
+                  "consist of 32 hex digits", argv[0]);
+    }
+
+    hex_to_uint8(argv[1], key, 16);
+    hex_to_uint8(argv[2], plaintext, 16);
+
+    aes128_schedule(&aes, key);
+    aes128_encrypt(&aes, plaintext, ciphertext);
+    for (i = 0; i < 16; i++) {
+        printf("%02x", ciphertext[i]);
+    }
+    putchar('\n');
+
+    return 0;
+}
index 0307e48..d36c8eb 100644 (file)
@@ -446,7 +446,7 @@ make_rule(int wc_fields, unsigned int priority, int value_pat)
         }
     }
 
-    rule = xcalloc(1, sizeof *rule);
+    rule = xzalloc(sizeof *rule);
     cls_rule_from_flow(&rule->cls_rule, &flow, wildcards,
                        !wildcards ? UINT_MAX : priority);
     return rule;
index e4471c7..3b35dac 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,7 +23,6 @@
 #include "command-line.h"
 #include "dhcp.h"
 #include "fatal-signal.h"
-#include "fault.h"
 #include "poll-loop.h"
 #include "util.h"
 #include "vlog.h"
@@ -51,7 +50,6 @@ main(int argc, char *argv[])
     int error;
 
     set_program_name(argv[0]);
-    register_fault_handlers();
     vlog_init();
     parse_options(argc, argv);
 
@@ -67,10 +65,9 @@ main(int argc, char *argv[])
         ovs_fatal(error, "dhclient_create failed");
     }
     dhclient_init(cli, request_ip.s_addr);
-    fatal_signal_add_hook(release, cli, true);
+    fatal_signal_add_hook(release, NULL, cli, true);
 
     for (;;) {
-        fatal_signal_block();
         dhclient_run(cli);
         if (dhclient_changed(cli)) {
             dhclient_configure_netdev(cli);
@@ -79,7 +76,6 @@ main(int argc, char *argv[])
             }
         }
         dhclient_wait(cli);
-        fatal_signal_unblock();
         poll_block();
     }
 }
similarity index 67%
rename from lib/fault.h
rename to tests/test-dir_name.c
index 0d12927..627ecf1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008 Nicira Networks.
+ * Copyright (c) 2009 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * limitations under the License.
  */
 
-#ifndef FAULT_H
-#define FAULT_H 1
+#include <config.h>
+#include "util.h"
+#include <stdlib.h>
 
-void register_fault_handlers(void);
-void log_backtrace(void);
+int
+main(int argc, char *argv[])
+{
+    int i;
 
-#endif /* fault.h */
+    for (i = 1; i < argc; i++) {
+        char *dir = dir_name(argv[i]);
+        puts(dir);
+        free(dir);
+    }
+
+    return 0;
+}
diff --git a/tests/test-json.c b/tests/test-json.c
new file mode 100644 (file)
index 0000000..f297dc2
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "json.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+
+#include "util.h"
+
+/* --pretty: If set, the JSON output is pretty-printed, instead of printed as
+ * compactly as possible.  */
+static int pretty = 0;
+
+/* --multiple: If set, the input is a sequence of JSON objects or arrays,
+ * instead of exactly one object or array. */
+static int multiple = 0;
+
+static bool
+print_and_free_json(struct json *json)
+{
+    bool ok;
+    if (json->type == JSON_STRING) {
+        printf("error: %s\n", json->u.string);
+        ok = false;
+    } else {
+        char *s = json_to_string(json, JSSF_SORT | (pretty ? JSSF_PRETTY : 0));
+        puts(s);
+        free(s);
+        ok = true;
+    }
+    json_destroy(json);
+    return ok;
+}
+
+static bool
+refill(FILE *file, void *buffer, size_t buffer_size, size_t *n, size_t *used)
+{
+    *used = 0;
+    if (feof(file)) {
+        *n = 0;
+        return false;
+    } else {
+        *n = fread(buffer, 1, buffer_size, file);
+        if (ferror(file)) {
+            ovs_fatal(errno, "Error reading input file");
+        }
+        return *n > 0;
+    }
+}
+
+static bool
+parse_multiple(FILE *stream)
+{
+    struct json_parser *parser;
+    char buffer[BUFSIZ];
+    size_t n, used;
+    bool ok;
+
+    parser = NULL;
+    n = used = 0;
+    ok = true;
+    while (used < n || refill(stream, buffer, sizeof buffer, &n, &used)) {
+        if (!parser && isspace((unsigned char) buffer[used])) {
+            /* Skip white space. */
+            used++;
+        } else {
+            if (!parser) {
+                parser = json_parser_create(0);
+            }
+
+            used += json_parser_feed(parser, &buffer[used], n - used);
+            if (used < n) {
+                if (!print_and_free_json(json_parser_finish(parser))) {
+                    ok = false;
+                }
+                parser = NULL;
+            }
+        }
+    }
+    if (parser) {
+        if (!print_and_free_json(json_parser_finish(parser))) {
+            ok = false;
+        }
+    }
+    return ok;
+}
+
+int
+main(int argc, char *argv[])
+{
+    const char *input_file;
+    FILE *stream;
+    bool ok;
+
+    set_program_name(argv[0]);
+
+    for (;;) {
+        static const struct option options[] = {
+            {"pretty", no_argument, &pretty, 1},
+            {"multiple", no_argument, &multiple, 1},
+        };
+        int option_index = 0;
+        int c = getopt_long (argc, argv, "", options, &option_index);
+
+        if (c == -1) {
+            break;
+        }
+        switch (c) {
+        case 0:
+            break;
+
+        case '?':
+            exit(1);
+
+        default:
+            abort();
+        }
+    }
+
+    if (argc - optind != 1) {
+        ovs_fatal(0, "usage: %s [--pretty] [--multiple] INPUT.json",
+                  program_name);
+    }
+
+    input_file = argv[optind];
+    stream = !strcmp(input_file, "-") ? stdin : fopen(input_file, "r");
+    if (!stream) {
+        ovs_fatal(errno, "Cannot open \"%s\"", input_file);
+    }
+
+    if (multiple) {
+        ok = parse_multiple(stream);
+    } else {
+        ok = print_and_free_json(json_from_stream(stream));
+    }
+
+    fclose(stream);
+
+    return !ok;
+}
diff --git a/tests/test-jsonrpc.c b/tests/test-jsonrpc.c
new file mode 100644 (file)
index 0000000..03d3000
--- /dev/null
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "jsonrpc.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "json.h"
+#include "poll-loop.h"
+#include "stream-ssl.h"
+#include "stream.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    enum {
+        OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1
+    };
+    static struct option long_options[] = {
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        DAEMON_LONG_OPTIONS,
+#ifdef HAVE_OPENSSL
+        {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
+        STREAM_SSL_LONG_OPTIONS
+#endif
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 'h':
+            usage();
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        DAEMON_OPTION_HANDLERS
+
+#ifdef HAVE_OPENSSL
+        STREAM_SSL_OPTION_HANDLERS
+
+        case OPT_BOOTSTRAP_CA_CERT:
+            stream_ssl_set_ca_cert_file(optarg, true);
+            break;
+#endif
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: JSON-RPC test utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n"
+           "  listen LOCAL             listen for connections on LOCAL\n"
+           "  request REMOTE METHOD PARAMS   send request, print reply\n"
+           "  notify REMOTE METHOD PARAMS  send notification and exit\n",
+           program_name, program_name);
+    stream_usage("JSON-RPC", true, true, true);
+    daemon_usage();
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -h, --help                  display this help message\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+/* Command helper functions. */
+
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+\f
+/* Command implementations. */
+
+static void
+handle_rpc(struct jsonrpc *rpc, struct jsonrpc_msg *msg, bool *done)
+{
+    struct jsonrpc_msg *reply = NULL;
+    if (msg->type == JSONRPC_REQUEST) {
+        if (!strcmp(msg->method, "echo")) {
+            reply = jsonrpc_create_reply(json_clone(msg->params), msg->id);
+        } else {
+            struct json *error = json_object_create();
+            json_object_put_string(error, "error", "unknown method");
+            reply = jsonrpc_create_error(error, msg->id);
+            ovs_error(0, "unknown request %s", msg->method);
+        }
+
+    } else if (msg->type == JSONRPC_NOTIFY) {
+        if (!strcmp(msg->method, "shutdown")) {
+            *done = true;
+        } else {
+            jsonrpc_error(rpc, ENOTTY);
+            ovs_error(0, "unknown notification %s", msg->method);
+        }
+    } else {
+        jsonrpc_error(rpc, EPROTO);
+        ovs_error(0, "unsolicited JSON-RPC reply or error");
+    }
+
+    if (reply) {
+        jsonrpc_send(rpc, reply);
+    }
+}
+
+static void
+do_listen(int argc UNUSED, char *argv[])
+{
+    struct pstream *pstream;
+    struct jsonrpc **rpcs;
+    size_t n_rpcs, allocated_rpcs;
+    bool done;
+    int error;
+
+    die_if_already_running();
+
+    error = pstream_open(argv[1], &pstream);
+    if (error) {
+        ovs_fatal(error, "could not listen on \"%s\"", argv[1]);
+    }
+
+    daemonize();
+
+    rpcs = NULL;
+    n_rpcs = allocated_rpcs = 0;
+    done = false;
+    for (;;) {
+        struct stream *stream;
+        size_t i;
+
+        /* Accept new connections. */
+        error = pstream_accept(pstream, &stream);
+        if (!error) {
+            if (n_rpcs >= allocated_rpcs) {
+                rpcs = x2nrealloc(rpcs, &allocated_rpcs, sizeof *rpcs);
+            }
+            rpcs[n_rpcs++] = jsonrpc_open(stream);
+        } else if (error != EAGAIN) {
+            ovs_fatal(error, "pstream_accept failed");
+        }
+
+        /* Service existing connections. */
+        for (i = 0; i < n_rpcs; ) {
+            struct jsonrpc *rpc = rpcs[i];
+            struct jsonrpc_msg *msg;
+
+            jsonrpc_run(rpc);
+            if (!jsonrpc_get_backlog(rpc)) {
+                error = jsonrpc_recv(rpc, &msg);
+                if (!error) {
+                    handle_rpc(rpc, msg, &done);
+                    jsonrpc_msg_destroy(msg);
+                }
+            }
+
+            error = jsonrpc_get_status(rpc);
+            if (error) {
+                jsonrpc_close(rpc);
+                ovs_error(error, "connection closed");
+                memmove(&rpcs[i], &rpcs[i + 1],
+                        (n_rpcs - i - 1) * sizeof *rpcs);
+                n_rpcs--;
+            } else {
+                i++;
+            }
+        }
+
+        /* Wait for something to do. */
+        if (done && !n_rpcs) {
+            break;
+        }
+        pstream_wait(pstream);
+        for (i = 0; i < n_rpcs; i++) {
+            struct jsonrpc *rpc = rpcs[i];
+
+            jsonrpc_wait(rpc);
+            if (!jsonrpc_get_backlog(rpc)) {
+                jsonrpc_recv_wait(rpc);
+            }
+        }
+        poll_block();
+    }
+    free(rpcs);
+    pstream_close(pstream);
+}
+
+static void
+do_request(int argc UNUSED, char *argv[])
+{
+    struct jsonrpc_msg *msg;
+    struct jsonrpc *rpc;
+    struct json *params;
+    struct stream *stream;
+    const char *method;
+    char *string;
+    int error;
+
+    method = argv[2];
+    params = parse_json(argv[3]);
+    msg = jsonrpc_create_request(method, params, NULL);
+    string = jsonrpc_msg_is_valid(msg);
+    if (string) {
+        ovs_fatal(0, "not a valid JSON-RPC request: %s", string);
+    }
+
+    error = stream_open_block(argv[1], &stream);
+    if (error) {
+        ovs_fatal(error, "could not open \"%s\"", argv[1]);
+    }
+    rpc = jsonrpc_open(stream);
+
+    error = jsonrpc_send(rpc, msg);
+    if (error) {
+        ovs_fatal(error, "could not send request");
+    }
+
+    error = jsonrpc_recv_block(rpc, &msg);
+    if (error) {
+        ovs_fatal(error, "error waiting for reply");
+    }
+    print_and_free_json(jsonrpc_msg_to_json(msg));
+
+    jsonrpc_close(rpc);
+}
+
+static void
+do_notify(int argc UNUSED, char *argv[])
+{
+    struct jsonrpc_msg *msg;
+    struct jsonrpc *rpc;
+    struct json *params;
+    struct stream *stream;
+    const char *method;
+    char *string;
+    int error;
+
+    method = argv[2];
+    params = parse_json(argv[3]);
+    msg = jsonrpc_create_notify(method, params);
+    string = jsonrpc_msg_is_valid(msg);
+    if (string) {
+        ovs_fatal(0, "not a JSON RPC-valid notification: %s", string);
+    }
+
+    error = stream_open_block(argv[1], &stream);
+    if (error) {
+        ovs_fatal(error, "could not open \"%s\"", argv[1]);
+    }
+    rpc = jsonrpc_open(stream);
+
+    error = jsonrpc_send_block(rpc, msg);
+    if (error) {
+        ovs_fatal(error, "could not send request");
+    }
+    jsonrpc_close(rpc);
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+    usage();
+}
+
+static struct command all_commands[] = {
+    { "listen", 1, 1, do_listen },
+    { "request", 3, 3, do_request },
+    { "notify", 3, 3, do_notify },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/tests/test-lockfile.c b/tests/test-lockfile.c
new file mode 100644 (file)
index 0000000..f0f4b01
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "lockfile.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "process.h"
+#include "timeval.h"
+#include "util.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+struct test {
+    const char *name;
+    void (*function)(void);
+};
+
+static const struct test tests[];
+
+static void
+run_lock_and_unlock(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_and_unlock_twice(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_blocks_same_process(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+    lockfile_unlock(lockfile);
+}
+
+static void
+run_lock_blocks_same_process_twice(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+    assert(lockfile_lock("file", 0, &lockfile) == EDEADLK);
+    lockfile_unlock(lockfile);
+}
+
+static enum { PARENT, CHILD }
+do_fork(void)
+{
+    switch (fork()) {
+    case 0:
+        time_postfork();
+        lockfile_postfork();
+        return CHILD;
+
+    default:
+        return PARENT;
+
+    case -1:
+        /* Error. */
+        ovs_fatal(errno, "fork failed");
+    }
+}
+
+static void
+run_lock_blocks_other_process(void)
+{
+    /* Making this static prevents a memory leak warning from valgrind for the
+     * parent process, which cannot easily unlock (and free) 'lockfile' because
+     * it can only do so after the child has exited, and it's the caller of
+     * this function that does the wait() call. */
+    static struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    if (do_fork() == CHILD) {
+        lockfile_unlock(lockfile);
+        assert(lockfile_lock("file", 0, &lockfile) == EAGAIN);
+        exit(11);
+    }
+}
+
+static void
+run_lock_twice_blocks_other_process(void)
+{
+    struct lockfile *lockfile, *dummy;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    assert(lockfile_lock("file", 0, &dummy) == EDEADLK);
+    if (do_fork() == CHILD) {
+        assert(lockfile_lock("file", 0, &dummy) == EAGAIN);
+        exit(11);
+    }
+}
+
+static void
+run_lock_and_unlock_allows_other_process(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+    lockfile_unlock(lockfile);
+
+    if (do_fork() == CHILD) {
+        assert(lockfile_lock("file", 0, &lockfile) == 0);
+        exit(11);
+    }
+}
+
+static void
+run_lock_timeout_gets_the_lock(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+
+    if (do_fork() == CHILD) {
+        lockfile_unlock(lockfile);
+        assert(lockfile_lock("file", TIME_UPDATE_INTERVAL * 3,
+                             &lockfile) == 0);
+        exit(11);
+    } else {
+        long long int now = time_msec();
+        while (time_msec() < now + TIME_UPDATE_INTERVAL) {
+            pause();
+        }
+        lockfile_unlock(lockfile);
+    }
+}
+
+static void
+run_lock_timeout_runs_out(void)
+{
+    struct lockfile *lockfile;
+
+    assert(lockfile_lock("file", 0, &lockfile) == 0);
+
+    if (do_fork() == CHILD) {
+        lockfile_unlock(lockfile);
+        assert(lockfile_lock("file", TIME_UPDATE_INTERVAL,
+                             &lockfile) == ETIMEDOUT);
+        exit(11);
+    } else {
+        long long int now = time_msec();
+        while (time_msec() < now + TIME_UPDATE_INTERVAL * 3) {
+            pause();
+        }
+        lockfile_unlock(lockfile);
+    }
+}
+
+static void
+run_lock_multiple(void)
+{
+    struct lockfile *a, *b, *c, *dummy;
+
+    assert(lockfile_lock("a", 0, &a) == 0);
+    assert(lockfile_lock("b", 0, &b) == 0);
+    assert(lockfile_lock("c", 0, &c) == 0);
+
+    lockfile_unlock(a);
+    assert(lockfile_lock("a", 0, &a) == 0);
+    assert(lockfile_lock("a", 0, &dummy) == EDEADLK);
+    lockfile_unlock(a);
+
+    lockfile_unlock(b);
+    assert(lockfile_lock("a", 0, &a) == 0);
+
+    lockfile_unlock(c);
+    lockfile_unlock(a);
+}
+
+static void
+run_help(void)
+{
+    size_t i;
+
+    printf("usage: %s TESTNAME\n"
+           "where TESTNAME is one of the following:\n",
+           program_name);
+    for (i = 0; tests[i].name; i++) {
+        fprintf(stderr, "\t%s\n", tests[i].name);
+    }
+}
+
+static const struct test tests[] = {
+#define TEST(NAME) { #NAME, run_##NAME }
+    TEST(lock_and_unlock),
+    TEST(lock_and_unlock_twice),
+    TEST(lock_blocks_same_process),
+    TEST(lock_blocks_same_process_twice),
+    TEST(lock_blocks_other_process),
+    TEST(lock_twice_blocks_other_process),
+    TEST(lock_and_unlock_allows_other_process),
+    TEST(lock_timeout_gets_the_lock),
+    TEST(lock_timeout_runs_out),
+    TEST(lock_multiple),
+    TEST(help),
+    { 0, 0 }
+#undef TEST
+};
+
+int
+main(int argc, char *argv[])
+{
+    size_t i;
+
+    set_program_name(argv[0]);
+    time_init();
+
+    if (argc != 2) {
+        ovs_fatal(0, "exactly one argument required; use \"%s help\" for help",
+                  program_name);
+        return 1;
+    }
+
+    for (i = 0; tests[i].name; i++) {
+        if (!strcmp(argv[1], tests[i].name)) {
+            int n_children;
+            int status;
+
+            (tests[i].function)();
+
+            n_children = 0;
+            while (wait(&status) > 0) {
+                if (WIFEXITED(status) && WEXITSTATUS(status) == 11) {
+                    n_children++;
+                } else {
+                    ovs_fatal(0, "child exited in unexpected way: %s",
+                              process_status_msg(status));
+                }
+            }
+            if (errno != ECHILD) {
+                ovs_fatal(errno, "wait");
+            }
+
+            printf("%s: success (%d child%s)\n",
+                   tests[i].name, n_children, n_children != 1 ? "ren" : "");
+            exit(0);
+        }
+    }
+    ovs_fatal(0, "unknown test \"%s\"; use \"%s help\" for help",
+              argv[1], program_name);
+}
+
diff --git a/tests/test-ovsdb.c b/tests/test-ovsdb.c
new file mode 100644 (file)
index 0000000..a40b780
--- /dev/null
@@ -0,0 +1,1813 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "command-line.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb-data.h"
+#include "ovsdb-error.h"
+#include "ovsdb-idl.h"
+#include "ovsdb-types.h"
+#include "ovsdb/column.h"
+#include "ovsdb/condition.h"
+#include "ovsdb/file.h"
+#include "ovsdb/log.h"
+#include "ovsdb/mutation.h"
+#include "ovsdb/ovsdb.h"
+#include "ovsdb/query.h"
+#include "ovsdb/row.h"
+#include "ovsdb/table.h"
+#include "ovsdb/transaction.h"
+#include "ovsdb/trigger.h"
+#include "poll-loop.h"
+#include "stream.h"
+#include "svec.h"
+#include "tests/idltest.h"
+#include "timeval.h"
+#include "util.h"
+#include "vlog.h"
+
+static struct command all_commands[];
+
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+int
+main(int argc, char *argv[])
+{
+    set_program_name(argv[0]);
+    time_init();
+    vlog_init();
+    parse_options(argc, argv);
+    run_command(argc - optind, argv + optind, all_commands);
+    return 0;
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    static struct option long_options[] = {
+        {"timeout", required_argument, 0, 't'},
+        {"verbose", optional_argument, 0, 'v'},
+        {"help", no_argument, 0, 'h'},
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        unsigned long int timeout;
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case 't':
+            timeout = strtoul(optarg, NULL, 10);
+            if (timeout <= 0) {
+                ovs_fatal(0, "value %s on -t or --timeout is not at least 1",
+                          optarg);
+            } else {
+                time_alarm(timeout);
+            }
+            break;
+
+        case 'h':
+            usage();
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: Open vSwitch database test utility\n"
+           "usage: %s [OPTIONS] COMMAND [ARG...]\n\n"
+           "  log-io FILE FLAGS COMMAND...\n"
+           "    open FILE with FLAGS, run COMMANDs\n"
+           "  parse-atomic-type TYPE\n"
+           "    parse TYPE as OVSDB atomic type, and re-serialize\n"
+           "  parse-type JSON\n"
+           "    parse JSON as OVSDB type, and re-serialize\n"
+           "  parse-atoms TYPE ATOM...\n"
+           "    parse JSON ATOMs as atoms of TYPE, and re-serialize\n"
+           "  parse-atom-strings TYPE ATOM...\n"
+           "    parse string ATOMs as atoms of given TYPE, and re-serialize\n"
+           "  sort-atoms TYPE ATOM...\n"
+           "    print JSON ATOMs in sorted order\n"
+           "  parse-data TYPE DATUM...\n"
+           "    parse JSON DATUMs as data of given TYPE, and re-serialize\n"
+           "  parse-data-strings TYPE DATUM...\n"
+           "    parse string DATUMs as data of given TYPE, and re-serialize\n"
+           "  parse-column NAME OBJECT\n"
+           "    parse column NAME with info OBJECT, and re-serialize\n"
+           "  parse-table NAME OBJECT\n"
+           "    parse table NAME with info OBJECT\n"
+           "  parse-row TABLE ROW..., and re-serialize\n"
+           "    parse each ROW of defined TABLE\n"
+           "  compare-row TABLE ROW...\n"
+           "    mutually compare all of the ROWs, print those that are equal\n"
+           "  parse-conditions TABLE CONDITION...\n"
+           "    parse each CONDITION on TABLE, and re-serialize\n"
+           "  evaluate-conditions TABLE [CONDITION,...] [ROW,...]\n"
+           "    test CONDITIONS on TABLE against each ROW, print results\n"
+           "  parse-mutations TABLE MUTATION...\n"
+           "    parse each MUTATION on TABLE, and re-serialize\n"
+           "  execute-mutations TABLE [MUTATION,...] [ROW,...]\n"
+           "    execute MUTATIONS on TABLE on each ROW, print results\n"
+           "  query TABLE [ROW,...] [CONDITION,...]\n"
+           "    add each ROW to TABLE, then query and print the rows that\n"
+           "    satisfy each CONDITION.\n"
+           "  query-distinct TABLE [ROW,...] [CONDITION,...] COLUMNS\n"
+           "    add each ROW to TABLE, then query and print the rows that\n"
+           "    satisfy each CONDITION and have distinct COLUMNS.\n"
+           "  transact COMMAND\n"
+           "    execute each specified transactional COMMAND:\n"
+           "      commit\n"
+           "      abort\n"
+           "      insert UUID I J\n"
+           "      delete UUID\n"
+           "      modify UUID I J\n"
+           "      print\n"
+           "  execute SCHEMA TRANSACTION...\n"
+           "    executes each TRANSACTION on an initially empty database\n"
+           "    the specified SCHEMA\n"
+           "  trigger SCHEMA TRANSACTION...\n"
+           "    executes each TRANSACTION on an initially empty database\n"
+           "    the specified SCHEMA.   A TRANSACTION of the form\n"
+           "    [\"advance\", NUMBER] advances NUMBER milliseconds in\n"
+           "    simulated time, for causing triggers to time out.\n"
+           "  idl SERVER [TRANSACTION...]\n"
+           "    connect to SERVER and dump the contents of the database\n"
+           "    as seen initially by the IDL implementation and after\n"
+           "    executing each TRANSACTION.  (Each TRANSACTION must modify\n"
+           "    the database or this command will hang.)\n",
+           program_name, program_name);
+    vlog_usage();
+    printf("\nOther options:\n"
+           "  -t, --timeout=SECS          give up after SECS seconds\n"
+           "  -h, --help                  display this help message\n");
+    exit(EXIT_SUCCESS);
+}
+\f
+/* Command helper functions. */
+
+static struct json *
+parse_json(const char *s)
+{
+    struct json *json = json_from_string(s);
+    if (json->type == JSON_STRING) {
+        ovs_fatal(0, "\"%s\": %s", s, json->u.string);
+    }
+    return json;
+}
+
+static struct json *
+unbox_json(struct json *json)
+{
+    if (json->type == JSON_ARRAY && json->u.array.n == 1) {
+        struct json *inner = json->u.array.elems[0];
+        json->u.array.elems[0] = NULL;
+        json_destroy(json);
+        return inner;
+    } else {
+        return json;
+    }
+}
+
+static void
+print_and_free_json(struct json *json)
+{
+    char *string = json_to_string(json, JSSF_SORT);
+    json_destroy(json);
+    puts(string);
+    free(string);
+}
+
+static void
+print_and_free_ovsdb_error(struct ovsdb_error *error)
+{
+    char *string = ovsdb_error_to_string(error);
+    ovsdb_error_destroy(error);
+    puts(string);
+    free(string);
+}
+
+static void
+check_ovsdb_error(struct ovsdb_error *error)
+{
+    if (error) {
+        char *s = ovsdb_error_to_string(error);
+        ovsdb_error_destroy(error);
+        ovs_fatal(0, "%s", s);
+    }
+}
+
+static void
+die_if_error(char *error)
+{
+    if (error) {
+        ovs_fatal(0, "%s", error);
+    }
+}
+\f
+/* Command implementations. */
+
+static void
+do_log_io(int argc, char *argv[])
+{
+    const char *name = argv[1];
+    char *mode = argv[2];
+
+    struct ovsdb_error *error;
+    struct ovsdb_log *log;
+    char *save_ptr = NULL;
+    const char *token;
+    int flags;
+    int i;
+
+    for (flags = 0, token = strtok_r(mode, " |", &save_ptr); token != NULL;
+         token = strtok_r(NULL, " |", &save_ptr))
+    {
+        if (!strcmp(token, "O_RDONLY")) {
+            flags |= O_RDONLY;
+        } else if (!strcmp(token, "O_RDWR")) {
+            flags |= O_RDWR;
+        } else if (!strcmp(token, "O_TRUNC")) {
+            flags |= O_TRUNC;
+        } else if (!strcmp(token, "O_CREAT")) {
+            flags |= O_CREAT;
+        } else if (!strcmp(token, "O_EXCL")) {
+            flags |= O_EXCL;
+        } else if (!strcmp(token, "O_TRUNC")) {
+            flags |= O_TRUNC;
+        }
+    }
+
+    check_ovsdb_error(ovsdb_log_open(name, flags, &log));
+    printf("%s: open successful\n", name);
+
+    for (i = 3; i < argc; i++) {
+        const char *command = argv[i];
+        if (!strcmp(command, "read")) {
+            struct json *json;
+
+            error = ovsdb_log_read(log, &json);
+            if (!error) {
+                printf("%s: read: ", name);
+                if (json) {
+                    print_and_free_json(json);
+                } else {
+                    printf("end of log\n");
+                }
+                continue;
+            }
+        } else if (!strncmp(command, "write:", 6)) {
+            struct json *json = parse_json(command + 6);
+            error = ovsdb_log_write(log, json);
+            json_destroy(json);
+        } else if (!strcmp(command, "commit")) {
+            error = ovsdb_log_commit(log);
+        } else {
+            ovs_fatal(0, "unknown log-io command \"%s\"", command);
+        }
+        if (error) {
+            char *s = ovsdb_error_to_string(error);
+            printf("%s: %s failed: %s\n", name, command, s);
+            free(s);
+            ovsdb_error_destroy(error);
+        } else {
+            printf("%s: %s successful\n", name, command);
+        }
+    }
+
+    ovsdb_log_close(log);
+}
+
+static void
+do_parse_atomic_type(int argc UNUSED, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    struct json *json;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+    print_and_free_json(ovsdb_atomic_type_to_json(type));
+}
+
+static void
+do_parse_type(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_type type;
+    struct json *json;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_type_from_json(&type, json));
+    json_destroy(json);
+    print_and_free_json(ovsdb_type_to_json(&type));
+}
+
+static void
+do_parse_atoms(int argc, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        union ovsdb_atom atom;
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_atom_from_json(&atom, type, json, NULL));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_atom_to_json(&atom, type));
+
+        ovsdb_atom_destroy(&atom, type);
+    }
+}
+
+static void
+do_parse_atom_strings(int argc, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        union ovsdb_atom atom;
+        struct ds out;
+
+        die_if_error(ovsdb_atom_from_string(&atom, type, argv[i]));
+
+        ds_init(&out);
+        ovsdb_atom_to_string(&atom, type, &out);
+        puts(ds_cstr(&out));
+        ds_destroy(&out);
+
+        ovsdb_atom_destroy(&atom, type);
+    }
+}
+
+static void
+do_parse_data(int argc, char *argv[])
+{
+    struct ovsdb_type type;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_type_from_json(&type, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_datum datum;
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_datum_from_json(&datum, &type, json, NULL));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_datum_to_json(&datum, &type));
+
+        ovsdb_datum_destroy(&datum, &type);
+    }
+}
+
+static void
+do_parse_data_strings(int argc, char *argv[])
+{
+    struct ovsdb_type type;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_type_from_json(&type, json));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_datum datum;
+        struct ds out;
+
+        die_if_error(ovsdb_datum_from_string(&datum, &type, argv[i]));
+
+        ds_init(&out);
+        ovsdb_datum_to_string(&datum, &type, &out);
+        puts(ds_cstr(&out));
+        ds_destroy(&out);
+
+        ovsdb_datum_destroy(&datum, &type);
+    }
+}
+
+static enum ovsdb_atomic_type compare_atoms_atomic_type;
+
+static int
+compare_atoms(const void *a_, const void *b_)
+{
+    const union ovsdb_atom *a = a_;
+    const union ovsdb_atom *b = b_;
+
+    return ovsdb_atom_compare_3way(a, b, compare_atoms_atomic_type);
+}
+
+static void
+do_sort_atoms(int argc UNUSED, char *argv[])
+{
+    enum ovsdb_atomic_type type;
+    union ovsdb_atom *atoms;
+    struct json *json, **json_atoms;
+    size_t n_atoms;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json));
+    json_destroy(json);
+
+    json = unbox_json(parse_json(argv[2]));
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "second argument must be array");
+    }
+
+    /* Convert JSON atoms to internal representation. */
+    n_atoms = json->u.array.n;
+    atoms = xmalloc(n_atoms * sizeof *atoms);
+    for (i = 0; i < n_atoms; i++) {
+        check_ovsdb_error(ovsdb_atom_from_json(&atoms[i], type,
+                                               json->u.array.elems[i], NULL));
+    }
+    json_destroy(json);
+
+    /* Sort atoms. */
+    compare_atoms_atomic_type = type;
+    qsort(atoms, n_atoms, sizeof *atoms, compare_atoms);
+
+    /* Convert internal representation back to JSON. */
+    json_atoms = xmalloc(n_atoms * sizeof *json_atoms);
+    for (i = 0; i < n_atoms; i++) {
+        json_atoms[i] = ovsdb_atom_to_json(&atoms[i], type);
+        ovsdb_atom_destroy(&atoms[i], type);
+    }
+    print_and_free_json(json_array_create(json_atoms, n_atoms));
+    free(atoms);
+}
+
+static void
+do_parse_column(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_column *column;
+    struct json *json;
+
+    json = parse_json(argv[2]);
+    check_ovsdb_error(ovsdb_column_from_json(json, argv[1], &column));
+    json_destroy(json);
+    print_and_free_json(ovsdb_column_to_json(column));
+    ovsdb_column_destroy(column);
+}
+
+static void
+do_parse_table(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+
+    json = parse_json(argv[2]);
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, argv[1], &ts));
+    json_destroy(json);
+    print_and_free_json(ovsdb_table_schema_to_json(ts));
+    ovsdb_table_schema_destroy(ts);
+}
+
+static void
+do_parse_rows(int argc, char *argv[])
+{
+    struct ovsdb_column_set all_columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct json *json;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+    ovsdb_column_set_init(&all_columns);
+    ovsdb_column_set_add_all(&all_columns, table);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_column_set columns;
+        struct ovsdb_row *row;
+
+        ovsdb_column_set_init(&columns);
+        row = ovsdb_row_create(table);
+
+        json = unbox_json(parse_json(argv[i]));
+        check_ovsdb_error(ovsdb_row_from_json(row, json, NULL, &columns));
+        json_destroy(json);
+
+        print_and_free_json(ovsdb_row_to_json(row, &all_columns));
+
+        if (columns.n_columns) {
+            struct svec names;
+            size_t j;
+            char *s;
+
+            svec_init(&names);
+            for (j = 0; j < columns.n_columns; j++) {
+                svec_add(&names, columns.columns[j]->name);
+            }
+            svec_sort(&names);
+            s = svec_join(&names, ", ", "");
+            puts(s);
+            free(s);
+            svec_destroy(&names);
+        } else {
+            printf("<none>\n");
+        }
+
+        ovsdb_column_set_destroy(&columns);
+        ovsdb_row_destroy(row);
+    }
+
+    ovsdb_column_set_destroy(&all_columns);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_compare_rows(int argc, char *argv[])
+{
+    struct ovsdb_column_set all_columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_row **rows;
+    struct json *json;
+    char **names;
+    int n_rows;
+    int i, j;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+    ovsdb_column_set_init(&all_columns);
+    ovsdb_column_set_add_all(&all_columns, table);
+
+    n_rows = argc - 2;
+    rows = xmalloc(sizeof *rows * n_rows);
+    names = xmalloc(sizeof *names * n_rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+
+        json = parse_json(argv[i + 2]);
+        if (json->type != JSON_ARRAY || json->u.array.n != 2
+            || json->u.array.elems[0]->type != JSON_STRING) {
+            ovs_fatal(0, "\"%s\" does not have expected form "
+                      "[\"name\", {data}]", argv[i]);
+        }
+        names[i] = xstrdup(json->u.array.elems[0]->u.string);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[1],
+                                              NULL, NULL));
+        json_destroy(json);
+    }
+    for (i = 0; i < n_rows; i++) {
+        uint32_t i_hash = ovsdb_row_hash_columns(rows[i], &all_columns, 0);
+        for (j = i + 1; j < n_rows; j++) {
+            uint32_t j_hash = ovsdb_row_hash_columns(rows[j], &all_columns, 0);
+            if (ovsdb_row_equal_columns(rows[i], rows[j], &all_columns)) {
+                printf("%s == %s\n", names[i], names[j]);
+                if (i_hash != j_hash) {
+                    printf("but hash(%s) != hash(%s)\n", names[i], names[j]);
+                    abort();
+                }
+            } else if (i_hash == j_hash) {
+                printf("hash(%s) == hash(%s)\n", names[i], names[j]);
+            }
+        }
+    }
+    for (i = 0; i < n_rows; i++) {
+        ovsdb_row_destroy(rows[i]);
+        free(names[i]);
+    }
+    free(rows);
+    free(names);
+
+    ovsdb_column_set_destroy(&all_columns);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_parse_conditions(int argc, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+    int exit_code = 0;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_condition cnd;
+        struct ovsdb_error *error;
+
+        json = parse_json(argv[i]);
+        error = ovsdb_condition_from_json(ts, json, NULL, &cnd);
+        if (!error) {
+            print_and_free_json(ovsdb_condition_to_json(&cnd));
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            ovs_error(0, "%s", s);
+            free(s);
+            ovsdb_error_destroy(error);
+            exit_code = 1;
+        }
+        json_destroy(json);
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    ovsdb_table_schema_destroy(ts);
+
+    exit(exit_code);
+}
+
+static void
+do_evaluate_conditions(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_condition *conditions;
+    size_t n_conditions;
+    struct ovsdb_row **rows;
+    size_t n_rows;
+    struct json *json;
+    size_t i, j;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse conditions. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    n_conditions = json->u.array.n;
+    conditions = xmalloc(n_conditions * sizeof *conditions);
+    for (i = 0; i < n_conditions; i++) {
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &conditions[i]));
+    }
+    json_destroy(json);
+
+    /* Parse rows. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i],
+                                              NULL, NULL));
+    }
+    json_destroy(json);
+
+    for (i = 0; i < n_conditions; i++) {
+        printf("condition %2d:", i);
+        for (j = 0; j < n_rows; j++) {
+            bool result = ovsdb_condition_evaluate(rows[j], &conditions[i]);
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            putchar(result ? 'T' : '-');
+        }
+        printf("\n");
+    }
+
+    for (i = 0; i < n_conditions; i++) {
+        ovsdb_condition_destroy(&conditions[i]);
+    }
+    free(conditions);
+    for (i = 0; i < n_rows; i++) {
+        ovsdb_row_destroy(rows[i]);
+    }
+    free(rows);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+static void
+do_parse_mutations(int argc, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct json *json;
+    int exit_code = 0;
+    int i;
+
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    for (i = 2; i < argc; i++) {
+        struct ovsdb_mutation_set set;
+        struct ovsdb_error *error;
+
+        json = parse_json(argv[i]);
+        error = ovsdb_mutation_set_from_json(ts, json, NULL, &set);
+        if (!error) {
+            print_and_free_json(ovsdb_mutation_set_to_json(&set));
+        } else {
+            char *s = ovsdb_error_to_string(error);
+            ovs_error(0, "%s", s);
+            free(s);
+            ovsdb_error_destroy(error);
+            exit_code = 1;
+        }
+        json_destroy(json);
+
+        ovsdb_mutation_set_destroy(&set);
+    }
+    ovsdb_table_schema_destroy(ts);
+
+    exit(exit_code);
+}
+
+static void
+do_execute_mutations(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct ovsdb_mutation_set *sets;
+    size_t n_sets;
+    struct ovsdb_row **rows;
+    size_t n_rows;
+    struct json *json;
+    size_t i, j;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse mutations. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "MUTATION argument is not JSON array");
+    }
+    n_sets = json->u.array.n;
+    sets = xmalloc(n_sets * sizeof *sets);
+    for (i = 0; i < n_sets; i++) {
+        check_ovsdb_error(ovsdb_mutation_set_from_json(ts,
+                                                       json->u.array.elems[i],
+                                                       NULL, &sets[i]));
+    }
+    json_destroy(json);
+
+    /* Parse rows. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    for (i = 0; i < n_rows; i++) {
+        rows[i] = ovsdb_row_create(table);
+        check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i],
+                                              NULL, NULL));
+    }
+    json_destroy(json);
+
+    for (i = 0; i < n_sets; i++) {
+        printf("mutation %2d:\n", i);
+        for (j = 0; j < n_rows; j++) {
+            struct ovsdb_error *error;
+            struct ovsdb_row *row;
+
+            row = ovsdb_row_clone(rows[j]);
+            error = ovsdb_mutation_set_execute(row, &sets[i]);
+
+            printf("row %zu: ", j);
+            if (error) {
+                print_and_free_ovsdb_error(error);
+            } else {
+                struct ovsdb_column_set columns;
+                struct shash_node *node;
+
+                ovsdb_column_set_init(&columns);
+                SHASH_FOR_EACH (node, &ts->columns) {
+                    struct ovsdb_column *c = node->data;
+                    if (!ovsdb_datum_equals(&row->fields[c->index],
+                                            &rows[j]->fields[c->index],
+                                            &c->type)) {
+                        ovsdb_column_set_add(&columns, c);
+                    }
+                }
+                if (columns.n_columns) {
+                    print_and_free_json(ovsdb_row_to_json(row, &columns));
+                } else {
+                    printf("no change\n");
+                }
+                ovsdb_column_set_destroy(&columns);
+            }
+            ovsdb_row_destroy(row);
+        }
+        printf("\n");
+    }
+
+    for (i = 0; i < n_sets; i++) {
+        ovsdb_mutation_set_destroy(&sets[i]);
+    }
+    free(sets);
+    for (i = 0; i < n_rows; i++) {
+        ovsdb_row_destroy(rows[i]);
+    }
+    free(rows);
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+}
+
+struct do_query_cbdata {
+    struct uuid *row_uuids;
+    int *counts;
+    size_t n_rows;
+};
+
+static bool
+do_query_cb(const struct ovsdb_row *row, void *cbdata_)
+{
+    struct do_query_cbdata *cbdata = cbdata_;
+    size_t i;
+
+    for (i = 0; i < cbdata->n_rows; i++) {
+        if (uuid_equals(ovsdb_row_get_uuid(row), &cbdata->row_uuids[i])) {
+            cbdata->counts[i]++;
+        }
+    }
+
+    return true;
+}
+
+static void
+do_query(int argc UNUSED, char *argv[])
+{
+    struct do_query_cbdata cbdata;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct json *json;
+    int exit_code = 0;
+    size_t i;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse rows, add to table. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    cbdata.n_rows = json->u.array.n;
+    cbdata.row_uuids = xmalloc(cbdata.n_rows * sizeof *cbdata.row_uuids);
+    cbdata.counts = xmalloc(cbdata.n_rows * sizeof *cbdata.counts);
+    for (i = 0; i < cbdata.n_rows; i++) {
+        struct ovsdb_row *row = ovsdb_row_create(table);
+        uuid_generate(ovsdb_row_get_uuid_rw(row));
+        check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+                                              NULL, NULL));
+        if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+            ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+                      UUID_ARGS(ovsdb_row_get_uuid(row)));
+        }
+        cbdata.row_uuids[i] = *ovsdb_row_get_uuid(row);
+        ovsdb_table_put_row(table, row);
+    }
+    json_destroy(json);
+
+    /* Parse conditions and execute queries. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    for (i = 0; i < json->u.array.n; i++) {
+        struct ovsdb_condition cnd;
+        size_t j;
+
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &cnd));
+
+        memset(cbdata.counts, 0, cbdata.n_rows * sizeof *cbdata.counts);
+        ovsdb_query(table, &cnd, do_query_cb, &cbdata);
+
+        printf("query %2d:", i);
+        for (j = 0; j < cbdata.n_rows; j++) {
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            if (cbdata.counts[j]) {
+                printf("%d", cbdata.counts[j]);
+                if (cbdata.counts[j] > 1) {
+                    /* Dup! */
+                    exit_code = 1;
+                }
+            } else {
+                putchar('-');
+            }
+        }
+        putchar('\n');
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    json_destroy(json);
+
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+    exit(exit_code);
+}
+
+struct do_query_distinct_class {
+    struct ovsdb_row *example;
+    int count;
+};
+
+struct do_query_distinct_row {
+    struct uuid uuid;
+    struct do_query_distinct_class *class;
+};
+
+static void
+do_query_distinct(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_column_set columns;
+    struct ovsdb_table_schema *ts;
+    struct ovsdb_table *table;
+    struct do_query_distinct_row *rows;
+    size_t n_rows;
+    struct do_query_distinct_class *classes;
+    size_t n_classes;
+    struct json *json;
+    int exit_code = 0;
+    size_t i, j, k;
+
+    /* Parse table schema, create table. */
+    json = unbox_json(parse_json(argv[1]));
+    check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts));
+    json_destroy(json);
+
+    table = ovsdb_table_create(ts);
+
+    /* Parse column set. */
+    json = parse_json(argv[4]);
+    check_ovsdb_error(ovsdb_column_set_from_json(json, table, &columns));
+    json_destroy(json);
+
+    /* Parse rows, add to table. */
+    json = parse_json(argv[2]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "ROW argument is not JSON array");
+    }
+    n_rows = json->u.array.n;
+    rows = xmalloc(n_rows * sizeof *rows);
+    classes = xmalloc(n_rows * sizeof *classes);
+    n_classes = 0;
+    for (i = 0; i < n_rows; i++) {
+        struct ovsdb_row *row;
+        size_t j;
+
+        /* Parse row. */
+        row = ovsdb_row_create(table);
+        uuid_generate(ovsdb_row_get_uuid_rw(row));
+        check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i],
+                                              NULL, NULL));
+
+        /* Initialize row and find equivalence class. */
+        rows[i].uuid = *ovsdb_row_get_uuid(row);
+        rows[i].class = NULL;
+        for (j = 0; j < n_classes; j++) {
+            if (ovsdb_row_equal_columns(row, classes[j].example, &columns)) {
+                rows[i].class = &classes[j];
+                break;
+            }
+        }
+        if (!rows[i].class) {
+            rows[i].class = &classes[n_classes];
+            classes[n_classes].example = ovsdb_row_clone(row);
+            n_classes++;
+        }
+
+        /* Add row to table. */
+        if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) {
+            ovs_fatal(0, "duplicate UUID "UUID_FMT" in table",
+                      UUID_ARGS(ovsdb_row_get_uuid(row)));
+        }
+        ovsdb_table_put_row(table, row);
+
+    }
+    json_destroy(json);
+
+    /* Parse conditions and execute queries. */
+    json = parse_json(argv[3]);
+    if (json->type != JSON_ARRAY) {
+        ovs_fatal(0, "CONDITION argument is not JSON array");
+    }
+    for (i = 0; i < json->u.array.n; i++) {
+        struct ovsdb_row_set results;
+        struct ovsdb_condition cnd;
+
+        check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i],
+                                                    NULL, &cnd));
+
+        for (j = 0; j < n_classes; j++) {
+            classes[j].count = 0;
+        }
+        ovsdb_row_set_init(&results);
+        ovsdb_query_distinct(table, &cnd, &columns, &results);
+        for (j = 0; j < results.n_rows; j++) {
+            for (k = 0; k < n_rows; k++) {
+                if (uuid_equals(ovsdb_row_get_uuid(results.rows[j]),
+                                &rows[k].uuid)) {
+                    rows[k].class->count++;
+                }
+            }
+        }
+        ovsdb_row_set_destroy(&results);
+
+        printf("query %2d:", i);
+        for (j = 0; j < n_rows; j++) {
+            int count = rows[j].class->count;
+
+            if (j % 5 == 0) {
+                putchar(' ');
+            }
+            if (count > 1) {
+                /* Dup! */
+                printf("%d", count);
+                exit_code = 1;
+            } else if (count == 1) {
+                putchar("abcdefghijklmnopqrstuvwxyz"[rows[j].class - classes]);
+            } else {
+                putchar('-');
+            }
+        }
+        putchar('\n');
+
+        ovsdb_condition_destroy(&cnd);
+    }
+    json_destroy(json);
+
+    ovsdb_table_destroy(table); /* Also destroys 'ts'. */
+
+    exit(exit_code);
+}
+
+static void
+do_execute(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct json *json;
+    struct ovsdb *db;
+    int i;
+
+    /* Create database. */
+    json = parse_json(argv[1]);
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    db = ovsdb_create(schema);
+
+    for (i = 2; i < argc; i++) {
+        struct json *params, *result;
+        char *s;
+
+        params = parse_json(argv[i]);
+        result = ovsdb_execute(db, params, 0, NULL);
+        s = json_to_string(result, JSSF_SORT);
+        printf("%s\n", s);
+        free(s);
+        json_destroy(params);
+        json_destroy(result);
+    }
+
+    ovsdb_destroy(db);
+}
+
+struct test_trigger {
+    struct ovsdb_trigger trigger;
+    int number;
+};
+
+static void
+do_trigger_dump(struct test_trigger *t, long long int now, const char *title)
+{
+    struct json *result;
+    char *s;
+
+    result = ovsdb_trigger_steal_result(&t->trigger);
+    s = json_to_string(result, JSSF_SORT);
+    printf("t=%lld: trigger %d (%s): %s\n", now, t->number, title, s);
+    free(s);
+    json_destroy(result);
+    ovsdb_trigger_destroy(&t->trigger);
+    free(t);
+}
+
+static void
+do_trigger(int argc UNUSED, char *argv[])
+{
+    struct ovsdb_schema *schema;
+    struct list completions;
+    struct json *json;
+    struct ovsdb *db;
+    long long int now;
+    int number;
+    int i;
+
+    /* Create database. */
+    json = parse_json(argv[1]);
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    db = ovsdb_create(schema);
+
+    list_init(&completions);
+    now = 0;
+    number = 0;
+    for (i = 2; i < argc; i++) {
+        struct json *params = parse_json(argv[i]);
+        if (params->type == JSON_ARRAY
+            && json_array(params)->n == 2
+            && json_array(params)->elems[0]->type == JSON_STRING
+            && !strcmp(json_string(json_array(params)->elems[0]), "advance")
+            && json_array(params)->elems[1]->type == JSON_INTEGER) {
+            now += json_integer(json_array(params)->elems[1]);
+            json_destroy(params);
+        } else {
+            struct test_trigger *t = xmalloc(sizeof *t);
+            ovsdb_trigger_init(db, &t->trigger, params, &completions, now);
+            t->number = number++;
+            if (ovsdb_trigger_is_complete(&t->trigger)) {
+                do_trigger_dump(t, now, "immediate");
+            } else {
+                printf("t=%lld: new trigger %d\n", now, t->number);
+            }
+        }
+
+        ovsdb_trigger_run(db, now);
+        while (!list_is_empty(&completions)) {
+            do_trigger_dump(CONTAINER_OF(list_pop_front(&completions),
+                                         struct test_trigger, trigger.node),
+                            now, "delayed");
+        }
+
+        ovsdb_trigger_wait(db, now);
+        poll_immediate_wake();
+        poll_block();
+    }
+
+    ovsdb_destroy(db);
+}
+
+static void
+do_help(int argc UNUSED, char *argv[] UNUSED)
+{
+    usage();
+}
+\f
+/* "transact" command. */
+
+static struct ovsdb *do_transact_db;
+static struct ovsdb_txn *do_transact_txn;
+static struct ovsdb_table *do_transact_table;
+
+static void
+do_transact_commit(int argc UNUSED, char *argv[] UNUSED)
+{
+    ovsdb_txn_commit(do_transact_txn, false);
+    do_transact_txn = NULL;
+}
+
+static void
+do_transact_abort(int argc UNUSED, char *argv[] UNUSED)
+{
+    ovsdb_txn_abort(do_transact_txn);
+    do_transact_txn = NULL;
+}
+
+static void
+uuid_from_integer(int integer, struct uuid *uuid)
+{
+    uuid_zero(uuid);
+    uuid->parts[3] = integer;
+}
+
+static const struct ovsdb_row *
+do_transact_find_row(const char *uuid_string)
+{
+    const struct ovsdb_row *row;
+    struct uuid uuid;
+
+    uuid_from_integer(atoi(uuid_string), &uuid);
+    row = ovsdb_table_get_row(do_transact_table, &uuid);
+    if (!row) {
+        ovs_fatal(0, "table does not contain row with UUID "UUID_FMT,
+                  UUID_ARGS(&uuid));
+    }
+    return row;
+}
+
+static void
+do_transact_set_integer(struct ovsdb_row *row, const char *column_name,
+                        int integer)
+{
+    if (integer != -1) {
+        const struct ovsdb_column *column;
+
+        column = ovsdb_table_schema_get_column(do_transact_table->schema,
+                                               column_name);
+        row->fields[column->index].keys[0].integer = integer;
+    }
+}
+
+static int
+do_transact_get_integer(const struct ovsdb_row *row, const char *column_name)
+{
+    const struct ovsdb_column *column;
+
+    column = ovsdb_table_schema_get_column(do_transact_table->schema,
+                                           column_name);
+    return row->fields[column->index].keys[0].integer;
+}
+
+static void
+do_transact_set_i_j(struct ovsdb_row *row,
+                    const char *i_string, const char *j_string)
+{
+    do_transact_set_integer(row, "i", atoi(i_string));
+    do_transact_set_integer(row, "j", atoi(j_string));
+}
+
+static void
+do_transact_insert(int argc UNUSED, char *argv[] UNUSED)
+{
+    struct ovsdb_row *row;
+    struct uuid *uuid;
+
+    row = ovsdb_row_create(do_transact_table);
+
+    /* Set UUID. */
+    uuid = ovsdb_row_get_uuid_rw(row);
+    uuid_from_integer(atoi(argv[1]), uuid);
+    if (ovsdb_table_get_row(do_transact_table, uuid)) {
+        ovs_fatal(0, "table already contains row with UUID "UUID_FMT,
+                  UUID_ARGS(uuid));
+    }
+
+    do_transact_set_i_j(row, argv[2], argv[3]);
+
+    /* Insert row. */
+    ovsdb_txn_row_insert(do_transact_txn, row);
+}
+
+static void
+do_transact_delete(int argc UNUSED, char *argv[] UNUSED)
+{
+    const struct ovsdb_row *row = do_transact_find_row(argv[1]);
+    ovsdb_txn_row_delete(do_transact_txn, row);
+}
+
+static void
+do_transact_modify(int argc UNUSED, char *argv[] UNUSED)
+{
+    const struct ovsdb_row *row_ro;
+    struct ovsdb_row *row_rw;
+
+    row_ro = do_transact_find_row(argv[1]);
+    row_rw = ovsdb_txn_row_modify(do_transact_txn, row_ro);
+    do_transact_set_i_j(row_rw, argv[2], argv[3]);
+}
+
+static int
+compare_rows_by_uuid(const void *a_, const void *b_)
+{
+    struct ovsdb_row *const *ap = a_;
+    struct ovsdb_row *const *bp = b_;
+
+    return uuid_compare_3way(ovsdb_row_get_uuid(*ap), ovsdb_row_get_uuid(*bp));
+}
+
+static void
+do_transact_print(int argc UNUSED, char *argv[] UNUSED)
+{
+    const struct ovsdb_row **rows;
+    const struct ovsdb_row *row;
+    size_t n_rows;
+    size_t i;
+
+    n_rows = hmap_count(&do_transact_table->rows);
+    rows = xmalloc(n_rows * sizeof *rows);
+    i = 0;
+    HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node,
+                   &do_transact_table->rows) {
+        rows[i++] = row;
+    }
+    assert(i == n_rows);
+
+    qsort(rows, n_rows, sizeof *rows, compare_rows_by_uuid);
+
+    for (i = 0; i < n_rows; i++) {
+        printf("\n%"PRId32": i=%d, j=%d",
+               ovsdb_row_get_uuid(rows[i])->parts[3],
+               do_transact_get_integer(rows[i], "i"),
+               do_transact_get_integer(rows[i], "j"));
+    }
+
+    free(rows);
+}
+
+static void
+do_transact(int argc, char *argv[])
+{
+    static const struct command do_transact_commands[] = {
+        { "commit", 0, 0, do_transact_commit },
+        { "abort", 0, 0, do_transact_abort },
+        { "insert", 2, 3, do_transact_insert },
+        { "delete", 1, 1, do_transact_delete },
+        { "modify", 2, 3, do_transact_modify },
+        { "print", 0, 0, do_transact_print },
+        { NULL, 0, 0, NULL },
+    };
+
+    struct ovsdb_schema *schema;
+    struct json *json;
+    int i;
+
+    /* Create table. */
+    json = parse_json("{\"name\": \"testdb\", "
+                      " \"tables\": "
+                      "  {\"mytable\": "
+                      "    {\"columns\": "
+                      "      {\"i\": {\"type\": \"integer\"}, "
+                      "       \"j\": {\"type\": \"integer\"}}}}}");
+    check_ovsdb_error(ovsdb_schema_from_json(json, &schema));
+    json_destroy(json);
+    do_transact_db = ovsdb_create(schema);
+    do_transact_table = ovsdb_get_table(do_transact_db, "mytable");
+    assert(do_transact_table != NULL);
+
+    for (i = 1; i < argc; i++) {
+        struct json *command;
+        size_t n_args;
+        char **args;
+        int j;
+
+        command = parse_json(argv[i]);
+        if (command->type != JSON_ARRAY) {
+            ovs_fatal(0, "transaction %d must be JSON array "
+                      "with at least 1 element", i);
+        }
+
+        n_args = command->u.array.n;
+        args = xmalloc((n_args + 1) * sizeof *args);
+        for (j = 0; j < n_args; j++) {
+            struct json *s = command->u.array.elems[j];
+            if (s->type != JSON_STRING) {
+                ovs_fatal(0, "transaction %d argument %d must be JSON string",
+                          i, j);
+            }
+            args[j] = xstrdup(json_string(s));
+        }
+        args[n_args] = NULL;
+
+        if (!do_transact_txn) {
+            do_transact_txn = ovsdb_txn_create(do_transact_db);
+        }
+
+        for (j = 0; j < n_args; j++) {
+            if (j) {
+                putchar(' ');
+            }
+            fputs(args[j], stdout);
+        }
+        fputs(":", stdout);
+        run_command(n_args, args, do_transact_commands);
+        putchar('\n');
+
+        for (j = 0; j < n_args; j++) {
+            free(args[j]);
+        }
+        free(args);
+        json_destroy(command);
+    }
+    ovsdb_txn_abort(do_transact_txn);
+    ovsdb_destroy(do_transact_db); /* Also destroys 'schema'. */
+}
+
+static int
+compare_link1(const void *a_, const void *b_)
+{
+    const struct idltest_link1 *const *ap = a_;
+    const struct idltest_link1 *const *bp = b_;
+    const struct idltest_link1 *a = *ap;
+    const struct idltest_link1 *b = *bp;
+
+    return a->i < b->i ? -1 : a->i > b->i;
+}
+
+static void
+print_idl(struct ovsdb_idl *idl, int step)
+{
+    const struct idltest_simple *s;
+    const struct idltest_link1 *l1;
+    const struct idltest_link2 *l2;
+    int n = 0;
+
+    IDLTEST_SIMPLE_FOR_EACH (s, idl) {
+        size_t i;
+
+        printf("%03d: i=%"PRId64" r=%g b=%s s=%s u="UUID_FMT" ia=[",
+               step, s->i, s->r, s->b ? "true" : "false",
+               s->s, UUID_ARGS(&s->u));
+        for (i = 0; i < s->n_ia; i++) {
+            printf("%s%"PRId64, i ? " " : "", s->ia[i]);
+        }
+        printf("] ra=[");
+        for (i = 0; i < s->n_ra; i++) {
+            printf("%s%g", i ? " " : "", s->ra[i]);
+        }
+        printf("] ba=[");
+        for (i = 0; i < s->n_ba; i++) {
+            printf("%s%s", i ? " " : "", s->ba[i] ? "true" : "false");
+        }
+        printf("] sa=[");
+        for (i = 0; i < s->n_sa; i++) {
+            printf("%s%s", i ? " " : "", s->sa[i]);
+        }
+        printf("] ua=[");
+        for (i = 0; i < s->n_ua; i++) {
+            printf("%s"UUID_FMT, i ? " " : "", UUID_ARGS(&s->ua[i]));
+        }
+        printf("] uuid="UUID_FMT"\n", UUID_ARGS(&s->header_.uuid));
+        n++;
+    }
+    IDLTEST_LINK1_FOR_EACH (l1, idl) {
+        struct idltest_link1 **links;
+        size_t i;
+
+        printf("%03d: i=%"PRId64" k=", step, l1->i);
+        if (l1->k) {
+            printf("%"PRId64, l1->k->i);
+        }
+        printf(" ka=[");
+        links = xmemdup(l1->ka, l1->n_ka * sizeof *l1->ka);
+        qsort(links, l1->n_ka, sizeof *links, compare_link1);
+        for (i = 0; i < l1->n_ka; i++) {
+            printf("%s%"PRId64, i ? " " : "", links[i]->i);
+        }
+        free(links);
+        printf("] l2=");
+        if (l1->l2) {
+            printf("%"PRId64, l1->l2->i);
+        }
+        printf(" uuid="UUID_FMT"\n", UUID_ARGS(&l1->header_.uuid));
+        n++;
+    }
+    IDLTEST_LINK2_FOR_EACH (l2, idl) {
+        printf("%03d: i=%"PRId64" l1=", step, l2->i);
+        if (l2->l1) {
+            printf("%"PRId64, l2->l1->i);
+        }
+        printf(" uuid="UUID_FMT"\n", UUID_ARGS(&l2->header_.uuid));
+        n++;
+    }
+    if (!n) {
+        printf("%03d: empty\n", step);
+    }
+}
+
+static unsigned int
+print_updated_idl(struct ovsdb_idl *idl, struct jsonrpc *rpc,
+                  int step, unsigned int seqno)
+{
+    for (;;) {
+        unsigned int new_seqno;
+
+        if (rpc) {
+            jsonrpc_run(rpc);
+        }
+        ovsdb_idl_run(idl);
+        new_seqno = ovsdb_idl_get_seqno(idl);
+        if (new_seqno != seqno) {
+            print_idl(idl, step);
+            return new_seqno;
+        }
+
+        if (rpc) {
+            jsonrpc_wait(rpc);
+        }
+        ovsdb_idl_wait(idl);
+        poll_block();
+    }
+}
+
+static void
+parse_uuids(const struct json *json, struct ovsdb_symbol_table *symtab,
+            size_t *n)
+{
+    struct uuid uuid;
+
+    if (json->type == JSON_STRING && uuid_from_string(&uuid, json->u.string)) {
+        char *name = xasprintf("#%d#", *n);
+        fprintf(stderr, "%s = "UUID_FMT"\n", name, UUID_ARGS(&uuid));
+        ovsdb_symbol_table_put(symtab, name, &uuid, false);
+        free(name);
+        *n += 1;
+    } else if (json->type == JSON_ARRAY) {
+        size_t i;
+
+        for (i = 0; i < json->u.array.n; i++) {
+            parse_uuids(json->u.array.elems[i], symtab, n);
+        }
+    } else if (json->type == JSON_OBJECT) {
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, json_object(json)) {
+            parse_uuids(node->data, symtab, n);
+        }
+    }
+}
+
+static void
+substitute_uuids(struct json *json, const struct ovsdb_symbol_table *symtab)
+{
+    if (json->type == JSON_STRING) {
+        const struct ovsdb_symbol *symbol;
+
+        symbol = ovsdb_symbol_table_get(symtab, json->u.string);
+        if (symbol) {
+            free(json->u.string);
+            json->u.string = xasprintf(UUID_FMT, UUID_ARGS(&symbol->uuid));
+        }
+    } else if (json->type == JSON_ARRAY) {
+        size_t i;
+
+        for (i = 0; i < json->u.array.n; i++) {
+            substitute_uuids(json->u.array.elems[i], symtab);
+        }
+    } else if (json->type == JSON_OBJECT) {
+        const struct shash_node *node;
+
+        SHASH_FOR_EACH (node, json_object(json)) {
+            substitute_uuids(node->data, symtab);
+        }
+    }
+}
+
+static const struct idltest_simple *
+idltest_find_simple(struct ovsdb_idl *idl, int i)
+{
+    const struct idltest_simple *s;
+
+    IDLTEST_SIMPLE_FOR_EACH (s, idl) {
+        if (s->i == i) {
+            return s;
+        }
+    }
+    return NULL;
+}
+
+static void
+idl_set(struct ovsdb_idl *idl, char *commands, int step)
+{
+    char *cmd, *save_ptr1 = NULL;
+    struct ovsdb_idl_txn *txn;
+    enum ovsdb_idl_txn_status status;
+    bool increment = false;
+
+    txn = ovsdb_idl_txn_create(idl);
+    for (cmd = strtok_r(commands, ",", &save_ptr1); cmd;
+         cmd = strtok_r(NULL, ",", &save_ptr1)) {
+        char *save_ptr2 = NULL;
+        char *name, *arg1, *arg2, *arg3;
+
+        name = strtok_r(cmd, " ", &save_ptr2);
+        arg1 = strtok_r(NULL, " ", &save_ptr2);
+        arg2 = strtok_r(NULL, " ", &save_ptr2);
+        arg3 = strtok_r(NULL, " ", &save_ptr2);
+
+        if (!strcmp(name, "set")) {
+            const struct idltest_simple *s;
+
+            if (!arg3) {
+                ovs_fatal(0, "\"set\" command requires 3 arguments");
+            }
+
+            s = idltest_find_simple(idl, atoi(arg1));
+            if (!s) {
+                ovs_fatal(0, "\"set\" command asks for nonexistent "
+                          "i=%d", atoi(arg1));
+            }
+
+            if (!strcmp(arg2, "b")) {
+                idltest_simple_set_b(s, atoi(arg3));
+            } else if (!strcmp(arg2, "s")) {
+                idltest_simple_set_s(s, arg3);
+            } else if (!strcmp(arg2, "u")) {
+                struct uuid uuid;
+                uuid_from_string(&uuid, arg3);
+                idltest_simple_set_u(s, uuid);
+            } else if (!strcmp(arg2, "r")) {
+                idltest_simple_set_r(s, atof(arg3));
+            } else {
+                ovs_fatal(0, "\"set\" command asks for unknown column %s",
+                          arg2);
+            }
+        } else if (!strcmp(name, "insert")) {
+            struct idltest_simple *s;
+
+            if (!arg1 || arg2) {
+                ovs_fatal(0, "\"set\" command requires 1 argument");
+            }
+
+            s = idltest_simple_insert(txn);
+            idltest_simple_set_i(s, atoi(arg1));
+        } else if (!strcmp(name, "delete")) {
+            const struct idltest_simple *s;
+
+            if (!arg1 || arg2) {
+                ovs_fatal(0, "\"set\" command requires 1 argument");
+            }
+
+            s = idltest_find_simple(idl, atoi(arg1));
+            if (!s) {
+                ovs_fatal(0, "\"set\" command asks for nonexistent "
+                          "i=%d", atoi(arg1));
+            }
+            idltest_simple_delete(s);
+        } else if (!strcmp(name, "increment")) {
+            if (!arg2 || arg3) {
+                ovs_fatal(0, "\"set\" command requires 2 arguments");
+            }
+            ovsdb_idl_txn_increment(txn, arg1, arg2, NULL);
+            increment = true;
+        } else {
+            ovs_fatal(0, "unknown command %s", name);
+        }
+    }
+
+    while ((status = ovsdb_idl_txn_commit(txn)) == TXN_INCOMPLETE) {
+        ovsdb_idl_run(idl);
+        ovsdb_idl_wait(idl);
+        ovsdb_idl_txn_wait(txn);
+        poll_block();
+    }
+    printf("%03d: commit, status=%s",
+           step, ovsdb_idl_txn_status_to_string(status));
+    if (increment) {
+        printf(", increment=%"PRId64,
+               ovsdb_idl_txn_get_increment_new_value(txn));
+    }
+    putchar('\n');
+    ovsdb_idl_txn_destroy(txn);
+}
+
+static void
+do_idl(int argc, char *argv[])
+{
+    struct jsonrpc *rpc;
+    struct ovsdb_idl *idl;
+    unsigned int seqno = 0;
+    struct ovsdb_symbol_table *symtab;
+    size_t n_uuids = 0;
+    int step = 0;
+    int error;
+    int i;
+
+    idl = ovsdb_idl_create(argv[1], &idltest_idl_class);
+    if (argc > 2) {
+        struct stream *stream;
+
+        error = stream_open_block(argv[1], &stream);
+        if (error) {
+            ovs_fatal(error, "failed to connect to \"%s\"", argv[1]);
+        }
+        rpc = jsonrpc_open(stream);
+    } else {
+        rpc = NULL;
+    }
+
+    symtab = ovsdb_symbol_table_create();
+    for (i = 2; i < argc; i++) {
+        struct jsonrpc_msg *request, *reply;
+        int error;
+
+        seqno = print_updated_idl(idl, rpc, step++, seqno);
+
+        if (!strcmp(argv[i], "reconnect")) {
+            printf("%03d: reconnect\n", step++);
+            ovsdb_idl_force_reconnect(idl);
+        } else if (argv[i][0] != '[') {
+            idl_set(idl, argv[i], step++);
+        } else {
+            struct json *json = parse_json(argv[i]);
+            substitute_uuids(json, symtab);
+            request = jsonrpc_create_request("transact", json, NULL);
+            error = jsonrpc_transact_block(rpc, request, &reply);
+            if (error) {
+                ovs_fatal(error, "jsonrpc transaction failed");
+            }
+            printf("%03d: ", step++);
+            if (reply->result) {
+                parse_uuids(reply->result, symtab, &n_uuids);
+            }
+            json_destroy(reply->id);
+            reply->id = NULL;
+            print_and_free_json(jsonrpc_msg_to_json(reply));
+        }
+    }
+    ovsdb_symbol_table_destroy(symtab);
+
+    if (rpc) {
+        jsonrpc_close(rpc);
+    }
+    print_updated_idl(idl, NULL, step++, seqno);
+    ovsdb_idl_destroy(idl);
+    printf("%03d: done\n", step);
+}
+
+static struct command all_commands[] = {
+    { "log-io", 2, INT_MAX, do_log_io },
+    { "parse-atomic-type", 1, 1, do_parse_atomic_type },
+    { "parse-type", 1, 1, do_parse_type },
+    { "parse-atoms", 2, INT_MAX, do_parse_atoms },
+    { "parse-atom-strings", 2, INT_MAX, do_parse_atom_strings },
+    { "parse-data", 2, INT_MAX, do_parse_data },
+    { "parse-data-strings", 2, INT_MAX, do_parse_data_strings },
+    { "sort-atoms", 2, 2, do_sort_atoms },
+    { "parse-column", 2, 2, do_parse_column },
+    { "parse-table", 2, 2, do_parse_table },
+    { "parse-rows", 2, INT_MAX, do_parse_rows },
+    { "compare-rows", 2, INT_MAX, do_compare_rows },
+    { "parse-conditions", 2, INT_MAX, do_parse_conditions },
+    { "evaluate-conditions", 3, 3, do_evaluate_conditions },
+    { "parse-mutations", 2, INT_MAX, do_parse_mutations },
+    { "execute-mutations", 3, 3, do_execute_mutations },
+    { "query", 3, 3, do_query },
+    { "query-distinct", 4, 4, do_query_distinct },
+    { "transact", 1, INT_MAX, do_transact },
+    { "execute", 2, INT_MAX, do_execute },
+    { "trigger", 2, INT_MAX, do_trigger },
+    { "idl", 1, INT_MAX, do_idl },
+    { "help", 0, INT_MAX, do_help },
+    { NULL, 0, 0, NULL },
+};
diff --git a/tests/test-reconnect.c b/tests/test-reconnect.c
new file mode 100644 (file)
index 0000000..8441fad
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "reconnect.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "svec.h"
+#include "util.h"
+
+static struct reconnect *reconnect;
+static int now;
+
+static const struct command commands[];
+
+static void diff_stats(const struct reconnect_stats *old,
+                       const struct reconnect_stats *new);
+
+int
+main(void)
+{
+    struct reconnect_stats prev;
+    unsigned int old_max_tries;
+    int old_time;
+    char line[128];
+
+    now = 1000;
+    reconnect = reconnect_create(now);
+    reconnect_set_name(reconnect, "remote");
+    reconnect_get_stats(reconnect, now, &prev);
+    printf("### t=%d ###\n", now);
+    old_time = now;
+    old_max_tries = reconnect_get_max_tries(reconnect);
+    while (fgets(line, sizeof line, stdin)) {
+        struct reconnect_stats cur;
+        struct svec args;
+
+        fputs(line, stdout);
+        if (line[0] == '#') {
+            continue;
+        }
+
+        svec_init(&args);
+        svec_parse_words(&args, line);
+        svec_terminate(&args);
+        if (!svec_is_empty(&args)) {
+            run_command(args.n, args.names, commands);
+        }
+        svec_destroy(&args);
+
+        if (old_time != now) {
+            printf("\n### t=%d ###\n", now);
+            old_time = now;
+        }
+
+        reconnect_get_stats(reconnect, now, &cur);
+        diff_stats(&prev, &cur);
+        prev = cur;
+        if (reconnect_get_max_tries(reconnect) != old_max_tries) {
+            old_max_tries = reconnect_get_max_tries(reconnect);
+            printf("  %u tries left\n", old_max_tries);
+        }
+    }
+
+    return 0;
+}
+
+static void
+do_enable(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_enable(reconnect, now);
+}
+
+static void
+do_disable(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_disable(reconnect, now);
+}
+
+static void
+do_force_reconnect(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_force_reconnect(reconnect, now);
+}
+
+static int
+error_from_string(const char *s)
+{
+    if (!s) {
+        return 0;
+    } else if (!strcmp(s, "ECONNREFUSED")) {
+        return ECONNREFUSED;
+    } else if (!strcmp(s, "EOF")) {
+        return EOF;
+    } else {
+        ovs_fatal(0, "unknown error '%s'", s);
+    }
+}
+
+static void
+do_disconnected(int argc UNUSED, char *argv[])
+{
+    reconnect_disconnected(reconnect, now, error_from_string(argv[1]));
+}
+
+static void
+do_connecting(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_connecting(reconnect, now);
+}
+
+static void
+do_connect_failed(int argc UNUSED, char *argv[])
+{
+    reconnect_connect_failed(reconnect, now, error_from_string(argv[1]));
+}
+
+static void
+do_connected(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_connected(reconnect, now);
+}
+
+static void
+do_received(int argc UNUSED, char *argv[] UNUSED)
+{
+    reconnect_received(reconnect, now);
+}
+
+static void
+do_run(int argc, char *argv[])
+{
+    enum reconnect_action action;
+
+    if (argc > 1) {
+        now += atoi(argv[1]);
+    }
+
+    action = reconnect_run(reconnect, now);
+    switch (action) {
+    default:
+        if (action != 0) {
+            NOT_REACHED();
+        }
+        break;
+
+    case RECONNECT_CONNECT:
+        printf("  should connect\n");
+        break;
+
+    case RECONNECT_DISCONNECT:
+        printf("  should disconnect\n");
+        break;
+
+    case RECONNECT_PROBE:
+        printf("  should send probe\n");
+        break;
+    }
+}
+
+static void
+do_advance(int argc UNUSED, char *argv[])
+{
+    now += atoi(argv[1]);
+}
+
+static void
+do_timeout(int argc UNUSED, char *argv[] UNUSED)
+{
+    int timeout = reconnect_timeout(reconnect, now);
+    if (timeout >= 0) {
+        printf("  advance %d ms\n", timeout);
+        now += timeout;
+    } else {
+        printf("  no timeout\n");
+    }
+}
+
+static void
+do_set_max_tries(int argc UNUSED, char *argv[])
+{
+    reconnect_set_max_tries(reconnect, atoi(argv[1]));
+}
+
+static void
+diff_stats(const struct reconnect_stats *old,
+           const struct reconnect_stats *new)
+{
+    if (old->state != new->state
+        || old->state_elapsed != new->state_elapsed
+        || old->backoff != new->backoff) {
+        printf("  in %s for %u ms (%d ms backoff)\n",
+               new->state, new->state_elapsed, new->backoff);
+    }
+    if (old->creation_time != new->creation_time
+        || old->last_received != new->last_received
+        || old->last_connected != new->last_connected) {
+        printf("  created %lld, last received %lld, last connected %lld\n",
+               new->creation_time, new->last_received, new->last_connected);
+    }
+    if (old->n_successful_connections != new->n_successful_connections
+        || old->n_attempted_connections != new->n_attempted_connections
+        || old->seqno != new->seqno) {
+        printf("  %u successful connections out of %u attempts, seqno %u\n",
+               new->n_successful_connections, new->n_attempted_connections,
+               new->seqno);
+    }
+    if (old->is_connected != new->is_connected
+        || old->current_connection_duration != new->current_connection_duration
+        || old->total_connected_duration != new->total_connected_duration) {
+        printf("  %sconnected (%u ms), total %u ms connected\n",
+               new->is_connected ? "" : "not ",
+               new->current_connection_duration,
+               new->total_connected_duration);
+    }
+}
+
+static const struct command commands[] = {
+    { "enable", 0, 0, do_enable },
+    { "disable", 0, 0, do_disable },
+    { "force-reconnect", 0, 0, do_force_reconnect },
+    { "disconnected", 0, 1, do_disconnected },
+    { "connecting", 0, 0, do_connecting },
+    { "connect-failed", 0, 1, do_connect_failed },
+    { "connected", 0, 0, do_connected },
+    { "received", 0, 0, do_received },
+    { "run", 0, 1, do_run },
+    { "advance", 1, 1, do_advance },
+    { "timeout", 0, 0, do_timeout },
+    { "set-max-tries", 1, 1, do_set_max_tries },
+    { NULL, 0, 0, NULL },
+};
+
index ce9decc..eab13d6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -301,6 +301,7 @@ simulate(struct test_case *tc, int granularity)
                     struct bpdu *bpdu = &b->rxq[b->rxq_tail % RXQ_SIZE];
                     stp_received_bpdu(stp_get_port(b->stp, bpdu->port_no),
                                       bpdu->data, bpdu->size);
+                    free(bpdu->data);
                     any = true;
                 }
             }
@@ -357,6 +358,7 @@ get_token(void)
         pos++;
     }
     if (*pos == '\0') {
+        free(token);
         token = NULL;
         return false;
     }
@@ -644,6 +646,19 @@ main(int argc, char *argv[])
             err("trailing garbage on line");
         }
     }
+    free(token);
+
+    for (i = 0; i < tc->n_lans; i++) {
+        struct lan *lan = tc->lans[i];
+        free((char *) lan->name);
+        free(lan);
+    }
+    for (i = 0; i < tc->n_bridges; i++) {
+        struct bridge *bridge = tc->bridges[i];
+        stp_destroy(bridge->stp);
+        free(bridge);
+    }
+    free(tc);
 
     return 0;
 }
diff --git a/tests/test-timeval.c b/tests/test-timeval.c
new file mode 100644 (file)
index 0000000..533f81a
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "timeval.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "util.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+static long long int
+gettimeofday_in_msec(void)
+{
+    struct timeval tv;
+
+    assert(!gettimeofday(&tv, NULL));
+    return timeval_to_msec(&tv);
+}
+
+static void
+do_test(void)
+{
+    /* Wait until we are awakened by a signal (typically EINTR due to the
+     * setitimer()).  Then ensure that, if time has really advanced by
+     * TIME_UPDATE_INTERVAL, then time_msec() reports that it advanced.
+     */
+    long long int start_time_msec;
+    long long int start_gtod;
+
+    start_time_msec = time_msec();
+    start_gtod = gettimeofday_in_msec();
+    for (;;) {
+        /* Wait up to 1 second.  Using select() to do the timeout avoids
+         * interfering with the interval timer. */
+        struct timeval timeout;
+        int retval;
+
+        timeout.tv_sec = 1;
+        timeout.tv_usec = 0;
+        retval = select(0, NULL, NULL, NULL, &timeout);
+        if (retval != -1) {
+            ovs_fatal(0, "select returned %d", retval);
+        } else if (errno != EINTR) {
+            ovs_fatal(errno, "select reported unexpected error");
+        }
+
+        if (gettimeofday_in_msec() - start_gtod >= TIME_UPDATE_INTERVAL) {
+            assert(time_msec() - start_time_msec >= TIME_UPDATE_INTERVAL);
+            break;
+        }
+    }
+}
+
+static void
+usage(void)
+{
+    ovs_fatal(0, "usage: %s TEST, where TEST is \"plain\" or \"daemon\"",
+              program_name);
+}
+
+int
+main(int argc, char *argv[])
+{
+    proctitle_init(argc, argv);
+    set_program_name(argv[0]);
+    time_init();
+
+    if (argc != 2) {
+        usage();
+    } else if (!strcmp(argv[1], "plain")) {
+        do_test();
+    } else if (!strcmp(argv[1], "daemon")) {
+        /* Test that time still advances even in a daemon.  This is an
+         * interesting test because fork() cancels the interval timer. */
+        char cwd[1024], *pidfile;
+        FILE *success;
+
+        assert(getcwd(cwd, sizeof cwd) == cwd);
+
+        unlink("test-timeval.success");
+
+        /* Daemonize, with a pidfile in the current directory. */
+        set_detach();
+        pidfile = xasprintf("%s/test-timeval.pid", cwd);
+        set_pidfile(pidfile);
+        free(pidfile);
+        set_no_chdir();
+        daemonize();
+
+        /* Run the test. */
+        do_test();
+
+        /* Report success by writing out a file, since the ultimate invoker of
+         * test-timeval can't wait on the daemonized process. */
+        success = fopen("test-timeval.success", "w");
+        if (!success) {
+            ovs_fatal(errno, "test-timeval.success: create failed");
+        }
+        fprintf(success, "success\n");
+        fclose(success);
+    } else {
+        usage();
+    }
+
+    return 0;
+}
diff --git a/tests/test-uuid.c b/tests/test-uuid.c
new file mode 100644 (file)
index 0000000..750b74e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "uuid.h"
+#include <stdio.h>
+
+int
+main(int argc, char *argv[])
+{
+    struct uuid uuid;
+
+    if (argc == 1) {
+        uuid_generate(&uuid);
+    } else if (argc == 2) {
+        if (!uuid_from_string(&uuid, argv[1])) {
+            ovs_fatal(0, "\"%s\" is not a valid UUID", argv[1]);
+        }
+    } else {
+        ovs_fatal(0, "usage: %s [UUID]", argv[0]);
+    }
+
+    printf(UUID_FMT"\n", UUID_ARGS(&uuid));
+
+    return 0;
+}
index 1bcd8c6..4ce2f71 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009 Nicira Networks.
+ * Copyright (c) 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <signal.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include "command-line.h"
 #include "poll-loop.h"
 #include "socket-util.h"
+#include "stream.h"
+#include "stream-ssl.h"
 #include "timeval.h"
 #include "util.h"
 #include "vlog.h"
@@ -34,94 +37,90 @@ struct fake_pvconn {
     const char *type;
     char *pvconn_name;
     char *vconn_name;
-    int fd;
+    struct pstream *pstream;
 };
 
+static void
+check(int a, int b, const char *as, const char *file, int line)
+{
+    if (a != b) {
+        ovs_fatal(0, "%s:%d: %s is %d but should be %d", file, line, as, a, b);
+    }
+}
+
+
+#define CHECK(A, B) check(A, B, #A, __FILE__, __LINE__)
+
+static void
+check_errno(int a, int b, const char *as, const char *file, int line)
+{
+    if (a != b) {
+        ovs_fatal(0, "%s:%d: %s is %d (%s) but should be %d (%s)",
+                  file, line, as, a, strerror(abs(a)), b, strerror(abs(b)));
+    }
+}
+
+#define CHECK_ERRNO(A, B) check_errno(A, B, #A, __FILE__, __LINE__)
+
 static void
 fpv_create(const char *type, struct fake_pvconn *fpv)
 {
+#ifdef HAVE_OPENSSL
+    if (!strcmp(type, "ssl")) {
+        stream_ssl_set_private_key_file("testpki-privkey.pem");
+        stream_ssl_set_certificate_file("testpki-cert.pem");
+        stream_ssl_set_ca_cert_file("testpki-cacert.pem", false);
+    }
+#endif
+
     fpv->type = type;
     if (!strcmp(type, "unix")) {
         static int unix_count = 0;
         char *bind_path;
-        int fd;
 
         bind_path = xasprintf("fake-pvconn.%d", unix_count++);
-        fd = make_unix_socket(SOCK_STREAM, false, false, bind_path, NULL);
-        if (fd < 0) {
-            ovs_fatal(-fd, "%s: could not bind to Unix domain socket",
-                      bind_path);
-        }
-
         fpv->pvconn_name = xasprintf("punix:%s", bind_path);
         fpv->vconn_name = xasprintf("unix:%s", bind_path);
-        fpv->fd = fd;
+        CHECK_ERRNO(pstream_open(fpv->pvconn_name, &fpv->pstream), 0);
         free(bind_path);
-    } else if (!strcmp(type, "tcp")) {
-        struct sockaddr_in sin;
-        socklen_t sin_len;
-        int fd;
-
-        /* Create TCP socket. */
-        fd = socket(PF_INET, SOCK_STREAM, 0);
-        if (fd < 0) {
-            ovs_fatal(errno, "failed to create TCP socket");
-        }
+    } else if (!strcmp(type, "tcp") || !strcmp(type, "ssl")) {
+        char *s, *method, *port, *save_ptr = NULL;
+        char *open_name;
 
-        /* Bind TCP socket to localhost on any available port. */
-        sin.sin_family = AF_INET;
-        sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-        sin.sin_port = htons(0);
-        if (bind(fd, (struct sockaddr *) &sin, sizeof sin) < 0) {
-            ovs_fatal(errno, "failed to bind TCP socket");
-        }
+        open_name = xasprintf("p%s:0:127.0.0.1", type);
+        CHECK_ERRNO(pstream_open(open_name, &fpv->pstream), 0);
 
-        /* Retrieve socket's port number. */
-        sin_len = sizeof sin;
-        if (getsockname(fd, (struct sockaddr *)&sin, &sin_len) < 0) {
-            ovs_fatal(errno, "failed to read TCP socket name");
-        }
-        if (sin_len != sizeof sin || sin.sin_family != AF_INET) {
-            ovs_fatal(errno, "bad TCP socket name");
-        }
+        /* Extract bound port number from pstream name. */
+        s = xstrdup(pstream_get_name(fpv->pstream));
+        method = strtok_r(s, ":", &save_ptr);
+        port = strtok_r(NULL, ":", &save_ptr);
 
         /* Save info. */
-        fpv->pvconn_name = xasprintf("ptcp:%"PRIu16":127.0.0.1",
-                                    ntohs(sin.sin_port));
-        fpv->vconn_name = xasprintf("tcp:127.0.0.1:%"PRIu16,
-                                    ntohs(sin.sin_port));
-        fpv->fd = fd;
+        fpv->pvconn_name = xstrdup(pstream_get_name(fpv->pstream));
+        fpv->vconn_name = xasprintf("%s:127.0.0.1:%s", type, port);
+
+        free(open_name);
+        free(s);
     } else {
         abort();
     }
-
-    /* Listen. */
-    if (listen(fpv->fd, 0) < 0) {
-        ovs_fatal(errno, "%s: listen failed", fpv->vconn_name);
-    }
 }
 
-static int
+static struct stream *
 fpv_accept(struct fake_pvconn *fpv)
 {
-    int fd;
+    struct stream *stream;
 
-    fd = accept(fpv->fd, NULL, NULL);
-    if (fd < 0) {
-        ovs_fatal(errno, "%s: accept failed", fpv->pvconn_name);
-    }
-    return fd;
+    CHECK_ERRNO(pstream_accept_block(fpv->pstream, &stream), 0);
+
+    return stream;
 }
 
 static void
 fpv_close(struct fake_pvconn *fpv)
 {
-    if (fpv->fd >= 0) {
-        if (close(fpv->fd) < 0) {
-            ovs_fatal(errno, "failed to close %s fake pvconn", fpv->type);
-        }
-        fpv->fd = -1;
-    }
+    pstream_close(fpv->pstream);
+    fpv->pstream = NULL;
 }
 
 static void
@@ -135,15 +134,20 @@ fpv_destroy(struct fake_pvconn *fpv)
 /* Connects to a fake_pvconn with vconn_open(), then closes the listener and
  * verifies that vconn_connect() reports 'expected_error'. */
 static void
-test_refuse_connection(const char *type, int expected_error)
+test_refuse_connection(int argc UNUSED, char *argv[])
 {
+    const char *type = argv[1];
+    int expected_error;
     struct fake_pvconn fpv;
     struct vconn *vconn;
 
+    expected_error = !strcmp(type, "unix") ? EPIPE : ECONNRESET;
+
     fpv_create(type, &fpv);
-    assert(!vconn_open(fpv.vconn_name, OFP_VERSION, &vconn));
+    CHECK_ERRNO(vconn_open(fpv.vconn_name, OFP_VERSION, &vconn), 0);
     fpv_close(&fpv);
-    assert(vconn_connect(vconn) == expected_error);
+    vconn_run(vconn);
+    CHECK_ERRNO(vconn_connect(vconn), expected_error);
     vconn_close(vconn);
     fpv_destroy(&fpv);
 }
@@ -152,16 +156,23 @@ test_refuse_connection(const char *type, int expected_error)
  * closes it immediately, and verifies that vconn_connect() reports
  * 'expected_error'. */
 static void
-test_accept_then_close(const char *type, int expected_error)
+test_accept_then_close(int argc UNUSED, char *argv[])
 {
+    const char *type = argv[1];
+    int expected_error;
     struct fake_pvconn fpv;
     struct vconn *vconn;
 
+    expected_error = (!strcmp(type, "unix") ? EPIPE
+                      : !strcmp(type, "tcp") ? ECONNRESET
+                      : EPROTO);
+
     fpv_create(type, &fpv);
-    assert(!vconn_open(fpv.vconn_name, OFP_VERSION, &vconn));
-    close(fpv_accept(&fpv));
+    CHECK_ERRNO(vconn_open(fpv.vconn_name, OFP_VERSION, &vconn), 0);
+    vconn_run(vconn);
+    stream_close(fpv_accept(&fpv));
     fpv_close(&fpv);
-    assert(vconn_connect(vconn) == expected_error);
+    CHECK_ERRNO(vconn_connect(vconn), expected_error);
     vconn_close(vconn);
     fpv_destroy(&fpv);
 }
@@ -170,38 +181,41 @@ test_accept_then_close(const char *type, int expected_error)
  * reads the hello message from it, then closes the connection and verifies
  * that vconn_connect() reports 'expected_error'. */
 static void
-test_read_hello(const char *type, int expected_error)
+test_read_hello(int argc UNUSED, char *argv[])
 {
+    const char *type = argv[1];
     struct fake_pvconn fpv;
     struct vconn *vconn;
-    int fd;
+    struct stream *stream;
 
     fpv_create(type, &fpv);
-    assert(!vconn_open(fpv.vconn_name, OFP_VERSION, &vconn));
-    fd = fpv_accept(&fpv);
+    CHECK_ERRNO(vconn_open(fpv.vconn_name, OFP_VERSION, &vconn), 0);
+    vconn_run(vconn);
+    stream = fpv_accept(&fpv);
     fpv_destroy(&fpv);
-    assert(!set_nonblocking(fd));
     for (;;) {
        struct ofp_header hello;
        int retval;
 
-       retval = read(fd, &hello, sizeof hello);
+       retval = stream_recv(stream, &hello, sizeof hello);
        if (retval == sizeof hello) {
-           assert(hello.version == OFP_VERSION);
-           assert(hello.type == OFPT_HELLO);
-           assert(hello.length == htons(sizeof hello));
+           CHECK(hello.version, OFP_VERSION);
+           CHECK(hello.type, OFPT_HELLO);
+           CHECK(hello.length, htons(sizeof hello));
            break;
        } else {
-           assert(errno == EAGAIN);
+           CHECK_ERRNO(retval, -EAGAIN);
        }
 
-       assert(vconn_connect(vconn) == EAGAIN);
+       vconn_run(vconn);
+       CHECK_ERRNO(vconn_connect(vconn), EAGAIN);
+       vconn_run_wait(vconn);
        vconn_connect_wait(vconn);
-       poll_fd_wait(fd, POLLIN);
+       stream_recv_wait(stream);
        poll_block();
     }
-    close(fd);
-    assert(vconn_connect(vconn) == expected_error);
+    stream_close(stream);
+    CHECK_ERRNO(vconn_connect(vconn), ECONNRESET);
     vconn_close(vconn);
 }
 
@@ -217,44 +231,62 @@ test_send_hello(const char *type, const void *out, size_t out_size,
     struct vconn *vconn;
     bool read_hello, connected;
     struct ofpbuf *msg;
-    int fd;
+    struct stream *stream;
+    size_t n_sent;
 
     fpv_create(type, &fpv);
-    assert(!vconn_open(fpv.vconn_name, OFP_VERSION, &vconn));
-    fd = fpv_accept(&fpv);
+    CHECK_ERRNO(vconn_open(fpv.vconn_name, OFP_VERSION, &vconn), 0);
+    vconn_run(vconn);
+    stream = fpv_accept(&fpv);
     fpv_destroy(&fpv);
 
-    write(fd, out, out_size);
-
-    assert(!set_nonblocking(fd));
+    n_sent = 0;
+    while (n_sent < out_size) {
+        int retval;
+
+        retval = stream_send(stream, (char *) out + n_sent, out_size - n_sent);
+        if (retval > 0) {
+            n_sent += retval;
+        } else if (retval == -EAGAIN) {
+            stream_run(stream);
+            vconn_run(vconn);
+            stream_recv_wait(stream);
+            vconn_connect_wait(vconn);
+            vconn_run_wait(vconn);
+            poll_block();
+        } else {
+            ovs_fatal(0, "stream_send returned unexpected value %d", retval);
+        }
+    }
 
     read_hello = connected = false;
     for (;;) {
        if (!read_hello) {
            struct ofp_header hello;
-           int retval = read(fd, &hello, sizeof hello);
+           int retval = stream_recv(stream, &hello, sizeof hello);
            if (retval == sizeof hello) {
-               assert(hello.version == OFP_VERSION);
-               assert(hello.type == OFPT_HELLO);
-               assert(hello.length == htons(sizeof hello));
+               CHECK(hello.version, OFP_VERSION);
+               CHECK(hello.type, OFPT_HELLO);
+               CHECK(hello.length, htons(sizeof hello));
                read_hello = true;
            } else {
-               assert(errno == EAGAIN);
+               CHECK_ERRNO(retval, -EAGAIN);
            }
        }
 
+       vconn_run(vconn);
        if (!connected) {
            int error = vconn_connect(vconn);
            if (error == expect_connect_error) {
                if (!error) {
                    connected = true;
                } else {
-                   close(fd);
+                   stream_close(stream);
                    vconn_close(vconn);
                    return;
                }
            } else {
-               assert(error == EAGAIN);
+               CHECK_ERRNO(error, EAGAIN);
            }
        }
 
@@ -262,23 +294,25 @@ test_send_hello(const char *type, const void *out, size_t out_size,
            break;
        }
 
+       vconn_run_wait(vconn);
        if (!connected) {
            vconn_connect_wait(vconn);
        }
        if (!read_hello) {
-           poll_fd_wait(fd, POLLIN);
+           stream_recv_wait(stream);
        }
        poll_block();
     }
-    close(fd);
-    assert(vconn_recv(vconn, &msg) == EOF);
+    stream_close(stream);
+    CHECK_ERRNO(vconn_recv(vconn, &msg), EOF);
     vconn_close(vconn);
 }
 
 /* Try connecting and sending a normal hello, which should succeed. */
 static void
-test_send_plain_hello(const char *type)
+test_send_plain_hello(int argc UNUSED, char *argv[])
 {
+    const char *type = argv[1];
     struct ofp_header hello;
 
     hello.version = OFP_VERSION;
@@ -292,8 +326,9 @@ test_send_plain_hello(const char *type)
  * the specification says that implementations must accept and ignore extra
  * data). */
 static void
-test_send_long_hello(const char *type)
+test_send_long_hello(int argc UNUSED, char *argv[])
 {
+    const char *type = argv[1];
     struct ofp_header hello;
     char buffer[sizeof hello * 2];
 
@@ -309,8 +344,9 @@ test_send_long_hello(const char *type)
 /* Try connecting and sending an echo request instead of a hello, which should
  * fail with EPROTO. */
 static void
-test_send_echo_hello(const char *type)
+test_send_echo_hello(int argc UNUSED, char *argv[])
 {
+    const char *type = argv[1];
     struct ofp_header echo;
 
     echo.version = OFP_VERSION;
@@ -323,8 +359,9 @@ test_send_echo_hello(const char *type)
 /* Try connecting and sending a hello packet that has its length field as 0,
  * which should fail with EPROTO. */
 static void
-test_send_short_hello(const char *type)
+test_send_short_hello(int argc UNUSED, char *argv[])
 {
+    const char *type = argv[1];
     struct ofp_header hello;
 
     memset(&hello, 0, sizeof hello);
@@ -334,8 +371,9 @@ test_send_short_hello(const char *type)
 /* Try connecting and sending a hello packet that has a bad version, which
  * should fail with EPROTO. */
 static void
-test_send_invalid_version_hello(const char *type)
+test_send_invalid_version_hello(int argc UNUSED, char *argv[])
 {
+    const char *type = argv[1];
     struct ofp_header hello;
 
     hello.version = OFP_VERSION - 1;
@@ -345,40 +383,31 @@ test_send_invalid_version_hello(const char *type)
     test_send_hello(type, &hello, sizeof hello, EPROTO);
 }
 
+static const struct command commands[] = {
+    {"refuse-connection", 1, 1, test_refuse_connection},
+    {"accept-then-close", 1, 1, test_accept_then_close},
+    {"read-hello", 1, 1, test_read_hello},
+    {"send-plain-hello", 1, 1, test_send_plain_hello},
+    {"send-long-hello", 1, 1, test_send_long_hello},
+    {"send-echo-hello", 1, 1, test_send_echo_hello},
+    {"send-short-hello", 1, 1, test_send_short_hello},
+    {"send-invalid-version-hello", 1, 1, test_send_invalid_version_hello},
+    {NULL, 0, 0, NULL},
+};
+
 int
-main(int argc UNUSED, char *argv[])
+main(int argc, char *argv[])
 {
     set_program_name(argv[0]);
     time_init();
     vlog_init();
-    signal(SIGPIPE, SIG_IGN);
     vlog_set_levels(VLM_ANY_MODULE, VLF_ANY_FACILITY, VLL_EMER);
+    vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_DBG);
+    signal(SIGPIPE, SIG_IGN);
 
     time_alarm(10);
 
-    test_refuse_connection("unix", EPIPE);
-    test_refuse_connection("tcp", ECONNRESET);
-
-    test_accept_then_close("unix", EPIPE);
-    test_accept_then_close("tcp", ECONNRESET);
-
-    test_read_hello("unix", ECONNRESET);
-    test_read_hello("tcp", ECONNRESET);
-
-    test_send_plain_hello("unix");
-    test_send_plain_hello("tcp");
-
-    test_send_long_hello("unix");
-    test_send_long_hello("tcp");
-
-    test_send_echo_hello("unix");
-    test_send_echo_hello("tcp");
-
-    test_send_short_hello("unix");
-    test_send_short_hello("tcp");
-
-    test_send_invalid_version_hello("unix");
-    test_send_invalid_version_hello("tcp");
+    run_command(argc - 1, argv + 1, commands);
 
     return 0;
 }
diff --git a/tests/testpki-cacert.pem b/tests/testpki-cacert.pem
new file mode 100644 (file)
index 0000000..e888505
--- /dev/null
@@ -0,0 +1,70 @@
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 1 (0x1)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=CA, O=Open vSwitch, OU=switchca, CN=OVS switchca CA Certificate (2010 Jan 06 17:08:30)
+        Validity
+            Not Before: Jan  7 01:08:32 2010 GMT
+            Not After : Jan  7 01:08:32 2016 GMT
+        Subject: C=US, ST=CA, O=Open vSwitch, OU=switchca, CN=OVS switchca CA Certificate (2010 Jan 06 17:08:30)
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:cc:b2:25:ba:07:b8:d6:e1:23:20:1e:41:a1:49:
+                    35:68:09:71:19:ef:68:a0:45:e0:bd:33:41:0d:2b:
+                    b7:7d:33:16:57:d4:16:da:ba:a0:7e:ae:9c:76:5b:
+                    92:93:96:a9:5b:bd:6f:b3:fd:6a:62:b9:10:46:98:
+                    d9:b4:ea:ab:99:f3:72:4b:d1:11:81:77:75:09:be:
+                    fd:9f:55:f7:6c:78:0a:b1:9d:f8:c5:c5:a0:de:05:
+                    0d:78:62:66:ed:b1:0f:b3:9a:69:fd:13:9f:43:a7:
+                    aa:e4:3c:a1:63:68:46:c2:a1:56:56:eb:62:b5:0e:
+                    2b:be:7b:8e:c9:aa:c2:6f:04:af:7b:5a:ed:4b:16:
+                    fb:47:4d:45:81:d8:b8:2e:08:21:a7:4d:cc:78:9b:
+                    b0:b0:a0:18:91:53:ab:64:c3:eb:66:74:93:cc:8a:
+                    b4:40:c5:4e:2e:cc:c5:63:c0:6b:2d:6e:cd:b9:1c:
+                    a9:45:ad:82:0a:d2:1f:5d:84:bc:29:a1:82:0d:75:
+                    1c:1a:21:8b:15:03:88:94:e5:89:ed:48:22:e0:7e:
+                    b9:15:f1:13:fb:6c:a2:48:c7:2d:e5:01:04:b7:23:
+                    6a:06:45:7b:e3:14:59:ac:1d:87:e6:a5:ec:7c:86:
+                    80:17:64:71:a0:43:27:27:f5:2c:bd:34:60:c7:a1:
+                    22:3f
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: md5WithRSAEncryption
+        c7:85:13:17:b6:ca:c8:1b:8a:8e:eb:3b:64:05:e4:d5:a2:2c:
+        6b:ee:83:d8:e0:67:f9:99:59:15:59:9d:6d:16:c0:6b:c3:ed:
+        61:31:0a:40:1b:63:1d:57:a5:67:3d:46:55:6b:9f:ed:18:79:
+        45:fc:db:d9:48:d2:86:0f:aa:e0:43:18:3f:f4:e3:71:a3:28:
+        d4:00:ae:7c:0e:91:2c:5b:5b:ff:be:ad:b6:4b:b7:0e:e3:ea:
+        7a:66:69:6c:83:90:0c:59:c1:d7:4d:1a:b9:69:0d:ac:6e:07:
+        b3:42:3c:3e:54:ac:85:c5:58:67:51:2a:c0:05:1f:70:6a:07:
+        86:2c:42:56:ee:3b:69:7b:db:35:e6:c6:5b:eb:25:66:ca:89:
+        bb:d7:37:ae:d2:b9:e8:56:38:a2:ec:ff:45:38:97:ae:43:20:
+        c8:55:c9:c8:0f:45:37:70:97:b9:8a:2e:56:52:6f:20:f3:08:
+        b7:1a:26:98:b9:d9:7d:52:69:b3:95:2b:c5:4e:0c:7b:fd:cd:
+        6a:a2:23:cf:eb:ee:de:74:17:0b:cc:a3:91:f1:41:0b:1e:94:
+        e2:ea:52:85:c1:3d:de:f9:e6:44:5a:f6:fe:7d:2f:fb:6f:60:
+        89:2c:f0:0c:c7:c7:fb:6f:23:4d:a1:18:89:28:ea:61:f4:3a:
+        9d:ca:1f:60
+-----BEGIN CERTIFICATE-----
+MIIDeDCCAmACAQEwDQYJKoZIhvcNAQEEBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD
+VQQIEwJDQTEVMBMGA1UEChMMT3BlbiB2U3dpdGNoMREwDwYDVQQLEwhzd2l0Y2hj
+YTE7MDkGA1UEAxMyT1ZTIHN3aXRjaGNhIENBIENlcnRpZmljYXRlICgyMDEwIEph
+biAwNiAxNzowODozMCkwHhcNMTAwMTA3MDEwODMyWhcNMTYwMTA3MDEwODMyWjCB
+gTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQKEwxPcGVuIHZTd2l0
+Y2gxETAPBgNVBAsTCHN3aXRjaGNhMTswOQYDVQQDEzJPVlMgc3dpdGNoY2EgQ0Eg
+Q2VydGlmaWNhdGUgKDIwMTAgSmFuIDA2IDE3OjA4OjMwKTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAMyyJboHuNbhIyAeQaFJNWgJcRnvaKBF4L0zQQ0r
+t30zFlfUFtq6oH6unHZbkpOWqVu9b7P9amK5EEaY2bTqq5nzckvREYF3dQm+/Z9V
+92x4CrGd+MXFoN4FDXhiZu2xD7Oaaf0Tn0OnquQ8oWNoRsKhVlbrYrUOK757jsmq
+wm8Er3ta7UsW+0dNRYHYuC4IIadNzHibsLCgGJFTq2TD62Z0k8yKtEDFTi7MxWPA
+ay1uzbkcqUWtggrSH12EvCmhgg11HBohixUDiJTlie1IIuB+uRXxE/tsokjHLeUB
+BLcjagZFe+MUWawdh+al7HyGgBdkcaBDJyf1LL00YMehIj8CAwEAATANBgkqhkiG
+9w0BAQQFAAOCAQEAx4UTF7bKyBuKjus7ZAXk1aIsa+6D2OBn+ZlZFVmdbRbAa8Pt
+YTEKQBtjHVelZz1GVWuf7Rh5Rfzb2UjShg+q4EMYP/TjcaMo1ACufA6RLFtb/76t
+tku3DuPqemZpbIOQDFnB100auWkNrG4Hs0I8PlSshcVYZ1EqwAUfcGoHhixCVu47
+aXvbNebGW+slZsqJu9c3rtK56FY4ouz/RTiXrkMgyFXJyA9FN3CXuYouVlJvIPMI
+txommLnZfVJps5UrxU4Me/3NaqIjz+vu3nQXC8yjkfFBCx6U4upShcE93vnmRFr2
+/n0v+29giSzwDMfH+28jTaEYiSjqYfQ6ncofYA==
+-----END CERTIFICATE-----
diff --git a/tests/testpki-cert.pem b/tests/testpki-cert.pem
new file mode 100644 (file)
index 0000000..75d815d
--- /dev/null
@@ -0,0 +1,70 @@
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 2 (0x2)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=CA, O=Open vSwitch, OU=switchca, CN=OVS switchca CA Certificate (2010 Jan 06 17:08:30)
+        Validity
+            Not Before: Jan  7 01:08:59 2010 GMT
+            Not After : Jan  7 01:08:59 2011 GMT
+        Subject: C=US, ST=CA, O=Open vSwitch, OU=Open vSwitch certifier, CN=Open vSwitch certificate for testpki
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:ac:3f:c6:b1:ef:a3:e3:68:98:2c:91:a1:3a:21:
+                    02:38:87:5b:75:7a:1c:17:c9:b0:64:a9:f7:80:17:
+                    08:0f:b5:25:b4:46:80:6b:7e:92:ab:f8:93:05:17:
+                    77:e4:12:86:eb:54:5d:a7:a0:45:70:16:5e:d7:4f:
+                    6b:7c:9f:fe:83:a4:c1:62:83:33:71:6f:4f:4e:68:
+                    84:a6:92:a5:77:8f:ad:cd:ee:bf:61:72:24:c0:64:
+                    df:73:98:de:37:6b:b8:4d:78:f4:ba:06:95:64:ef:
+                    82:b1:2f:71:01:44:ca:3c:de:fa:32:28:b6:ea:72:
+                    7b:4d:d6:a0:fb:4b:73:de:a9:7f:25:ad:20:02:3d:
+                    5f:7f:7f:8e:91:34:97:0a:10:96:be:3d:ee:37:5b:
+                    a9:91:9e:7f:d5:ac:7b:e3:56:47:a4:14:15:dd:48:
+                    ce:32:6f:c4:83:09:07:31:bb:34:77:4d:f7:12:70:
+                    86:b8:b2:64:16:3b:ea:d2:72:e0:73:6b:6f:ce:59:
+                    cf:56:6d:a8:94:3c:10:d7:47:7e:b2:91:9d:c7:65:
+                    23:8a:b1:ca:9c:15:36:c5:d9:db:b1:e7:b8:1f:09:
+                    20:1d:da:97:de:93:7c:e2:5d:94:ea:38:d8:ce:60:
+                    c9:9e:43:da:6d:9d:c9:d2:a0:e9:6d:5a:9b:57:53:
+                    86:7d
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: md5WithRSAEncryption
+        19:a9:2a:66:fc:09:78:c9:87:e6:73:be:9a:d2:b7:87:07:7b:
+        93:70:04:cd:f2:c9:47:a3:8f:9f:c4:af:92:ef:cf:07:d3:83:
+        90:f7:8a:f0:55:f6:8a:2e:af:57:b9:e4:9c:72:37:b7:af:12:
+        fb:dc:07:9b:94:7b:18:c8:53:86:6d:02:77:eb:e3:ac:21:e1:
+        6d:b5:fe:04:6b:a1:d2:78:a6:58:4b:5d:a7:17:e1:3b:d9:94:
+        ab:81:5e:c1:9a:b5:34:a5:a7:9a:2b:1b:74:d7:a4:aa:fa:81:
+        5c:e5:5f:1a:07:54:36:21:76:04:a9:5e:11:38:46:b8:1c:11:
+        15:78:f8:0c:31:8d:9a:a3:e4:d0:72:a8:29:80:c2:3d:9d:f6:
+        61:dd:ca:c9:6c:7e:ca:c0:0d:61:28:4d:3e:ea:51:9d:c2:c4:
+        7c:47:da:cc:24:35:9c:2a:0d:ac:ea:5f:33:5a:ab:b7:94:cb:
+        3f:91:38:92:a3:62:3b:40:ef:79:55:96:b3:24:5a:19:a2:53:
+        99:63:f9:85:d4:b6:48:b8:9a:f8:bc:b7:74:f8:cf:95:dc:1a:
+        f2:66:cd:2b:4b:d4:c1:19:69:77:f9:f6:08:04:61:cd:80:ee:
+        46:44:27:82:49:60:a9:be:4b:51:75:ca:15:16:0b:97:c2:2f:
+        26:f2:dd:42
+-----BEGIN CERTIFICATE-----
+MIIDeDCCAmACAQIwDQYJKoZIhvcNAQEEBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD
+VQQIEwJDQTEVMBMGA1UEChMMT3BlbiB2U3dpdGNoMREwDwYDVQQLEwhzd2l0Y2hj
+YTE7MDkGA1UEAxMyT1ZTIHN3aXRjaGNhIENBIENlcnRpZmljYXRlICgyMDEwIEph
+biAwNiAxNzowODozMCkwHhcNMTAwMTA3MDEwODU5WhcNMTEwMTA3MDEwODU5WjCB
+gTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQKEwxPcGVuIHZTd2l0
+Y2gxHzAdBgNVBAsTFk9wZW4gdlN3aXRjaCBjZXJ0aWZpZXIxLTArBgNVBAMTJE9w
+ZW4gdlN3aXRjaCBjZXJ0aWZpY2F0ZSBmb3IgdGVzdHBraTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAKw/xrHvo+NomCyRoTohAjiHW3V6HBfJsGSp94AX
+CA+1JbRGgGt+kqv4kwUXd+QShutUXaegRXAWXtdPa3yf/oOkwWKDM3FvT05ohKaS
+pXePrc3uv2FyJMBk33OY3jdruE149LoGlWTvgrEvcQFEyjze+jIotupye03WoPtL
+c96pfyWtIAI9X39/jpE0lwoQlr497jdbqZGef9Wse+NWR6QUFd1IzjJvxIMJBzG7
+NHdN9xJwhriyZBY76tJy4HNrb85Zz1ZtqJQ8ENdHfrKRncdlI4qxypwVNsXZ27Hn
+uB8JIB3al96TfOJdlOo42M5gyZ5D2m2dydKg6W1am1dThn0CAwEAATANBgkqhkiG
+9w0BAQQFAAOCAQEAGakqZvwJeMmH5nO+mtK3hwd7k3AEzfLJR6OPn8Svku/PB9OD
+kPeK8FX2ii6vV7nknHI3t68S+9wHm5R7GMhThm0Cd+vjrCHhbbX+BGuh0nimWEtd
+pxfhO9mUq4FewZq1NKWnmisbdNekqvqBXOVfGgdUNiF2BKleEThGuBwRFXj4DDGN
+mqPk0HKoKYDCPZ32Yd3KyWx+ysANYShNPupRncLEfEfazCQ1nCoNrOpfM1qrt5TL
+P5E4kqNiO0DveVWWsyRaGaJTmWP5hdS2SLia+Ly3dPjPldwa8mbNK0vUwRlpd/n2
+CARhzYDuRkQngklgqb5LUXXKFRYLl8IvJvLdQg==
+-----END CERTIFICATE-----
diff --git a/tests/testpki-cert2.pem b/tests/testpki-cert2.pem
new file mode 100644 (file)
index 0000000..b282bd0
--- /dev/null
@@ -0,0 +1,70 @@
+Certificate:
+    Data:
+        Version: 1 (0x0)
+        Serial Number: 3 (0x3)
+        Signature Algorithm: md5WithRSAEncryption
+        Issuer: C=US, ST=CA, O=Open vSwitch, OU=switchca, CN=OVS switchca CA Certificate (2010 Jan 06 17:08:30)
+        Validity
+            Not Before: Jan 11 20:06:58 2010 GMT
+            Not After : Jan 11 20:06:58 2011 GMT
+        Subject: C=US, ST=CA, O=Open vSwitch, OU=Open vSwitch certifier, CN=Open vSwitch certificate for testpki2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:b0:0a:97:1e:d8:48:76:57:32:e6:4d:d7:84:86:
+                    7a:d8:0c:b7:a4:13:9c:23:bc:d5:01:94:10:81:f7:
+                    5f:b7:70:ba:1e:85:c6:5c:72:ca:cc:2a:d5:fe:cc:
+                    94:0b:54:5e:ae:40:fd:d9:c1:cc:8b:58:7e:87:73:
+                    5e:ac:98:17:f2:93:b4:cd:d7:dd:12:59:b7:8e:31:
+                    d8:63:c9:7a:14:27:f6:67:64:cb:8f:b1:d4:c1:3c:
+                    be:30:e2:08:1e:e2:db:12:9b:23:53:9f:6b:46:9b:
+                    08:46:80:6f:89:f5:77:88:b8:e0:48:89:be:0e:47:
+                    c5:5e:64:28:7e:c3:f3:10:cb:e2:95:20:6a:81:7a:
+                    14:e3:8f:e8:b8:d3:f9:31:b2:98:0c:a6:5e:9f:ea:
+                    25:89:34:22:f2:fc:a1:5d:2b:2d:0a:40:85:7a:ff:
+                    4f:5e:5a:51:72:f2:b8:03:17:db:d2:3e:40:7f:1b:
+                    6b:f1:ad:e8:ae:d3:33:d4:ad:9c:05:d4:b1:1f:53:
+                    1d:45:18:50:31:28:88:56:93:a7:f9:b2:cd:90:d5:
+                    91:dc:14:a9:33:2d:0b:8c:74:7d:94:1e:be:58:d7:
+                    fe:1e:6d:a5:a5:3b:e2:66:f0:06:f9:d9:5c:2c:66:
+                    fb:7b:85:38:13:65:ff:38:ba:1b:59:f4:08:a0:49:
+                    03:3f
+                Exponent: 65537 (0x10001)
+    Signature Algorithm: md5WithRSAEncryption
+        6b:36:9d:38:52:14:c7:59:a4:3d:39:eb:7f:47:53:8a:f2:3e:
+        42:b3:ba:f9:1b:9f:72:3f:3d:38:7f:c7:41:2d:e9:9d:ed:94:
+        50:79:00:55:d0:52:8a:c4:2f:0b:74:36:05:d4:0a:d5:fd:a6:
+        87:6e:3a:e2:12:c0:4e:0e:92:9f:98:8d:77:68:0a:bf:21:0d:
+        78:20:03:0e:13:9f:49:cf:bd:c3:42:b2:69:a6:d0:67:e8:33:
+        e0:f2:9e:1c:ff:04:2f:1b:fd:37:68:a9:23:54:a5:a2:83:9d:
+        cb:ee:70:3a:c5:03:30:51:c6:90:01:39:f4:e1:29:be:53:2e:
+        fd:71:90:b7:31:33:95:70:48:98:08:4b:2f:3b:33:11:ba:4c:
+        ce:ed:f5:d8:f7:02:e7:da:f3:e9:56:9d:3a:e2:af:ec:61:e5:
+        9c:06:8a:21:18:64:5c:b8:71:e5:4e:64:cc:2d:35:65:e7:cb:
+        96:f3:8b:bc:51:79:42:92:70:e2:e4:28:70:58:44:81:45:83:
+        e2:c3:2c:3b:5b:01:04:94:ce:25:40:8e:15:a3:b3:05:e4:68:
+        17:4b:50:41:1a:58:51:75:81:0c:72:bd:4c:bc:b3:d1:dd:d9:
+        aa:8f:8c:b4:bb:61:b7:55:c1:3f:74:2c:76:73:1e:25:cc:3d:
+        5c:ac:d5:22
+-----BEGIN CERTIFICATE-----
+MIIDeTCCAmECAQMwDQYJKoZIhvcNAQEEBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD
+VQQIEwJDQTEVMBMGA1UEChMMT3BlbiB2U3dpdGNoMREwDwYDVQQLEwhzd2l0Y2hj
+YTE7MDkGA1UEAxMyT1ZTIHN3aXRjaGNhIENBIENlcnRpZmljYXRlICgyMDEwIEph
+biAwNiAxNzowODozMCkwHhcNMTAwMTExMjAwNjU4WhcNMTEwMTExMjAwNjU4WjCB
+gjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRUwEwYDVQQKEwxPcGVuIHZTd2l0
+Y2gxHzAdBgNVBAsTFk9wZW4gdlN3aXRjaCBjZXJ0aWZpZXIxLjAsBgNVBAMTJU9w
+ZW4gdlN3aXRjaCBjZXJ0aWZpY2F0ZSBmb3IgdGVzdHBraTIwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQCwCpce2Eh2VzLmTdeEhnrYDLekE5wjvNUBlBCB
+91+3cLoehcZccsrMKtX+zJQLVF6uQP3ZwcyLWH6Hc16smBfyk7TN190SWbeOMdhj
+yXoUJ/ZnZMuPsdTBPL4w4gge4tsSmyNTn2tGmwhGgG+J9XeIuOBIib4OR8VeZCh+
+w/MQy+KVIGqBehTjj+i40/kxspgMpl6f6iWJNCLy/KFdKy0KQIV6/09eWlFy8rgD
+F9vSPkB/G2vxreiu0zPUrZwF1LEfUx1FGFAxKIhWk6f5ss2Q1ZHcFKkzLQuMdH2U
+Hr5Y1/4ebaWlO+Jm8Ab52VwsZvt7hTgTZf84uhtZ9AigSQM/AgMBAAEwDQYJKoZI
+hvcNAQEEBQADggEBAGs2nThSFMdZpD05639HU4ryPkKzuvkbn3I/PTh/x0Et6Z3t
+lFB5AFXQUorELwt0NgXUCtX9poduOuISwE4Okp+YjXdoCr8hDXggAw4Tn0nPvcNC
+smmm0GfoM+Dynhz/BC8b/TdoqSNUpaKDncvucDrFAzBRxpABOfThKb5TLv1xkLcx
+M5VwSJgISy87MxG6TM7t9dj3Aufa8+lWnTrir+xh5ZwGiiEYZFy4ceVOZMwtNWXn
+y5bzi7xReUKScOLkKHBYRIFFg+LDLDtbAQSUziVAjhWjswXkaBdLUEEaWFF1gQxy
+vUy8s9Hd2aqPjLS7YbdVwT90LHZzHiXMPVys1SI=
+-----END CERTIFICATE-----
diff --git a/tests/testpki-privkey.pem b/tests/testpki-privkey.pem
new file mode 100644 (file)
index 0000000..759f58a
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEArD/Gse+j42iYLJGhOiECOIdbdXocF8mwZKn3gBcID7UltEaA
+a36Sq/iTBRd35BKG61Rdp6BFcBZe109rfJ/+g6TBYoMzcW9PTmiEppKld4+tze6/
+YXIkwGTfc5jeN2u4TXj0ugaVZO+CsS9xAUTKPN76Mii26nJ7Tdag+0tz3ql/Ja0g
+Aj1ff3+OkTSXChCWvj3uN1upkZ5/1ax741ZHpBQV3UjOMm/EgwkHMbs0d033EnCG
+uLJkFjvq0nLgc2tvzlnPVm2olDwQ10d+spGdx2UjirHKnBU2xdnbsee4HwkgHdqX
+3pN84l2U6jjYzmDJnkPabZ3J0qDpbVqbV1OGfQIDAQABAoIBADr/MSAa82hdl9mU
+G8PcMHWKLxJCu8KOC0O/T41o1hMDOaHQkAXBeZ07a6fPzPmqOtn5sIZMh9wHXX6j
+ri4mYrdWRAJo68LLnD8/30dqbRBRfvdM8fH/dYUMR9jBIEOdOqgWaMQaoyrKOlpT
+5IHJvPcybEGn3lbY1VDo1YSc6Ff36AGLdORVH8dY9tYx/IKbyzRmDvzai6EVSDtl
+yp2zinXRNJ+AVwB0epsKbOVZa0WaYN1KclqOtFn7xANoUvy5YBHZDedC3yWxuZvZ
+dNeTjUniauukz7ivKg9/rWZFfYZ2251mrOfO9aIHOUzBurbDS/rzjVgwQmv483T9
+2cDL/IUCgYEA3tXDA0Mcv1d7IzP6A4CQ6o49JyWVkMHxKkhZy1cR+pBRc7tgpQrF
+YrtEWdsDvUJLGMUQBmm7VMpMjTRQ/YuBcdIB2USkJcDHPaZRAA1mlDPG6cSsy7yI
+d2qZFOOkUEjLqKicxiHTrCOz9HBb1McolTo9h5SdfBy5bHb9LPul3E8CgYEAxeKk
+L3m1C1rFVpVF8zoHF+zK3/d9zwzdLzmFfFrKqzIT3/6cJKEwHLJN69mja60+MKLZ
+6F1G+R4/JxE8TnCSXHh7UYULhyFolZaWwZn5xVld0210QU+f4EBUZMt4bZjDxEyr
+/vxDZaqu7SB5Mmqq++C9YzdeIk1GGJ0TE2MmwXMCgYEAuPJ+ayS2pXD8ONmY9nMs
+1CC+TNF686ykd02ZiZV4zJgfooiwzArGjQ2Uy2dmER0Gq0ZT6J605skJBGGZnva8
+tzVwZ137R4JbW6XAsORucS8QN1IPgQG32jVVXOsbo67nqdJYXHIS91qir4zaCx5J
+ZqHyE6ebljlZBNc1hrJOlS0CgYEAhoc/626oYCHDistMlMBcVi2K9pwAkaRDMnm+
+f/4RTjVrQZqMeHKEjN3DD5YT/X33i4UK82eGepHPiTW0c/cf6XGXFKKIZcOWoCuS
+LegJ39qTaMs+f7AsFn5lYWjaZFe4r1kYjO7eut1AssCi5F2UBEyTNEJN4q/5+X2/
+nCyKCnUCgYBzxbUnJBhuA8ivNb/lXdGCvqnBaZb1Bjb+Ljv0yDhMVEJwKpGpm5H3
+DySodzklBrU+eL9TLPcFM6N+okmDsMQqUygUJ1PXLRZKnpLVRILmHcMWbgCrbw4Q
+pd22A01NncdtPY2107ZeVZnjzqHF+5CXxMlKBl4QG07KFtzbNOcQuA==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testpki-privkey2.pem b/tests/testpki-privkey2.pem
new file mode 100644 (file)
index 0000000..21b2698
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAsAqXHthIdlcy5k3XhIZ62Ay3pBOcI7zVAZQQgfdft3C6HoXG
+XHLKzCrV/syUC1RerkD92cHMi1h+h3NerJgX8pO0zdfdElm3jjHYY8l6FCf2Z2TL
+j7HUwTy+MOIIHuLbEpsjU59rRpsIRoBvifV3iLjgSIm+DkfFXmQofsPzEMvilSBq
+gXoU44/ouNP5MbKYDKZen+oliTQi8vyhXSstCkCFev9PXlpRcvK4Axfb0j5Afxtr
+8a3ortMz1K2cBdSxH1MdRRhQMSiIVpOn+bLNkNWR3BSpMy0LjHR9lB6+WNf+Hm2l
+pTviZvAG+dlcLGb7e4U4E2X/OLobWfQIoEkDPwIDAQABAoIBAQCcFVe2Dnf5DQlh
+LyVmKEIk6umr/YMUIKoPkzMruKRSJg2vYFbmwxTR/yzrlIGypeSoxZENkIoGBrlw
+6TbI+rVI6/OECt/FFGzuE33Tw+CMnH4ZYEIoLrj/eBb9ins+v08T6R9iVlesK2sj
+7151yIFqZNjfF9m+GZ6COXW6J4yMl3Aq+x72IBq46pyu/WoBkxBPfXcIfImoaoUI
+2dbid5S074qmPpyfaVLkS6s77nxypH1T2dhi1TkpQmIIx+aCyxdlTmpCwa7Uik6l
+ZQxpzQPtT1aiBixJbEHmvyTnnptNTzWXQP+mxnfGUZ6TyCNtW2V3o2I9/EoHc7I4
+OXb1p/WpAoGBAOHgNlyYanlm0ezvROA/JDMPgizJJrYv9BGJez+1NaEw+gSRBFrZ
+IpVU9OuiCnkJCcNAH1KO/f4InnbJyvSweQd0CUOvNPAwqMHLjH2FnL2WQLv+9afY
+tt0oZpDT9Wj+VskYoVu2jPhGfKzSrtjevmCU7w7heUjdQM2WCYuSHLktAoGBAMeE
+8bGq+v5yfjn8/8SYvxYe3iRtpEgn8hg1XEl0VN7zVXVOVfastdh9xuNiAnHn16GT
++4VeqYCBfb9ygcRyid048dXhLCUsD84EMsdCtKHgG0iJh2RJ7L67z4ZFgEZw03K3
+sRfRo/2QxFMIG30ZFip7MLiTZc1OvP+OZ/lovZmbAoGARqaN+mqGK1D46qZwob++
+P+Zi7kVAwY3ARtf84BF7I98g5NrDRPNT+Oeo8CcwJWmpTxQ4d+dYFOR3RGqb++tS
+//zQhQZBhbuCnZNgb0ainz5nIyZ9ijGkCQsBAd36jgu385Crr/cqouHRT3Fa1WTe
+oXEUUVA/UoY6JdP/SlO6fkUCgYBfTFHBYgCm3nsKKZzlA2xqHW5Pigso5+OLypj9
+ANK09xc/g54tx4rIEDOaUisGyw1EwREnP/LITZGJiyEOewL8poFkfjv+uVAHQBwc
+7vCmTQvbFs2TinfJFp3l7XZ6rtNgfPrafKjOqYIMgtfWZdAflF3OG6FJci12B0gE
+ahH9twKBgQDgMuZ2RomZST/x8ClWpE6jDJzwBRAO8RwFDQqTV3OXqu1fImTrB8x0
+Qtclr6NeHWLgb00njgZTCk4/Yo7htdMoL8CsuUJ3Rz3hzjdN2fLO5SUNNI5vJu5/
+iHCAqSlDUmPuTSIE+xvfwZrHAxPNO+xCPoAgvFXUs7BWaGJNf16HSQ==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/testpki-req.pem b/tests/testpki-req.pem
new file mode 100644 (file)
index 0000000..da53b10
--- /dev/null
@@ -0,0 +1,63 @@
+Certificate Request:
+    Data:
+        Version: 0 (0x0)
+        Subject: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch certifier, CN=Open vSwitch certificate for testpki
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:ac:3f:c6:b1:ef:a3:e3:68:98:2c:91:a1:3a:21:
+                    02:38:87:5b:75:7a:1c:17:c9:b0:64:a9:f7:80:17:
+                    08:0f:b5:25:b4:46:80:6b:7e:92:ab:f8:93:05:17:
+                    77:e4:12:86:eb:54:5d:a7:a0:45:70:16:5e:d7:4f:
+                    6b:7c:9f:fe:83:a4:c1:62:83:33:71:6f:4f:4e:68:
+                    84:a6:92:a5:77:8f:ad:cd:ee:bf:61:72:24:c0:64:
+                    df:73:98:de:37:6b:b8:4d:78:f4:ba:06:95:64:ef:
+                    82:b1:2f:71:01:44:ca:3c:de:fa:32:28:b6:ea:72:
+                    7b:4d:d6:a0:fb:4b:73:de:a9:7f:25:ad:20:02:3d:
+                    5f:7f:7f:8e:91:34:97:0a:10:96:be:3d:ee:37:5b:
+                    a9:91:9e:7f:d5:ac:7b:e3:56:47:a4:14:15:dd:48:
+                    ce:32:6f:c4:83:09:07:31:bb:34:77:4d:f7:12:70:
+                    86:b8:b2:64:16:3b:ea:d2:72:e0:73:6b:6f:ce:59:
+                    cf:56:6d:a8:94:3c:10:d7:47:7e:b2:91:9d:c7:65:
+                    23:8a:b1:ca:9c:15:36:c5:d9:db:b1:e7:b8:1f:09:
+                    20:1d:da:97:de:93:7c:e2:5d:94:ea:38:d8:ce:60:
+                    c9:9e:43:da:6d:9d:c9:d2:a0:e9:6d:5a:9b:57:53:
+                    86:7d
+                Exponent: 65537 (0x10001)
+        Attributes:
+            a0:00
+    Signature Algorithm: sha1WithRSAEncryption
+        21:46:4c:7a:a9:da:58:cf:ee:d3:0a:81:ee:cd:bf:73:cf:05:
+        93:2b:ef:f5:c7:7d:5e:96:a5:82:d2:62:34:26:8f:1e:f6:db:
+        6f:0e:05:39:a5:3c:df:bb:51:02:2f:bc:5b:a8:a0:a5:5e:ce:
+        e4:55:21:73:92:1d:bf:53:a4:f5:dc:7e:0e:f8:b1:05:57:3d:
+        0c:04:5e:a6:35:c6:ae:81:59:6c:28:c5:19:4b:8c:da:dd:1e:
+        97:51:bf:8b:f8:21:dc:c9:23:07:7a:66:66:fc:e2:b6:c6:e7:
+        f3:b4:e4:3e:7e:be:72:3e:3a:65:98:f1:6c:4f:79:8a:3c:11:
+        59:3d:9f:28:c8:80:eb:9d:e3:1d:6c:4e:b7:59:4e:48:b9:f8:
+        87:cf:35:13:f8:15:d3:6f:fb:1c:89:6f:ec:2c:24:5c:7b:9f:
+        fa:f0:a9:61:7b:4d:ab:40:84:dc:f8:5a:13:13:7a:b2:f4:09:
+        36:95:76:1d:c8:d8:33:eb:67:c8:c9:a9:de:98:9a:77:33:46:
+        83:37:19:60:d0:38:6f:dd:39:14:d7:a0:74:40:91:1f:60:bc:
+        0b:f8:ca:81:7d:88:67:c7:89:cf:4c:c5:95:65:66:f5:c2:98:
+        29:77:a2:93:b6:37:55:cc:f4:85:01:58:30:30:54:9a:c4:57:
+        35:e4:21:bd
+-----BEGIN CERTIFICATE REQUEST-----
+MIIC2zCCAcMCAQAwgZUxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UE
+BxMJUGFsbyBBbHRvMRUwEwYDVQQKEwxPcGVuIHZTd2l0Y2gxHzAdBgNVBAsTFk9w
+ZW4gdlN3aXRjaCBjZXJ0aWZpZXIxLTArBgNVBAMTJE9wZW4gdlN3aXRjaCBjZXJ0
+aWZpY2F0ZSBmb3IgdGVzdHBraTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAKw/xrHvo+NomCyRoTohAjiHW3V6HBfJsGSp94AXCA+1JbRGgGt+kqv4kwUX
+d+QShutUXaegRXAWXtdPa3yf/oOkwWKDM3FvT05ohKaSpXePrc3uv2FyJMBk33OY
+3jdruE149LoGlWTvgrEvcQFEyjze+jIotupye03WoPtLc96pfyWtIAI9X39/jpE0
+lwoQlr497jdbqZGef9Wse+NWR6QUFd1IzjJvxIMJBzG7NHdN9xJwhriyZBY76tJy
+4HNrb85Zz1ZtqJQ8ENdHfrKRncdlI4qxypwVNsXZ27HnuB8JIB3al96TfOJdlOo4
+2M5gyZ5D2m2dydKg6W1am1dThn0CAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4IBAQAh
+Rkx6qdpYz+7TCoHuzb9zzwWTK+/1x31elqWC0mI0Jo8e9ttvDgU5pTzfu1ECL7xb
+qKClXs7kVSFzkh2/U6T13H4O+LEFVz0MBF6mNcaugVlsKMUZS4za3R6XUb+L+CHc
+ySMHemZm/OK2xufztOQ+fr5yPjplmPFsT3mKPBFZPZ8oyIDrneMdbE63WU5IufiH
+zzUT+BXTb/sciW/sLCRce5/68Klhe02rQITc+FoTE3qy9Ak2lXYdyNgz62fIyane
+mJp3M0aDNxlg0Dhv3TkU16B0QJEfYLwL+MqBfYhnx4nPTMWVZWb1wpgpd6KTtjdV
+zPSFAVgwMFSaxFc15CG9
+-----END CERTIFICATE REQUEST-----
diff --git a/tests/testpki-req2.pem b/tests/testpki-req2.pem
new file mode 100644 (file)
index 0000000..0ec61cd
--- /dev/null
@@ -0,0 +1,63 @@
+Certificate Request:
+    Data:
+        Version: 0 (0x0)
+        Subject: C=US, ST=CA, L=Palo Alto, O=Open vSwitch, OU=Open vSwitch certifier, CN=Open vSwitch certificate for testpki2
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (2048 bit)
+                Modulus (2048 bit):
+                    00:b0:0a:97:1e:d8:48:76:57:32:e6:4d:d7:84:86:
+                    7a:d8:0c:b7:a4:13:9c:23:bc:d5:01:94:10:81:f7:
+                    5f:b7:70:ba:1e:85:c6:5c:72:ca:cc:2a:d5:fe:cc:
+                    94:0b:54:5e:ae:40:fd:d9:c1:cc:8b:58:7e:87:73:
+                    5e:ac:98:17:f2:93:b4:cd:d7:dd:12:59:b7:8e:31:
+                    d8:63:c9:7a:14:27:f6:67:64:cb:8f:b1:d4:c1:3c:
+                    be:30:e2:08:1e:e2:db:12:9b:23:53:9f:6b:46:9b:
+                    08:46:80:6f:89:f5:77:88:b8:e0:48:89:be:0e:47:
+                    c5:5e:64:28:7e:c3:f3:10:cb:e2:95:20:6a:81:7a:
+                    14:e3:8f:e8:b8:d3:f9:31:b2:98:0c:a6:5e:9f:ea:
+                    25:89:34:22:f2:fc:a1:5d:2b:2d:0a:40:85:7a:ff:
+                    4f:5e:5a:51:72:f2:b8:03:17:db:d2:3e:40:7f:1b:
+                    6b:f1:ad:e8:ae:d3:33:d4:ad:9c:05:d4:b1:1f:53:
+                    1d:45:18:50:31:28:88:56:93:a7:f9:b2:cd:90:d5:
+                    91:dc:14:a9:33:2d:0b:8c:74:7d:94:1e:be:58:d7:
+                    fe:1e:6d:a5:a5:3b:e2:66:f0:06:f9:d9:5c:2c:66:
+                    fb:7b:85:38:13:65:ff:38:ba:1b:59:f4:08:a0:49:
+                    03:3f
+                Exponent: 65537 (0x10001)
+        Attributes:
+            a0:00
+    Signature Algorithm: sha1WithRSAEncryption
+        6e:d7:7c:0b:91:75:9a:36:25:44:cf:9b:0c:6b:8f:a5:ed:f2:
+        4f:cc:bd:9f:fd:43:dd:b4:27:0a:0d:61:4c:54:99:b0:4c:a9:
+        52:3e:39:93:68:63:e1:15:fc:47:27:54:17:08:eb:b0:6a:61:
+        61:d8:d5:d4:95:fc:1e:50:ba:6a:7f:7b:33:87:a9:b4:27:6b:
+        f4:12:05:c3:90:ca:bf:98:ea:ae:d2:1e:26:bb:cd:23:cd:38:
+        c8:f0:a1:03:9e:d7:e1:e4:d9:c0:ea:b0:31:5c:ba:7c:53:d1:
+        5e:23:c2:e8:74:57:0b:fb:23:79:89:5a:88:74:96:37:7e:3a:
+        06:8d:76:fe:e0:f0:d1:8e:60:a3:7d:0c:99:16:a0:a3:ec:f9:
+        62:c0:7d:22:18:33:16:b6:a8:31:39:07:6e:8f:d7:3f:5c:c4:
+        44:53:c6:1c:09:1a:f7:f6:27:3e:d2:73:ce:e9:37:ca:86:8a:
+        58:cc:47:0b:5c:c9:58:12:92:88:4e:6b:13:f9:b4:44:db:9c:
+        e3:f2:0a:61:8e:f8:49:59:44:35:ba:7d:b8:eb:6c:ea:72:e3:
+        cf:39:12:fc:df:08:af:b5:67:91:08:06:f6:2c:69:e9:de:b6:
+        a4:95:0a:30:72:bc:0e:f4:72:ab:0b:bc:68:ad:90:cf:c7:2d:
+        71:6e:4f:9f
+-----BEGIN CERTIFICATE REQUEST-----
+MIIC3DCCAcQCAQAwgZYxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UE
+BxMJUGFsbyBBbHRvMRUwEwYDVQQKEwxPcGVuIHZTd2l0Y2gxHzAdBgNVBAsTFk9w
+ZW4gdlN3aXRjaCBjZXJ0aWZpZXIxLjAsBgNVBAMTJU9wZW4gdlN3aXRjaCBjZXJ0
+aWZpY2F0ZSBmb3IgdGVzdHBraTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCwCpce2Eh2VzLmTdeEhnrYDLekE5wjvNUBlBCB91+3cLoehcZccsrMKtX+
+zJQLVF6uQP3ZwcyLWH6Hc16smBfyk7TN190SWbeOMdhjyXoUJ/ZnZMuPsdTBPL4w
+4gge4tsSmyNTn2tGmwhGgG+J9XeIuOBIib4OR8VeZCh+w/MQy+KVIGqBehTjj+i4
+0/kxspgMpl6f6iWJNCLy/KFdKy0KQIV6/09eWlFy8rgDF9vSPkB/G2vxreiu0zPU
+rZwF1LEfUx1FGFAxKIhWk6f5ss2Q1ZHcFKkzLQuMdH2UHr5Y1/4ebaWlO+Jm8Ab5
+2VwsZvt7hTgTZf84uhtZ9AigSQM/AgMBAAGgADANBgkqhkiG9w0BAQUFAAOCAQEA
+btd8C5F1mjYlRM+bDGuPpe3yT8y9n/1D3bQnCg1hTFSZsEypUj45k2hj4RX8RydU
+FwjrsGphYdjV1JX8HlC6an97M4eptCdr9BIFw5DKv5jqrtIeJrvNI804yPChA57X
+4eTZwOqwMVy6fFPRXiPC6HRXC/sjeYlaiHSWN346Bo12/uDw0Y5go30MmRago+z5
+YsB9IhgzFraoMTkHbo/XP1zERFPGHAka9/YnPtJzzuk3yoaKWMxHC1zJWBKSiE5r
+E/m0RNuc4/IKYY74SVlENbp9uOts6nLjzzkS/N8Ir7VnkQgG9ixp6d62pJUKMHK8
+DvRyqwu8aK2Qz8ctcW5Pnw==
+-----END CERTIFICATE REQUEST-----
index c232a87..4d8e896 100644 (file)
@@ -1,6 +1,6 @@
 AT_INIT
 
-AT_COPYRIGHT([Copyright (c) 2009 Nicira Networks.
+AT_COPYRIGHT([Copyright (c) 2009, 2010 Nicira Networks.
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -16,9 +16,39 @@ limitations under the License.])
 
 AT_TESTED([ovs-vswitchd])
 AT_TESTED([ovs-vsctl])
+AT_TESTED([perl])
+
+m4_define([OVS_WAIT],
+  [AT_CHECK(
+    [# First try a quick sleep, so that the test completes very quickly
+     # in the normal case.  POSIX doesn't require fractional times to
+     # work, so this might not work.
+     sleep 0.1
+     $1
+     # Then wait up to 10 seconds.
+     for d in 0 1 2 3 4 5 6 7 8 9; do
+       sleep 1
+       $1
+     done
+     exit 1], [0], [ignore], [ignore], [$2])])
+
+m4_define([OVS_WAIT_UNTIL], [OVS_WAIT([if $1; then exit 0; fi], [$2])])
+m4_define([OVS_WAIT_WHILE], [OVS_WAIT([if $1; then :; else exit 0; fi], [$2])])
+
+m4_include([tests/ovsdb-macros.at])
 
-m4_include([tests/lcov-pre.at])
 m4_include([tests/library.at])
+m4_include([tests/check-structs.at])
+m4_include([tests/daemon.at])
+m4_include([tests/vconn.at])
+m4_include([tests/dir_name.at])
+m4_include([tests/aes128.at])
+m4_include([tests/uuid.at])
+m4_include([tests/json.at])
+m4_include([tests/jsonrpc.at])
+m4_include([tests/timeval.at])
+m4_include([tests/lockfile.at])
+m4_include([tests/reconnect.at])
+m4_include([tests/ovsdb.at])
 m4_include([tests/stp.at])
 m4_include([tests/ovs-vsctl.at])
-m4_include([tests/lcov-post.at])
diff --git a/tests/timeval.at b/tests/timeval.at
new file mode 100644 (file)
index 0000000..0d61bff
--- /dev/null
@@ -0,0 +1,22 @@
+AT_BANNER([timeval unit tests])
+
+AT_SETUP([check that time advances])
+AT_KEYWORDS([timeval])
+AT_CHECK([test-timeval plain], [0])
+AT_CLEANUP
+
+AT_SETUP([check that time advances after daemonize()])
+AT_KEYWORDS([timeval])
+AT_CHECK([test-timeval daemon], [0])
+AT_CHECK(
+  [# First try a quick sleep, so that the test completes very quickly
+   # in the normal case.  POSIX doesn't require fractional times to
+   # work, so this might not work.
+   sleep 0.1; if test -e test-timeval.success; then echo success; exit 0; fi
+   # Then wait up to 2 seconds.
+   sleep 1; if test -e test-timeval.success; then echo success; exit 0; fi
+   sleep 1; if test -e test-timeval.success; then echo success; exit 0; fi
+   echo failure; exit 1],
+  [0], [success
+], [])
+AT_CLEANUP
diff --git a/tests/uuid.at b/tests/uuid.at
new file mode 100644 (file)
index 0000000..24f7180
--- /dev/null
@@ -0,0 +1,59 @@
+AT_BANNER([UUID unit tests])
+
+m4_define([UUID_REGEX], 
+  [[[0-9a-f]\{8\}-[0-9a-f]\{4\}-4[0-9a-f]\{3\}-[89ab][0-9a-f]\{3\}-[0-9a-f]\{12\}$]])
+
+m4_define([CHECK_UUID],
+  [if expr "$uuid" : 'UUID_REGEX' > /dev/null
+   then
+      :
+   else
+     echo "$uuid: not a random UUID"
+     exit 1
+   fi])
+
+# This test is a strict subset of the larger test down below, but it
+# completes in a realistic amount of time with the "lcov" wrapper.
+AT_SETUP([UUID generation])
+AT_KEYWORDS([UUID])
+AT_CHECK([test-uuid > uuid])
+AT_CHECK([
+  uuid=`cat uuid`
+  CHECK_UUID])
+AT_CLEANUP
+
+# This test is a strict subset of the larger test down below, but it
+# completes in a realistic amount of time with the "lcov" wrapper.
+AT_SETUP([UUID parsing and serialization])
+AT_KEYWORDS([UUID])
+AT_CHECK([test-uuid f47ac10b-58cc-4372-a567-0e02b2c3d479], [0],
+         [f47ac10b-58cc-4372-a567-0e02b2c3d479
+])
+AT_CLEANUP
+
+AT_SETUP([UUID generation, parsing, serialization])
+AT_SKIP_IF([test "$CHECK_LCOV" = true]) # lcov makes this test absurdly slow
+AT_KEYWORDS([UUID])
+AT_CHECK([
+  uuids=
+  for i in m4_for([count], [1], [100], [1], [count ]); do
+     # Generate random UUID and check that it is in the expected format.
+     uuid=`test-uuid`
+     CHECK_UUID
+
+     # Verify that $uuid does not duplicate any UUID generated so far.
+     case $uuids in
+       *$uuid*) 
+         echo "$uuid: generated duplicate UUID"
+         exit 1
+     esac
+     uuids="$uuids $uuid"
+
+     # Verify that test-uuid parses and re-serializes this UUID correctly.
+     serialized=`test-uuid $uuid`
+     if test "$uuid" != "$serialized"; then
+       echo "$uuid: test-uuid serialized this as $serialized"
+       exit 1
+     fi
+   done], [0])
+AT_CLEANUP
diff --git a/tests/uuidfilt.pl b/tests/uuidfilt.pl
new file mode 100755 (executable)
index 0000000..6f003a5
--- /dev/null
@@ -0,0 +1,21 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+our %uuids;
+our $n_uuids = 0;
+sub lookup_uuid {
+    my ($uuid) = @_;
+    if (!exists($uuids{$uuid})) {
+        $uuids{$uuid} = $n_uuids++;
+    }
+    return "<$uuids{$uuid}>";
+}
+
+my $u = '[0-9a-fA-F]';
+my $uuid_re = "${u}{8}-${u}{4}-${u}{4}-${u}{4}-${u}{12}";
+while (<>) {
+    s/($uuid_re)/lookup_uuid($1)/eg;
+    print $_;
+}
diff --git a/tests/valgrind-wrapper.in b/tests/valgrind-wrapper.in
new file mode 100755 (executable)
index 0000000..ed9a053
--- /dev/null
@@ -0,0 +1,36 @@
+#! /bin/sh
+
+wrap_program=`basename '@wrap_program@'`
+
+# Strip the first directory from $PATH that contains $wrap_program,
+# so that below we run the real $wrap_program, not ourselves.
+not_found=true
+new_path=
+first=true
+save_IFS=$IFS
+IFS=:
+for dir in $PATH; do
+    IFS=$save_IFS
+    if $not_found && test -x "$dir/$wrap_program"; then
+        not_found=false
+    else
+        if $first; then
+            first=false
+            new_path=$dir
+        else
+            new_path=$new_path:$dir
+        fi
+    fi
+done
+IFS=$save_IFS
+if $not_found; then
+    echo "$0: error: cannot find $wrap_program in \$PATH" >&2
+    exit 1
+fi
+PATH=$new_path
+export PATH
+
+: ${VALGRIND:=valgrind -q --log-file=valgrind.%p --leak-check=full}
+exec $VALGRIND $wrap_program "$@"
+echo "$0: failed to execute $VALGRIND $wrap_program" "$@" >&2
+exit 1
diff --git a/tests/vconn.at b/tests/vconn.at
new file mode 100644 (file)
index 0000000..2f64f82
--- /dev/null
@@ -0,0 +1,22 @@
+m4_define([TEST_VCONN_CLASS],
+  [AT_BANNER([vconn library -- $1 class])
+   m4_foreach(
+     [testname], 
+     [[refuse-connection], 
+      [accept-then-close],
+      [read-hello],
+      [send-plain-hello],
+      [send-long-hello],
+      [send-echo-hello],
+      [send-short-hello],
+      [send-invalid-version-hello]],
+     [AT_SETUP([$1 vconn - m4_bpatsubst(testname, [-], [ ])])
+      m4_if([$1], [ssl], [
+        AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+        AT_CHECK([cp $abs_top_srcdir/tests/testpki*.pem .])])
+      AT_CHECK([test-vconn testname $1], [0], [], [ignore])
+      AT_CLEANUP])])
+
+TEST_VCONN_CLASS([unix])
+TEST_VCONN_CLASS([tcp])
+TEST_VCONN_CLASS([ssl])
index ebbd691..fbaba1e 100644 (file)
@@ -21,4 +21,6 @@
 /ovs-pki
 /ovs-pki-cgi
 /ovs-pki.8
+/ovs-vsctl
+/ovs-vsctl.8
 /ovs-wdt
index 1a9d492..5feb01c 100644 (file)
@@ -1,12 +1,12 @@
 bin_PROGRAMS += \
        utilities/ovs-appctl \
-       utilities/ovs-cfg-mod \
        utilities/ovs-controller \
        utilities/ovs-discover \
        utilities/ovs-dpctl \
        utilities/ovs-kill \
        utilities/ovs-ofctl \
        utilities/ovs-openflowd \
+       utilities/ovs-vsctl \
        utilities/ovs-wdt
 noinst_PROGRAMS += utilities/nlmon
 bin_SCRIPTS += utilities/ovs-pki utilities/ovs-vsctl
@@ -15,7 +15,6 @@ dist_sbin_SCRIPTS += utilities/ovs-monitor
 
 EXTRA_DIST += \
        utilities/ovs-appctl.8.in \
-       utilities/ovs-cfg-mod.8.in \
        utilities/ovs-controller.8.in \
        utilities/ovs-discover.8.in \
        utilities/ovs-dpctl.8.in \
@@ -26,11 +25,9 @@ EXTRA_DIST += \
        utilities/ovs-pki-cgi.in \
        utilities/ovs-pki.8.in \
        utilities/ovs-pki.in \
-       utilities/ovs-vsctl.8.in \
-       utilities/ovs-vsctl.in
+       utilities/ovs-vsctl.8.in
 DISTCLEANFILES += \
        utilities/ovs-appctl.8 \
-       utilities/ovs-cfg-mod.8 \
        utilities/ovs-controller.8 \
        utilities/ovs-discover.8 \
        utilities/ovs-dpctl.8 \
@@ -41,12 +38,10 @@ DISTCLEANFILES += \
        utilities/ovs-pki \
        utilities/ovs-pki-cgi \
        utilities/ovs-pki.8 \
-       utilities/ovs-vsctl \
        utilities/ovs-vsctl.8
 
 man_MANS += \
        utilities/ovs-appctl.8 \
-       utilities/ovs-cfg-mod.8 \
        utilities/ovs-controller.8 \
        utilities/ovs-discover.8 \
        utilities/ovs-dpctl.8 \
@@ -59,32 +54,31 @@ man_MANS += \
 utilities_ovs_appctl_SOURCES = utilities/ovs-appctl.c
 utilities_ovs_appctl_LDADD = lib/libopenvswitch.a
 
-utilities_ovs_cfg_mod_SOURCES = utilities/ovs-cfg-mod.c
-utilities_ovs_cfg_mod_LDADD = lib/libopenvswitch.a
-
 utilities_ovs_controller_SOURCES = utilities/ovs-controller.c
-utilities_ovs_controller_LDADD = lib/libopenvswitch.a $(FAULT_LIBS) $(SSL_LIBS)
+utilities_ovs_controller_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
 
 utilities_ovs_discover_SOURCES = utilities/ovs-discover.c
 utilities_ovs_discover_LDADD = lib/libopenvswitch.a
 
 utilities_ovs_dpctl_SOURCES = utilities/ovs-dpctl.c
-utilities_ovs_dpctl_LDADD = lib/libopenvswitch.a $(FAULT_LIBS)
+utilities_ovs_dpctl_LDADD = lib/libopenvswitch.a
 
 utilities_ovs_kill_SOURCES = utilities/ovs-kill.c
 utilities_ovs_kill_LDADD = lib/libopenvswitch.a
 
 utilities_ovs_ofctl_SOURCES = utilities/ovs-ofctl.c
-utilities_ovs_ofctl_LDADD = lib/libopenvswitch.a $(FAULT_LIBS) $(SSL_LIBS)
+utilities_ovs_ofctl_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
 
 utilities_ovs_openflowd_SOURCES = utilities/ovs-openflowd.c
 utilities_ovs_openflowd_LDADD = \
        ofproto/libofproto.a \
        lib/libsflow.a \
        lib/libopenvswitch.a \
-       $(FAULT_LIBS) \
        $(SSL_LIBS)
 
+utilities_ovs_vsctl_SOURCES = utilities/ovs-vsctl.c vswitchd/vswitch-idl.c
+utilities_ovs_vsctl_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
+
 utilities_ovs_wdt_SOURCES = utilities/ovs-wdt.c
 
 utilities_nlmon_SOURCES = utilities/nlmon.c
index 1302bf2..7a3d91e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -71,6 +71,10 @@ main(int argc, char *argv[])
     }
     fputs(reply, stdout);
 
+    unixctl_client_destroy(client);
+    free(reply);
+    ds_destroy(&request);
+
     return 0;
 }
 
diff --git a/utilities/ovs-cfg-mod.8.in b/utilities/ovs-cfg-mod.8.in
deleted file mode 100644 (file)
index 5b96f28..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-.\" -*- nroff -*-
-.de IQ
-.  br
-.  ns
-.  IP "\\$1"
-..
-.TH ovs\-cfg\-mod 8 "June 2009" "Open vSwitch" "Open vSwitch Manual"
-.ds PN ovs\-cfg\-mod
-.
-.SH NAME
-ovs\-cfg\-mod \- Safely manage a ovs\-vswitchd.conf\-style configuration file
-.
-.SH SYNOPSIS
-\fB ovs\-cfg\-mod \fR[\fB\-T \fItimeout\fR] \fB\-F \fIfile\fR
-[\fIaction\fR] [\fIaction\fR...\fR]
-.
-.SH DESCRIPTION
-A program for managing a \fovs\-vswitchd.conf\fR(5)\-style configuration
-file.  \fBovs\-cfg\-mod\fR uses the same locking mechanisms as
-\fBovs\-vswitchd\fR and its related utilities.  This allows it to be
-run safely on ``live'' configurations.
-.
-.SH OPTIONS
-.SS "Specifying the Configuration File"
-.
-.IP "\fB\-T\fR \fItimeout\fR
-.IQ "\fB\-\-timeout=\fItimeout\fR
-By default, \fBovs\-cfg\-mod\fR will wait forever to lock the
-configuration file specified on \fB\-F\fR or \fB\-\-config\-file\fR.  This
-option makes \fBovs\-cfg\-mod\fR wait no more than \fItimeout\fR
-milliseconds to obtain the lock, after which it exits unsuccessfully.
-.
-If it is present, this option must be specified before \fB\-F\fR or
-\fB\-\-config\-file\fR.
-.
-.IP "\fB\-F\fR \fIfile\fR"
-.IQ "\fB\-\-config\-file=\fIfile\fR"
-Use \fIfile\fR as the configuration file to query or modify.
-.
-This option is required.  It must be specified before any action
-options.
-.
-.SS "Specifying Actions"
-A series of one or more action options may follow the configuration
-file options.  These are executed in the order provided and under a
-single lock instance, so they appear atomic to external viewers of
-\fIfile\fR.
-.
-As discussed in \fBovs\-vswitchd.conf\fR(5), each line in the
-configuration file consists of a key\-value pair.  Actions generally
-take either a \fIkey\fR or \fIentry\fR argument.  A \fIkey\fR is a
-dot\-separated description of a configuration option.  A \fIentry\fR is
-a key\-value pair, separated by the \fB=\fR sign.
-.
-The following actions are supported:
-.
-.IP "\fB\-a\fR \fIentry\fR"
-.IQ "\fB\-\-add=\fIentry\fR"
-Add \fIentry\fR to \fIfile\fR.  Please note that duplicates are
-allowed, so if a unique key is required, a delete must be done first.
-.
-.IP "\fB\-d\fR \fIentry\fR"
-.IQ "\fB\-\-del\-entry=\fIentry\fR"
-Delete \fIentry\fR from \fIfile\fR.  Deletes only the first entry 
-that matches \fIentry\fR.  
-.
-.IP "\fB\-D\fR \fIkey\fR"
-.IQ "\fB\-\-del\-section=\fIkey\fR"
-Delete section \fIkey\fR from \fIfile\fR.  
-.
-.IP "\fB\-\-del\-match=\fIpattern\fR"
-Deletes every entry that matches the given shell glob \fIpattern\fR.
-For example, \fB\-\-del\-match=bridge.*.port=*\fR deletes all the ports
-from every bridge, and \fB\-\-del\-match=bonding.bond0.*\fR is equivalent
-to \fB\-\-del\-section=bonding.bond0\fR.
-.
-.IP "\fB\-q\fR \fIkey\fR"
-.IQ "\fB\-\-query=\fIkey\fR"
-Queries \fIfile\fR for entries that match \fIkey\fR.  Each matching
-value is printed on a separate line.  Duplicates will be printed
-multiple times.  
-.
-.IP "\fB\-c\fR"
-.IQ "\fB\-\-changes\fR"
-Logs all of the changes made to the configuration file in a ``unified
-diff''\-like format.  Only actual changes are logged, so that if, for
-example, a \fB\-\-del\-match\fR action did not match any key\-value pairs,
-then nothing will be logged due to that action.  Furthermore, only the
-net effects of changes are logged: if a key\-value pair was deleted and
-then an identical key\-value pair was added back, then nothing would be
-logged due to those changes.
-.
-This action logs changes that have taken effect at the point where it
-is inserted.  Thus, if it is given before any other action, it will
-not log any changes.  If \fB\-\-changes\fR is given more than once,
-instances after the first log only the changes since the previous
-instance.
-.
-.SH "SEE ALSO"
-.BR ovs\-vswitchd (8),
-.BR ovs\-vswitchd.conf (5)
diff --git a/utilities/ovs-cfg-mod.c b/utilities/ovs-cfg-mod.c
deleted file mode 100644 (file)
index 1b52a7b..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-/* Copyright (c) 2008, 2009  Nicira Networks
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <config.h>
-
-#include <dirent.h>
-#include <errno.h>
-#include <getopt.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "cfg.h"
-#include "command-line.h"
-#include "svec.h"
-#include "timeval.h"
-#include "util.h"
-
-#define THIS_MODULE VLM_cfg_mod
-#include "vlog.h"
-
-/* Configuration when we first read the configuration file. */
-static struct svec orig_cfg = SVEC_EMPTY_INITIALIZER;
-
-static void
-usage(char *prog_name, int exit_code)
-{
-    printf("Usage: %s --config-file=FILE ACTIONS\n"
-           "\nConfig:\n"
-           "  -T, --timeout=MS        wait at most MS milliseconds for lock\n"
-           "  -F, --config-file=FILE  use configuration FILE\n"
-           "\nActions:\n"
-           "  -a, --add=ENTRY         add ENTRY\n"
-           "  -d, --del-entry=ENTRY   delete ENTRY\n"
-           "  -D, --del-section=KEY   delete section matching KEY\n"
-           "  --del-match=PATTERN     delete entries matching shell PATTERN\n"
-           "  -q, --query=KEY         return all entries matching KEY\n"
-           "  -c, --log-changes       log changes up to this point\n"
-           "\nOther options:\n"
-           "  -h, --help              display this help message\n"
-           "  -V, --version           display version information\n",
-           prog_name);
-    exit(exit_code);
-}
-
-static void 
-open_config(char *config_file, int timeout) 
-{
-    int error;
-
-    cfg_init();
-    error = cfg_set_file(config_file);
-    if (error) {
-        ovs_fatal(error, "failed to add configuration file \"%s\"",
-                config_file);
-    }
-
-    error = cfg_lock(NULL, timeout);
-    if (error) {
-        ovs_fatal(error, "could not lock configuration file\n");
-    }
-
-    cfg_get_all(&orig_cfg);
-}
-
-static void
-print_vals(char *key)
-{
-    struct svec vals;
-    int i;
-
-    svec_init(&vals);
-    cfg_get_all_strings(&vals, "%s", key);
-
-    for (i=0; i<vals.n; i++) {
-        printf("%s\n", vals.names[i]);
-    }
-}
-
-static void
-log_diffs(void)
-{
-    struct svec new_cfg, removed, added;
-    size_t i;
-
-    svec_init(&new_cfg);
-    cfg_get_all(&new_cfg);
-    svec_diff(&orig_cfg, &new_cfg, &removed, NULL, &added);
-    if (removed.n || added.n) {
-        VLOG_INFO("configuration changes:");
-        for (i = 0; i < removed.n; i++) {
-            VLOG_INFO("-%s", removed.names[i]);
-        }
-        for (i = 0; i < added.n; i++) {
-            VLOG_INFO("+%s", added.names[i]);
-        }
-    } else {
-        VLOG_INFO("configuration unchanged");
-    }
-    svec_destroy(&added);
-    svec_destroy(&removed);
-    svec_swap(&new_cfg, &orig_cfg);
-    svec_destroy(&new_cfg);
-}
-
-int main(int argc, char *argv[])
-{
-    enum {
-        OPT_DEL_MATCH = UCHAR_MAX + 1,
-    };
-    static const struct option long_options[] = {
-        {"config-file",  required_argument, 0, 'F'},
-        {"timeout",      required_argument, 0, 'T'},
-        {"add",          required_argument, 0, 'a'},
-        {"del-entry",    required_argument, 0, 'd'},
-        {"del-section",  required_argument, 0, 'D'},
-        {"del-match",    required_argument, 0, OPT_DEL_MATCH},
-        {"query",        required_argument, 0, 'q'},
-        {"changes",      no_argument, 0, 'c'},
-        {"verbose",      optional_argument, 0, 'v'},
-        {"help",         no_argument, 0, 'h'},
-        {"version",      no_argument, 0, 'V'},
-        {0, 0, 0, 0},
-    };
-    char *short_options;
-    bool config_set = false;
-    int timeout = INT_MAX;
-
-    set_program_name(argv[0]);
-    time_init();
-    vlog_init();
-
-    short_options = long_options_to_short_options(long_options);
-    for (;;) {
-        int option;
-
-        option = getopt_long(argc, argv, short_options, long_options, NULL);
-        if (option == -1) {
-            break;
-        }
-
-        if ((option > UCHAR_MAX || !strchr("FhVv?", option))
-            && config_set == false) {
-            ovs_fatal(0, "no config file specified (use --help for help)");
-        }
-
-        switch (option) {
-        case 'T':
-            if (config_set) {
-                ovs_fatal(0, "--timeout or -T must be specified "
-                          "before --file or -F");
-            }
-            timeout = atoi(optarg);
-            break;
-
-        case 'F': 
-            open_config(optarg, timeout);
-            config_set = true;
-            break;
-
-       case 'a':
-            cfg_add_entry("%s", optarg);
-            break;
-
-        case 'd':
-            cfg_del_entry("%s", optarg);
-            break;
-
-        case 'D':
-            cfg_del_section("%s", optarg);
-            break;
-
-        case OPT_DEL_MATCH:
-            cfg_del_match("%s", optarg);
-            break;
-
-        case 'q':
-            print_vals(optarg);
-            break;
-
-        case 'c':
-            log_diffs();
-            break;
-
-        case 'h':
-            usage(argv[0], EXIT_SUCCESS);
-            break;
-
-        case 'V':
-            OVS_PRINT_VERSION(0, 0);
-            exit(EXIT_SUCCESS);
-
-        case 'v':
-            vlog_set_verbosity(optarg);
-            break;
-
-        case '?':
-            exit(EXIT_FAILURE);
-
-        default:
-            NOT_REACHED();
-        }
-    }
-    free(short_options);
-
-    if (optind != argc) {
-        ovs_fatal(0, "non-option arguments not accepted "
-                  "(use --help for help)");
-    }
-
-    if (cfg_is_dirty()) {
-        cfg_write();
-    }
-    cfg_unlock();
-
-    exit(0);
-}
index 040f633..f4a3888 100644 (file)
@@ -15,74 +15,12 @@ protocol, causing them to function as L2 MAC-learning switches or hub.
 \fBovs\-controller\fR controls one or more OpenFlow switches, specified as
 one or more of the following OpenFlow connection methods:
 
-.TP
-\fBpssl:\fR[\fIport\fR][\fB:\fIip\fR]
-Listens for SSL connections from remote OpenFlow switches on
-\fIport\fR (default: 6633).  The \fB--private-key\fR,
-\fB--certificate\fR, and \fB--ca-cert\fR options are mandatory when
-this form is used.
-By default, \fB\*(PN\fR listens for connections to any local IP
-address, but \fIip\fR may be specified to listen only for connections
-to the given \fIip\fR.
-
-.TP
-\fBptcp:\fR[\fIport\fR][\fB:\fIip\fR]
-Listens for TCP connections from remote OpenFlow switches on
-\fIport\fR (default: 6633).
-By default, \fB\*(PN\fR listens for connections to any local IP
-address, but \fIip\fR may be specified to listen only for connections
-to the given \fIip\fR.
-
-.TP
-\fBpunix:\fIfile\fR
-Listens for connections from OpenFlow switches on the Unix domain
-server socket named \fIfile\fR.
-
-.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
-The specified SSL \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-The \fB--private-key\fR, \fB--certificate\fR, and \fB--ca-cert\fR
-options are mandatory when this form is used.
-
-.IP "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
-The specified TCP \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-
-.TP
-\fBunix:\fIfile\fR
-The Unix domain server socket named \fIfile\fR.
+.RS
+.so lib/vconn-passive.man
+.so lib/vconn-active.man
+.RE
 
 .SH OPTIONS
-.TP
-\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR
-Specifies a PEM file containing the private key used as the switch's
-identity for SSL connections to the controller.
-
-.TP
-\fB-c\fR, \fB--certificate=\fIcert.pem\fR
-Specifies a PEM file containing a certificate, signed by the
-controller's certificate authority (CA), that certifies the switch's
-private key to identify a trustworthy switch.
-
-.TP
-\fB-C\fR, \fB--ca-cert=\fIswitch-cacert.pem\fR
-Specifies a PEM file containing the CA certificate used to verify that
-the switch is connected to a trustworthy controller.
-
-.TP
-\fB--peer-ca-cert=\fIcontroller-cacert.pem\fR
-Specifies a PEM file that contains one or more additional certificates
-to send to switches.  \fIcontroller-cacert.pem\fR should be the CA
-certificate used to sign the controller's own certificate (the
-certificate specified on \fB-c\fR or \fB--certificate\fR).
-
-This option is not useful in normal operation, because the switch must
-already have the controller CA certificate for it to have any
-confidence in the controller's identity.  However, this option allows
-a newly installed switch to obtain the controller CA certificate on
-first boot using, e.g., the \fB--bootstrap-ca-cert\fR option to
-\fBovs\-openflowd\fR(8).
-
 .IP "\fB-n\fR, \fB--noflow\fR"
 By default, \fBovs\-controller\fR sets up a flow in each OpenFlow switch
 whenever it receives a packet whose destination is known due through
@@ -143,6 +81,8 @@ to it by switches.
 This option is only for debugging the Open vSwitch implementation of
 ``fail open'' mode.  It must not be used in production.
 
+.so lib/ssl.man
+.so lib/ssl-peer-ca-cert.man
 .so lib/daemon.man
 .so lib/vlog.man
 .so lib/common.man
index bb55c7f..0497d9a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "command-line.h"
 #include "compiler.h"
 #include "daemon.h"
-#include "fault.h"
 #include "learning-switch.h"
 #include "ofpbuf.h"
 #include "openflow/openflow.h"
 #include "poll-loop.h"
 #include "rconn.h"
+#include "stream-ssl.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
-#include "vconn-ssl.h"
 #include "vconn.h"
 
 #include "vlog.h"
@@ -83,8 +82,8 @@ main(int argc, char *argv[])
     int retval;
     int i;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
-    register_fault_handlers();
     time_init();
     vlog_init();
     parse_options(argc, argv);
@@ -127,13 +126,15 @@ main(int argc, char *argv[])
     }
 
     die_if_already_running();
-    daemonize();
+    daemonize_start();
 
     retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ovs_fatal(retval, "Could not listen for unixctl connections");
+        exit(EXIT_FAILURE);
     }
 
+    daemonize_complete();
+
     while (n_switches > 0 || n_listeners > 0) {
         int iteration;
         int i;
@@ -255,7 +256,7 @@ parse_options(int argc, char *argv[])
         DAEMON_LONG_OPTIONS,
         VLOG_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
-        VCONN_SSL_LONG_OPTIONS
+        STREAM_SSL_LONG_OPTIONS
         {"peer-ca-cert", required_argument, 0, OPT_PEER_CA_CERT},
 #endif
         {0, 0, 0, 0},
@@ -315,10 +316,10 @@ parse_options(int argc, char *argv[])
         DAEMON_OPTION_HANDLERS
 
 #ifdef HAVE_OPENSSL
-        VCONN_SSL_OPTION_HANDLERS
+        STREAM_SSL_OPTION_HANDLERS
 
         case OPT_PEER_CA_CERT:
-            vconn_ssl_set_peer_ca_cert_file(optarg);
+            stream_ssl_set_peer_ca_cert_file(optarg);
             break;
 #endif
 
index 2a7ba41..0b7d92d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -75,6 +75,7 @@ main(int argc, char *argv[])
     int retval;
     int i;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
     time_init();
     vlog_init();
@@ -102,7 +103,7 @@ main(int argc, char *argv[])
         struct iface *iface = &ifaces[i];
         dhclient_init(iface->dhcp, 0);
     }
-    fatal_signal_add_hook(release_ifaces, NULL, true);
+    fatal_signal_add_hook(release_ifaces, NULL, NULL, true);
 
     retval = regcomp(&accept_controller_regex, accept_controller_re,
                      REG_NOSUB | REG_EXTENDED);
@@ -115,14 +116,13 @@ main(int argc, char *argv[])
 
     retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ovs_fatal(retval, "Could not listen for unixctl connections");
+        exit(EXIT_FAILURE);
     }
 
     die_if_already_running();
 
     signal(SIGPIPE, SIG_IGN);
     for (;;) {
-        fatal_signal_block();
         for (i = 0; i < n_ifaces; i++) {
             struct iface *iface = &ifaces[i];
             dhclient_run(iface->dhcp);
@@ -195,7 +195,6 @@ main(int argc, char *argv[])
             dhclient_wait(iface->dhcp);
         }
         unixctl_server_wait(unixctl);
-        fatal_signal_unblock();
         poll_block();
     }
 
@@ -215,7 +214,7 @@ iface_init(struct iface *iface, const char *netdev_name)
          * persists past program termination. */
         struct netdev *netdev;
 
-        retval = netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, &netdev);
+        retval = netdev_open_default(iface->name, &netdev);
         if (retval) {
             ovs_error(retval, "Could not open %s device", iface->name);
             return false;
index 932aa89..ebcf2e2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include "vlog.h"
 #define THIS_MODULE VLM_dpctl
 
-struct command {
-    const char *name;
-    int min_args;
-    int max_args;
-    void (*handler)(int argc, char *argv[]);
-};
-
-static struct command all_commands[];
+static const struct command all_commands[];
 
 static void usage(void) NO_RETURN;
 static void parse_options(int argc, char *argv[]);
 
-int main(int argc, char *argv[])
+int
+main(int argc, char *argv[])
 {
-    struct command *p;
-
     set_program_name(argv[0]);
     time_init();
     vlog_init();
     parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
-
-    argc -= optind;
-    argv += optind;
-    if (argc < 1)
-        ovs_fatal(0, "missing command name; use --help for help");
-
-    for (p = all_commands; p->name != NULL; p++) {
-        if (!strcmp(p->name, argv[0])) {
-            int n_arg = argc - 1;
-            if (n_arg < p->min_args)
-                ovs_fatal(0, "'%s' command requires at least %d arguments",
-                          p->name, p->min_args);
-            else if (n_arg > p->max_args)
-                ovs_fatal(0, "'%s' command takes at most %d arguments",
-                          p->name, p->max_args);
-            else {
-                p->handler(argc, argv);
-                if (ferror(stdout)) {
-                    ovs_fatal(0, "write to stdout failed");
-                }
-                if (ferror(stderr)) {
-                    ovs_fatal(0, "write to stderr failed");
-                }
-                exit(0);
-            }
-        }
-    }
-    ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
-
+    run_command(argc - optind, argv + optind, all_commands);
     return 0;
 }
 
@@ -204,7 +168,7 @@ static int if_up(const char *netdev_name)
     struct netdev *netdev;
     int retval;
 
-    retval = netdev_open(netdev_name, NETDEV_ETH_TYPE_NONE, &netdev);
+    retval = netdev_open_default(netdev_name, &netdev);
     if (!retval) {
         retval = netdev_turn_flags_on(netdev, NETDEV_UP, true);
         netdev_close(netdev);
@@ -212,11 +176,30 @@ static int if_up(const char *netdev_name)
     return retval;
 }
 
+static int
+parsed_dpif_open(const char *arg_, bool create, struct dpif **dpifp)
+{
+    int result;
+    char *name, *type;
+
+    dp_parse_name(arg_, &name, &type);
+
+    if (create) {
+        result = dpif_create(name, type, dpifp);
+    } else {
+        result = dpif_open(name, type, dpifp);
+    }
+
+    free(name);
+    free(type);
+    return result;
+}
+
 static void
 do_add_dp(int argc UNUSED, char *argv[])
 {
     struct dpif *dpif;
-    run(dpif_create(argv[1], &dpif), "add_dp");
+    run(parsed_dpif_open(argv[1], true, &dpif), "add_dp");
     dpif_close(dpif);
     if (argc > 2) {
         do_add_if(argc, argv);
@@ -227,7 +210,7 @@ static void
 do_del_dp(int argc UNUSED, char *argv[])
 {
     struct dpif *dpif;
-    run(dpif_open(argv[1], &dpif), "opening datapath");
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
     run(dpif_delete(dpif), "del_dp");
     dpif_close(dpif);
 }
@@ -254,7 +237,7 @@ do_add_if(int argc UNUSED, char *argv[])
     struct dpif *dpif;
     int i;
 
-    run(dpif_open(argv[1], &dpif), "opening datapath");
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
     for (i = 2; i < argc; i++) {
         char *save_ptr = NULL;
         char *devname, *suboptions;
@@ -332,7 +315,7 @@ do_del_if(int argc UNUSED, char *argv[])
     struct dpif *dpif;
     int i;
 
-    run(dpif_open(argv[1], &dpif), "opening datapath");
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
     for (i = 2; i < argc; i++) {
         const char *name = argv[i];
         uint16_t port;
@@ -404,7 +387,7 @@ do_show(int argc, char *argv[])
             struct dpif *dpif;
             int error;
 
-            error = dpif_open(name, &dpif);
+            error = parsed_dpif_open(name, false, &dpif);
             if (!error) {
                 show_dpif(dpif);
             } else {
@@ -420,7 +403,7 @@ do_show(int argc, char *argv[])
             int error;
 
             sprintf(name, "dp%u", i);
-            error = dpif_open(name, &dpif);
+            error = parsed_dpif_open(name, false, &dpif);
             if (!error) {
                 show_dpif(dpif);
             } else if (error != ENODEV) {
@@ -437,22 +420,34 @@ do_show(int argc, char *argv[])
 static void
 do_dump_dps(int argc UNUSED, char *argv[] UNUSED)
 {
-    struct svec all_dps;
+    struct svec dpif_names, dpif_types;
     unsigned int i;
-    int error;
+    int error = 0;
+
+    svec_init(&dpif_names);
+    svec_init(&dpif_types);
+    dp_enumerate_types(&dpif_types);
+
+    for (i = 0; i < dpif_types.n; i++) {
+        unsigned int j;
+        int retval;
 
-    svec_init(&all_dps);
-    error = dp_enumerate(&all_dps);
+        retval = dp_enumerate_names(dpif_types.names[i], &dpif_names);
+        if (retval) {
+            error = retval;
+        }
 
-    for (i = 0; i < all_dps.n; i++) {
-        struct dpif *dpif;
-        if (!dpif_open(all_dps.names[i], &dpif)) {
-            printf("%s\n", dpif_name(dpif));
-            dpif_close(dpif);
+        for (j = 0; j < dpif_names.n; j++) {
+            struct dpif *dpif;
+            if (!dpif_open(dpif_names.names[j], dpif_types.names[i], &dpif)) {
+                printf("%s\n", dpif_name(dpif));
+                dpif_close(dpif);
+            }
         }
     }
 
-    svec_destroy(&all_dps);
+    svec_destroy(&dpif_names);
+    svec_destroy(&dpif_types);
     if (error) {
         exit(EXIT_FAILURE);
     }
@@ -467,7 +462,7 @@ do_dump_flows(int argc UNUSED, char *argv[])
     struct ds ds;
     size_t i;
 
-    run(dpif_open(argv[1], &dpif), "opening datapath");
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
     run(dpif_flow_list_all(dpif, &flows, &n_flows), "listing all flows");
 
     ds_init(&ds);
@@ -493,7 +488,7 @@ do_del_flows(int argc UNUSED, char *argv[])
 {
     struct dpif *dpif;
 
-    run(dpif_open(argv[1], &dpif), "opening datapath");
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
     run(dpif_flow_flush(dpif), "deleting all flows");
     dpif_close(dpif);
 }
@@ -505,7 +500,7 @@ do_dump_groups(int argc UNUSED, char *argv[])
     struct dpif *dpif;
     unsigned int i;
 
-    run(dpif_open(argv[1], &dpif), "opening datapath");
+    run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath");
     run(dpif_get_dp_stats(dpif, &stats), "get datapath stats");
     for (i = 0; i < stats.max_groups; i++) {
         uint16_t *ports;
@@ -531,7 +526,7 @@ do_help(int argc UNUSED, char *argv[] UNUSED)
     usage();
 }
 
-static struct command all_commands[] = {
+static const struct command all_commands[] = {
     { "add-dp", 1, INT_MAX, do_add_dp },
     { "del-dp", 1, 1, do_del_dp },
     { "add-if", 2, INT_MAX, do_add_if },
index 439d3f5..17b5a40 100644 (file)
@@ -26,27 +26,20 @@ connecting to an OpenFlow switch.  The following connection methods
 are supported:
 
 .RS
-.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
-The specified SSL \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-The \fB--private-key\fR, \fB--certificate\fR, and \fB--ca-cert\fR
-options are mandatory when this form is used.
-
-.IP "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
-The specified TCP \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-
-.TP
-\fBunix:\fIfile\fR
-The Unix domain server socket named \fIfile\fR.  
-
+.so lib/vconn-active.man
+.
 .IP "\fIfile\fR"
 This is short for \fBunix:\fIfile\fR, as long as \fIfile\fR does not
 contain a colon.
-
-.IP \fIdp\fR
-This is short for \fBunix:@RUNDIR@/\fIdp\fB.mgmt\fR, as long as
-\fIdp\fR does not contain a colon.
+.
+.IP \fIbridge\fR
+This is short for \fBunix:@RUNDIR@/\fIbridge\fB.mgmt\fR, as long as
+\fIbridge\fR does not contain a colon.
+.
+.IP [\fItype\fB@\fR]\fIdp\fR
+Attempts to look up the bridge associated with \fIdp\fR and open as
+above.  If \fItype\fR is given, it specifies the datapath provider of
+\fIdp\fR, otherwise the default provider \fBsystem\fR is assumed.
 .RE
 
 .TP
@@ -164,15 +157,6 @@ flow expirations.
 This command may be useful for debugging switch or controller
 implementations.
 
-.TP
-\fBexecute \fIswitch command \fR[\fIarg\fR...]
-Sends a request to \fIswitch\fR to execute \fIcommand\fR along with
-each \fIarg\fR, if any, then waits for the command to complete and
-reports its completion status on \fBstderr\fR and its output, if any,
-on \fBstdout\fR.  The set of available commands and their argument is
-switch-dependent.  (This command uses a Nicira extension to OpenFlow
-that may not be available on all switches.)
-
 .SS "OpenFlow Switch and Controller Commands"
 
 The following commands, like those in the previous section, may be
@@ -451,28 +435,7 @@ described in \fBFlow Syntax\fR, above.
 \fB--strict\fR
 Uses strict matching when running flow modification commands.
 
-.TP
-\fB-t\fR, \fB--timeout=\fIsecs\fR
-Limits \fBovs\-ofctl\fR runtime to approximately \fIsecs\fR seconds.  If
-the timeout expires, \fBovs\-ofctl\fR will exit with a \fBSIGALRM\fR
-signal.
-
-.TP
-\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR
-Specifies a PEM file containing the private key used as the
-identity for SSL connections to a switch.
-
-.TP
-\fB-c\fR, \fB--certificate=\fIcert.pem\fR
-Specifies a PEM file containing a certificate, signed by the
-controller's certificate authority (CA), that certifies the
-private key to identify a trustworthy controller.
-
-.TP
-\fB-C\fR, \fB--ca-cert=\fIcacert.pem\fR
-Specifies a PEM file containing the CA certificate used to verify that
-a switch is trustworthy.
-
+.so lib/ssl.man
 .so lib/vlog.man
 .so lib/common.man
 
index 199bd43..2447ba2 100644 (file)
@@ -44,9 +44,9 @@
 #include "packets.h"
 #include "random.h"
 #include "socket-util.h"
+#include "stream-ssl.h"
 #include "timeval.h"
 #include "util.h"
-#include "vconn-ssl.h"
 #include "vconn.h"
 
 #include "vlog.h"
 #define MOD_PORT_CMD_FLOOD   "flood"
 #define MOD_PORT_CMD_NOFLOOD "noflood"
 
+/* Use strict matching for flow mod commands? */
+static bool strict;
 
-/* Settings that may be configured by the user. */
-struct settings {
-    bool strict;        /* Use strict matching for flow mod commands */
-};
-
-struct command {
-    const char *name;
-    int min_args;
-    int max_args;
-    void (*handler)(const struct settings *, int argc, char *argv[]);
-};
-
-static struct command all_commands[];
+static const struct command all_commands[];
 
 static void usage(void) NO_RETURN;
-static void parse_options(int argc, char *argv[], struct settings *);
+static void parse_options(int argc, char *argv[]);
 
-int main(int argc, char *argv[])
+int
+main(int argc, char *argv[])
 {
-    struct settings s;
-    struct command *p;
-
     set_program_name(argv[0]);
     time_init();
     vlog_init();
-    parse_options(argc, argv, &s);
+    parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
-
-    argc -= optind;
-    argv += optind;
-    if (argc < 1)
-        ovs_fatal(0, "missing command name; use --help for help");
-
-    for (p = all_commands; p->name != NULL; p++) {
-        if (!strcmp(p->name, argv[0])) {
-            int n_arg = argc - 1;
-            if (n_arg < p->min_args)
-                ovs_fatal(0, "'%s' command requires at least %d arguments",
-                          p->name, p->min_args);
-            else if (n_arg > p->max_args)
-                ovs_fatal(0, "'%s' command takes at most %d arguments",
-                          p->name, p->max_args);
-            else {
-                p->handler(&s, argc, argv);
-                if (ferror(stdout)) {
-                    ovs_fatal(0, "write to stdout failed");
-                }
-                if (ferror(stderr)) {
-                    ovs_fatal(0, "write to stderr failed");
-                }
-                exit(0);
-            }
-        }
-    }
-    ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]);
-
+    run_command(argc - optind, argv + optind, all_commands);
     return 0;
 }
 
 static void
-parse_options(int argc, char *argv[], struct settings *s)
+parse_options(int argc, char *argv[])
 {
     enum {
         OPT_STRICT = UCHAR_MAX + 1,
@@ -132,14 +92,11 @@ parse_options(int argc, char *argv[], struct settings *s)
         {"help", no_argument, 0, 'h'},
         {"version", no_argument, 0, 'V'},
         VLOG_LONG_OPTIONS,
-        VCONN_SSL_LONG_OPTIONS
+        STREAM_SSL_LONG_OPTIONS
         {0, 0, 0, 0},
     };
     char *short_options = long_options_to_short_options(long_options);
 
-    /* Set defaults that we can figure out before parsing options. */
-    s->strict = false;
-
     for (;;) {
         unsigned long int timeout;
         int c;
@@ -168,11 +125,11 @@ parse_options(int argc, char *argv[], struct settings *s)
             exit(EXIT_SUCCESS);
 
         case OPT_STRICT:
-            s->strict = true;
+            strict = true;
             break;
 
         VLOG_OPTION_HANDLERS
-        VCONN_SSL_OPTION_HANDLERS
+        STREAM_SSL_OPTION_HANDLERS
 
         case '?':
             exit(EXIT_FAILURE);
@@ -205,7 +162,6 @@ usage(void)
            "  mod-flows SWITCH FLOW       modify actions of matching FLOWs\n"
            "  del-flows SWITCH [FLOW]     delete matching FLOWs\n"
            "  monitor SWITCH MISSLEN EXP  print packets received from SWITCH\n"
-           "  execute SWITCH CMD [ARG...] execute CMD with ARGS on SWITCH\n"
            "\nFor OpenFlow switches and controllers:\n"
            "  probe VCONN                 probe whether VCONN is up\n"
            "  ping VCONN [N]              latency of N-byte echos\n"
@@ -246,25 +202,36 @@ static void run(int retval, const char *message, ...)
 \f
 /* Generic commands. */
 
+static void
+open_vconn_socket(const char *name, struct vconn **vconnp)
+{
+    char *vconn_name = xasprintf("unix:%s", name);
+    VLOG_INFO("connecting to %s", vconn_name);
+    run(vconn_open_block(vconn_name, OFP_VERSION, vconnp),
+        "connecting to %s", vconn_name);
+    free(vconn_name);
+}
+
 static void
 open_vconn(const char *name, struct vconn **vconnp)
 {
     struct dpif *dpif;
     struct stat s;
+    char *bridge_path, *datapath_name, *datapath_type;
+
+    bridge_path = xasprintf("%s/%s.mgmt", ovs_rundir, name);
+    dp_parse_name(name, &datapath_name, &datapath_type);
 
     if (strstr(name, ":")) {
         run(vconn_open_block(name, OFP_VERSION, vconnp),
             "connecting to %s", name);
     } else if (!stat(name, &s) && S_ISSOCK(s.st_mode)) {
-        char *vconn_name = xasprintf("unix:%s", name);
-        VLOG_INFO("connecting to %s", vconn_name);
-        run(vconn_open_block(vconn_name, OFP_VERSION, vconnp),
-            "connecting to %s", vconn_name);
-        free(vconn_name);
-    } else if (!dpif_open(name, &dpif)) {
+        open_vconn_socket(name, vconnp);
+    } else if (!stat(bridge_path, &s) && S_ISSOCK(s.st_mode)) {
+        open_vconn_socket(bridge_path, vconnp);
+    } else if (!dpif_open(datapath_name, datapath_type, &dpif)) {
         char dpif_name[IF_NAMESIZE + 1];
         char *socket_name;
-        char *vconn_name;
 
         run(dpif_port_get_name(dpif, ODPP_LOCAL, dpif_name, sizeof dpif_name),
             "obtaining name of %s", dpif_name);
@@ -282,15 +249,15 @@ open_vconn(const char *name, struct vconn **vconnp)
                       name, socket_name);
         }
 
-        vconn_name = xasprintf("unix:%s", socket_name);
-        VLOG_INFO("connecting to %s", vconn_name);
-        run(vconn_open_block(vconn_name, OFP_VERSION, vconnp),
-            "connecting to %s", vconn_name);
+        open_vconn_socket(socket_name, vconnp);
         free(socket_name);
-        free(vconn_name);
     } else {
         ovs_fatal(0, "%s is not a valid connection method", name);
     }
+
+    free(datapath_name);
+    free(datapath_type);
+    free(bridge_path);
 }
 
 static void *
@@ -372,14 +339,14 @@ dump_trivial_stats_transaction(const char *vconn_name, uint8_t stats_type)
 }
 
 static void
-do_show(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_show(int argc UNUSED, char *argv[])
 {
     dump_trivial_transaction(argv[1], OFPT_FEATURES_REQUEST);
     dump_trivial_transaction(argv[1], OFPT_GET_CONFIG_REQUEST);
 }
 
 static void
-do_status(const struct settings *s UNUSED, int argc, char *argv[])
+do_status(int argc, char *argv[])
 {
     struct nicira_header *request, *reply;
     struct vconn *vconn;
@@ -411,13 +378,13 @@ do_status(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_dump_desc(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_dump_desc(int argc UNUSED, char *argv[])
 {
     dump_trivial_stats_transaction(argv[1], OFPST_DESC);
 }
 
 static void
-do_dump_tables(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_dump_tables(int argc UNUSED, char *argv[])
 {
     dump_trivial_stats_transaction(argv[1], OFPST_TABLE);
 }
@@ -813,7 +780,7 @@ str_to_flow(char *string, struct ofp_match *match, struct ofpbuf *actions,
 }
 
 static void
-do_dump_flows(const struct settings *s UNUSED, int argc, char *argv[])
+do_dump_flows(int argc, char *argv[])
 {
     struct ofp_flow_stats_request *req;
     uint16_t out_port;
@@ -829,7 +796,7 @@ do_dump_flows(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_dump_aggregate(const struct settings *s UNUSED, int argc, char *argv[])
+do_dump_aggregate(int argc, char *argv[])
 {
     struct ofp_aggregate_stats_request *req;
     struct ofpbuf *request;
@@ -845,7 +812,7 @@ do_dump_aggregate(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_add_flow(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_add_flow(int argc UNUSED, char *argv[])
 {
     struct vconn *vconn;
     struct ofpbuf *buffer;
@@ -873,7 +840,7 @@ do_add_flow(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_add_flows(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_add_flows(int argc UNUSED, char *argv[])
 {
     struct vconn *vconn;
     FILE *file;
@@ -926,7 +893,7 @@ do_add_flows(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_mod_flows(const struct settings *s, int argc UNUSED, char *argv[])
+do_mod_flows(int argc UNUSED, char *argv[])
 {
     uint16_t priority, idle_timeout, hard_timeout;
     struct vconn *vconn;
@@ -941,7 +908,7 @@ do_mod_flows(const struct settings *s, int argc UNUSED, char *argv[])
                 NULL, NULL, &priority, &idle_timeout, &hard_timeout);
     ofm = buffer->data;
     ofm->match = match;
-    if (s->strict) {
+    if (strict) {
         ofm->command = htons(OFPFC_MODIFY_STRICT);
     } else {
         ofm->command = htons(OFPFC_MODIFY);
@@ -957,7 +924,7 @@ do_mod_flows(const struct settings *s, int argc UNUSED, char *argv[])
     vconn_close(vconn);
 }
 
-static void do_del_flows(const struct settings *s, int argc, char *argv[])
+static void do_del_flows(int argc, char *argv[])
 {
     struct vconn *vconn;
     uint16_t priority;
@@ -969,7 +936,7 @@ static void do_del_flows(const struct settings *s, int argc, char *argv[])
     ofm = make_openflow(sizeof *ofm, OFPT_FLOW_MOD, &buffer);
     str_to_flow(argc > 2 ? argv[2] : "", &ofm->match, NULL, NULL, 
                 &out_port, &priority, NULL, NULL);
-    if (s->strict) {
+    if (strict) {
         ofm->command = htons(OFPFC_DELETE_STRICT);
     } else {
         ofm->command = htons(OFPFC_DELETE);
@@ -987,7 +954,7 @@ static void do_del_flows(const struct settings *s, int argc, char *argv[])
 }
 
 static void
-do_monitor(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_monitor(int argc UNUSED, char *argv[])
 {
     struct vconn *vconn;
 
@@ -1012,13 +979,13 @@ do_monitor(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_dump_ports(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_dump_ports(int argc UNUSED, char *argv[])
 {
     dump_trivial_stats_transaction(argv[1], OFPST_PORT);
 }
 
 static void
-do_probe(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_probe(int argc UNUSED, char *argv[])
 {
     struct ofpbuf *request;
     struct vconn *vconn;
@@ -1035,7 +1002,7 @@ do_probe(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_mod_port(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_mod_port(int argc UNUSED, char *argv[])
 {
     struct ofpbuf *request, *reply;
     struct ofp_switch_features *osf;
@@ -1115,7 +1082,7 @@ do_mod_port(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_ping(const struct settings *s UNUSED, int argc, char *argv[])
+do_ping(int argc, char *argv[])
 {
     size_t max_payload = 65535 - sizeof(struct ofp_header);
     unsigned int payload;
@@ -1162,7 +1129,7 @@ do_ping(const struct settings *s UNUSED, int argc, char *argv[])
 }
 
 static void
-do_benchmark(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
+do_benchmark(int argc UNUSED, char *argv[])
 {
     size_t max_payload = 65535 - sizeof(struct ofp_header);
     struct timeval start, end;
@@ -1205,77 +1172,12 @@ do_benchmark(const struct settings *s UNUSED, int argc UNUSED, char *argv[])
 }
 
 static void
-do_execute(const struct settings *s UNUSED, int argc, char *argv[])
-{
-    struct vconn *vconn;
-    struct ofpbuf *request;
-    struct nicira_header *nicira;
-    struct nx_command_reply *ncr;
-    uint32_t xid;
-    int i;
-
-    nicira = make_openflow(sizeof *nicira, OFPT_VENDOR, &request);
-    xid = nicira->header.xid;
-    nicira->vendor = htonl(NX_VENDOR_ID);
-    nicira->subtype = htonl(NXT_COMMAND_REQUEST);
-    ofpbuf_put(request, argv[2], strlen(argv[2]));
-    for (i = 3; i < argc; i++) {
-        ofpbuf_put_zeros(request, 1);
-        ofpbuf_put(request, argv[i], strlen(argv[i]));
-    }
-    update_openflow_length(request);
-
-    open_vconn(argv[1], &vconn);
-    run(vconn_send_block(vconn, request), "send");
-
-    for (;;) {
-        struct ofpbuf *reply;
-        uint32_t status;
-
-        run(vconn_recv_xid(vconn, xid, &reply), "recv_xid");
-        if (reply->size < sizeof *ncr) {
-            ovs_fatal(0, "reply is too short (%zu bytes < %zu bytes)",
-                      reply->size, sizeof *ncr);
-        }
-        ncr = reply->data;
-        if (ncr->nxh.header.type != OFPT_VENDOR
-            || ncr->nxh.vendor != htonl(NX_VENDOR_ID)
-            || ncr->nxh.subtype != htonl(NXT_COMMAND_REPLY)) {
-            ovs_fatal(0, "reply is invalid");
-        }
-
-        status = ntohl(ncr->status);
-        if (status & NXT_STATUS_STARTED) {
-            /* Wait for a second reply. */
-            continue;
-        } else if (status & NXT_STATUS_EXITED) {
-            fprintf(stderr, "process terminated normally with exit code %d",
-                    status & NXT_STATUS_EXITSTATUS);
-        } else if (status & NXT_STATUS_SIGNALED) {
-            fprintf(stderr, "process terminated by signal %d",
-                    status & NXT_STATUS_TERMSIG);
-        } else if (status & NXT_STATUS_ERROR) {
-            fprintf(stderr, "error executing command");
-        } else {
-            fprintf(stderr, "process terminated for unknown reason");
-        }
-        if (status & NXT_STATUS_COREDUMP) {
-            fprintf(stderr, " (core dumped)");
-        }
-        putc('\n', stderr);
-
-        fwrite(ncr + 1, reply->size - sizeof *ncr, 1, stdout);
-        break;
-    }
-}
-
-static void
-do_help(const struct settings *s UNUSED, int argc UNUSED, char *argv[] UNUSED)
+do_help(int argc UNUSED, char *argv[] UNUSED)
 {
     usage();
 }
 
-static struct command all_commands[] = {
+static const struct command all_commands[] = {
     { "show", 1, 1, do_show },
     { "status", 1, 2, do_status },
     { "monitor", 1, 3, do_monitor },
@@ -1292,7 +1194,6 @@ static struct command all_commands[] = {
     { "probe", 1, 1, do_probe },
     { "ping", 1, 2, do_ping },
     { "benchmark", 3, 3, do_benchmark },
-    { "execute", 2, INT_MAX, do_execute },
     { "help", 0, INT_MAX, do_help },
     { NULL, 0, 0, NULL },
 };
index 25b222f..2441279 100644 (file)
@@ -21,23 +21,9 @@ to relay.  It takes one of the following forms:
 .PP
 The optional \fIcontroller\fR argument specifies how to connect to
 the OpenFlow controller.  It takes one of the following forms:
-
-.RS
-.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
-The specified SSL \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-The \fB--private-key\fR, \fB--certificate\fR, and \fB--ca-cert\fR
-options are mandatory when this form is used.
-
-.IP "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
-The specified TCP \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-
-.TP
-\fBunix:\fIfile\fR
-The Unix domain server socket named \fIfile\fR.
-.RE
-
+.
+.so lib/vconn-active.man
+.
 .PP
 If \fIcontroller\fR is omitted, \fBovs\-openflowd\fR attempts to discover the
 location of the controller automatically (see below).
@@ -218,14 +204,6 @@ If this option is omitted, the default datapath ID is taken from the
 Ethernet address of the datapath's local port (which is typically
 randomly generated).
 
-.TP
-\fB--mgmt-id=\fImgmtid\fR
-Sets \fImgmtid\fR, which must consist of exactly 12 hexadecimal
-digits, as the switch's management ID.
-
-If this option is omitted, the management ID defaults to 0, signaling
-to the controller that management is supported but not configured.
-
 .TP
 \fB--fail=\fR[\fBopen\fR|\fBclosed\fR]
 The controller is, ordinarily, responsible for setting up all flows on
@@ -310,25 +288,7 @@ multiple connection methods.  If a single \fImethod\fR of \fBnone\fR is
 used, no listeners will be created.
 
 .RS
-.TP
-\fBpssl:\fR[\fIport\fR][\fB:\fIip\fR]
-Listens for SSL connections on \fIport\fR (default: 6633).  The
-\fB--private-key\fR, \fB--certificate\fR, and \fB--ca-cert\fR options
-are mandatory when this form is used.
-By default, \fB\*(PN\fR listens for connections to any local IP
-address, but \fIip\fR may be specified to listen only for connections
-to the given \fIip\fR.
-
-.TP
-\fBptcp:\fR[\fIport\fR][\fB:\fIip\fR]
-Listens for TCP connections on \fIport\fR (default: 6633).
-By default, \fB\*(PN\fR listens for connections to any local IP
-address, but \fIip\fR may be specified to listen only for connections
-to the given \fIip\fR.
-
-.TP
-\fBpunix:\fIfile\fR
-Listens for connections on Unix domain server socket named \fIfile\fR.
+.so lib/vconn-passive.man
 .RE
 
 .TP
@@ -388,34 +348,6 @@ specified on \fB--rate-limit\fR.
 
 This option takes effect only when \fB--rate-limit\fR is also specified.
 
-.SS "Remote Command Execution Options"
-
-.TP
-\fB--command-acl=\fR[\fB!\fR]\fIglob\fR[\fB,\fR[\fB!\fR]\fIglob\fR...]
-Configures the commands that remote OpenFlow connections are allowed
-to invoke using (e.g.) \fBovs\-ofctl execute\fR.  The argument is a
-comma-separated sequence of shell glob patterns.  A glob pattern
-specified without a leading \fB!\fR is a ``whitelist'' that specifies
-a set of commands that are that may be invoked, whereas a pattern that
-does begin with \fB!\fR is a ``blacklist'' that specifies commands
-that may not be invoked.  To be permitted, a command name must be
-whitelisted and must not be blacklisted;
-e.g. \fB--command-acl=up*,!upgrade\fR would allow any command whose name
-begins with \fBup\fR except for the command named \fBupgrade\fR.
-Command names that include characters other than upper- and lower-case
-English letters, digits, and the underscore and hyphen characters are
-unconditionally disallowed.
-
-When the whitelist and blacklist permit a command name, \fBovs\-openflowd\fR
-looks for a program with the same name as the command in the commands
-directory (see below).  Other directories are not searched.
-
-.TP
-\fB--command-dir=\fIdirectory\fR
-Sets the directory searched for remote command execution to
-\fBdirectory\fR.  The default directory is
-\fB@pkgdatadir@/commands\fR.
-
 .SS "Datapath Options"
 .
 .IP "\fB\-\-ports=\fIport\fR[\fB,\fIport\fR...]"
@@ -435,43 +367,8 @@ switching.
 .SS "Daemon Options"
 .so lib/daemon.man
 
-.SS "Public Key Infrastructure Options"
-
-.TP
-\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR
-Specifies a PEM file containing the private key used as the switch's
-identity for SSL connections to the controller.
-
-.TP
-\fB-c\fR, \fB--certificate=\fIcert.pem\fR
-Specifies a PEM file containing a certificate, signed by the
-controller's certificate authority (CA), that certifies the switch's
-private key to identify a trustworthy switch.
-
-.TP
-\fB-C\fR, \fB--ca-cert=\fIcacert.pem\fR
-Specifies a PEM file containing the CA certificate used to verify that
-the switch is connected to a trustworthy controller.
-
-.TP
-\fB--bootstrap-ca-cert=\fIcacert.pem\fR
-When \fIcacert.pem\fR exists, this option has the same effect as
-\fB-C\fR or \fB--ca-cert\fR.  If it does not exist, then \fBovs\-openflowd\fR
-will attempt to obtain the CA certificate from the controller on its
-first SSL connection and save it to the named PEM file.  If it is
-successful, it will immediately drop the connection and reconnect, and
-from then on all SSL connections must be authenticated by a
-certificate signed by the CA certificate thus obtained.
-
-\fBThis option exposes the SSL connection to a man-in-the-middle
-attack obtaining the initial CA certificate\fR, but it may be useful
-for bootstrapping.
-
-This option is only useful if the controller sends its CA certificate
-as part of the SSL certificate chain.  The SSL protocol does not
-require the controller to send the CA certificate, but
-\fBcontroller\fR(8) can be configured to do so with the
-\fB--peer-ca-cert\fR option.
+.so lib/ssl.man
+.so lib/ssl-bootstrap.man
 
 .SS "Logging Options"
 .so lib/vlog.man
index c275cd6..983481a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010 Nicira Networks.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -29,7 +29,6 @@
 #include "daemon.h"
 #include "dirs.h"
 #include "dpif.h"
-#include "fault.h"
 #include "leak-checker.h"
 #include "list.h"
 #include "netdev.h"
 #include "packets.h"
 #include "poll-loop.h"
 #include "rconn.h"
+#include "stream-ssl.h"
 #include "svec.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
-#include "vconn-ssl.h"
 #include "vconn.h"
 
 #include "vlog.h"
@@ -63,7 +62,8 @@ struct ofsettings {
 
     /* Datapath. */
     uint64_t datapath_id;       /* Datapath ID. */
-    const char *dp_name;        /* Name of local datapath. */
+    char *dp_name;              /* Name of local datapath. */
+    char *dp_type;              /* Type of local datapath. */
     struct svec ports;          /* Set of ports to add to datapath (if any). */
 
     /* Description strings. */
@@ -94,13 +94,6 @@ struct ofsettings {
     /* Spanning tree protocol. */
     bool enable_stp;
 
-    /* Remote command execution. */
-    char *command_acl;          /* Command white/blacklist, as shell globs. */
-    char *command_dir;          /* Directory that contains commands. */
-
-    /* Management. */
-    uint64_t mgmt_id;           /* Management ID. */
-
     /* NetFlow. */
     struct svec netflow;        /* NetFlow targets. */
 };
@@ -115,49 +108,54 @@ main(int argc, char *argv[])
     struct ofproto *ofproto;
     struct ofsettings s;
     int error;
+    struct dpif *dpif;
     struct netflow_options nf_options;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
-    register_fault_handlers();
     time_init();
     vlog_init();
     parse_options(argc, argv, &s);
     signal(SIGPIPE, SIG_IGN);
 
     die_if_already_running();
-    daemonize();
+    daemonize_start();
 
     /* Start listening for ovs-appctl requests. */
     error = unixctl_server_create(NULL, &unixctl);
     if (error) {
-        ovs_fatal(error, "Could not listen for unixctl connections");
+        exit(EXIT_FAILURE);
     }
 
     VLOG_INFO("Open vSwitch version %s", VERSION BUILDNR);
     VLOG_INFO("OpenFlow protocol version 0x%02x", OFP_VERSION);
 
-    /* Create the datapath and add ports to it, if requested by the user. */
+    error = dpif_create_and_open(s.dp_name, s.dp_type, &dpif);
+    if (error) {
+        ovs_fatal(error, "could not create datapath");
+    }
+
+    /* Add ports to the datapath if requested by the user. */
     if (s.ports.n) {
-        struct dpif *dpif;
         const char *port;
         size_t i;
-
-        error = dpif_create_and_open(s.dp_name, &dpif);
-        if (error) {
-            ovs_fatal(error, "could not create datapath");
-        }
+        struct netdev *netdev;
 
         SVEC_FOR_EACH (i, port, &s.ports) {
+            error = netdev_open_default(port, &netdev);
+            if (error) {
+                ovs_fatal(error, "failed to open %s as a device", port);
+            }
+
             error = dpif_port_add(dpif, port, 0, NULL);
             if (error) {
                 ovs_fatal(error, "failed to add %s as a port", port);
             }
         }
-        dpif_close(dpif);
     }
 
     /* Start OpenFlow processing. */
-    error = ofproto_create(s.dp_name, NULL, NULL, &ofproto);
+    error = ofproto_create(s.dp_name, s.dp_type, NULL, NULL, &ofproto);
     if (error) {
         ovs_fatal(error, "could not initialize openflow switch");
     }
@@ -173,9 +171,6 @@ main(int argc, char *argv[])
     if (s.datapath_id) {
         ofproto_set_datapath_id(ofproto, s.datapath_id);
     }
-    if (s.mgmt_id) {
-        ofproto_set_mgmt_id(ofproto, s.mgmt_id);
-    }
     ofproto_set_desc(ofproto, s.mfr_desc, s.hw_desc, s.sw_desc, s.serial_desc);
     if (!s.listeners.n) {
         svec_add_nocopy(&s.listeners, xasprintf("punix:%s/%s.mgmt",
@@ -206,11 +201,6 @@ main(int argc, char *argv[])
     if (error) {
         ovs_fatal(error, "failed to configure STP");
     }
-    error = ofproto_set_remote_execution(ofproto, s.command_acl,
-                                         s.command_dir);
-    if (error) {
-        ovs_fatal(error, "failed to configure remote command execution");
-    }
     if (!s.discovery) {
         error = ofproto_set_controller(ofproto, s.controller_name);
         if (error) {
@@ -218,6 +208,8 @@ main(int argc, char *argv[])
         }
     }
 
+    daemonize_complete();
+
     while (ofproto_is_alive(ofproto)) {
         error = ofproto_run(ofproto);
         if (error) {
@@ -234,6 +226,8 @@ main(int argc, char *argv[])
         poll_block();
     }
 
+    dpif_close(dpif);
+
     return 0;
 }
 \f
@@ -263,8 +257,6 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
         OPT_NO_STP,
         OPT_OUT_OF_BAND,
         OPT_IN_BAND,
-        OPT_COMMAND_ACL,
-        OPT_COMMAND_DIR,
         OPT_NETFLOW,
         OPT_MGMT_ID,
         OPT_PORTS,
@@ -293,10 +285,7 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
         {"no-stp",      no_argument, 0, OPT_NO_STP},
         {"out-of-band", no_argument, 0, OPT_OUT_OF_BAND},
         {"in-band",     no_argument, 0, OPT_IN_BAND},
-        {"command-acl", required_argument, 0, OPT_COMMAND_ACL},
-        {"command-dir", required_argument, 0, OPT_COMMAND_DIR},
         {"netflow",     required_argument, 0, OPT_NETFLOW},
-        {"mgmt-id",     required_argument, 0, OPT_MGMT_ID},
         {"ports",       required_argument, 0, OPT_PORTS},
         {"verbose",     optional_argument, 0, 'v'},
         {"help",        no_argument, 0, 'h'},
@@ -305,7 +294,7 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
         VLOG_LONG_OPTIONS,
         LEAK_CHECKER_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
-        VCONN_SSL_LONG_OPTIONS
+        STREAM_SSL_LONG_OPTIONS
         {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
 #endif
         {0, 0, 0, 0},
@@ -330,10 +319,7 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
     s->accept_controller_re = NULL;
     s->enable_stp = false;
     s->in_band = true;
-    s->command_acl = "";
-    s->command_dir = NULL;
     svec_init(&s->netflow);
-    s->mgmt_id = 0;
     svec_init(&s->ports);
     for (;;) {
         int c;
@@ -345,14 +331,9 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
 
         switch (c) {
         case OPT_DATAPATH_ID:
-            if (strlen(optarg) != 12
-                || strspn(optarg, "0123456789abcdefABCDEF") != 12) {
+            if (!dpid_from_string(optarg, &s->datapath_id)) {
                 ovs_fatal(0, "argument to --datapath-id must be "
-                          "exactly 12 hex digits");
-            }
-            s->datapath_id = strtoll(optarg, NULL, 16);
-            if (!s->datapath_id) {
-                ovs_fatal(0, "argument to --datapath-id must be nonzero");
+                          "exactly 12 hex digits and may not be all-zero");
             }
             break;
 
@@ -452,32 +433,10 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
             s->in_band = true;
             break;
 
-        case OPT_COMMAND_ACL:
-            s->command_acl = (s->command_acl[0]
-                              ? xasprintf("%s,%s", s->command_acl, optarg)
-                              : optarg);
-            break;
-
-        case OPT_COMMAND_DIR:
-            s->command_dir = optarg;
-            break;
-
         case OPT_NETFLOW:
             svec_add(&s->netflow, optarg);
             break;
 
-        case OPT_MGMT_ID:
-            if (strlen(optarg) != 12
-                || strspn(optarg, "0123456789abcdefABCDEF") != 12) {
-                ovs_fatal(0, "argument to --mgmt-id must be "
-                          "exactly 12 hex digits");
-            }
-            s->mgmt_id = strtoll(optarg, NULL, 16);
-            if (!s->mgmt_id) {
-                ovs_fatal(0, "argument to --mgmt-id must be nonzero");
-            }
-            break;
-
         case 'l':
             svec_add(&s->listeners, optarg);
             break;
@@ -504,10 +463,10 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
         LEAK_CHECKER_OPTION_HANDLERS
 
 #ifdef HAVE_OPENSSL
-        VCONN_SSL_OPTION_HANDLERS
+        STREAM_SSL_OPTION_HANDLERS
 
         case OPT_BOOTSTRAP_CA_CERT:
-            vconn_ssl_set_ca_cert_file(optarg, true);
+            stream_ssl_set_ca_cert_file(optarg, true);
             break;
 #endif
 
@@ -528,13 +487,14 @@ parse_options(int argc, char *argv[], struct ofsettings *s)
     }
 
     /* Local and remote vconns. */
-    s->dp_name = argv[0];
+    dp_parse_name(argv[0], &s->dp_name, &s->dp_type);
+
     s->controller_name = argc > 1 ? xstrdup(argv[1]) : NULL;
 
     /* Set accept_controller_regex. */
     if (!s->accept_controller_re) {
         s->accept_controller_re
-            = vconn_ssl_is_configured() ? "^ssl:.*" : "^tcp:.*";
+            = stream_ssl_is_configured() ? "^ssl:.*" : "^tcp:.*";
     }
 
     /* Mode of operation. */
@@ -562,8 +522,6 @@ usage(void)
     printf("\nOpenFlow options:\n"
            "  -d, --datapath-id=ID    Use ID as the OpenFlow switch ID\n"
            "                          (ID must consist of 12 hex digits)\n"
-           "  --mgmt-id=ID            Use ID as the management ID\n"
-           "                          (ID must consist of 12 hex digits)\n"
            "  --manufacturer=MFR      Identify manufacturer as MFR\n"
            "  --hardware=HW           Identify hardware as HW\n"
            "  --software=SW           Identify software as SW\n"
@@ -587,11 +545,7 @@ usage(void)
            "  --netflow=HOST:PORT     configure NetFlow output target\n"
            "\nRate-limiting of \"packet-in\" messages to the controller:\n"
            "  --rate-limit[=PACKETS]  max rate, in packets/s (default: 1000)\n"
-           "  --burst-limit=BURST     limit on packet credit for idle time\n"
-           "\nRemote command execution options:\n"
-           "  --command-acl=[!]GLOB[,[!]GLOB...] set allowed/denied commands\n"
-           "  --command-dir=DIR       set command dir (default: %s/commands)\n",
-           ovs_pkgdatadir);
+           "  --burst-limit=BURST     limit on packet credit for idle time\n");
     daemon_usage();
     vlog_usage();
     printf("\nOther options:\n"
index 2eea7fd..84565b9 100644 (file)
@@ -4,6 +4,13 @@
 .  ns
 .  IP "\\$1"
 ..
+.de ST
+.  PP
+.  RS -0.15in
+.  I "\\$1"
+.  RE
+.  PP
+..
 .TH ovs\-vsctl 8 "November 2009" "Open vSwitch" "Open vSwitch Manual"
 .ds PN ovs\-vsctl
 .
 ovs\-vsctl \- utility for querying and configuring \fBovs\-vswitchd\fR
 .
 .SH SYNOPSIS
-\fBovs\-vsctl\fR [\fIoptions\fR] \fIcommand \fR[\fIargs\fR\&...]
+\fBovs\-vsctl\fR [\fIoptions\fR] [\fB\-\-\fR] \fIcommand \fR[\fIargs\fR\&...]
 [\fB\-\-\fR \fIcommand \fR[\fIargs\fR\&...]]
 .
 .SH DESCRIPTION
-The \fBovs\-vsctl\fR program configures \fBovs\-vswitchd\fR(8), mainly
-by providing a high\-level interface to editing its configuration file
-\fBovs\-vswitchd.conf\fR(5).  This program is mainly intended for use
-when \fBovs\-vswitchd\fR is running, but it can also be used when
-\fBovs\-vswitchd\fR is not running.  In the latter case configuration
-changes will only take effect when \fBovs\-vswitchd\fR is started.
-.PP
-By default, each time \fBovs\-vsctl\fR runs, it examines and,
-depending on the requested command or commands, possibly applies
-changes to an
-\fBovs\-vswitchd.conf\fR file.  Then, if it applied any changes and if
-\fBovs\-vswitchd\fR is running, it tells \fBovs\-vswitchd\fR to reload
-the modified configuration file and waits for the reload to complete
-before exiting.
+The \fBovs\-vsctl\fR program configures \fBovs\-vswitchd\fR(8) by
+providing a high\-level interface to editing its configuration
+database.  This program is mainly intended for use when
+\fBovs\-vswitchd\fR is running.  If it is used when
+\fBovs\-vswitchd\fR is not running, then \fB\-\-no\-wait\fR should be
+specified and configuration changes will only take effect when
+\fBovs\-vswitchd\fR is started.
+.PP
+By default, each time \fBovs\-vsctl\fR runs, it connects to an
+\fBovsdb\-server\fR process that maintains an Open vSwitch
+configuration database.  Using this connection, it queries and
+possibly applies changes to the database, depending on the supplied
+commands.  Then, if it applied any changes, it waits until
+\fBovs\-vswitchd\fR has finished reconfiguring itself before it exits.
+.PP
+\fBovs\-vsctl\fR can perform any number of commands in a single run,
+implemented as a single atomic transaction against the database.
+Commands are separated on the command line by \fB\-\-\fR arguments.
 .
 .SS "Linux VLAN Bridging Compatibility"
 The \fBovs\-vsctl\fR program supports the model of a bridge
@@ -51,49 +62,38 @@ they are members.
 .
 .SH OPTIONS
 .
-The following options affect the general outline of \fBovs\-vsctl\fR's
-activities:
-.
-.IP "\fB\-c \fIfile\fR"
-.IQ "\fB\-\-config=\fIfile\fR"
-Sets the configuration file that \fBovs\-vsctl\fR reads and possibly
-modifies.  The default is \fB@localstatedir@/ovs\-vswitchd.conf\fR.
-.IP
-If \fIfile\fR is specified as \fB\-\fR, then \fBovs\-vsctl\fR reads
-the configuration file from standard input and, for commands that
-modify the configuration, writes the new one to standard output.  This
-is useful for testing but it should not be used in production because
-it bypasses the Open vSwitch configuration file locking protocol.
-.
-.IP "\fB\-t \fItarget\fR"
-.IQ "\fB\-\-target=\fItarget\fR"
-Configures how \fBovs\-vsctl\fR contacts \fBovs\-vswitchd\fR to
-instruct it to reload its configuration file.
-.IP
-If \fItarget\fR begins with \fB/\fR it must name a Unix domain socket
-on which \fBovs\-vswitchd\fR is listening for control channel
-connections.  By default, \fBovs\-vswitchd\fR listens on a Unix domain
-socket named \fB@RUNDIR@/ovs\-vswitchd.\fIpid\fB.ctl\fR, where
-\fIpid\fR is \fBovs\-vswitchd\fR's process ID.
-.IP
-Otherwise, \fBovs\-appctl\fR looks for a pidfile, that is, a file
-whose contents are the process ID of a running process as a decimal
-number, named \fB@RUNDIR@/\fItarget\fB.pid\fR.  (The \fB\-\-pidfile\fR
-option makes an Open vSwitch daemon create a pidfile.)
-\fBovs\-appctl\fR reads the pidfile, then looks for a Unix socket
-named \fB@RUNDIR@/\fItarget\fB.\fIpid\fB.ctl\fR, where \fIpid\fR is
-replaced by the process ID read from \fItarget\fR, and uses that file
-as if it had been specified directly as the target.
-.IP
-The default target is \fBovs\-vswitchd\fR.
-.IP "\fB\-\-no\-reload\fR"
-Prevents \fBovs\-vsctl\fR from telling \fBovs\-vswitchd\fR to reload
-its configuration file.
+The following options affect the behavior \fBovs\-vsctl\fR as a whole.
+Some individual commands also accept their own options, which are
+given just before the command name.  If the first command on the
+command line has options, then those options must be separated from
+the global options by \fB\-\-\fR.
+.
+.IP "\fB\-\-db=\fIserver\fR"
+Sets \fIserver\fR as the database server that \fBovs\-vsctl\fR
+contacts to query or modify configuration.  The default is
+\fBunix:@RUNDIR@/ovsdb\-server\fR.  \fIserver\fR must take one of the
+following forms:
+.RS
+.so ovsdb/remote-active.man
+.RE
+.
+.IP "\fB\-\-no\-wait\fR"
+Prevents \fBovs\-vsctl\fR from waiting for \fBovs\-vswitchd\fR to
+reconfigure itself according to the the modified database.  This
+option should be used if \fBovs\-vswitchd\fR is not running;
+otherwise, \fBovs-vsctl\fR will not exit until \fBovs-vswitchd\fR
+starts.
+.IP
+This option has no effect if the commands specified do not change the
+database.
 .
 .IP "\fB\-\-no\-syslog\fR"
 By default, \fBovs\-vsctl\fR logs its arguments and the details of any
 changes that it makes to the system log.  This option disables this
 logging.
+.IP
+This option is equivalent to \fB\-\-verbose=vvsctl:syslog:warn\fR.
+.
 .IP "\fB\-\-oneline\fR"
 Modifies the output format so that the output for each command is printed
 on a single line.  New-line characters that would otherwise separate
@@ -101,9 +101,33 @@ lines are printed as \fB\\n\fR, and any instances of \fB\\\fR that
 would otherwise appear in the output are doubled.
 Prints a blank line for each command that has no output.
 .
+.IP "\fB\-\-dry\-run\fR"
+Prevents \fBovs\-vsctl\fR from actually modifying the database.
+.
+.IP "\fB-t \fIsecs\fR"
+.IQ "\fB--timeout=\fIsecs\fR"
+Limits runtime to approximately \fIsecs\fR seconds.  A value of 
+zero will cause \fBovs\-vsctl\fR to wait forever.  If the timeout expires, 
+\fBovs\-vsctl\fR will exit with a \fBSIGALRM\fR signal.  If this option is
+not used, \fBovs\-vsctl\fR uses a timeout of five seconds.
+(A timeout would normally happen only if the database cannot be contacted.)
+.
+.so lib/ssl.man
+.so lib/vlog.man
+.
 .SH COMMANDS
 The commands implemented by \fBovs\-vsctl\fR are described in the
 sections below.
+.SS "Open vSwitch Commands"
+These commands work with an Open vSwitch as a whole.
+.
+.IP "\fBinit\fR"
+Initializes the Open vSwitch database, if it is empty.  If the
+database has already been initialized, this command has no effect.
+.IP
+Any successful \fBovs\-vsctl\fR command automatically initializes the
+Open vSwitch database if it is empty.  This command is provided to
+initialize the database without executing any other command.
 .
 .SS "Bridge Commands"
 These commands examine and manipulate Open vSwitch bridges.
@@ -119,10 +143,14 @@ itself be a fake bridge.  The new fake bridge will be on 802.1Q VLAN
 \fIvlan\fR, which must be an integer between 1 and 4095.  Initially
 \fIbridge\fR will have no ports (other than \fIbridge\fR itself).
 .
-.IP "\fBdel\-br \fIbridge\fR"
+.IP "[\fB\-\-if\-exists\fR] \fBdel\-br \fIbridge\fR"
 Deletes \fIbridge\fR and all of its ports.  If \fIbridge\fR is a real
 bridge, this command also deletes any fake bridges that were created
 with \fIbridge\fR as parent, including all of their ports.
+.IP
+Without \fB\-\-if\-exists\fR, attempting to delete a bridge that does
+not exist is an error.  With \fB\-\-if\-exists\fR, attempting to
+delete a bridge that does not exist has no effect.
 .
 .IP "\fBlist\-br\fR"
 Lists all existing real and fake bridges on standard output, one per
@@ -141,6 +169,35 @@ decimal integer.  If \fIbridge\fR is a real bridge, prints 0.
 If \fIbridge\fR is a fake bridge, prints the name of its parent
 bridge.  If \fIbridge\fR is a real bridge, print \fIbridge\fR.
 .
+.IP "\fBbr\-set\-external\-id \fIbridge key\fR [\fIvalue\fR]"
+Sets or clears an ``external ID'' value on \fIbridge\fR.  These values
+are intended to identify entities external to Open vSwitch with which
+\fIbridge\fR is associated, e.g. the bridge's identifier in a
+virtualization management platform.  The Open vSwitch database schema
+specifies well-known \fIkey\fR values, but \fIkey\fR and \fIvalue\fR
+are otherwise arbitrary strings.
+.IP
+If \fIvalue\fR is specified, then \fIkey\fR is set to \fIvalue\fR for
+\fIbridge\fR, overwriting any previous value.  If \fIvalue\fR is
+omitted, then \fIkey\fR is removed from \fIbridge\fR's set of external
+IDs (if it was present).
+.IP
+For real bridges, the effect of this command is similar to that of a
+\fBset\fR or \fBremove\fR command in the \fBexternal\-ids\fR column of
+the \fBBridge\fR table.  For fake bridges, it actually modifies keys
+with names prefixed by \fBfake\-bridge\-\fR in the \fBPort\fR table.
+.
+.IP "\fBbr\-get\-external\-id \fIbridge\fR [\fIkey\fR]"
+Queries the external IDs on \fIbridge\fR.  If \fIkey\fR is specified,
+the output is the value for that \fIkey\fR or the empty string if
+\fIkey\fR is unset.  If \fIkey\fR is omitted, the output is
+\fIkey\fB=\fIvalue\fR, one per line, for each key-value pair.
+.IP
+For real bridges, the effect of this command is similar to that of a
+\fBget\fR command in the \fBexternal\-ids\fR column of the
+\fBBridge\fR table.  For fake bridges, it queries keys with names
+prefixed by \fBfake\-bridge\-\fR in the \fBPort\fR table.
+.
 .SS "Port Commands"
 .
 These commands examine and manipulate Open vSwitch ports.  These
@@ -154,15 +211,23 @@ line.  The local port \fIbridge\fR is not included in the list.
 Creates on \fIbridge\fR a new port named \fIport\fR from the network
 device of the same name.
 .
-.IP "\fBadd\-bond \fIbridge port iface\fR\&..."
+.IP "[\fB\-\-fake\-iface\fR] \fBadd\-bond \fIbridge port iface\fR\&..."
 Creates on \fIbridge\fR a new port named \fIport\fR that bonds
 together the network devices given as each \fIiface\fR.  At least two
 interfaces must be named.
+.IP
+With \fB\-\-fake\-iface\fR, a fake interface with the name \fIport\fR is
+created.  This should only be used for compatibility with legacy
+software that requires it.
 .
-.IP "\fBdel\-port \fR[\fIbridge\fR] \fIport\fR"
+.IP "[\fB\-\-if\-exists\fR] \fBdel\-port \fR[\fIbridge\fR] \fIport\fR"
 Deletes \fIport\fR.  If \fIbridge\fR is omitted, \fIport\fR is removed
 from whatever bridge contains it; if \fIbridge\fR is specified, it
 must be the real or fake bridge that contains \fIport\fR.
+.IP
+Without \fB\-\-if\-exists\fR, attempting to delete a port that does
+not exist is an error.  With \fB\-\-if\-exists\fR, attempting to
+delete a port that does not exist has no effect.
 .
 .IP "\fBport\-to\-br \fIport\fR"
 Prints the name of the bridge that contains \fIport\fR on standard
@@ -182,6 +247,274 @@ list.
 .IP "\fBiface\-to\-br \fIiface\fR"
 Prints the name of the bridge that contains \fIiface\fR on standard
 output.
+.
+.SS "OpenFlow Controller Connectivity"
+.
+\fBovs\-vswitchd\fR can perform all configured bridging and switching
+locally, or it can be configured to connect a given bridge to an
+external OpenFlow controller, such as NOX.  
+.
+If a \fIbridge\fR argument is given, the settings apply only to the
+specified bridge.  Otherwise, they apply to the Open vSwitch instance,
+and its configuration applies to any bridge that has not been explicitly
+configured through a \fIbridge\fR argument.
+.
+.IP "\fBget\-controller\fR [\fIbridge\fR]"
+Prints the configured controller target.
+.
+.IP "\fBdel\-controller\fR [\fIbridge\fR]"
+Deletes the configured controller target.
+.
+.IP "\fBset\-controller\fR [\fIbridge\fR] \fItarget\fR"
+Sets the configured controller target.  The \fItarget\fR may use any of
+the following forms:
+.
+.RS
+.so lib/vconn-active.man
+.RE
+.
+.ST "Controller Failure Settings"
+.
+When a controller is configured, it is, ordinarily, responsible for
+setting up all flows on the switch.  Thus, if the connection to
+the controller fails, no new network connections can be set up.  If
+the connection to the controller stays down long enough, no packets
+can pass through the switch at all.
+.PP
+If the value is \fBstandalone\fR, or if neither of these settings
+is set, \fBovs\-vswitchd\fR will take over
+responsibility for setting up
+flows when no message has been received from the controller for three
+times the inactivity probe interval (xxx needs to be exposed).  In this mode,
+\fBovs\-vswitchd\fR causes the datapath to act like an ordinary
+MAC-learning switch.  \fBovs\-vswitchd\fR will continue to retry connecting
+to the controller in the background and, when the connection succeeds,
+it discontinues its standalone behavior.
+.PP
+If this option is set to \fBsecure\fR, \fBovs\-vswitchd\fR will not
+set up flows on its own when the controller connection fails.
+.
+.IP "\fBget\-fail\-mode\fR [\fIbridge\fR]"
+Prints the configured failure mode.
+.
+.IP "\fBdel\-fail\-mode\fR [\fIbridge\fR]"
+Deletes the configured failure mode.
+.
+.IP "\fBset\-fail\-mode\fR [\fIbridge\fR] \fBstandalone\fR|\fBsecure\fR"
+Sets the configured failure mode.
+.
+.SS "SSL Configuration"
+When \fBovs\-vswitchd\fR is configured to connect over SSL for management or
+controller connectivity, the following parameters are required:
+.TP
+\fBprivate-key\fR
+Specifies a PEM file containing the private key used as the virtual
+switch's identity for SSL connections to the controller.
+.TP
+\fBcertificate\fR
+Specifies a PEM file containing a certificate, signed by the
+certificate authority (CA) used by the controller and manager, that
+certifies the virtual switch's private key, identifying a trustworthy
+switch.
+.TP
+\fBca-cert\fR
+Specifies a PEM file containing the CA certificate used to verify that
+the virtual switch is connected to a trustworthy controller.
+.PP
+These files are read only once, at \fBovs\-vswitchd\fR startup time.  If
+their contents change, \fBovs\-vswitchd\fR must be killed and restarted.
+.PP
+These SSL settings apply to all SSL connections made by the virtual
+switch.
+.
+.IP "\fBget\-ssl\fR"
+Prints the SSL configuration.
+.
+.IP "\fBdel\-ssl\fR"
+Deletes the current SSL configuration.
+.
+.IP "[\fB\-\-bootstrap\fR] \fBset\-ssl\fR \fIprivate-key\fR \fIcertificate\fR \fIca-cert\fR"
+Sets the SSL configuration.  The \fB\-\-bootstrap\fR option is described 
+below.
+.
+.ST "CA Certificate Bootstrap"
+Ordinarily, all of the files named in the SSL configuration must exist
+when \fBovs\-vswitchd\fR starts.  However, if the \fB\-\-bootstrap\fR 
+option is given, then \fBovs\-vswitchd\fR will attempt to obtain the
+CA certificate from the controller on its first SSL connection and
+save it to the named PEM file.  If it is successful, it will
+immediately drop the connection and reconnect, and from then on all
+SSL connections must be authenticated by a certificate signed by the
+CA certificate thus obtained.
+.PP
+\fBThis option exposes the SSL connection to a man-in-the-middle
+attack obtaining the initial CA certificate\fR, but it may be useful
+for bootstrapping.
+.PP
+This option is only useful if the controller sends its CA certificate
+as part of the SSL certificate chain.  The SSL protocol does not
+require the controller to send the CA certificate, but
+\fBcontroller\fR(8) can be configured to do so with the
+\fB--peer-ca-cert\fR option.
+.
+.SS "Database Commands"
+.
+These commands query and modify the contents of \fBovsdb\fR tables.
+They are a slight abstraction of the \fBovsdb\fR interface and as such
+they operate at a lower level than other \fBovs\-vsctl\fR commands.
+.PP
+.ST "Identifying Tables, Records, and Columns"
+.PP
+Each of these commands has a \fItable\fR parameter to identify a table
+within the database.  Many of them also take a \fIrecord\fR parameter
+that identifies a particular record within a table.  The \fIrecord\fR
+parameter may be the UUID for a record, and many tables offer
+additional ways to identify records.  Some commands also take
+\fIcolumn\fR parameters that identify a particular field within the
+records in a table.
+.PP
+The following tables are currently defined:
+.IP "\fBOpen_vSwitch\fR"
+Global configuration for an \fBovs\-vswitchd\fR.  This table contains
+exactly one record, identified by specifying \fB.\fR as the record
+name.
+.IP "\fBBridge\fR"
+Configuration for a bridge within an Open vSwitch.  Records may be
+identified by bridge name.
+.IP "\fBPort\fR"
+A bridge port.  Records may be identified by port name.
+.IP "\fBInterface\fR"
+A network device attached to a port.  Records may be identified by
+name.
+.IP "\fBController\fR"
+Configuration for an OpenFlow controller.  A controller attached to a
+particular bridge may be identified by the bridge's name.  The default
+controller controller for an Open vSwitch may be identified by
+specifying \fB.\fR as the record name.
+.IP "\fBMirror\fR"
+A port mirroring configuration attached to a bridge.  Records may be
+identified by mirror name.
+.IP "\fBNetFlow\fR"
+A NetFlow configuration attached to a bridge.  Records may be
+identified by bridge name.
+.PP
+Names of tables, records, and columns are not case-sensitive, and
+\fB--\fR and \fB_\fR are treated interchangeably.  Unique
+abbreviations are acceptable, e.g. \fBnet\fR or \fRn\fR is sufficient
+to identify the \fBNetFlow\fR table.
+.
+.ST "Database Values"
+Each column in the database accepts a fixed type of data.  The
+currently defined basic types, and their representations, are:
+.IP "integer"
+A decimal integer in the range \-2**63 to 2**63\-1, inclusive.
+.IP "real"
+A floating-point number.
+.IP "Boolean"
+True or false, written \fBtrue\fR or \fBfalse\fR, respectively.
+.IP "string"
+An arbitrary Unicode string, except that null bytes are not allowed.
+Quotes are optional for most strings that begin with an English letter
+or underscore and consist only of letters, underscores, hyphens, and
+periods.  However, \fBtrue\fR and \fBfalse\fR and strings that match
+the syntax of UUIDs (see below) must be enclosed in double quotes to
+distinguish them from other basic types.  When double quotes are used,
+the syntax is that of strings in JSON, e.g. backslashes may be used to
+escape special characters.  The empty string must be represented as a
+pair of double quotes (\fB""\fR).
+.IP "UUID"
+A universally unique identifier in the style of RFC 4122,
+e.g. \fBf81d4fae-7dec-11d0-a765-00a0c91e6bf6\fR.
+.PP
+Multiple values in a single column may be separated by spaces or a
+single comma.  When multiple values are present, duplicates are not
+allowed, and order is not important.  Conversely, some database
+columns can have an empty set of values, represented as \fB[]\fR, and
+square brackets may optionally enclose other non-empty sets or single
+values as well.
+.PP
+A few database columns are ``maps'' of key-value pairs, where the key
+and the value are each some fixed database type.  These are specified
+in the form \fIkey\fB=\fIvalue\fR, where \fIkey\fR and \fIvalue\fR
+follow the syntax for the column's key type and value type,
+respectively.  When multiple pairs are present (separated by spaces or
+a comma), duplicate keys are not allowed, and again the order is not
+important.  Duplicate values are allowed.  An empty map is represented
+as \fB{}\fR, and curly braces may be optionally enclose non-empty maps
+as well.
+.
+.ST "Database Command Syntax"
+.PP
+By default, database commands refuse to make some kinds of
+modifications that could violate database structuring constraints.  If
+you are sure that you know what you are doing, use \fB\-\-force\fR to
+override this safety measure.  Constraints that are enforced by the
+database server itself, instead of by \fBovs\-vsctl\fR, cannot be
+overridden this way.
+.
+.IP "\fBlist \fItable \fR[\fIrecord\fR]..."
+List the values of all columns of each specified \fIrecord\fR.  If no
+records are specified, lists all the records in \fItable\fR.
+.IP
+The UUIDs shown for rows created in the same \fBovs\-vsctl\fR
+invocation will be wrong.
+.
+.IP "[\fB\-\-if\-exists\fR] \fBget \fItable record column\fR[\fB:\fIkey\fR]..."
+Prints the value of each specified \fIcolumn\fR in the given
+\fIrecord\fR in \fItable\fR.  For map columns, a \fIkey\fR may
+optionally be specified, in which case the value associated with
+\fIkey\fR in the column is printed, instead of the entire map.
+.IP
+For a map column, without \fB\-\-if\-exists\fR it is an error if
+\fIkey\fR does not exist; with it, a blank line is printed.  If
+\fIcolumn\fR is not a map column or if \fIkey\fR is not specified,
+\fB\-\-if\-exists\fR has no effect.
+.
+.IP "[\fB\-\-force\fR] \fBset \fItable record column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
+Sets the value of each specified \fIcolumn\fR in the given
+\fIrecord\fR in \fItable\fR to \fIvalue\fR.  For map columns, a
+\fIkey\fR may optionally be specified, in which case the value
+associated with \fIkey\fR in that column is changed (or added, if none
+exists), instead of the entire map.
+.
+.IP "[\fB\-\-force\fR] \fBadd \fItable record column \fR[\fIkey\fB=\fR]\fIvalue\fR..."
+Adds the specified value or key-value pair to \fIcolumn\fR in
+\fIrecord\fR in \fItable\fR.  If \fIcolumn\fR is a map, then \fIkey\fR
+is required, otherwise it is prohibited.  If \fIkey\fR already exists
+in a map column, then the current \fIvalue\fR is not replaced (use the
+\fBset\fR command to replace an existing value).
+.
+.IP "[\fB\-\-force\fR] \fBremove \fItable record column \fR\fIvalue\fR..."
+.IQ "[\fB\-\-force\fR] \fBremove \fItable record column \fR\fIkey\fR..."
+.IQ "[\fB\-\-force\fR] \fBremove \fItable record column \fR\fIkey\fB=\fR\fIvalue\fR..."
+Removes the specified values or key-value pairs from \fIcolumn\fR in
+\fIrecord\fR in \fItable\fR.  The first form applies to columns that
+are not maps: each specified \fIvalue\fR is removed from the column.
+The second and third forms apply to map columns: if only a \fIkey\fR
+is specified, then any key-value pair with the given \fIkey\fR is
+removed, regardless of its value; if a \fIvalue\fR is given then a
+pair is removed only if both key and value match.
+.IP
+It is not an error if the column does not contain the specified key or
+value or pair.
+.
+.IP "\fB[\fB\-\-force\fR] \fBclear\fR \fItable record column\fR..."
+Sets each \fIcolumn\fR in \fIrecord\fR in \fItable\fR to the empty set
+or empty map, as appropriate.  This command applies only to columns
+that are allowed to be empty.
+.
+.IP "\fB\-\-force create \fItable column\fR[\fB:\fIkey\fR]\fB=\fIvalue\fR..."
+Creates a new record in \fItable\fR and sets the initial values of
+each \fIcolumn\fR.  Columns not explicitly set will receive their
+default values.  Outputs the UUID of the new row.
+.IP
+This command requires the \fB\-\-force\fR option.
+.
+.IP "\fB\-\-force \fR[\fB\-\-if\-exists\fR] \fBdestroy \fItable record\fR..."
+Deletes each specified \fIrecord\fR from \fItable\fR.  Unless
+\fB\-\-if\-exists\fR is specified, each \fIrecord\fRs must exist.
+.IP
+This command requires the \fB\-\-force\fR option.
 .SH "EXAMPLES"
 Create a new bridge named br0 and add port eth0 to it:
 .IP
@@ -192,6 +525,15 @@ Create a new bridge named br0 and add port eth0 to it:
 Alternatively, perform both operations in a single atomic transaction:
 .IP 
 .B "ovs-vsctl add\-br br0 \-\- add\-port br0 eth0"
+.PP
+Delete bridge \fBbr0\fR, reporting an error if it does not exist:
+.IP
+.B "ovs\-vsctl del\-br br0"
+.PP
+Delete bridge \fBbr0\fR if it exists (the \fB\-\-\fR is required to
+separate \fBdel\-br\fR's options from the global options):
+.IP
+.B "ovs\-vsctl \-\- \-\-if\-exists del\-br br0"
 .
 .SH "EXIT STATUS"
 .IP "0"
@@ -203,5 +545,5 @@ The \fIbridge\fR argument to \fBbr\-exists\fR specified the name of a
 bridge that does not exist.
 .SH "SEE ALSO"
 .
-.BR ovs\-vswitchd.conf (5),
+.BR ovsdb\-server (1),
 .BR ovs\-vswitchd (8).
diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c
new file mode 100644 (file)
index 0000000..981a4e2
--- /dev/null
@@ -0,0 +1,2659 @@
+/*
+ * Copyright (c) 2009, 2010 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <regex.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "command-line.h"
+#include "compiler.h"
+#include "dirs.h"
+#include "dynamic-string.h"
+#include "json.h"
+#include "ovsdb-data.h"
+#include "ovsdb-idl.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "svec.h"
+#include "vswitchd/vswitch-idl.h"
+#include "timeval.h"
+#include "util.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_vsctl
+
+/* vsctl_fatal() also logs the error, so it is preferred in this file. */
+#define ovs_fatal please_use_vsctl_fatal_instead_of_ovs_fatal
+
+struct vsctl_context;
+
+typedef void vsctl_handler_func(struct vsctl_context *);
+
+struct vsctl_command_syntax {
+    const char *name;
+    int min_args;
+    int max_args;
+    vsctl_handler_func *run;
+    vsctl_handler_func *postprocess;
+    const char *options;
+};
+
+struct vsctl_command {
+    /* Data that remains constant after initialization. */
+    const struct vsctl_command_syntax *syntax;
+    int argc;
+    char **argv;
+    struct shash options;
+
+    /* Data modified by commands. */
+    struct ds output;
+};
+
+/* --db: The database server to contact. */
+static const char *db;
+
+/* --oneline: Write each command's output as a single line? */
+static bool oneline;
+
+/* --dry-run: Do not commit any changes. */
+static bool dry_run;
+
+/* --no-wait: Wait for ovs-vswitchd to reload its configuration? */
+static bool wait_for_reload = true;
+
+/* --timeout: Time to wait for a connection to 'db'. */
+static int timeout = 5;
+
+/* All supported commands. */
+static const struct vsctl_command_syntax all_commands[];
+
+/* The IDL we're using and the current transaction, if any.
+ * This is for use by vsctl_exit() only, to allow it to clean up.
+ * Other code should use its context arguments. */
+static struct ovsdb_idl *the_idl;
+static struct ovsdb_idl_txn *the_idl_txn;
+
+static void vsctl_exit(int status) NO_RETURN;
+static void vsctl_fatal(const char *, ...) PRINTF_FORMAT(1, 2) NO_RETURN;
+static char *default_db(void);
+static void usage(void) NO_RETURN;
+static void parse_options(int argc, char *argv[]);
+
+static struct vsctl_command *parse_commands(int argc, char *argv[],
+                                            size_t *n_commandsp);
+static void parse_command(int argc, char *argv[], struct vsctl_command *);
+static void do_vsctl(const char *args,
+                     struct vsctl_command *, size_t n_commands,
+                     struct ovsdb_idl *);
+
+int
+main(int argc, char *argv[])
+{
+    struct ovsdb_idl *idl;
+    unsigned int seqno;
+    struct vsctl_command *commands;
+    size_t n_commands;
+    char *args;
+    int trials;
+
+    set_program_name(argv[0]);
+    signal(SIGPIPE, SIG_IGN);
+    time_init();
+    vlog_init();
+    vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_WARN);
+    vlog_set_levels(VLM_reconnect, VLF_ANY_FACILITY, VLL_WARN);
+
+    /* Log our arguments.  This is often valuable for debugging systems. */
+    args = process_escape_args(argv);
+    VLOG_INFO("Called as %s", args);
+
+    /* Parse command line. */
+    parse_options(argc, argv);
+    commands = parse_commands(argc - optind, argv + optind, &n_commands);
+
+    if (timeout) {
+        time_alarm(timeout);
+    }
+
+    /* Do basic command syntax checking. */
+
+    /* Now execute the commands. */
+    idl = the_idl = ovsdb_idl_create(db, &ovsrec_idl_class);
+    seqno = ovsdb_idl_get_seqno(idl);
+    trials = 0;
+    for (;;) {
+        unsigned int new_seqno;
+
+        ovsdb_idl_run(idl);
+        new_seqno = ovsdb_idl_get_seqno(idl);
+        if (new_seqno != seqno) {
+            if (++trials > 5) {
+                vsctl_fatal("too many database inconsistency failures");
+            }
+            do_vsctl(args, commands, n_commands, idl);
+            seqno = new_seqno;
+        }
+
+        ovsdb_idl_wait(idl);
+        poll_block();
+    }
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    enum {
+        OPT_DB = UCHAR_MAX + 1,
+        OPT_ONELINE,
+        OPT_NO_SYSLOG,
+        OPT_NO_WAIT,
+        OPT_DRY_RUN,
+        VLOG_OPTION_ENUMS
+    };
+    static struct option long_options[] = {
+        {"db", required_argument, 0, OPT_DB},
+        {"no-syslog", no_argument, 0, OPT_NO_SYSLOG},
+        {"no-wait", no_argument, 0, OPT_NO_WAIT},
+        {"dry-run", no_argument, 0, OPT_DRY_RUN},
+        {"oneline", no_argument, 0, OPT_ONELINE},
+        {"timeout", required_argument, 0, 't'},
+        {"help", no_argument, 0, 'h'},
+        {"version", no_argument, 0, 'V'},
+        VLOG_LONG_OPTIONS,
+        {0, 0, 0, 0},
+    };
+
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, "+v::hVt:", long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_DB:
+            db = optarg;
+            break;
+
+        case OPT_ONELINE:
+            oneline = true;
+            break;
+
+        case OPT_NO_SYSLOG:
+            vlog_set_levels(VLM_vsctl, VLF_SYSLOG, VLL_WARN);
+            break;
+
+        case OPT_NO_WAIT:
+            wait_for_reload = false;
+            break;
+
+        case OPT_DRY_RUN:
+            dry_run = true;
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            OVS_PRINT_VERSION(0, 0);
+            exit(EXIT_SUCCESS);
+
+        case 't':
+            timeout = strtoul(optarg, NULL, 10);
+            if (timeout < 0) {
+                vsctl_fatal("value %s on -t or --timeout is invalid",
+                            optarg);
+            }
+            break;
+
+        VLOG_OPTION_HANDLERS
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+
+    if (!db) {
+        db = default_db();
+    }
+}
+
+static struct vsctl_command *
+parse_commands(int argc, char *argv[], size_t *n_commandsp)
+{
+    struct vsctl_command *commands;
+    size_t n_commands, allocated_commands;
+    int i, start;
+
+    commands = NULL;
+    n_commands = allocated_commands = 0;
+
+    for (start = i = 0; i <= argc; i++) {
+        if (i == argc || !strcmp(argv[i], "--")) {
+            if (i > start) {
+                if (n_commands >= allocated_commands) {
+                    struct vsctl_command *c;
+
+                    commands = x2nrealloc(commands, &allocated_commands,
+                                          sizeof *commands);
+                    for (c = commands; c < &commands[n_commands]; c++) {
+                        shash_moved(&c->options);
+                    }
+                }
+                parse_command(i - start, &argv[start],
+                              &commands[n_commands++]);
+            }
+            start = i + 1;
+        }
+    }
+    if (!n_commands) {
+        vsctl_fatal("missing command name (use --help for help)");
+    }
+    *n_commandsp = n_commands;
+    return commands;
+}
+
+static void
+parse_command(int argc, char *argv[], struct vsctl_command *command)
+{
+    const struct vsctl_command_syntax *p;
+    int i;
+
+    shash_init(&command->options);
+    for (i = 0; i < argc; i++) {
+        if (argv[i][0] != '-') {
+            break;
+        }
+        if (!shash_add_once(&command->options, argv[i], NULL)) {
+            vsctl_fatal("'%s' option specified multiple times", argv[i]);
+        }
+    }
+    if (i == argc) {
+        vsctl_fatal("missing command name");
+    }
+
+    for (p = all_commands; p->name; p++) {
+        if (!strcmp(p->name, argv[i])) {
+            struct shash_node *node;
+            int n_arg;
+
+            SHASH_FOR_EACH (node, &command->options) {
+                const char *s = strstr(p->options, node->name);
+                int end = s ? s[strlen(node->name)] : EOF;
+                if (end != ',' && end != ' ' && end != '\0') {
+                    vsctl_fatal("'%s' command has no '%s' option",
+                                argv[i], node->name);
+                }
+            }
+
+            n_arg = argc - i - 1;
+            if (n_arg < p->min_args) {
+                vsctl_fatal("'%s' command requires at least %d arguments",
+                            p->name, p->min_args);
+            } else if (n_arg > p->max_args) {
+                vsctl_fatal("'%s' command takes at most %d arguments",
+                            p->name, p->max_args);
+            } else {
+                command->syntax = p;
+                command->argc = n_arg + 1;
+                command->argv = &argv[i];
+                return;
+            }
+        }
+    }
+
+    vsctl_fatal("unknown command '%s'; use --help for help", argv[i]);
+}
+
+static void
+vsctl_fatal(const char *format, ...)
+{
+    char *message;
+    va_list args;
+
+    va_start(args, format);
+    message = xvasprintf(format, args);
+    va_end(args);
+
+    vlog_set_levels(VLM_vsctl, VLF_CONSOLE, VLL_EMER);
+    VLOG_ERR("%s", message);
+    ovs_error(0, "%s", message);
+    vsctl_exit(EXIT_FAILURE);
+}
+
+/* Frees the current transaction and the underlying IDL and then calls
+ * exit(status).
+ *
+ * Freeing the transaction and the IDL is not strictly necessary, but it makes
+ * for a clean memory leak report from valgrind in the normal case.  That makes
+ * it easier to notice real memory leaks. */
+static void
+vsctl_exit(int status)
+{
+    if (the_idl_txn) {
+        ovsdb_idl_txn_abort(the_idl_txn);
+        ovsdb_idl_txn_destroy(the_idl_txn);
+    }
+    ovsdb_idl_destroy(the_idl);
+    exit(status);
+}
+
+static void
+usage(void)
+{
+    printf("\
+%s: ovs-vswitchd management utility\n\
+usage: %s [OPTIONS] COMMAND [ARG...]\n\
+\n\
+Bridge commands:\n\
+  add-br BRIDGE               create a new bridge named BRIDGE\n\
+  add-br BRIDGE PARENT VLAN   create new fake BRIDGE in PARENT on VLAN\n\
+  del-br BRIDGE               delete BRIDGE and all of its ports\n\
+  list-br                     print the names of all the bridges\n\
+  br-exists BRIDGE            test whether BRIDGE exists\n\
+  br-to-vlan BRIDGE           print the VLAN which BRIDGE is on\n\
+  br-to-parent BRIDGE         print the parent of BRIDGE\n\
+  br-set-external-id BRIDGE KEY VALUE  set KEY on BRIDGE to VALUE\n\
+  br-set-external-id BRIDGE KEY  unset KEY on BRIDGE\n\
+  br-get-external-id BRIDGE KEY  print value of KEY on BRIDGE\n\
+  br-get-external-id BRIDGE  list key-value pairs on BRIDGE\n\
+\n\
+Port commands:\n\
+  list-ports BRIDGE           print the names of all the ports on BRIDGE\n\
+  add-port BRIDGE PORT        add network device PORT to BRIDGE\n\
+  add-bond BRIDGE PORT IFACE...  add bonded port PORT in BRIDGE from IFACES\n\
+  del-port [BRIDGE] PORT      delete PORT (which may be bonded) from BRIDGE\n\
+  port-to-br PORT             print name of bridge that contains PORT\n\
+A bond is considered to be a single port.\n\
+\n\
+Interface commands (a bond consists of multiple interfaces):\n\
+  list-ifaces BRIDGE          print the names of all interfaces on BRIDGE\n\
+  iface-to-br IFACE           print name of bridge that contains IFACE\n\
+\n\
+Controller commands:\n\
+  get-controller [BRIDGE]     print the controller for BRIDGE\n\
+  del-controller [BRIDGE]     delete the controller for BRIDGE\n\
+  set-controller [BRIDGE] TARGET  set the controller for BRIDGE to TARGET\n\
+  get-fail-mode [BRIDGE]      print the fail-mode for BRIDGE\n\
+  del-fail-mode [BRIDGE]      delete the fail-mode for BRIDGE\n\
+  set-fail-mode [BRIDGE] MODE set the fail-mode for BRIDGE to MODE\n\
+\n\
+SSL commands:\n\
+  get-ssl                     print the SSL configuration\n\
+  del-ssl                     delete the SSL configuration\n\
+  set-ssl PRIV-KEY CERT CA-CERT  set the SSL configuration\n\
+\n\
+Database commands:\n\
+  list TBL [REC]              list RECord (or all records) in TBL\n\
+  get TBL REC COL[:KEY]       print values of COLumns in RECORD in TBL\n\
+  set TBL REC COL[:KEY]=VALUE set COLumn values in RECord in TBL\n\
+  add TBL REC COL [KEY=]VALUE add (KEY=)VALUE to COLumn in RECord in TBL\n\
+  remove TBL REC COL [KEY=]VALUE  remove (KEY=)VALUE from COLumn\n\
+  clear TBL REC COL           clear values from COLumn in RECord in TBL\n\
+  create TBL COL[:KEY]=VALUE  create and initialize new record\n\
+  destroy TBL REC             delete REC from TBL\n\
+Potentially unsafe database commands require --force option.\n\
+\n\
+Options:\n\
+  --db=DATABASE               connect to DATABASE\n\
+                              (default: %s)\n\
+  --oneline                   print exactly one line of output per command\n",
+           program_name, program_name, default_db());
+    vlog_usage();
+    printf("\n\
+Other options:\n\
+  -h, --help                  display this help message\n\
+  -V, --version               display version information\n");
+    exit(EXIT_SUCCESS);
+}
+
+static char *
+default_db(void)
+{
+    static char *def;
+    if (!def) {
+        def = xasprintf("unix:%s/ovsdb-server", ovs_rundir);
+    }
+    return def;
+}
+\f
+struct vsctl_context {
+    /* Read-only. */
+    int argc;
+    char **argv;
+    struct shash options;
+
+    /* Modifiable state. */
+    struct ds output;
+    struct ovsdb_idl *idl;
+    struct ovsdb_idl_txn *txn;
+    const struct ovsrec_open_vswitch *ovs;
+};
+
+struct vsctl_bridge {
+    struct ovsrec_bridge *br_cfg;
+    char *name;
+    struct ovsrec_controller *ctrl;
+    struct vsctl_bridge *parent;
+    int vlan;
+};
+
+struct vsctl_port {
+    struct ovsrec_port *port_cfg;
+    struct vsctl_bridge *bridge;
+};
+
+struct vsctl_iface {
+    struct ovsrec_interface *iface_cfg;
+    struct vsctl_port *port;
+};
+
+struct vsctl_info {
+    struct shash bridges;
+    struct shash ports;
+    struct shash ifaces;
+    struct ovsrec_controller *ctrl;
+};
+
+static struct vsctl_bridge *
+add_bridge(struct vsctl_info *b,
+           struct ovsrec_bridge *br_cfg, const char *name,
+           struct vsctl_bridge *parent, int vlan)
+{
+    struct vsctl_bridge *br = xmalloc(sizeof *br);
+    br->br_cfg = br_cfg;
+    br->name = xstrdup(name);
+    br->parent = parent;
+    br->vlan = vlan;
+    br->ctrl = parent ? parent->br_cfg->controller : br_cfg->controller;
+    shash_add(&b->bridges, br->name, br);
+    return br;
+}
+
+static bool
+port_is_fake_bridge(const struct ovsrec_port *port_cfg)
+{
+    return (port_cfg->fake_bridge
+            && port_cfg->tag
+            && *port_cfg->tag >= 1 && *port_cfg->tag <= 4095);
+}
+
+static struct vsctl_bridge *
+find_vlan_bridge(struct vsctl_info *info,
+                 struct vsctl_bridge *parent, int vlan)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &info->bridges) {
+        struct vsctl_bridge *br = node->data;
+        if (br->parent == parent && br->vlan == vlan) {
+            return br;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+free_info(struct vsctl_info *info)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &info->bridges) {
+        struct vsctl_bridge *bridge = node->data;
+        free(bridge->name);
+        free(bridge);
+    }
+    shash_destroy(&info->bridges);
+
+    SHASH_FOR_EACH (node, &info->ports) {
+        struct vsctl_port *port = node->data;
+        free(port);
+    }
+    shash_destroy(&info->ports);
+
+    SHASH_FOR_EACH (node, &info->ifaces) {
+        struct vsctl_iface *iface = node->data;
+        free(iface);
+    }
+    shash_destroy(&info->ifaces);
+}
+
+static void
+get_info(const struct ovsrec_open_vswitch *ovs, struct vsctl_info *info)
+{
+    struct shash bridges, ports;
+    size_t i;
+
+    shash_init(&info->bridges);
+    shash_init(&info->ports);
+    shash_init(&info->ifaces);
+
+    info->ctrl = ovs->controller;
+
+    shash_init(&bridges);
+    shash_init(&ports);
+    for (i = 0; i < ovs->n_bridges; i++) {
+        struct ovsrec_bridge *br_cfg = ovs->bridges[i];
+        struct vsctl_bridge *br;
+        size_t j;
+
+        if (!shash_add_once(&bridges, br_cfg->name, NULL)) {
+            VLOG_WARN("%s: database contains duplicate bridge name",
+                      br_cfg->name);
+            continue;
+        }
+        br = add_bridge(info, br_cfg, br_cfg->name, NULL, 0);
+        if (!br) {
+            continue;
+        }
+
+        for (j = 0; j < br_cfg->n_ports; j++) {
+            struct ovsrec_port *port_cfg = br_cfg->ports[j];
+
+            if (!shash_add_once(&ports, port_cfg->name, NULL)) {
+                VLOG_WARN("%s: database contains duplicate port name",
+                          port_cfg->name);
+                continue;
+            }
+
+            if (port_is_fake_bridge(port_cfg)
+                && shash_add_once(&bridges, port_cfg->name, NULL)) {
+                add_bridge(info, NULL, port_cfg->name, br, *port_cfg->tag);
+            }
+        }
+    }
+    shash_destroy(&bridges);
+    shash_destroy(&ports);
+
+    shash_init(&bridges);
+    shash_init(&ports);
+    for (i = 0; i < ovs->n_bridges; i++) {
+        struct ovsrec_bridge *br_cfg = ovs->bridges[i];
+        struct vsctl_bridge *br;
+        size_t j;
+
+        if (!shash_add_once(&bridges, br_cfg->name, NULL)) {
+            continue;
+        }
+        br = shash_find_data(&info->bridges, br_cfg->name);
+        for (j = 0; j < br_cfg->n_ports; j++) {
+            struct ovsrec_port *port_cfg = br_cfg->ports[j];
+            struct vsctl_port *port;
+            size_t k;
+
+            if (!shash_add_once(&ports, port_cfg->name, NULL)) {
+                continue;
+            }
+
+            if (port_is_fake_bridge(port_cfg)
+                && !shash_add_once(&bridges, port_cfg->name, NULL)) {
+                continue;
+            }
+
+            port = xmalloc(sizeof *port);
+            port->port_cfg = port_cfg;
+            if (port_cfg->tag
+                && *port_cfg->tag >= 1 && *port_cfg->tag <= 4095) {
+                port->bridge = find_vlan_bridge(info, br, *port_cfg->tag);
+                if (!port->bridge) {
+                    port->bridge = br;
+                }
+            } else {
+                port->bridge = br;
+            }
+            shash_add(&info->ports, port_cfg->name, port);
+
+            for (k = 0; k < port_cfg->n_interfaces; k++) {
+                struct ovsrec_interface *iface_cfg = port_cfg->interfaces[k];
+                struct vsctl_iface *iface;
+
+                if (shash_find(&info->ifaces, iface_cfg->name)) {
+                    VLOG_WARN("%s: database contains duplicate interface name",
+                              iface_cfg->name);
+                    continue;
+                }
+
+                iface = xmalloc(sizeof *iface);
+                iface->iface_cfg = iface_cfg;
+                iface->port = port;
+                shash_add(&info->ifaces, iface_cfg->name, iface);
+            }
+        }
+    }
+    shash_destroy(&bridges);
+    shash_destroy(&ports);
+}
+
+static void
+check_conflicts(struct vsctl_info *info, const char *name,
+                char *msg)
+{
+    struct vsctl_iface *iface;
+    struct vsctl_port *port;
+
+    if (shash_find(&info->bridges, name)) {
+        vsctl_fatal("%s because a bridge named %s already exists",
+                    msg, name);
+    }
+
+    port = shash_find_data(&info->ports, name);
+    if (port) {
+        vsctl_fatal("%s because a port named %s already exists on "
+                    "bridge %s", msg, name, port->bridge->name);
+    }
+
+    iface = shash_find_data(&info->ifaces, name);
+    if (iface) {
+        vsctl_fatal("%s because an interface named %s already exists "
+                    "on bridge %s", msg, name, iface->port->bridge->name);
+    }
+
+    free(msg);
+}
+
+static struct vsctl_bridge *
+find_bridge(struct vsctl_info *info, const char *name, bool must_exist)
+{
+    struct vsctl_bridge *br = shash_find_data(&info->bridges, name);
+    if (must_exist && !br) {
+        vsctl_fatal("no bridge named %s", name);
+    }
+    return br;
+}
+
+static struct vsctl_bridge *
+find_real_bridge(struct vsctl_info *info, const char *name, bool must_exist)
+{
+    struct vsctl_bridge *br = find_bridge(info, name, must_exist);
+    if (br && br->parent) {
+        vsctl_fatal("%s is a fake bridge", name);
+    }
+    return br;
+}
+
+static struct vsctl_port *
+find_port(struct vsctl_info *info, const char *name, bool must_exist)
+{
+    struct vsctl_port *port = shash_find_data(&info->ports, name);
+    if (port && !strcmp(name, port->bridge->name)) {
+        port = NULL;
+    }
+    if (must_exist && !port) {
+        vsctl_fatal("no port named %s", name);
+    }
+    return port;
+}
+
+static struct vsctl_iface *
+find_iface(struct vsctl_info *info, const char *name, bool must_exist)
+{
+    struct vsctl_iface *iface = shash_find_data(&info->ifaces, name);
+    if (iface && !strcmp(name, iface->port->bridge->name)) {
+        iface = NULL;
+    }
+    if (must_exist && !iface) {
+        vsctl_fatal("no interface named %s", name);
+    }
+    return iface;
+}
+
+static void
+bridge_insert_port(struct ovsrec_bridge *br, struct ovsrec_port *port)
+{
+    struct ovsrec_port **ports;
+    size_t i;
+
+    ports = xmalloc(sizeof *br->ports * (br->n_ports + 1));
+    for (i = 0; i < br->n_ports; i++) {
+        ports[i] = br->ports[i];
+    }
+    ports[br->n_ports] = port;
+    ovsrec_bridge_set_ports(br, ports, br->n_ports + 1);
+    free(ports);
+}
+
+static void
+bridge_delete_port(struct ovsrec_bridge *br, struct ovsrec_port *port)
+{
+    struct ovsrec_port **ports;
+    size_t i, n;
+
+    ports = xmalloc(sizeof *br->ports * br->n_ports);
+    for (i = n = 0; i < br->n_ports; i++) {
+        if (br->ports[i] != port) {
+            ports[n++] = br->ports[i];
+        }
+    }
+    ovsrec_bridge_set_ports(br, ports, n);
+    free(ports);
+}
+
+static void
+ovs_insert_bridge(const struct ovsrec_open_vswitch *ovs,
+                  struct ovsrec_bridge *bridge)
+{
+    struct ovsrec_bridge **bridges;
+    size_t i;
+
+    bridges = xmalloc(sizeof *ovs->bridges * (ovs->n_bridges + 1));
+    for (i = 0; i < ovs->n_bridges; i++) {
+        bridges[i] = ovs->bridges[i];
+    }
+    bridges[ovs->n_bridges] = bridge;
+    ovsrec_open_vswitch_set_bridges(ovs, bridges, ovs->n_bridges + 1);
+    free(bridges);
+}
+
+static void
+ovs_delete_bridge(const struct ovsrec_open_vswitch *ovs,
+                  struct ovsrec_bridge *bridge)
+{
+    struct ovsrec_bridge **bridges;
+    size_t i, n;
+
+    bridges = xmalloc(sizeof *ovs->bridges * ovs->n_bridges);
+    for (i = n = 0; i < ovs->n_bridges; i++) {
+        if (ovs->bridges[i] != bridge) {
+            bridges[n++] = ovs->bridges[i];
+        }
+    }
+    ovsrec_open_vswitch_set_bridges(ovs, bridges, n);
+    free(bridges);
+}
+
+static void
+cmd_init(struct vsctl_context *ctx UNUSED)
+{
+}
+
+static void
+cmd_add_br(struct vsctl_context *ctx)
+{
+    const char *br_name = ctx->argv[1];
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    check_conflicts(&info, br_name,
+                    xasprintf("cannot create a bridge named %s", br_name));
+
+    if (ctx->argc == 2) {
+        struct ovsrec_bridge *br;
+        struct ovsrec_port *port;
+        struct ovsrec_interface *iface;
+
+        iface = ovsrec_interface_insert(ctx->txn);
+        ovsrec_interface_set_name(iface, br_name);
+
+        port = ovsrec_port_insert(ctx->txn);
+        ovsrec_port_set_name(port, br_name);
+        ovsrec_port_set_interfaces(port, &iface, 1);
+
+        br = ovsrec_bridge_insert(ctx->txn);
+        ovsrec_bridge_set_name(br, br_name);
+        ovsrec_bridge_set_ports(br, &port, 1);
+
+        ovs_insert_bridge(ctx->ovs, br);
+    } else if (ctx->argc == 3) {
+        vsctl_fatal("'%s' command takes exactly 1 or 3 arguments",
+                    ctx->argv[0]);
+    } else if (ctx->argc == 4) {
+        const char *parent_name = ctx->argv[2];
+        int vlan = atoi(ctx->argv[3]);
+        struct ovsrec_bridge *br;
+        struct vsctl_bridge *parent;
+        struct ovsrec_port *port;
+        struct ovsrec_interface *iface;
+        int64_t tag = vlan;
+
+        if (vlan < 1 || vlan > 4095) {
+            vsctl_fatal("%s: vlan must be between 1 and 4095", ctx->argv[0]);
+        }
+
+        parent = find_bridge(&info, parent_name, false);
+        if (parent && parent->vlan) {
+            vsctl_fatal("cannot create bridge with fake bridge as parent");
+        }
+        if (!parent) {
+            vsctl_fatal("parent bridge %s does not exist", parent_name);
+        }
+        br = parent->br_cfg;
+
+        iface = ovsrec_interface_insert(ctx->txn);
+        ovsrec_interface_set_name(iface, br_name);
+        ovsrec_interface_set_type(iface, "internal");
+
+        port = ovsrec_port_insert(ctx->txn);
+        ovsrec_port_set_name(port, br_name);
+        ovsrec_port_set_interfaces(port, &iface, 1);
+        ovsrec_port_set_fake_bridge(port, true);
+        ovsrec_port_set_tag(port, &tag, 1);
+
+        bridge_insert_port(br, port);
+    } else {
+        NOT_REACHED();
+    }
+
+    free_info(&info);
+}
+
+static void
+del_port(struct vsctl_info *info, struct vsctl_port *port)
+{
+    struct shash_node *node;
+
+    SHASH_FOR_EACH (node, &info->ifaces) {
+        struct vsctl_iface *iface = node->data;
+        if (iface->port == port) {
+            ovsrec_interface_delete(iface->iface_cfg);
+        }
+    }
+    ovsrec_port_delete(port->port_cfg);
+
+    bridge_delete_port((port->bridge->parent
+                        ? port->bridge->parent->br_cfg
+                        : port->bridge->br_cfg), port->port_cfg);
+}
+
+static void
+cmd_del_br(struct vsctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    struct vsctl_bridge *bridge;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    bridge = find_bridge(&info, ctx->argv[1], must_exist);
+    if (bridge) {
+        struct shash_node *node;
+
+        SHASH_FOR_EACH (node, &info.ports) {
+            struct vsctl_port *port = node->data;
+            if (port->bridge == bridge
+                || !strcmp(port->port_cfg->name, bridge->name)) {
+                del_port(&info, port);
+            }
+        }
+        if (bridge->br_cfg) {
+            ovsrec_bridge_delete(bridge->br_cfg);
+            ovs_delete_bridge(ctx->ovs, bridge->br_cfg);
+        }
+    }
+    free_info(&info);
+}
+
+static void
+output_sorted(struct svec *svec, struct ds *output)
+{
+    const char *name;
+    size_t i;
+
+    svec_sort(svec);
+    SVEC_FOR_EACH (i, name, svec) {
+        ds_put_format(output, "%s\n", name);
+    }
+}
+
+static void
+cmd_list_br(struct vsctl_context *ctx)
+{
+    struct shash_node *node;
+    struct vsctl_info info;
+    struct svec bridges;
+
+    get_info(ctx->ovs, &info);
+
+    svec_init(&bridges);
+    SHASH_FOR_EACH (node, &info.bridges) {
+        struct vsctl_bridge *br = node->data;
+        svec_add(&bridges, br->name);
+    }
+    output_sorted(&bridges, &ctx->output);
+    svec_destroy(&bridges);
+
+    free_info(&info);
+}
+
+static void
+cmd_br_exists(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    if (!find_bridge(&info, ctx->argv[1], false)) {
+        vsctl_exit(2);
+    }
+    free_info(&info);
+}
+
+/* Returns true if 'b_prefix' (of length 'b_prefix_len') concatenated with 'b'
+ * equals 'a', false otherwise. */
+static bool
+key_matches(const char *a,
+            const char *b_prefix, size_t b_prefix_len, const char *b)
+{
+    return !strncmp(a, b_prefix, b_prefix_len) && !strcmp(a + b_prefix_len, b);
+}
+
+static void
+set_external_id(char **old_keys, char **old_values, size_t old_n,
+                char *key, char *value,
+                char ***new_keysp, char ***new_valuesp, size_t *new_np)
+{
+    char **new_keys;
+    char **new_values;
+    size_t new_n;
+    size_t i;
+
+    new_keys = xmalloc(sizeof *new_keys * (old_n + 1));
+    new_values = xmalloc(sizeof *new_values * (old_n + 1));
+    new_n = 0;
+    for (i = 0; i < old_n; i++) {
+        if (strcmp(key, old_keys[i])) {
+            new_keys[new_n] = old_keys[i];
+            new_values[new_n] = old_values[i];
+            new_n++;
+        }
+    }
+    if (value) {
+        new_keys[new_n] = key;
+        new_values[new_n] = value;
+        new_n++;
+    }
+    *new_keysp = new_keys;
+    *new_valuesp = new_values;
+    *new_np = new_n;
+}
+
+static void
+cmd_br_set_external_id(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+    struct vsctl_bridge *bridge;
+    char **keys, **values;
+    size_t n;
+
+    get_info(ctx->ovs, &info);
+    bridge = find_bridge(&info, ctx->argv[1], true);
+    if (bridge->br_cfg) {
+        set_external_id(bridge->br_cfg->key_external_ids,
+                        bridge->br_cfg->value_external_ids,
+                        bridge->br_cfg->n_external_ids,
+                        ctx->argv[2], ctx->argc >= 4 ? ctx->argv[3] : NULL,
+                        &keys, &values, &n);
+        ovsrec_bridge_set_external_ids(bridge->br_cfg, keys, values, n);
+    } else {
+        char *key = xasprintf("fake-bridge-%s", ctx->argv[2]);
+        struct vsctl_port *port = shash_find_data(&info.ports, ctx->argv[1]);
+        set_external_id(port->port_cfg->key_external_ids,
+                        port->port_cfg->value_external_ids,
+                        port->port_cfg->n_external_ids,
+                        key, ctx->argc >= 4 ? ctx->argv[3] : NULL,
+                        &keys, &values, &n);
+        ovsrec_port_set_external_ids(port->port_cfg, keys, values, n);
+        free(key);
+    }
+    free(keys);
+    free(values);
+
+    free_info(&info);
+}
+
+static void
+get_external_id(char **keys, char **values, size_t n,
+                const char *prefix, const char *key,
+                struct ds *output)
+{
+    size_t prefix_len = strlen(prefix);
+    struct svec svec;
+    size_t i;
+
+    svec_init(&svec);
+    for (i = 0; i < n; i++) {
+        if (!key && !strncmp(keys[i], prefix, prefix_len)) {
+            svec_add_nocopy(&svec, xasprintf("%s=%s",
+                                             keys[i] + prefix_len, values[i]));
+        } else if (key_matches(keys[i], prefix, prefix_len, key)) {
+            svec_add(&svec, values[i]);
+            break;
+        }
+    }
+    output_sorted(&svec, output);
+    svec_destroy(&svec);
+}
+
+static void
+cmd_br_get_external_id(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+    struct vsctl_bridge *bridge;
+
+    get_info(ctx->ovs, &info);
+    bridge = find_bridge(&info, ctx->argv[1], true);
+    if (bridge->br_cfg) {
+        get_external_id(bridge->br_cfg->key_external_ids,
+                        bridge->br_cfg->value_external_ids,
+                        bridge->br_cfg->n_external_ids,
+                        "", ctx->argc >= 3 ? ctx->argv[2] : NULL,
+                        &ctx->output);
+    } else {
+        struct vsctl_port *port = shash_find_data(&info.ports, ctx->argv[1]);
+        get_external_id(port->port_cfg->key_external_ids,
+                        port->port_cfg->value_external_ids,
+                        port->port_cfg->n_external_ids,
+                        "fake-bridge-", ctx->argc >= 3 ? ctx->argv[2] : NULL, &ctx->output);
+    }
+    free_info(&info);
+}
+
+
+static void
+cmd_list_ports(struct vsctl_context *ctx)
+{
+    struct vsctl_bridge *br;
+    struct shash_node *node;
+    struct vsctl_info info;
+    struct svec ports;
+
+    get_info(ctx->ovs, &info);
+    br = find_bridge(&info, ctx->argv[1], true);
+
+    svec_init(&ports);
+    SHASH_FOR_EACH (node, &info.ports) {
+        struct vsctl_port *port = node->data;
+
+        if (strcmp(port->port_cfg->name, br->name) && br == port->bridge) {
+            svec_add(&ports, port->port_cfg->name);
+        }
+    }
+    output_sorted(&ports, &ctx->output);
+    svec_destroy(&ports);
+
+    free_info(&info);
+}
+
+static void
+add_port(struct vsctl_context *ctx,
+         const char *br_name, const char *port_name, bool fake_iface,
+         char *iface_names[], int n_ifaces)
+{
+    struct vsctl_info info;
+    struct vsctl_bridge *bridge;
+    struct ovsrec_interface **ifaces;
+    struct ovsrec_port *port;
+    size_t i;
+
+    get_info(ctx->ovs, &info);
+    check_conflicts(&info, port_name,
+                    xasprintf("cannot create a port named %s", port_name));
+    /* XXX need to check for conflicts on interfaces too */
+    bridge = find_bridge(&info, br_name, true);
+
+    ifaces = xmalloc(n_ifaces * sizeof *ifaces);
+    for (i = 0; i < n_ifaces; i++) {
+        ifaces[i] = ovsrec_interface_insert(ctx->txn);
+        ovsrec_interface_set_name(ifaces[i], iface_names[i]);
+    }
+
+    port = ovsrec_port_insert(ctx->txn);
+    ovsrec_port_set_name(port, port_name);
+    ovsrec_port_set_interfaces(port, ifaces, n_ifaces);
+    ovsrec_port_set_bond_fake_iface(port, fake_iface);
+    free(ifaces);
+
+    if (bridge->vlan) {
+        int64_t tag = bridge->vlan;
+        ovsrec_port_set_tag(port, &tag, 1);
+    }
+
+    bridge_insert_port((bridge->parent ? bridge->parent->br_cfg
+                        : bridge->br_cfg), port);
+
+    free_info(&info);
+}
+
+static void
+cmd_add_port(struct vsctl_context *ctx)
+{
+    add_port(ctx, ctx->argv[1], ctx->argv[2], false, &ctx->argv[2], 1);
+}
+
+static void
+cmd_add_bond(struct vsctl_context *ctx)
+{
+    bool fake_iface = shash_find(&ctx->options, "--fake-iface");
+
+    add_port(ctx, ctx->argv[1], ctx->argv[2], fake_iface,
+             &ctx->argv[3], ctx->argc - 3);
+}
+
+static void
+cmd_del_port(struct vsctl_context *ctx)
+{
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    if (ctx->argc == 2) {
+        struct vsctl_port *port = find_port(&info, ctx->argv[1], must_exist);
+        if (port) {
+            del_port(&info, port);
+        }
+    } else if (ctx->argc == 3) {
+        struct vsctl_bridge *bridge = find_bridge(&info, ctx->argv[1], true);
+        struct vsctl_port *port = find_port(&info, ctx->argv[2], must_exist);
+
+        if (port) {
+            if (port->bridge == bridge) {
+                del_port(&info, port);
+            } else if (port->bridge->parent == bridge) {
+                vsctl_fatal("bridge %s does not have a port %s (although its "
+                            "parent bridge %s does)",
+                            ctx->argv[1], ctx->argv[2], bridge->parent->name);
+            } else {
+                vsctl_fatal("bridge %s does not have a port %s",
+                            ctx->argv[1], ctx->argv[2]);
+            }
+        }
+    }
+    free_info(&info);
+}
+
+static void
+cmd_port_to_br(struct vsctl_context *ctx)
+{
+    struct vsctl_port *port;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    port = find_port(&info, ctx->argv[1], true);
+    ds_put_format(&ctx->output, "%s\n", port->bridge->name);
+    free_info(&info);
+}
+
+static void
+cmd_br_to_vlan(struct vsctl_context *ctx)
+{
+    struct vsctl_bridge *bridge;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    bridge = find_bridge(&info, ctx->argv[1], true);
+    ds_put_format(&ctx->output, "%d\n", bridge->vlan);
+    free_info(&info);
+}
+
+static void
+cmd_br_to_parent(struct vsctl_context *ctx)
+{
+    struct vsctl_bridge *bridge;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    bridge = find_bridge(&info, ctx->argv[1], true);
+    if (bridge->parent) {
+        bridge = bridge->parent;
+    }
+    ds_put_format(&ctx->output, "%s\n", bridge->name);
+    free_info(&info);
+}
+
+static void
+cmd_list_ifaces(struct vsctl_context *ctx)
+{
+    struct vsctl_bridge *br;
+    struct shash_node *node;
+    struct vsctl_info info;
+    struct svec ifaces;
+
+    get_info(ctx->ovs, &info);
+    br = find_bridge(&info, ctx->argv[1], true);
+
+    svec_init(&ifaces);
+    SHASH_FOR_EACH (node, &info.ifaces) {
+        struct vsctl_iface *iface = node->data;
+
+        if (strcmp(iface->iface_cfg->name, br->name)
+            && br == iface->port->bridge) {
+            svec_add(&ifaces, iface->iface_cfg->name);
+        }
+    }
+    output_sorted(&ifaces, &ctx->output);
+    svec_destroy(&ifaces);
+
+    free_info(&info);
+}
+
+static void
+cmd_iface_to_br(struct vsctl_context *ctx)
+{
+    struct vsctl_iface *iface;
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+    iface = find_iface(&info, ctx->argv[1], true);
+    ds_put_format(&ctx->output, "%s\n", iface->port->bridge->name);
+    free_info(&info);
+}
+
+static void
+cmd_get_controller(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+
+    if (ctx->argc == 1) {
+        /* Return the controller from the "Open_vSwitch" table */
+        if (info.ctrl) {
+            ds_put_format(&ctx->output, "%s\n", info.ctrl->target);
+        }
+    } else {
+        /* Return the controller for a particular bridge. */
+        struct vsctl_bridge *br = find_bridge(&info, ctx->argv[1], true);
+
+        /* If no controller is explicitly defined for the requested
+         * bridge, fallback to the "Open_vSwitch" table's controller. */
+        if (br->ctrl) {
+            ds_put_format(&ctx->output, "%s\n", br->ctrl->target);
+        } else if (info.ctrl) {
+            ds_put_format(&ctx->output, "%s\n", info.ctrl->target);
+        }
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_del_controller(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+
+    if (ctx->argc == 1) {
+        if (info.ctrl) {
+            ovsrec_controller_delete(info.ctrl);
+            ovsrec_open_vswitch_set_controller(ctx->ovs, NULL);
+        }
+    } else {
+        struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+        if (br->ctrl) {
+            ovsrec_controller_delete(br->ctrl);
+            ovsrec_bridge_set_controller(br->br_cfg, NULL);
+        }
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_set_controller(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+    struct ovsrec_controller *ctrl;
+
+    get_info(ctx->ovs, &info);
+
+    if (ctx->argc == 2) {
+        /* Set the controller in the "Open_vSwitch" table. */
+        if (info.ctrl) {
+            ovsrec_controller_delete(info.ctrl);
+        }
+        ctrl = ovsrec_controller_insert(ctx->txn);
+        ovsrec_controller_set_target(ctrl, ctx->argv[1]);
+        ovsrec_open_vswitch_set_controller(ctx->ovs, ctrl);
+    } else {
+        /* Set the controller for a particular bridge. */
+        struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+        if (br->ctrl) {
+            ovsrec_controller_delete(br->ctrl);
+        }
+        ctrl = ovsrec_controller_insert(ctx->txn);
+        ovsrec_controller_set_target(ctrl, ctx->argv[2]);
+        ovsrec_bridge_set_controller(br->br_cfg, ctrl);
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_get_fail_mode(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+    const char *fail_mode = NULL;
+
+    get_info(ctx->ovs, &info);
+
+    if (ctx->argc == 1) {
+        /* Return the fail-mode from the "Open_vSwitch" table */
+        if (info.ctrl && info.ctrl->fail_mode) {
+            fail_mode = info.ctrl->fail_mode;
+        }
+    } else {
+        /* Return the fail-mode for a particular bridge. */
+        struct vsctl_bridge *br = find_bridge(&info, ctx->argv[1], true);
+
+        /* If no controller or fail-mode is explicitly defined for the 
+         * requested bridge, fallback to the "Open_vSwitch" table's 
+         * setting. */
+        if (br->ctrl && br->ctrl->fail_mode) {
+            fail_mode = br->ctrl->fail_mode;
+        } else if (info.ctrl && info.ctrl->fail_mode) {
+            fail_mode = info.ctrl->fail_mode;
+        }
+    }
+
+    if (fail_mode && strlen(fail_mode)) {
+        ds_put_format(&ctx->output, "%s\n", fail_mode);
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_del_fail_mode(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+
+    get_info(ctx->ovs, &info);
+
+    if (ctx->argc == 1) {
+        if (info.ctrl && info.ctrl->fail_mode) {
+            ovsrec_controller_set_fail_mode(info.ctrl, NULL);
+        }
+    } else {
+        struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+        if (br->ctrl && br->ctrl->fail_mode) {
+            ovsrec_controller_set_fail_mode(br->ctrl, NULL);
+        }
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_set_fail_mode(struct vsctl_context *ctx)
+{
+    struct vsctl_info info;
+    const char *fail_mode;
+
+    get_info(ctx->ovs, &info);
+
+    fail_mode = (ctx->argc == 2) ? ctx->argv[1] : ctx->argv[2];
+
+    if (strcmp(fail_mode, "standalone") && strcmp(fail_mode, "secure")) {
+        vsctl_fatal("fail-mode must be \"standalone\" or \"secure\"");
+    }
+
+    if (ctx->argc == 2) {
+        /* Set the fail-mode in the "Open_vSwitch" table. */
+        if (!info.ctrl) {
+            vsctl_fatal("no controller declared");
+        }
+        ovsrec_controller_set_fail_mode(info.ctrl, fail_mode);
+    } else {
+        struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true);
+
+        if (!br->ctrl) {
+            vsctl_fatal("no controller declared for %s", br->name);
+        }
+        ovsrec_controller_set_fail_mode(br->ctrl, fail_mode);
+    }
+
+    free_info(&info);
+}
+
+static void
+cmd_get_ssl(struct vsctl_context *ctx)
+{
+    struct ovsrec_ssl *ssl = ctx->ovs->ssl;
+
+    if (ssl) {
+        ds_put_format(&ctx->output, "Private key: %s\n", ssl->private_key);
+        ds_put_format(&ctx->output, "Certificate: %s\n", ssl->certificate);
+        ds_put_format(&ctx->output, "CA Certificate: %s\n", ssl->ca_cert);
+        ds_put_format(&ctx->output, "Bootstrap: %s\n",
+                ssl->bootstrap_ca_cert ? "true" : "false");
+    }
+}
+
+static void
+cmd_del_ssl(struct vsctl_context *ctx)
+{
+    struct ovsrec_ssl *ssl = ctx->ovs->ssl;
+
+    if (ssl) {
+        ovsrec_ssl_delete(ssl);
+        ovsrec_open_vswitch_set_ssl(ctx->ovs, NULL);
+    }
+}
+
+static void
+cmd_set_ssl(struct vsctl_context *ctx)
+{
+    bool bootstrap = shash_find(&ctx->options, "--bootstrap");
+    struct ovsrec_ssl *ssl = ctx->ovs->ssl;
+
+    if (ssl) {
+        ovsrec_ssl_delete(ssl);
+    }
+    ssl = ovsrec_ssl_insert(ctx->txn);
+
+    ovsrec_ssl_set_private_key(ssl, ctx->argv[1]);
+    ovsrec_ssl_set_certificate(ssl, ctx->argv[2]);
+    ovsrec_ssl_set_ca_cert(ssl, ctx->argv[3]);
+
+    ovsrec_ssl_set_bootstrap_ca_cert(ssl, bootstrap);
+
+    ovsrec_open_vswitch_set_ssl(ctx->ovs, ssl);
+}
+\f
+/* Parameter commands. */
+
+/* POSIX extended regular expression for an 8-bit unsigned decimal integer. */
+#define OCTET_RE "([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"
+
+/* POSIX extended regular expression for an IP address. */
+#define IP_RE "("OCTET_RE"\\."OCTET_RE"\\."OCTET_RE"\\."OCTET_RE")"
+
+/* POSIX extended regular expression for a netmask. */
+#define NETMASK_RE                              \
+        "255.255.255."NETMASK_END_RE"|"         \
+        "255.255."NETMASK_END_RE".0|"           \
+        "255."NETMASK_END_RE".0.0|"             \
+        NETMASK_END_RE".0.0.0"
+#define NETMASK_END_RE "(255|254|252|248|240|224|192|128|0)"
+
+/* POSIX extended regular expression for an Ethernet address. */
+#define XX_RE "[0-9a-fA-F][0-9a-fA-F]"
+#define MAC_RE XX_RE":"XX_RE":"XX_RE":"XX_RE":"XX_RE":"XX_RE
+
+/* POSIX extended regular expression for a TCP or UDP port number. */
+#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])"
+
+enum {
+    VSCF_READONLY = 1 << 0,
+    VSCF_HIDDEN = 1 << 1
+};
+
+struct vsctl_column {
+    struct ovsdb_idl_column *idl;
+    int flags;
+    const char *constraint;
+};
+
+static const struct vsctl_column bridge_columns[] = {
+    {&ovsrec_bridge_col_controller, VSCF_READONLY, NULL},
+    {&ovsrec_bridge_col_datapath_id, VSCF_READONLY, NULL},
+    {&ovsrec_bridge_col_datapath_type, VSCF_READONLY, NULL},
+    {&ovsrec_bridge_col_external_ids, 0, NULL},
+    {&ovsrec_bridge_col_flood_vlans, 0, "[1,4095]"},
+    {&ovsrec_bridge_col_mirrors, VSCF_READONLY, NULL},
+    {&ovsrec_bridge_col_name, VSCF_READONLY, NULL},
+    {&ovsrec_bridge_col_netflow, VSCF_READONLY, NULL},
+    {&ovsrec_bridge_col_other_config, 0, NULL},
+    {&ovsrec_bridge_col_ports, VSCF_READONLY, NULL},
+    {NULL, 0, NULL},
+};
+
+static const struct vsctl_column controller_columns[] = {
+    {&ovsrec_controller_col_connection_mode, 0, "in-band|out-of-band"},
+    {&ovsrec_controller_col_controller_burst_limit, 0, "[25,]"},
+    {&ovsrec_controller_col_controller_rate_limit, 0, "[100,]"},
+    {&ovsrec_controller_col_discover_accept_regex, 0, NULL},
+    {&ovsrec_controller_col_discover_update_resolv_conf, 0, NULL},
+    {&ovsrec_controller_col_fail_mode, 0, "standalone|secure"},
+    {&ovsrec_controller_col_inactivity_probe, 0, "[5000,]"},
+    {&ovsrec_controller_col_local_gateway, 0, IP_RE},
+    {&ovsrec_controller_col_local_ip, 0, IP_RE},
+    {&ovsrec_controller_col_local_netmask, 0, NETMASK_RE},
+    {&ovsrec_controller_col_max_backoff, 0, "[1000,]"},
+    {&ovsrec_controller_col_target, 0, NULL},
+    {NULL, 0, NULL},
+};
+
+static const struct vsctl_column interface_columns[] = {
+    {&ovsrec_interface_col_external_ids, 0, NULL},
+    {&ovsrec_interface_col_ingress_policing_burst, 0, "[10,]"},
+    {&ovsrec_interface_col_ingress_policing_rate, 0, "[100,]"},
+    {&ovsrec_interface_col_mac, 0, MAC_RE},
+    {&ovsrec_interface_col_name, VSCF_READONLY, NULL},
+    {&ovsrec_interface_col_ofport, VSCF_READONLY, NULL},
+    {&ovsrec_interface_col_options, 0, NULL},
+    {&ovsrec_interface_col_type, VSCF_READONLY, NULL},
+    {NULL, 0, NULL},
+};
+
+static const struct vsctl_column mirror_columns[] = {
+    {&ovsrec_mirror_col_name, VSCF_READONLY, NULL},
+    {&ovsrec_mirror_col_output_port, 0, "Port"},
+    {&ovsrec_mirror_col_output_vlan, 0, "[1,4095]"},
+    {&ovsrec_mirror_col_select_dst_port, 0, "Port"},
+    {&ovsrec_mirror_col_select_src_port, 0, "Port"},
+    {&ovsrec_mirror_col_select_vlan, 0, "[1,4095]"},
+    {NULL, 0, NULL},
+};
+
+static const struct vsctl_column netflow_columns[] = {
+    {&ovsrec_netflow_col_active_timeout, 0, "[-1,]"},
+    {&ovsrec_netflow_col_add_id_to_interface, 0, NULL},
+    {&ovsrec_netflow_col_engine_id, 0, "[0,255]"},
+    {&ovsrec_netflow_col_engine_type, 0, "[0,255]"},
+    {&ovsrec_netflow_col_targets, 0, IP_RE":"PORT_RE},
+    {NULL, 0, NULL},
+};
+
+static const struct vsctl_column open_vswitch_columns[] = {
+    {&ovsrec_open_vswitch_col_bridges, VSCF_READONLY, NULL},
+    {&ovsrec_open_vswitch_col_controller, VSCF_READONLY, NULL},
+    {&ovsrec_open_vswitch_col_cur_cfg, VSCF_HIDDEN, NULL},
+    {&ovsrec_open_vswitch_col_managers, 0, "p?(ssl|tcp|unix):.*"},
+    {&ovsrec_open_vswitch_col_next_cfg, VSCF_HIDDEN, NULL},
+    {&ovsrec_open_vswitch_col_ssl, VSCF_READONLY, NULL},
+    {NULL, 0, NULL},
+};
+
+static const struct vsctl_column port_columns[] = {
+    {&ovsrec_port_col_bond_downdelay, 0, "[0,]"},
+    {&ovsrec_port_col_bond_fake_iface, VSCF_READONLY, NULL},
+    {&ovsrec_port_col_bond_updelay, 0, "[0,]"},
+    {&ovsrec_port_col_external_ids, 0, NULL},
+    {&ovsrec_port_col_fake_bridge, VSCF_READONLY, NULL},
+    {&ovsrec_port_col_interfaces, VSCF_READONLY, NULL},
+    {&ovsrec_port_col_mac, 0, MAC_RE},
+    {&ovsrec_port_col_name, VSCF_READONLY, NULL},
+    {&ovsrec_port_col_other_config, 0, NULL},
+    {&ovsrec_port_col_tag, 0, "[0,4095]"},
+    {&ovsrec_port_col_trunks, 0, "[0,4095]"},
+    {NULL, 0, NULL},
+};
+
+static const struct vsctl_column ssl_columns[] = {
+    {&ovsrec_ssl_col_bootstrap_ca_cert, 0, NULL},
+    {&ovsrec_ssl_col_ca_cert, 0, NULL},
+    {&ovsrec_ssl_col_certificate, 0, NULL},
+    {&ovsrec_ssl_col_private_key, 0, NULL},
+    {NULL, 0, NULL},
+};
+
+struct vsctl_row_id {
+    const struct ovsdb_idl_table_class *table;
+    const struct ovsdb_idl_column *name_column;
+    const struct ovsdb_idl_column *uuid_column;
+};
+
+struct vsctl_table_class {
+    struct ovsdb_idl_table_class *class;
+    const struct vsctl_column *columns;
+    struct vsctl_row_id row_ids[2];
+};
+
+static const struct vsctl_table_class tables[] = {
+    {&ovsrec_table_bridge, bridge_columns,
+     {{&ovsrec_table_bridge, &ovsrec_bridge_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_controller, controller_columns,
+     {{&ovsrec_table_bridge,
+       &ovsrec_bridge_col_name,
+       &ovsrec_bridge_col_controller},
+      {&ovsrec_table_open_vswitch,
+       NULL,
+       &ovsrec_open_vswitch_col_controller}}},
+
+    {&ovsrec_table_interface, interface_columns,
+     {{&ovsrec_table_interface, &ovsrec_interface_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_mirror, mirror_columns,
+     {{&ovsrec_table_mirror, &ovsrec_mirror_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_netflow, netflow_columns,
+     {{&ovsrec_table_bridge,
+       &ovsrec_bridge_col_name,
+       &ovsrec_bridge_col_netflow},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_open_vswitch, open_vswitch_columns,
+     {{&ovsrec_table_open_vswitch, NULL, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_port, port_columns,
+     {{&ovsrec_table_port, &ovsrec_port_col_name, NULL},
+      {NULL, NULL, NULL}}},
+
+    {&ovsrec_table_ssl, ssl_columns,
+     {{&ovsrec_table_open_vswitch, NULL, &ovsrec_open_vswitch_col_ssl}}},
+
+    {NULL, NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}}
+};
+
+static void
+die_if_error(char *error)
+{
+    if (error) {
+        vsctl_fatal("%s", error);
+    }
+}
+
+static int
+to_lower_and_underscores(unsigned c)
+{
+    return c == '-' ? '_' : tolower(c);
+}
+
+static unsigned int
+score_partial_match(const char *name, const char *s)
+{
+    int score;
+
+    if (!strcmp(name, s)) {
+        return UINT_MAX;
+    }
+    for (score = 0; ; score++, name++, s++) {
+        if (to_lower_and_underscores(*name) != to_lower_and_underscores(*s)) {
+            break;
+        } else if (*name == '\0') {
+            return UINT_MAX - 1;
+        }
+    }
+    return *s == '\0' ? score : 0;
+}
+
+static const struct vsctl_table_class *
+get_table(const char *table_name)
+{
+    const struct vsctl_table_class *table;
+    const struct vsctl_table_class *best_match = NULL;
+    unsigned int best_score = 0;
+
+    for (table = tables; table->class; table++) {
+        unsigned int score = score_partial_match(table->class->name,
+                                                 table_name);
+        if (score > best_score) {
+            best_match = table;
+            best_score = score;
+        } else if (score == best_score) {
+            best_match = NULL;
+        }
+    }
+    if (best_match) {
+        return best_match;
+    } else if (best_score) {
+        vsctl_fatal("multiple table names match \"%s\"", table_name);
+    } else {
+        vsctl_fatal("unknown table \"%s\"", table_name);
+    }
+}
+
+static const struct ovsdb_idl_row *
+get_row_by_id(struct vsctl_context *ctx, const struct vsctl_table_class *table,
+              const struct vsctl_row_id *id, const char *record_id)
+{
+    const struct ovsdb_idl_row *referrer, *final;
+
+    if (!id->table) {
+        return NULL;
+    }
+
+    if (!id->name_column) {
+        if (strcmp(record_id, ".")) {
+            return NULL;
+        }
+        referrer = ovsdb_idl_first_row(ctx->idl, id->table);
+        if (!referrer || ovsdb_idl_next_row(referrer)) {
+            return NULL;
+        }
+    } else {
+        const struct ovsdb_idl_row *row;
+        unsigned int best_score = 0;
+
+        /* It might make sense to relax this assertion. */
+        assert(id->name_column->type.key_type == OVSDB_TYPE_STRING);
+
+        referrer = NULL;
+        for (row = ovsdb_idl_first_row(ctx->idl, id->table);
+             row != NULL && best_score != UINT_MAX;
+             row = ovsdb_idl_next_row(row))
+        {
+            struct ovsdb_datum name;
+
+            ovsdb_idl_txn_read(row, id->name_column, &name);
+            if (name.n == 1) {
+                unsigned int score = score_partial_match(name.keys[0].string,
+                                                         record_id);
+                if (score > best_score) {
+                    referrer = row;
+                    best_score = score;
+                } else if (score == best_score) {
+                    referrer = NULL;
+                }
+            }
+            ovsdb_datum_destroy(&name, &id->name_column->type);
+        }
+        if (best_score && !referrer) {
+            vsctl_fatal("multiple rows in %s match \"%s\"",
+                        table->class->name, record_id);
+        }
+    }
+    if (!referrer) {
+        return NULL;
+    }
+
+    final = NULL;
+    if (id->uuid_column) {
+        struct ovsdb_datum uuid;
+
+        assert(id->uuid_column->type.key_type == OVSDB_TYPE_UUID);
+        assert(id->uuid_column->type.value_type == OVSDB_TYPE_VOID);
+
+        ovsdb_idl_txn_read(referrer, id->uuid_column, &uuid);
+        if (uuid.n == 1) {
+            final = ovsdb_idl_get_row_for_uuid(ctx->idl, table->class,
+                                               &uuid.keys[0].uuid);
+        }
+        ovsdb_datum_destroy(&uuid, &id->uuid_column->type);
+    } else {
+        final = referrer;
+    }
+
+    return final;
+}
+
+static const struct ovsdb_idl_row *
+get_row(struct vsctl_context *ctx,
+        const struct vsctl_table_class *table, const char *record_id)
+{
+    const struct ovsdb_idl_row *row;
+    struct uuid uuid;
+
+    if (uuid_from_string(&uuid, record_id)) {
+        row = ovsdb_idl_get_row_for_uuid(ctx->idl, table->class, &uuid);
+    } else {
+        int i;
+
+        for (i = 0; i < ARRAY_SIZE(table->row_ids); i++) {
+            row = get_row_by_id(ctx, table, &table->row_ids[i], record_id);
+            if (row) {
+                break;
+            }
+        }
+    }
+    return row;
+}
+
+static const struct ovsdb_idl_row *
+must_get_row(struct vsctl_context *ctx,
+             const struct vsctl_table_class *table, const char *record_id)
+{
+    const struct ovsdb_idl_row *row = get_row(ctx, table, record_id);
+    if (!row) {
+        vsctl_fatal("no row \"%s\" in table %s",
+                    record_id, table->class->name);
+    }
+    return row;
+}
+
+static char *
+get_column(const struct vsctl_table_class *table, const char *column_name,
+           const struct vsctl_column **columnp)
+{
+    const struct vsctl_column *column;
+    const struct vsctl_column *best_match = NULL;
+    unsigned int best_score = 0;
+
+    for (column = table->columns; column->idl; column++) {
+        if (!(column->flags & VSCF_HIDDEN)) {
+            unsigned int score = score_partial_match(column->idl->name,
+                                                     column_name);
+            if (score > best_score) {
+                best_match = column;
+                best_score = score;
+            } else if (score == best_score) {
+                best_match = NULL;
+            }
+        }
+    }
+
+    *columnp = best_match;
+    if (best_match) {
+        return NULL;
+    } else if (best_score) {
+        return xasprintf("%s contains more than one column whose name "
+                         "matches \"%s\"", table->class->name, column_name);
+    } else {
+        return xasprintf("%s does not contain a column whose name matches "
+                         "\"%s\"", table->class->name, column_name);
+    }
+}
+
+static char * WARN_UNUSED_RESULT
+parse_column_key_value(const char *arg, const struct vsctl_table_class *table,
+                       const struct vsctl_column **columnp,
+                       char **keyp, char **valuep)
+{
+    const char *p = arg;
+    char *error;
+
+    assert(columnp || keyp);
+    if (keyp) {
+        *keyp = NULL;
+    }
+    if (valuep) {
+        *valuep = NULL;
+    }
+
+    /* Parse column name. */
+    if (columnp) {
+        char *column_name;
+
+        error = ovsdb_token_parse(&p, &column_name);
+        if (error) {
+            goto error;
+        }
+        if (column_name[0] == '\0') {
+            free(column_name);
+            error = xasprintf("%s: missing column name", arg);
+            goto error;
+        }
+        error = get_column(table, column_name, columnp);
+        free(column_name);
+        if (error) {
+            goto error;
+        }
+    }
+
+    /* Parse key string. */
+    if (*p == ':' || !columnp) {
+        if (columnp) {
+            p++;
+        } else if (!keyp) {
+            error = xasprintf("%s: key not accepted here", arg);
+            goto error;
+        }
+        error = ovsdb_token_parse(&p, keyp);
+        if (error) {
+            goto error;
+        }
+    } else if (keyp) {
+        *keyp = NULL;
+    }
+
+    /* Parse value string. */
+    if (*p == '=') {
+        if (!valuep) {
+            error = xasprintf("%s: value not accepted here", arg);
+            goto error;
+        }
+        *valuep = xstrdup(p + 1);
+    } else {
+        if (valuep) {
+            *valuep = NULL;
+        }
+        if (*p != '\0') {
+            error = xasprintf("%s: trailing garbage \"%s\" in argument",
+                              arg, p);
+            goto error;
+        }
+    }
+    return NULL;
+
+error:
+    if (columnp) {
+        *columnp = NULL;
+    }
+    if (keyp) {
+        free(*keyp);
+        *keyp = NULL;
+    }
+    if (valuep) {
+        free(*valuep);
+        *valuep = NULL;
+    }
+    return error;
+}
+
+static void
+cmd_get(struct vsctl_context *ctx)
+{
+    bool if_exists = shash_find(&ctx->options, "--if-exists");
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const struct vsctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    struct ds *out = &ctx->output;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    for (i = 3; i < ctx->argc; i++) {
+        const struct vsctl_column *column;
+        struct ovsdb_datum datum;
+        char *key_string;
+
+        die_if_error(parse_column_key_value(ctx->argv[i], table,
+                                            &column, &key_string, NULL));
+
+        ovsdb_idl_txn_read(row, column->idl, &datum);
+        if (key_string) {
+            union ovsdb_atom key;
+            unsigned int idx;
+
+            if (column->idl->type.value_type == OVSDB_TYPE_VOID) {
+                vsctl_fatal("cannot specify key to get for non-map column %s",
+                            column->idl->name);
+            }
+
+            die_if_error(ovsdb_atom_from_string(&key,
+                                                column->idl->type.key_type,
+                                                key_string));
+
+            idx = ovsdb_datum_find_key(&datum, &key,
+                                       column->idl->type.key_type);
+            if (idx == UINT_MAX) {
+                if (!if_exists) {
+                    vsctl_fatal("no key \"%s\" in %s record \"%s\" column %s",
+                                key_string, table->class->name, record_id,
+                                column->idl->name);
+                }
+            } else {
+                ovsdb_atom_to_string(&datum.values[idx],
+                                     column->idl->type.value_type, out);
+            }
+            ovsdb_atom_destroy(&key, column->idl->type.key_type);
+        } else {
+            ovsdb_datum_to_string(&datum, &column->idl->type, out);
+        }
+        ds_put_char(out, '\n');
+        ovsdb_datum_destroy(&datum, &column->idl->type);
+
+        free(key_string);
+    }
+}
+
+static void
+list_record(const struct vsctl_table_class *table,
+            const struct ovsdb_idl_row *row, struct ds *out)
+{
+    const struct vsctl_column *column;
+
+    ds_put_format(out, "%-20s (RO): "UUID_FMT"\n", "_uuid",
+                  UUID_ARGS(&row->uuid));
+    for (column = table->columns; column->idl; column++) {
+        struct ovsdb_datum datum;
+
+        if (column->flags & VSCF_HIDDEN) {
+            continue;
+        }
+
+        ovsdb_idl_txn_read(row, column->idl, &datum);
+
+        ds_put_format(out, "%-20s (%s): ", column->idl->name,
+                      column->flags & VSCF_READONLY ? "RO" : "RW");
+        ovsdb_datum_to_string(&datum, &column->idl->type, out);
+        ds_put_char(out, '\n');
+
+        ovsdb_datum_destroy(&datum, &column->idl->type);
+    }
+}
+
+static void
+cmd_list(struct vsctl_context *ctx)
+{
+    const char *table_name = ctx->argv[1];
+    const struct vsctl_table_class *table;
+    struct ds *out = &ctx->output;
+    int i;
+
+    table = get_table(table_name);
+    if (ctx->argc > 2) {
+        for (i = 2; i < ctx->argc; i++) {
+            if (i > 2) {
+                ds_put_char(out, '\n');
+            }
+            list_record(table, must_get_row(ctx, table, ctx->argv[i]), out);
+        }
+    } else {
+        const struct ovsdb_idl_row *row;
+        bool first;
+
+        for (row = ovsdb_idl_first_row(ctx->idl, table->class), first = true;
+             row != NULL;
+             row = ovsdb_idl_next_row(row), first = false) {
+            if (!first) {
+                ds_put_char(out, '\n');
+            }
+            list_record(table, row, out);
+        }
+    }
+}
+
+static void
+check_string_constraint(const struct ovsdb_datum *datum,
+                        const char *constraint)
+{
+    unsigned int i;
+    char *regex;
+    regex_t re;
+    int retval;
+
+    regex = xasprintf("^%s$", constraint);
+    retval = regcomp(&re, regex, REG_NOSUB | REG_EXTENDED);
+    if (retval) {
+        size_t length = regerror(retval, &re, NULL, 0);
+        char *buffer = xmalloc(length);
+        regerror(retval, &re, buffer, length);
+        vsctl_fatal("internal error compiling regular expression %s: %s",
+                    regex, buffer);
+    }
+
+    for (i = 0; i < datum->n; i++) {
+        const char *key = datum->keys[i].string;
+        if (regexec(&re, key, 0, NULL, 0)) {
+            vsctl_fatal("%s is not valid (it does not match %s)", key, regex);
+        }
+    }
+    free(regex);
+    regfree(&re);
+}
+
+static void
+check_integer_constraint(const struct ovsdb_datum *datum,
+                         const char *constraint)
+{
+    int64_t min, max;
+    unsigned int i;
+    int n = -1;
+
+    sscanf(constraint, "[%"SCNd64",%"SCNd64"]%n", &min, &max, &n);
+    if (n == -1) {
+        sscanf(constraint, "[%"SCNd64",]%n", &min, &n);
+        if (n == -1) {
+            sscanf(constraint, "[,%"SCNd64"]%n", &max, &n);
+            if (n == -1) {
+                VLOG_DBG("internal error: bad integer contraint \"%s\"",
+                         constraint);
+                return;
+            } else {
+                min = INT64_MIN;
+            }
+        } else {
+            max = INT64_MAX;
+        }
+    }
+
+    for (i = 0; i < datum->n; i++) {
+        int64_t value = datum->keys[i].integer;
+        if (value < min || value > max) {
+            if (max == INT64_MAX) {
+                vsctl_fatal("%"PRId64" is less than the minimum "
+                            "allowed value %"PRId64, value, min);
+            } else if (min == INT64_MIN) {
+                vsctl_fatal("%"PRId64" is greater than the maximum "
+                            "allowed value %"PRId64, value, max);
+            } else {
+                vsctl_fatal("%"PRId64" is outside the valid range %"PRId64" "
+                            "to %"PRId64" (inclusive)", value, min, max);
+            }
+        }
+    }
+}
+
+static void
+check_constraint(const struct ovsdb_datum *datum,
+                 const struct ovsdb_type *type, const char *constraint)
+{
+    if (constraint && datum->n) {
+        if (type->key_type == OVSDB_TYPE_STRING) {
+            check_string_constraint(datum, constraint);
+        } else if (type->key_type == OVSDB_TYPE_INTEGER) {
+            check_integer_constraint(datum, constraint);
+        }
+    }
+}
+
+static void
+set_column(const struct vsctl_table_class *table,
+           const struct ovsdb_idl_row *row,
+           const char *arg, bool force)
+{
+    const struct vsctl_column *column;
+    char *key_string, *value_string;
+    char *error;
+
+    error = parse_column_key_value(arg, table, &column, &key_string,
+                                   &value_string);
+    die_if_error(error);
+    if (column->flags & VSCF_READONLY && !force) {
+        vsctl_fatal("%s: cannot modify read-only column %s in table %s",
+                    arg, column->idl->name, table->class->name);
+    }
+    if (!value_string) {
+        vsctl_fatal("%s: missing value", arg);
+    }
+
+    if (key_string) {
+        union ovsdb_atom key, value;
+        struct ovsdb_datum old, new;
+
+        if (column->idl->type.value_type == OVSDB_TYPE_VOID) {
+            vsctl_fatal("cannot specify key to set for non-map column %s",
+                        column->idl->name);
+        }
+
+        die_if_error(ovsdb_atom_from_string(&key,
+                                            column->idl->type.key_type,
+                                            key_string));
+        die_if_error(ovsdb_atom_from_string(&value,
+                                            column->idl->type.value_type,
+                                            value_string));
+
+        ovsdb_datum_init_empty(&new);
+        ovsdb_datum_add_unsafe(&new, &key, &value, &column->idl->type);
+
+        ovsdb_atom_destroy(&key, column->idl->type.key_type);
+        ovsdb_atom_destroy(&value, column->idl->type.value_type);
+
+        ovsdb_idl_txn_read(row, column->idl, &old);
+        ovsdb_datum_union(&old, &new, &column->idl->type, true);
+        ovsdb_idl_txn_write(row, column->idl, &old);
+
+        ovsdb_datum_destroy(&new, &column->idl->type);
+    } else {
+        struct ovsdb_datum datum;
+
+        die_if_error(ovsdb_datum_from_string(&datum, &column->idl->type,
+                                             value_string));
+        if (!force) {
+            check_constraint(&datum, &column->idl->type,
+                             column->constraint);
+        }
+        ovsdb_idl_txn_write(row, column->idl, &datum);
+    }
+
+    free(key_string);
+    free(value_string);
+}
+
+static void
+cmd_set(struct vsctl_context *ctx)
+{
+    bool force = shash_find(&ctx->options, "--force");
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const struct vsctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    for (i = 3; i < ctx->argc; i++) {
+        set_column(table, row, ctx->argv[i], force);
+    }
+}
+
+static void
+cmd_add(struct vsctl_context *ctx)
+{
+    bool force = shash_find(&ctx->options, "--force");
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const char *column_name = ctx->argv[3];
+    const struct vsctl_table_class *table;
+    const struct vsctl_column *column;
+    const struct ovsdb_idl_row *row;
+    const struct ovsdb_type *type;
+    struct ovsdb_datum old;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    die_if_error(get_column(table, column_name, &column));
+    if (column->flags & VSCF_READONLY && !force) {
+        vsctl_fatal("cannot modify read-only column %s in table %s",
+                    column->idl->name, table->class->name);
+    }
+
+    type = &column->idl->type;
+    ovsdb_idl_txn_read(row, column->idl, &old);
+    for (i = 4; i < ctx->argc; i++) {
+        struct ovsdb_type add_type;
+        struct ovsdb_datum add;
+
+        add_type = *type;
+        add_type.n_min = 1;
+        add_type.n_max = UINT_MAX;
+        die_if_error(ovsdb_datum_from_string(&add, &add_type, ctx->argv[i]));
+        ovsdb_datum_union(&old, &add, type, false);
+        ovsdb_datum_destroy(&add, type);
+    }
+    if (old.n > type->n_max) {
+        vsctl_fatal("\"add\" operation would put %u %s in column %s of "
+                    "table %s but the maximum number is %u",
+                    old.n,
+                    type->value_type == OVSDB_TYPE_VOID ? "values" : "pairs",
+                    column->idl->name, table->class->name, type->n_max);
+    }
+    ovsdb_idl_txn_write(row, column->idl, &old);
+}
+
+static void
+cmd_remove(struct vsctl_context *ctx)
+{
+    bool force = shash_find(&ctx->options, "--force");
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const char *column_name = ctx->argv[3];
+    const struct vsctl_table_class *table;
+    const struct vsctl_column *column;
+    const struct ovsdb_idl_row *row;
+    const struct ovsdb_type *type;
+    struct ovsdb_datum old;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    die_if_error(get_column(table, column_name, &column));
+    if (column->flags & VSCF_READONLY && !force) {
+        vsctl_fatal("cannot modify read-only column %s in table %s",
+                    column->idl->name, table->class->name);
+    }
+
+    type = &column->idl->type;
+    ovsdb_idl_txn_read(row, column->idl, &old);
+    for (i = 4; i < ctx->argc; i++) {
+        struct ovsdb_type rm_type;
+        struct ovsdb_datum rm;
+        char *error;
+
+        rm_type = *type;
+        rm_type.n_min = 1;
+        rm_type.n_max = UINT_MAX;
+        error = ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]);
+        if (error && ovsdb_type_is_map(&rm_type)) {
+            free(error);
+            rm_type.value_type = OVSDB_TYPE_VOID;
+            die_if_error(ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]));
+        }
+        ovsdb_datum_subtract(&old, type, &rm, &rm_type);
+        ovsdb_datum_destroy(&rm, &rm_type);
+    }
+    if (old.n < type->n_min) {
+        vsctl_fatal("\"remove\" operation would put %u %s in column %s of "
+                    "table %s but the minimun number is %u",
+                    old.n,
+                    type->value_type == OVSDB_TYPE_VOID ? "values" : "pairs",
+                    column->idl->name, table->class->name, type->n_min);
+    }
+    ovsdb_idl_txn_write(row, column->idl, &old);
+}
+
+static void
+cmd_clear(struct vsctl_context *ctx)
+{
+    bool force = shash_find(&ctx->options, "--force");
+    const char *table_name = ctx->argv[1];
+    const char *record_id = ctx->argv[2];
+    const struct vsctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    int i;
+
+    table = get_table(table_name);
+    row = must_get_row(ctx, table, record_id);
+    for (i = 3; i < ctx->argc; i++) {
+        const struct vsctl_column *column;
+        const struct ovsdb_type *type;
+        struct ovsdb_datum datum;
+
+        die_if_error(get_column(table, ctx->argv[i], &column));
+
+        type = &column->idl->type;
+        if (column->flags & VSCF_READONLY && !force) {
+            vsctl_fatal("cannot modify read-only column %s in table %s",
+                        column->idl->name, table->class->name);
+        } else if (type->n_min > 0) {
+            vsctl_fatal("\"clear\" operation cannot be applied to column %s "
+                        "of table %s, which is not allowed to be empty",
+                        column->idl->name, table->class->name);
+        }
+
+        ovsdb_datum_init_empty(&datum);
+        ovsdb_idl_txn_write(row, column->idl, &datum);
+    }
+}
+
+static void
+cmd_create(struct vsctl_context *ctx)
+{
+    bool force = shash_find(&ctx->options, "--force");
+    const char *table_name = ctx->argv[1];
+    const struct vsctl_table_class *table;
+    const struct ovsdb_idl_row *row;
+    int i;
+
+    if (!force) {
+        vsctl_fatal("\"create\" requires --force");
+    }
+
+    table = get_table(table_name);
+    row = ovsdb_idl_txn_insert(ctx->txn, table->class);
+    for (i = 2; i < ctx->argc; i++) {
+        set_column(table, row, ctx->argv[i], force);
+    }
+    ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid));
+}
+
+/* This function may be used as the 'postprocess' function for commands that
+ * insert new rows into the database.  It expects that the command's 'run'
+ * function prints the UUID reported by ovsdb_idl_txn_insert() as the command's
+ * sole output.  It replaces that output by the row's permanent UUID assigned
+ * by the database server and appends a new-line.
+ *
+ * Currently we use this only for "create", because the higher-level commands
+ * are supposed to be independent of the actual structure of the vswitch
+ * configuration. */
+static void
+post_create(struct vsctl_context *ctx)
+{
+    const struct uuid *real;
+    struct uuid dummy;
+
+    uuid_from_string(&dummy, ds_cstr(&ctx->output));
+    real = ovsdb_idl_txn_get_insert_uuid(ctx->txn, &dummy);
+    if (real) {
+        ds_clear(&ctx->output);
+        ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(real));
+    }
+    ds_put_char(&ctx->output, '\n');
+}
+
+static void
+cmd_destroy(struct vsctl_context *ctx)
+{
+    bool force = shash_find(&ctx->options, "--force");
+    bool must_exist = !shash_find(&ctx->options, "--if-exists");
+    const char *table_name = ctx->argv[1];
+    const struct vsctl_table_class *table;
+    int i;
+
+    if (!force) {
+        vsctl_fatal("\"destroy\" requires --force");
+    }
+
+    table = get_table(table_name);
+    for (i = 2; i < ctx->argc; i++) {
+        const struct ovsdb_idl_row *row;
+
+        row = (must_exist ? must_get_row : get_row)(ctx, table, ctx->argv[i]);
+        if (row) {
+            ovsdb_idl_txn_delete(row);
+        }
+    }
+}
+\f
+static struct json *
+where_uuid_equals(const struct uuid *uuid)
+{
+    return
+        json_array_create_1(
+            json_array_create_3(
+                json_string_create("_uuid"),
+                json_string_create("=="),
+                json_array_create_2(
+                    json_string_create("uuid"),
+                    json_string_create_nocopy(
+                        xasprintf(UUID_FMT, UUID_ARGS(uuid))))));
+}
+
+static void
+vsctl_context_init(struct vsctl_context *ctx, struct vsctl_command *command,
+                   struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn,
+                   const struct ovsrec_open_vswitch *ovs)
+{
+    ctx->argc = command->argc;
+    ctx->argv = command->argv;
+    ctx->options = command->options;
+
+    ds_swap(&ctx->output, &command->output);
+    ctx->idl = idl;
+    ctx->txn = txn;
+    ctx->ovs = ovs;
+
+}
+
+static void
+vsctl_context_done(struct vsctl_context *ctx, struct vsctl_command *command)
+{
+    ds_swap(&ctx->output, &command->output);
+}
+
+static void
+do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands,
+         struct ovsdb_idl *idl)
+{
+    struct ovsdb_idl_txn *txn;
+    const struct ovsrec_open_vswitch *ovs;
+    enum ovsdb_idl_txn_status status;
+    struct vsctl_command *c;
+    int64_t next_cfg = 0;
+    char *comment;
+
+    txn = the_idl_txn = ovsdb_idl_txn_create(idl);
+    if (dry_run) {
+        ovsdb_idl_txn_set_dry_run(txn);
+    }
+
+    comment = xasprintf("ovs-vsctl: %s", args);
+    ovsdb_idl_txn_add_comment(txn, comment);
+    free(comment);
+
+    ovs = ovsrec_open_vswitch_first(idl);
+    if (!ovs) {
+        /* XXX add verification that table is empty */
+        ovs = ovsrec_open_vswitch_insert(txn);
+    }
+
+    if (wait_for_reload) {
+        struct json *where = where_uuid_equals(&ovs->header_.uuid);
+        ovsdb_idl_txn_increment(txn, "Open_vSwitch", "next_cfg", where);
+        json_destroy(where);
+    }
+
+    for (c = commands; c < &commands[n_commands]; c++) {
+        struct vsctl_context ctx;
+
+        ds_init(&c->output);
+        vsctl_context_init(&ctx, c, idl, txn, ovs);
+        (c->syntax->run)(&ctx);
+        vsctl_context_done(&ctx, c);
+    }
+
+    while ((status = ovsdb_idl_txn_commit(txn)) == TXN_INCOMPLETE) {
+        ovsdb_idl_run(idl);
+        ovsdb_idl_wait(idl);
+        ovsdb_idl_txn_wait(txn);
+        poll_block();
+    }
+    if (wait_for_reload && status == TXN_SUCCESS) {
+        next_cfg = ovsdb_idl_txn_get_increment_new_value(txn);
+    }
+    for (c = commands; c < &commands[n_commands]; c++) {
+        if (c->syntax->postprocess) {
+            struct vsctl_context ctx;
+
+            vsctl_context_init(&ctx, c, idl, txn, ovs);
+            (c->syntax->postprocess)(&ctx);
+            vsctl_context_done(&ctx, c);
+        }
+    }
+    ovsdb_idl_txn_destroy(txn);
+    the_idl_txn = NULL;
+
+    switch (status) {
+    case TXN_INCOMPLETE:
+        NOT_REACHED();
+
+    case TXN_ABORTED:
+        /* Should not happen--we never call ovsdb_idl_txn_abort(). */
+        vsctl_fatal("transaction aborted");
+
+    case TXN_UNCHANGED:
+    case TXN_SUCCESS:
+        break;
+
+    case TXN_TRY_AGAIN:
+        for (c = commands; c < &commands[n_commands]; c++) {
+            ds_destroy(&c->output);
+        }
+        return;
+
+    case TXN_ERROR:
+        vsctl_fatal("transaction error");
+
+    default:
+        NOT_REACHED();
+    }
+
+    for (c = commands; c < &commands[n_commands]; c++) {
+        struct ds *ds = &c->output;
+        if (oneline) {
+            size_t j;
+
+            ds_chomp(ds, '\n');
+            for (j = 0; j < ds->length; j++) {
+                int c = ds->string[j];
+                switch (c) {
+                case '\n':
+                    fputs("\\n", stdout);
+                    break;
+
+                case '\\':
+                    fputs("\\\\", stdout);
+                    break;
+
+                default:
+                    putchar(c);
+                }
+            }
+            putchar('\n');
+        } else {
+            fputs(ds_cstr(ds), stdout);
+        }
+        ds_destroy(&c->output);
+        shash_destroy(&c->options);
+    }
+    free(commands);
+
+    if (wait_for_reload && status != TXN_UNCHANGED) {
+        for (;;) {
+            const struct ovsrec_open_vswitch *ovs;
+
+            ovsdb_idl_run(idl);
+            OVSREC_OPEN_VSWITCH_FOR_EACH (ovs, idl) {
+                if (ovs->cur_cfg >= next_cfg) {
+                    goto done;
+                }
+            }
+            ovsdb_idl_wait(idl);
+            poll_block();
+        }
+    done: ;
+    }
+    ovsdb_idl_destroy(idl);
+
+    exit(EXIT_SUCCESS);
+}
+
+static const struct vsctl_command_syntax all_commands[] = {
+    /* Open vSwitch commands. */
+    {"init", 0, 0, cmd_init, NULL, ""},
+
+    /* Bridge commands. */
+    {"add-br", 1, 3, cmd_add_br, NULL, ""},
+    {"del-br", 1, 1, cmd_del_br, NULL, "--if-exists"},
+    {"list-br", 0, 0, cmd_list_br, NULL, ""},
+    {"br-exists", 1, 1, cmd_br_exists, NULL, ""},
+    {"br-to-vlan", 1, 1, cmd_br_to_vlan, NULL, ""},
+    {"br-to-parent", 1, 1, cmd_br_to_parent, NULL, ""},
+    {"br-set-external-id", 2, 3, cmd_br_set_external_id, NULL, ""},
+    {"br-get-external-id", 1, 2, cmd_br_get_external_id, NULL, ""},
+
+    /* Port commands. */
+    {"list-ports", 1, 1, cmd_list_ports, NULL, ""},
+    {"add-port", 2, 2, cmd_add_port, NULL, ""},
+    {"add-bond", 4, INT_MAX, cmd_add_bond, NULL, "--fake-iface"},
+    {"del-port", 1, 2, cmd_del_port, NULL, "--if-exists"},
+    {"port-to-br", 1, 1, cmd_port_to_br, NULL, ""},
+
+    /* Interface commands. */
+    {"list-ifaces", 1, 1, cmd_list_ifaces, NULL, ""},
+    {"iface-to-br", 1, 1, cmd_iface_to_br, NULL, ""},
+
+    /* Controller commands. */
+    {"get-controller", 0, 1, cmd_get_controller, NULL, ""},
+    {"del-controller", 0, 1, cmd_del_controller, NULL, ""},
+    {"set-controller", 1, 2, cmd_set_controller, NULL, ""},
+    {"get-fail-mode", 0, 1, cmd_get_fail_mode, NULL, ""},
+    {"del-fail-mode", 0, 1, cmd_del_fail_mode, NULL, ""},
+    {"set-fail-mode", 1, 2, cmd_set_fail_mode, NULL, ""},
+
+    /* SSL commands. */
+    {"get-ssl", 0, 0, cmd_get_ssl, NULL, ""},
+    {"del-ssl", 0, 0, cmd_del_ssl, NULL, ""},
+    {"set-ssl", 3, 3, cmd_set_ssl, NULL, "--bootstrap"},
+
+    /* Parameter commands. */
+    {"get", 3, INT_MAX, cmd_get, NULL, "--if-exists"},
+    {"list", 1, INT_MAX, cmd_list, NULL, ""},
+    {"set", 3, INT_MAX, cmd_set, NULL, "--force"},
+    {"add", 4, INT_MAX, cmd_add, NULL, "--force"},
+    {"remove", 4, INT_MAX, cmd_remove, NULL, "--force"},
+    {"clear", 3, INT_MAX, cmd_clear, NULL, "--force"},
+    {"create", 2, INT_MAX, cmd_create, post_create, "--force"},
+    {"destroy", 1, INT_MAX, cmd_destroy, NULL, "--force,--if-exists"},
+
+    {NULL, 0, 0, NULL, NULL, NULL},
+};
+
diff --git a/utilities/ovs-vsctl.in b/utilities/ovs-vsctl.in
deleted file mode 100755 (executable)
index 06dbabb..0000000
+++ /dev/null
@@ -1,609 +0,0 @@
-#! @PYTHON@
-# Copyright (c) 2009 Nicira Networks.                       -*- python -*-
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at:
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import errno
-import fcntl
-import fnmatch
-import getopt
-import os
-import re
-import socket
-import stat
-import sys
-import syslog
-
-argv0 = sys.argv[0]
-if argv0.find('/') >= 0:
-    argv0 = argv0[argv0.rfind('/') + 1:]
-
-DEFAULT_VSWITCHD_CONF = "@sysconfdir@/ovs-vswitchd.conf"
-vswitchd_conf = DEFAULT_VSWITCHD_CONF
-
-DEFAULT_VSWITCHD_TARGET = "ovs-vswitchd"
-vswitchd_target = DEFAULT_VSWITCHD_TARGET
-
-reload_vswitchd = True
-
-enable_syslog = True
-
-class Error(Exception):
-    def __init__(self, msg):
-        Exception.__init__(self)
-        self.msg = msg
-
-def log(message):
-    if enable_syslog:
-        syslog.syslog(message)
-
-# XXX Most of the functions below should be integrated into a
-# VSwitchConfiguration object with logically named fields and methods
-# instead of this mishmash of functionality.
-
-# Locks 'filename' for writing.
-def cfg_lock(filename):
-    if filename == '-':
-        return
-
-    if '/' in filename:
-        lastSlash = filename.rfind('/')
-        prefix = filename[:lastSlash]
-        suffix = filename[lastSlash + 1:]
-        lock_name = "%s/.%s.~lock~" % (prefix, suffix)
-    else:
-        lock_name = ".%s.~lock~" % filename
-
-    while True:
-        # Try to open an existing lock file.
-        try:
-            f = open(lock_name, 'r')
-        except IOError, e:
-            if e.errno != errno.ENOENT:
-                raise
-
-            # Try to create a new lock file.
-            try:
-                fd = os.open(lock_name, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0600)
-            except OSError, e:
-                if e.errno != errno.EEXIST:
-                    raise
-                # Someone else created the lock file, try again.
-            os.close(fd)
-            continue
-    
-        fcntl.flock(f, fcntl.LOCK_EX)
-        return
-
-# Read the ovs-vswitchd.conf file named 'filename' and return its contents as a
-# dictionary that maps from string keys to lists of string values.  (Even
-# singleton values are represented as lists.)
-def cfg_read(filename, lock=False):
-    if lock:
-        cfg_lock(filename)
-
-    try:
-        if filename == '-':
-            f = open('/dev/stdin')
-        else:
-            f = open(filename)
-    except IOError, e:
-        sys.stderr.write("%s: could not open %s (%s)\n"
-                         % (argv0, filename, e.strerror))
-        sys.exit(1)
-
-    cfg = {}
-    rx = re.compile('([-._@$:+a-zA-Z0-9]+)(?:[ \t\r\n\v]*)=(?:[ \t\r\n\v]*)(.*)$')
-    for line in f:
-        line = line.strip()
-        if len(line) == 0 or line[0] == '#':
-            continue
-
-        match = rx.match(line)
-        if match == None:
-            continue
-
-        key, value = match.groups()
-        if key not in cfg:
-            cfg[key] = []
-        cfg[key].append(value)
-
-    global orig_cfg
-    orig_cfg = cfg_clone(cfg)
-
-    return cfg
-
-# Returns a deep copy of 'cfg', which must be in the format returned
-# by cfg_read().
-def cfg_clone(cfg):
-    new = {}
-    for key in cfg:
-        new[key] = list(cfg[key])
-    return new
-
-# Returns a list of all the configuration lines that are in 'a' but
-# not in 'b'.
-def cfg_subtract(a, b):
-    difference = []
-    for key in a:
-        for value in a[key]:
-            if key not in b or value not in b[key]:
-                difference.append("%s=%s" % (key, value))
-    return difference
-
-def do_cfg_save(cfg, file):
-    # Log changes.
-    added = cfg_subtract(cfg, orig_cfg)
-    removed = cfg_subtract(orig_cfg, cfg)
-    if added or removed:
-        log("configuration changes:")
-        for line in removed:
-            log("-%s\n" % line)
-        for line in added:
-            log("+%s\n" % line)
-
-    # Write changes.
-    for key in sorted(cfg.keys()):
-        for value in sorted(cfg[key]):
-            file.write("%s=%s\n" % (key, value))
-
-def cfg_reload():
-    target = vswitchd_target
-    if not target.startswith('/'):
-        pid = read_first_line_of_file('%s/%s.pid' % ('@RUNDIR@', target))
-        target = '%s/%s.%s.ctl' % ('@RUNDIR@', target, pid)
-    s = os.stat(target)
-    if not stat.S_ISSOCK(s.st_mode):
-        raise Error("%s is not a Unix domain socket, cannot reload" % target)
-    skt = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-    skt.connect(target)
-    f = os.fdopen(skt.fileno(), "r+")
-    f.write("vswitchd/reload\n")
-    f.flush()
-    f.readline()
-    f.close()
-
-def cfg_save(cfg, filename):
-    if filename == '-':
-        do_cfg_save(cfg, sys.stdout)
-    else:
-        tmp_name = filename + ".~tmp~"
-        f = open(tmp_name, 'w')
-        do_cfg_save(cfg, f)
-        f.close()
-        os.rename(tmp_name, filename)
-        if reload_vswitchd:
-            cfg_reload()
-
-# Returns a set of the immediate subsections of 'section' within 'cfg'.  For
-# example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c,
-# and bridge.c.x.y.z exist, returns set(['a', 'b', 'c']).
-def cfg_get_subsections(cfg, section):
-    subsections = set()
-    for key in cfg:
-        if key.startswith(section + "."):
-            dot = key.find(".", len(section) + 1)
-            if dot == -1:
-                dot = len(key)
-            subsections.add(key[len(section) + 1:dot])
-    return subsections
-
-# Returns True if 'cfg' contains a key whose single value is 'true'.  Otherwise
-# returns False.
-def cfg_get_bool(cfg, name):
-    return name in cfg and cfg[name] == ['true']
-
-# If 'cfg' has a port named 'port' configured with an implicit VLAN, returns
-# that VLAN number.  Otherwise, returns 0.
-def get_port_vlan(cfg, port):
-    try:
-        return int(cfg["vlan.%s.tag" % port][0])
-    except (ValueError, KeyError):
-        return 0
-
-# Returns all the ports within 'bridge' in 'cfg'.  If 'vlan' is nonnegative,
-# the ports returned are only those configured with implicit VLAN 'vlan'.
-def get_bridge_ports(cfg, bridge, vlan):
-    ports = []
-    for port in cfg["bridge.%s.port" % bridge]:
-        if vlan < 0 or get_port_vlan(cfg, port) == vlan:
-            ports.append(port)
-    return ports
-
-# Returns all the interfaces within 'bridge' in 'cfg'.  If 'vlan' is
-# nonnegative, the interfaces returned are only those whose ports are
-# configured with implicit VLAN 'vlan'.
-def get_bridge_ifaces(cfg, bridge, vlan):
-    ifaces = []
-    for port in get_bridge_ports(cfg, bridge, vlan):
-        ifaces.extend(cfg.get("bonding.%s.slave" % port, [port]))
-    return ifaces
-
-# Returns the first line of the file named 'name', with the trailing new-line
-# (if any) stripped off.
-def read_first_line_of_file(name):
-    file = None
-    try:
-        file = open(name, 'r')
-        return file.readline().rstrip('\n')
-    finally:
-        if file != None:
-            file.close()
-
-# Returns a bridge ID constructed from the MAC address of network device
-# 'netdev', in the format "8000.000102030405".
-def get_bridge_id(netdev):
-    try:
-        hwaddr = read_first_line_of_file("/sys/class/net/%s/address" % netdev)
-        return "8000.%s" % (hwaddr.replace(":", ""))
-    except:
-        return "8000.002320ffffff"
-
-# Returns a list of 3-tuples based on 'cfg'.  Each 3-tuple represents
-# one real bridge or one fake bridge and has the form (bridge, parent,
-# vlan), where 'bridge' is the real or fake bridge name, 'parent' is
-# the same as 'bridge' for a real bridge or the name of the containing
-# bridge for a fake bridge, and 'vlan' is 0 for a real bridge or a
-# VLAN number for a fake bridge.
-def get_bridge_info(cfg):
-    real_bridges = [(br, br, 0) for br in get_real_bridges(cfg)]
-    fake_bridges = []
-    for linux_bridge, ovs_bridge, vlan in real_bridges:
-        for iface in get_bridge_ifaces(cfg, ovs_bridge, -1):
-            if cfg_get_bool(cfg, "iface.%s.fake-bridge" % iface):
-                fake_bridges.append((iface, ovs_bridge,
-                                     get_port_vlan(cfg, iface)))
-    return real_bridges + fake_bridges
-
-# Returns the real bridges configured in 'cfg'.
-def get_real_bridges(cfg):
-    return cfg_get_subsections(cfg, "bridge")
-
-# Returns the fake bridges configured in 'cfg'.
-def get_fake_bridges(cfg):
-    return [bridge for bridge, parent, vlan in get_bridge_info(cfg)
-            if bridge != parent]
-
-# Returns all the real and fake bridges configured in 'cfg'.
-def get_all_bridges(cfg):
-    return [bridge for bridge, parent, vlan in get_bridge_info(cfg)]
-
-# Returns the parent bridge and VLAN of real or fake 'bridge' in
-# 'cfg', where the parent bridge and VLAN are as defined in the
-# description of get_bridge_info().  Raises an error if no bridge
-# named 'bridge' exists in 'cfg'.
-def find_bridge(cfg, bridge):
-    for br, parent, vlan in get_bridge_info(cfg):
-        if br == bridge:
-            return parent, vlan
-    raise Error("no bridge named %s" % bridge)
-
-def del_matching_keys(cfg, pattern):
-    for key in [key for key in cfg.keys() if fnmatch.fnmatch(key, pattern)]:
-        del cfg[key]
-
-# Deletes anything related to a port named 'port' from 'cfg'.  No port
-# named 'port' need actually exist; this function will clean up
-# regardless.
-def del_port(cfg, port):
-    # The use of [!0-9] keeps an interface of 'eth0' from matching
-    # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
-    # interfaces.
-    for iface in cfg.get('bonding.%s.slave' % port, [port]):
-        del_matching_keys(cfg, 'iface.%s.[!0-9]*' % iface)
-        # Yes, this "port" setting applies to interfaces, not ports, *sigh*.
-        del_matching_keys(cfg, 'port.%s.ingress-policing*' % iface)
-    del_matching_keys(cfg, 'bonding.%s.[!0-9]*' % port)
-    del_matching_keys(cfg, 'vlan.%s.[!0-9]*' % port)
-    for key in cfg.keys():
-        if fnmatch.fnmatch(key, 'bridge.*.port'):
-            cfg[key] = [s for s in cfg[key] if s != port]
-
-# Returns the name of the (real or fake) bridge in 'cfg' that contains
-# port 'port', or None if there is no such port.
-def port_to_bridge(cfg, port):
-    for bridge, parent, vlan in get_bridge_info(cfg):
-        if port != bridge and port in get_bridge_ports(cfg, parent, vlan):
-            return bridge
-    return None
-
-def usage():
-    print """%(argv0)s: ovs-vswitchd management utility
-usage: %(argv0)s [OPTIONS] COMMAND [ARG...]
-
-Bridge commands:
-  add-br BRIDGE               create a new bridge named BRIDGE
-  add-br BRIDGE PARENT VLAN   create new fake bridge BRIDGE in PARENT on VLAN
-  del-br BRIDGE               delete BRIDGE and all of its ports
-  list-br                     print the names of all the bridges
-  br-exists BRIDGE            test whether BRIDGE exists
-  br-to-vlan BRIDGE           print the VLAN which BRIDGE is on
-  br-to-parent BRIDGE         print the parent of BRIDGE
-  
-Port commands:
-  list-ports BRIDGE           print the names of all the ports on BRIDGE
-  add-port BRIDGE PORT        add network device PORT to BRIDGE
-  add-bond BRIDGE PORT IFACE...  add new bonded port PORT in BRIDGE from IFACES
-  del-port [BRIDGE] PORT      delete PORT (which may be bonded) from BRIDGE
-  port-to-br PORT             print name of bridge that contains PORT
-A bond is considered to be a single port.
-
-Interface commands (a bond consists of multiple interfaces):
-  list-ifaces BRIDGE          print the names of all the interfaces on BRIDGE
-  iface-to-br IFACE           print name of bridge that contains IFACE
-A bond is considered to consist of interfaces.
-
-General options:
-  --no-syslog                 do not write mesages to syslog
-  -c, --config=FILE           set configuration file
-                              (default: %(config)s)
-  -t, --target=PROGRAM|SOCKET set ovs-vswitchd target
-                              (default: %(target)s)
-  --no-reload                 do not make ovs-vswitchd reload its configuration
-  -h, --help                  display this help message and exit
-  -V, --version               display version information and exit
-Report bugs to bugs@openvswitch.org.""" % {'argv0': argv0,
-                                           'config': DEFAULT_VSWITCHD_CONF,
-                                           'target': DEFAULT_VSWITCHD_TARGET}
-    sys.exit(0)
-
-def version():
-    print "ovs-vsctl (Open vSwitch) @VERSION@"
-    sys.exit(0)
-
-def check_conflicts(cfg, name, op):
-    bridges = get_bridge_info(cfg)
-    if name in [bridge for bridge, parent, vlan in bridges]:
-        raise Error("%s because a bridge named %s already exists" % (op, name))
-
-    for bridge, parent, vlan in bridges:
-        if name in get_bridge_ports(cfg, parent, vlan):
-            raise Error("%s because a port named %s already exists on bridge %s" % (op, name, bridge))
-        if name in get_bridge_ifaces(cfg, parent, vlan):
-            raise Error("%s because an interface named %s already exists on bridge %s" % (op, name, bridge))
-    
-def cmd_add_br(cfg, bridge, parent=None, vlan=None):
-    check_conflicts(cfg, bridge, "cannot create a bridge named %s" % bridge)
-    
-    if parent and vlan:
-        if parent in get_fake_bridges(cfg):
-            raise Error("cannot create bridge with fake bridge as parent")
-        if parent not in get_real_bridges(cfg):
-            raise Error("parent bridge %s does not exist" % bridge)
-        try:
-            if int(vlan) < 0 or int(vlan) > 4095:
-                raise ValueError
-        except ValueError:
-            raise Error("invalid VLAN number %s" % vlan)
-
-        # Create fake bridge internal port.
-        cfg['iface.%s.internal' % bridge] = ['true']
-        cfg['iface.%s.fake-bridge' % bridge] = ['true']
-        cfg['vlan.%s.tag' % bridge] = [vlan]
-
-        # Add fake bridge port to parent.
-        cfg['bridge.%s.port' % parent].append(bridge)
-    else:
-        cfg['bridge.%s.port' % bridge] = [bridge]
-
-def cmd_del_br(cfg, bridge):
-    parent, vlan = find_bridge(cfg, bridge)
-    if vlan == 0:
-        vlan = -1
-    for port in set(get_bridge_ports(cfg, parent, vlan) + [bridge]):
-        del_port(cfg, port)
-    if vlan < 0: 
-        del_matching_keys(cfg, 'bridge.%s.[!0-9]*' % bridge)
-
-def cmd_list_br(cfg):
-    return get_all_bridges(cfg)
-
-def cmd_br_exists(cfg, bridge):
-    if bridge not in get_all_bridges(cfg):
-        sys.exit(2)
-
-def cmd_list_ports(cfg, bridge):
-    ports = []
-    parent, vlan = find_bridge(cfg, bridge)
-    for port in get_bridge_ports(cfg, parent, vlan):
-        if port != bridge:
-            ports.append(port)
-    return ports
-
-def do_add_port(cfg, bridge, parent, port, vlan):
-    check_conflicts(cfg, port, "cannot create a port named %s" % port)
-    cfg['bridge.%s.port' % parent].append(port)
-    if vlan > 0:
-        cfg['vlan.%s.tag' % port] = [vlan]
-
-def cmd_add_port(cfg, bridge, port):
-    parent, vlan = find_bridge(cfg, bridge)
-    do_add_port(cfg, bridge, parent, port, vlan)
-
-def cmd_add_bond(cfg, bridge, port, *slaves):
-    parent, vlan = find_bridge(cfg, bridge)
-    do_add_port(cfg, bridge, parent, port, vlan)
-    cfg['bonding.%s.slave' % port] = list(slaves)
-
-def cmd_del_port(cfg, *args):
-    if len(args) == 2:
-        bridge, port = args
-        parent, vlan = find_bridge(cfg, bridge)
-        if port not in get_bridge_ports(cfg, parent, vlan):
-            if port in get_bridge_ports(cfg, parent, -1):
-                raise Error("bridge %s does not have a port %s (although its parent bridge %s does)" % (bridge, port, parent))
-            else:
-                raise Error("bridge %s does not have a port %s" % (bridge, port))
-    else:
-        port, = args
-        if not port_to_bridge(cfg, port):
-            raise Error("no port %s on any bridge" % port)
-    del_port(cfg, port)
-
-def cmd_port_to_br(cfg, port):
-    bridge = port_to_bridge(cfg, port)
-    if bridge:
-        return (bridge,)
-    else:
-        raise Error("no port named %s" % port)
-
-def cmd_list_ifaces(cfg, bridge):
-    ifaces = []
-    parent, vlan = find_bridge(cfg, bridge)
-    for iface in get_bridge_ifaces(cfg, parent, vlan):
-        if iface != bridge:
-            ifaces.append(iface)
-    return ifaces
-
-def cmd_iface_to_br(cfg, iface):
-    for bridge, parent, vlan in get_bridge_info(cfg):
-        if iface != bridge and iface in get_bridge_ifaces(cfg, parent, vlan):
-            return (bridge,)
-    raise Error("no interface named %s" % iface)
-
-def cmd_br_to_vlan(cfg, bridge):
-    parent, vlan = find_bridge(cfg, bridge)
-    return (vlan,)
-
-def cmd_br_to_parent(cfg, bridge):
-    parent, vlan = find_bridge(cfg, bridge)
-    return (parent,)
-    
-cmdTable = {'add-br': (cmd_add_br, True, lambda n: n == 1 or n == 3),
-            'del-br': (cmd_del_br, True, 1),
-            'list-br': (cmd_list_br, False, 0),
-            'br-exists': (cmd_br_exists, False, 1),
-            'list-ports': (cmd_list_ports, False, 1),
-            'add-port': (cmd_add_port, True, 2),
-            'add-bond': (cmd_add_bond, True, lambda n: n >= 4),
-            'del-port': (cmd_del_port, True, lambda n: n == 1 or n == 2),
-            'port-to-br': (cmd_port_to_br, False, 1),
-            'br-to-vlan': (cmd_br_to_vlan, False, 1),
-            'br-to-parent': (cmd_br_to_parent, False, 1),
-            'list-ifaces': (cmd_list_ifaces, False, 1),
-            'iface-to-br': (cmd_iface_to_br, False, 1)}
-
-# Break up commands at -- boundaries.
-def split_commands(args):
-    commands = []
-    command = []
-    for arg in args:
-        if arg == '--':
-            if command:
-                commands.append(command)
-            command = []
-        else:
-            command.append(arg)
-    if command:
-        commands.append(command)
-    return commands
-
-def check_command(args):
-    command, args = args[0], args[1:]
-    if command not in cmdTable:
-        sys.stderr.write("%s: unknown command '%s' (use --help for help)\n"
-                         % (argv0, command))
-        sys.exit(1)
-
-    function, is_mutator, nargs = cmdTable[command]
-    if callable(nargs) and not nargs(len(args)):
-        sys.stderr.write("%s: '%s' command does not accept %d arguments (use --help for help)\n" % (argv0, command, len(args)))
-        sys.exit(1)
-    elif not callable(nargs) and len(args) != nargs:
-        sys.stderr.write("%s: '%s' command takes %d arguments but %d were supplied (use --help for help)\n" % (argv0, command, nargs, len(args)))
-        sys.exit(1)
-
-def run_command(cfg, args):
-    command, args = args[0], args[1:]
-    function, need_lock, nargs = cmdTable[command]
-    return function(cfg, *args)
-
-def main():
-    # Parse command line.
-    try:
-        options, args = getopt.getopt(sys.argv[1:], "c:t:hV",
-                                      ["config=",
-                                       "target=",
-                                       "no-reload",
-                                       "no-syslog",
-                                       "oneline",
-                                       "help",
-                                       "version"])
-    except getopt.GetoptError, msg:
-        sys.stderr.write("%s: %s (use --help for help)\n" % (argv0, msg))
-        sys.exit(1)
-
-    # Handle options.
-    oneline = False
-    for opt, optarg in options:
-        if opt == "-c" or opt == "--config":
-            global vswitchd_conf
-            vswitchd_conf = optarg
-        elif opt == "-t" or opt == "--target":
-            global vswitchd_target
-            vswitchd_target = optarg
-        elif opt == "--no-reload":
-            global reload_vswitchd
-            reload_vswitchd = False
-        elif opt == "-h" or opt == "--help":
-            usage()
-        elif opt == "-V" or opt == "--version":
-            version()
-        elif opt == "--no-syslog":
-            global enable_syslog
-            enable_syslog = False
-        elif opt == "--oneline":
-            oneline = True
-        else:
-            raise RuntimeError("unhandled option %s" % opt)
-
-    if enable_syslog:
-        syslog.openlog("ovs-vsctl")
-        log("Called as %s" % ' '.join(sys.argv[1:]))
-
-    # Break arguments into a series of commands.
-    commands = split_commands(args)
-    if not commands:
-        sys.stderr.write("%s: missing command name (use --help for help)\n"
-                         % argv0)
-        sys.exit(1)
-
-    # Check command syntax.
-    need_lock = False
-    for command in commands:
-        check_command(command)
-        if cmdTable[command[0]][1]:
-            need_lock = True
-
-    # Execute commands.
-    cfg = cfg_read(vswitchd_conf, need_lock)
-    for command in commands:
-        output = run_command(cfg, command)
-        if oneline:
-            if output == None:
-                output = ()
-            print '\\n'.join([str(s).replace('\\', '\\\\')
-                              for s in output])
-        elif output != None:
-            for line in output:
-                print line
-    if need_lock:
-        cfg_save(cfg, vswitchd_conf)
-    sys.exit(0)
-
-if __name__ == "__main__":
-    try:
-        main()
-    except Error, msg:
-        sys.stderr.write("%s: %s\n" % (argv0, msg.msg))
-        sys.exit(1)
index 01d57ae..872a726 100644 (file)
@@ -5,3 +5,7 @@
 /ovs-vswitchd
 /ovs-vswitchd.8
 /ovs-vswitchd.conf.5
+/vswitch-idl.c
+/vswitch-idl.h
+/vswitch-idl.ovsidl
+/vswitch-idl.txt
index d810c83..c38add6 100644 (file)
@@ -1,39 +1,47 @@
 sbin_PROGRAMS += vswitchd/ovs-vswitchd vswitchd/ovs-brcompatd
 man_MANS += \
-       vswitchd/ovs-vswitchd.conf.5 \
        vswitchd/ovs-vswitchd.8 \
        vswitchd/ovs-brcompatd.8
 DISTCLEANFILES += \
-       vswitchd/ovs-vswitchd.conf.5 \
        vswitchd/ovs-vswitchd.8 \
        vswitchd/ovs-brcompatd.8
 
 vswitchd_ovs_vswitchd_SOURCES = \
        vswitchd/bridge.c \
        vswitchd/bridge.h \
-       vswitchd/mgmt.c \
-       vswitchd/mgmt.h \
        vswitchd/proc-net-compat.c \
        vswitchd/proc-net-compat.h \
        vswitchd/ovs-vswitchd.c \
-       vswitchd/ovs-vswitchd.h \
+       vswitchd/vswitch-idl.c \
+       vswitchd/vswitch-idl.h \
        vswitchd/xenserver.c \
        vswitchd/xenserver.h
 vswitchd_ovs_vswitchd_LDADD = \
        ofproto/libofproto.a \
        lib/libsflow.a \
        lib/libopenvswitch.a \
-       $(FAULT_LIBS) \
        $(SSL_LIBS)
 
 vswitchd_ovs_brcompatd_SOURCES = \
-       vswitchd/ovs-brcompatd.c
+       vswitchd/ovs-brcompatd.c \
+       vswitchd/vswitch-idl.c \
+       vswitchd/vswitch-idl.h
 
-vswitchd_ovs_brcompatd_LDADD = \
-       lib/libopenvswitch.a \
-       $(FAULT_LIBS) 
+vswitchd_ovs_brcompatd_LDADD = lib/libopenvswitch.a $(SSL_LIBS)
 
 EXTRA_DIST += \
-       vswitchd/ovs-vswitchd.conf.5.in \
        vswitchd/ovs-vswitchd.8.in \
        vswitchd/ovs-brcompatd.8.in
+
+
+# vswitch schema and IDL
+OVSIDL_BUILT += \
+       vswitchd/vswitch-idl.c \
+       vswitchd/vswitch-idl.h \
+       vswitchd/vswitch-idl.ovsidl
+VSWITCH_IDL_FILES = vswitchd/vswitch.ovsschema vswitchd/vswitch-idl.ann
+noinst_DATA += vswitchd/vswitch-idl.txt
+EXTRA_DIST += $(VSWITCH_IDL_FILES) vswitchd/vswitch-idl.txt
+vswitchd/vswitch-idl.ovsidl: $(VSWITCH_IDL_FILES)
+       $(OVSDB_IDLC) -C $(srcdir) annotate $(VSWITCH_IDL_FILES) > $@.tmp
+       mv $@.tmp $@
index 3b7ec51..88f8db1 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@
 #include <sys/types.h>
 #include <unistd.h>
 #include "bitmap.h"
-#include "cfg.h"
 #include "coverage.h"
 #include "dirs.h"
 #include "dpif.h"
 #include "port-array.h"
 #include "proc-net-compat.h"
 #include "process.h"
+#include "sha1.h"
 #include "shash.h"
 #include "socket-util.h"
-#include "stp.h"
+#include "stream-ssl.h"
 #include "svec.h"
 #include "timeval.h"
 #include "util.h"
 #include "unixctl.h"
 #include "vconn.h"
-#include "vconn-ssl.h"
+#include "vswitchd/vswitch-idl.h"
 #include "xenserver.h"
 #include "xtoxll.h"
 #include "sflow_api.h"
@@ -71,8 +71,6 @@ struct dst {
     uint16_t dp_ifidx;
 };
 
-extern uint64_t mgmt_id;
-
 struct iface {
     /* These members are always valid. */
     struct port *port;          /* Containing port. */
@@ -86,6 +84,9 @@ struct iface {
     int dp_ifidx;               /* Index within kernel datapath. */
     struct netdev *netdev;      /* Network device. */
     bool enabled;               /* May be chosen for flows? */
+
+    /* This member is only valid *during* bridge_reconfigure(). */
+    const struct ovsrec_interface *cfg;
 };
 
 #define BOND_MASK 0xff
@@ -105,8 +106,8 @@ struct mirror {
     char *name;
 
     /* Selection criteria. */
-    struct svec src_ports;
-    struct svec dst_ports;
+    struct shash src_ports;     /* Name is port name; data is always NULL. */
+    struct shash dst_ports;     /* Name is port name; data is always NULL. */
     int *vlans;
     size_t n_vlans;
 
@@ -135,15 +136,15 @@ struct port {
     tag_type no_ifaces_tag;     /* Tag for flows when all ifaces disabled. */
     int updelay, downdelay;     /* Delay before iface goes up/down, in ms. */
     bool bond_compat_is_stale;  /* Need to call port_update_bond_compat()? */
+    bool bond_fake_iface;       /* Fake a bond interface for legacy compat? */
 
     /* Port mirroring info. */
     mirror_mask_t src_mirrors;  /* Mirrors triggered when packet received. */
     mirror_mask_t dst_mirrors;  /* Mirrors triggered when packet sent. */
     bool is_mirror_output_port; /* Does port mirroring send frames here? */
 
-    /* Spanning tree info. */
-    enum stp_state stp_state;   /* Always STP_FORWARDING if STP not in use. */
-    tag_type stp_state_tag;     /* Tag for STP state change. */
+    /* This member is only valid *during* bridge_reconfigure(). */
+    const struct ovsrec_port *cfg;
 };
 
 #define DP_MAX_PORTS 255
@@ -183,9 +184,8 @@ struct bridge {
     /* Port mirroring. */
     struct mirror *mirrors[MAX_MIRRORS];
 
-    /* Spanning tree. */
-    struct stp *stp;
-    long long int stp_last_tick;
+    /* This member is only valid *during* bridge_reconfigure(). */
+    const struct ovsrec_bridge *cfg;
 };
 
 /* List of all bridges. */
@@ -194,14 +194,16 @@ static struct list all_bridges = LIST_INITIALIZER(&all_bridges);
 /* Maximum number of datapaths. */
 enum { DP_MAX = 256 };
 
-static struct bridge *bridge_create(const char *name);
+static struct bridge *bridge_create(const struct ovsrec_bridge *br_cfg);
 static void bridge_destroy(struct bridge *);
 static struct bridge *bridge_lookup(const char *name);
-static void bridge_unixctl_dump_flows(struct unixctl_conn *, const char *);
+static unixctl_cb_func bridge_unixctl_dump_flows;
 static int bridge_run_one(struct bridge *);
-static void bridge_reconfigure_one(struct bridge *);
-static void bridge_reconfigure_controller(struct bridge *);
-static void bridge_get_all_ifaces(const struct bridge *, struct svec *ifaces);
+static void bridge_reconfigure_one(const struct ovsrec_open_vswitch *,
+                                   struct bridge *);
+static void bridge_reconfigure_controller(const struct ovsrec_open_vswitch *,
+                                          struct bridge *);
+static void bridge_get_all_ifaces(const struct bridge *, struct shash *ifaces);
 static void bridge_fetch_dp_ifaces(struct bridge *);
 static void bridge_flush(struct bridge *);
 static void bridge_pick_local_hw_addr(struct bridge *,
@@ -211,10 +213,9 @@ static uint64_t bridge_pick_datapath_id(struct bridge *,
                                         const uint8_t bridge_ea[ETH_ADDR_LEN],
                                         struct iface *hw_addr_iface);
 static struct iface *bridge_get_local_iface(struct bridge *);
-static const char *bridge_get_controller(const struct bridge *br);
 static uint64_t dpid_from_hash(const void *, size_t nbytes);
 
-static void bridge_unixctl_fdb_show(struct unixctl_conn *, const char *args);
+static unixctl_cb_func bridge_unixctl_fdb_show;
 
 static void bond_init(void);
 static void bond_run(struct bridge *);
@@ -223,8 +224,8 @@ static void bond_rebalance_port(struct port *);
 static void bond_send_learning_packets(struct port *);
 static void bond_enable_slave(struct iface *iface, bool enable);
 
-static void port_create(struct bridge *, const char *name);
-static void port_reconfigure(struct port *);
+static struct port *port_create(struct bridge *, const char *name);
+static void port_reconfigure(struct port *, const struct ovsrec_port *);
 static void port_destroy(struct port *);
 static struct port *port_lookup(const struct bridge *, const char *name);
 static struct iface *port_lookup_iface(const struct port *, const char *name);
@@ -234,18 +235,14 @@ static void port_update_bond_compat(struct port *);
 static void port_update_vlan_compat(struct port *);
 static void port_update_bonding(struct port *);
 
-static void mirror_create(struct bridge *, const char *name);
+static struct mirror *mirror_create(struct bridge *, const char *name);
 static void mirror_destroy(struct mirror *);
 static void mirror_reconfigure(struct bridge *);
-static void mirror_reconfigure_one(struct mirror *);
+static void mirror_reconfigure_one(struct mirror *, struct ovsrec_mirror *);
 static bool vlan_is_mirrored(const struct mirror *, int vlan);
 
-static void brstp_reconfigure(struct bridge *);
-static void brstp_adjust_timers(struct bridge *);
-static void brstp_run(struct bridge *);
-static void brstp_wait(struct bridge *);
-
-static void iface_create(struct port *, const char *name);
+static struct iface *iface_create(struct port *port, 
+                                  const struct ovsrec_interface *if_cfg);
 static void iface_destroy(struct iface *);
 static struct iface *iface_lookup(const struct bridge *, const char *name);
 static struct iface *iface_from_dp_ifidx(const struct bridge *,
@@ -285,53 +282,65 @@ bridge_get_ifaces(struct svec *svec)
     }
 }
 
-/* The caller must already have called cfg_read(). */
 void
-bridge_init(void)
+bridge_init(const struct ovsrec_open_vswitch *cfg)
 {
-    struct svec dpif_names;
+    struct svec bridge_names;
+    struct svec dpif_names, dpif_types;
     size_t i;
 
-    unixctl_command_register("fdb/show", bridge_unixctl_fdb_show);
+    unixctl_command_register("fdb/show", bridge_unixctl_fdb_show, NULL);
+
+    svec_init(&bridge_names);
+    for (i = 0; i < cfg->n_bridges; i++) {
+        svec_add(&bridge_names, cfg->bridges[i]->name);
+    }
+    svec_sort(&bridge_names);
 
     svec_init(&dpif_names);
-    dp_enumerate(&dpif_names);
-    for (i = 0; i < dpif_names.n; i++) {
-        const char *dpif_name = dpif_names.names[i];
+    svec_init(&dpif_types);
+    dp_enumerate_types(&dpif_types);
+    for (i = 0; i < dpif_types.n; i++) {
         struct dpif *dpif;
         int retval;
+        size_t j;
 
-        retval = dpif_open(dpif_name, &dpif);
-        if (!retval) {
-            struct svec all_names;
-            size_t j;
+        dp_enumerate_names(dpif_types.names[i], &dpif_names);
 
-            svec_init(&all_names);
-            dpif_get_all_names(dpif, &all_names);
-            for (j = 0; j < all_names.n; j++) {
-                if (cfg_has("bridge.%s.port", all_names.names[j])) {
-                    goto found;
+        for (j = 0; j < dpif_names.n; j++) {
+            retval = dpif_open(dpif_names.names[j], dpif_types.names[i], &dpif);
+            if (!retval) {
+                struct svec all_names;
+                size_t k;
+
+                svec_init(&all_names);
+                dpif_get_all_names(dpif, &all_names);
+                for (k = 0; k < all_names.n; k++) {
+                    if (svec_contains(&bridge_names, all_names.names[k])) {
+                        goto found;
+                    }
                 }
+                dpif_delete(dpif);
+            found:
+                svec_destroy(&all_names);
+                dpif_close(dpif);
             }
-            dpif_delete(dpif);
-        found:
-            svec_destroy(&all_names);
-            dpif_close(dpif);
         }
     }
     svec_destroy(&dpif_names);
+    svec_destroy(&dpif_types);
 
-    unixctl_command_register("bridge/dump-flows", bridge_unixctl_dump_flows);
+    unixctl_command_register("bridge/dump-flows", bridge_unixctl_dump_flows,
+                             NULL);
 
     bond_init();
-    bridge_reconfigure();
+    bridge_reconfigure(cfg);
 }
 
 #ifdef HAVE_OPENSSL
 static bool
-config_string_change(const char *key, char **valuep)
+config_string_change(const char *value, char **valuep)
 {
-    const char *value = cfg_get_string(0, "%s", key);
     if (value && (!*valuep || strcmp(value, *valuep))) {
         free(*valuep);
         *valuep = xstrdup(value);
@@ -342,7 +351,7 @@ config_string_change(const char *key, char **valuep)
 }
 
 static void
-bridge_configure_ssl(void)
+bridge_configure_ssl(const struct ovsrec_ssl *ssl)
 {
     /* XXX SSL should be configurable on a per-bridge basis.
      * XXX should be possible to de-configure SSL. */
@@ -351,12 +360,17 @@ bridge_configure_ssl(void)
     static char *cacert_file;
     struct stat s;
 
-    if (config_string_change("ssl.private-key", &private_key_file)) {
-        vconn_ssl_set_private_key_file(private_key_file);
+    if (!ssl) {
+        /* XXX We can't un-set SSL settings. */
+        return;
+    }
+
+    if (config_string_change(ssl->private_key, &private_key_file)) {
+        stream_ssl_set_private_key_file(private_key_file);
     }
 
-    if (config_string_change("ssl.certificate", &certificate_file)) {
-        vconn_ssl_set_certificate_file(certificate_file);
+    if (config_string_change(ssl->certificate, &certificate_file)) {
+        stream_ssl_set_certificate_file(certificate_file);
     }
 
     /* We assume that even if the filename hasn't changed, if the CA cert 
@@ -364,10 +378,9 @@ bridge_configure_ssl(void)
      * boot-strapping mode.  This opens a small security hole, because
      * the old certificate will still be trusted until vSwitch is
      * restarted.  We may want to address this in vconn's SSL library. */
-    if (config_string_change("ssl.ca-cert", &cacert_file)
+    if (config_string_change(ssl->ca_cert, &cacert_file)
         || (cacert_file && stat(cacert_file, &s) && errno == ENOENT)) {
-        vconn_ssl_set_ca_cert_file(cacert_file,
-                                   cfg_get_bool(0, "ssl.bootstrap-ca-cert"));
+        stream_ssl_set_ca_cert_file(cacert_file, ssl->bootstrap_ca_cert);
     }
 }
 #endif
@@ -375,85 +388,80 @@ bridge_configure_ssl(void)
 /* Attempt to create the network device 'iface_name' through the netdev
  * library. */
 static int
-set_up_iface(const char *iface_name, bool create) 
+set_up_iface(const struct ovsrec_interface *iface_cfg, struct iface *iface,
+             bool create)
 {
-    const char *type;
-    const char *arg;
-    struct svec arg_svec;
-    struct shash args;
-    int error;
+    struct shash_node *node;
+    struct shash options;
+    int error = 0;
     size_t i;
 
-    /* If a type is not explicitly declared, then assume it's an existing
-     * "system" device. */
-    type = cfg_get_string(0, "iface.%s.type", iface_name);
-    if (!type || !strcmp(type, "system")) {
-        return 0;
+    shash_init(&options);
+    for (i = 0; i < iface_cfg->n_options; i++) {
+        shash_add(&options, iface_cfg->key_options[i],
+                  xstrdup(iface_cfg->value_options[i]));
     }
 
-    svec_init(&arg_svec);
-    cfg_get_subsections(&arg_svec, "iface.%s.args", iface_name);
+    if (create) {
+        struct netdev_options netdev_options;
+
+        memset(&netdev_options, 0, sizeof netdev_options);
+        netdev_options.name = iface_cfg->name;
+        netdev_options.type = iface_cfg->type;
+        netdev_options.args = &options;
+        netdev_options.ethertype = NETDEV_ETH_TYPE_NONE;
+        netdev_options.may_create = true;
+        if (iface_is_internal(iface->port->bridge, iface_cfg->name)) {
+            netdev_options.may_open = true;
+        }
 
-    shash_init(&args);
-    SVEC_FOR_EACH (i, arg, &arg_svec) {
-        const char *value;
+        error = netdev_open(&netdev_options, &iface->netdev);
 
-        value = cfg_get_string(0, "iface.%s.args.%s", iface_name, arg);
-        if (value) {
-            shash_add(&args, arg, xstrdup(value));
+        if (iface->netdev) {
+            netdev_get_carrier(iface->netdev, &iface->enabled);
         }
-    }
+    } else if (iface->netdev) {
+        const char *netdev_type = netdev_get_type(iface->netdev);
+        const char *iface_type = iface_cfg->type && strlen(iface_cfg->type)
+                                  ? iface_cfg->type : NULL;
 
-    if (create) {
-        error = netdev_create(iface_name, type, &args);
-    } else {
-        /* xxx Check to make sure that the type hasn't changed. */
-        error = netdev_reconfigure(iface_name, &args);
+        if (!iface_type || !strcmp(netdev_type, iface_type)) {
+            error = netdev_reconfigure(iface->netdev, &options);
+        } else {
+            VLOG_WARN("%s: attempting change device type from %s to %s",
+                      iface_cfg->name, netdev_type, iface_type);
+            error = EINVAL;
+        }
     }
 
-    svec_destroy(&arg_svec);
-    shash_destroy(&args);
+    SHASH_FOR_EACH (node, &options) {
+        free(node->data);
+    }
+    shash_destroy(&options);
 
     return error;
 }
 
 static int
-create_iface(const char *iface_name)
-{
-    return set_up_iface(iface_name, true);
-}
-
-static int
-reconfigure_iface(const char *iface_name)
+reconfigure_iface(const struct ovsrec_interface *iface_cfg, struct iface *iface)
 {
-    return set_up_iface(iface_name, false);
+    return set_up_iface(iface_cfg, iface, false);
 }
 
-static void
-destroy_iface(const char *iface_name)
-{
-    netdev_destroy(iface_name);
-}
-
-
-/* iterate_and_prune_ifaces() callback function that opens the network device
- * for 'iface', if it is not already open, and retrieves the interface's MAC
- * address and carrier status. */
 static bool
-init_iface_netdev(struct bridge *br UNUSED, struct iface *iface,
-                  void *aux UNUSED)
+check_iface_netdev(struct bridge *br UNUSED, struct iface *iface,
+                   void *aux UNUSED)
 {
-    if (iface->netdev) {
-        return true;
-    } else if (!netdev_open(iface->name, NETDEV_ETH_TYPE_NONE,
-                            &iface->netdev)) {
-        netdev_get_carrier(iface->netdev, &iface->enabled);
-        return true;
-    } else {
-        /* If the network device can't be opened, then we're not going to try
-         * to do anything with this interface. */
-        return false;
+    if (!iface->netdev) {
+        int error = set_up_iface(iface->cfg, iface, true);
+        if (error) {
+            VLOG_WARN("could not open netdev on %s, dropping: %s", iface->name,
+                                                               strerror(error));
+            return false;
+        }
     }
+
+    return true;
 }
 
 static bool
@@ -475,12 +483,10 @@ static bool
 set_iface_properties(struct bridge *br UNUSED, struct iface *iface,
                    void *aux UNUSED)
 {
-    int rate, burst;
-
     /* Set policing attributes. */
-    rate = cfg_get_int(0, "port.%s.ingress.policing-rate", iface->name);
-    burst = cfg_get_int(0, "port.%s.ingress.policing-burst", iface->name);
-    netdev_set_policing(iface->netdev, rate, burst);
+    netdev_set_policing(iface->netdev,
+                        iface->cfg->ingress_policing_rate,
+                        iface->cfg->ingress_policing_burst);
 
     /* Set MAC address of internal interfaces other than the local
      * interface. */
@@ -524,50 +530,67 @@ iterate_and_prune_ifaces(struct bridge *br,
 }
 
 void
-bridge_reconfigure(void)
+bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg)
 {
-    struct svec old_br, new_br;
+    struct ovsdb_idl_txn *txn;
+    struct shash old_br, new_br;
+    struct shash_node *node;
     struct bridge *br, *next;
     size_t i;
     int sflow_bridge_number;
 
     COVERAGE_INC(bridge_reconfigure);
 
+    txn = ovsdb_idl_txn_create(ovs_cfg->header_.table->idl);
+
     /* Collect old and new bridges. */
-    svec_init(&old_br);
-    svec_init(&new_br);
+    shash_init(&old_br);
+    shash_init(&new_br);
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
-        svec_add(&old_br, br->name);
+        shash_add(&old_br, br->name, br);
+    }
+    for (i = 0; i < ovs_cfg->n_bridges; i++) {
+        const struct ovsrec_bridge *br_cfg = ovs_cfg->bridges[i];
+        if (!shash_add_once(&new_br, br_cfg->name, br_cfg)) {
+            VLOG_WARN("more than one bridge named %s", br_cfg->name);
+        }
     }
-    cfg_get_subsections(&new_br, "bridge");
 
     /* Get rid of deleted bridges and add new bridges. */
-    svec_sort(&old_br);
-    svec_sort(&new_br);
-    assert(svec_is_unique(&old_br));
-    assert(svec_is_unique(&new_br));
     LIST_FOR_EACH_SAFE (br, next, struct bridge, node, &all_bridges) {
-        if (!svec_contains(&new_br, br->name)) {
+        struct ovsrec_bridge *br_cfg = shash_find_data(&new_br, br->name);
+        if (br_cfg) {
+            br->cfg = br_cfg;
+        } else {
             bridge_destroy(br);
         }
     }
-    for (i = 0; i < new_br.n; i++) {
-        const char *name = new_br.names[i];
-        if (!svec_contains(&old_br, name)) {
-            bridge_create(name);
+    SHASH_FOR_EACH (node, &new_br) {
+        const char *br_name = node->name;
+        const struct ovsrec_bridge *br_cfg = node->data;
+        br = shash_find_data(&old_br, br_name);
+        if (br) {
+            /* If the bridge datapath type has changed, we need to tear it
+             * down and recreate. */
+            if (strcmp(br->cfg->datapath_type, br_cfg->datapath_type)) {
+                bridge_destroy(br);
+                bridge_create(br_cfg);
+            }
+        } else {
+            bridge_create(br_cfg);
         }
     }
-    svec_destroy(&old_br);
-    svec_destroy(&new_br);
+    shash_destroy(&old_br);
+    shash_destroy(&new_br);
 
 #ifdef HAVE_OPENSSL
     /* Configure SSL. */
-    bridge_configure_ssl();
+    bridge_configure_ssl(ovs_cfg->ssl);
 #endif
 
     /* Reconfigure all bridges. */
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
-        bridge_reconfigure_one(br);
+        bridge_reconfigure_one(ovs_cfg, br);
     }
 
     /* Add and delete ports on all datapaths.
@@ -578,13 +601,13 @@ bridge_reconfigure(void)
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         struct odp_port *dpif_ports;
         size_t n_dpif_ports;
-        struct svec want_ifaces;
+        struct shash want_ifaces;
 
         dpif_port_list(br->dpif, &dpif_ports, &n_dpif_ports);
         bridge_get_all_ifaces(br, &want_ifaces);
         for (i = 0; i < n_dpif_ports; i++) {
             const struct odp_port *p = &dpif_ports[i];
-            if (!svec_contains(&want_ifaces, p->devname)
+            if (!shash_find(&want_ifaces, p->devname)
                 && strcmp(p->devname, br->name)) {
                 int retval = dpif_port_del(br->dpif, p->port);
                 if (retval) {
@@ -592,62 +615,61 @@ bridge_reconfigure(void)
                              p->devname, dpif_name(br->dpif),
                              strerror(retval));
                 }
-                destroy_iface(p->devname);
             }
         }
-        svec_destroy(&want_ifaces);
+        shash_destroy(&want_ifaces);
         free(dpif_ports);
     }
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         struct odp_port *dpif_ports;
         size_t n_dpif_ports;
-        struct svec cur_ifaces, want_ifaces, add_ifaces;
+        struct shash cur_ifaces, want_ifaces;
+        struct shash_node *node;
 
+        /* Get the set of interfaces currently in this datapath. */
         dpif_port_list(br->dpif, &dpif_ports, &n_dpif_ports);
-        svec_init(&cur_ifaces);
+        shash_init(&cur_ifaces);
         for (i = 0; i < n_dpif_ports; i++) {
-            svec_add(&cur_ifaces, dpif_ports[i].devname);
+            const char *name = dpif_ports[i].devname;
+            if (!shash_find(&cur_ifaces, name)) {
+                shash_add(&cur_ifaces, name, NULL);
+            }
         }
         free(dpif_ports);
-        svec_sort_unique(&cur_ifaces);
-        bridge_get_all_ifaces(br, &want_ifaces);
-        svec_diff(&want_ifaces, &cur_ifaces, &add_ifaces, NULL, NULL);
-
-        for (i = 0; i < cur_ifaces.n; i++) {
-            const char *if_name = cur_ifaces.names[i];
-            reconfigure_iface(if_name);
-        }
 
-        for (i = 0; i < add_ifaces.n; i++) {
-            const char *if_name = add_ifaces.names[i];
-            bool internal;
-            int error;
+        /* Get the set of interfaces we want on this datapath. */
+        bridge_get_all_ifaces(br, &want_ifaces);
 
-            /* Attempt to create the network interface in case it
-             * doesn't exist yet. */
-            error = create_iface(if_name);
-            if (error) {
-                VLOG_WARN("could not create iface %s: %s\n", if_name,
-                        strerror(error));
-                continue;
-            }
+        SHASH_FOR_EACH (node, &want_ifaces) {
+            const char *if_name = node->name;
+            struct iface *iface = node->data;
 
-            /* Add to datapath. */
-            internal = iface_is_internal(br, if_name);
-            error = dpif_port_add(br->dpif, if_name,
-                                  internal ? ODP_PORT_INTERNAL : 0, NULL);
-            if (error == EFBIG) {
-                VLOG_ERR("ran out of valid port numbers on %s",
-                         dpif_name(br->dpif));
-                break;
-            } else if (error) {
-                VLOG_ERR("failed to add %s interface to %s: %s",
-                         if_name, dpif_name(br->dpif), strerror(error));
+            if (shash_find(&cur_ifaces, if_name)) {
+                /* Already exists, just reconfigure it. */
+                if (iface) {
+                    reconfigure_iface(iface->cfg, iface);
+                }
+            } else {
+                /* Need to add to datapath. */
+                bool internal;
+                int error;
+
+                /* Add to datapath. */
+                internal = iface_is_internal(br, if_name);
+                error = dpif_port_add(br->dpif, if_name,
+                                      internal ? ODP_PORT_INTERNAL : 0, NULL);
+                if (error == EFBIG) {
+                    VLOG_ERR("ran out of valid port numbers on %s",
+                             dpif_name(br->dpif));
+                    break;
+                } else if (error) {
+                    VLOG_ERR("failed to add %s interface to %s: %s",
+                             if_name, dpif_name(br->dpif), strerror(error));
+                }
             }
         }
-        svec_destroy(&cur_ifaces);
-        svec_destroy(&want_ifaces);
-        svec_destroy(&add_ifaces);
+        shash_destroy(&cur_ifaces);
+        shash_destroy(&want_ifaces);
     }
     sflow_bridge_number = 0;
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
@@ -655,11 +677,11 @@ bridge_reconfigure(void)
         uint64_t dpid;
         struct iface *local_iface;
         struct iface *hw_addr_iface;
-        struct netflow_options nf_options;
+        char *dpid_string;
 
         bridge_fetch_dp_ifaces(br);
-        iterate_and_prune_ifaces(br, init_iface_netdev, NULL);
 
+        iterate_and_prune_ifaces(br, check_iface_netdev, NULL);
         iterate_and_prune_ifaces(br, check_iface_dp_ifidx, NULL);
 
         /* Pick local port hardware address, datapath ID. */
@@ -678,77 +700,91 @@ bridge_reconfigure(void)
         dpid = bridge_pick_datapath_id(br, ea, hw_addr_iface);
         ofproto_set_datapath_id(br->ofproto, dpid);
 
+        dpid_string = xasprintf("%012"PRIx64, dpid);
+        ovsrec_bridge_set_datapath_id(br->cfg, dpid_string);
+        free(dpid_string);
+
         /* Set NetFlow configuration on this bridge. */
-        memset(&nf_options, 0, sizeof nf_options);
-        dpif_get_netflow_ids(br->dpif, &nf_options.engine_type,
-                             &nf_options.engine_id);
-        nf_options.active_timeout = -1;
-
-        if (cfg_has("netflow.%s.engine-type", br->name)) {
-            nf_options.engine_type = cfg_get_int(0, "netflow.%s.engine-type", 
-                    br->name);
-        }
-        if (cfg_has("netflow.%s.engine-id", br->name)) {
-            nf_options.engine_id = cfg_get_int(0, "netflow.%s.engine-id",
-                                               br->name);
-        }
-        if (cfg_has("netflow.%s.active-timeout", br->name)) {
-            nf_options.active_timeout = cfg_get_int(0,
-                                                    "netflow.%s.active-timeout",
-                                                    br->name);
-        }
-        if (cfg_has("netflow.%s.add-id-to-iface", br->name)) {
-            nf_options.add_id_to_iface = cfg_get_bool(0,
-                                                   "netflow.%s.add-id-to-iface",
-                                                    br->name);
-        }
-        if (nf_options.add_id_to_iface && nf_options.engine_id > 0x7f) {
-            VLOG_WARN("bridge %s: netflow port mangling may conflict with "
-                    "another vswitch, choose an engine id less than 128", 
-                    br->name);
-        }
-        if (nf_options.add_id_to_iface && br->n_ports > 508) {
-            VLOG_WARN("bridge %s: netflow port mangling will conflict with "
-                    "another port when more than 508 ports are used", 
-                    br->name);
-        }
-        svec_init(&nf_options.collectors);
-        cfg_get_all_keys(&nf_options.collectors, "netflow.%s.host", br->name);
-        if (ofproto_set_netflow(br->ofproto, &nf_options)) {
-            VLOG_ERR("bridge %s: problem setting netflow collectors", 
-                    br->name);
-        }
-        svec_destroy(&nf_options.collectors);
-
-        if (cfg_has("sflow.%s.host", br->name)) {
+        if (br->cfg->netflow) {
+            struct ovsrec_netflow *nf_cfg = br->cfg->netflow;
+            struct netflow_options opts;
+
+            memset(&opts, 0, sizeof opts);
+
+            dpif_get_netflow_ids(br->dpif, &opts.engine_type, &opts.engine_id);
+            if (nf_cfg->engine_type) {
+                opts.engine_type = *nf_cfg->engine_type;
+            }
+            if (nf_cfg->engine_id) {
+                opts.engine_id = *nf_cfg->engine_id;
+            }
+
+            opts.active_timeout = nf_cfg->active_timeout;
+            if (!opts.active_timeout) {
+                opts.active_timeout = -1;
+            } else if (opts.active_timeout < 0) {
+                VLOG_WARN("bridge %s: active timeout interval set to negative "
+                          "value, using default instead (%d seconds)", br->name,
+                          NF_ACTIVE_TIMEOUT_DEFAULT);
+                opts.active_timeout = -1;
+            }
+
+            opts.add_id_to_iface = nf_cfg->add_id_to_interface;
+            if (opts.add_id_to_iface) {
+                if (opts.engine_id > 0x7f) {
+                    VLOG_WARN("bridge %s: netflow port mangling may conflict "
+                              "with another vswitch, choose an engine id less "
+                              "than 128", br->name);
+                }
+                if (br->n_ports > 508) {
+                    VLOG_WARN("bridge %s: netflow port mangling will conflict "
+                              "with another port when more than 508 ports are "
+                              "used", br->name);
+                }
+            }
+
+            opts.collectors.n = nf_cfg->n_targets;
+            opts.collectors.names = nf_cfg->targets;
+            if (ofproto_set_netflow(br->ofproto, &opts)) {
+                VLOG_ERR("bridge %s: problem setting netflow collectors", 
+                         br->name);
+            }
+        } else {
+            ofproto_set_netflow(br->ofproto, NULL);
+        }
+
+        /* Set sFlow configuration on this bridge. */
+        if (br->cfg->sflow) {
+            struct ovsrec_sflow *sflow_cfg = br->cfg->sflow;
             struct ofproto_sflow_options oso;
 
-            svec_init(&oso.targets);
-            cfg_get_all_keys(&oso.targets, "sflow.%s.host", br->name);
+            memset(&oso, 0, sizeof oso);
+
+            oso.targets.n = sflow_cfg->n_targets;
+            oso.targets.names = sflow_cfg->targets;
 
             oso.sampling_rate = SFL_DEFAULT_SAMPLING_RATE;
-            if (cfg_has("sflow.%s.sampling", br->name)) {
-                oso.sampling_rate = cfg_get_int(0, "sflow.%s.sampling",
-                                                br->name);
+            if (sflow_cfg->sampling) {
+                oso.sampling_rate = *sflow_cfg->sampling;
             }
 
             oso.polling_interval = SFL_DEFAULT_POLLING_INTERVAL;
-            if (cfg_has("sflow.%s.polling", br->name)) {
-                oso.polling_interval = cfg_get_int(0, "sflow.%s.polling",
-                                                   br->name);
+            if (sflow_cfg->polling) {
+                oso.polling_interval = *sflow_cfg->polling;
             }
 
             oso.header_len = SFL_DEFAULT_HEADER_SIZE;
-            if (cfg_has("sflow.%s.header", br->name)) {
-                oso.header_len = cfg_get_int(0, "sflow.%s.header", br->name);
+            if (sflow_cfg->header) {
+                oso.header_len = *sflow_cfg->header;
             }
 
             oso.sub_id = sflow_bridge_number++;
-            oso.agent_device = (char *) cfg_get_string(0, "sflow.%s.agent",
-                                                       br->name);
-            oso.control_ip = (char *) cfg_get_string(0,
-                                                     "bridge.%s.controller.ip",
-                                                     br->name);
+            oso.agent_device = sflow_cfg->agent;
+
+#if 0       /* xxx foo */
+            ctrl = bridge_get_controller(ovs_cfg, br);
+            oso.control_ip = ctrl ? ctrl->local_ip : NULL;
+#endif
             ofproto_set_sflow(br->ofproto, &oso);
 
             svec_destroy(&oso.targets);
@@ -765,7 +801,7 @@ bridge_reconfigure(void)
          * yet; when a controller is configured, resetting the datapath ID will
          * immediately disconnect from the controller, so it's better to set
          * the datapath ID before the controller. */
-        bridge_reconfigure_controller(br);
+        bridge_reconfigure_controller(ovs_cfg, br);
     }
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         for (i = 0; i < br->n_ports; i++) {
@@ -776,25 +812,41 @@ bridge_reconfigure(void)
         }
     }
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
-        brstp_reconfigure(br);
         iterate_and_prune_ifaces(br, set_iface_properties, NULL);
     }
+
+    ovsrec_open_vswitch_set_cur_cfg(ovs_cfg, ovs_cfg->next_cfg);
+
+    ovsdb_idl_txn_commit(txn);
+    ovsdb_idl_txn_destroy(txn); /* XXX */
+}
+
+static const char *
+bridge_get_other_config(const struct ovsrec_bridge *br_cfg, const char *key)
+{
+    size_t i;
+
+    for (i = 0; i < br_cfg->n_other_config; i++) {
+        if (!strcmp(br_cfg->key_other_config[i], key)) {
+            return br_cfg->value_other_config[i];
+        }
+    }
+    return NULL;
 }
 
 static void
 bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
                           struct iface **hw_addr_iface)
 {
-    uint64_t requested_ea;
+    const char *hwaddr;
     size_t i, j;
     int error;
 
     *hw_addr_iface = NULL;
 
     /* Did the user request a particular MAC? */
-    requested_ea = cfg_get_mac(0, "bridge.%s.mac", br->name);
-    if (requested_ea) {
-        eth_addr_from_uint64(requested_ea, ea);
+    hwaddr = bridge_get_other_config(br->cfg, "hwaddr");
+    if (hwaddr && eth_addr_from_string(hwaddr, ea)) {
         if (eth_addr_is_multicast(ea)) {
             VLOG_ERR("bridge %s: cannot set MAC address to multicast "
                      "address "ETH_ADDR_FMT, br->name, ETH_ADDR_ARGS(ea));
@@ -805,14 +857,12 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
         }
     }
 
-    /* Otherwise choose the minimum MAC address among all of the interfaces.
-     * (Xen uses FE:FF:FF:FF:FF:FF for virtual interfaces so this will get the
-     * MAC of the physical interface in such an environment.) */
+    /* Otherwise choose the minimum non-local MAC address among all of the
+     * interfaces. */
     memset(ea, 0xff, sizeof ea);
     for (i = 0; i < br->n_ports; i++) {
         struct port *port = br->ports[i];
         uint8_t iface_ea[ETH_ADDR_LEN];
-        uint64_t iface_ea_u64;
         struct iface *iface;
 
         /* Mirror output ports don't participate. */
@@ -821,11 +871,7 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
         }
 
         /* Choose the MAC address to represent the port. */
-        iface_ea_u64 = cfg_get_mac(0, "port.%s.mac", port->name);
-        if (iface_ea_u64) {
-            /* User specified explicitly. */
-            eth_addr_from_uint64(iface_ea_u64, iface_ea);
-
+        if (port->cfg->mac && eth_addr_from_string(port->cfg->mac, iface_ea)) {
             /* Find the interface with this Ethernet address (if any) so that
              * we can provide the correct devname to the caller. */
             iface = NULL;
@@ -853,11 +899,8 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
             }
 
             /* The local port doesn't count (since we're trying to choose its
-             * MAC address anyway).  Other internal ports don't count because
-             * we really want a physical MAC if we can get it, and internal
-             * ports typically have randomly generated MACs. */
-            if (iface->dp_ifidx == ODPP_LOCAL
-                || cfg_get_bool(0, "iface.%s.internal", iface->name)) {
+             * MAC address anyway). */
+            if (iface->dp_ifidx == ODPP_LOCAL) {
                 continue;
             }
 
@@ -873,6 +916,7 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
 
         /* Compare against our current choice. */
         if (!eth_addr_is_multicast(iface_ea) &&
+            !eth_addr_is_local(iface_ea) &&
             !eth_addr_is_reserved(iface_ea) &&
             !eth_addr_is_zero(iface_ea) &&
             memcmp(iface_ea, ea, ETH_ADDR_LEN) < 0)
@@ -881,7 +925,7 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN],
             *hw_addr_iface = iface;
         }
     }
-    if (eth_addr_is_multicast(ea) || eth_addr_is_vif(ea)) {
+    if (eth_addr_is_multicast(ea)) {
         memcpy(ea, br->default_ea, ETH_ADDR_LEN);
         *hw_addr_iface = NULL;
         VLOG_WARN("bridge %s: using default bridge Ethernet "
@@ -914,10 +958,11 @@ bridge_pick_datapath_id(struct bridge *br,
      * stable from one run to the next, so that policy set on a datapath
      * "sticks".
      */
+    const char *datapath_id;
     uint64_t dpid;
 
-    dpid = cfg_get_dpid(0, "bridge.%s.datapath-id", br->name);
-    if (dpid) {
+    datapath_id = bridge_get_other_config(br->cfg, "datapath-id");
+    if (datapath_id && dpid_from_string(datapath_id, &dpid)) {
         return dpid;
     }
 
@@ -1016,7 +1061,6 @@ bridge_wait(void)
 
         mac_learning_wait(br->ml);
         bond_wait(br);
-        brstp_wait(br);
     }
 }
 
@@ -1052,7 +1096,8 @@ bridge_get_local_iface(struct bridge *br)
 \f
 /* Bridge unixctl user interface functions. */
 static void
-bridge_unixctl_fdb_show(struct unixctl_conn *conn, const char *args)
+bridge_unixctl_fdb_show(struct unixctl_conn *conn,
+                        const char *args, void *aux UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     const struct bridge *br;
@@ -1078,33 +1123,36 @@ bridge_unixctl_fdb_show(struct unixctl_conn *conn, const char *args)
 }
 \f
 /* Bridge reconfiguration functions. */
-
 static struct bridge *
-bridge_create(const char *name)
+bridge_create(const struct ovsrec_bridge *br_cfg)
 {
     struct bridge *br;
     int error;
 
-    assert(!bridge_lookup(name));
-    br = xcalloc(1, sizeof *br);
+    assert(!bridge_lookup(br_cfg->name));
+    br = xzalloc(sizeof *br);
 
-    error = dpif_create_and_open(name, &br->dpif);
+    error = dpif_create_and_open(br_cfg->name, br_cfg->datapath_type,
+                                 &br->dpif);
     if (error) {
         free(br);
         return NULL;
     }
     dpif_flow_flush(br->dpif);
 
-    error = ofproto_create(name, &bridge_ofhooks, br, &br->ofproto);
+    error = ofproto_create(br_cfg->name, br_cfg->datapath_type, &bridge_ofhooks,
+                           br, &br->ofproto);
     if (error) {
-        VLOG_ERR("failed to create switch %s: %s", name, strerror(error));
+        VLOG_ERR("failed to create switch %s: %s", br_cfg->name,
+                 strerror(error));
         dpif_delete(br->dpif);
         dpif_close(br->dpif);
         free(br);
         return NULL;
     }
 
-    br->name = xstrdup(name);
+    br->name = xstrdup(br_cfg->name);
+    br->cfg = br_cfg;
     br->ml = mac_learning_create();
     br->sent_config_request = false;
     eth_addr_random(br->default_ea);
@@ -1176,7 +1224,8 @@ bridge_get_datapathid(const char *name)
 /* Handle requests for a listing of all flows known by the OpenFlow
  * stack, including those normally hidden. */
 static void
-bridge_unixctl_dump_flows(struct unixctl_conn *conn, const char *args)
+bridge_unixctl_dump_flows(struct unixctl_conn *conn,
+                          const char *args, void *aux UNUSED)
 {
     struct bridge *br;
     struct ds results;
@@ -1206,7 +1255,6 @@ bridge_run_one(struct bridge *br)
 
     mac_learning_run(br->ml, ofproto_get_revalidate_set(br->ofproto));
     bond_run(br);
-    brstp_run(br);
 
     error = ofproto_run2(br->ofproto, br->flush);
     br->flush = false;
@@ -1214,16 +1262,21 @@ bridge_run_one(struct bridge *br)
     return error;
 }
 
-static const char *
-bridge_get_controller(const struct bridge *br)
+static const struct ovsrec_controller *
+bridge_get_controller(const struct ovsrec_open_vswitch *ovs_cfg,
+                      const struct bridge *br)
 {
-    const char *controller;
+    const struct ovsrec_controller *controller;
+
+    controller = (br->cfg->controller ? br->cfg->controller
+                  : ovs_cfg->controller ? ovs_cfg->controller
+                  : NULL);
 
-    controller = cfg_get_string(0, "bridge.%s.controller", br->name);
-    if (!controller) {
-        controller = cfg_get_string(0, "mgmt.controller");
+    if (controller && !strcmp(controller->target, "none")) {
+        return NULL;
     }
-    return controller && controller[0] ? controller : NULL;
+
+    return controller;
 }
 
 static bool
@@ -1243,66 +1296,64 @@ check_duplicate_ifaces(struct bridge *br, struct iface *iface, void *ifaces_)
 }
 
 static void
-bridge_reconfigure_one(struct bridge *br)
+bridge_reconfigure_one(const struct ovsrec_open_vswitch *ovs_cfg,
+                       struct bridge *br)
 {
-    struct svec old_ports, new_ports, ifaces;
+    struct shash old_ports, new_ports;
+    struct svec ifaces;
     struct svec listeners, old_listeners;
     struct svec snoops, old_snoops;
+    struct shash_node *node;
     size_t i;
 
     /* Collect old ports. */
-    svec_init(&old_ports);
+    shash_init(&old_ports);
     for (i = 0; i < br->n_ports; i++) {
-        svec_add(&old_ports, br->ports[i]->name);
+        shash_add(&old_ports, br->ports[i]->name, br->ports[i]);
     }
-    svec_sort(&old_ports);
-    assert(svec_is_unique(&old_ports));
 
     /* Collect new ports. */
-    svec_init(&new_ports);
-    cfg_get_all_keys(&new_ports, "bridge.%s.port", br->name);
-    svec_sort(&new_ports);
-    if (bridge_get_controller(br)) {
+    shash_init(&new_ports);
+    for (i = 0; i < br->cfg->n_ports; i++) {
+        const char *name = br->cfg->ports[i]->name;
+        if (!shash_add_once(&new_ports, name, br->cfg->ports[i])) {
+            VLOG_WARN("bridge %s: %s specified twice as bridge port",
+                      br->name, name);
+        }
+    }
+
+    /* If we have a controller, then we need a local port.  Complain if the
+     * user didn't specify one.
+     *
+     * XXX perhaps we should synthesize a port ourselves in this case. */
+    if (bridge_get_controller(ovs_cfg, br)) {
         char local_name[IF_NAMESIZE];
         int error;
 
         error = dpif_port_get_name(br->dpif, ODPP_LOCAL,
                                    local_name, sizeof local_name);
-        if (!error && !svec_contains(&new_ports, local_name)) {
-            svec_add(&new_ports, local_name);
-            svec_sort(&new_ports);
+        if (!error && !shash_find(&new_ports, local_name)) {
+            VLOG_WARN("bridge %s: controller specified but no local port "
+                      "(port named %s) defined",
+                      br->name, local_name);
         }
     }
-    if (!svec_is_unique(&new_ports)) {
-        VLOG_WARN("bridge %s: %s specified twice as bridge port",
-                  br->name, svec_get_duplicate(&new_ports));
-        svec_unique(&new_ports);
-    }
-
-    ofproto_set_mgmt_id(br->ofproto, mgmt_id);
 
     /* Get rid of deleted ports and add new ports. */
-    for (i = 0; i < br->n_ports; ) {
-        struct port *port = br->ports[i];
-        if (!svec_contains(&new_ports, port->name)) {
-            port_destroy(port);
-        } else {
-            i++;
+    SHASH_FOR_EACH (node, &old_ports) {
+        if (!shash_find(&new_ports, node->name)) {
+            port_destroy(node->data);
         }
     }
-    for (i = 0; i < new_ports.n; i++) {
-        const char *name = new_ports.names[i];
-        if (!svec_contains(&old_ports, name)) {
-            port_create(br, name);
+    SHASH_FOR_EACH (node, &new_ports) {
+        struct port *port = shash_find_data(&old_ports, node->name);
+        if (!port) {
+            port = port_create(br, node->name);
         }
+        port_reconfigure(port, node->data);
     }
-    svec_destroy(&old_ports);
-    svec_destroy(&new_ports);
-
-    /* Reconfigure all ports. */
-    for (i = 0; i < br->n_ports; i++) {
-        port_reconfigure(br->ports[i]);
-    }
+    shash_destroy(&old_ports);
+    shash_destroy(&new_ports);
 
     /* Check and delete duplicate interfaces. */
     svec_init(&ifaces);
@@ -1313,6 +1364,7 @@ bridge_reconfigure_one(struct bridge *br)
      * versa.  (XXX Should we delete all flows if we are switching from one
      * controller to another?) */
 
+#if 0
     /* Configure OpenFlow management listeners. */
     svec_init(&listeners);
     cfg_get_all_strings(&listeners, "bridge.%s.openflow.listeners", br->name);
@@ -1354,56 +1406,79 @@ bridge_reconfigure_one(struct bridge *br)
     }
     svec_destroy(&snoops);
     svec_destroy(&old_snoops);
+#else
+    /* Default listener. */
+    svec_init(&listeners);
+    svec_add_nocopy(&listeners, xasprintf("punix:%s/%s.mgmt",
+                                          ovs_rundir, br->name));
+    svec_init(&old_listeners);
+    ofproto_get_listeners(br->ofproto, &old_listeners);
+    if (!svec_equal(&listeners, &old_listeners)) {
+        ofproto_set_listeners(br->ofproto, &listeners);
+    }
+    svec_destroy(&listeners);
+    svec_destroy(&old_listeners);
+
+    /* Default snoop. */
+    svec_init(&snoops);
+    svec_add_nocopy(&snoops, xasprintf("punix:%s/%s.snoop",
+                                       ovs_rundir, br->name));
+    svec_init(&old_snoops);
+    ofproto_get_snoops(br->ofproto, &old_snoops);
+    if (!svec_equal(&snoops, &old_snoops)) {
+        ofproto_set_snoops(br->ofproto, &snoops);
+    }
+    svec_destroy(&snoops);
+    svec_destroy(&old_snoops);
+#endif
 
     mirror_reconfigure(br);
 }
 
 static void
-bridge_reconfigure_controller(struct bridge *br)
+bridge_reconfigure_controller(const struct ovsrec_open_vswitch *ovs_cfg,
+                              struct bridge *br)
 {
     char *pfx = xasprintf("bridge.%s.controller", br->name);
-    const char *controller;
+    const struct ovsrec_controller *c;
 
-    controller = bridge_get_controller(br);
-    if ((br->controller != NULL) != (controller != NULL)) {
+    c = bridge_get_controller(ovs_cfg, br);
+    if ((br->controller != NULL) != (c != NULL)) {
         ofproto_flush_flows(br->ofproto);
     }
     free(br->controller);
-    br->controller = controller ? xstrdup(controller) : NULL;
+    br->controller = c ? xstrdup(c->target) : NULL;
 
-    if (controller) {
-        const char *fail_mode;
+    if (c) {
         int max_backoff, probe;
         int rate_limit, burst_limit;
 
-        if (!strcmp(controller, "discover")) {
-            bool update_resolv_conf = true;
-
-            if (cfg_has("%s.update-resolv.conf", pfx)) {
-                update_resolv_conf = cfg_get_bool(0, "%s.update-resolv.conf",
-                        pfx);
-            }
+        if (!strcmp(c->target, "discover")) {
             ofproto_set_discovery(br->ofproto, true,
-                                  cfg_get_string(0, "%s.accept-regex", pfx),
-                                  update_resolv_conf);
+                                  c->discover_accept_regex,
+                                  c->discover_update_resolv_conf);
         } else {
             struct iface *local_iface;
+            struct in_addr ip;
             bool in_band;
 
-            in_band = (!cfg_is_valid(CFG_BOOL | CFG_REQUIRED,
-                                     "%s.in-band", pfx)
-                       || cfg_get_bool(0, "%s.in-band", pfx));
+            in_band = (!c->connection_mode
+                       || !strcmp(c->connection_mode, "out-of-band"));
             ofproto_set_discovery(br->ofproto, false, NULL, NULL);
             ofproto_set_in_band(br->ofproto, in_band);
 
             local_iface = bridge_get_local_iface(br);
-            if (local_iface
-                && cfg_is_valid(CFG_IP | CFG_REQUIRED, "%s.ip", pfx)) {
+            if (local_iface && c->local_ip && inet_aton(c->local_ip, &ip)) {
                 struct netdev *netdev = local_iface->netdev;
                 struct in_addr ip, mask, gateway;
-                ip.s_addr = cfg_get_ip(0, "%s.ip", pfx);
-                mask.s_addr = cfg_get_ip(0, "%s.netmask", pfx);
-                gateway.s_addr = cfg_get_ip(0, "%s.gateway", pfx);
+
+                if (!c->local_netmask || !inet_aton(c->local_netmask, &mask)) {
+                    mask.s_addr = 0;
+                }
+                if (!c->local_gateway
+                    || !inet_aton(c->local_gateway, &gateway)) {
+                    gateway.s_addr = 0;
+                }
 
                 netdev_turn_flags_on(netdev, NETDEV_UP, true);
                 if (!mask.s_addr) {
@@ -1425,62 +1500,20 @@ bridge_reconfigure_controller(struct bridge *br)
             }
         }
 
-        fail_mode = cfg_get_string(0, "%s.fail-mode", pfx);
-        if (!fail_mode) {
-            fail_mode = cfg_get_string(0, "mgmt.fail-mode");
-        }
         ofproto_set_failure(br->ofproto,
-                            (!fail_mode
-                             || !strcmp(fail_mode, "standalone")
-                             || !strcmp(fail_mode, "open")));
-
-        probe = cfg_get_int(0, "%s.inactivity-probe", pfx);
-        if (probe < 5) {
-            probe = cfg_get_int(0, "mgmt.inactivity-probe");
-            if (probe < 5) {
-                probe = 5;
-            }
-        }
+                            (!c->fail_mode
+                             || !strcmp(c->fail_mode, "standalone")
+                             || !strcmp(c->fail_mode, "open")));
+
+        probe = c->inactivity_probe ? *c->inactivity_probe / 1000 : 5;
         ofproto_set_probe_interval(br->ofproto, probe);
 
-        max_backoff = cfg_get_int(0, "%s.max-backoff", pfx);
-        if (!max_backoff) {
-            max_backoff = cfg_get_int(0, "mgmt.max-backoff");
-            if (!max_backoff) {
-                max_backoff = 8;
-            }
-        }
+        max_backoff = c->max_backoff ? *c->max_backoff / 1000 : 8;
         ofproto_set_max_backoff(br->ofproto, max_backoff);
 
-        rate_limit = cfg_get_int(0, "%s.rate-limit", pfx);
-        if (!rate_limit) {
-            rate_limit = cfg_get_int(0, "mgmt.rate-limit");
-        }
-        burst_limit = cfg_get_int(0, "%s.burst-limit", pfx);
-        if (!burst_limit) {
-            burst_limit = cfg_get_int(0, "mgmt.burst-limit");
-        }
+        rate_limit = c->controller_rate_limit ? *c->controller_rate_limit : 0;
+        burst_limit = c->controller_burst_limit ? *c->controller_burst_limit : 0;
         ofproto_set_rate_limit(br->ofproto, rate_limit, burst_limit);
-
-        ofproto_set_stp(br->ofproto, cfg_get_bool(0, "%s.stp", pfx));
-
-        if (cfg_has("%s.commands.acl", pfx)) {
-            struct svec command_acls;
-            char *command_acl;
-
-            svec_init(&command_acls);
-            cfg_get_all_strings(&command_acls, "%s.commands.acl", pfx);
-            command_acl = svec_join(&command_acls, ",", "");
-
-            ofproto_set_remote_execution(br->ofproto, command_acl,
-                                         cfg_get_string(0, "%s.commands.dir",
-                                                        pfx));
-
-            svec_destroy(&command_acls);
-            free(command_acl);
-        } else {
-            ofproto_set_remote_execution(br->ofproto, NULL, NULL);
-        }
     } else {
         union ofp_action action;
         flow_t flow;
@@ -1499,7 +1532,6 @@ bridge_reconfigure_controller(struct bridge *br)
         ofproto_set_max_backoff(br->ofproto, 1);
         ofproto_set_probe_interval(br->ofproto, 5);
         ofproto_set_failure(br->ofproto, false);
-        ofproto_set_stp(br->ofproto, false);
     }
     free(pfx);
 
@@ -1507,23 +1539,21 @@ bridge_reconfigure_controller(struct bridge *br)
 }
 
 static void
-bridge_get_all_ifaces(const struct bridge *br, struct svec *ifaces)
+bridge_get_all_ifaces(const struct bridge *br, struct shash *ifaces)
 {
     size_t i, j;
 
-    svec_init(ifaces);
+    shash_init(ifaces);
     for (i = 0; i < br->n_ports; i++) {
         struct port *port = br->ports[i];
         for (j = 0; j < port->n_ifaces; j++) {
             struct iface *iface = port->ifaces[j];
-            svec_add(ifaces, iface->name);
+            shash_add_once(ifaces, iface->name, iface);
         }
-        if (port->n_ifaces > 1
-            && cfg_get_bool(0, "bonding.%s.fake-iface", port->name)) {
-            svec_add(ifaces, port->name);
+        if (port->n_ifaces > 1 && port->cfg->bond_fake_iface) {
+            shash_add_once(ifaces, port->name, NULL);
         }
     }
-    svec_sort_unique(ifaces);
 }
 
 /* For robustness, in case the administrator moves around datapath ports behind
@@ -1566,6 +1596,13 @@ bridge_fetch_dp_ifaces(struct bridge *br)
                 port_array_set(&br->ifaces, p->port, iface);
                 iface->dp_ifidx = p->port;
             }
+
+            if (iface->cfg) {
+                int64_t ofport = (iface->dp_ifidx >= 0
+                                  ? odp_port_to_ofp_port(iface->dp_ifidx)
+                                  : -1);
+                ovsrec_interface_set_ofport(iface->cfg, &ofport, 1);
+            }
         }
     }
     free(dpif_ports);
@@ -1795,18 +1832,6 @@ set_dst(struct dst *p, const flow_t *flow,
         const struct port *in_port, const struct port *out_port,
         tag_type *tags)
 {
-    /* STP handling.
-     *
-     * XXX This uses too many tags: any broadcast flow will get one tag per
-     * destination port, and thus a broadcast on a switch of any size is likely
-     * to have all tag bits set.  We should figure out a way to be smarter.
-     *
-     * This is OK when STP is disabled, because stp_state_tag is 0 then. */
-    *tags |= out_port->stp_state_tag;
-    if (!(out_port->stp_state & (STP_DISABLED | STP_FORWARDING))) {
-        return false;
-    }
-
     p->vlan = (out_port->vlan >= 0 ? OFP_VLAN_NONE
               : in_port->vlan >= 0 ? in_port->vlan
               : ntohs(flow->dl_vlan));
@@ -1899,7 +1924,6 @@ compose_dsts(const struct bridge *br, const flow_t *flow, uint16_t vlan,
     struct dst *dst = dsts;
     size_t i;
 
-    *tags |= in_port->stp_state_tag;
     if (out_port == FLOOD_PORT) {
         /* XXX use ODP_FLOOD if no vlans or bonding. */
         /* XXX even better, define each VLAN as a datapath port group */
@@ -2128,13 +2152,6 @@ process_flow(struct bridge *br, const flow_t *flow,
         goto done;
     }
 
-    /* Drop frames for ports that STP wants entirely killed (both for
-     * forwarding and for learning).  Later, after we do learning, we'll drop
-     * the frames that STP wants to do learning but not forwarding on. */
-    if (in_port->stp_state & (STP_LISTENING | STP_BLOCKING)) {
-        goto done;
-    }
-
     /* Drop frames for reserved multicast addresses. */
     if (eth_addr_is_reserved(flow->dl_dst)) {
         goto done;
@@ -2193,9 +2210,8 @@ process_flow(struct bridge *br, const flow_t *flow,
         return false;
     }
 
-    /* Don't send packets out their input ports.  Don't forward frames that STP
-     * wants us to discard. */
-    if (in_port == out_port || in_port->stp_state == STP_LEARNING) {
+    /* Don't send packets out their input ports. */
+    if (in_port == out_port) {
         out_port = NULL;
     }
 
@@ -2250,14 +2266,6 @@ bridge_normal_ofhook_cb(const flow_t *flow, const struct ofpbuf *packet,
 {
     struct bridge *br = br_;
 
-#if 0
-    if (flow->dl_type == htons(OFP_DL_TYPE_NOT_ETH_TYPE)
-        && eth_addr_equals(flow->dl_dst, stp_eth_addr)) {
-        brstp_receive(br, flow, payload);
-        return true;
-    }
-#endif
-
     COVERAGE_INC(bridge_process_flow);
     return process_flow(br, flow, packet, actions, tags, nf_output_iface);
 }
@@ -2698,7 +2706,8 @@ bond_send_learning_packets(struct port *port)
 /* Bonding unixctl user interface functions. */
 
 static void
-bond_unixctl_list(struct unixctl_conn *conn, const char *args UNUSED)
+bond_unixctl_list(struct unixctl_conn *conn,
+                  const char *args UNUSED, void *aux UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     const struct bridge *br;
@@ -2748,7 +2757,8 @@ bond_find(const char *name)
 }
 
 static void
-bond_unixctl_show(struct unixctl_conn *conn, const char *args)
+bond_unixctl_show(struct unixctl_conn *conn,
+                  const char *args, void *aux UNUSED)
 {
     struct ds ds = DS_EMPTY_INITIALIZER;
     const struct port *port;
@@ -2813,7 +2823,8 @@ bond_unixctl_show(struct unixctl_conn *conn, const char *args)
 }
 
 static void
-bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_)
+bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_,
+                     void *aux UNUSED)
 {
     char *args = (char *) args_;
     char *save_ptr = NULL;
@@ -2869,7 +2880,8 @@ bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_)
 }
 
 static void
-bond_unixctl_set_active_slave(struct unixctl_conn *conn, const char *args_)
+bond_unixctl_set_active_slave(struct unixctl_conn *conn, const char *args_,
+                              void *aux UNUSED)
 {
     char *args = (char *) args_;
     char *save_ptr = NULL;
@@ -2949,19 +2961,22 @@ enable_slave(struct unixctl_conn *conn, const char *args_, bool enable)
 }
 
 static void
-bond_unixctl_enable_slave(struct unixctl_conn *conn, const char *args)
+bond_unixctl_enable_slave(struct unixctl_conn *conn, const char *args,
+                          void *aux UNUSED)
 {
     enable_slave(conn, args, true);
 }
 
 static void
-bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args)
+bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args,
+                           void *aux UNUSED)
 {
     enable_slave(conn, args, false);
 }
 
 static void
-bond_unixctl_hash(struct unixctl_conn *conn, const char *args)
+bond_unixctl_hash(struct unixctl_conn *conn, const char *args,
+                  void *aux UNUSED)
 {
        uint8_t mac[ETH_ADDR_LEN];
        uint8_t hash;
@@ -2982,32 +2997,32 @@ bond_unixctl_hash(struct unixctl_conn *conn, const char *args)
 static void
 bond_init(void)
 {
-    unixctl_command_register("bond/list", bond_unixctl_list);
-    unixctl_command_register("bond/show", bond_unixctl_show);
-    unixctl_command_register("bond/migrate", bond_unixctl_migrate);
+    unixctl_command_register("bond/list", bond_unixctl_list, NULL);
+    unixctl_command_register("bond/show", bond_unixctl_show, NULL);
+    unixctl_command_register("bond/migrate", bond_unixctl_migrate, NULL);
     unixctl_command_register("bond/set-active-slave",
-                             bond_unixctl_set_active_slave);
-    unixctl_command_register("bond/enable-slave", bond_unixctl_enable_slave);
-    unixctl_command_register("bond/disable-slave", bond_unixctl_disable_slave);
-    unixctl_command_register("bond/hash", bond_unixctl_hash);
+                             bond_unixctl_set_active_slave, NULL);
+    unixctl_command_register("bond/enable-slave", bond_unixctl_enable_slave,
+                             NULL);
+    unixctl_command_register("bond/disable-slave", bond_unixctl_disable_slave,
+                             NULL);
+    unixctl_command_register("bond/hash", bond_unixctl_hash, NULL);
 }
 \f
 /* Port functions. */
 
-static void
+static struct port *
 port_create(struct bridge *br, const char *name)
 {
     struct port *port;
 
-    port = xcalloc(1, sizeof *port);
+    port = xzalloc(sizeof *port);
     port->bridge = br;
     port->port_idx = br->n_ports;
     port->vlan = -1;
     port->trunks = NULL;
     port->name = xstrdup(name);
     port->active_iface = -1;
-    port->stp_state = STP_DISABLED;
-    port->stp_state_tag = 0;
 
     if (br->n_ports >= br->allocated_ports) {
         br->ports = x2nrealloc(br->ports, &br->allocated_ports,
@@ -3017,70 +3032,70 @@ port_create(struct bridge *br, const char *name)
 
     VLOG_INFO("created port %s on bridge %s", port->name, br->name);
     bridge_flush(br);
+
+    return port;
 }
 
 static void
-port_reconfigure(struct port *port)
+port_reconfigure(struct port *port, const struct ovsrec_port *cfg)
 {
-    bool bonded = cfg_has_section("bonding.%s", port->name);
-    struct svec old_ifaces, new_ifaces;
+    struct shash old_ifaces, new_ifaces;
+    struct shash_node *node;
     unsigned long *trunks;
     int vlan;
     size_t i;
 
+    port->cfg = cfg;
+
     /* Collect old and new interfaces. */
-    svec_init(&old_ifaces);
-    svec_init(&new_ifaces);
+    shash_init(&old_ifaces);
+    shash_init(&new_ifaces);
     for (i = 0; i < port->n_ifaces; i++) {
-        svec_add(&old_ifaces, port->ifaces[i]->name);
+        shash_add(&old_ifaces, port->ifaces[i]->name, port->ifaces[i]);
     }
-    svec_sort(&old_ifaces);
-    if (bonded) {
-        cfg_get_all_keys(&new_ifaces, "bonding.%s.slave", port->name);
-        if (!new_ifaces.n) {
-            VLOG_ERR("port %s: no interfaces specified for bonded port",
-                     port->name);
-        } else if (new_ifaces.n == 1) {
-            VLOG_WARN("port %s: only 1 interface specified for bonded port",
-                      port->name);
-        }
-
-        port->updelay = cfg_get_int(0, "bonding.%s.updelay", port->name);
-        if (port->updelay < 0) {
-            port->updelay = 0;
+    for (i = 0; i < cfg->n_interfaces; i++) {
+        const char *name = cfg->interfaces[i]->name;
+        if (!shash_add_once(&new_ifaces, name, cfg->interfaces[i])) {
+            VLOG_WARN("port %s: %s specified twice as port interface",
+                      port->name, name);
         }
-        port->downdelay = cfg_get_int(0, "bonding.%s.downdelay", port->name);
-        if (port->downdelay < 0) {
-            port->downdelay = 0;
-        }
-    } else {
-        svec_init(&new_ifaces);
-        svec_add(&new_ifaces, port->name);
+    }
+    port->updelay = cfg->bond_updelay;
+    if (port->updelay < 0) {
+        port->updelay = 0;
+    }
+    port->updelay = cfg->bond_downdelay;
+    if (port->downdelay < 0) {
+        port->downdelay = 0;
     }
 
     /* Get rid of deleted interfaces and add new interfaces. */
-    for (i = 0; i < port->n_ifaces; i++) {
-        struct iface *iface = port->ifaces[i];
-        if (!svec_contains(&new_ifaces, iface->name)) {
-            iface_destroy(iface);
-        } else {
-            i++;
+    SHASH_FOR_EACH (node, &old_ifaces) {
+        if (!shash_find(&new_ifaces, node->name)) {
+            iface_destroy(node->data);
         }
     }
-    for (i = 0; i < new_ifaces.n; i++) {
-        const char *name = new_ifaces.names[i];
-        if (!svec_contains(&old_ifaces, name)) {
-            iface_create(port, name);
+    SHASH_FOR_EACH (node, &new_ifaces) {
+        const struct ovsrec_interface *if_cfg = node->data;
+        struct iface *iface;
+
+        iface = shash_find_data(&old_ifaces, if_cfg->name);
+        if (!iface) {
+            iface = iface_create(port, if_cfg);
+        } else {
+            iface->cfg = if_cfg;
         }
     }
 
     /* Get VLAN tag. */
     vlan = -1;
-    if (cfg_has("vlan.%s.tag", port->name)) {
-        if (!bonded) {
-            vlan = cfg_get_vlan(0, "vlan.%s.tag", port->name);
+    if (cfg->tag) {
+        if (port->n_ifaces < 2) {
+            vlan = *cfg->tag;
             if (vlan >= 0 && vlan <= 4095) {
                 VLOG_DBG("port %s: assigning VLAN tag %d", port->name, vlan);
+            } else {
+                vlan = -1;
             }
         } else {
             /* It's possible that bonded, VLAN-tagged ports make sense.  Maybe
@@ -3097,14 +3112,13 @@ port_reconfigure(struct port *port)
     /* Get trunked VLANs. */
     trunks = NULL;
     if (vlan < 0) {
-        size_t n_trunks, n_errors;
+        size_t n_errors;
         size_t i;
 
         trunks = bitmap_allocate(4096);
-        n_trunks = cfg_count("vlan.%s.trunks", port->name);
         n_errors = 0;
-        for (i = 0; i < n_trunks; i++) {
-            int trunk = cfg_get_vlan(i, "vlan.%s.trunks", port->name);
+        for (i = 0; i < cfg->n_trunks; i++) {
+            int trunk = cfg->trunks[i];
             if (trunk >= 0) {
                 bitmap_set1(trunks, trunk);
             } else {
@@ -3113,9 +3127,9 @@ port_reconfigure(struct port *port)
         }
         if (n_errors) {
             VLOG_ERR("port %s: invalid values for %zu trunk VLANs",
-                     port->name, n_trunks);
+                     port->name, cfg->n_trunks);
         }
-        if (n_errors == n_trunks) {
+        if (n_errors == cfg->n_trunks) {
             if (n_errors) {
                 VLOG_ERR("port %s: no valid trunks, trunking all VLANs",
                          port->name);
@@ -3123,9 +3137,9 @@ port_reconfigure(struct port *port)
             bitmap_set_multiple(trunks, 0, 4096, 1);
         }
     } else {
-        if (cfg_has("vlan.%s.trunks", port->name)) {
-            VLOG_ERR("ignoring vlan.%s.trunks in favor of vlan.%s.vlan",
-                     port->name, port->name);
+        if (cfg->n_trunks) {
+            VLOG_ERR("port %s: ignoring trunks in favor of implicit vlan",
+                     port->name);
         }
     }
     if (trunks == NULL
@@ -3136,8 +3150,8 @@ port_reconfigure(struct port *port)
     bitmap_free(port->trunks);
     port->trunks = trunks;
 
-    svec_destroy(&old_ifaces);
-    svec_destroy(&new_ifaces);
+    shash_destroy(&old_ifaces);
+    shash_destroy(&new_ifaces);
 }
 
 static void
@@ -3146,7 +3160,7 @@ port_destroy(struct port *port)
     if (port) {
         struct bridge *br = port->bridge;
         struct port *del;
-        size_t i;
+        int i;
 
         proc_net_compat_update_vlan(port->name, NULL, 0);
         proc_net_compat_update_bond(port->name, NULL);
@@ -3217,6 +3231,7 @@ port_update_bonding(struct port *port)
             free(port->bond_hash);
             port->bond_hash = NULL;
             port->bond_compat_is_stale = true;
+            port->bond_fake_iface = false;
         }
     } else {
         if (!port->bond_hash) {
@@ -3232,6 +3247,7 @@ port_update_bonding(struct port *port)
             bond_choose_active_iface(port);
         }
         port->bond_compat_is_stale = true;
+        port->bond_fake_iface = port->cfg->bond_fake_iface;
     }
 }
 
@@ -3291,10 +3307,10 @@ port_update_bond_compat(struct port *port)
         netdev_get_etheraddr(iface->netdev, slave->mac);
     }
 
-    if (cfg_get_bool(0, "bonding.%s.fake-iface", port->name)) {
+    if (port->bond_fake_iface) {
         struct netdev *bond_netdev;
 
-        if (!netdev_open(port->name, NETDEV_ETH_TYPE_NONE, &bond_netdev)) {
+        if (!netdev_open_default(port->name, &bond_netdev)) {
             if (bond.up) {
                 netdev_turn_flags_on(bond_netdev, NETDEV_UP, true);
             } else {
@@ -3347,12 +3363,14 @@ port_update_vlan_compat(struct port *port)
 \f
 /* Interface functions. */
 
-static void
-iface_create(struct port *port, const char *name)
+static struct iface *
+iface_create(struct port *port, const struct ovsrec_interface *if_cfg)
 {
     struct iface *iface;
+    char *name = if_cfg->name;
+    int error;
 
-    iface = xcalloc(1, sizeof *iface);
+    iface = xzalloc(sizeof *iface);
     iface->port = port;
     iface->port_ifidx = port->n_ifaces;
     iface->name = xstrdup(name);
@@ -3360,6 +3378,7 @@ iface_create(struct port *port, const char *name)
     iface->tag = tag_create_random();
     iface->delay_expires = LLONG_MAX;
     iface->netdev = NULL;
+    iface->cfg = if_cfg;
 
     if (port->n_ifaces >= port->allocated_ifaces) {
         port->ifaces = x2nrealloc(port->ifaces, &port->allocated_ifaces,
@@ -3370,9 +3389,21 @@ iface_create(struct port *port, const char *name)
         port->bridge->has_bonded_ports = true;
     }
 
+    /* Attempt to create the network interface in case it
+     * doesn't exist yet. */
+    if (!iface_is_internal(port->bridge, iface->name)) {
+        error = set_up_iface(if_cfg, iface, true);
+        if (error) {
+            VLOG_WARN("could not create iface %s: %s", iface->name,
+                    strerror(error));
+        }
+    }
+
     VLOG_DBG("attached network device %s to port %s", iface->name, port->name);
 
     bridge_flush(port->bridge);
+
+    return iface;
 }
 
 static void
@@ -3392,8 +3423,6 @@ iface_destroy(struct iface *iface)
         del->port_ifidx = iface->port_ifidx;
 
         netdev_close(iface->netdev);
-        free(iface->name);
-        free(iface);
 
         if (del_active) {
             ofproto_revalidate(port->bridge->ofproto, port->active_iface_tag);
@@ -3401,6 +3430,9 @@ iface_destroy(struct iface *iface)
             bond_send_learning_packets(port);
         }
 
+        free(iface->name);
+        free(iface);
+
         bridge_flush(port->bridge);
     }
 }
@@ -3439,20 +3471,25 @@ iface_from_dp_ifidx(const struct bridge *br, uint16_t dp_ifidx)
  * reason why this function takes a name instead of a struct iface: the fake
  * interfaces created this way do not have a struct iface. */
 static bool
-iface_is_internal(const struct bridge *br, const char *iface)
+iface_is_internal(const struct bridge *br, const char *if_name)
 {
-    if (!strcmp(iface, br->name)
-        || cfg_get_bool(0, "iface.%s.internal", iface)) {
+    /* XXX wastes time */
+    struct iface *iface;
+    struct port *port;
+
+    if (!strcmp(if_name, br->name)) {
         return true;
     }
 
-    if (cfg_get_bool(0, "bonding.%s.fake-iface", iface)) {
-        struct port *port = port_lookup(br, iface);
-        if (port && port->n_ifaces > 1) {
-            return true;
-        }
+    iface = iface_lookup(br, if_name);
+    if (iface && !strcmp(iface->cfg->type, "internal")) {
+        return true;
     }
 
+    port = port_lookup(br, if_name);
+    if (port && port->n_ifaces > 1 && port->cfg->bond_fake_iface) {
+        return true;
+    }
     return false;
 }
 
@@ -3461,11 +3498,9 @@ iface_is_internal(const struct bridge *br, const char *iface)
 static void
 iface_set_mac(struct iface *iface)
 {
-    uint64_t mac = cfg_get_mac(0, "iface.%s.mac", iface->name);
-    if (mac) {
-        static uint8_t ea[ETH_ADDR_LEN];
+    uint8_t ea[ETH_ADDR_LEN];
 
-        eth_addr_from_uint64(mac, ea);
+    if (iface->cfg->mac && eth_addr_from_string(iface->cfg->mac, ea)) {
         if (eth_addr_is_multicast(ea)) {
             VLOG_ERR("interface %s: cannot set MAC to multicast address",
                      iface->name);
@@ -3487,46 +3522,47 @@ iface_set_mac(struct iface *iface)
 static void
 mirror_reconfigure(struct bridge *br)
 {
-    struct svec old_mirrors, new_mirrors;
-    size_t i, n_rspan_vlans;
+    struct shash old_mirrors, new_mirrors;
+    struct shash_node *node;
     unsigned long *rspan_vlans;
+    int i;
 
-    /* Collect old and new mirrors. */
-    svec_init(&old_mirrors);
-    svec_init(&new_mirrors);
-    cfg_get_subsections(&new_mirrors, "mirror.%s", br->name);
+    /* Collect old mirrors. */
+    shash_init(&old_mirrors);
     for (i = 0; i < MAX_MIRRORS; i++) {
         if (br->mirrors[i]) {
-            svec_add(&old_mirrors, br->mirrors[i]->name);
+            shash_add(&old_mirrors, br->mirrors[i]->name, br->mirrors[i]);
         }
     }
 
-    /* Get rid of deleted mirrors and add new mirrors. */
-    svec_sort(&old_mirrors);
-    assert(svec_is_unique(&old_mirrors));
-    svec_sort(&new_mirrors);
-    assert(svec_is_unique(&new_mirrors));
-    for (i = 0; i < MAX_MIRRORS; i++) {
-        struct mirror *m = br->mirrors[i];
-        if (m && !svec_contains(&new_mirrors, m->name)) {
-            mirror_destroy(m);
+    /* Collect new mirrors. */
+    shash_init(&new_mirrors);
+    for (i = 0; i < br->cfg->n_mirrors; i++) {
+        struct ovsrec_mirror *cfg = br->cfg->mirrors[i];
+        if (!shash_add_once(&new_mirrors, cfg->name, cfg)) {
+            VLOG_WARN("bridge %s: %s specified twice as mirror",
+                      br->name, cfg->name);
         }
     }
-    for (i = 0; i < new_mirrors.n; i++) {
-        const char *name = new_mirrors.names[i];
-        if (!svec_contains(&old_mirrors, name)) {
-            mirror_create(br, name);
+
+    /* Get rid of deleted mirrors and add new mirrors. */
+    SHASH_FOR_EACH (node, &old_mirrors) {
+        if (!shash_find(&new_mirrors, node->name)) {
+            mirror_destroy(node->data);
         }
     }
-    svec_destroy(&old_mirrors);
-    svec_destroy(&new_mirrors);
-
-    /* Reconfigure all mirrors. */
-    for (i = 0; i < MAX_MIRRORS; i++) {
-        if (br->mirrors[i]) {
-            mirror_reconfigure_one(br->mirrors[i]);
+    SHASH_FOR_EACH (node, &new_mirrors) {
+        struct mirror *mirror = shash_find_data(&old_mirrors, node->name);
+        if (!mirror) {
+            mirror = mirror_create(br, node->name);
+            if (!mirror) {
+                break;
+            }
         }
+        mirror_reconfigure_one(mirror, node->data);
     }
+    shash_destroy(&old_mirrors);
+    shash_destroy(&new_mirrors);
 
     /* Update port reserved status. */
     for (i = 0; i < br->n_ports; i++) {
@@ -3539,31 +3575,29 @@ mirror_reconfigure(struct bridge *br)
         }
     }
 
-    /* Update learning disabled vlans (for RSPAN). */
+    /* Update flooded vlans (for RSPAN). */
     rspan_vlans = NULL;
-    n_rspan_vlans = cfg_count("vlan.%s.disable-learning", br->name);
-    if (n_rspan_vlans) {
+    if (br->cfg->n_flood_vlans) {
         rspan_vlans = bitmap_allocate(4096);
 
-        for (i = 0; i < n_rspan_vlans; i++) {
-            int vlan = cfg_get_vlan(i, "vlan.%s.disable-learning", br->name);
-            if (vlan >= 0) {
+        for (i = 0; i < br->cfg->n_flood_vlans; i++) {
+            int64_t vlan = br->cfg->flood_vlans[i];
+            if (vlan >= 0 && vlan < 4096) {
                 bitmap_set1(rspan_vlans, vlan);
-                VLOG_INFO("bridge %s: disabling learning on vlan %d\n",
+                VLOG_INFO("bridge %s: disabling learning on vlan %"PRId64,
                           br->name, vlan);
             } else {
-                VLOG_ERR("bridge %s: invalid value '%s' for learning disabled "
-                         "VLAN", br->name,
-                       cfg_get_string(i, "vlan.%s.disable-learning", br->name));
+                VLOG_ERR("bridge %s: invalid value %"PRId64 "for flood VLAN",
+                         br->name, vlan);
             }
         }
     }
-    if (mac_learning_set_disabled_vlans(br->ml, rspan_vlans)) {
+    if (mac_learning_set_flood_vlans(br->ml, rspan_vlans)) {
         bridge_flush(br);
     }
 }
 
-static void
+static struct mirror *
 mirror_create(struct bridge *br, const char *name)
 {
     struct mirror *m;
@@ -3573,7 +3607,7 @@ mirror_create(struct bridge *br, const char *name)
         if (i >= MAX_MIRRORS) {
             VLOG_WARN("bridge %s: maximum of %d port mirrors reached, "
                       "cannot create %s", br->name, MAX_MIRRORS, name);
-            return;
+            return NULL;
         }
         if (!br->mirrors[i]) {
             break;
@@ -3583,16 +3617,18 @@ mirror_create(struct bridge *br, const char *name)
     VLOG_INFO("created port mirror %s on bridge %s", name, br->name);
     bridge_flush(br);
 
-    br->mirrors[i] = m = xcalloc(1, sizeof *m);
+    br->mirrors[i] = m = xzalloc(sizeof *m);
     m->bridge = br;
     m->idx = i;
     m->name = xstrdup(name);
-    svec_init(&m->src_ports);
-    svec_init(&m->dst_ports);
+    shash_init(&m->src_ports);
+    shash_init(&m->dst_ports);
     m->vlans = NULL;
     m->n_vlans = 0;
     m->out_vlan = -1;
     m->out_port = NULL;
+
+    return m;
 }
 
 static void
@@ -3607,8 +3643,8 @@ mirror_destroy(struct mirror *m)
             br->ports[i]->dst_mirrors &= ~(MIRROR_MASK_C(1) << m->idx);
         }
 
-        svec_destroy(&m->src_ports);
-        svec_destroy(&m->dst_ports);
+        shash_destroy(&m->src_ports);
+        shash_destroy(&m->dst_ports);
         free(m->vlans);
 
         m->bridge->mirrors[m->idx] = NULL;
@@ -3619,45 +3655,36 @@ mirror_destroy(struct mirror *m)
 }
 
 static void
-prune_ports(struct mirror *m, struct svec *ports)
+mirror_collect_ports(struct mirror *m, struct ovsrec_port **ports, int n_ports,
+                     struct shash *names)
 {
-    struct svec tmp;
     size_t i;
 
-    svec_sort_unique(ports);
-
-    svec_init(&tmp);
-    for (i = 0; i < ports->n; i++) {
-        const char *name = ports->names[i];
+    for (i = 0; i < n_ports; i++) {
+        const char *name = ports[i]->name;
         if (port_lookup(m->bridge, name)) {
-            svec_add(&tmp, name);
+            shash_add_once(names, name, NULL);
         } else {
-            VLOG_WARN("mirror.%s.%s: cannot match on nonexistent port %s",
-                      m->bridge->name, m->name, name);
+            VLOG_WARN("bridge %s: mirror %s cannot match on nonexistent "
+                      "port %s", m->bridge->name, m->name, name);
         }
     }
-    svec_swap(ports, &tmp);
-    svec_destroy(&tmp);
 }
 
 static size_t
-prune_vlans(struct mirror *m, struct svec *vlan_strings, int **vlans)
+mirror_collect_vlans(struct mirror *m, const struct ovsrec_mirror *cfg,
+                     int **vlans)
 {
-    size_t n_vlans, i;
-
-    /* This isn't perfect: it won't combine "0" and "00", and the textual sort
-     * order won't give us numeric sort order.  But that's good enough for what
-     * we need right now. */
-    svec_sort_unique(vlan_strings);
+    size_t n_vlans;
+    size_t i;
 
-    *vlans = xmalloc(sizeof *vlans * vlan_strings->n);
+    *vlans = xmalloc(sizeof *vlans * cfg->n_select_vlan);
     n_vlans = 0;
-    for (i = 0; i < vlan_strings->n; i++) {
-        const char *name = vlan_strings->names[i];
-        int vlan;
-        if (!str_to_int(name, 10, &vlan) || vlan < 0 || vlan > 4095) {
-            VLOG_WARN("mirror.%s.%s.select.vlan: ignoring invalid VLAN %s",
-                      m->bridge->name, m->name, name);
+    for (i = 0; i < cfg->n_select_vlan; i++) {
+        int64_t vlan = cfg->select_vlan[i];
+        if (vlan < 0 || vlan > 4095) {
+            VLOG_WARN("bridge %s: mirror %s selects invalid VLAN %"PRId64,
+                      m->bridge->name, m->name, vlan);
         } else {
             (*vlans)[n_vlans++] = vlan;
         }
@@ -3692,13 +3719,10 @@ port_trunks_any_mirrored_vlan(const struct mirror *m, const struct port *p)
 }
 
 static void
-mirror_reconfigure_one(struct mirror *m)
+mirror_reconfigure_one(struct mirror *m, struct ovsrec_mirror *cfg)
 {
-    char *pfx = xasprintf("mirror.%s.%s", m->bridge->name, m->name);
-    struct svec src_ports, dst_ports, ports;
-    struct svec vlan_strings;
+    struct shash src_ports, dst_ports;
     mirror_mask_t mirror_bit;
-    const char *out_port_name;
     struct port *out_port;
     int out_vlan;
     size_t n_vlans;
@@ -3706,74 +3730,71 @@ mirror_reconfigure_one(struct mirror *m)
     size_t i;
     bool mirror_all_ports;
     bool any_ports_specified;
+    bool any_vlans_specified;
 
     /* Get output port. */
-    out_port_name = cfg_get_key(0, "mirror.%s.%s.output.port",
-                                m->bridge->name, m->name);
-    if (out_port_name) {
-        out_port = port_lookup(m->bridge, out_port_name);
+    if (cfg->output_port) {
+        out_port = port_lookup(m->bridge, cfg->output_port->name);
         if (!out_port) {
-            VLOG_ERR("%s.output.port: bridge %s does not have a port "
-                      "named %s", pfx, m->bridge->name, out_port_name);
+            VLOG_ERR("bridge %s: mirror %s outputs to port not on bridge",
+                     m->bridge->name, m->name);
             mirror_destroy(m);
-            free(pfx);
             return;
         }
         out_vlan = -1;
 
-        if (cfg_has("%s.output.vlan", pfx)) {
-            VLOG_ERR("%s.output.port and %s.output.vlan both specified; "
-                     "ignoring %s.output.vlan", pfx, pfx, pfx);
+        if (cfg->output_vlan) {
+            VLOG_ERR("bridge %s: mirror %s specifies both output port and "
+                     "output vlan; ignoring output vlan",
+                     m->bridge->name, m->name);
         }
-    } else if (cfg_has("%s.output.vlan", pfx)) {
+    } else if (cfg->output_vlan) {
         out_port = NULL;
-        out_vlan = cfg_get_vlan(0, "%s.output.vlan", pfx);
+        out_vlan = *cfg->output_vlan;
     } else {
-        VLOG_ERR("%s: neither %s.output.port nor %s.output.vlan specified, "
-                 "but exactly one is required; disabling port mirror %s",
-                 pfx, pfx, pfx, pfx);
+        VLOG_ERR("bridge %s: mirror %s does not specify output; ignoring",
+                 m->bridge->name, m->name);
         mirror_destroy(m);
-        free(pfx);
         return;
     }
 
     /* Get all the ports, and drop duplicates and ports that don't exist. */
-    svec_init(&src_ports);
-    svec_init(&dst_ports);
-    svec_init(&ports);
-    cfg_get_all_keys(&src_ports, "%s.select.src-port", pfx);
-    cfg_get_all_keys(&dst_ports, "%s.select.dst-port", pfx);
-    cfg_get_all_keys(&ports, "%s.select.port", pfx);
-    any_ports_specified = src_ports.n || dst_ports.n || ports.n;
-    svec_append(&src_ports, &ports);
-    svec_append(&dst_ports, &ports);
-    svec_destroy(&ports);
-    prune_ports(m, &src_ports);
-    prune_ports(m, &dst_ports);
-    if (any_ports_specified && !src_ports.n && !dst_ports.n) {
-        VLOG_ERR("%s: none of the specified ports exist; "
-                 "disabling port mirror %s", pfx, pfx);
+    shash_init(&src_ports);
+    shash_init(&dst_ports);
+    mirror_collect_ports(m, cfg->select_src_port, cfg->n_select_src_port,
+                         &src_ports);
+    mirror_collect_ports(m, cfg->select_dst_port, cfg->n_select_dst_port,
+                         &dst_ports);
+    any_ports_specified = cfg->n_select_dst_port || cfg->n_select_dst_port;
+    if (any_ports_specified
+        && shash_is_empty(&src_ports) && shash_is_empty(&dst_ports)) {
+        VLOG_ERR("bridge %s: disabling mirror %s since none of the specified "
+                 "selection ports exists", m->bridge->name, m->name);
         mirror_destroy(m);
         goto exit;
     }
 
     /* Get all the vlans, and drop duplicate and invalid vlans. */
-    svec_init(&vlan_strings);
-    cfg_get_all_keys(&vlan_strings, "%s.select.vlan", pfx);
-    n_vlans = prune_vlans(m, &vlan_strings, &vlans);
-    svec_destroy(&vlan_strings);
+    n_vlans = mirror_collect_vlans(m, cfg, &vlans);
+    any_vlans_specified = cfg->n_select_vlan > 0;
+    if (any_vlans_specified && !n_vlans) {
+        VLOG_ERR("bridge %s: disabling mirror %s since none of the specified "
+                 "VLANs exists", m->bridge->name, m->name);
+        mirror_destroy(m);
+        goto exit;
+    }
 
     /* Update mirror data. */
-    if (!svec_equal(&m->src_ports, &src_ports)
-        || !svec_equal(&m->dst_ports, &dst_ports)
+    if (!shash_equal_keys(&m->src_ports, &src_ports)
+        || !shash_equal_keys(&m->dst_ports, &dst_ports)
         || m->n_vlans != n_vlans
         || memcmp(m->vlans, vlans, sizeof *vlans * n_vlans)
         || m->out_port != out_port
         || m->out_vlan != out_vlan) {
         bridge_flush(m->bridge);
     }
-    svec_swap(&m->src_ports, &src_ports);
-    svec_swap(&m->dst_ports, &dst_ports);
+    shash_swap(&m->src_ports, &src_ports);
+    shash_swap(&m->dst_ports, &dst_ports);
     free(m->vlans);
     m->vlans = vlans;
     m->n_vlans = n_vlans;
@@ -3781,7 +3802,7 @@ mirror_reconfigure_one(struct mirror *m)
     m->out_vlan = out_vlan;
 
     /* If no selection criteria have been given, mirror for all ports. */
-    mirror_all_ports = (!m->src_ports.n) && (!m->dst_ports.n) && (!m->n_vlans);
+    mirror_all_ports = !any_ports_specified && !any_vlans_specified;
 
     /* Update ports. */
     mirror_bit = MIRROR_MASK_C(1) << m->idx;
@@ -3789,7 +3810,7 @@ mirror_reconfigure_one(struct mirror *m)
         struct port *port = m->bridge->ports[i];
 
         if (mirror_all_ports
-            || svec_contains(&m->src_ports, port->name)
+            || shash_find(&m->src_ports, port->name)
             || (m->n_vlans
                 && (!port->vlan
                     ? port_trunks_any_mirrored_vlan(m, port)
@@ -3799,7 +3820,7 @@ mirror_reconfigure_one(struct mirror *m)
             port->src_mirrors &= ~mirror_bit;
         }
 
-        if (mirror_all_ports || svec_contains(&m->dst_ports, port->name)) {
+        if (mirror_all_ports || shash_find(&m->dst_ports, port->name)) {
             port->dst_mirrors |= mirror_bit;
         } else {
             port->dst_mirrors &= ~mirror_bit;
@@ -3808,216 +3829,6 @@ mirror_reconfigure_one(struct mirror *m)
 
     /* Clean up. */
 exit:
-    svec_destroy(&src_ports);
-    svec_destroy(&dst_ports);
-    free(pfx);
-}
-\f
-/* Spanning tree protocol. */
-
-static void brstp_update_port_state(struct port *);
-
-static void
-brstp_send_bpdu(struct ofpbuf *pkt, int port_no, void *br_)
-{
-    struct bridge *br = br_;
-    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
-    struct iface *iface = iface_from_dp_ifidx(br, port_no);
-    if (!iface) {
-        VLOG_WARN_RL(&rl, "%s: cannot send BPDU on unknown port %d",
-                     br->name, port_no);
-    } else {
-        struct eth_header *eth = pkt->l2;
-
-        netdev_get_etheraddr(iface->netdev, eth->eth_src);
-        if (eth_addr_is_zero(eth->eth_src)) {
-            VLOG_WARN_RL(&rl, "%s: cannot send BPDU on port %d "
-                         "with unknown MAC", br->name, port_no);
-        } else {
-            union ofp_action action;
-            flow_t flow;
-
-            memset(&action, 0, sizeof action);
-            action.type = htons(OFPAT_OUTPUT);
-            action.output.len = htons(sizeof action);
-            action.output.port = htons(port_no);
-
-            flow_extract(pkt, ODPP_NONE, &flow);
-            ofproto_send_packet(br->ofproto, &flow, &action, 1, pkt);
-        }
-    }
-    ofpbuf_delete(pkt);
-}
-
-static void
-brstp_reconfigure(struct bridge *br)
-{
-    size_t i;
-
-    if (!cfg_get_bool(0, "stp.%s.enabled", br->name)) {
-        if (br->stp) {
-            stp_destroy(br->stp);
-            br->stp = NULL;
-
-            bridge_flush(br);
-        }
-    } else {
-        uint64_t bridge_address, bridge_id;
-        int bridge_priority;
-
-        bridge_address = cfg_get_mac(0, "stp.%s.address", br->name);
-        if (!bridge_address) {
-            if (br->stp) {
-                bridge_address = (stp_get_bridge_id(br->stp)
-                                  & ((UINT64_C(1) << 48) - 1));
-            } else {
-                uint8_t mac[ETH_ADDR_LEN];
-                eth_addr_random(mac);
-                bridge_address = eth_addr_to_uint64(mac);
-            }
-        }
-
-        if (cfg_is_valid(CFG_INT | CFG_REQUIRED, "stp.%s.priority",
-                         br->name)) {
-            bridge_priority = cfg_get_int(0, "stp.%s.priority", br->name);
-        } else {
-            bridge_priority = STP_DEFAULT_BRIDGE_PRIORITY;
-        }
-
-        bridge_id = bridge_address | ((uint64_t) bridge_priority << 48);
-        if (!br->stp) {
-            br->stp = stp_create(br->name, bridge_id, brstp_send_bpdu, br);
-            br->stp_last_tick = time_msec();
-            bridge_flush(br);
-        } else {
-            if (bridge_id != stp_get_bridge_id(br->stp)) {
-                stp_set_bridge_id(br->stp, bridge_id);
-                bridge_flush(br);
-            }
-        }
-
-        for (i = 0; i < br->n_ports; i++) {
-            struct port *p = br->ports[i];
-            int dp_ifidx;
-            struct stp_port *sp;
-            int path_cost, priority;
-            bool enable;
-
-            if (!p->n_ifaces) {
-                continue;
-            }
-            dp_ifidx = p->ifaces[0]->dp_ifidx;
-            if (dp_ifidx < 0 || dp_ifidx >= STP_MAX_PORTS) {
-                continue;
-            }
-
-            sp = stp_get_port(br->stp, dp_ifidx);
-            enable = (!cfg_is_valid(CFG_BOOL | CFG_REQUIRED,
-                                    "stp.%s.port.%s.enabled",
-                                    br->name, p->name)
-                      || cfg_get_bool(0, "stp.%s.port.%s.enabled",
-                                      br->name, p->name));
-            if (p->is_mirror_output_port) {
-                enable = false;
-            }
-            if (enable != (stp_port_get_state(sp) != STP_DISABLED)) {
-                bridge_flush(br); /* Might not be necessary. */
-                if (enable) {
-                    stp_port_enable(sp);
-                } else {
-                    stp_port_disable(sp);
-                }
-            }
-
-            path_cost = cfg_get_int(0, "stp.%s.port.%s.path-cost",
-                                    br->name, p->name);
-            stp_port_set_path_cost(sp, path_cost ? path_cost : 19 /* XXX */);
-
-            priority = (cfg_is_valid(CFG_INT | CFG_REQUIRED,
-                                     "stp.%s.port.%s.priority",
-                                     br->name, p->name)
-                        ? cfg_get_int(0, "stp.%s.port.%s.priority",
-                                      br->name, p->name)
-                        : STP_DEFAULT_PORT_PRIORITY);
-            stp_port_set_priority(sp, priority);
-        }
-
-        brstp_adjust_timers(br);
-    }
-    for (i = 0; i < br->n_ports; i++) {
-        brstp_update_port_state(br->ports[i]);
-    }
-}
-
-static void
-brstp_update_port_state(struct port *p)
-{
-    struct bridge *br = p->bridge;
-    enum stp_state state;
-
-    /* Figure out new state. */
-    state = STP_DISABLED;
-    if (br->stp && p->n_ifaces > 0) {
-        int dp_ifidx = p->ifaces[0]->dp_ifidx;
-        if (dp_ifidx >= 0 && dp_ifidx < STP_MAX_PORTS) {
-            state = stp_port_get_state(stp_get_port(br->stp, dp_ifidx));
-        }
-    }
-
-    /* Update state. */
-    if (p->stp_state != state) {
-        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 10);
-        VLOG_INFO_RL(&rl, "port %s: STP state changed from %s to %s",
-                     p->name, stp_state_name(p->stp_state),
-                     stp_state_name(state));
-        if (p->stp_state == STP_DISABLED) {
-            bridge_flush(br);
-        } else {
-            ofproto_revalidate(p->bridge->ofproto, p->stp_state_tag);
-        }
-        p->stp_state = state;
-        p->stp_state_tag = (p->stp_state == STP_DISABLED ? 0
-                            : tag_create_random());
-    }
-}
-
-static void
-brstp_adjust_timers(struct bridge *br)
-{
-    int hello_time = cfg_get_int(0, "stp.%s.hello-time", br->name);
-    int max_age = cfg_get_int(0, "stp.%s.max-age", br->name);
-    int forward_delay = cfg_get_int(0, "stp.%s.forward-delay", br->name);
-
-    stp_set_hello_time(br->stp, hello_time ? hello_time : 2000);
-    stp_set_max_age(br->stp, max_age ? max_age : 20000);
-    stp_set_forward_delay(br->stp, forward_delay ? forward_delay : 15000);
-}
-
-static void
-brstp_run(struct bridge *br)
-{
-    if (br->stp) {
-        long long int now = time_msec();
-        long long int elapsed = now - br->stp_last_tick;
-        struct stp_port *sp;
-
-        if (elapsed > 0) {
-            stp_tick(br->stp, MIN(INT_MAX, elapsed));
-            br->stp_last_tick = now;
-        }
-        while (stp_get_changed_port(br->stp, &sp)) {
-            struct port *p = port_from_dp_ifidx(br, stp_port_no(sp));
-            if (p) {
-                brstp_update_port_state(p);
-            }
-        }
-    }
-}
-
-static void
-brstp_wait(struct bridge *br)
-{
-    if (br->stp) {
-        poll_timer_wait(1000);
-    }
+    shash_destroy(&src_ports);
+    shash_destroy(&dst_ports);
 }
index a7250b2..3d3770d 100644 (file)
 #include <stddef.h>
 #include "list.h"
 
+struct ovsrec_open_vswitch;
 struct svec;
 
-void bridge_init(void);
-void bridge_reconfigure(void);
+void bridge_init(const struct ovsrec_open_vswitch *);
+void bridge_reconfigure(const struct ovsrec_open_vswitch *);
 int bridge_run(void);
 void bridge_wait(void);
 bool bridge_exists(const char *);
diff --git a/vswitchd/mgmt.c b/vswitchd/mgmt.c
deleted file mode 100644 (file)
index 8da640f..0000000
+++ /dev/null
@@ -1,909 +0,0 @@
-/* Copyright (c) 2009 Nicira Networks
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <config.h>
-
-#include <arpa/inet.h>
-#include <assert.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-
-#include "bridge.h"
-#include "cfg.h"
-#include "coverage.h"
-#include "list.h"
-#include "mgmt.h"
-#include "openflow/nicira-ext.h"
-#include "openflow/openflow.h"
-#include "openflow/openflow-mgmt.h"
-#include "ofpbuf.h"
-#include "ovs-vswitchd.h"
-#include "packets.h"
-#include "rconn.h"
-#include "svec.h"
-#include "vconn.h"
-#include "vconn-ssl.h"
-#include "xenserver.h"
-#include "xtoxll.h"
-
-#define THIS_MODULE VLM_mgmt
-#include "vlog.h"
-
-#define MAX_BACKOFF_DEFAULT 15
-#define INACTIVITY_PROBE_DEFAULT 15
-
-static struct svec mgmt_cfg;
-static uint8_t cfg_cookie[CFG_COOKIE_LEN];
-static bool need_reconfigure = false;
-static struct rconn *mgmt_rconn;
-static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60);
-static struct svec capabilities;
-static struct ofpbuf ext_data_buffer;
-static uint32_t ext_data_xid = UINT32_MAX;
-uint64_t mgmt_id;
-
-
-#define TXQ_LIMIT 128         /* Max number of packets to queue for tx. */
-struct rconn_packet_counter *txqlen; /* # pkts queued for tx on mgmt_rconn. */
-
-static uint64_t pick_fallback_mgmt_id(void);
-static void send_config_update(uint32_t xid, bool use_xid);
-static void send_resources_update(uint32_t xid, bool use_xid);
-static int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph, size_t len);
-
-void
-mgmt_init(void)
-{
-    txqlen = rconn_packet_counter_create();
-
-    svec_init(&mgmt_cfg);
-    svec_init(&capabilities);
-    svec_add_nocopy(&capabilities, 
-            xasprintf("com.nicira.mgmt.manager=true\n"));
-
-    mgmt_id = cfg_get_dpid(0, "mgmt.id");
-    if (!mgmt_id) {
-        /* Randomly generate a mgmt id */
-        mgmt_id = pick_fallback_mgmt_id();
-    }
-
-    ofpbuf_init(&ext_data_buffer, 0);
-}
-
-#ifdef HAVE_OPENSSL
-static bool
-config_string_change(const char *key, char **valuep)
-{
-    const char *value = cfg_get_string(0, "%s", key);
-    if (value && (!*valuep || strcmp(value, *valuep))) {
-        free(*valuep);
-        *valuep = xstrdup(value);
-        return true;
-    } else {
-        return false;
-    }
-}
-
-static void
-mgmt_configure_ssl(void)
-{
-    static char *private_key_file;
-    static char *certificate_file;
-    static char *cacert_file;
-    struct stat s;
-
-    /* XXX SSL should be configurable separate from the bridges.
-     * XXX should be possible to de-configure SSL. */
-    if (config_string_change("ssl.private-key", &private_key_file)) {
-        vconn_ssl_set_private_key_file(private_key_file);
-    }
-
-    if (config_string_change("ssl.certificate", &certificate_file)) {
-        vconn_ssl_set_certificate_file(certificate_file);
-    }
-
-    /* We assume that even if the filename hasn't changed, if the CA cert 
-     * file has been removed, that we want to move back into
-     * boot-strapping mode.  This opens a small security hole, because
-     * the old certificate will still be trusted until vSwitch is
-     * restarted.  We may want to address this in vconn's SSL library. */
-    if (config_string_change("ssl.ca-cert", &cacert_file) 
-            || (stat(cacert_file, &s) && errno == ENOENT)) {
-        vconn_ssl_set_ca_cert_file(cacert_file,
-                cfg_get_bool(0, "ssl.bootstrap-ca-cert"));
-    }
-}
-#endif
-
-void
-mgmt_reconfigure(void)
-{
-    struct svec new_cfg;
-    uint8_t new_cookie[CFG_COOKIE_LEN];
-    bool cfg_updated = false;
-    const char *controller_name;
-    int max_backoff;
-    int inactivity_probe;
-    int retval;
-
-    if (!cfg_has_section("mgmt")) {
-        svec_clear(&mgmt_cfg);
-        if (mgmt_rconn) {
-            rconn_destroy(mgmt_rconn);
-            mgmt_rconn = NULL;
-        }
-        return;
-    }
-
-    /* If this is an established connection, send a resources update. */
-    /* xxx This is wasteful if there were no resource changes!!! */
-    if (mgmt_rconn) {
-        send_resources_update(0, false);
-    }
-
-    cfg_get_cookie(new_cookie);
-    if (memcmp(cfg_cookie, new_cookie, sizeof(cfg_cookie))) {
-        memcpy(cfg_cookie, new_cookie, sizeof(cfg_cookie));
-        cfg_updated = true;
-    }
-
-    svec_init(&new_cfg);
-    cfg_get_section(&new_cfg, "mgmt");
-    if (svec_equal(&mgmt_cfg, &new_cfg)) {
-        /* Reconnecting to the controller causes the config file to be
-         * resent automatically.  If we're not reconnecting and the
-         * config file has changed, we need to notify the controller of
-         * changes. */
-        if (cfg_updated && mgmt_rconn) {
-            send_config_update(0, false);
-        }
-        svec_destroy(&new_cfg);
-        return;
-    }
-
-    controller_name = cfg_get_string(0, "mgmt.controller");
-    if (!controller_name) {
-        VLOG_ERR("no controller specified for managment");
-        svec_destroy(&new_cfg);
-        return;
-    }
-
-    max_backoff = cfg_get_int(0, "mgmt.max-backoff");
-    if (max_backoff < 1) {
-        max_backoff = MAX_BACKOFF_DEFAULT;
-    } else if (max_backoff > 3600) {
-        max_backoff = 3600;
-    }
-
-    inactivity_probe = cfg_get_int(0, "mgmt.inactivity-probe");
-    if (inactivity_probe < 5) {
-        inactivity_probe = INACTIVITY_PROBE_DEFAULT;
-    }
-
-    /* xxx If this changes, we need to restart bridges to use new id,
-     * xxx but they need the id before the connect to controller, but we
-     * xxx need their dpids. */
-    /* Check if a different mgmt id has been assigned. */
-    if (cfg_has("mgmt.id")) {
-        uint64_t cfg_mgmt_id = cfg_get_dpid(0, "mgmt.id");
-        if (cfg_mgmt_id != mgmt_id) {
-            mgmt_id = cfg_mgmt_id;
-        }
-    }
-
-    svec_swap(&new_cfg, &mgmt_cfg);
-    svec_destroy(&new_cfg);
-
-#ifdef HAVE_OPENSSL
-    /* Configure SSL. */
-    mgmt_configure_ssl();
-#endif
-
-    if (mgmt_rconn) {
-        rconn_destroy(mgmt_rconn);
-        mgmt_rconn = NULL;
-    }
-    mgmt_rconn = rconn_create(inactivity_probe, max_backoff);
-    retval = rconn_connect(mgmt_rconn, controller_name);
-    if (retval == EAFNOSUPPORT) {
-        VLOG_ERR("no support for %s vconn", controller_name);
-    }
-
-    /* Reset the extended message buffer when we create a new
-     * management connection. */
-    ofpbuf_clear(&ext_data_buffer);
-}
-
-static void *
-make_ofmp_xid(size_t ofmp_len, uint16_t type, uint32_t xid,
-        struct ofpbuf **bufferp)
-{
-    struct ofmp_header *oh;
-
-    oh = make_openflow_xid(ofmp_len, OFPT_VENDOR, xid, bufferp);
-    oh->header.vendor = htonl(NX_VENDOR_ID);
-    oh->header.subtype = htonl(NXT_MGMT);
-    oh->type = htons(type);
-
-    return oh;
-}
-
-static void *
-make_ofmp(size_t ofmp_len, uint16_t type, struct ofpbuf **bufferp)
-{
-    struct ofmp_header *oh;
-
-    oh = make_openflow(ofmp_len, OFPT_VENDOR, bufferp);
-    oh->header.vendor = htonl(NX_VENDOR_ID);
-    oh->header.subtype = htonl(NXT_MGMT);
-    oh->type = htons(type);
-
-    return oh;
-}
-
-static int
-send_openflow_buffer(struct ofpbuf *buffer)
-{               
-    int retval;
-
-    if (!mgmt_rconn) {
-        VLOG_ERR("attempt to send openflow packet with no rconn\n");
-        return EINVAL;
-    }
-
-    /* Make sure there's room to transmit the data.  We don't want to
-     * fail part way through a send. */
-    if (rconn_packet_counter_read(txqlen) >= TXQ_LIMIT) {
-        return EAGAIN;
-    }
-
-    /* OpenFlow messages use a 16-bit length field, so messages over 64K
-     * must be broken into multiple pieces. 
-     */
-    if (buffer->size <= 65535) {
-        update_openflow_length(buffer);
-        retval = rconn_send(mgmt_rconn, buffer, txqlen);
-        if (retval) {
-            VLOG_WARN_RL(&rl, "send to %s failed: %s",
-                         rconn_get_name(mgmt_rconn), strerror(retval));
-        }   
-        return retval;
-    } else {
-        struct ofmp_header *header = (struct ofmp_header *)buffer->data;
-        uint32_t xid = header->header.header.xid;
-        size_t remain = buffer->size;
-        uint8_t *ptr = buffer->data;
-        
-        /* Mark the OpenFlow header with a zero length to indicate some
-         * funkiness. 
-         */
-        header->header.header.length = 0;
-
-        while (remain > 0) {
-            struct ofpbuf *new_buffer;
-            struct ofmp_extended_data *oed;
-            size_t new_len = MIN(65535 - sizeof *oed, remain);
-
-            oed = make_ofmp_xid(sizeof *oed, OFMPT_EXTENDED_DATA, xid, 
-                    &new_buffer);
-            oed->type = header->type;
-
-            if (remain > new_len) {
-                oed->flags |= OFMPEDF_MORE_DATA;
-            }
-
-            /* Copy the entire original message, including the OpenFlow
-             * header, since management protocol structure definitions
-             * include these headers.
-             */
-            ofpbuf_put(new_buffer, ptr, new_len);
-
-            update_openflow_length(new_buffer);
-            retval = rconn_send(mgmt_rconn, new_buffer, txqlen);
-            if (retval) {
-                VLOG_WARN_RL(&rl, "send to %s failed: %s",
-                             rconn_get_name(mgmt_rconn), strerror(retval));
-                ofpbuf_delete(buffer);
-                return retval;
-            }   
-
-            remain -= new_len;
-            ptr += new_len;
-        }
-
-        ofpbuf_delete(buffer);
-        return 0;
-    }
-}   
-    
-static void
-send_features_reply(uint32_t xid)
-{
-    struct ofpbuf *buffer;
-    struct ofp_switch_features *ofr;
-
-    ofr = make_openflow_xid(sizeof *ofr, OFPT_FEATURES_REPLY, xid, &buffer);
-    ofr->datapath_id  = 0;
-    ofr->n_tables     = 0;
-    ofr->n_buffers    = 0;
-    ofr->capabilities = 0;
-    ofr->actions      = 0;
-    send_openflow_buffer(buffer);
-}
-
-static void 
-send_capability_reply(uint32_t xid)
-{
-    int i;
-    struct ofpbuf *buffer;
-    struct ofmp_capability_reply *ofmpcr;
-
-    ofmpcr = make_ofmp_xid(sizeof *ofmpcr, OFMPT_CAPABILITY_REPLY, 
-            xid, &buffer);
-    ofmpcr->format = htonl(OFMPCOF_SIMPLE);
-    ofmpcr->mgmt_id = htonll(mgmt_id);
-    for (i=0; i<capabilities.n; i++) {
-        ofpbuf_put(buffer, capabilities.names[i], 
-                strlen(capabilities.names[i]));
-    }
-    send_openflow_buffer(buffer);
-}
-
-static void 
-send_resources_update(uint32_t xid, bool use_xid)
-{
-    struct ofpbuf *buffer;
-    struct ofmp_resources_update *ofmpru;
-    struct ofmp_tlv *tlv;
-    struct svec br_list;
-    struct svec port_list;
-    const char *host_uuid;
-    int i;
-
-    if (use_xid) {
-        ofmpru = make_ofmp_xid(sizeof *ofmpru, OFMPT_RESOURCES_UPDATE, 
-                xid, &buffer);
-    } else {
-        ofmpru = make_ofmp(sizeof *ofmpru, OFMPT_RESOURCES_UPDATE, &buffer);
-    }
-
-    /* On XenServer systems, each host has its own UUID, which we provide
-     * to the controller. 
-     */ 
-    host_uuid = xenserver_get_host_uuid();
-    if (host_uuid) {
-        struct ofmptsr_mgmt_uuid *mgmt_uuid_tlv;
-
-        mgmt_uuid_tlv = ofpbuf_put_zeros(buffer, sizeof(*mgmt_uuid_tlv));
-        mgmt_uuid_tlv->type = htons(OFMPTSR_MGMT_UUID);
-        mgmt_uuid_tlv->len = htons(sizeof(*mgmt_uuid_tlv));
-        mgmt_uuid_tlv->mgmt_id = htonll(mgmt_id);
-        memcpy(mgmt_uuid_tlv->uuid, host_uuid, OFMP_UUID_LEN);
-    }
-
-    svec_init(&br_list);
-    cfg_get_subsections(&br_list, "bridge");
-    for (i=0; i < br_list.n; i++) {
-        struct ofmptsr_dp *dp_tlv;
-        uint64_t dp_id;
-        int n_uuid;
-
-        dp_id = bridge_get_datapathid(br_list.names[i]);
-        if (!dp_id) {
-            VLOG_WARN_RL(&rl, "bridge %s doesn't seem to exist", 
-                    br_list.names[i]);
-            continue;
-        }
-        dp_tlv = ofpbuf_put_zeros(buffer, sizeof(*dp_tlv));
-        dp_tlv->type = htons(OFMPTSR_DP);
-        dp_tlv->len = htons(sizeof(*dp_tlv));
-
-        dp_tlv->dp_id = htonll(dp_id);
-        memcpy(dp_tlv->name, br_list.names[i], strlen(br_list.names[i])+1);
-
-        /* On XenServer systems, each network has one or more UUIDs
-         * associated with it, which we provide to the controller. 
-         */
-        n_uuid = cfg_count("bridge.%s.xs-network-uuids", br_list.names[i]);
-        if (n_uuid) {
-            struct ofmptsr_dp_uuid *dp_uuid_tlv;
-            size_t tlv_len = sizeof(*dp_uuid_tlv) + n_uuid * OFMP_UUID_LEN;
-            int j;
-
-            dp_uuid_tlv = ofpbuf_put_zeros(buffer, sizeof(*dp_uuid_tlv));
-            dp_uuid_tlv->type = htons(OFMPTSR_DP_UUID);
-            dp_uuid_tlv->len = htons(tlv_len);
-            dp_uuid_tlv->dp_id = htonll(dp_id);
-
-            for (j=0; j<n_uuid; j++) {
-                const char *dp_uuid = cfg_get_string(j, 
-                        "bridge.%s.xs-network-uuids", br_list.names[i]);
-
-                /* The UUID list could change underneath us, so just
-                 * fill with zeros in that case.  Another update will be
-                 * initiated shortly, which should contain corrected data.
-                 */
-                if (dp_uuid) {
-                    ofpbuf_put(buffer, dp_uuid, OFMP_UUID_LEN);
-                } else {
-                    ofpbuf_put_zeros(buffer, OFMP_UUID_LEN);
-                }
-            }
-        }
-    }
-    svec_destroy(&br_list);
-
-    /* On XenServer systems, extended information about virtual interfaces 
-     * (VIFs) is available, which is needed by the controller. 
-     */ 
-    svec_init(&port_list);
-    bridge_get_ifaces(&port_list);
-    for (i=0; i < port_list.n; i++) {
-        const char *vif_uuid, *vm_uuid, *net_uuid;
-        uint64_t vif_mac;
-        struct ofmptsr_vif *vif_tlv;
-
-        vif_uuid = cfg_get_string(0, "port.%s.vif-uuid", port_list.names[i]);
-        if (!vif_uuid) {
-            continue;
-        }
-
-        vif_tlv = ofpbuf_put_zeros(buffer, sizeof(*vif_tlv));
-        vif_tlv->type = htons(OFMPTSR_VIF);
-        vif_tlv->len = htons(sizeof(*vif_tlv));
-
-        memcpy(vif_tlv->name, port_list.names[i], strlen(port_list.names[i])+1);
-        memcpy(vif_tlv->vif_uuid, vif_uuid, sizeof(vif_tlv->vif_uuid));
-
-        vm_uuid = cfg_get_string(0, "port.%s.vm-uuid", port_list.names[i]);
-        if (vm_uuid) {
-            memcpy(vif_tlv->vm_uuid, vm_uuid, sizeof(vif_tlv->vm_uuid));
-        } else {
-            /* In case the vif disappeared underneath us. */
-            memset(vif_tlv->vm_uuid, '\0', sizeof(vif_tlv->vm_uuid));
-        }
-
-        net_uuid = cfg_get_string(0, "port.%s.net-uuid", port_list.names[i]);
-        if (net_uuid) {
-            memcpy(vif_tlv->net_uuid, net_uuid, sizeof(vif_tlv->net_uuid));
-        } else {
-            /* In case the vif disappeared underneath us. */
-            memset(vif_tlv->net_uuid, '\0', sizeof(vif_tlv->net_uuid));
-        }
-
-        vif_mac = cfg_get_mac(0, "port.%s.vif-mac", port_list.names[i]);
-        vif_tlv->vif_mac = htonll(vif_mac);
-    }
-    svec_destroy(&port_list);
-
-    /* Put end marker. */
-    tlv = ofpbuf_put_zeros(buffer, sizeof(*tlv));
-    tlv->type = htons(OFMPTSR_END);
-    tlv->len = htons(sizeof(*tlv));
-    send_openflow_buffer(buffer);
-}
-
-static void 
-send_config_update(uint32_t xid, bool use_xid)
-{
-    struct ofpbuf *buffer;
-    struct ofmp_config_update *ofmpcu;
-
-    if (use_xid) {
-        ofmpcu = make_ofmp_xid(sizeof *ofmpcu, OFMPT_CONFIG_UPDATE, 
-                xid, &buffer);
-    } else {
-        ofmpcu = make_ofmp(sizeof *ofmpcu, OFMPT_CONFIG_UPDATE, &buffer);
-    }
-
-    ofmpcu->format = htonl(OFMPCOF_SIMPLE);
-    memcpy(ofmpcu->cookie, cfg_cookie, sizeof(ofmpcu->cookie));
-    cfg_buf_put(buffer);
-    send_openflow_buffer(buffer);
-}
-
-static void 
-send_config_update_ack(uint32_t xid, bool success)
-{
-    struct ofpbuf *buffer;
-    struct ofmp_config_update_ack *ofmpcua;
-
-    ofmpcua = make_ofmp_xid(sizeof *ofmpcua, OFMPT_CONFIG_UPDATE_ACK, 
-            xid, &buffer);
-
-    ofmpcua->format = htonl(OFMPCOF_SIMPLE);
-    if (success) {
-        ofmpcua->flags = htonl(OFMPCUAF_SUCCESS);
-    }
-    cfg_get_cookie(ofmpcua->cookie);
-    send_openflow_buffer(buffer);
-}
-
-static void
-send_error_msg(uint32_t xid, uint16_t type, uint16_t code, 
-            const void *data, size_t len)
-{
-    struct ofpbuf *buffer;
-    struct ofp_error_msg *oem;
-
-    oem = make_openflow_xid(sizeof(*oem)+len, OFPT_ERROR, xid, &buffer);
-    oem->type = htons(type);
-    oem->code = htons(code);
-    memcpy(oem->data, data, len);
-    send_openflow_buffer(buffer);
-}
-
-static int
-recv_echo_request(uint32_t xid UNUSED, const void *msg)
-{
-    const struct ofp_header *rq = msg;
-    send_openflow_buffer(make_echo_reply(rq));
-    return 0;
-}
-
-static int
-recv_features_request(uint32_t xid, const void *msg UNUSED)
-{
-    send_features_reply(xid);
-    return 0;
-}
-
-static int
-recv_set_config(uint32_t xid UNUSED, const void *msg UNUSED)
-{
-    /* Nothing to configure! */
-    return 0;
-}
-
-static int
-recv_ofmp_capability_request(uint32_t xid, const struct ofmp_header *ofmph,
-        size_t len)
-{
-    struct ofmp_capability_request *ofmpcr;
-
-    if (len != sizeof(*ofmpcr)) {
-        /* xxx Send error */
-        return -EINVAL;
-    }
-
-    ofmpcr = (struct ofmp_capability_request *)ofmph;
-    if (ofmpcr->format != htonl(OFMPCAF_SIMPLE)) {
-        /* xxx Send error */
-        return -EINVAL;
-    }
-
-    send_capability_reply(xid);
-
-    return 0;
-}
-
-static int
-recv_ofmp_resources_request(uint32_t xid, const void *msg UNUSED, 
-        size_t len UNUSED)
-{
-    send_resources_update(xid, true);
-    return 0;
-}
-
-static int
-recv_ofmp_config_request(uint32_t xid, const struct ofmp_header *ofmph, 
-        size_t len)
-{
-    struct ofmp_config_request *ofmpcr;
-
-    if (len != sizeof(*ofmpcr)) {
-        /* xxx Send error */
-        return -EINVAL;
-    }
-
-    ofmpcr = (struct ofmp_config_request *)ofmph;
-    if (ofmpcr->format != htonl(OFMPCOF_SIMPLE)) {
-        /* xxx Send error */
-        return -EINVAL;
-    }
-
-    send_config_update(xid, true);
-
-    return 0;
-}
-
-static int
-recv_ofmp_config_update(uint32_t xid, const struct ofmp_header *ofmph,
-        size_t len)
-{
-    struct ofmp_config_update *ofmpcu;
-    int data_len;
-
-    data_len = len - sizeof(*ofmpcu);
-    if (data_len <= sizeof(*ofmpcu)) {
-        /* xxx Send error. */
-        return -EINVAL;
-    }
-
-    ofmpcu = (struct ofmp_config_update *)ofmph;
-    if (ofmpcu->format != htonl(OFMPCOF_SIMPLE)) {
-        /* xxx Send error */
-        return -EINVAL;
-    }
-
-    /* Check if the supplied cookie matches our current understanding of
-     * it.  If they don't match, tell the controller and let it sort
-     * things out. */
-    if (cfg_lock(ofmpcu->cookie, 0)) {  
-        /* xxx cfg_lock can fail for other reasons, such as being
-         * xxx locked... */
-        VLOG_WARN_RL(&rl, "config update failed due to bad cookie\n");
-
-        /* Check if our local view matches the controller, in which
-         * case, it is likely that there were local modifications
-         * without our being told to reread the config file. */
-        if (!memcmp(cfg_cookie, ofmpcu->cookie, sizeof cfg_cookie)) {
-            VLOG_WARN_RL(&rl, "config appears to have been locally modified "
-                              "without having told ovs-vswitchd to reload");
-        }
-        send_config_update_ack(xid, false);
-        return 0;
-    }
-
-    /* xxx We should probably do more sanity checking than this. */
-
-    cfg_write_data(ofmpcu->data, data_len);
-    cfg_unlock();
-
-    /* Send the ACK before running reconfigure, since our management
-     * connection settings may have changed. */
-    send_config_update_ack(xid, true);
-
-    need_reconfigure = true;
-
-    return 0;
-}
-
-static int
-recv_ofmp_extended_data(uint32_t xid, const struct ofmp_header *ofmph,
-        size_t len)
-{
-    int data_len;
-    struct ofmp_extended_data *ofmped;
-
-    if (len <= sizeof(*ofmped)) {
-        /* xxx Send error. */
-        return -EINVAL;
-    }
-
-    ext_data_xid = xid;
-    ofmped = (struct ofmp_extended_data *)ofmph;
-
-    data_len = len - sizeof(*ofmped);
-    ofpbuf_put(&ext_data_buffer, ofmped->data, data_len);
-
-    if (!(ofmped->flags & OFMPEDF_MORE_DATA)) {
-        struct ofmp_header *new_oh;
-        int error;
-
-        /* An embedded message must be greater than the size of an
-         * OpenFlow message. */
-        new_oh = ofpbuf_at(&ext_data_buffer, 0, 65536);
-        if (!new_oh) {
-            VLOG_WARN_RL(&rl, "received short embedded message: %zu\n",
-                    ext_data_buffer.size);
-            return -EINVAL;
-        }
-
-        /* Make sure that this is a management message and that there's
-         * not an embedded extended data message. */
-        if ((new_oh->header.vendor != htonl(NX_VENDOR_ID))
-                || (new_oh->header.subtype != htonl(NXT_MGMT))
-                || (new_oh->type == htonl(OFMPT_EXTENDED_DATA))) {
-            VLOG_WARN_RL(&rl, "received bad embedded message\n");
-            return -EINVAL;
-        }
-        new_oh->header.header.xid = ext_data_xid;
-        new_oh->header.header.length = 0;
-
-        error = recv_ofmp(xid, ext_data_buffer.data, ext_data_buffer.size);
-        ofpbuf_clear(&ext_data_buffer);
-
-        return error;
-    }
-
-    return 0;
-}
-
-/* Handles receiving a management message.  Generally, this function
- * will be called 'len' set to zero, and the length will be derived by
- * the OpenFlow header.  With the extended data message, management
- * messages are not constrained by OpenFlow's 64K message length limit.  
- * The extended data handler calls this function with the 'len' set to
- * the total message length and the OpenFlow header's length field is 
- * ignored.
- */
-static
-int recv_ofmp(uint32_t xid, struct ofmp_header *ofmph, size_t len)
-{
-    if (!len) {
-        len = ntohs(ofmph->header.header.length);
-    }
-
-    /* Reset the extended data buffer if this isn't a continuation of an 
-     * existing extended data message. */
-    if (ext_data_xid != xid) {
-        ofpbuf_clear(&ext_data_buffer);
-    }
-
-    /* xxx Should sanity-check for min/max length */
-    switch (ntohs(ofmph->type)) 
-    {
-        case OFMPT_CAPABILITY_REQUEST:
-            return recv_ofmp_capability_request(xid, ofmph, len);
-        case OFMPT_RESOURCES_REQUEST:
-            return recv_ofmp_resources_request(xid, ofmph, len);
-        case OFMPT_CONFIG_REQUEST:
-            return recv_ofmp_config_request(xid, ofmph, len);
-        case OFMPT_CONFIG_UPDATE:
-            return recv_ofmp_config_update(xid, ofmph, len);
-        case OFMPT_EXTENDED_DATA:
-            return recv_ofmp_extended_data(xid, ofmph, len);
-        default:
-            VLOG_WARN_RL(&rl, "unknown mgmt message: %d", 
-                    ntohs(ofmph->type));
-            return -EINVAL;
-    }
-}
-
-static int 
-recv_nx_msg(uint32_t xid, const void *oh)
-{
-    const struct nicira_header *nh = oh;
-
-    switch (ntohl(nh->subtype)) {
-
-    case NXT_MGMT:
-        return recv_ofmp(xid, (struct ofmp_header *)oh, 0);
-
-    default:
-        send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE, 
-                oh, ntohs(nh->header.length));
-        return -EINVAL;
-    }
-}
-
-static int
-recv_vendor(uint32_t xid, const void *oh)
-{
-    const struct ofp_vendor_header *ovh = oh;
-
-    switch (ntohl(ovh->vendor))
-    {
-    case NX_VENDOR_ID:
-        return recv_nx_msg(xid, oh);
-
-    default:
-        VLOG_WARN_RL(&rl, "unknown vendor: 0x%x", ntohl(ovh->vendor));
-        send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR, 
-                oh, ntohs(ovh->header.length));
-        return -EINVAL; 
-    }
-}
-
-static int
-handle_msg(uint32_t xid, const void *msg, size_t length)
-{
-    int (*handler)(uint32_t, const void *);
-    struct ofp_header *oh;
-    size_t min_size;
-
-    COVERAGE_INC(mgmt_received);
-
-    /* Check encapsulated length. */
-    oh = (struct ofp_header *) msg;
-    if (ntohs(oh->length) > length) {
-        return -EINVAL;
-    }
-    assert(oh->version == OFP_VERSION);
-
-    /* Figure out how to handle it. */
-    switch (oh->type) {
-    case OFPT_ECHO_REQUEST:
-        min_size = sizeof(struct ofp_header);
-        handler = recv_echo_request;
-        break;
-    case OFPT_ECHO_REPLY:
-        return 0;
-    case OFPT_FEATURES_REQUEST:
-        min_size = sizeof(struct ofp_header);
-        handler = recv_features_request;
-        break;
-    case OFPT_SET_CONFIG:
-        min_size = sizeof(struct ofp_switch_config);
-        handler = recv_set_config;
-        break;
-    case OFPT_VENDOR:
-        min_size = sizeof(struct ofp_vendor_header);
-        handler = recv_vendor;
-        break;
-    default:
-        VLOG_WARN_RL(&rl, "unknown openflow type: %d", oh->type);
-        send_error_msg(xid, OFPET_BAD_REQUEST, OFPBRC_BAD_TYPE,
-                msg, length);
-        return -EINVAL;
-    }
-
-    /* Handle it. */
-    if (length < min_size) {
-        return -EFAULT;
-    }
-    return handler(xid, msg);
-}
-
-bool 
-mgmt_run(void)
-{
-    int i;
-
-    if (!mgmt_rconn) {
-        return false;
-    }
-
-    need_reconfigure = false;
-    rconn_run(mgmt_rconn);
-
-    /* Do some processing, but cap it at a reasonable amount so that
-     * other processing doesn't starve. */
-    for (i=0; i<50; i++) {
-        struct ofpbuf *buffer;
-        struct ofp_header *oh;
-
-        buffer = rconn_recv(mgmt_rconn);
-        if (!buffer) {
-            break;
-        }
-
-        if (buffer->size >= sizeof *oh) {
-            oh = buffer->data;
-            handle_msg(oh->xid, buffer->data, buffer->size);
-            ofpbuf_delete(buffer);
-        } else {
-            VLOG_WARN_RL(&rl, "received too-short OpenFlow message");
-        }
-    }
-
-    return need_reconfigure;
-}
-
-void
-mgmt_wait(void)
-{
-    if (!mgmt_rconn) {
-        return;
-    }
-
-    rconn_run_wait(mgmt_rconn);
-    rconn_recv_wait(mgmt_rconn);
-}
-
-static uint64_t
-pick_fallback_mgmt_id(void)
-{
-    uint8_t ea[ETH_ADDR_LEN];
-    eth_addr_random(ea);
-    ea[0] = 0x00;               /* Set Nicira OUI. */
-    ea[1] = 0x23;
-    ea[2] = 0x20;
-    return eth_addr_to_uint64(ea);
-}
index e1d448d..f5dd62a 100644 (file)
@@ -6,14 +6,18 @@ ovs\-brcompatd \- Bridge compatibility front-end for ovs\-vswitchd
 .
 .SH SYNOPSIS
 .B ovs\-brcompatd
-[\fIoptions\fR] \fIconfig\fR
+[\fIoptions\fR] \fIdatabase\fR
 .
 .SH DESCRIPTION
 A daemon that provides a legacy bridge front-end for \fBovs\-vswitchd\fR.  It 
 does this by listening for bridge ioctl commands (e.g., those generated by 
 the \fBbrctl\fR program) to add or remove datapaths and the interfaces 
-that attach to them.  It modifies \fIconfig\fR and forces
-\fBovs\-vswitchd\fR to reload its configuration file.
+that attach to them.  
+.PP
+The mandatory \fIdatabase\fR argument specifies the
+\fBovsdb\-server\fR from which \fBovs\-vswitchd\fR's configuration is
+retrieved.  It should take the form \fBunix:\fIfile\fR, to connect to
+the Unix domain server socket named \fIfile\fR.
 .PP
 .SH OPTIONS
 .IP "\fB--appctl-command=\fIcommand\fR"
@@ -39,14 +43,9 @@ both of these streams separately.
 \fB--prune-timeout=\fIsecs\fR
 .
 Sets the maximum time between pruning port entries to \fIsecs\fR seconds.
-Pruning ports is the process of removing port entries from \fIconfig\fR 
+Pruning ports is the process of removing port entries from \fIdatabase\fR 
 that no longer exist.  If \fIsecs\fR is zero, then entries are never
 pruned.  The default prune timeout is 5 seconds.
-.TP
-\fB--lock-timeout=\fImsecs\fR
-.
-Sets the maximum time to wait for \fIconfig\fR to become unlocked to 
-\fImsecs\fR milliseconds.  The default lock timeout is 500 milliseconds.
 .
 .so lib/daemon.man
 .so lib/vlog.man
@@ -59,5 +58,5 @@ loaded.
 .SH "SEE ALSO"
 .BR ovs\-appctl (8),
 .BR ovs\-vswitchd (8),
-.BR ovs\-vswitchd.conf (5),
+.BR ovsdb\-server (1),
 \fBINSTALL.bridge\fR in the Open vSwitch distribution.
index 99d08c5..585ab2c 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <fcntl.h>
 #include <unistd.h>
 
-#include "cfg.h"
 #include "command-line.h"
 #include "coverage.h"
 #include "daemon.h"
 #include "dirs.h"
 #include "dynamic-string.h"
 #include "fatal-signal.h"
-#include "fault.h"
 #include "leak-checker.h"
 #include "netdev.h"
 #include "netlink.h"
 #include "ofpbuf.h"
 #include "openvswitch/brcompat-netlink.h"
+#include "ovsdb-idl.h"
 #include "packets.h"
 #include "poll-loop.h"
 #include "process.h"
@@ -54,6 +53,7 @@
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
+#include "vswitchd/vswitch-idl.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_brcompatd
@@ -69,22 +69,15 @@ enum bmc_action {
     BMC_DEL_PORT
 };
 
-static void parse_options(int argc, char *argv[]);
+static const char *parse_options(int argc, char *argv[]);
 static void usage(void) NO_RETURN;
 
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 60);
 
-/* Maximum number of milliseconds to wait for the config file to be
- * unlocked.  If set to zero, no waiting will occur. */
-static int lock_timeout = 500;
-
 /* Maximum number of milliseconds to wait before pruning port entries that 
  * no longer exist.  If set to zero, ports are never pruned. */
 static int prune_timeout = 5000;
 
-/* Config file shared with ovs-vswitchd (usually ovs-vswitchd.conf). */
-static char *config_file;
-
 /* Shell command to execute (via popen()) to send a control command to the
  * running ovs-vswitchd process.  The string must contain one instance of %s,
  * which is replaced by the control command. */
@@ -173,10 +166,18 @@ static const struct nl_policy brc_dp_policy[] = {
     [BRC_GENL_A_DP_NAME] = { .type = NL_A_STRING },
 };
 
-static bool
-bridge_exists(const char *name)
+static struct ovsrec_bridge *
+find_bridge(const struct ovsrec_open_vswitch *ovs, const char *br_name)
 {
-    return cfg_has_section("bridge.%s", name);
+    size_t i;
+
+    for (i = 0; i < ovs->n_bridges; i++) {
+        if (!strcmp(br_name, ovs->bridges[i]->name)) {
+            return ovs->bridges[i];
+        }
+    }
+
+    return NULL;
 }
 
 static int
@@ -221,52 +222,31 @@ execute_appctl_command(const char *unixctl_command, char **output)
     return error;
 }
 
-static int
-rewrite_and_reload_config(void)
-{
-    if (cfg_is_dirty()) {
-        int error1 = cfg_write();
-        int error2 = cfg_read();
-        long long int reload_start = time_msec();
-        int error3 = execute_appctl_command("vswitchd/reload", NULL);
-        long long int elapsed = time_msec() - reload_start;
-        COVERAGE_INC(brcompatd_reload);
-        if (elapsed > 0) {
-            VLOG_INFO("reload command executed in %lld ms", elapsed);
-        }
-        return error1 ? error1 : error2 ? error2 : error3;
-    }
-    return 0;
-}
-
 static void
-do_get_bridge_parts(const char *bridge, struct svec *parts, int vlan,
-                    bool break_down_bonds)
+do_get_bridge_parts(const struct ovsrec_bridge *br, struct svec *parts, 
+                    int vlan, bool break_down_bonds)
 {
     struct svec ports;
-    int i;
+    size_t i, j;
 
     svec_init(&ports);
-    cfg_get_all_keys(&ports, "bridge.%s.port", bridge);
-    for (i = 0; i < ports.n; i++) {
-        const char *port_name = ports.names[i];
+    for (i = 0; i < br->n_ports; i++) {
+        const struct ovsrec_port *port = br->ports[i];
+
+        svec_add(&ports, port->name);
         if (vlan >= 0) {
-            int port_vlan = cfg_get_vlan(0, "vlan.%s.tag", port_name);
-            if (port_vlan < 0) {
-                port_vlan = 0;
-            }
+            int port_vlan = port->n_tag ? *port->tag : 0;
             if (vlan != port_vlan) {
                 continue;
             }
         }
-        if (break_down_bonds && cfg_has_section("bonding.%s", port_name)) {
-            struct svec slaves;
-            svec_init(&slaves);
-            cfg_get_all_keys(&slaves, "bonding.%s.slave", port_name);
-            svec_append(parts, &slaves);
-            svec_destroy(&slaves);
+        if (break_down_bonds) {
+            for (j = 0; j < port->n_interfaces; j++) {
+                const struct ovsrec_interface *iface = port->interfaces[j];
+                svec_add(parts, iface->name);
+            }
         } else {
-            svec_add(parts, port_name);
+            svec_add(parts, port->name);
         }
     }
     svec_destroy(&ports);
@@ -280,9 +260,10 @@ do_get_bridge_parts(const char *bridge, struct svec *parts, int vlan,
  * reported.  If 'vlan' > 0, only interfaces with implicit VLAN 'vlan' are
  * reported.  */
 static void
-get_bridge_ifaces(const char *bridge, struct svec *ifaces, int vlan)
+get_bridge_ifaces(const struct ovsrec_bridge *br, struct svec *ifaces, 
+                  int vlan)
 {
-    do_get_bridge_parts(bridge, ifaces, vlan, true);
+    do_get_bridge_parts(br, ifaces, vlan, true);
 }
 
 /* Add all the ports for 'bridge' to 'ports'.  Bonded ports are reported under
@@ -292,11 +273,13 @@ get_bridge_ifaces(const char *bridge, struct svec *ifaces, int vlan)
  * only trunk ports or ports with implicit VLAN 0 are reported.  If 'vlan' > 0,
  * only port with implicit VLAN 'vlan' are reported.  */
 static void
-get_bridge_ports(const char *bridge, struct svec *ports, int vlan)
+get_bridge_ports(const struct ovsrec_bridge *br, struct svec *ports, 
+                 int vlan)
 {
-    do_get_bridge_parts(bridge, ports, vlan, false);
+    do_get_bridge_parts(br, ports, vlan, false);
 }
 
+#if 0
 /* Go through the configuration file and remove any ports that no longer
  * exist associated with a bridge. */
 static void
@@ -349,48 +332,181 @@ prune_ports(void)
             cfg_del_match("bridge.*.port=%s", delete.names[i]);
             cfg_del_match("bonding.*.slave=%s", delete.names[i]);
         }
-        rewrite_and_reload_config();
+        reload_config();
         cfg_unlock();
     } else {
         cfg_unlock();
     }
     svec_destroy(&delete);
 }
+#endif
+
+static struct ovsdb_idl_txn *
+txn_from_openvswitch(const struct ovsrec_open_vswitch *ovs)
+{
+    return ovsdb_idl_txn_get(&ovs->header_);
+}
+
+static bool
+port_is_fake_bridge(const struct ovsrec_port *port)
+{
+    return (port->fake_bridge
+            && port->tag
+            && *port->tag >= 1 && *port->tag <= 4095);
+}
+
+static void
+ovs_insert_bridge(const struct ovsrec_open_vswitch *ovs,
+                  struct ovsrec_bridge *bridge)
+{
+    struct ovsrec_bridge **bridges;
+    size_t i;     
+
+    bridges = xmalloc(sizeof *ovs->bridges * (ovs->n_bridges + 1));
+    for (i = 0; i < ovs->n_bridges; i++) {
+        bridges[i] = ovs->bridges[i];
+    }
+    bridges[ovs->n_bridges] = bridge;
+    ovsrec_open_vswitch_set_bridges(ovs, bridges, ovs->n_bridges + 1);
+    free(bridges);
+}   
 
 static int
-add_bridge(const char *br_name)
+add_bridge(const struct ovsrec_open_vswitch *ovs, const char *br_name)
 {
-    if (bridge_exists(br_name)) {
+    struct ovsrec_bridge *br;
+    struct ovsrec_port *port;
+    struct ovsrec_interface *iface;
+
+    if (find_bridge(ovs, br_name)) {
         VLOG_WARN("addbr %s: bridge %s exists", br_name, br_name);
         return EEXIST;
     } else if (netdev_exists(br_name)) {
-        if (cfg_get_bool(0, "iface.%s.fake-bridge", br_name)) {
-            VLOG_WARN("addbr %s: %s exists as a fake bridge",
-                      br_name, br_name);
-            return 0;
-        } else {
-            VLOG_WARN("addbr %s: cannot create bridge %s because a network "
-                      "device named %s already exists",
-                      br_name, br_name, br_name);
-            return EEXIST;
+        size_t i;
+
+        for (i = 0; i < ovs->n_bridges; i++) {
+            size_t j;
+            struct ovsrec_bridge *br_cfg = ovs->bridges[i];
+
+            for (j = 0; j < br_cfg->n_ports; j++) {
+                if (port_is_fake_bridge(br_cfg->ports[j])) {
+                    VLOG_WARN("addbr %s: %s exists as a fake bridge",
+                              br_name, br_name);
+                    return 0;
+                }
+            }
         }
+
+        VLOG_WARN("addbr %s: cannot create bridge %s because a network "
+                  "device named %s already exists",
+                  br_name, br_name, br_name);
+        return EEXIST;
     }
 
-    cfg_add_entry("bridge.%s.port=%s", br_name, br_name);
+    iface = ovsrec_interface_insert(txn_from_openvswitch(ovs));
+    ovsrec_interface_set_name(iface, br_name);
+
+    port = ovsrec_port_insert(txn_from_openvswitch(ovs));
+    ovsrec_port_set_name(port, br_name);
+    ovsrec_port_set_interfaces(port, &iface, 1);
+    
+    br = ovsrec_bridge_insert(txn_from_openvswitch(ovs));
+    ovsrec_bridge_set_name(br, br_name);
+    ovsrec_bridge_set_ports(br, &port, 1);
+    
+    ovs_insert_bridge(ovs, br);
+
     VLOG_INFO("addbr %s: success", br_name);
 
     return 0;
 }
 
+static void
+add_port(const struct ovsrec_open_vswitch *ovs, 
+         const struct ovsrec_bridge *br, const char *port_name)
+{
+    struct ovsrec_interface *iface;
+    struct ovsrec_port *port;
+    struct ovsrec_port **ports;
+    size_t i;
+
+    /* xxx Check conflicts? */
+    iface = ovsrec_interface_insert(txn_from_openvswitch(ovs));
+    ovsrec_interface_set_name(iface, port_name);
+
+    port = ovsrec_port_insert(txn_from_openvswitch(ovs));
+    ovsrec_port_set_name(port, port_name);
+    ovsrec_port_set_interfaces(port, &iface, 1);
+
+    ports = xmalloc(sizeof *br->ports * (br->n_ports + 1));
+    for (i = 0; i < br->n_ports; i++) {
+        ports[i] = br->ports[i];
+    }
+    ports[br->n_ports] = port;
+    ovsrec_bridge_set_ports(br, ports, br->n_ports + 1);
+    free(ports);
+}
+
+static void
+del_port(const struct ovsrec_bridge *br, const char *port_name)
+{
+    size_t i, j;
+    struct ovsrec_port *port_rec = NULL;
+
+    for (i = 0; i < br->n_ports; i++) {
+        struct ovsrec_port *port = br->ports[i];
+        if (!strcmp(port_name, port->name)) {
+            port_rec = port;
+        }
+        for (j = 0; j < port->n_interfaces; j++) {
+            struct ovsrec_interface *iface = port->interfaces[j];
+            if (!strcmp(port_name, iface->name)) {
+                ovsrec_interface_delete(iface);
+            }
+        }
+    }
+
+    /* xxx Probably can move this into the "for" loop. */
+    if (port_rec) {
+        struct ovsrec_port **ports;
+        size_t n;
+
+        ports = xmalloc(sizeof *br->ports * br->n_ports);
+        for (i = n = 0; i < br->n_ports; i++) {
+            if (br->ports[i] != port_rec) {
+                ports[n++] = br->ports[i];
+            }
+        }
+        ovsrec_bridge_set_ports(br, ports, n);
+        free(ports);
+    }
+}
+
 static int 
-del_bridge(const char *br_name)
+del_bridge(const struct ovsrec_open_vswitch *ovs, const char *br_name)
 {
-    if (!bridge_exists(br_name)) {
+    struct ovsrec_bridge *br = find_bridge(ovs, br_name);
+    struct ovsrec_bridge **bridges;
+    size_t i, n;
+
+    if (!br) {
         VLOG_WARN("delbr %s: no bridge named %s", br_name, br_name);
         return ENXIO;
     }
 
-    cfg_del_section("bridge.%s", br_name);
+    del_port(br, br_name);
+
+    ovsrec_bridge_delete(br);
+
+    bridges = xmalloc(sizeof *ovs->bridges * ovs->n_bridges);
+    for (i = n = 0; i < ovs->n_bridges; i++) {
+        if (ovs->bridges[i] != br) {
+            bridges[n++] = ovs->bridges[i];
+        }
+    }
+    ovsrec_open_vswitch_set_bridges(ovs, bridges, n);
+    free(bridges);
+
     VLOG_INFO("delbr %s: success", br_name);
 
     return 0;
@@ -468,7 +584,8 @@ send_simple_reply(uint32_t seq, int error)
 }
 
 static int
-handle_bridge_cmd(struct ofpbuf *buffer, bool add)
+handle_bridge_cmd(const struct ovsrec_open_vswitch *ovs, 
+                  struct ofpbuf *buffer, bool add)
 {
     const char *br_name;
     uint32_t seq;
@@ -476,10 +593,7 @@ handle_bridge_cmd(struct ofpbuf *buffer, bool add)
 
     error = parse_command(buffer, &seq, &br_name, NULL, NULL, NULL);
     if (!error) {
-        error = add ? add_bridge(br_name) : del_bridge(br_name);
-        if (!error) {
-            error = rewrite_and_reload_config();
-        }
+        error = add ? add_bridge(ovs, br_name) : del_bridge(ovs, br_name);
         send_simple_reply(seq, error);
     }
     return error;
@@ -490,16 +604,9 @@ static const struct nl_policy brc_port_policy[] = {
     [BRC_GENL_A_PORT_NAME] = { .type = NL_A_STRING },
 };
 
-static void
-del_port(const char *br_name, const char *port_name)
-{
-    cfg_del_entry("bridge.%s.port=%s", br_name, port_name);
-    cfg_del_match("bonding.*.slave=%s", port_name);
-    cfg_del_match("vlan.%s.[!0-9]*", port_name);
-}
-
 static int
-handle_port_cmd(struct ofpbuf *buffer, bool add)
+handle_port_cmd(const struct ovsrec_open_vswitch *ovs,
+                struct ofpbuf *buffer, bool add)
 {
     const char *cmd_name = add ? "add-if" : "del-if";
     const char *br_name, *port_name;
@@ -508,7 +615,9 @@ handle_port_cmd(struct ofpbuf *buffer, bool add)
 
     error = parse_command(buffer, &seq, &br_name, &port_name, NULL, NULL);
     if (!error) {
-        if (!bridge_exists(br_name)) {
+        struct ovsrec_bridge *br = find_bridge(ovs, br_name);
+
+        if (!br) {
             VLOG_WARN("%s %s %s: no bridge named %s",
                       cmd_name, br_name, port_name, br_name);
             error = EINVAL;
@@ -518,12 +627,11 @@ handle_port_cmd(struct ofpbuf *buffer, bool add)
             error = EINVAL;
         } else {
             if (add) {
-                cfg_add_entry("bridge.%s.port=%s", br_name, port_name);
+                add_port(ovs, br, port_name);
             } else {
-                del_port(br_name, port_name);
+                del_port(br, port_name);
             }
             VLOG_INFO("%s %s %s: success", cmd_name, br_name, port_name);
-            error = rewrite_and_reload_config();
         }
         send_simple_reply(seq, error);
     }
@@ -531,56 +639,47 @@ handle_port_cmd(struct ofpbuf *buffer, bool add)
     return error;
 }
 
-/* Returns the name of the bridge that contains a port named 'port_name', as a
- * malloc'd string that the caller must free, or a null pointer if no bridge
- * contains a port named 'port_name'. */
-static char *
-get_bridge_containing_port(const char *port_name)
-{
-    struct svec matches;
-    const char *start, *end;
-
-    svec_init(&matches);
-    cfg_get_matches(&matches, "bridge.*.port=%s", port_name);
-    if (!matches.n) {
-        return 0;
-    }
-
-    start = matches.names[0] + strlen("bridge.");
-    end = strstr(start, ".port=");
-    assert(end);
-    return xmemdup0(start, end - start);
-}
-
+/* The caller is responsible for freeing '*ovs_name' if the call is
+ * successful. */
 static int
-linux_bridge_to_ovs_bridge(const char *linux_bridge,
-                           char **ovs_bridge, int *br_vlan)
+linux_bridge_to_ovs_bridge(const struct ovsrec_open_vswitch *ovs,
+                           const char *linux_name,
+                           const struct ovsrec_bridge **ovs_bridge,
+                           int *br_vlan)
 {
-    if (bridge_exists(linux_bridge)) {
+    *ovs_bridge = find_bridge(ovs, linux_name);
+    if (*ovs_bridge) {
         /* Bridge name is the same.  We are interested in VLAN 0. */
-        *ovs_bridge = xstrdup(linux_bridge);
         *br_vlan = 0;
         return 0;
     } else {
-        /* No such Open vSwitch bridge 'linux_bridge', but there might be an
-         * internal port named 'linux_bridge' on some other bridge
+        /* No such Open vSwitch bridge 'linux_name', but there might be an
+         * internal port named 'linux_name' on some other bridge
          * 'ovs_bridge'.  If so then we are interested in the VLAN assigned to
-         * port 'linux_bridge' on the bridge named 'ovs_bridge'. */
-        const char *port_name = linux_bridge;
+         * port 'linux_name' on the bridge named 'ovs_bridge'. */
+        size_t i, j;
+
+        for (i = 0; i < ovs->n_bridges; i++) {
+            const struct ovsrec_bridge *br = ovs->bridges[i];
+
+            for (j = 0; j < br->n_ports; j++) {
+                const struct ovsrec_port *port = br->ports[j];
+
+                if (!strcmp(port->name, linux_name)) {
+                    *ovs_bridge = br;
+                    *br_vlan = port->n_tag ? *port->tag : -1;
+                    return 0;
+                }
+            }
 
-        *ovs_bridge = get_bridge_containing_port(port_name);
-        *br_vlan = cfg_get_vlan(0, "vlan.%s.tag", port_name);
-        if (*ovs_bridge && *br_vlan >= 0) {
-            return 0;
-        } else {
-            free(*ovs_bridge);
-            return ENODEV;
         }
+        return ENODEV;
     }
 }
 
 static int
-handle_fdb_query_cmd(struct ofpbuf *buffer)
+handle_fdb_query_cmd(const struct ovsrec_open_vswitch *ovs,
+                     struct ofpbuf *buffer)
 {
     /* This structure is copied directly from the Linux 2.6.30 header files.
      * It would be more straightforward to #include <linux/if_bridge.h>, but
@@ -608,8 +707,8 @@ handle_fdb_query_cmd(struct ofpbuf *buffer)
      * vswitchd can deal with all the VLANs on a single bridge.  We have to
      * pretend that the former is the case even though the latter is the
      * implementation. */
-    const char *linux_bridge;   /* Name used by brctl. */
-    char *ovs_bridge;           /* Name used by ovs-vswitchd. */
+    const char *linux_name;   /* Name used by brctl. */
+    const struct ovsrec_bridge *ovs_bridge;  /* Bridge used by ovs-vswitchd. */
     int br_vlan;                /* VLAN tag. */
     struct svec ifaces;
 
@@ -623,25 +722,24 @@ handle_fdb_query_cmd(struct ofpbuf *buffer)
     int error;
 
     /* Parse the command received from brcompat_mod. */
-    error = parse_command(buffer, &seq, &linux_bridge, NULL, &count, &skip);
+    error = parse_command(buffer, &seq, &linux_name, NULL, &count, &skip);
     if (error) {
         return error;
     }
 
     /* Figure out vswitchd bridge and VLAN. */
-    cfg_read();
-    error = linux_bridge_to_ovs_bridge(linux_bridge, &ovs_bridge, &br_vlan);
+    error = linux_bridge_to_ovs_bridge(ovs, linux_name, 
+                                       &ovs_bridge, &br_vlan);
     if (error) {
         send_simple_reply(seq, error);
         return error;
     }
 
     /* Fetch the forwarding database using ovs-appctl. */
-    unixctl_command = xasprintf("fdb/show %s", ovs_bridge);
+    unixctl_command = xasprintf("fdb/show %s", ovs_bridge->name);
     error = execute_appctl_command(unixctl_command, &output);
     free(unixctl_command);
     if (error) {
-        free(ovs_bridge);
         send_simple_reply(seq, error);
         return error;
     }
@@ -657,7 +755,7 @@ handle_fdb_query_cmd(struct ofpbuf *buffer)
         struct mac *mac = &local_macs[n_local_macs];
         struct netdev *netdev;
 
-        error = netdev_open(iface_name, NETDEV_ETH_TYPE_NONE, &netdev);
+        error = netdev_open_default(iface_name, &netdev);
         if (netdev) {
             if (!netdev_get_etheraddr(netdev, mac->addr)) {
                 n_local_macs++;
@@ -730,7 +828,6 @@ handle_fdb_query_cmd(struct ofpbuf *buffer)
 
     /* Free memory. */
     ofpbuf_uninit(&query_data);
-    free(ovs_bridge);
 
     return 0;
 }
@@ -769,11 +866,11 @@ send_ifindex_reply(uint32_t seq, struct svec *ifaces)
 }
 
 static int
-handle_get_bridges_cmd(struct ofpbuf *buffer)
+handle_get_bridges_cmd(const struct ovsrec_open_vswitch *ovs,
+                       struct ofpbuf *buffer)
 {
     struct svec bridges;
-    const char *br_name;
-    size_t i;
+    size_t i, j;
 
     uint32_t seq;
 
@@ -789,22 +886,18 @@ handle_get_bridges_cmd(struct ofpbuf *buffer)
     }
 
     /* Get all the real bridges and all the fake ones. */
-    cfg_read();
     svec_init(&bridges);
-    cfg_get_subsections(&bridges, "bridge");
-    SVEC_FOR_EACH (i, br_name, &bridges) {
-        const char *iface_name;
-        struct svec ifaces;
-        size_t j;
+    for (i = 0; i < ovs->n_bridges; i++) {
+        const struct ovsrec_bridge *br = ovs->bridges[i];
 
-        svec_init(&ifaces);
-        get_bridge_ifaces(br_name, &ifaces, -1);
-        SVEC_FOR_EACH (j, iface_name, &ifaces) {
-            if (cfg_get_bool(0, "iface.%s.fake-bridge", iface_name)) {
-                svec_add(&bridges, iface_name);
+        svec_add(&bridges, br->name);
+        for (j = 0; j < br->n_ports; j++) {
+            const struct ovsrec_port *port = br->ports[j];
+
+            if (port->fake_bridge) {
+                svec_add(&bridges, port->name);
             }
         }
-        svec_destroy(&ifaces);
     }
 
     send_ifindex_reply(seq, &bridges);
@@ -814,12 +907,13 @@ handle_get_bridges_cmd(struct ofpbuf *buffer)
 }
 
 static int
-handle_get_ports_cmd(struct ofpbuf *buffer)
+handle_get_ports_cmd(const struct ovsrec_open_vswitch *ovs,
+                     struct ofpbuf *buffer)
 {
     uint32_t seq;
 
-    const char *linux_bridge;
-    char *ovs_bridge;
+    const char *linux_name;
+    const struct ovsrec_bridge *ovs_bridge;
     int br_vlan;
 
     struct svec ports;
@@ -827,13 +921,13 @@ handle_get_ports_cmd(struct ofpbuf *buffer)
     int error;
 
     /* Parse Netlink command. */
-    error = parse_command(buffer, &seq, &linux_bridge, NULL, NULL, NULL);
+    error = parse_command(buffer, &seq, &linux_name, NULL, NULL, NULL);
     if (error) {
         return error;
     }
 
-    cfg_read();
-    error = linux_bridge_to_ovs_bridge(linux_bridge, &ovs_bridge, &br_vlan);
+    error = linux_bridge_to_ovs_bridge(ovs, linux_name, 
+                                       &ovs_bridge, &br_vlan);
     if (error) {
         send_simple_reply(seq, error);
         return error;
@@ -842,17 +936,15 @@ handle_get_ports_cmd(struct ofpbuf *buffer)
     svec_init(&ports);
     get_bridge_ports(ovs_bridge, &ports, br_vlan);
     svec_sort(&ports);
-    svec_del(&ports, linux_bridge);
+    svec_del(&ports, linux_name);
     send_ifindex_reply(seq, &ports); /* XXX bonds won't show up */
     svec_destroy(&ports);
 
-    free(ovs_bridge);
-
     return 0;
 }
 
-static int
-brc_recv_update(void)
+static void
+brc_recv_update(const struct ovsrec_open_vswitch *ovs)
 {
     int retval;
     struct ofpbuf *buffer;
@@ -871,7 +963,7 @@ brc_recv_update(void)
         if (retval != EAGAIN) {
             VLOG_WARN_RL(&rl, "brc_recv_update: %s", strerror(retval));
         }
-        return retval;
+        return;
     }
 
     genlmsghdr = nl_msg_genlmsghdr(buffer);
@@ -886,55 +978,57 @@ brc_recv_update(void)
         goto error;
     }
 
-    if (cfg_lock(NULL, lock_timeout)) {
-        /* Couldn't lock config file. */
-        retval = EAGAIN;
+    /* Just drop the request on the floor if a valid configuration
+     * doesn't exist.  We don't immediately do this check, because we
+     * want to drain pending netlink messages. */
+    if (!ovs) {
+        VLOG_WARN_RL(&rl, "could not find valid configuration to update");
         goto error;
     }
 
     switch (genlmsghdr->cmd) {
     case BRC_GENL_C_DP_ADD:
-        retval = handle_bridge_cmd(buffer, true);
+        handle_bridge_cmd(ovs, buffer, true);
         break;
 
     case BRC_GENL_C_DP_DEL:
-        retval = handle_bridge_cmd(buffer, false);
+        handle_bridge_cmd(ovs, buffer, false);
         break;
 
     case BRC_GENL_C_PORT_ADD:
-        retval = handle_port_cmd(buffer, true);
+        handle_port_cmd(ovs, buffer, true);
         break;
 
     case BRC_GENL_C_PORT_DEL:
-        retval = handle_port_cmd(buffer, false);
+        handle_port_cmd(ovs, buffer, false);
         break;
 
     case BRC_GENL_C_FDB_QUERY:
-        retval = handle_fdb_query_cmd(buffer);
+        handle_fdb_query_cmd(ovs, buffer);
         break;
 
     case BRC_GENL_C_GET_BRIDGES:
-        retval = handle_get_bridges_cmd(buffer);
+        handle_get_bridges_cmd(ovs, buffer);
         break;
 
     case BRC_GENL_C_GET_PORTS:
-        retval = handle_get_ports_cmd(buffer);
+        handle_get_ports_cmd(ovs, buffer);
         break;
 
     default:
-        retval = EPROTO;
+        VLOG_WARN_RL(&rl, "received unknown brc netlink command: %d\n",
+                genlmsghdr->cmd);
+        break;
     }
 
-    cfg_unlock();
-
 error:
     ofpbuf_delete(buffer);
-    return retval;
+    return;
 }
 
 /* Check for interface configuration changes announced through RTNL. */
 static void
-rtnl_recv_update(void)
+rtnl_recv_update(const struct ovsrec_open_vswitch *ovs)
 {
     struct ofpbuf *buf;
 
@@ -976,28 +1070,21 @@ rtnl_recv_update(void)
                 return;
             }
 
-            if (cfg_lock(NULL, lock_timeout)) {
-                /* Couldn't lock config file. */
-                /* xxx this should try again and print error msg. */
-                ofpbuf_delete(buf);
-                return;
-            }
-
             if (!netdev_exists(port_name)) {
                 /* Network device is really gone. */
-                struct svec ports;
+                struct ovsrec_bridge *br = find_bridge(ovs, br_name);
 
                 VLOG_INFO("network device %s destroyed, "
                           "removing from bridge %s", port_name, br_name);
 
-                svec_init(&ports);
-                cfg_get_all_keys(&ports, "bridge.%s.port", br_name);
-                svec_sort(&ports);
-                if (svec_contains(&ports, port_name)) {
-                    del_port(br_name, port_name);
-                    rewrite_and_reload_config();
+                if (!br) {
+                    VLOG_WARN("no bridge named %s from which to remove %s", 
+                            br_name, port_name);
+                    ofpbuf_delete(buf);
+                    return;
                 }
-                svec_destroy(&ports);
+
+                del_port(br, port_name);
             } else {
                 /* A network device by that name exists even though the kernel
                  * told us it had disappeared.  Probably, what happened was
@@ -1044,7 +1131,6 @@ rtnl_recv_update(void)
                           "a device by that name exists (XS Tools 5.0.0?)",
                           port_name);
             }
-            cfg_unlock();
         }
         ofpbuf_delete(buf);
     }
@@ -1054,22 +1140,27 @@ int
 main(int argc, char *argv[])
 {
     struct unixctl_server *unixctl;
+    const char *remote;
+    struct ovsdb_idl *idl;
     int retval;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
-    register_fault_handlers();
     time_init();
     vlog_init();
-    parse_options(argc, argv);
+    vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_WARN);
+    vlog_set_levels(VLM_reconnect, VLF_ANY_FACILITY, VLL_WARN);
+
+    remote = parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
     process_init();
 
     die_if_already_running();
-    daemonize();
+    daemonize_start();
 
     retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ovs_fatal(retval, "could not listen for vlog connections");
+        exit(EXIT_FAILURE);
     }
 
     if (brc_open(&brc_sock)) {
@@ -1083,19 +1174,33 @@ main(int argc, char *argv[])
         }
     }
 
-    retval = cfg_read();
-    if (retval) {
-        ovs_fatal(retval, "could not read config file");
-    }
+    daemonize_complete();
+
+    idl = ovsdb_idl_create(remote, &ovsrec_idl_class);
 
     for (;;) {
+        const struct ovsrec_open_vswitch *ovs;
+        struct ovsdb_idl_txn *txn;
+        enum ovsdb_idl_txn_status status;
+
+        ovsdb_idl_run(idl);
+
+        txn = ovsdb_idl_txn_create(idl);
+
         unixctl_server_run(unixctl);
-        brc_recv_update();
+        ovs = ovsrec_open_vswitch_first(idl);
+        brc_recv_update(ovs);
+
+        if (!ovs && ovsdb_idl_has_ever_connected(idl)) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(&rl, "%s: database does not contain any Open vSwitch "
+                         "configuration", remote);
+        }
         netdev_run();
 
         /* If 'prune_timeout' is non-zero, we actively prune from the
-         * config file any 'bridge.<br_name>.port' entries that are no 
-         * longer valid.  We use two methods: 
+         * configuration of port entries that are no longer valid.  We 
+         * use two methods: 
          *
          *   1) The kernel explicitly notifies us of removed ports
          *      through the RTNL messages.
@@ -1103,20 +1208,58 @@ main(int argc, char *argv[])
          *   2) We periodically check all ports associated with bridges
          *      to see if they no longer exist.
          */
-        if (prune_timeout) {
-            rtnl_recv_update();
+        if (ovs && prune_timeout) {
+            rtnl_recv_update(ovs);
+#if 0
             prune_ports();
+#endif
 
             nl_sock_wait(rtnl_sock, POLLIN);
             poll_timer_wait(prune_timeout);
         }
 
+        while ((status = ovsdb_idl_txn_commit(txn)) == TXN_INCOMPLETE) {
+            ovsdb_idl_run(idl);
+            ovsdb_idl_wait(idl);
+            ovsdb_idl_txn_wait(txn);
+            poll_block();
+        }
+        ovsdb_idl_txn_destroy(txn);
+            
+        switch (status) {
+        case TXN_INCOMPLETE:
+            NOT_REACHED();
+        
+        case TXN_ABORTED:
+            /* Should not happen--we never call ovsdb_idl_txn_abort(). */
+            ovs_fatal(0, "transaction aborted");
+        
+        case TXN_SUCCESS:
+        case TXN_UNCHANGED:
+            break;
+        
+        case TXN_TRY_AGAIN:
+            /* xxx Handle this better! */
+            printf("xxx We need to try again!\n");
+            break;
+
+        case TXN_ERROR:
+            /* xxx Is this what we want to do? */
+            ovs_fatal(0, "transaction error");
+                
+        default:
+            NOT_REACHED();
+        }
+
         nl_sock_wait(brc_sock, POLLIN);
+        ovsdb_idl_wait(idl);
         unixctl_server_wait(unixctl);
         netdev_wait();
         poll_block();
     }
 
+    ovsdb_idl_destroy(idl);
+
     return 0;
 }
 
@@ -1141,11 +1284,10 @@ validate_appctl_command(void)
     }
 }
 
-static void
+static const char *
 parse_options(int argc, char *argv[])
 {
     enum {
-        OPT_LOCK_TIMEOUT = UCHAR_MAX + 1,
         OPT_PRUNE_TIMEOUT,
         OPT_APPCTL_COMMAND,
         VLOG_OPTION_ENUMS,
@@ -1154,7 +1296,6 @@ parse_options(int argc, char *argv[])
     static struct option long_options[] = {
         {"help",             no_argument, 0, 'h'},
         {"version",          no_argument, 0, 'V'},
-        {"lock-timeout",     required_argument, 0, OPT_LOCK_TIMEOUT},
         {"prune-timeout",    required_argument, 0, OPT_PRUNE_TIMEOUT},
         {"appctl-command",   required_argument, 0, OPT_APPCTL_COMMAND},
         DAEMON_LONG_OPTIONS,
@@ -1163,7 +1304,6 @@ parse_options(int argc, char *argv[])
         {0, 0, 0, 0},
     };
     char *short_options = long_options_to_short_options(long_options);
-    int error;
 
     appctl_command = xasprintf("%s/ovs-appctl %%s", ovs_bindir);
     for (;;) {
@@ -1183,10 +1323,6 @@ parse_options(int argc, char *argv[])
             OVS_PRINT_VERSION(0, 0);
             exit(EXIT_SUCCESS);
 
-        case OPT_LOCK_TIMEOUT:
-            lock_timeout = atoi(optarg);
-            break;
-
         case OPT_PRUNE_TIMEOUT:
             prune_timeout = atoi(optarg) * 1000;
             break;
@@ -1214,17 +1350,11 @@ parse_options(int argc, char *argv[])
     argv += optind;
 
     if (argc != 1) {
-        ovs_fatal(0, "exactly one non-option argument required; "
+        ovs_fatal(0, "database socket is non-option argument; "
                 "use --help for usage");
     }
 
-    cfg_init();
-    config_file = argv[0];
-    error = cfg_set_file(config_file);
-    if (error) {
-        ovs_fatal(error, "failed to add configuration file \"%s\"", 
-                config_file);
-    }
+    return argv[0];
 }
 
 static void
@@ -1237,7 +1367,6 @@ usage(void)
     printf("\nConfiguration options:\n"
            "  --appctl-command=COMMAND  shell command to run ovs-appctl\n"
            "  --prune-timeout=SECS    wait at most SECS before pruning ports\n"
-           "  --lock-timeout=MSECS    wait at most MSECS for CONFIG to unlock\n"
           );
     daemon_usage();
     vlog_usage();
index fafc3fe..71115f9 100644 (file)
@@ -12,21 +12,25 @@ ovs\-vswitchd \- Open vSwitch daemon
 .
 .SH SYNOPSIS
 .B ovs\-vswitchd
-\fIconfig\fR
+\fIdatabase\fR
 .
 .SH DESCRIPTION
 A daemon that manages and controls any number of Open vSwitch switches 
 on the local machine.
 .PP
-The mandatory \fIconfig\fR argument specifies a configuration file.
-For a description of \fBovs\-vswitchd\fR configuration syntax, see
-\fBovs\-vswitchd.conf\fR(5).
+The mandatory \fIdatabase\fR argument specifies the
+\fBovsdb\-server\fR from which \fBovs\-vswitchd\fR's configuration
+should be retrieved.  It takes one of the following forms:
+.so ovsdb/remote-active.man
 .PP
-At startup or upon receipt of a \fBSIGHUP\fR signal, \fBovs\-vswitchd\fR
-reads the configuration file.  It sets up Open vSwitch datapaths and then 
-operates switching across each bridge described in its configuration 
-files.  If a logfile was specified on the command line it will also 
-be opened or reopened.
+\fBovs\-vswitchd\fR retrieves its configuration from \fIdatabase\fR at
+startup.  It sets up Open vSwitch datapaths and then operates
+switching across each bridge described in its configuration files.  As
+the database changes, \fBovs\-vswitchd\fR automatically updates its
+configuration to match.
+.PP
+Upon receipt of a SIGHUP signal, \fBovs\-vswitchd\fR reopens its log
+file, if one was specified on the command line.
 .PP
 \fBovs\-vswitchd\fR switches may be configured with any of the following 
 features:
@@ -78,6 +82,8 @@ actually in use.  It requires the \fBbrcompat_mod.ko\fR kernel module
 to be loaded.
 .
 .so lib/daemon.man
+.so lib/ssl.man
+.so lib/ssl-bootstrap.man
 .so lib/vlog.man
 .so lib/common.man
 .so lib/leak-checker.man
@@ -86,16 +92,7 @@ to be loaded.
 \fBovs\-appctl\fR(8) can send commands to a running
 \fBovs\-vswitchd\fR process.  The currently supported commands are
 described below.  The command descriptions assume an understanding of
-how to configure Open vSwitch, as described in
-\fBovs-vswitchd.conf\fR(5).
-.SS "OVS\-VSWITCHD COMMANDS"
-These commands manage the \fBovs-vswitchd\fR process.
-.IP "\fBvswitchd/reload\fR"
-Reloads the \fBovs\-vswitchd\fR configuration file, as if a
-\fBSIGHUP\fR signal were received.  The command completes only after
-reloading is finished, in particular after all datapaths have been
-created and destroyed and ports added and removed as specified by the
-new configuration.
+how to configure Open vSwitch.
 .SS "BRIDGE COMMANDS"
 These commands manage bridges.
 .IP "\fBfdb/show\fR \fIbridge\fR"
@@ -157,6 +154,6 @@ Returns the hash value which would be used for \fImac\fR.
 .so lib/vlog-unixctl.man
 .SH "SEE ALSO"
 .BR ovs\-appctl (8),
-.BR ovs\-vswitchd.conf (5),
 .BR ovs\-brcompatd (8),
+.BR ovsdb\-server (1),
 \fBINSTALL.Linux\fR in the Open vSwitch distribution.
index 28491fc..ea997b0 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2008, 2009 Nicira Networks
+/* Copyright (c) 2008, 2009, 2010 Nicira Networks
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #include <string.h>
 
 #include "bridge.h"
-#include "cfg.h"
 #include "command-line.h"
 #include "compiler.h"
 #include "daemon.h"
 #include "dpif.h"
-#include "fault.h"
 #include "leak-checker.h"
-#include "mgmt.h"
 #include "netdev.h"
-#include "ovs-vswitchd.h"
+#include "ovsdb-idl.h"
 #include "poll-loop.h"
 #include "proc-net-compat.h"
 #include "process.h"
 #include "signals.h"
+#include "stream-ssl.h"
+#include "stream.h"
 #include "svec.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
-#include "vconn-ssl.h"
 #include "vconn.h"
+#include "vswitchd/vswitch-idl.h"
 
 #include "vlog.h"
 #define THIS_MODULE VLM_vswitchd
 
-static void parse_options(int argc, char *argv[]);
+static const char *parse_options(int argc, char *argv[]);
 static void usage(void) NO_RETURN;
-static void reload(struct unixctl_conn *, const char *args);
-
-static bool need_reconfigure;
-static struct unixctl_conn **conns;
-static size_t n_conns;
 
 int
 main(int argc, char *argv[])
 {
     struct unixctl_server *unixctl;
     struct signal *sighup;
+    struct ovsdb_idl *idl;
+    const char *remote;
+    bool need_reconfigure;
+    bool inited;
+    unsigned int idl_seqno;
     int retval;
 
+    proctitle_init(argc, argv);
     set_program_name(argv[0]);
-    register_fault_handlers();
     time_init();
     vlog_init();
-    parse_options(argc, argv);
+    remote = parse_options(argc, argv);
     signal(SIGPIPE, SIG_IGN);
     sighup = signal_register(SIGHUP);
     process_init();
 
     die_if_already_running();
-    daemonize();
+    daemonize_start();
 
     retval = unixctl_server_create(NULL, &unixctl);
     if (retval) {
-        ovs_fatal(retval, "could not listen for control connections");
+        exit(EXIT_FAILURE);
     }
-    unixctl_command_register("vswitchd/reload", reload);
 
-    retval = cfg_read();
-    if (retval) {
-        ovs_fatal(retval, "could not read config file");
-    }
-    mgmt_init();
-    bridge_init();
-    mgmt_reconfigure();
+    daemonize_complete();
+
+    idl = ovsdb_idl_create(remote, &ovsrec_idl_class);
+    idl_seqno = ovsdb_idl_get_seqno(idl);
 
     need_reconfigure = false;
+    inited = false;
     for (;;) {
-        if (need_reconfigure || signal_poll(sighup)) {
-            need_reconfigure = false;
+        if (signal_poll(sighup)) {
             vlog_reopen_log_file();
-            reconfigure();
         }
-        if (mgmt_run()) {
+        if (inited && bridge_run()) {
             need_reconfigure = true;
         }
-        if (bridge_run()) {
+        ovsdb_idl_run(idl);
+        if (idl_seqno != ovsdb_idl_get_seqno(idl)) {
+            idl_seqno = ovsdb_idl_get_seqno(idl);
             need_reconfigure = true;
         }
+        if (need_reconfigure) {
+            const struct ovsrec_open_vswitch *cfg;
+
+            need_reconfigure = false;
+            cfg = ovsrec_open_vswitch_first(idl);
+            if (cfg) {
+                if (inited) {
+                    bridge_reconfigure(cfg);
+                } else {
+                    bridge_init(cfg);
+                    inited = true;
+                }
+            }
+        }
         unixctl_server_run(unixctl);
         dp_run();
         netdev_run();
 
-        if (need_reconfigure) {
-            poll_immediate_wake();
-        }
         signal_wait(sighup);
-        mgmt_wait();
-        bridge_wait();
+        if (inited) {
+            bridge_wait();
+        }
+        ovsdb_idl_wait(idl);
         unixctl_server_wait(unixctl);
         dp_wait();
         netdev_wait();
@@ -121,39 +130,15 @@ main(int argc, char *argv[])
     return 0;
 }
 
-static void
-reload(struct unixctl_conn *conn, const char *args UNUSED)
-{
-    need_reconfigure = true;
-    conns = xrealloc(conns, sizeof *conns * (n_conns + 1));
-    conns[n_conns++] = conn;
-}
-
-void
-reconfigure(void)
-{
-    size_t i;
-
-    cfg_read();
-    bridge_reconfigure();
-    mgmt_reconfigure();
-
-    for (i = 0; i < n_conns; i++) {
-        unixctl_command_reply(conns[i], 202, NULL);
-    }
-    free(conns);
-    conns = NULL;
-    n_conns = 0;
-}
-
-static void
+static const char *
 parse_options(int argc, char *argv[])
 {
     enum {
         OPT_PEER_CA_CERT = UCHAR_MAX + 1,
         OPT_FAKE_PROC_NET,
         VLOG_OPTION_ENUMS,
-        LEAK_CHECKER_OPTION_ENUMS
+        LEAK_CHECKER_OPTION_ENUMS,
+        OPT_BOOTSTRAP_CA_CERT
     };
     static struct option long_options[] = {
         {"help",        no_argument, 0, 'h'},
@@ -163,13 +148,13 @@ parse_options(int argc, char *argv[])
         VLOG_LONG_OPTIONS,
         LEAK_CHECKER_LONG_OPTIONS,
 #ifdef HAVE_OPENSSL
-        VCONN_SSL_LONG_OPTIONS
+        STREAM_SSL_LONG_OPTIONS
         {"peer-ca-cert", required_argument, 0, OPT_PEER_CA_CERT},
+        {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT},
 #endif
         {0, 0, 0, 0},
     };
     char *short_options = long_options_to_short_options(long_options);
-    const char *config_file;
     int error;
 
     for (;;) {
@@ -199,12 +184,17 @@ parse_options(int argc, char *argv[])
 
         VLOG_OPTION_HANDLERS
         DAEMON_OPTION_HANDLERS
-        VCONN_SSL_OPTION_HANDLERS
         LEAK_CHECKER_OPTION_HANDLERS
 
 #ifdef HAVE_OPENSSL
+        STREAM_SSL_OPTION_HANDLERS
+
         case OPT_PEER_CA_CERT:
-            vconn_ssl_set_peer_ca_cert_file(optarg);
+            stream_ssl_set_peer_ca_cert_file(optarg);
+            break;
+
+        case OPT_BOOTSTRAP_CA_CERT:
+            stream_ssl_set_ca_cert_file(optarg, true);
             break;
 #endif
 
@@ -221,26 +211,21 @@ parse_options(int argc, char *argv[])
     argv += optind;
 
     if (argc != 1) {
-        ovs_fatal(0, "config file is only non-option argument; "
+        ovs_fatal(0, "database socket is only non-option argument; "
                 "use --help for usage");
     }
 
-    cfg_init();
-    config_file = argv[0];
-    error = cfg_set_file(config_file);
-    if (error) {
-       ovs_fatal(error, "failed to add configuration file \"%s\"", 
-                config_file);
-    }
+    return argv[0];
 }
 
 static void
 usage(void)
 {
     printf("%s: Open vSwitch daemon\n"
-           "usage: %s [OPTIONS] CONFIG\n"
-           "CONFIG is a configuration file in ovs-vswitchd.conf(5) format.\n",
+           "usage: %s [OPTIONS] DATABASE\n"
+           "where DATABASE is a socket on which ovsdb-server is listening.\n",
            program_name, program_name);
+    stream_usage("DATABASE", true, false, true);
     daemon_usage();
     vlog_usage();
     printf("\nLegacy compatibility options:\n"
diff --git a/vswitchd/ovs-vswitchd.conf.5.in b/vswitchd/ovs-vswitchd.conf.5.in
deleted file mode 100644 (file)
index 5c4a092..0000000
+++ /dev/null
@@ -1,813 +0,0 @@
-.\" -*- nroff -*-
-.de TQ
-.  br
-.  ns
-.  TP "\\$1"
-..
-.de IQ
-.  br
-.  ns
-.  IP "\\$1"
-..
-.de ST
-.  PP
-.  RS -0.15in
-.  I "\\$1"
-.  RE
-.  PP
-..
-.TH ovs\-vswitchd.conf 5 "June 2009" "Open vSwitch" "Open vSwitch Manual"
-.
-.SH NAME
-ovs\-vswitchd.conf \- configuration file for \fBovs\-vswitchd\fR
-.
-.SH DESCRIPTION
-This manual page explains how to configure \fBovs\-vswitchd\fR, the
-Open vSwitch virtual switch daemon.  Refer to \fBovs\-vswitchd\fR(8)
-for instructions on how to start, stop, and control the virtual switch
-daemon and for an overview of its features.
-.SS "Overview"
-\fBovs\-vswitchd\fR configuration is hierarchical.
-.ST "Global Configuration"
-A few aspects of configuration apply to the entire \fBovs\-vswitchd\fR
-process:
-.IP \(bu
-Remote management (see \fBRemote Management\fR below).
-.IP \(bu
-SSL key and certificate configuration (see \fBSSL Configuration\fR
-below).
-.ST "Bridge Configuration"
-\fBovs\-vswitchd\fR manages one or more ``bridges.''  A bridge is,
-conceptually, an Ethernet switch.  Properties configurable at the
-bridge level include:
-.
-.IP \(bu
-The set of bridge ports (see \fBBridge Configuration\fR below).
-.IP \(bu
-Mirroring of packets across ports and VLANs (see \fBPort mirroring
-(SPAN and RSPAN)\fR below).
-.IP \(bu
-Flow logging via NetFlow (see \fBNetFlow v5 Flow Logging\fR below).
-.IP \(bu
-Connectivity to an OpenFlow controller (see \fBOpenFlow Controller
-Connectivity\fR below).
-.IP \(bu
-Addresses on which to listen for OpenFlow management connections (see
-\fBOpenFlow Management Connections\fR below) or for snooping on the
-connection to the primary OpenFlow controller (see \fBOpenFlow
-Controller Connection Snooping\fR below).
-.PP
-.ST "Port Configuration"
-Each bridge has one or more ``ports.''  The main configurable property
-of a port is its 802.1Q VLAN configuration (see \fB802.1Q VLAN
-support\fR below).
-.PP
-Most commonly, a port has exactly one ``interface.''  Such a port
-logically corresponds to a port on a physical Ethernet switch.
-.PP
-A port that has more than one interface is a ``bonded port.''  Bonding
-allows for load balancing and fail-over (see \fBNetwork Device
-Bonding\fR below).
-.ST "Interface Configuration"
-There are two different kinds of interfaces:
-.IP "``external interfaces''"
-These interfaces are ordinary network devices, e.g. \fBeth0\fR on
-Linux.
-.IP "``internal interfaces''"
-These interfaces are simulated network device that sent and receive
-traffic.  Every bridge has one internal interface called the ``local
-interface'' and may also have additional internal interfaces.  It does
-not make sense to bond an internal interface, so the terms ``port''
-and ``interface'' are often used imprecisely for internal interfaces.
-.PP
-Interfaces have a few configurable properties of their own:
-.IP \(bu
-Ingress rate-limiting (see \fBInterface Rate-Limiting\fR below).
-.IP \(bu
-Ethernet address (internal interfaces only, see \fBBridge
-Configuration\fR below).
-.SS "Configuration File Syntax"
-The \fBovs\-vswitchd\fR configuration file syntax is based on
-key-value pairs, which are given
-one per line in the form \fIkey\fB=\fIvalue\fR.  Each \fIkey\fR
-consists of one or more parts separated by dots,
-e.g. \fIpart1\fB.\fIpart2\fB.\fIpart3\fR.  Each \fIpart\fR may consist
-only of the English letters, digits, and the special characters
-\fB_-@$:+\fR.  White space within \fIvalue\fR  and at the beginning of a
-line is significant, but is otherwise ignored.
-.PP
-If a single key is specified more than once, that key has multiple
-values, one value for each time the key is specified.  The ordering of
-key-value pairs, and the ordering of multiple values for a single key,
-within a configuration file is not significant.
-.PP
-Blank lines, lines that consist only of white space, and lines that
-begin with \fB#\fR (optionally preceded by white space) are ignored.
-Keep in mind that programs that modify the configuration file, such as 
-\fBovs\-brcompatd\fR and \fBovs-cfg-mod\fR, may alter the order of
-elements and 
-strip comments and blank lines.
-.PP
-The following subsections describe how key-value pairs are used to
-configure \fBovs\-vswitchd\fR.
-.SS "Bridge Configuration"
-A bridge (switch) with a given \fIname\fR is configured by specifying
-the names of its network devices as values for key
-\fBbridge.\fIname\fB.port\fR.
-.PP
-To designate network device \fInetdev\fR as an internal port, add
-\fBiface.\fInetdev\fB.internal=true\fR to the configuration file,
-which causes \fBovs\-vswitchd\fR to automatically creates
-\fInetdev\fR, which may then be configured using the usual system
-tools (e.g. \fBifconfig\fR, \fBip\fR).  An internal interface by
-default has a random Ethernet address, but you may configure a
-specific address by setting \fBiface.\fInetdev\fB.mac\fR to a MAC
-address in the format
-\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR, where each
-\fIx\fR is a hex digit.
-.PP
-A bridge with a given \fIname\fR always has an internal port with the
-same \fIname\fR, called the ``local port.''  This network device may
-be included
-in the bridge, by specifying it as one of the values for key
-\fBbridge.\fIname\fB.port\fR, or it may be omitted.  If it is
-included, then its MAC address is by default the lowest-numbered MAC
-address among the other bridge ports, ignoring other internal ports
-and bridge ports that are
-used as port mirroring destinations (see \fBPort Mirroring\fR, below).
-For this purpose, the MAC of a bonded port (see \fBNetwork Device
-Bonding\fR, below) is by default the MAC of its slave whose name is first in
-alphabetical order.
-There are two ways to modify this algorithm for selecting the MAC
-address of the local port:
-.IP \(bu
-To use a specific MAC address for the local port, set
-\fBbridge.\fIname\fB.mac\fR to a MAC address in the format
-\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fB:\fIxx\fR, where each
-\fIx\fR is a hex digit.
-.IP \(bu
-To override the MAC of a port for the purpose of this algorithm, set
-\fBport.\fIport\fB.mac\fR to a MAC address in the format described
-above.
-.PP
-If no valid MAC address can be determined
-either of these ways, then a MAC address is randomly generated.
-.PP
-The following syntax defines a bridge named \fBmybr\fR, configured
-with network devices \fBeth0\fR, \fBeth1\fR, and \fBeth2\fR:
-.RS
-.nf
-
-bridge.mybr.port=eth0
-bridge.mybr.port=eth1
-bridge.mybr.port=eth2
-
-.fi
-.RE
-.SS "802.1Q VLAN support"
-A bridge port may be configured either as a trunk port or as belonging
-to a single, untagged VLAN.  These two options are mutually exclusive,
-and a port must be configured in one way or the other.
-.ST "Trunk Ports"
-By default, bridge ports are trunk ports that carry all VLANs.  To
-limit the VLANs that a trunk port carries, define
-\fBvlan.\fIport\fB.trunks\fR to one or more integers between 0 and
-4095 designating VLANs.  Only frames that have an 802.1Q header with
-one of the listed VLANs are accepted on a trunk port.  If 0 is
-included in the list, then frames without an 802.1Q header are also
-accepted.  Other frames are discarded.
-.PP
-The following syntax makes network device \fBeth0\fR a trunk port that
-carries VLANs 1, 2, and 3:
-.PP
-.RS
-.nf
-
-vlan.eth0.trunks=1
-vlan.eth0.trunks=2
-vlan.eth0.trunks=3
-        
-.fi
-.RE
-.ST "Untagged VLAN Ports"
-A bridge port may be configured with an implicit, untagged VLAN.  
-Define key
-\fBvlan.\fIport\fB.tag\fR to an integer value \fIvid\fR between 0 and
-4095, inclusive, to designate the named \fIport\fR as a member
-of 802.1Q VLAN \fIvid\fR.  When \fIport\fR is assigned a VLAN tag this
-way, frames arriving on trunk ports will be forwarded to \fIport\fR
-only if they are tagged with VLAN \fIvid\fR, and frames arriving on
-other VLAN ports will be forwarded to \fIport\fR only if their
-\fIvid\fR values are equal.  Frames forwarded to \fIport\fR will not
-have an 802.1Q header.
-.PP
-When \fIvid\fR is 0, frames arriving on trunk ports without an 802.1Q
-VLAN header will also be forwarded to \fIport\fR.
-.PP
-When a frame with a 802.1Q header that indicates a nonzero VLAN is
-received on an implicit VLAN port, it is discarded.
-.PP
-The following syntax makes network device \fBeth0\fR a member of VLAN
-101:
-.PP
-.RS
-.nf
-
-vlan.eth0.tag=101
-        
-.fi
-.RE
-.SS "Network Device Bonding"
-Bonding allows multiple ``slave'' network devices to be treated as if
-they were a single virtual ``bonded'' network device.  It is useful for
-load balancing and fail-over.
-.PP
-\fBovs\-vswitchd\fR supports ``source load balancing'' (SLB) bonding, which
-assigns flows to slaves based on source MAC address, with periodic
-rebalancing as traffic patterns change.  This form of bonding does not
-require 802.3ad or other special support from the upstream switch to
-which the slave devices are connected.
-.PP
-To configure bonding, create a virtual bonding device by specifying
-the slave network device names as values for
-\fBbonding.\fIname\fB.slave\fR, then specify \fIname\fR as a bridge
-port.  The chosen \fIname\fR should not be the name of any real
-network device on the host system.
-.PP
-By default, bonding interfaces are enabled or disabled immediately
-when a carrier is detected or dropped on the underlying network
-device.  To insert a delay when carrier comes up or goes down before
-enabling or disabling an interface, set the value of
-\fBbonding.\fIname\fB.updelay\fR or
-\fBbonding.\fIname\fB.downdelay\fR, respectively, to a positive
-integer, interpreted in milliseconds.
-The \fBupdelay\fR setting is honored only when at least one bonded
-interface is already enabled.  When no interfaces are enabled, then
-the first bond interface to come up is enabled immediately.  The
-\fBdowndelay\fR setting is always honored.
-.PP
-The following syntax bonds \fBeth0\fR and \fBeth1\fR into a bonding
-device named \fBbond0\fR, which is added to bridge \fBmybr\fR along
-with physical network devices \fBeth2\fR and \fBeth3\fR:
-.PP
-.RS
-.nf
-
-bridge.mybr.port=bond0
-bridge.mybr.port=eth2
-bridge.mybr.port=eth3
-
-bonding.bond0.slave=eth0
-bonding.bond0.slave=eth1
-        
-.fi
-.RE
-.SS "Port Mirroring (SPAN and RSPAN)"
-\fBovs\-vswitchd\fR may be configured to send selected frames to special
-``mirrored'' ports, in addition to their normal destinations.  Mirroring
-traffic may also be referred to as SPAN or RSPAN, depending on the
-mechanism used for delivery.
-.PP
-Up to 32 instances of port mirroring may be configured on a given
-bridge.  Each must be given a name that is unique within the bridge.
-The keys associated with port mirroring instance \fIpmname\fR for
-bridge \fIbrname\fR begin with \fBmirror.\fIbrname\fB.\fIpmname\fR.
-.PP
-The selection of the frames to mirror and the form in which they
-should be output is configured separately for each port mirroring
-instances, through a subsection of
-\fBmirror.\fIbrname\fB.\fIpmname\fR, named \fBselect\fR, and
-\fBoutput\fR, respectively.
-.ST "Selecting Frames to Mirror"
-The values for the following keys, if specified, limit the frames that
-are chosen for mirroring.  If none of these keys is specified, then
-all frames received by the bridge are mirrored.  If more than one of
-these keys is specified, then a frame must meet all specified criteria
-to be mirrored.
-.TP
-\fBmirror.\fIbrname\fB.\fIpmname\fB.select.src-port=\fIport\fR
-.TQ
-\fBmirror.\fIbrname\fB.\fIpmname\fB.select.dst-port=\fIport\fR
-.TQ
-\fBmirror.\fIbrname\fB.\fIpmname\fB.select.port=\fIport\fR
-Frame received on \fIport\fR, output to \fIport\fR, or either received
-on or output to \fIport\fR, respectively.  \fIport\fR must be part of
-the bridge \fIbrname\fR; that is, it must be listed on
-\fBbridge.\fIbrname\fB.port\fR.
-.TP
-\fBmirror.\fIbrname\fB.\fIpmname\fB.select.vlan=\fIvid\fR
-.
-\fIvid\fR must be an integer between 0 and 4095, inclusive.  A nonzero
-\fIvid\fR selects frames that belong to VLAN \fIvid\fR, that is,
-frames that arrived on a trunk port tagged with VLAN \fIvid\fR or on a
-port that is configured as part of VLAN \fIvid\fR (see \fB802.1Q VLAN
-tagging\fR, above).  A \fIvid\fR of zero selects frames that do not
-belong to a VLAN, that is, frames that arrived on a trunk port without
-a VLAN tag or tagged with VLAN 0.
-.ST "Mirror Output"
-The values of the following keys determine how frames selected for
-mirroring are output.  Only one of the keys may be specified.
-.TP
-\fBmirror.\fIbrname\fB.\fIpmname\fB.output.port=\fIport\fR
-.
-Causes the selected frames to be sent out \fIport\fR, which must be
-part of the bridge \fIbrname\fR; that is, it must be listed on
-\fBbridge.\fIbrname\fB.port\fR.
-.IP
-Specifying a \fIport\fR in this way reserves that port exclusively for
-mirroring.  No frames other than those selected for mirroring will be
-forwarded to \fIport\fR, and any frames received on \fIport\fR will be
-discarded.  This type of mirroring may be referred to as SPAN.
-.TP
-\fBmirror.\fIbrname\fB.\fIpmname\fB.output.vlan=\fIvid\fR
-.
-Causes the selected frames to be sent on the VLAN numbered \fIvid\fR,
-which must be an integer between 0 and 4095, inclusive.  The frames
-will be sent out all ports that trunk VLAN \fIvid\fR, as well as any
-ports with implicit VLAN \fIvid\fR.  When a mirrored frame is sent out
-a trunk port, the frame's VLAN tag will be set to \fIvid\fR, replacing
-any existing tag; when it is sent out an implicit VLAN port, the frame
-will not be tagged.  This type of mirroring may be referred to as
-RSPAN.
-.IP
-Please note that mirroring to a VLAN can disrupt a network that
-contains unmanaged switches.  Consider an unmanaged physical switch
-with two ports: port 1, connected to an end host, and port 2,
-connected to an Open vSwitch configured to mirror received packets
-into VLAN 123 on port 2.  Suppose that the end host sends a packet on
-port 1 that the physical switch forwards to port 2.  The Open vSwitch
-forwards this packet to its destination and then reflects it back on
-port 2 in VLAN 123.  This reflected packet causes the unmanaged
-physical switch to replace the MAC learning table entry, which
-correctly pointed to port 1, with one that incorrectly points to port
-2.  Afterward, the physical switch will direct packets destined for
-the end host to the Open vSwitch on port 2, instead of to the end host
-on port 1, disrupting connectivity.  If mirroring to a VLAN is desired
-in this scenario, then the physical switch must be replaced by one
-that learns Ethernet addresses on a per-VLAN basis.  In addition,
-learning should be disabled on the VLAN containing mirrored traffic.
-If this is not done then intermediate switches will learn the MAC
-address of each end host from the mirrored traffic.  If packets being
-sent to that end host are also mirrored, then they will be dropped
-since the switch will attempt to send them out the input port.
-Disabling learning for the VLAN will cause the switch to correctly
-send the packet out all ports configured for that VLAN.  If Open
-vSwitch is being used as an intermediate switch learning can be disabled
-by setting the key \fBvlan.\fIbrname\fB.disable-learning=\fIvid\fR
-to the mirrored VLAN.
-.ST "Example"
-The following \fBovs\-vswitchd\fR configuration copies all frames received
-on \fBeth1\fR or \fBeth2\fR to \fBeth3\fR.
-.PP
-.RS
-.nf
-
-bridge.mybr.port=eth1
-bridge.mybr.port=eth2
-bridge.mybr.port=eth3
-
-mirror.mybr.a.select.src-port=eth1
-mirror.mybr.a.select.src-port=eth2
-mirror.mybr.a.output.port=eth3
-        
-.fi
-.RE
-.SS "Interface Rate-Limiting"
-Traffic policing and shaping are configured on interfaces.  Policing
-defines a hard limit at which traffic that exceeds the specified rate is
-dropped.  Shaping uses queues to delay packets so that egress traffic
-leaves at the specified rate.
-
-.ST "Ingress Policing"
-The rate at which traffic is allowed to enter through a interface may be 
-configured with ingress policing.  Note that "ingress" is from the 
-perspective of \fBovs\-vswitchd\fR.  If configured on a physical interface, 
-then it limits the rate at which traffic is allowed into the system from 
-the outside.  If configured on a virtual interface that is connected to 
-a virtual machine, then it limits the rate at which the guest is able to 
-transmit.
-
-The rate is specified in kilobits (1000 bits) per second with a maximum 
-burst size specified in kilobits (1000 bits).  The burst size should be at 
-least the size of the interface's MTU.  
-
-An interface may be configured to enforce ingress policing by defining the
-key \fBport.\fIname\fB.ingress.policing-rate\fR with an integer
-indicating the rate.  The interface \fIname\fR will only allow traffic to be
-received at the rate specified in kilobits per second.  If the rate is zero 
-or the key is not defined, then ingress policing is disabled.
-
-If ingress policing is enabled, then the burst rate may be set by defining 
-the key \fBport.\fIname\fB.ingress.policing-burst\fR with an integer 
-indicating the burst rate in kilobits.  If the key is not supplied or is 
-zero, then the default burst is 10 kilobits.
-
-.PP
-The following syntax limits interface \fBeth1\fR to receiving traffic at
-\fB512\fR kilobits per second with a burst of \fB20\fR kilobits:
-.PP
-.RS
-.nf
-
-port.eth1.ingress.policing-rate=512
-port.eth1.ingress.policing-burst=20
-
-.fi
-.SS "NetFlow v5 Flow Logging"
-NetFlow is a protocol that exports a number of details about terminating
-IP flows, such as the principals involved and duration.  A bridge may be
-configured to send NetFlow v5 records to NetFlow collectors when flows
-end.  To enable, define the key \fBnetflow.\fIbridge\fB.host\fR for each
-collector in the form \fIip\fB:\fIport\fR.  Records from \fIbridge\fR
-will be sent to each \fIip\fR on UDP \fIport\fR.  The \fIip\fR must
-be specified numerically, not as a DNS name.
-
-In addition to terminating flows, NetFlow can also send records at a set
-interval for flows that are still active.  This interval can be configured
-by defining the key \fBnetflow.\fIbridge\fB\.active-timeout\fR.  The value
-is in seconds.  An active timeout of 0 will disable this functionality.  By
-default there is timeout value of 600 seconds.
-
-The NetFlow messages will use the datapath index for the engine type and id.
-This can be overridden with the \fBnetflow.\fIbridge\fB.engine-type\fR and
-\fBnetflow.\fIbridge\fB.engine-id\fR, respectively.  Each takes a value
-between 0 and 255, inclusive.
-
-Many NetFlow collectors do not expect multiple switches to be
-sending messages from the same host, and they do not store the engine
-information which could be used to disambiguate the traffic.  To prevent
-flows from multiple switches appearing as if they came on the interface,
-add \fBnetflow.\fIbridge\fB.add-id-to-iface=true\fR to the configuration
-file.  This will place the least significant 7 bits of the engine id
-into the most significant bits of the ingress and egress interface fields
-of flow records.  When this option is enabled, a maximum of 508 ports are
-supported.  By default, this behavior is disabled.
-
-The egress interface field normally contains the OpenFlow port number,
-however, certain port values have special meaning: 0xFFFF indicates
-flooding, 0xFFFE is multiple controller-specified output interfaces, and
-0xFFFD means that packets from the flow were dropped.  If add-id-to-iface
-is enabled then these values become 0x1FF, 0x1FE, and 0x1FD respectively.
-
-The following syntax sends NetFlow records for \fBmybr\fR to the NetFlow
-collector \fBnflow.example.com\fR on UDP port \fB9995\fR:
-.PP
-.RS
-.nf
-
-netflow.mybr.host=nflow.example.com:9995
-
-.fi
-.RE
-.SS "sFlow Monitoring"
-sFlow(R) is a protocol for monitoring switches.  A bridge may be
-configured to send sFlow records to sFlow collectors by defining the
-key \fBsflow.\fIbridge\fB.host\fR for each collector in the form
-\fIip\fR[\fB:\fIport\fR].  Records from \fIbridge\fR will be sent to
-each \fIip\fR on UDP \fIport\fR.  The \fIip\fR must be specified
-numerically, not as a DNS name.  If \Iport\fR is omitted, port 6343 is
-used.
-.PP
-By default, 1 out of every 400 packets is sent to the configured sFlow
-collector.  To override this, set \fBsflow.\fIbridge\fB.sampling\fR to
-the number of switched packets out of which one, on average, will be
-sent to the sFlow collector, e.g. a value of 1 sends every packet to
-the collector, a value of 2 sends 50% of the packets to the collector,
-and so on.
-.PP
-\fBovs\-vswitchd\fR also occasionally sends switch port statistics to
-sFlow collectors, by default every 30 seconds.  To override this, set
-\fBsflow.\fIbridge\fB.polling\fR to a duration in seconds.
-.PP
-By default, \fBovs\-vswitchd\fR sends the first 128 bytes of sampled
-packets to sFlow collectors.  To override this, set
-\fBsflow.\fIbridge\fB.header\fR to a size in bytes.
-.PP
-The sFlow module must be able to report an ``agent address'' to sFlow
-collectors, which should be an IP address for the Open vSwitch that is
-persistent and reachable over the network, if possible.  If a
-local IP is configured as \fBbridge.\fIbridge\fB.controller.ip\fR,
-then that IP address is used by default.  To override this default,
-set \fBsflow.\fIbridge\fB.agent\fR to the name of a network device, in
-which case the IP address set on that device is used.  If no IP
-address can be determined either way, sFlow is disabled.
-.SS "Remote Management"
-A \fBovs\-vswitchd\fR instance may be remotely managed by a controller that
-supports the OpenFlow Management Protocol, such as NOX.  This
-functionality is enabled by setting the key \fBmgmt.controller\fR to one 
-of the following values:
-.
-.IP "\fBssl:\fIip\fR[\fB:\fIport\fR]"
-The specified SSL \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-SSL must be configured when this form is used (see \fBSSL
-Configuration\fR, below).
-.
-.IP "\fBtcp:\fIip\fR[\fB:\fIport\fR]"
-The specified TCP \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-.PP
-The maximum time between attempts to connect to the controller may be
-specified in integral seconds with the \fBmgmt.max-backoff\fR key.  The
-default maximum backoff is 8 seconds, and the minimum value is 1
-second.
-
-An inactivity probe may be configured with the \fBmgmt.inactivity-probe\fR
-key.  If \fBovs\-vswitchd\fR does not communicate with the controller for the
-specified number of seconds, it will send a probe.  If a response is not
-received for an additional amount of that time, \fBovs\-vswitchd\fR assumes
-the connection has been broken and attempts to reconnect.  The default
-and minimum values are both 5 seconds.
-
-A management id may be specified with the \fBmgmt.id\fR key.  It takes
-an id in the form of exactly 12 hexadecimal digits.  If one is not
-specified, a random id is generated each time \fBovs\-vswitchd\fR is started.
-.fi
-.RE
-.SS "OpenFlow Controller Connectivity"
-\fBovs\-vswitchd\fR can perform all configured bridging and switching
-locally, or it can be configured to connect a given bridge to an
-external OpenFlow controller, such as NOX.  Its behavior depends on
-the \fBbridge.\fIname\fB.controller\fR setting:
-.
-.TP
-\fI\[la]unset\[ra]\fR
-When the key is not set, the behavior depends on whether remote 
-management is configured.  If management is configured, then the switch 
-will connect to the controller specified on \fBmgmt.controller\fR.  If 
-management is not configured, the switch will perform all configured 
-bridging and switching locally.
-.
-.TP
-\fI\[la]empty\[ra]\fR
-Setting an empty string value disables controller connectivity.  The
-switch will perform all configured bridging and switching locally.
-.
-.TP
-\fBdiscover\fR
-Use controller discovery to find the local OpenFlow controller.
-Refer to \fBovs\-openflowd\fR(8) for information on how to configure a DHCP
-server to support controller discovery.  The following additional
-options control the discovery process:
-.
-.RS
-.TP
-\fBbridge.\fIname\fB.controller.accept-regex=\fIregex\fR
-A POSIX extended regular expression against which the discovered
-controller location is validated.  Only controllers whose names match
-the regular expression will be accepted.
-.IP
-The default regular expression is \fBssl:.*\fR, meaning that only SSL
-controller connections will be accepted, when SSL is configured (see
-\fBSSL Configuration\fR), and \fBtcp:.*\fR otherwise, meaning that only
-TCP controller connections will be accepted.
-.IP
-The regular expression is implicitly anchored at the beginning of the
-controller location string, as if it begins with \fB^\fR.
-.TP
-\fBbridge.\fIname\fB.controller.update-resolv.conf=\fBtrue\fR|\fBfalse\fR
-By default, or if this is set to \fBtrue\fR, \fBovs\-vswitchd\fR overwrites
-the system's \fB/etc/resolv.conf\fR with domain information and DNS
-servers obtained via DHCP.  If this setting is \fBfalse\fR,
-\fBovs\-vswitchd\fR will not modify \fB/etc/resolv.conf\fR.
-.IP
-\fBovs\-vswitchd\fR will only modify \fBresolv.conf\fR if the DHCP response
-that it receives specifies one or more DNS servers.
-.RE
-.
-.TP
-\fBssl:\fIip\fR[\fB:\fIport\fR]
-The specified SSL \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-SSL must be configured when this form is used (see \fBSSL
-Configuration\fR, below).
-.
-.TP
-\fBtcp:\fIip\fR[\fB:\fIport\fR]
-The specified TCP \fIport\fR (default: 6633) on the host at the given
-\fIip\fR, which must be expressed as an IP address (not a DNS name).
-.
-.TP
-\fBunix:\fIfile\fR
-The Unix domain server socket named \fIfile\fR.
-.PP
-The datapath ID used by the bridge to identify itself to the remote
-controller may be specified as \fBbridge.\fIname\fB.datapath-id\fR,
-in the form of exactly 12 hexadecimal digits.  If the datapath ID
-is not specified, then it defaults to the bridge's MAC address (see
-\fBBridge Configuration\fR, above, for information on how the bridge's
-MAC address is chosen).
-.ST "Local Port Network Configuration"
-When an external controller is configured, but controller discovery is
-not in use, the following additional settings are honored:
-.TP
-\fBbridge.\fIname\fB.controller.in-band=\fBtrue\fR|\fBfalse\fR
-By default, or if this is set to \fBtrue\fR, \fBovs\-vswitchd\fR connects
-to the controller in-band.  If this is set to \fBfalse\fR,
-\fBovs\-vswitchd\fR connects to the controller out-of-band.  Refer to
-\fBovs\-openflowd\fR(8) for a description of in-band and out-of-band control.
-.IP "\fBbridge.\fIname\fB.controller.ip=\fIip\fR"
-If specified, the IP address to configure on the bridge's local port.
-.IP "\fBbridge.\fIname\fB.controller.netmask=\fInetmask\fR"
-When an IP is specified, the corresponding netmask.  The default is
-255.255.255.0 for a Class C IP address, 255.255.0.0 for Class B, and
-255.0.0.0 for Class A.
-.IP "\fBbridge.\fIname\fB.controller.gateway=\fIip\fR"
-When an IP is specified, the corresponding IP gateway.  There is no
-default gateway.
-.ST "Controller Failure Settings"
-The following additional settings take effect when any remote
-controller is configured:
-.IP "\fBbridge.\fIname\fB.controller.inactivity-probe=\fIsecs\fR"
-This optional setting may be set to \fIsecs\fR, a number of seconds.
-The minimum value of \fIsecs\fR is 5 seconds.  The default is taken
-from \fBmgmt.inactivity-probe\fR (see above).
-.IP
-When the switch is connected to the controller, it waits for a
-message to be received from the controller for \fIsecs\fR seconds
-before it sends a inactivity probe to the controller.  After sending
-the inactivity probe, if no response is received for an additional
-\fIsecs\fR seconds, \fBovs-vswitchd\fR assumes that the connection has
-been broken and attempts to reconnect.
-.IP
-Changing the inactivity probe interval also changes the interval
-before entering standalone mode (see below).
-.IP "\fBbridge.\fIname\fB.controller.fail-mode=\fBstandalone\fR|\fBsecure\fR"
-.IQ "\fBmgmt.fail-mode=standalone\fR|\fBsecure\fR"
-When a controller is configured, it is, ordinarily, responsible for
-setting up all flows on the switch.  Thus, if the connection to
-the controller fails, no new network connections can be set up.  If
-the connection to the controller stays down long enough, no packets
-can pass through the switch at all.
-.IP
-The first of these that is set takes effect.
-If the value is \fBstandalone\fR, or if neither of these settings
-is set, \fBovs\-vswitchd\fR will take over
-responsibility for setting up
-flows when no message has been received from the controller for three
-times the inactivity probe interval (see above).  In this mode,
-\fBovs\-vswitchd\fR causes the datapath to act like an ordinary
-MAC-learning switch.  \fBovs\-vswitchd\fR will continue to retry connecting
-to the controller in the background and, when the connection succeeds,
-it discontinues its standalone behavior.
-.IP
-If this option is set to \fBsecure\fR, \fBovs\-vswitchd\fR will not
-set up flows on its own when the controller connection fails.
-.IP "\fBbridge.\fIname\fB.controller.max-backoff=\fIsecs\fR"
-Sets the maximum time between attempts to connect to the controller to
-\fIsecs\fR, which must be at least 1.  The actual interval between
-connection attempts starts at 1 second and doubles on each failing
-attempt until it reaches the maximum.  The default maximum backoff
-time is taken from \fBmgmt.max-backoff\fR.
-.ST "Controller Rate-Limiting"
-These settings configure how the switch applies a ``token
-bucket'' to limit the rate at which packets in unknown flows are
-forwarded to the OpenFlow controller for flow-setup processing.  This
-feature prevents a single bridge from overwhelming a controller.
-.PP
-In addition, when a high rate triggers rate-limiting,
-\fBovs\-vswitchd\fR queues controller packets for each port and
-transmits them to the controller at the configured rate.  The number
-of queued packets is limited by a ``burst size'' parameter.  The
-packet queue is shared fairly among the ports on a bridge.
-.PP
-\fBovs\-vswitchd\fR maintains two such packet rate-limiters per
-bridge.  One of these applies to packets sent up to the controller
-because they do not correspond to any flow.  The other applies to
-packets sent up to the controller by request through flow actions.
-When both rate-limiters are filled with packets, the actual rate that
-packets are sent to the controller is up to twice the specified rate.
-.IP "\fBbridge.\fIname\fB.controller.rate-limit=\fIrate\fR"
-.IQ "\fBmgmt.rate-limit=\fIrate\fR"
-Limits the maximum rate at which packets will be forwarded to the
-OpenFlow controller to \fIrate\fR packets per second.  A rate specified
-explicitly for \fIname\fR overrides a value configured using the
-\fBmgmt.rate-limit\fR key.
-.IP
-If neither one of these settings is set, then the bridge does not
-limit the rate at which packets are forwarded to the controller.
-.IP "\fBbridge.\fIname\fB.controller.burst-limit=\fIburst\fR"
-.IQ "\fBmgmt.burst-limit=\fIburst\fR"
-Sets the maximum number of unused packet credits that the bridge will
-allow to accumulate during the time in which no packets are being
-forwarded to the OpenFlow controller to \fIburst\fR (measured in
-packets).  The default \fIburst\fR is one-quarter of the \fIrate\fR
-specified in the rate-limit setting.
-.IP
-A burst specified explicitly for \fIname\fR overrides a value configured 
-using the \fBmgmt.burst-limit\fR key.  This option takes effect only 
-when a rate-limit is specified.
-.ST "Remote Command Execution Settings"
-These settings configure the commands that remote OpenFlow connections
-are allowed to invoke using (e.g.) \fBovs\-ofctl execute\fR.  To be
-permitted, a command name must be whitelisted and must not be
-blacklisted.  When the whitelist and blacklist permit a command name,
-\fBovs\-vswitchd\fR looks for a program with the same name as the command
-in the commands directory (see below).  Other directories are not
-searched.
-.IP "\fBbridge.\fIname\fB.controller.commands.acl=\fIglob\fR"
-Whitelists commands whose names match shell glob pattern \fIglob\fR,
-allowing those commands to be invoked by the remote controller.
-.IP
-By default, no commands are whitelisted, so this setting is mandatory
-if any remote command execution is to be allowed.
-.IP "\fBbridge.\fIname\fB.controller.commands.acl=\fB!\fR\fIglob\fR"
-Blacklists commands whose names match shell glob pattern \fIglob\fR,
-prohibiting those commands from being invoked by the remote
-controller.  Command names that include characters other than upper-
-and lower-case English letters, digits, and the underscore and hyphen
-characters are blacklisted unconditionally.
-.IP "\fBbridge.\fIname\fB.controller.commands.dir=\fIdirectory\fR"
-Sets the directory searched for remote command execution to
-\fIdirectory\fR.  The default directory is
-\fB@pkgdatadir@/commands\fR.
-.SS "SSL Configuration"
-When \fBovs\-vswitchd\fR is configured to connect over SSL for management or
-for controller connectivity, the following settings are required:
-.TP
-\fBssl.private-key=\fIprivkey.pem\fR
-Specifies a PEM file containing the private key used as the 
-switch's identity for SSL connections to the controller.
-.TP
-\fBssl.certificate=\fIcert.pem\fR
-Specifies a PEM file containing a certificate, signed by the
-certificate authority (CA) used by the controller and manager, that
-certifies the switch's private key, identifying a trustworthy
-switch.
-.TP
-\fBssl.ca-cert=\fIcacert.pem\fR
-Specifies a PEM file containing the CA certificate used to verify that
-the switch is connected to a trustworthy controller.
-.PP
-These files are read only once, at \fBovs\-vswitchd\fR startup time.  If
-their contents change, \fBovs\-vswitchd\fR must be killed and restarted.
-.PP
-These SSL settings apply to all SSL connections made by the switch.
-.ST "CA Certificate Bootstrap"
-Ordinarily, all of the files named in the SSL configuration must exist
-when \fBovs\-vswitchd\fR starts.  However, if \fBssl.bootstrap-ca-cert\fR
-is set to \fBtrue\fR, then \fBovs\-vswitchd\fR will attempt to obtain the
-CA certificate from the controller on its first SSL connection and
-save it to the named PEM file.  If it is successful, it will
-immediately drop the connection and reconnect, and from then on all
-SSL connections must be authenticated by a certificate signed by the
-CA certificate thus obtained.
-.PP
-\fBThis option exposes the SSL connection to a man-in-the-middle
-attack obtaining the initial CA certificate\fR, but it may be useful
-for bootstrapping.
-.PP
-This option is only useful if the controller sends its CA certificate
-as part of the SSL certificate chain.  The SSL protocol does not
-require the controller to send the CA certificate, but
-\fBcontroller\fR(8) can be configured to do so with the
-\fB--peer-ca-cert\fR option.
-.SS "OpenFlow Management Connections"
-By default, each bridge \fIname\fR listens for OpenFlow management
-connections on a Unix domain socket named
-\fB@RUNDIR@/\fIname\fB.mgmt\fR.  This socket can be used to perform
-local OpenFlow monitoring and administration, e.g., \fBovs\-ofctl dump-flows
-unix:@RUNDIR@/\fIname\fB.mgmt\fR to display the flows currently set up
-in bridge \fIname\fR.
-.PP
-If \fBbridge.\fIname\fB.openflow.listeners\fR is set to one or more
-values, \fBovs\-vswitchd\fR instead listens on the specified connection
-methods.  Acceptable connection methods include:
-.RS
-.IP "\fBpunix:\fIfile\fR"
-Listens for connections on the Unix domain server socket named \fIfile\fR.
-.IP "\fBpssl:\fR[\fIport\fR]"
-Listens for SSL connections on \fIport\fR (default: 6633).  SSL must
-be configured when this form is used (see \fBSSL Configuration\fR,
-above).
-.IP "\fBptcp:\fR[\fIport\fR][\fB:\fIip\fR]"
-Listens for TCP connections on \fIport\fR (default: 6633).
-By default, \fB\ovs\-vswitchd\fR listens for connections to any local
-IP address, but \fIip\fR may be specified to limit connections to the
-specified local \fIip\fR.
-.RE
-To entirely disable listening for management connections, set
-\fBbridge.\fIname\fB.openflow.listeners\fR to the single value
-\fBnone\fR.
-
-.SS "OpenFlow Controller Connection Snooping"
-By default, each bridge \fIname\fR listens for OpenFlow controller
-connection snooping connections on a Unix domain socket named
-\fB@RUNDIR@/\fIname\fB.snoop\fR.  A client that connects to this
-socket, e.g., \fBovs\-ofctl monitor unix:@RUNDIR@/\fIname\fB.snoop\fR, will
-receive a copy of every OpenFlow message sent by the switch to the
-controller, or vice versa, on the primary OpenFlow controller
-connection.
-.PP
-If \fBbridge.\fIname\fB.openflow.snoops\fR is set to one or more
-values, \fBovs\-vswitchd\fR instead listens on the specified connection
-methods.  The acceptable connection methods are the same as for
-OpenFlow management connections (see above).
-.PP
-To entirely disable controller connection snooping, set
-\fBbridge.\fIname\fB.openflow.snoops\fR to the single value
-\fBnone\fR.
-.SH "SEE ALSO"
-.BR ovs\-brcompatd (8),
-.BR ovs\-cfg\-mod (8),
-.BR ovs\-vswitchd (8)
index 7a59526..68ae1ac 100644 (file)
@@ -256,7 +256,7 @@ proc_net_compat_update_vlan(const char *tagged_dev, const char *trunk_dev,
         }
         if (!vlan) {
             /* Create a new compat_vlan for (trunk_dev,vid). */
-            vlan = xcalloc(1, sizeof *vlan);
+            vlan = xzalloc(sizeof *vlan);
             vlan->trunk_dev = xstrdup(trunk_dev);
             vlan->vid = vid;
             vlan->vlan_dev = xasprintf("%s.%d", trunk_dev, vid);
diff --git a/vswitchd/vswitch-idl.ann b/vswitchd/vswitch-idl.ann
new file mode 100644 (file)
index 0000000..b8e457d
--- /dev/null
@@ -0,0 +1,21 @@
+# -*- python -*-
+
+# This code, when invoked by "ovsdb-idlc annotate" (by the build
+# process), annotates vswitch.ovsschema with additional data that give
+# the ovsdb-idl engine information about the types involved, so that
+# it can generate more programmer-friendly data structures.
+
+s["idlPrefix"] = "ovsrec_"
+s["idlHeader"] = "\"vswitchd/vswitch-idl.h\""
+s["tables"]["Open_vSwitch"]["columns"]["bridges"]["type"]["keyRefTable"] = "Bridge"
+s["tables"]["Open_vSwitch"]["columns"]["controller"]["type"]["keyRefTable"] = "Controller"
+s["tables"]["Open_vSwitch"]["columns"]["ssl"]["type"]["keyRefTable"] = "SSL"
+s["tables"]["Bridge"]["columns"]["ports"]["type"]["keyRefTable"] = "Port"
+s["tables"]["Bridge"]["columns"]["mirrors"]["type"]["keyRefTable"] = "Mirror"
+s["tables"]["Bridge"]["columns"]["netflow"]["type"]["keyRefTable"] = "NetFlow"
+s["tables"]["Bridge"]["columns"]["sflow"]["type"]["keyRefTable"] = "sFlow"
+s["tables"]["Bridge"]["columns"]["controller"]["type"]["keyRefTable"] = "Controller"
+s["tables"]["Port"]["columns"]["interfaces"]["type"]["keyRefTable"] = "Interface"
+s["tables"]["Mirror"]["columns"]["select_src_port"]["type"]["keyRefTable"] = "Port"
+s["tables"]["Mirror"]["columns"]["select_dst_port"]["type"]["keyRefTable"] = "Port"
+s["tables"]["Mirror"]["columns"]["output_port"]["type"]["keyRefTable"] = "Port"
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
new file mode 100644 (file)
index 0000000..022d65f
--- /dev/null
@@ -0,0 +1,237 @@
+{"name": "ovs_vswitchd_db",
+ "comment": "Configuration for one Open vSwitch daemon.",
+ "tables": {
+   "Open_vSwitch": {
+     "comment": "Configuration for an Open vSwitch daemon.",
+     "columns": {
+       "bridges": {
+         "comment": "Set of bridges managed by the daemon.",
+         "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+       "controller": {
+         "comment": "Default Controller used by bridges.",
+         "type": {"key": "uuid", "min": 0, "max": 1}},
+       "managers": {
+         "comment": "Remote database clients to which the Open vSwitch's database server should connect or to which it should listen.",
+         "type": {"key": "string", "min": 0, "max": "unlimited"}},
+       "ssl": {
+         "comment": "SSL used globally by the daemon.",
+         "type": {"key": "uuid", "min": 0, "max": 1}},
+       "next_cfg": {
+         "comment": "Sequence number for client to increment when it modifies the configuration and wishes to wait for Open vSwitch to finish applying the changes.",
+         "type": "integer"},
+       "cur_cfg": {
+         "comment": "Sequence number that Open vSwitch sets to the current value of 'next_cfg' after it finishing applying a set of configuration changes.",
+         "type": "integer"}}},
+   "Bridge": {
+     "comment": "Configuration for a bridge within an Open_vSwitch.",
+     "columns": {
+       "name": {
+         "comment": "Bridge identifier.  Should be alphanumeric and no more than about 8 bytes long.  Must be unique among the names of ports, interfaces, and bridges on a host.",
+         "type": "string"},
+       "datapath_type": {
+         "comment": "Name of datapath provider.  The kernel datapath has type \"system\".  The userspace datapath has type \"netdev\".",
+         "type": "string"},
+       "datapath_id": {
+         "comment": "Reports the OpenFlow datapath ID in use.  Exactly 12 hex digits.",
+         "type": {"key": "string", "min": 0, "max": 1},
+         "ephemeral": true},
+       "ports": {
+         "comment": "Ports included in the bridge.",
+         "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+       "mirrors": {
+         "comment": "Port mirroring configuration.",
+         "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+       "netflow": {
+         "comment": "NetFlow configuration.",
+         "type": {"key": "uuid", "min": 0, "max": 1}},
+       "sflow": {
+         "comment": "sFlow configuration.",
+         "type": {"key": "uuid", "min": 0, "max": 1}},
+       "controller": {
+         "comment": "OpenFlow controller.  If unset, defaults to that specified by the parent Open_vSwitch.",
+         "type": {"key": "uuid", "min": 0, "max": 1}},
+       "other_config": {
+         "comment": "Key-value pairs for configuring rarely used bridge features.  The currently defined key-value pairs are: \"datapath-id\", exactly 12 hex digits to set the OpenFlow datapath ID to a specific value; \"hwaddr\", exactly 12 hex digits in the form \"XX:XX:XX:XX:XX:XX\" to set the hardware address of the local port and influence the datapath ID.",
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+       "external_ids": {
+         "comment": "Key-value pairs that identify this bridge's role in external systems.  The currently defined key-value pairs are: \"xs-network-uuids\", a space-delimited set of the Citrix XenServer network UUIDs with which this bridge is associated; \"xs-network-names\", a semicolon-delimited set of Citrix XenServer network names with which this bridge is associated.",
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+       "flood_vlans": {
+         "comment": "VLAN IDs of VLANs on which MAC address learning should be disabled, so that packets are flooded instead of being sent to specific ports that are believed to contain packets' destination MACs.  This should ordinarily be used to disable MAC learning on VLANs used for mirroring (RSPAN VLANs).  It may also be useful for debugging.",
+         "type": {"key": "integer", "min": 0, "max": 4096}
+}}},
+   "Port": {
+     "comment": "A port within a Bridge.  May contain a single Interface or multiple (bonded) Interfaces.",
+     "columns": {
+       "name": {
+         "comment": "Port name.  Should be alphanumeric and no more than about 8 bytes long.    May be the same as the interface name, for non-bonded ports.  Must otherwise be unique among the names of ports, interfaces, and bridges on a host.",
+         "type": "string"},
+       "interfaces": {
+         "comment": "The Port's Interfaces.  If there is more than one, this is a bonded Port.",
+         "type": {"key": "uuid", "min": 1, "max": "unlimited"}},
+       "trunks": {
+         "comment": "The 802.1Q VLAN(s) that this port trunks.  Should be empty if this port trunks all VLAN(s) or if this is not a trunk port.",
+         "type": {"key": "integer", "min": 0, "max": 4096}},
+       "tag": {
+         "comment": "This port's implicitly tagged VLAN.  Should be empty if this is a trunk port.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "mac": {
+         "comment": "The MAC address to use for this port for the purpose of choosing the bridge's MAC address.  This column does not necessarily reflect the port's actual MAC address, nor will setting it change the port's actual MAC address.  Exactly 12 hex digits in the form XX:XX:XX:XX:XX:XX.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "bond_updelay": {
+         "comment": "For a bonded port, the number of milliseconds for which carrier must stay up on an interface before the interface is considered to be up.  Ignored for non-bonded ports.",
+         "type": "integer"},
+       "bond_downdelay": {
+         "comment": "For a bonded port, the number of milliseconds for which carrier must stay down on an interface before the interface is considered to be down.  Ignored for non-bonded ports.",
+         "type": "integer"},
+       "bond_fake_iface": {
+         "comment": "For a bonded port, whether to create a fake interface with the name of the port.  Use only for compatibility with legacy software that requires this.",
+         "type": "boolean"},
+       "fake_bridge": {
+         "comment": "Does this port represent a sub-bridge for its tagged VLAN within the Bridge?  See ovs-vsctl(8) for more information.",
+         "type": "boolean"},
+       "other_config": {
+         "comment": "Key-value pairs for configuring rarely used port features.  The currently defined key-value pairs are: \"hwaddr\", exactly 12 hex digits in the form \"XX:XX:XX:XX:XX:XX\".",
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+       "external_ids": {
+         "comment": "Key-value pairs that identify this port's role in external systems.  No key-value pairs native to Port are currently defined.  For fake bridges (see the \"fake-bridge\" column), external IDs for the fake bridge are defined here by prefixing their keys with \"fake-bridge\", e.g. \"fake-bridge-xs-network-uuids\".",
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}},
+   "Interface": {
+     "comment": "An interface within a Port.",
+     "columns": {
+       "name": {
+         "comment": "Interface name.  Should be alphanumeric and no more than about 8 bytes long.  May be the same as the port name, for non-bonded ports.  Must otherwise be unique among the names of ports, interfaces, and bridges on a host.",
+         "type": "string"},
+       "type": {
+         "comment": "The interface type.  Normal network devices, e.g.  eth0, have type \"system\" or \"\" (which are synonyms).  Internal ports have type \"internal\".  TUN/TAP devices have type \"tap\".  GRE devices have type \"gre\".",
+         "type": "string"},
+       "options": {
+         "comment": "Configuration options whose interpretation varies based on \"type\".",
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+       "ingress_policing_rate": {
+         "comment": "Maximum rate for data received on this interface, in kbps.  Set to 0 to disable policing.",
+         "type": "integer"},
+       "ingress_policing_burst": {
+         "comment": "Maximum burst size for data received on this interface, in kb.  The default burst size if set to 0 is 1000 kb.",
+         "type": "integer"},
+       "mac": {
+         "comment": "Ethernet address to set for this interface.  If unset then the default MAC address is used.  May not be supported on all interfaces.  Exactly 12 hex digits in the form XX:XX:XX:XX:XX:XX.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "external_ids": {
+         "comment": "Key-value pairs that identify this interface's role in external systems.  The currently defined key-value pairs are: \"xs-vif-uuid\", the UUID of the Citrix XenServer VIF associated with this interface; \"xs-network-uuid\", the UUID of the Citrix XenServer network to which this interface is attached; \"xs-vif-vm-uuid\", the UUID of the Citrix XenServer VM to which this interface belongs; \"xs-vif-mac\", the value of the \"MAC\" field in the Citrix XenServer VIF record for this interface.",
+         "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}},
+       "ofport": {
+         "comment": "OpenFlow port number for this interface.  This is populated when the port number becomes known.  Before it is populated its value will be missing.  If the interface cannot be added then this is indicated by a value of -1.",
+         "type": {"key": "integer", "min": 0, "max": 1},
+         "ephemeral": true}}},
+   "Mirror": {
+     "comment": "A port mirror within a Bridge.",
+     "columns": {
+       "name": {
+         "comment": "Arbitrary identifier for the Mirror.",
+         "type": "string"},
+       "select_src_port": {
+         "comment": "Ports on which arriving packets are selected for mirroring.",
+         "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+       "select_dst_port": {
+         "comment": "Ports on which departing packets are selected for mirroring.",
+         "type": {"key": "uuid", "min": 0, "max": "unlimited"}},
+       "select_vlan": {
+         "comment": "VLANs on which packets are selected for mirroring.",
+         "type": {"key": "integer", "min": 0, "max": 4096}},
+       "output_port": {
+         "comment": "Output port for selected packets.  Mutually exclusive with output_vlan.",
+         "type": {"key": "uuid", "min": 0, "max": 1}},
+       "output_vlan": {
+         "comment": "Output VLAN for selected packets.  Mutually exclusive with output_port.",
+         "type": {"key": "integer", "min": 0, "max": 1}}}},
+   "NetFlow": {
+     "comment": "A NetFlow target.",
+     "columns": {
+       "targets": {
+         "comment": "NetFlow targets in the form \"IP:PORT\".",
+         "type": {"key": "string", "min": 1, "max": "unlimited"}},
+       "engine_type": {
+         "comment": "Engine type to use in NetFlow messages.  Defaults to datapath index if not specified.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "engine_id": {
+         "comment": "Engine ID to use in NetFlow messages.  Defaults to datapath index if not specified.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "add_id_to_interface": {
+         "comment": "Place least-significant 7 bits of engine ID into most significant bits of ingress and egress interface fields of NetFlow records?",
+         "type": "boolean"},
+       "active_timeout": {
+         "comment": "Active timeout interval, in seconds.  A value of 0 requests the default timeout; a negative value disables active timeouts.",
+         "type": "integer"}}},
+   "sFlow": {
+     "comment": "A sFlow target.",
+     "columns": {
+       "targets": {
+         "comment": "sFlow targets in the form \"IP:PORT\".",
+         "type": {"key": "string", "min": 1, "max": "unlimited"}},
+       "sampling": {
+         "comment": "Rate at which packets should be sampled and sent to the collector.  If not specified, defaults to 400, which means one out of 400, on average, will be sent to the collector.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "polling": {
+         "comment": "Polling rate in seconds to send port statistics to the collector.  If not specified, defaults to 30 seconds.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "header": {
+         "comment": "Number of bytes of a sampled packet to send to the collector.  If not specified, defaults is 128 bytes.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "agent": {
+         "comment": "IP address to report as \"agent address\" to collectors.  If not specified, defaults to collector's \"local_ip\" value.  If neither is specified, sFlow is disabled.",
+         "type": {"key": "string", "min": 0, "max": 1}}}},
+   "Controller": {
+     "comment": "An OpenFlow controller.",
+     "columns": {
+       "target": {
+         "comment": "Connection method for controller, e.g. \"ssl:...\", \"tcp:...\".  The special string \"discover\" enables controller discovery.  The special string \"none\" disables the controller.",
+         "type": "string"},
+       "max_backoff": {
+         "comment": "Maximum number of milliseconds to wait between connection attempts.  Default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "inactivity_probe": {
+         "comment": "Maximum number of milliseconds of idle time on connection to controller before sending an inactivity probe message.  Default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "fail_mode": {
+         "comment": "Either \"standalone\" or \"secure\", or empty to use the implementation's default.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "discover_accept_regex": {
+         "comment": "If \"target\" is \"discover\", a POSIX extended regular expression against which the discovered controller location is validated.  If not specified, the default is implementation-specific.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "discover_update_resolv_conf": {
+         "comment": "If \"target\" is \"discover\", whether to update /etc/resolv.conf when the controller is discovered.  If not specified, the default is implementation-specific.",
+         "type": {"key": "boolean", "min": 0, "max": 1}},
+       "connection_mode": {
+         "comment": "Either \"in-band\" or \"out-of-band\".  If not specified, the default is implementation-specific.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "local_ip": {
+         "comment": "If \"target\" is not \"discover\", the IP address to configure on the local port.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "local_netmask": {
+         "comment": "If \"target\" is not \"discover\", the IP netmask to configure on the local port.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "local_gateway": {
+         "comment": "If \"target\" is not \"discover\", the IP gateway to configure on the local port.",
+         "type": {"key": "string", "min": 0, "max": 1}},
+       "controller_rate_limit": {
+         "comment": "The maximum rate at which packets will be forwarded to the OpenFlow controller, in packets per second.  If not specified, the default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}},
+       "controller_burst_limit": {
+         "comment": "The maximum number of unused packet credits that the bridge will allow to accumulate, in packets.  If not specified, the default is implementation-specific.",
+         "type": {"key": "integer", "min": 0, "max": 1}}}},
+   "SSL": {
+     "comment": "SSL configuration for an Open_vSwitch.",
+     "columns": {
+       "private_key": {
+         "comment": "Name of a PEM file containing the private key used as the switch's identity for SSL connections to the controller.",
+         "type": "string"},
+       "certificate": {
+         "comment": "Name of a PEM file containing a certificate, signed by the certificate authority (CA) used by the controller and manager, that certifies the switch's private key, identifying a trustworthy switch.",
+         "type": "string"},
+       "ca_cert": {
+         "comment": "Name of a PEM file containing the CA certificate used to verify that the switch is connected to a trustworthy controller.",
+         "type": "string"},
+       "bootstrap_ca_cert": {
+         "comment": "If set to true, then Open vSwitch will attempt to obtain the CA certificate from the controller on its first SSL connection and save it to the named PEM file. If it is successful, it will immediately drop the connection and reconnect, and from then on all SSL connections must be authenticated by a certificate signed by the CA certificate thus obtained.  This option exposes the SSL connection to a man-in-the-middle attack obtaining the initial CA certificate, but it may be useful for bootstrapping.",
+         "type": "boolean"}}}}}
index c8172c8..95c6b80 100755 (executable)
@@ -5,7 +5,7 @@
 # chkconfig: 2345 09 91
 # description: Manage vswitch kernel modules and user-space daemon
 
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -26,10 +26,27 @@ test -e /etc/sysconfig/vswitch && . /etc/sysconfig/vswitch
 # General config variables in /etc/sysconfig/vswitch
 : ${ENABLE_BRCOMPAT:=y}
 : ${ENABLE_FAKE_PROC_NET:=y}
+: ${ENABLE_MONITOR:=y}
 : ${FORCE_COREFILES:=y}
 
+# Config variables specific to ovsdb-server
+: ${OVSDB_SERVER_REMOTES:=punix:/var/run/ovsdb-server db:Open_vSwitch,managers}
+: ${OVSDB_SERVER_DB:=/etc/ovs-vswitchd.conf.db}
+: ${OVSDB_SERVER_PIDFILE:=/var/run/ovsdb-server.pid}
+: ${OVSDB_SERVER_RUN_DIR:=/var/xen/vswitch}
+: ${OVSDB_SERVER_PRIORITY:=-10}
+: ${OVSDB_SERVER_LOGFILE:=/var/log/ovsdb-server.log}
+: ${OVSDB_SERVER_FILE_LOGLEVEL:=INFO}
+: ${OVSDB_SERVER_SYSLOG_LOGLEVEL:=ERR}
+: ${OVSDB_SERVER_MEMLEAK_LOGFILE:=}
+: ${OVSDB_SERVER_STRACE_LOG:=}
+: ${OVSDB_SERVER_STRACE_OPT:=}
+: ${OVSDB_SERVER_VALGRIND_LOG:=}
+: ${OVSDB_SERVER_VALGRIND_OPT:=}
+
 # Config variables specific to ovs-vswitchd
-: ${VSWITCHD_CONF:=/etc/ovs-vswitchd.conf}
+: ${VSWITCHD_OVSDB_SERVER:=unix:/var/run/ovsdb-server}
+: ${VSWITCHD_OVSDB_SCHEMA:=/usr/share/vswitch/vswitch.ovsschema}
 : ${VSWITCHD_PIDFILE:=/var/run/ovs-vswitchd.pid}
 : ${VSWITCHD_RUN_DIR:=/var/xen/vswitch}
 : ${VSWITCHD_PRIORITY:=-10}
@@ -56,12 +73,14 @@ test -e /etc/sysconfig/vswitch && . /etc/sysconfig/vswitch
 : ${BRCOMPATD_VALGRIND_OPT:=}
 
 # Full paths to executables & modules
+ovsdb_server="/usr/sbin/ovsdb-server"
+ovsdb_tool="/usr/bin/ovsdb-tool"
 vswitchd="/usr/sbin/ovs-vswitchd"
 brcompatd="/usr/sbin/ovs-brcompatd"
 dpctl="/usr/bin/ovs-dpctl"
 appctl="/usr/bin/ovs-appctl"
 ofctl="/usr/bin/ovs-ofctl"
-
+vsctl="/usr/bin/ovs-vsctl"
 
 if [ "$ENABLE_FAKE_PROC_NET" = "y" ]; then
     if [ "$ENABLE_BRCOMPAT" != "y" ]; then
@@ -70,6 +89,12 @@ if [ "$ENABLE_FAKE_PROC_NET" = "y" ]; then
     fi
 fi
 
+if test "$ENABLE_MONITOR" = "y"; then
+    monitor_opt="--monitor"
+else
+    monitor_opt=
+fi
+
 function dp_list {
     "$dpctl" show | grep '^dp[0-9]\+:' | cut -d':' -f 1
 }
@@ -92,6 +117,9 @@ function insert_modules_if_required {
     if [ -n "$BRCOMPATD_PIDFILE" ] && ! lsmod | grep -q "brcompat_mod"; then
         action "Inserting brcompat module" modprobe brcompat_mod
     fi
+    if [ -f "/lib/modules/`uname -r`/kernel/net/vswitch/ip_gre_mod.ko" ] && ! lsmod | grep -q "ip_gre_mod"; then
+        action "Inserting ip_gre module" modprobe ip_gre_mod
+    fi
 }
 
 function remove_modules {
@@ -101,17 +129,54 @@ function remove_modules {
     if lsmod | grep -q "openvswitch_mod"; then
         action "Removing openvswitch module" rmmod openvswitch_mod.ko
     fi
-}
-
-function reload_vswitchd {
-    if [ -f "$VSWITCHD_PIDFILE" ]; then
-        "$appctl" --target=/var/run/ovs-vswitchd.`cat $VSWITCHD_PIDFILE`.ctl vswitchd/reload
+    if lsmod | grep -q "ip_gre_mod"; then
+        action "Removing ip_gre module" rmmod ip_gre_mod.ko
     fi
 }
 
-function reload_brcompatd {
-    if [ -f "$BRCOMPATD_PIDFILE" ]; then
-        "$appctl" --target=/var/run/ovs-brcompatd.`cat $BRCOMPATD_PIDFILE`.ctl vlog/reopen
+function start_ovsdb_server {
+    local syslog_opt="-vANY:SYSLOG:${OVSDB_SERVER_SYSLOG_LOGLEVEL}"
+    local logfile_file_opt=""
+    local logfile_level_opt=""
+    if [ ! -d "$OVSDB_SERVER_RUN_DIR" ]; then
+        mkdir -p "$OVSDB_SERVER_RUN_DIR"
+    fi
+    cd "$OVSDB_SERVER_RUN_DIR"
+    local remotes=
+    for remote in $OVSDB_SERVER_REMOTES; do
+        remotes="$remotes --remote=$remote"
+    done
+    if [ -n "$OVSDB_SERVER_FILE_LOGLEVEL" ]; then
+        logfile_level_opt="-vANY:FILE:${OVSDB_SERVER_FILE_LOGLEVEL}"
+        logfile_file_opt="--log-file=$OVSDB_SERVER_LOGFILE"
+    fi
+    local leak_opt=""
+    if [ -n "$OVSDB_SERVER_MEMLEAK_LOGFILE" ]; then
+        leak_opt="--check-leaks=$OVSDB_SERVER_MEMLEAK_LOGFILE"
+        if [ -e "$OVSDB_SERVER_MEMLEAK_LOGFILE" ]; then
+            mv "$OVSDB_SERVER_MEMLEAK_LOGFILE" "$OVSDB_SERVER_MEMLEAK_LOGFILE.prev"
+        fi
+    fi
+    local strace_opt=""
+    local daemonize="y"
+    if [ -n "$OVSDB_SERVER_STRACE_LOG" ] && [ -n "$OVSDB_SERVER_VALGRIND_LOG" ]; then
+        printf "Can not start with both VALGRIND and STRACE\n"
+        exit 1
+    fi
+    if [ -n "$OVSDB_SERVER_STRACE_LOG" ]; then
+        strace_opt="strace -o $OVSDB_SERVER_STRACE_LOG $OVSDB_SERVER_STRACE_OPT"
+        daemonize="n"
+    fi
+    if [ -n "$OVSDB_SERVER_VALGRIND_LOG" ]; then
+        valgrind_opt="valgrind --log-file=$OVSDB_SERVER_VALGRIND_LOG $OVSDB_SERVER_VALGRIND_OPT"
+        daemonize="n"
+    fi
+    if [ "$daemonize" != "y" ]; then
+        # Start in background and force a "success" message
+        action "Starting ovsdb_server ($strace_opt$valgrind_opt)" true
+        (nice -n "$OVSDB_SERVER_PRIORITY" $strace_opt $valgrind_opt "$ovsdb_server" "$OVSDB_SERVER_DB" --pidfile="$OVSDB_SERVER_PIDFILE" --detach $monitor_opt --no-chdir -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt $remotes) &
+    else
+        action "Starting ovsdb-server" nice -n "$OVSDB_SERVER_PRIORITY" "$ovsdb_server" "$OVSDB_SERVER_DB" --pidfile="$OVSDB_SERVER_PIDFILE" --detach $monitor_opt --no-chdir -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt $remotes
     fi
 }
 
@@ -155,9 +220,9 @@ function start_vswitchd {
     if [ "$daemonize" != "y" ]; then
         # Start in background and force a "success" message
         action "Starting ovs-vswitchd ($strace_opt$valgrind_opt)" true
-        (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach --no-chdir $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF") &
+        (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach $monitor_opt --no-chdir $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_OVSDB_SERVER") &
     else
-        action "Starting ovs-vswitchd" nice -n "$VSWITCHD_PRIORITY" "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach --no-chdir $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF"
+        action "Starting ovs-vswitchd" nice -n "$VSWITCHD_PRIORITY" "$vswitchd" --pidfile="$VSWITCHD_PIDFILE" --detach $monitor_opt --no-chdir $fake_proc_net_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_OVSDB_SERVER"
     fi
 }
 
@@ -174,7 +239,7 @@ function start_brcompatd {
         logfile_file_opt="--log-file=$BRCOMPATD_LOGFILE"
     fi
     local leak_opt=""
-    if [ -n "$BRCOMPATD_MEMLEAK_LOG" ]; then
+    if [ -n "$BRCOMPATD_MEMLEAK_LOGFILE" ]; then
         leak_opt="--check-leaks=$BRCOMPATD_MEMLEAK_LOGFILE"
         if [ -e "$BRCOMPATD_MEMLEAK_LOGFILE" ]; then
             mv "$BRCOMPATD_MEMLEAK_LOGFILE" "$BRCOMPATD_MEMLEAK_LOGFILE.prev"
@@ -198,9 +263,17 @@ function start_brcompatd {
     if [ "$daemonize" != "y" ]; then
         # Start in background and force a "success" message
         action "Starting ovs-brcompatd ($strace_opt$valgrind_opt)" true
-        (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd"--no-chdir --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF") &
+        (nice -n "$VSWITCHD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd"--no-chdir --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_OVSDB_SERVER") &
     else
-        action "Starting ovs-brcompatd" nice -n "$BRCOMPATD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd" --no-chdir --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE --detach -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_CONF"
+        action "Starting ovs-brcompatd" nice -n "$BRCOMPATD_PRIORITY" $strace_opt $valgrind_opt "$brcompatd" --no-chdir --appctl-command="$appctl_cmd" --pidfile=$BRCOMPATD_PIDFILE --detach $monitor_opt -vANY:CONSOLE:EMER $syslog_opt $logfile_level_opt $logfile_file_opt $leak_opt "$VSWITCHD_OVSDB_SERVER"
+    fi
+}
+
+function stop_ovsdb_server {
+    if [ -f "$OVSDB_SERVER_PIDFILE" ]; then
+        local pid=$(cat "$OVSDB_SERVER_PIDFILE")
+        action "Killing ovsdb-server ($pid)" kill -TERM $pid
+        rm -f "$OVSDB_SERVER_PIDFILE"
     fi
 }
 
@@ -231,8 +304,6 @@ WARNING!!!
 
 Restarting vswitch on a live server is not guaranteed to work.  It is
 provided as a convenience for those situations in which it does work.
-If you just want to reload the configuration file, use "reload"
-instead of restart.
 
 EOF
     read -s -r -n 1 -p "Countinue with restart (y/N): " response
@@ -258,29 +329,34 @@ function start {
     # ovs-vswitchd needs a few per bridge
     ulimit -n 4096
 
-    if [ ! -e "$VSWITCHD_CONF" ]; then
-        warning "$VSWITCHD_CONF does not exist"
-        action "Creating empty $VSWITCHD_CONF" touch "$VSWITCHD_CONF"
-    elif [ ! -e /var/run/vswitch.booted ]; then
+    # Allow GRE traffic.
+    iptables -I INPUT -p gre -j ACCEPT
+
+    if [ ! -e "$OVSDB_SERVER_DB" ]; then
+        warning "$OVSDB_SERVER_DB does not exist"
+
+        action "Creating empty database $OVSDB_SERVER_DB" true
+        $ovsdb_tool -vANY:console:emer create "$OVSDB_SERVER_DB" "$VSWITCHD_OVSDB_SCHEMA"
+    fi
+
+    start_ovsdb_server
+    $vsctl --no-wait init
+    if [ ! -e /var/run/vswitch.booted ]; then
         touch /var/run/vswitch.booted
-        /usr/bin/ovs-cfg-mod '-vANY:console:emer' -F "$VSWITCHD_CONF" \
-            '--del-match=bridge.*' \
-            '--del-match=port.*' \
-            '--del-match=bonding.*' \
-            '--del-match=iface.*' \
-            '--del-match=vlan.*.trunks=*' \
-            '--del-match=vlan.*.tag=*'
+        for bridge in $($vsctl list-br); do
+            $vsctl --no-wait del-br $bridge
+        done
     fi
 
     start_vswitchd
     start_brcompatd
-    reload_vswitchd  # ensures ovs-vswitchd has fully read config file.
     touch /var/lock/subsys/vswitch
 }
 
 function stop {
     stop_brcompatd
     stop_vswitchd
+    stop_ovsdb_server
     rm -f /var/lock/subsys/vswitch
 }
 
@@ -301,10 +377,6 @@ case "$1" in
     restart)
         restart
         ;;
-    reload)
-        reload_vswitchd
-        reload_brcompatd
-        ;;
     strace-vswitchd)
         shift
         strace -p $(cat "$VSWITCHD_PIDFILE") "$@"
@@ -314,15 +386,17 @@ case "$1" in
         strace -p $(cat "$BRCOMPATD_PIDFILE") "$@"
         ;;
     status)
+        status -p "$OVSDB_SERVER_PIDFILE" ovsdb-server
         status -p "$VSWITCHD_PIDFILE" ovs-vswitchd
         status -p "$BRCOMPATD_PIDFILE" ovs-brcompatd
         ;;
     version)
+        /usr/sbin/ovsdb-server -V
         /usr/sbin/ovs-vswitchd -V
         /usr/sbin/ovs-brcompatd -V
         ;;
     help)
-        printf "vswitch [start|stop|restart|reload|unload|status|version]\n"
+        printf "vswitch [start|stop|restart|unload|status|version]\n"
         ;;
     *)
         printf "Unknown command: $1\n"
index de92296..1c5f079 100755 (executable)
 
 . /etc/init.d/functions
 
-test -e /etc/sysconfig/vswitch && . /etc/sysconfig/vswitch
-: ${VSWITCHD_CONF:=/etc/ovs-vswitchd.conf}
-: ${VSWITCHD_PIDFILE:=/var/run/ovs-vswitchd.pid}
-: ${VSWITCHD_PRIORITY:=-5}
-: ${VSWITCHD_LOGFILE:=/var/log/ovs-vswitchd.log}
-: ${VSWITCHD_FILE_LOGLEVEL:=}
-: ${VSWITCHD_SYSLOG_LOGLEVEL:=WARN}
-: ${VSWITCHD_MEMLEAK_LOGFILE:=}
-: ${BRCOMPATD_PIDFILE:=/var/run/ovs-brcompatd.pid}
-: ${BRCOMPATD_PRIORITY:=-5}
-: ${BRCOMPATD_LOGFILE:=/var/log/ovs-brcompatd.log}
-: ${BRCOMPATD_FILE_LOGLEVEL:=}
-: ${BRCOMPATD_SYSLOG_LOGLEVEL:=WARN}
-: ${BRCOMPATD_MEMLEAK_LOGFILE:=}
-
 function do_host_call {
     xe host-call-plugin host-uuid="$INSTALLATION_UUID" plugin="vswitch-cfg-update" fn="update" >/dev/null
 }
@@ -60,7 +45,7 @@ case "$1" in
         start
         ;;
     help)
-        printf "vswitch [start|stop|restart]\n"
+        printf "vswitch-xapi-update [start|stop|restart]\n"
         ;;
     *)
         printf "Unknown command: $1\n"
index 6366c67..d6f71af 100644 (file)
@@ -9,6 +9,8 @@
        sharedscripts
        postrotate
        # Tell ovs-vswitchd and ovs-brcompatd to reopen their log files
-       /sbin/service vswitch reload
+       /usr/bin/ovs-appctl -t ovs-vswitchd vlog/reopen
+       /usr/bin/ovs-appctl -t ovs-brcompatd vlog/reopen
+       /usr/bin/ovs-appctl -t ovsdb-server vlog/reopen
        endscript
 }
index 7472d1b..7f49e33 100755 (executable)
@@ -26,8 +26,7 @@ import XenAPI
 import os
 import subprocess
 
-cfg_mod="/usr/bin/ovs-cfg-mod"
-vswitchd_cfg_filename="/etc/ovs-vswitchd.conf"
+vsctl="/usr/bin/ovs-vsctl"
 cacert_filename="/etc/ovs-vswitchd.cacert"
 
 # Delete the CA certificate, so that we go back to boot-strapping mode
@@ -50,6 +49,23 @@ def update(session, args):
         controller = pool["other_config"]["vSwitchController"]
     except KeyError, e:
         controller = ""
+    if controller == "do-not-update":
+        return "XAPI key set to do-not-update"
+    if controller.startswith("for-bridges|"):
+        l = controller.split("|")[1:]
+        for netctrl in l:
+            xapiNet = session.xenapi.network
+            n, t = netctrl.split("=")
+            blist = xapiNet.get_by_name_label(n)
+            if len(blist) == 0:
+                # If there is no bridge for the network, just keep
+                # going so we bring up as much as possible.
+                continue
+            elif len(blist) > 1:
+                raise XenAPIPlugin.Failure("TOO_MANY_MATCHING_NETWORKS", [n,blist])
+            b = xapiNet.get_bridge(blist[0])
+            setBrControllerCfg(b, t)
+        return "Completed setting controllers on specific bridges"
     currentController = vswitchCurrentController()
     if controller == "" and currentController != "":
         delete_cacert()
@@ -61,9 +77,9 @@ def update(session, args):
         return "Successfully set controller to " + controller
     else:
         return "No change to configuration"
-        
+
 def vswitchCurrentController():
-    controller = vswitchCfgQuery("mgmt.controller")
+    controller = vswitchCfgQuery("get-controller")
     if controller == "":
         return controller
     if len(controller) < 4 or controller[0:4] != "ssl:":
@@ -72,26 +88,83 @@ def vswitchCurrentController():
         return controller[4:]
 
 def removeControllerCfg():
-    vswitchCfgMod(["--del-match", "mgmt.controller=*",
-                   "--del-match", "ssl.bootstrap-ca-cert=*",
-                   "--del-match", "ssl.ca-cert=*",
-                   "--del-match", "ssl.private-key=*",
-                   "--del-match", "ssl.certificate=*"])
-                                       
+    vswitchCfgMod(["--", "del-controller",
+                   "--", "del-ssl"])
+
+def setBrControllerCfg(br, target):
+    # Terrible hack... When this is run at boot the required bridges
+    # may not be present.  So, we fork a process for each bridge that
+    # needs to be set which sits around in the background and updates
+    # it when it becomes available, finally timing out after a long
+    # interval if it never becomes available.
+    #
+    # The right way to do this is to hook the bridge creation somehow
+    # but I don't believe this is possible in XenServer 5.5 without
+    # either listening to XAPI events or writing it in C code in
+    # brcompatd.
+    import time
+    import syslog
+    import resource
+
+    p = os.fork()
+    if p != 0:
+        return
+
+    os.setsid()
+    p = os.fork()
+    if p != 0:
+        sys.exit(0)
+
+    os.chdir("/")
+    os.umask(0)
+    maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
+    if maxfd == resource.RLIM_INFINITY:
+        maxfd = 1024
+    for fd in range(0, maxfd):
+        try:
+            os.close(fd)
+        except OSError:
+            pass
+    os.open("/dev/null", os.O_RDWR)
+    os.dup2(0, 1)
+    os.dup2(0, 2)
+
+    syslog.openlog("vswitch-cfg-update", syslog.LOG_PID)
+    syslog.syslog(syslog.LOG_INFO,
+                  "Started background process waiting on bridge %s" % (br,))
+
+    count = 0
+    error = None
+    sleep_time = 10
+    while count < 60:
+        count += 1
+        try:
+            vswitchCfgMod(["--", "del-controller", br,
+                           "--", "set-controller", br, target,
+                           "--", "set-fail-mode", br, "secure"])
+        except XenAPIPlugin.Failure, e:
+            error = e
+            syslog.syslog(syslog.LOG_INFO,
+                          "Attempt to set br %s controller failed" % (br,))
+            time.sleep(sleep_time)
+            continue
+        syslog.syslog(syslog.LOG_INFO,
+                      "Successfully set br %s controller to %s" % (br, repr(target)))
+        return
+    syslog.syslog(syslog.LOG_ERR,
+                  "Giving up on setting br %s controller" % (br,))
+
 def setControllerCfg(controller):
-    vswitchCfgMod(["--del-match", "mgmt.controller=*",
-                   "--del-match", "ssl.bootstrap-ca-cert=*",
-                   "--del-match", "ssl.ca-cert=*",
-                   "--del-match", "ssl.private-key=*",
-                   "--del-match", "ssl.certificate=*",
-                   "-a", "mgmt.controller=ssl:" + controller,
-                   "-a", "ssl.bootstrap-ca-cert=true",
-                   "-a", "ssl.ca-cert=/etc/ovs-vswitchd.cacert",
-                   "-a", "ssl.private-key=/etc/xensource/xapi-ssl.pem",
-                   "-a", "ssl.certificate=/etc/xensource/xapi-ssl.pem"])
-
-def vswitchCfgQuery(key):
-    cmd = [cfg_mod, "--config-file=" + vswitchd_cfg_filename, "-q", key]
+    vswitchCfgMod(["--", "del-controller",
+                   "--", "del-ssl",
+                   "--", "--bootstrap", "set-ssl",
+                   "/etc/xensource/xapi-ssl.pem",
+                   "/etc/xensource/xapi-ssl.pem",
+                   "/etc/ovs-vswitchd.cacert",
+                   "--", "set-controller", "ssl:" + controller])
+
+def vswitchCfgQuery(action):
+    cmd = [vsctl, "-vANY:console:emer", action]
     output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()
     if len(output) == 0 or output[0] == None:
         output = ""
@@ -100,19 +173,11 @@ def vswitchCfgQuery(key):
     return output
 
 def vswitchCfgMod(action_args):
-    cmd = [cfg_mod, "-vANY:console:emer",
-           "--config-file=" + vswitchd_cfg_filename] + action_args
+    cmd = [vsctl, "-vANY:console:emer"] + action_args
     exitcode = subprocess.call(cmd)
     if exitcode != 0:
         raise XenAPIPlugin.Failure("VSWITCH_CONFIG_MOD_FAILURE",
                                    [ str(exitcode) , str(action_args) ])
-    vswitchReload()
     
-def vswitchReload():
-    exitcode = subprocess.call(["/sbin/service", "vswitch", "reload"])
-    if exitcode != 0:
-        raise XenAPIPlugin.Failure("VSWITCH_CFG_RELOAD_FAILURE", [ str(exitcode) ])
-    
-
 if __name__ == "__main__":
     XenAPIPlugin.dispatch({"update": update})
index 9113159..4e24d83 100755 (executable)
@@ -23,9 +23,8 @@
 BRCTL="/usr/sbin/brctl"
 IP="/sbin/ip"
 
-cfg_mod="/usr/bin/ovs-cfg-mod"
 vsctl="/usr/bin/ovs-vsctl"
-service="/sbin/service"
+dump_vif_details="/usr/share/vswitch/scripts/dump-vif-details"
 
 handle_promiscuous()
 {
@@ -66,34 +65,6 @@ handle_mtu()
     fi
 }
 
-handle_vswitch_vif_details()
-{
-    local vif_details=
-    local net_uuid=$(xenstore-read "${PRIVATE}/network-uuid" 2>/dev/null)
-    if [ -n "${net_uuid}" ] ; then
-       vif_details="$vif_details --add=port.${dev}.net-uuid=${net_uuid}"
-    fi
-
-    local address=$(xenstore-read "/local/domain/$DOMID/device/vif/$DEVID/mac" 2>/dev/null)
-    if [ -n "${address}" ] ; then
-       vif_details="$vif_details --add=port.${dev}.vif-mac=${address}"
-    fi
-
-    local vif_uuid=$(xenstore-read "${PRIVATE}/vif-uuid" 2>/dev/null)
-    if [ -n "${vif_uuid}" ] ; then
-       vif_details="$vif_details --add=port.${dev}.vif-uuid=${vif_uuid}"
-    fi
-
-    local vm=$(xenstore-read "/local/domain/$DOMID/vm" 2>/dev/null)
-    if [ $? -eq 0 -a -n "${vm}" ] ; then
-       local vm_uuid=$(xenstore-read "$vm/uuid" 2>/dev/null)
-    fi
-    if [ -n "${vm_uuid}" ] ; then
-       vif_details="$vif_details --add=port.${dev}.vm-uuid=${vm_uuid}"
-    fi
-    echo ${vif_details}
-}
-
 add_to_bridge()
 {
     local address=$(xenstore-read "${PRIVATE}/bridge-MAC")
@@ -120,25 +91,12 @@ add_to_bridge()
            ${BRCTL} addif "${bridge}" "${dev}"                 || logger -t scripts-vif "Failed to brctl addif ${bridge} ${dev}"
            ;;
        vswitch)
-           local VLAN_ID=$($vsctl br-to-vlan $bridge)
-           local vid=
-           if [ "$VLAN_ID" -ne 0 ] ; then
-               bridge=$($vsctl br-to-parent $bridge)
-               vid="--add=vlan.${dev}.tag=${VLAN_ID}"
-           fi
-
-           if [ "$TYPE" = "vif" ] ; then
-               local vif_details=$(handle_vswitch_vif_details)
-           fi
-
-           $cfg_mod -F /etc/ovs-vswitchd.conf \
-               --del-match="bridge.*.port=${dev}" \
-               --del-match="vlan.${dev}.trunks=*" \
-               --del-match="vlan.${dev}.tag=*" \
-               --del-match="port.${dev}.[!0-9]*" \
-               --add="bridge.$bridge.port=${dev}" \
-               $vid $vif_details -c 
-           $service vswitch reload
+               local vif_details=$($dump_vif_details $DOMID $DEVID)
+               if [ $? -ne 0 -o -z "${vif_details}" ]; then
+                       logger -t scripts-vif "Failed to retrieve vif details for vswitch"
+               fi
+
+               $vsctl add-port $bridge $dev $vif_details
            ;;
     esac
            
@@ -152,12 +110,7 @@ remove_from_bridge()
            # Nothing to do
            ;;
        vswitch)
-           $cfg_mod -vANY:console:emer -F /etc/ovs-vswitchd.conf \
-               --del-match="bridge.*.port=${dev}" \
-               --del-match="vlan.${dev}.trunks=*" \
-               --del-match="vlan.${dev}.tag=*" \
-               --del-match="port.${dev}.[!0-9]*" -c
-           $service vswitch reload
+        $vsctl del-port $bridge $dev
            ;;
     esac
 }
index 6f093a4..c083859 100644 (file)
@@ -118,25 +118,20 @@ A VLAN PIF cannot be a datapath PIF.
         return [pif]
 
 def datapath_deconfigure_physical(netdev):
-    # The use of [!0-9] keeps an interface of 'eth0' from matching
-    # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
-    # interfaces.
-    return ['--del-match=bridge.*.port=%s' % netdev,
-            '--del-match=port.%s.[!0-9]*' % netdev,
-            '--del-match=bonding.*.slave=%s' % netdev,
-            '--del-match=iface.%s.[!0-9]*' % netdev]
+    return ['--', '--if-exists', 'del-port', netdev]
 
 def datapath_configure_bond(pif,slaves):
-    pifrec = db().get_pif_record(pif)
+    bridge = pif_bridge_name(pif)
+    pifrec = db.get_pif_record(pif)
     interface = pif_netdev_name(pif)
 
-    argv = ['--del-match=bonding.%s.[!0-9]*' % interface]
-    argv += ["--add=bonding.%s.slave=%s" % (interface, pif_netdev_name(slave))
-             for slave in slaves]
-    argv += ['--add=bonding.%s.fake-iface=true' % interface]
+    argv = ['--', '--fake-iface', 'add-bond', bridge, interface]
+    for slave in slaves:
+        argv += [pif_netdev_name(slave)]
 
-    if pifrec['MAC'] != "":
-        argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
+    # XXX need ovs-vsctl support
+    #if pifrec['MAC'] != "":
+    #    argv += ['--add=port.%s.mac=%s' % (interface, pifrec['MAC'])]
 
     # Bonding options.
     bond_options = {
@@ -154,37 +149,27 @@ def datapath_configure_bond(pif,slaves):
     overrides = map(lambda (key,val): (key[5:], val), overrides)
     bond_options.update(overrides)
     for (name,val) in bond_options.items():
-        argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
+        # XXX need ovs-vsctl support for bond options
+        #argv += ["--add=bonding.%s.%s=%s" % (interface, name, val)]
+        pass
     return argv
 
 def datapath_deconfigure_bond(netdev):
-    # The use of [!0-9] keeps an interface of 'eth0' from matching
-    # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
-    # interfaces.
-    return ['--del-match=bonding.%s.[!0-9]*' % netdev,
-            '--del-match=port.%s.[!0-9]*' % netdev]
+    return ['--', '--if-exists', 'del-port', netdev]
 
 def datapath_deconfigure_ipdev(interface):
-    # The use of [!0-9] keeps an interface of 'eth0' from matching
-    # VLANs attached to eth0 (such as 'eth0.123'), which are distinct
-    # interfaces.
-    return ['--del-match=bridge.*.port=%s' % interface,
-            '--del-match=port.%s.[!0-9]*' % interface,
-            '--del-match=iface.%s.[!0-9]*' % interface,
-            '--del-match=vlan.%s.trunks=*' % interface,
-            '--del-match=vlan.%s.tag=*' % interface]
+    return ['--', '--if-exists', 'del-port', interface]
 
 def datapath_modify_config(commands):
-    #log("modifying configuration:")
-    #for c in commands:
-    #    log("  %s" % c)
-
-    rc = run_command(['/usr/bin/ovs-cfg-mod', '-vANY:console:emer',
-                 '-F', '/etc/ovs-vswitchd.conf']
-                + [c for c in commands if c[0] != '#'] + ['-c'])
-    if not rc:
+    if debug_mode():
+        log("modifying configuration:")
+        for c in commands:
+            log("  %s" % c)
+            
+    rc = run_command(['/usr/bin/ovs-vsctl'] + ['--timeout=20']
+                     + [c for c in commands if not c.startswith('#')])
+    if not rc:       
         raise Error("Failed to modify vswitch configuration")
-    run_command(['/sbin/service', 'vswitch', 'reload'])
     return True
 
 #
@@ -199,12 +184,12 @@ def configure_datapath(pif):
     Should take care of tearing down other PIFs which encompass common physical devices.
 
     Returns a tuple containing
-    - A list containing the necessary cfgmod command line arguments
+    - A list containing the necessary vsctl command line arguments
     - A list of additional devices which should be brought up after
       the configuration is applied.
     """
 
-    cfgmod_argv = []
+    vsctl_argv = []
     extra_up_ports = []
 
     bridge = pif_bridge_name(pif)
@@ -260,42 +245,41 @@ def configure_datapath(pif):
         #ifdown(b)
         # XXX
         netdev_down(b)
-        cfgmod_argv += ['# remove bridge %s' % b]
-        cfgmod_argv += ['--del-match=bridge.%s.*' % b]
+        vsctl_argv += ['# remove bridge %s' % b]
+        vsctl_argv += ['--', '--if-exists', 'del-br', b]
 
     for n in extra_down_ports:
         dev = pif_netdev_name(n)
-        cfgmod_argv += ['# deconfigure sibling physical device %s' % dev]
-        cfgmod_argv += datapath_deconfigure_physical(dev)
+        vsctl_argv += ['# deconfigure sibling physical device %s' % dev]
+        vsctl_argv += datapath_deconfigure_physical(dev)
         netdev_down(dev)
 
     for n in extra_down_bonds:
         dev = pif_netdev_name(n)
-        cfgmod_argv += ['# deconfigure bond device %s' % dev]
-        cfgmod_argv += datapath_deconfigure_bond(dev)
+        vsctl_argv += ['# deconfigure bond device %s' % dev]
+        vsctl_argv += datapath_deconfigure_bond(dev)
         netdev_down(dev)
 
     for p in physical_devices:
         dev = pif_netdev_name(p)
-        cfgmod_argv += ['# deconfigure physical port %s' % dev]
-        cfgmod_argv += datapath_deconfigure_physical(dev)
+        vsctl_argv += ['# deconfigure physical port %s' % dev]
+        vsctl_argv += datapath_deconfigure_physical(dev)
+
     if len(physical_devices) > 1:
-        cfgmod_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
-        cfgmod_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
-        cfgmod_argv += ['--del-entry=bridge.%s.port=%s' % (bridge,pif_netdev_name(pif))]
-        cfgmod_argv += ['# configure bond %s' % pif_netdev_name(pif)]
-        cfgmod_argv += datapath_configure_bond(pif, physical_devices)
-        cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge,pif_netdev_name(pif)) ]
+        vsctl_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
+        vsctl_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
+        vsctl_argv += ['# configure bond %s' % pif_netdev_name(pif)]
+        vsctl_argv += datapath_configure_bond(pif, physical_devices)
         extra_up_ports += [pif_netdev_name(pif)]
-    else:
+     else:
         iface = pif_netdev_name(physical_devices[0])
-        cfgmod_argv += ['# add physical device %s' % iface]
-        cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge,iface) ]
+        vsctl_argv += ['# add physical device %s' % iface]
+        vsctl_argv += ['--', 'add-port', bridge, iface]
 
-    return cfgmod_argv,extra_up_ports
+    return vsctl_argv,extra_up_ports
 
 def deconfigure_datapath(pif):
-    cfgmod_argv = []
+    vsctl_argv = []
 
     bridge = pif_bridge_name(pif)
 
@@ -306,18 +290,18 @@ def deconfigure_datapath(pif):
 
     for p in physical_devices:
         dev = pif_netdev_name(p)
-        cfgmod_argv += ['# deconfigure physical port %s' % dev]
-        cfgmod_argv += datapath_deconfigure_physical(dev)
+        vsctl_argv += ['# deconfigure physical port %s' % dev]
+        vsctl_argv += datapath_deconfigure_physical(dev)
         netdev_down(dev)
 
     if len(physical_devices) > 1:
-        cfgmod_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
-        cfgmod_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
+        vsctl_argv += ['# deconfigure bond %s' % pif_netdev_name(pif)]
+        vsctl_argv += datapath_deconfigure_bond(pif_netdev_name(pif))
 
-    cfgmod_argv += ['# deconfigure bridge %s' % bridge]
-    cfgmod_argv += ['--del-match=bridge.%s.*' % bridge]
+    vsctl_argv += ['# deconfigure bridge %s' % bridge]
+    vsctl_argv += ['--', '--if-exists', 'del-br', bridge]
 
-    return cfgmod_argv
+    return vsctl_argv
 
 #
 #
@@ -338,7 +322,7 @@ class DatapathVswitch(Datapath):
         cfg.write("TYPE=Ethernet\n")
 
     def preconfigure(self, parent):
-        cfgmod_argv = []
+        vsctl_argv = []
         extra_ports = []
 
         pifrec = db().get_pif_record(self._pif)
@@ -346,12 +330,16 @@ class DatapathVswitch(Datapath):
         ipdev = self._ipdev
         bridge = pif_bridge_name(self._dp)
         c,e = configure_datapath(self._dp)
-        cfgmod_argv += c
+        vsctl_argv += c
         extra_ports += e
 
-        cfgmod_argv += ['# configure xs-network-uuids']
-        cfgmod_argv += ['--del-match=bridge.%s.xs-network-uuids=*' % bridge]
+        if pif_is_vlan(pif):
+            datapath = pif_datapath(pif)
+            vsctl_argv += ['--', 'add-br', bridge, datapath, pifrec['VLAN']]
+        else:
+            vsctl_argv += ['--', 'add-br', bridge]
 
+        xs_network_uuids = []
         for nwpif in db().get_pifs_by_device(db().get_pif_record(self._pif)['device']):
             rec = db().get_pif_record(nwpif)
 
@@ -362,23 +350,24 @@ class DatapathVswitch(Datapath):
             #    log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid']))
             #    continue
             nwrec = db().get_network_record(rec['network'])
-            cfgmod_argv += ['--add=bridge.%s.xs-network-uuids=%s' % (bridge, nwrec['uuid'])]
-
-        cfgmod_argv += ["# deconfigure ipdev %s" % ipdev]
-        cfgmod_argv += datapath_deconfigure_ipdev(ipdev)
-        cfgmod_argv += ["# reconfigure ipdev %s" % ipdev]
-        cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge, ipdev)]
-        if bridge == ipdev:
-            cfgmod_argv += ['--add=bridge.%s.mac=%s' % (bridge, pifrec['MAC'])]
-        else:
-            cfgmod_argv += ['--add=iface.%s.mac=%s' % (ipdev, pifrec['MAC'])]
-            
-        if pif_is_vlan(self._pif):
-            cfgmod_argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])]
-            cfgmod_argv += ['--add=iface.%s.internal=true' % (ipdev)]
-            cfgmod_argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)]
+            xs_network_uuids += [nwrec['uuid']]
+
+        vsctl_argv += ['# configure xs-network-uuids']
+        vsctl_argv += ['--', 'br-set-external-id', bridge,
+                'xs-network-uuids', ';'.join(xs_network_uuids)]
+
+        vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
+        vsctl_argv += datapath_deconfigure_ipdev(ipdev)
+        vsctl_argv += ["# reconfigure ipdev %s" % ipdev]
+        vsctl_argv += ['--', 'add-port', bridge, ipdev]
+
+        # XXX Needs support in ovs-vsctl
+        #if bridge == ipdev:
+        #    vsctl_argv += ['--add=bridge.%s.mac=%s' % (bridge, pifrec['MAC'])]
+        #else:
+        #    vsctl_argv += ['--add=iface.%s.mac=%s' % (ipdev, pifrec['MAC'])]
 
-        self._cfgmod_argv = cfgmod_argv
+        self._vsctl_argv = vsctl_argv
         self._extra_ports = extra_ports
 
     def bring_down_existing(self):
@@ -406,7 +395,7 @@ class DatapathVswitch(Datapath):
             if len(offload):
                 run_command(['/sbin/ethtool', '-K', dev] + offload)
 
-        datapath_modify_config(self._cfgmod_argv)
+        datapath_modify_config(self._vsctl_argv)
 
     def post(self):
         for p in self._extra_ports:
@@ -414,7 +403,7 @@ class DatapathVswitch(Datapath):
             netdev_up(p)
 
     def bring_down(self):
-        cfgmod_argv = []
+        vsctl_argv = []
 
         dp = self._dp
         ipdev = self._ipdev
@@ -423,12 +412,12 @@ class DatapathVswitch(Datapath):
 
         #nw = db().get_pif_record(self._pif)['network']
         #nwrec = db().get_network_record(nw)
-        #cfgmod_argv += ['# deconfigure xs-network-uuids']
-        #cfgmod_argv += ['--del-entry=bridge.%s.xs-network-uuids=%s' % (bridge,nwrec['uuid'])]
+        #vsctl_argv += ['# deconfigure xs-network-uuids']
+        #vsctl_argv += ['--del-entry=bridge.%s.xs-network-uuids=%s' % (bridge,nwrec['uuid'])]
 
         log("deconfigure ipdev %s on %s" % (ipdev,bridge))
-        cfgmod_argv += ["# deconfigure ipdev %s" % ipdev]
-        cfgmod_argv += datapath_deconfigure_ipdev(ipdev)
+        vsctl_argv += ["# deconfigure ipdev %s" % ipdev]
+        vsctl_argv += datapath_deconfigure_ipdev(ipdev)
 
         if pif_is_vlan(self._pif):
             # If the VLAN's slave is attached, leave datapath setup.
@@ -454,5 +443,5 @@ class DatapathVswitch(Datapath):
                 dp = None
 
         if dp:
-            cfgmod_argv += deconfigure_datapath(dp)
-            datapath_modify_config(cfgmod_argv)
+            vsctl_argv += deconfigure_datapath(dp)
+            datapath_modify_config(vsctl_argv)
index 31a48c3..99dd15d 100644 (file)
@@ -13,8 +13,7 @@ import os
 import socket
 import subprocess
 
-cfg_mod="/usr/bin/ovs-cfg-mod"
-vswitchd_cfg_filename="/etc/ovs-vswitchd.conf"
+vsctl="/usr/bin/ovs-vsctl"
 
 if __name__ == "__main__":
     raise Exception("This script is a plugin for xsconsole and cannot run independently")
@@ -78,10 +77,9 @@ class VSwitchService:
 class VSwitchConfig:
 
     @staticmethod
-    def Get(key):
+    def Get(action):
         try:
-            output = ShellPipe([cfg_mod, "-vANY:console:emer", "-F", 
-                    vswitchd_cfg_filename, "-q", key]).Stdout()
+            output = ShellPipe([vsctl, "-vANY:console:emer", action]).Stdout()
         except StandardError, e:
             XSLogError("config retrieval error: " + str(e))
             return "<unknown>"
@@ -285,7 +283,7 @@ class XSFeatureVSwitch:
         if dbController == "":
             dbController = Lang("<None>")
         inPane.AddStatusField(Lang("Controller (config)", 20), dbController)
-        controller = VSwitchConfig.Get("mgmt.controller")
+        controller = VSwitchConfig.Get("get-controller")
         if controller == "":
             controller = Lang("<None>")
         elif controller[0:4] == "ssl:":
index 16242c0..fbaa6ad 100755 (executable)
@@ -23,7 +23,8 @@ import sys
 argv0 = sys.argv[0]
 
 BRCTL = "/usr/lib/vswitch/xs-original/brctl"
-VSWITCHD_CONF = "/etc/ovs-vswitchd.conf"
+VSCTL = "/usr/bin/ovs-vsctl"
+OVSDB_SERVER = "unix:/var/run/ovsdb-server"
 
 # Execute the real brctl program, passing the same arguments that were passed
 # to us.
@@ -32,77 +33,29 @@ def delegate():
     # execl should never return.  We only arrive here if brctl failed to exec.
     sys.exit(1)
 
-# Read the ovs-vswitchd.conf file named 'filename' and return its contents as a
-# dictionary that maps from string keys to lists of string values.  (Even
-# singleton values are represented as lists.)
-def cfg_read(filename):
-    try:
-        f = open(filename)
-    except IOError, e:
-        sys.stderr.write("%s: could not open %s (%s)\n"
-                         % (argv0, filename, e.strerror))
-        sys.exit(1)
+def call_vsctl(cmd, arg=""):
+    database = '--db=' + OVSDB_SERVER
+    command = [VSCTL, database, cmd]
+    if (arg):
+        command.append(arg)
+    return subprocess.Popen(command, stdout=subprocess.PIPE).communicate()[0].split()
 
-    cfg = {}
-    rx = re.compile('([-._@$:+a-zA-Z0-9]+)(?:[ \t\r\n\v]*)=(?:[ \t\r\n\v]*)(.*)$')
-    for line in f:
-        line = line.strip()
-        if len(line) == 0 or line[0] == '#':
-            continue
-
-        match = rx.match(line)
-        if match == None:
-            continue
-
-        key, value = match.groups()
-        if key not in cfg:
-            cfg[key] = []
-        cfg[key].append(value)
-    return cfg
-
-# Returns a set of the immediate subsections of 'section' within 'cfg'.  For
-# example, if 'section' is "bridge" and keys bridge.a, bridge.b, bridge.b.c,
-# and bridge.c.x.y.z exist, returns set(['a', 'b', 'c']).
-def cfg_get_subsections(cfg, section):
-    subsections = set()
-    for key in cfg:
-        if key.startswith(section + "."):
-            dot = key.find(".", len(section) + 1)
-            if dot == -1:
-                dot = len(key)
-            subsections.add(key[len(section) + 1:dot])
-    return subsections
-
-# Returns True if 'cfg' contains a key whose single value is 'true'.  Otherwise
-# returns False.
-def cfg_get_bool(cfg, name):
-    return name in cfg and cfg[name] == ['true']
-
-# If 'cfg' has a port named 'port' configured with an implicit VLAN, returns
-# that VLAN number.  Otherwise, returns 0.
-def get_port_vlan(cfg, port):
-    try:
-        return int(cfg["vlan.%s.tag" % port][0])
-    except (ValueError, KeyError):
-        return 0
-
-# Returns all the ports within 'bridge' in 'cfg'.  If 'vlan' is nonnegative,
-# the ports returned are only those configured with implicit VLAN 'vlan'.
-def get_bridge_ports(cfg, bridge, vlan):
-    ports = []
-    for port in cfg["bridge.%s.port" % bridge]:
-        if vlan < 0 or get_port_vlan(cfg, port) == vlan:
-            ports.append(port)
-    return ports
-
-# Returns all the interfaces within 'bridge' in 'cfg'.  If 'vlan' is
-# nonnegative, the interfaces returned are only those whose ports are
-# configured with implicit VLAN 'vlan'.
-def get_bridge_ifaces(cfg, bridge, vlan):
-    ifaces = []
-    for port in get_bridge_ports(cfg, bridge, vlan):
-        ifaces.extend(cfg.get("bonding.%s.slave" % port, [port]))
-    return ifaces
+# Returns a list of all the bridges 
+def get_bridges():
+    return call_vsctl('list-br')
+
+# Returns a list of all ports on 'bridge' 
+def get_bridge_ports(bridge):
+    return call_vsctl('list-ports', bridge)
+
+# Returns a list of all interfaces on 'bridge' 
+def get_bridge_ifaces(bridge):
+    return call_vsctl('list-ifaces', bridge)
+
+# Returns the parent of 'bridge'.  If 'bridge' does not have a parent,
+# 'bridge' is returned.
+def get_bridge_parent(bridge):
+    return call_vsctl('br-to-parent', bridge)
 
 # Returns the first line of the file named 'name', with the trailing new-line
 # (if any) stripped off.
@@ -126,29 +79,22 @@ def get_bridge_id(netdev):
 
 def cmd_show():
     print "bridge name\tbridge id\t\tSTP enabled\tinterfaces"
-    cfg = cfg_read(VSWITCHD_CONF)
 
     # Find all the bridges.
-    real_bridges = [(br, br, 0) for br in cfg_get_subsections(cfg, "bridge")]
-    fake_bridges = []
-    for linux_bridge, ovs_bridge, vlan in real_bridges:
-        for iface in get_bridge_ifaces(cfg, ovs_bridge, -1):
-            if cfg_get_bool(cfg, "iface.%s.fake-bridge" % iface):
-                fake_bridges.append((iface, ovs_bridge,
-                                     get_port_vlan(cfg, iface)))
-    bridges = real_bridges + fake_bridges
+    bridges = get_bridges()
 
     # Find all the interfaces on each bridge.
-    for linux_bridge, ovs_bridge, vlan in bridges:
-        bridge_ports = get_bridge_ports(cfg, ovs_bridge, vlan)
-        if linux_bridge in bridge_ports:
-            bridge_ports.remove(linux_bridge)
+    for bridge in bridges:
+        bridge_ports = get_bridge_ports(bridge)
+        parent = get_bridge_parent(bridge)
+        if parent in bridge_ports:
+            bridge_ports.remove(parent)
         bridge_ports.sort()
-        bridge_id = get_bridge_id(linux_bridge)
+        bridge_id = get_bridge_id(bridge)
         first_port = ""
         if bridge_ports:
             first_port = bridge_ports[0]
-        print "%s\t\t%s\t%s\t\t%s" % (linux_bridge, bridge_id, "no", first_port)
+        print "%s\t\t%s\t%s\t\t%s" % (bridge, bridge_id, "no", first_port)
         for port in bridge_ports[1:]:
             print "\t\t\t\t\t\t\t%s" % port
 
index 7ce8bf7..430ec54 100755 (executable)
@@ -4,7 +4,7 @@
 # needed by the controller.  This is called by the "vif" script,
 # which is run when virtual interfaces are added and removed.
 
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
 #
 # Copying and distribution of this file, with or without modification,
 # are permitted in any medium without royalty provided the copyright
@@ -25,7 +25,6 @@ def get_vif_ref(domid, devid):
 
 # Query XAPI for the information we need using the vif's opaque reference
 def dump_vif_info(domid, devid, vif_ref):
-       vif_info = []
        session = XenAPI.xapi_local()
        session.xenapi.login_with_password("root", "")
        try: 
@@ -34,11 +33,14 @@ def dump_vif_info(domid, devid, vif_ref):
                vm_uuid = session.xenapi.VM.get_uuid(vif_rec["VM"])
 
                # Data to allow vNetManager to associate VIFs with xapi data
-               add_port = '--add=port.vif%s.%s' % (domid, devid)
-               vif_info.append('%s.net-uuid=%s' % (add_port, net_rec["uuid"]))
-               vif_info.append('%s.vif-mac=%s' % (add_port, vif_rec["MAC"]))
-               vif_info.append('%s.vif-uuid=%s' % (add_port, vif_rec["uuid"]))
-               vif_info.append('%s.vm-uuid=%s' % (add_port, vm_uuid))
+               vif_info = []
+               vif_info.append(('xs-network-uuid', net_rec["uuid"]))
+               vif_info.append(('xs-vif-mac', vif_rec["MAC"]))
+               vif_info.append(('xs-vif-uuid', vif_rec["uuid"]))
+               vif_info.append(('xs-vm-uuid', vm_uuid))
+               for key, value in vif_info:
+                       print("-- set interface vif%s.%s external-ids:\"%s\"=\"%s\""
+                             % (domid, devid, key, value))
 
                # vNetManager needs to know the network UUID(s) associated with
                # each datapath.  Normally interface-reconfigure adds them, but
@@ -51,13 +53,17 @@ def dump_vif_info(domid, devid, vif_ref):
                # ovs-vswitchd connection to vNetManager and setting this
                # configuration variable, but vNetManager can tolerate that.
                if not net_rec['PIFs']:
-                       key = 'bridge.%s.xs-network-uuids' % net_rec['bridge']
-                       value = net_rec['uuid']
-                       vif_info.append('--del-match=%s=*' % key)
-                       vif_info.append('--add=%s=%s' % (key, value))
+                       bridge = net_rec['bridge']
+
+                       xs_network_uuid = net_rec['uuid']
+                       print("-- br-set-external-id %s %s %s"
+                             % (bridge, "xs-network-uuids", xs_network_uuid))
+
+                       xs_network_name = net_rec['name_label']
+                       print("-- br-set-external-id %s %s %s"
+                             % (bridge, "xs-network-names", xs_network_name))
        finally:
                session.xenapi.session.logout()
-       print ' '.join(vif_info)
        
 if __name__ == '__main__':
        if len(sys.argv) != 3:
index 3209477..8b740c2 100644 (file)
@@ -1,6 +1,6 @@
 ### Configuration options for vswitch
 
-# Copyright (C) 2009 Nicira Networks, Inc.
+# Copyright (C) 2009, 2010 Nicira Networks, Inc.
 #
 # Copying and distribution of this file, with or without modification,
 # are permitted in any medium without royalty provided the copyright
 #    This is useful in some cases when replacing existing solutions.
 # ENABLE_FAKE_PROC_NET=y
 
+# ENABLE_MONITOR: If 'y' then monitor daemon processes and restart them
+#    if they die due to an error signal.
+# ENABLE_MONITOR=y
+
 # FORCE_COREFILES: If 'y' then core files will be enabled.
 # FORCE_COREFILES=y
 
 #     See the manpage for "core".
 # COREFILE_PATTERN="/var/log/%e-%t"
 
-# VSWITCHD_CONF: File in which ovs-vswitchd stores its configuration.
-# VSWITCHD_CONF=/etc/ovs-vswitchd.conf
+# OVSDB_SERVER_REMOTES: Space-separated list of methods on which to have
+#     ovsdb-server listen or connect for a JSON-RPC connection.
+# OVSDB_SERVER_REMOTES="punix:/var/run/ovsdb-server db:Open_vSwitch,managers"
+
+# OVSDB_SERVER_DB: File for which ovsdb-server uses for storage.
+# OVSDB_SERVER_DB=/etc/ovs-vswitchd.conf.db
+
+# OVSDB_SERVER_PIDFILE: File in which to store the pid of the running
+#     ovsdb-server.
+# OVSDB_SERVER_PIDFILE=/var/run/ovsdb-server.pid
+
+# OVSDB_SERVER_RUN_DIR: Set the directory in which ovsdb-server should be
+#     run.  This mainly affects where core files will be placed.
+# OVSDB_SERVER_RUN_DIR=/var/xen/vswitch
+
+# OVSDB_SERVER_PRIORITY: "nice" priority at which to run ovsdb-server and 
+#     related processes.
+# OVSDB_SERVER_PRIORITY=-10
+
+# OVSDB_SERVER_LOGFILE: File to send the FILE_LOGLEVEL log messages to.
+# OVSDB_SERVER_LOGFILE=/var/log/ovsdb-server.log
+
+# OVSDB_SERVER_FILE_LOGLEVEL: Log level at which to log into the
+#     OVSDB_SERVER_LOG file.  If this is null or not set the logfile will
+#     not be created and nothing will be sent to it.  This is the
+#     default.  The available options are: EMER, WARN, INFO and DBG.
+# OVSDB_SERVER_FILE_LOGLEVEL=""
+
+# OVSDB_SERVER_SYSLOG_LOGLEVEL: Log level at which to log into syslog.  If
+#     this is null or not set the default is to log to syslog
+#     emergency and warning level messages only.
+# OVSDB_SERVER_SYSLOG_LOGLEVEL="WARN"
+
+# OVSDB_SERVER_MEMLEAK_LOGFILE: File for logging memory leak data.
+#     Enabling this option will slow ovsdb-server significantly.  Do not
+#     enable it except to debug a suspected memory leak.  Use the
+#     ovs-parse-leaks utility included with Open vSwitch to parse the
+#     log file.  For best results, you also need debug symbols.
+# OVSDB_SERVER_MEMLEAK_LOGFILE=""
+
+# OVSDB_SERVER_STRACE_LOG: File for logging strace output.
+#     If this is set to a nonempty string, then ovsdb-server will run
+#     under strace, whose output will be logged to the specified file.
+#     Enabling this option will slow ovsdb-server significantly.
+#     OVSDB_SERVER_STRACE_LOG and OVSDB_SERVER_VALGRIND_LOG are mutually 
+#     exclusive.
+# OVSDB_SERVER_STRACE_LOG=""
+
+# OVSDB_SERVER_STRACE_OPT: Options to pass to strace.
+#     This option's value is honored only when OVSDB_SERVER_STRACE_LOG is
+#     set to a nonempty string.
+# OVSDB_SERVER_STRACE_OPT=""
+
+# OVSDB_SERVER_VALGRIND_LOG: File for logging valgrind output.
+#     If this is set to a nonempty string, then ovsdb-server will run
+#     under valgrind, whose output will be logged to the specified file.
+#     Enabling this option will slow ovsdb-server by 100X or more.
+#     valgrind is not installed by default on XenServer systems; you must
+#     install it by hand to usefully enable this option.
+#     OVSDB_SERVER_STRACE_LOG and OVSDB_SERVER_VALGRIND_LOG are mutually 
+#     exclusive.
+# OVSDB_SERVER_VALGRIND_LOG=""
+
+# OVSDB_SERVER_VALGRIND_OPT: Options to pass to valgrind.
+#     This option's value is honored only when OVSDB_SERVER_VALGRIND_LOG is
+#     set to a nonempty string.
+# OVSDB_SERVER_VALGRIND_OPT=""
+
+# VSWITCHD_OVSDB_SERVER: Method to have ovs-vswitchd initiate a JSON-RPC
+#     connection to an ovsdb-server instance.
+# VSWITCHD_OVSDB_SERVER=unix:/var/run/ovsdb-server
+
+# VSWITCHD_OVSDB_SCHEMA: Schema file to use for generating a new OVSDB
+#     ovs-vswitchd database.
+# VSWITCHD_OVSDB_SCHEMA=/usr/share/vswitch/vswitch.ovsschema
 
 # VSWITCHD_PIDFILE: File in which to store the pid of the running
 #     ovs-vswitchd.
 # VSWITCHD_LOGFILE: File to send the FILE_LOGLEVEL log messages to.
 # VSWITCHD_LOGFILE=/var/log/ovs-vswitchd.log
 
-# VSWITCHD_MEMLEAK_LOGFILE: File for logging memory leak data.
-#     Enabling this option will slow ovs-vswitchd significantly.  Do not
-#     enable it except to debug a suspected memory leak.  Use the
-#     ovs-parse-leaks utility included with Open vSwitch to parse the
-#     log file.  For best results, you also need debug symbols.
-# VSWITCHD_MEMLEAK_LOGFILE=""
-
 # VSWITCHD_FILE_LOGLEVEL: Log level at which to log into the
 #     VSWITCHD_LOG file.  If this is null or not set the logfile will
 #     not be created and nothing will be sent to it.  This is the
 #     emergency and warning level messages only.
 # VSWITCHD_SYSLOG_LOGLEVEL="WARN"
 
+# VSWITCHD_MEMLEAK_LOGFILE: File for logging memory leak data.
+#     Enabling this option will slow ovs-vswitchd significantly.  Do not
+#     enable it except to debug a suspected memory leak.  Use the
+#     ovs-parse-leaks utility included with Open vSwitch to parse the
+#     log file.  For best results, you also need debug symbols.
+# VSWITCHD_MEMLEAK_LOGFILE=""
+
 # VSWITCHD_STRACE_LOG: File for logging strace output.
 #     If this is set to a nonempty string, then ovs-vswitchd will run
 #     under strace, whose output will be logged to the specified file.
index 2d8acd5..41f709d 100644 (file)
@@ -67,6 +67,8 @@ install -d -m 755 $RPM_BUILD_ROOT/etc/xapi.d/plugins
 install -m 755 xenserver/etc_xapi.d_plugins_vswitch-cfg-update \
          $RPM_BUILD_ROOT/etc/xapi.d/plugins/vswitch-cfg-update
 install -d -m 755 $RPM_BUILD_ROOT/usr/share/vswitch/scripts
+install -m 644 vswitchd/vswitch.ovsschema \
+         $RPM_BUILD_ROOT/usr/share/vswitch/vswitch.ovsschema
 install -m 755 xenserver/opt_xensource_libexec_interface-reconfigure \
              $RPM_BUILD_ROOT/usr/share/vswitch/scripts/interface-reconfigure
 install -m 755 xenserver/opt_xensource_libexec_InterfaceReconfigure.py \
@@ -109,8 +111,6 @@ rm \
     $RPM_BUILD_ROOT/usr/share/man/man8/ovs-openflowd.8 \
     $RPM_BUILD_ROOT/usr/share/man/man8/ovs-pki.8
 rm -f $RPM_BUILD_ROOT/lib/modules/%{xen_version}/kernel/net/vswitch/veth_mod.ko
-rm -r \
-    $RPM_BUILD_ROOT/usr/share/openvswitch/commands
 
 install -d -m 755 $RPM_BUILD_ROOT/var/lib/openvswitch
 
@@ -135,6 +135,15 @@ EOF
     elif md5sum -c --status <<EOF
 ca141d60061dcfdade73e75abc6529b5  /usr/sbin/brctl
 b8e9835862ef1a9cec2a3f477d26c989  /etc/xensource/scripts/vif
+51970ad613a3996d5997e18e44db47da  /opt/xensource/libexec/interface-reconfigure
+f6519085c2fc5f7bc4eccc294ed62000  /usr/sbin/xen-bugtool
+EOF
+    then
+        printf "\nVerified host scripts from XenServer 5.5.0-24648p (Update 1)\n"
+        printf "or 5.5.0-25727p (Update 2).\n\n"
+    elif md5sum -c --status <<EOF
+ca141d60061dcfdade73e75abc6529b5  /usr/sbin/brctl
+b8e9835862ef1a9cec2a3f477d26c989  /etc/xensource/scripts/vif
 ce451d3c985fd1db6497a363f0d9dedb  /opt/xensource/libexec/interface-reconfigure
 2b53f500431fcba5276c896e9e4281b9  /usr/sbin/xen-bugtool
 EOF
@@ -192,8 +201,16 @@ net.ipv4.conf.all.arp_filter = 1
 EOF
 fi
 
-# Ensure ovs-vswitchd.conf exists
-touch /etc/ovs-vswitchd.conf
+if test ! -e /etc/ovs-vswitchd.conf.db; then
+    # Create ovs-vswitchd config database
+    ovsdb-tool -vANY:console:emer create /etc/ovs-vswitchd.conf.db \
+            /usr/share/vswitch/vswitch.ovsschema \
+
+    # Create initial table in config database
+    ovsdb-tool -vANY:console:emer transact /etc/ovs-vswitchd.conf.db \
+            '[{"op": "insert", "table": "Open_vSwitch", "row": {}}]' \
+            > /dev/null
+fi
 
 # Create default or update existing /etc/sysconfig/vswitch.
 SYSCONFIG=/etc/sysconfig/vswitch
@@ -296,7 +313,7 @@ if [ "$1" = "0" ]; then     # $1 = 1 for upgrade
     done
 
     # Remove all configuration files
-    rm -f /etc/ovs-vswitchd.conf
+    rm -f /etc/ovs-vswitchd.conf.db
     rm -f /etc/sysconfig/vswitch
     rm -f /etc/ovs-vswitchd.cacert
     rm -f /var/xapi/network.dbcache
@@ -320,6 +337,9 @@ fi
 /etc/profile.d/vswitch.sh
 /lib/modules/%{xen_version}/kernel/net/vswitch/openvswitch_mod.ko
 /lib/modules/%{xen_version}/kernel/net/vswitch/brcompat_mod.ko
+%if %(echo '%{xen_version}'|awk -F"." '{if ($3>=18) print 1; else print 0;}')
+/lib/modules/%{xen_version}/kernel/net/vswitch/ip_gre_mod.ko
+%endif
 /usr/share/vswitch/scripts/dump-vif-details
 /usr/share/vswitch/scripts/refresh-xs-network-uuids
 /usr/share/vswitch/scripts/interface-reconfigure
@@ -344,17 +364,21 @@ fi
 # include them.
 /usr/share/vswitch/scripts/XSFeatureVSwitch.pyc
 /usr/share/vswitch/scripts/XSFeatureVSwitch.pyo
+/usr/share/vswitch/vswitch.ovsschema
 /usr/sbin/ovs-brcompatd
 /usr/sbin/ovs-vswitchd
+/usr/sbin/ovsdb-server
 /usr/bin/ovs-appctl
-/usr/bin/ovs-cfg-mod
 /usr/bin/ovs-dpctl
 /usr/bin/ovs-ofctl
 /usr/bin/ovs-vsctl
-/usr/share/man/man5/ovs-vswitchd.conf.5.gz
+/usr/bin/ovsdb-client
+/usr/bin/ovsdb-tool
+/usr/share/man/man1/ovsdb-client.1.gz
+/usr/share/man/man1/ovsdb-server.1.gz
+/usr/share/man/man1/ovsdb-tool.1.gz
 /usr/share/man/man8/ovs-appctl.8.gz
 /usr/share/man/man8/ovs-brcompatd.8.gz
-/usr/share/man/man8/ovs-cfg-mod.8.gz
 /usr/share/man/man8/ovs-dpctl.8.gz
 /usr/share/man/man8/ovs-ofctl.8.gz
 /usr/share/man/man8/ovs-vsctl.8.gz