aboutsummaryrefslogtreecommitdiff
path: root/ovsdb
diff options
context:
space:
mode:
authorBen Pfaff <blp@nicira.com>2009-11-04 15:11:44 -0800
committerBen Pfaff <blp@nicira.com>2009-11-04 17:12:10 -0800
commitf85f8ebbfac946c19b3c6eb0f4170f579d0a4d25 (patch)
tree2111aee77751f2143773907f81c6adb9101afb6c /ovsdb
parentf212909325be9bc7e296e1a32e2fc89694a0049f (diff)
Initial implementation of OVSDB.
Diffstat (limited to 'ovsdb')
-rw-r--r--ovsdb/SPECS628
-rw-r--r--ovsdb/automake.mk44
-rw-r--r--ovsdb/column.c232
-rw-r--r--ovsdb/column.h84
-rw-r--r--ovsdb/condition.c284
-rw-r--r--ovsdb/condition.h72
-rw-r--r--ovsdb/execution.c613
-rw-r--r--ovsdb/file.c360
-rw-r--r--ovsdb/file.h36
-rw-r--r--ovsdb/jsonrpc-server.c362
-rw-r--r--ovsdb/jsonrpc-server.h29
-rw-r--r--ovsdb/ovsdb-server.c223
-rw-r--r--ovsdb/ovsdb-tool.c204
-rw-r--r--ovsdb/ovsdb.c262
-rw-r--r--ovsdb/ovsdb.h73
-rw-r--r--ovsdb/query.c99
-rw-r--r--ovsdb/query.h37
-rw-r--r--ovsdb/row.c386
-rw-r--r--ovsdb/row.h139
-rw-r--r--ovsdb/table.c228
-rw-r--r--ovsdb/table.h63
-rw-r--r--ovsdb/transaction.c444
-rw-r--r--ovsdb/transaction.h41
-rw-r--r--ovsdb/trigger.c129
-rw-r--r--ovsdb/trigger.h44
25 files changed, 5116 insertions, 0 deletions
diff --git a/ovsdb/SPECS b/ovsdb/SPECS
new file mode 100644
index 00000000..12d97682
--- /dev/null
+++ b/ovsdb/SPECS
@@ -0,0 +1,628 @@
+ ===================================================
+ 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.
+
+<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).
+
+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 "tables" is a JSON object whose
+ names are table 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 at least 0,
+ "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. It
+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 a "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.
+
+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 a previous "insert"
+ operation's "uuid-name". For example, if a previous "insert"
+ operation specified a "uuid-name" of "myrow", the following
+ <named-uuid> represents the UUID created by that operation:
+
+ ["named-uuid", "myrow"]
+
+<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".
+
+Operations
+----------
+
+Each of the available operations is described below.
+
+insert
+......
+
+Request object members:
+
+ "op": "insert" required
+ "table": <table> required
+ "row": <row> required
+ "uuid-name": <string> 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.
+
+ The new row receives a new, randomly generated UUID, which is
+ returned as the "_uuid" member of the result. If "uuid-name"
+ is supplied, then the UUID is made available under that name
+ to later operations within the same 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 updated.
+ 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.
+
+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.
diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk
new file mode 100644
index 00000000..d2a3e04c
--- /dev/null
+++ b/ovsdb/automake.mk
@@ -0,0 +1,44 @@
+# 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/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
+
+# 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.8
+#man_MANS += ovsdb/ovsdb-tool.8
+#DISTCLEANFILES += ovsdb/ovsdb-tool.8
+#EXTRA_DIST += ovsdb/ovsdb-tool.8.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 $(FAULT_LIBS)
+## ovsdb-server.8
+#man_MANS += ovsdb/ovsdb-server.8
+#DISTCLEANFILES += ovsdb/ovsdb-server.8
+#EXTRA_DIST += ovsdb/ovsdb-server.8.in
diff --git a/ovsdb/column.c b/ovsdb/column.c
new file mode 100644
index 00000000..1e8a2d09
--- /dev/null
+++ b/ovsdb/column.c
@@ -0,0 +1,232 @@
+/* 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 *ts;
+
+ ts = xzalloc(sizeof *ts);
+ ts->name = xstrdup(name);
+ ts->comment = comment ? xstrdup(comment) : NULL;
+ ts->mutable = mutable;
+ ts->persistent = persistent;
+ ts->type = *type;
+
+ return ts;
+}
+
+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;
+}
+
+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 {
+ 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;
+
+ if (json->u.array.elems[i]->type != JSON_STRING) {
+ goto error;
+ }
+
+ column = shash_find_data(&table->schema->columns,
+ json->u.array.elems[i]->u.string);
+ if (ovsdb_column_set_contains(set, column->index)) {
+ goto error;
+ }
+ ovsdb_column_set_add(set, column);
+ }
+
+ return NULL;
+ }
+
+error:
+ ovsdb_column_set_destroy(set);
+ return ovsdb_syntax_error(json, NULL,
+ "array of distinct column names expected");
+}
+
+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
index 00000000..59421510
--- /dev/null
+++ b/ovsdb/column.h
@@ -0,0 +1,84 @@
+/* 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 *);
+
+/* 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 *);
+
+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
index 00000000..0342b8e8
--- /dev/null
+++ b/ovsdb/condition.c
@@ -0,0 +1,284 @@
+/* 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
index 00000000..8c422b95
--- /dev/null
+++ b/ovsdb/condition.h
@@ -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
index 00000000..25b34b19
--- /dev/null
+++ b/ovsdb/execution.c
@@ -0,0 +1,613 @@
+/* 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 <assert.h>
+#include <limits.h>
+
+#include "column.h"
+#include "condition.h"
+#include "file.h"
+#include "json.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 struct ovsdb_error *do_commit(struct ovsdb_execution *);
+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_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 *
+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 },
+ { "delete", ovsdb_execute_delete },
+ { "wait", ovsdb_execute_wait },
+ { "commit", ovsdb_execute_commit },
+ { "abort", ovsdb_execute_abort },
+ };
+
+ 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 {
+ error = ovsdb_syntax_error(operation, "unknown operation",
+ "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;
+ ovsdb_error_destroy(error);
+ json_destroy(results);
+ return NULL;
+ }
+
+ /* Add result to array. */
+ json_array_add(results, result);
+ if (error) {
+ break;
+ }
+ }
+
+ if (!error) {
+ /* Commit transaction. Bail if commit encounters error. */
+ error = do_commit(&x);
+ 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());
+ }
+
+ 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_error *
+do_commit(struct ovsdb_execution *x)
+{
+ if (x->db->file) {
+ struct ovsdb_error *error;
+ struct json *json;
+
+ json = ovsdb_txn_to_json(x->txn);
+ if (!json) {
+ /* Nothing to commit. */
+ return NULL;
+ }
+
+ error = ovsdb_file_write(x->db->file, json);
+ json_destroy(json);
+ if (error) {
+ return ovsdb_wrap_error(error, "writing transaction failed");
+ }
+
+ if (x->durable) {
+ error = ovsdb_file_commit(x->db->file);
+ if (error) {
+ return ovsdb_wrap_error(error,
+ "committing transaction failed");
+ }
+ }
+ }
+
+ ovsdb_txn_commit(x->txn);
+ return NULL;
+}
+
+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;
+
+ 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 (!error) {
+ error = parse_row(parser, "row", table, x->symtab, &row, NULL);
+ }
+ if (!error) {
+ uuid_generate(ovsdb_row_get_uuid_rw(row));
+ if (uuid_name) {
+ ovsdb_symbol_table_put(x->symtab, json_string(uuid_name),
+ ovsdb_row_get_uuid(row));
+ }
+ 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 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;
+}
diff --git a/ovsdb/file.c b/ovsdb/file.c
new file mode 100644
index 00000000..4883d76f
--- /dev/null
+++ b/ovsdb/file.c
@@ -0,0 +1,360 @@
+/* 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 "file.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-error.h"
+#include "sha1.h"
+#include "util.h"
+
+#define THIS_MODULE VLM_ovsdb_file
+#include "vlog.h"
+
+enum ovsdb_file_mode {
+ OVSDB_FILE_READ,
+ OVSDB_FILE_WRITE
+};
+
+struct ovsdb_file {
+ off_t offset;
+ char *name;
+ struct lockfile *lockfile;
+ FILE *stream;
+ struct ovsdb_error *read_error;
+ struct ovsdb_error *write_error;
+ enum ovsdb_file_mode mode;
+};
+
+struct ovsdb_error *
+ovsdb_file_open(const char *name, int flags, struct ovsdb_file **filep)
+{
+ struct lockfile *lockfile;
+ struct ovsdb_error *error;
+ struct ovsdb_file *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_FILE_READ;
+ *filep = file;
+ return NULL;
+
+error_close:
+ close(fd);
+error_unlock:
+ lockfile_unlock(lockfile);
+error:
+ return error;
+}
+
+void
+ovsdb_file_close(struct ovsdb_file *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_file_read_cbdata {
+ char input[4096];
+ struct ovsdb_file *file;
+ int error;
+ unsigned long length;
+};
+
+static struct ovsdb_error *
+parse_body(struct ovsdb_file *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_file_read(struct ovsdb_file *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_FILE_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_file_write(struct ovsdb_file *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_FILE_READ) {
+ file->mode = OVSDB_FILE_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. */
+ 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_file_commit(struct ovsdb_file *file)
+{
+ if (fsync(fileno(file->stream))) {
+ return ovsdb_io_error(errno, "%s: fsync failed", file->name);
+ }
+ return 0;
+}
diff --git a/ovsdb/file.h b/ovsdb/file.h
new file mode 100644
index 00000000..5178140a
--- /dev/null
+++ b/ovsdb/file.h
@@ -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_FILE_H
+#define OVSDB_FILE_H 1
+
+#include <sys/types.h>
+#include "compiler.h"
+
+struct json;
+struct ovsdb_file;
+
+struct ovsdb_error *ovsdb_file_open(const char *name, int flags,
+ struct ovsdb_file **) WARN_UNUSED_RESULT;
+void ovsdb_file_close(struct ovsdb_file *);
+
+struct ovsdb_error *ovsdb_file_read(struct ovsdb_file *, struct json **)
+ WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_file_write(struct ovsdb_file *, struct json *)
+ WARN_UNUSED_RESULT;
+struct ovsdb_error *ovsdb_file_commit(struct ovsdb_file *)
+ WARN_UNUSED_RESULT;
+
+#endif /* ovsdb/file.h */
diff --git a/ovsdb/jsonrpc-server.c b/ovsdb/jsonrpc-server.c
new file mode 100644
index 00000000..36c9a7a2
--- /dev/null
+++ b/ovsdb/jsonrpc-server.c
@@ -0,0 +1,362 @@
+/* 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 "jsonrpc-server.h"
+
+#include <errno.h>
+
+#include "json.h"
+#include "jsonrpc.h"
+#include "ovsdb.h"
+#include "stream.h"
+#include "svec.h"
+#include "timeval.h"
+#include "trigger.h"
+
+#define THIS_MODULE VLM_ovsdb_jsonrpc_server
+#include "vlog.h"
+
+struct ovsdb_jsonrpc_trigger {
+ struct ovsdb_trigger trigger;
+ struct ovsdb_jsonrpc_session *session;
+ struct hmap_node hmap_node; /* Element in session's trigger table. */
+ struct json *id;
+};
+
+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 *);
+
+struct ovsdb_jsonrpc_session {
+ struct ovsdb_jsonrpc_server *server;
+ struct list node; /* Element in server's sessions list. */
+ struct jsonrpc *rpc;
+ struct hmap triggers;
+ struct list completions; /* Completed triggers. */
+};
+
+static void ovsdb_jsonrpc_session_open(struct ovsdb_jsonrpc_server *,
+ struct stream *);
+static void ovsdb_jsonrpc_session_close(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 *);
+
+struct ovsdb_jsonrpc_server {
+ struct ovsdb *db;
+
+ struct list sessions; /* List of "struct ovsdb_jsonrpc_session"s. */
+ unsigned int n_sessions, max_sessions;
+ unsigned int max_triggers;
+
+ struct pstream **listeners;
+ size_t n_listeners, allocated_listeners;
+};
+
+static void ovsdb_jsonrpc_server_listen(struct ovsdb_jsonrpc_server *,
+ struct pstream *);
+
+int
+ovsdb_jsonrpc_server_create(struct ovsdb *db, const struct svec *active,
+ const struct svec *passive,
+ struct ovsdb_jsonrpc_server **serverp)
+{
+ struct ovsdb_jsonrpc_server *server;
+ const char *name;
+ int retval = 0;
+ size_t i;
+
+ server = xzalloc(sizeof *server);
+ server->db = db;
+ server->max_sessions = 64;
+ server->max_triggers = 64;
+ list_init(&server->sessions);
+
+ SVEC_FOR_EACH (i, name, active) {
+ struct stream *stream;
+ int error;
+
+ error = stream_open(name, &stream);
+ if (!error) {
+ ovsdb_jsonrpc_session_open(server, stream);
+ } else {
+ ovs_error(error, "%s: connection failed", name);
+ retval = error;
+ }
+ }
+
+ SVEC_FOR_EACH (i, name, passive) {
+ struct pstream *pstream;
+ int error;
+
+ error = pstream_open(name, &pstream);
+ if (!error) {
+ ovsdb_jsonrpc_server_listen(server, pstream);
+ } else {
+ ovs_error(error, "failed to listen on %s", name);
+ retval = error;
+ }
+ }
+
+ *serverp = server;
+ return retval;
+}
+
+void
+ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr)
+{
+ struct ovsdb_jsonrpc_session *s, *next;
+ size_t i;
+
+ /* Accept new connections. */
+ for (i = 0; i < svr->n_listeners && svr->n_sessions < svr->max_sessions;) {
+ struct pstream *listener = svr->listeners[i];
+ struct stream *stream;
+ int error;
+
+ error = pstream_accept(listener, &stream);
+ if (!error) {
+ ovsdb_jsonrpc_session_open(svr, stream);
+ } else if (error == EAGAIN) {
+ i++;
+ } else if (error) {
+ VLOG_WARN("%s: accept failed: %s",
+ pstream_get_name(listener), strerror(error));
+ pstream_close(listener);
+ svr->listeners[i] = svr->listeners[--svr->n_listeners];
+ }
+ }
+
+ /* Handle each session. */
+ LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node,
+ &svr->sessions) {
+ struct jsonrpc_msg *msg;
+ int error;
+
+ jsonrpc_run(s->rpc);
+
+ 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);
+ }
+
+ if (!jsonrpc_get_backlog(s->rpc) && !jsonrpc_recv(s->rpc, &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_get_name(s->rpc),
+ jsonrpc_msg_type_to_string(msg->type));
+ jsonrpc_error(s->rpc, EPROTO);
+ jsonrpc_msg_destroy(msg);
+ }
+ }
+
+ error = jsonrpc_get_status(s->rpc);
+ if (error) {
+ ovsdb_jsonrpc_session_close(s);
+ }
+ }
+}
+
+void
+ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *svr)
+{
+ struct ovsdb_jsonrpc_session *s;
+
+ if (svr->n_sessions < svr->max_sessions) {
+ size_t i;
+
+ for (i = 0; i < svr->n_sessions; i++) {
+ pstream_wait(svr->listeners[i]);
+ }
+ }
+
+ LIST_FOR_EACH (s, struct ovsdb_jsonrpc_session, node, &svr->sessions) {
+ jsonrpc_wait(s->rpc);
+ if (!jsonrpc_get_backlog(s->rpc)) {
+ jsonrpc_recv_wait(s->rpc);
+ }
+ }
+}
+
+static void
+ovsdb_jsonrpc_server_listen(struct ovsdb_jsonrpc_server *svr,
+ struct pstream *pstream)
+{
+ if (svr->n_listeners >= svr->allocated_listeners) {
+ svr->listeners = x2nrealloc(svr->listeners, &svr->allocated_listeners,
+ sizeof *svr->listeners);
+ }
+ svr->listeners[svr->n_listeners++] = pstream;
+}
+
+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_get_status(s->rpc)) {
+ 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_send(s->rpc, reply);
+ }
+
+ json_destroy(t->id);
+ ovsdb_trigger_destroy(&t->trigger);
+ hmap_remove(&s->triggers, &t->hmap_node);
+ free(t);
+}
+
+static void
+ovsdb_jsonrpc_session_open(struct ovsdb_jsonrpc_server *svr,
+ struct stream *stream)
+{
+ struct ovsdb_jsonrpc_session *s;
+
+ s = xzalloc(sizeof *s);
+ s->server = svr;
+ list_push_back(&svr->sessions, &s->node);
+ s->rpc = jsonrpc_open(stream);
+ hmap_init(&s->triggers);
+ list_init(&s->completions);
+}
+
+static void
+ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s)
+{
+ struct ovsdb_jsonrpc_trigger *t, *next;
+
+ jsonrpc_error(s->rpc, EOF);
+ HMAP_FOR_EACH_SAFE (t, next, struct ovsdb_jsonrpc_trigger, hmap_node,
+ &s->triggers) {
+ ovsdb_jsonrpc_trigger_complete(t);
+ }
+
+ jsonrpc_close(s->rpc);
+
+ list_remove(&s->node);
+ s->server->n_sessions--;
+}
+
+static struct jsonrpc_msg *
+execute_transaction(struct ovsdb_jsonrpc_session *s,
+ struct jsonrpc_msg *request)
+{
+ struct ovsdb_jsonrpc_trigger *t;
+ size_t hash;
+
+ /* Check for duplicate ID. */
+ hash = json_hash(request->id, 0);
+ t = ovsdb_jsonrpc_trigger_find(s, request->id, hash);
+ if (t) {
+ return jsonrpc_create_error(
+ json_string_create("duplicate request ID"), request->id);
+ }
+
+ /* Insert into trigger table. */
+ t = xmalloc(sizeof *t);
+ ovsdb_trigger_init(s->server->db,
+ &t->trigger, request->params, &s->completions,
+ time_msec());
+ t->session = s;
+ t->id = request->id;
+ hmap_insert(&s->triggers, &t->hmap_node, hash);
+
+ request->id = NULL;
+ request->params = NULL;
+
+ /* Complete early if possible. */
+ if (ovsdb_trigger_is_complete(&t->trigger)) {
+ ovsdb_jsonrpc_trigger_complete(t);
+ }
+
+ 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, "get_schema")) {
+ reply = jsonrpc_create_reply(
+ ovsdb_schema_to_json(s->server->db->schema), request->id);
+ } else {
+ reply = jsonrpc_create_error(json_string_create("unknown method"),
+ request->id);
+ }
+
+ if (reply) {
+ jsonrpc_msg_destroy(request);
+ jsonrpc_send(s->rpc, reply);
+ }
+}
+
+static void
+execute_cancel(struct ovsdb_jsonrpc_session *s, struct jsonrpc_msg *request)
+{
+ size_t hash = json_hash(request->id, 0);
+ struct ovsdb_jsonrpc_trigger *t;
+
+ t = ovsdb_jsonrpc_trigger_find(s, request->params, hash);
+ 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);
+}
diff --git a/ovsdb/jsonrpc-server.h b/ovsdb/jsonrpc-server.h
new file mode 100644
index 00000000..49b5f8a9
--- /dev/null
+++ b/ovsdb/jsonrpc-server.h
@@ -0,0 +1,29 @@
+/* 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_JSONRPC_SERVER_H
+#define OVSDB_JSONRPC_SERVER_H 1
+
+struct ovsdb;
+struct ovsdb_jsonrpc_server;
+struct svec;
+
+int ovsdb_jsonrpc_server_create(struct ovsdb *, const struct svec *active,
+ const struct svec *passive,
+ struct ovsdb_jsonrpc_server **);
+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/ovsdb-server.c b/ovsdb/ovsdb-server.c
new file mode 100644
index 00000000..17a9970e
--- /dev/null
+++ b/ovsdb/ovsdb-server.c
@@ -0,0 +1,223 @@
+/* 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.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+
+#include "command-line.h"
+#include "daemon.h"
+#include "fault.h"
+#include "json.h"
+#include "jsonrpc.h"
+#include "jsonrpc-server.h"
+#include "leak-checker.h"
+#include "list.h"
+#include "ovsdb-error.h"
+#include "poll-loop.h"
+#include "process.h"
+#include "stream.h"
+#include "svec.h"
+#include "timeval.h"
+#include "trigger.h"
+#include "util.h"
+#include "unixctl.h"
+
+#include "vlog.h"
+#define THIS_MODULE VLM_ovsdb_server
+
+static const struct jsonrpc_server_cbs ovsdb_jsonrpc_cbs;
+
+static void parse_options(int argc, char *argv[], char **file_namep,
+ struct svec *active, struct svec *passive);
+static void usage(void) NO_RETURN;
+
+static void ovsdb_transact(struct unixctl_conn *, const char *args, void *db);
+
+int
+main(int argc, char *argv[])
+{
+ struct unixctl_server *unixctl;
+ struct ovsdb_jsonrpc_server *jsonrpc;
+ struct svec active, passive;
+ struct ovsdb_error *error;
+ struct ovsdb *db;
+ char *file_name;
+ int retval;
+
+ set_program_name(argv[0]);
+ register_fault_handlers();
+ time_init();
+ vlog_init();
+ signal(SIGPIPE, SIG_IGN);
+ process_init();
+
+ parse_options(argc, argv, &file_name, &active, &passive);
+
+ error = ovsdb_open(file_name, false, &db);
+ if (error) {
+ ovs_fatal(0, "%s", ovsdb_error_to_string(error));
+ }
+
+ retval = ovsdb_jsonrpc_server_create(db, &active, &passive, &jsonrpc);
+ if (retval) {
+ ovs_fatal(retval, "failed to initialize JSON-RPC server for OVSDB");
+ }
+ svec_destroy(&active);
+ svec_destroy(&passive);
+
+ die_if_already_running();
+ daemonize();
+
+ retval = unixctl_server_create(NULL, &unixctl);
+ if (retval) {
+ ovs_fatal(retval, "could not listen for control connections");
+ }
+
+ unixctl_command_register("ovsdb/transact", ovsdb_transact, db);
+
+ for (;;) {
+ 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();
+ }
+
+ return 0;
+}
+
+static void
+parse_options(int argc, char *argv[], char **file_namep,
+ struct svec *active, struct svec *passive)
+{
+ enum {
+ OPT_DUMMY = UCHAR_MAX + 1,
+ OPT_CONNECT,
+ OPT_LISTEN,
+ VLOG_OPTION_ENUMS,
+ LEAK_CHECKER_OPTION_ENUMS
+ };
+ static struct option long_options[] = {
+ {"connect", required_argument, 0, OPT_CONNECT},
+ {"listen", required_argument, 0, OPT_LISTEN},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'V'},
+ DAEMON_LONG_OPTIONS,
+ VLOG_LONG_OPTIONS,
+ LEAK_CHECKER_LONG_OPTIONS,
+ {0, 0, 0, 0},
+ };
+ char *short_options = long_options_to_short_options(long_options);
+
+ svec_init(active);
+ svec_init(passive);
+ for (;;) {
+ int c;
+
+ c = getopt_long(argc, argv, short_options, long_options, NULL);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case OPT_CONNECT:
+ svec_add(active, optarg);
+ break;
+
+ case OPT_LISTEN:
+ svec_add(passive, 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
+
+ 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");
+ }
+
+ *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"
+ " --connect=REMOTE make active connection to REMOTE\n"
+ " --listen=LOCAL passively listen on LOCAL\n");
+ stream_usage("JSON-RPC", 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);
+}
+
+static void
+ovsdb_transact(struct unixctl_conn *conn, const char *args, void *db_)
+{
+ struct ovsdb *db = db_;
+ struct json *request, *reply;
+ char *reply_string;
+
+ /* Parse JSON. */
+ request = json_from_string(args);
+ if (request->type == JSON_STRING) {
+ unixctl_command_reply(conn, 501, request->u.string);
+ json_destroy(request);
+ return;
+ }
+
+ /* Execute command. */
+ reply = ovsdb_execute(db, request, 0, NULL);
+ reply_string = json_to_string(reply, 0);
+ unixctl_command_reply(conn, 200, reply_string);
+ free(reply_string);
+ json_destroy(reply);
+}
diff --git a/ovsdb/ovsdb-tool.c b/ovsdb/ovsdb-tool.c
new file mode 100644
index 00000000..5169653d
--- /dev/null
+++ b/ovsdb/ovsdb-tool.c
@@ -0,0 +1,204 @@
+/*
+ * 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 <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 "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
+
+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[] = {
+ {"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 '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",
+ program_name, program_name);
+ vlog_usage();
+ printf("\nOther options:\n"
+ " -h, --help display this help message\n"
+ " -V, --version display version information\n");
+ exit(EXIT_SUCCESS);
+}
+
+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));
+ }
+}
+
+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_file *db_file;
+ 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);
+
+ /* Create database file. */
+ check_ovsdb_error(ovsdb_file_open(db_file_name, O_RDWR | O_CREAT | O_EXCL,
+ &db_file));
+ check_ovsdb_error(ovsdb_file_write(db_file, json));
+ check_ovsdb_error(ovsdb_file_commit(db_file));
+ ovsdb_file_close(db_file);
+
+ json_destroy(json);
+}
+
+static void
+transact(int flags, const char *db_file_name, const char *transaction)
+{
+ struct json *request, *result;
+ struct ovsdb *db;
+
+ check_ovsdb_error(ovsdb_open(db_file_name, flags, &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(O_RDONLY, argv[1], argv[2]);
+}
+
+static void
+do_transact(int argc UNUSED, char *argv[])
+{
+ transact(O_RDWR, argv[1], argv[2]);
+}
+
+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 },
+ { "help", 0, INT_MAX, do_help },
+ { NULL, 0, 0, NULL },
+};
diff --git a/ovsdb/ovsdb.c b/ovsdb/ovsdb.c
new file mode 100644
index 00000000..e653758f
--- /dev/null
+++ b/ovsdb/ovsdb.c
@@ -0,0 +1,262 @@
+/* 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.h"
+
+#include <fcntl.h>
+
+#include "file.h"
+#include "json.h"
+#include "ovsdb-error.h"
+#include "ovsdb-parser.h"
+#include "table.h"
+#include "transaction.h"
+
+#define THIS_MODULE VLM_ovsdb
+#include "vlog.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);
+ if (error) {
+ json_destroy(json);
+ 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 {
+ 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;
+}
+
+struct ovsdb *
+ovsdb_create(struct ovsdb_file *file, struct ovsdb_schema *schema)
+{
+ struct shash_node *node;
+ struct ovsdb *db;
+
+ db = xmalloc(sizeof *db);
+ db->schema = schema;
+ db->file = file;
+ 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;
+}
+
+struct ovsdb_error *
+ovsdb_open(const char *file_name, bool read_only, struct ovsdb **dbp)
+{
+ struct ovsdb_schema *schema;
+ struct ovsdb_error *error;
+ struct ovsdb_file *file;
+ struct json *json;
+ struct ovsdb *db;
+
+ error = ovsdb_file_open(file_name, read_only ? O_RDONLY : O_RDWR, &file);
+ if (error) {
+ return error;
+ }
+
+ error = ovsdb_file_read(file, &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(read_only ? file : NULL, schema);
+ while ((error = ovsdb_file_read(file, &json)) == NULL && json) {
+ struct ovsdb_txn *txn;
+
+ error = ovsdb_txn_from_json(db, json, &txn);
+ json_destroy(json);
+ if (error) {
+ break;
+ }
+
+ ovsdb_txn_commit(txn);
+ }
+ if (error) {
+ char *msg = ovsdb_error_to_string(error);
+ VLOG_WARN("%s", msg);
+ free(msg);
+
+ ovsdb_error_destroy(error);
+ }
+
+ if (read_only) {
+ ovsdb_file_close(file);
+ }
+
+ *dbp = db;
+ return NULL;
+}
+
+void
+ovsdb_destroy(struct ovsdb *db)
+{
+ if (db) {
+ struct shash_node *node;
+
+ /* 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);
+
+ /* Clear the schema's hash of table schemas. The schemas, but not the
+ * table that points to them, were deleted in the previous step. */
+ shash_destroy(&db->schema->tables);
+
+ ovsdb_schema_destroy(db->schema);
+ ovsdb_file_close(db->file);
+ free(db);
+ }
+}
+
+struct ovsdb_table *
+ovsdb_get_table(const struct ovsdb *db, const char *name)
+{
+ return shash_find_data(&db->tables, name);
+}
diff --git a/ovsdb/ovsdb.h b/ovsdb/ovsdb.h
new file mode 100644
index 00000000..3f62966a
--- /dev/null
+++ b/ovsdb/ovsdb.h
@@ -0,0 +1,73 @@
+/* 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 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 *);
+
+/* Database. */
+struct ovsdb {
+ struct ovsdb_schema *schema;
+ struct ovsdb_file *file; /* Disk file (null for in-memory db). */
+ 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_file *, struct ovsdb_schema *);
+struct ovsdb_error *ovsdb_open(const char *file_name, bool read_only,
+ struct ovsdb **)
+ WARN_UNUSED_RESULT;
+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);
+
+#endif /* ovsdb/ovsdb.h */
diff --git a/ovsdb/query.c b/ovsdb/query.c
new file mode 100644
index 00000000..878ac5b2
--- /dev/null
+++ b/ovsdb/query.c
@@ -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
index 00000000..f5cfe2e6
--- /dev/null
+++ b/ovsdb/query.h
@@ -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/row.c b/ovsdb/row.c
new file mode 100644
index 00000000..1b819420
--- /dev/null
+++ b/ovsdb/row.c
@@ -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;
+}
+
+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);
+ }
+}
+
+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
index 00000000..55c4f142
--- /dev/null
+++ b/ovsdb/row.h
@@ -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));
+}
+
+/* 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 *);
+
+/* 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/table.c b/ovsdb/table.c
new file mode 100644
index 00000000..d017a6ba
--- /dev/null
+++ b/ovsdb/table.c
@@ -0,0 +1,228 @@
+/* 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 {
+ 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);
+}
+
+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
index 00000000..9e36c911
--- /dev/null
+++ b/ovsdb/table.h
@@ -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);
+
+/* 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
index 00000000..21a46ec7
--- /dev/null
+++ b/ovsdb/transaction.c
@@ -0,0 +1,444 @@
+/* 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 "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. */
+};
+
+/* 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. */
+};
+
+static const struct uuid *
+ovsdb_txn_row_get_uuid(const struct ovsdb_txn_row *txn_row)
+{
+ const struct ovsdb_row *row = txn_row->old ? txn_row->old : txn_row->new;
+ return ovsdb_row_get_uuid(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);
+ 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->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);
+ 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);
+}
+
+void
+ovsdb_txn_commit(struct ovsdb_txn *txn)
+{
+ txn->db->run_triggers = true;
+ ovsdb_txn_destroy(txn, ovsdb_txn_row_commit);
+}
+
+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));
+}
+
+static struct json *
+ovsdb_txn_row_to_json(const struct ovsdb_txn_row *txn_row)
+{
+ const struct ovsdb_row *old = txn_row->old;
+ const struct ovsdb_row *new = txn_row->new;
+ struct shash_node *node;
+ struct json *json;
+
+ if (!new) {
+ return json_null_create();
+ }
+
+ json = NULL;
+ SHASH_FOR_EACH (node, &new->table->schema->columns) {
+ struct ovsdb_column *column = node->data;
+ unsigned int index = column->index;
+
+ if (index != OVSDB_COL_UUID && column->persistent
+ && (!old || !ovsdb_datum_equals(&old->fields[index],
+ &new->fields[index],
+ &column->type)))
+ {
+ if (!json) {
+ json = json_object_create();
+ }
+ put_json_column(json, new, column);
+ }
+ }
+ return json;
+}
+
+static struct json *
+ovsdb_txn_table_to_json(const struct ovsdb_txn_table *txn_table)
+{
+ struct ovsdb_txn_row *txn_row;
+ struct json *txn_table_json;
+
+ txn_table_json = NULL;
+ HMAP_FOR_EACH (txn_row, struct ovsdb_txn_row, hmap_node,
+ &txn_table->txn_rows) {
+ struct json *txn_row_json = ovsdb_txn_row_to_json(txn_row);
+ if (txn_row_json) {
+ char uuid[UUID_LEN + 1];
+
+ if (!txn_table_json) {
+ txn_table_json = json_object_create();
+ }
+
+ snprintf(uuid, sizeof uuid,
+ UUID_FMT, UUID_ARGS(ovsdb_txn_row_get_uuid(txn_row)));
+ json_object_put(txn_table_json, uuid, txn_row_json);
+ }
+ }
+ return txn_table_json;
+}
+
+struct json *
+ovsdb_txn_to_json(const struct ovsdb_txn *txn)
+{
+ struct ovsdb_txn_table *txn_table;
+ struct json *txn_json;
+
+ txn_json = NULL;
+ HMAP_FOR_EACH (txn_table, struct ovsdb_txn_table, hmap_node,
+ &txn->txn_tables) {
+ struct json *txn_table_json = ovsdb_txn_table_to_json(txn_table);
+ if (!txn_json) {
+ txn_json = json_object_create();
+ }
+ json_object_put(txn_json, txn_table->table->schema->name,
+ txn_table_json);
+ }
+ return txn_json;
+}
+
+static struct ovsdb_error *
+ovsdb_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_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_txn_row_from_json(txn, table, &row_uuid, txn_row_json);
+ if (error) {
+ return error;
+ }
+ }
+
+ return NULL;
+}
+
+struct ovsdb_error *
+ovsdb_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) {
+ error = ovsdb_syntax_error(json, "unknown table",
+ "No table named %s.", table_name);
+ goto error;
+ }
+
+ error = ovsdb_txn_table_from_json(txn, table, txn_table_json);
+ if (error) {
+ goto error;
+ }
+ }
+ *txnp = txn;
+ return NULL;
+
+error:
+ ovsdb_txn_abort(txn);
+ return error;
+}
+
+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);
+ }
+ ovsdb_row_destroy(row);
+ }
+}
diff --git a/ovsdb/transaction.h b/ovsdb/transaction.h
new file mode 100644
index 00000000..293eaf4f
--- /dev/null
+++ b/ovsdb/transaction.h
@@ -0,0 +1,41 @@
+/* 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 ovsdb;
+struct ovsdb_table;
+struct uuid;
+
+struct ovsdb_txn *ovsdb_txn_create(struct ovsdb *);
+void ovsdb_txn_abort(struct ovsdb_txn *);
+void ovsdb_txn_commit(struct ovsdb_txn *);
+
+struct json *ovsdb_txn_to_json(const struct ovsdb_txn *);
+struct ovsdb_error *ovsdb_txn_from_json(struct ovsdb *, const struct json *,
+ struct ovsdb_txn **)
+ WARN_UNUSED_RESULT;
+
+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 *);
+
+#endif /* ovsdb/transaction.h */
diff --git a/ovsdb/trigger.c b/ovsdb/trigger.c
new file mode 100644
index 00000000..1ecfdcac
--- /dev/null
+++ b/ovsdb/trigger.c
@@ -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
index 00000000..521b150b
--- /dev/null
+++ b/ovsdb/trigger.h
@@ -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 */