#include "ovsdb-idl.h"
#include "ovsdb-idl-provider.h"
#include "shash.h"
+#include "sset.h"
#include "string.h"
#include "table.h"
#include "util.h"
VLOG_DEFINE_THIS_MODULE(db_ctl_base);
-/* The IDL we're using and the current transaction, if any.
- * This is for use by ctl_exit() only, to allow it to clean up.
- * Other code should use its context arguments. */
-struct ovsdb_idl *the_idl;
-struct ovsdb_idl_txn *the_idl_txn;
+/* This array defines the 'show' command output format. User can check the
+ * definition in utilities/ovs-vsctl.c as reference.
+ *
+ * Particularly, if an element in 'columns[]' represents a reference to
+ * another table, the referred table must also be defined as an entry in
+ * in 'cmd_show_tables[]'.
+ *
+ * The definition must end with an all-NULL entry. It is initalized once
+ * when ctl_init() is called.
+ *
+ * */
+static const struct cmd_show_table *cmd_show_tables;
+
+/* ctl_exit() is called by ctl_fatal(). User can optionally supply an exit
+ * function ctl_exit_func() via ctl_init. If supplied, this function will
+ * be called by ctl_exit()
+ */
+static void (*ctl_exit_func)(int status) = NULL;
+OVS_NO_RETURN static void ctl_exit(int status);
+
+/* Represents all tables in the schema. User must define 'tables'
+ * in implementation and supply via clt_init(). The definition must end
+ * with an all-NULL entry. */
+static const struct ctl_table_class *tables;
static struct shash all_commands = SHASH_INITIALIZER(&all_commands);
+static const struct ctl_table_class *get_table(const char *table_name);
+static void set_column(const struct ctl_table_class *,
+ const struct ovsdb_idl_row *, const char *,
+ struct ovsdb_symbol_table *);
\f
+static struct option *
+find_option(const char *name, struct option *options, size_t n_options)
+{
+ size_t i;
+
+ for (i = 0; i < n_options; i++) {
+ if (!strcmp(options[i].name, name)) {
+ return &options[i];
+ }
+ }
+ return NULL;
+}
+
+static struct option *
+add_option(struct option **optionsp, size_t *n_optionsp,
+ size_t *allocated_optionsp)
+{
+ if (*n_optionsp >= *allocated_optionsp) {
+ *optionsp = x2nrealloc(*optionsp, allocated_optionsp,
+ sizeof **optionsp);
+ }
+ return &(*optionsp)[(*n_optionsp)++];
+}
+
+/* Converts the command arguments into format that can be parsed by
+ * bash completion script.
+ *
+ * Therein, arguments will be attached with following prefixes:
+ *
+ * !argument :: The argument is required
+ * ?argument :: The argument is optional
+ * *argument :: The argument may appear any number (0 or more) times
+ * +argument :: The argument may appear one or more times
+ *
+ */
+static void
+print_command_arguments(const struct ctl_command_syntax *command)
+{
+ /*
+ * The argument string is parsed in reverse. We use a stack 'oew_stack' to
+ * keep track of nested optionals. Whenever a ']' is encountered, we push
+ * a bit to 'oew_stack'. The bit is set to 1 if the ']' is not nested.
+ * Subsequently, we pop an entry everytime '[' is met.
+ *
+ * We use 'whole_word_is_optional' value to decide whether or not a ! or +
+ * should be added on encountering a space: if the optional surrounds the
+ * whole word then it shouldn't be, but if it is only a part of the word
+ * (i.e. [key=]value), it should be.
+ */
+ uint32_t oew_stack = 0;
+
+ const char *arguments = command->arguments;
+ int length = strlen(arguments);
+ if (!length) {
+ return;
+ }
+
+ /* Output buffer, written backward from end. */
+ char *output = xmalloc(2 * length);
+ char *outp = output + 2 * length;
+ *--outp = '\0';
+
+ bool in_repeated = false;
+ bool whole_word_is_optional = false;
+
+ for (const char *inp = arguments + length; inp > arguments; ) {
+ switch (*--inp) {
+ case ']':
+ oew_stack <<= 1;
+ if (inp[1] == '\0' || inp[1] == ' ' || inp[1] == '.') {
+ oew_stack |= 1;
+ }
+ break;
+ case '[':
+ /* Checks if the whole word is optional, and sets the
+ * 'whole_word_is_optional' accordingly. */
+ if ((inp == arguments || inp[-1] == ' ') && oew_stack & 1) {
+ *--outp = in_repeated ? '*' : '?';
+ whole_word_is_optional = true;
+ } else {
+ *--outp = '?';
+ whole_word_is_optional = false;
+ }
+ oew_stack >>= 1;
+ break;
+ case ' ':
+ if (!whole_word_is_optional) {
+ *--outp = in_repeated ? '+' : '!';
+ }
+ *--outp = ' ';
+ in_repeated = false;
+ whole_word_is_optional = false;
+ break;
+ case '.':
+ in_repeated = true;
+ break;
+ default:
+ *--outp = *inp;
+ break;
+ }
+ }
+ if (arguments[0] != '[' && outp != output + 2 * length - 1) {
+ *--outp = in_repeated ? '+' : '!';
+ }
+ printf("%s", outp);
+ free(output);
+}
+
static void
die_if_error(char *error)
{
free(columns);
}
+/* Finds and returns the "struct ctl_table_class *" with 'table_name' by
+ * searching the 'tables'. */
+static const struct ctl_table_class *
+get_table(const char *table_name)
+{
+ const struct ctl_table_class *table;
+ const struct ctl_table_class *best_match = NULL;
+ unsigned int best_score = 0;
+
+ for (table = tables; table->class; table++) {
+ unsigned int score = score_partial_match(table->class->name,
+ table_name);
+ if (score > best_score) {
+ best_match = table;
+ best_score = score;
+ } else if (score == best_score) {
+ best_match = NULL;
+ }
+ }
+ if (best_match) {
+ return best_match;
+ } else if (best_score) {
+ ctl_fatal("multiple table names match \"%s\"", table_name);
+ } else {
+ ctl_fatal("unknown table \"%s\"", table_name);
+ }
+ return NULL;
+}
+
static void
pre_cmd_find(struct ctl_context *ctx)
{
free(columns);
}
+/* Sets the column of 'row' in 'table'. */
+static void
+set_column(const struct ctl_table_class *table,
+ const struct ovsdb_idl_row *row, const char *arg,
+ struct ovsdb_symbol_table *symtab)
+{
+ const struct ovsdb_idl_column *column;
+ char *key_string, *value_string;
+ char *error;
+
+ error = parse_column_key_value(arg, table, &column, &key_string,
+ NULL, NULL, 0, &value_string);
+ die_if_error(error);
+ if (!value_string) {
+ ctl_fatal("%s: missing value", arg);
+ }
+ check_mutable(row, column);
+
+ if (key_string) {
+ union ovsdb_atom key, value;
+ struct ovsdb_datum datum;
+
+ if (column->type.value.type == OVSDB_TYPE_VOID) {
+ ctl_fatal("cannot specify key to set for non-map column %s",
+ column->name);
+ }
+
+ die_if_error(ovsdb_atom_from_string(&key, &column->type.key,
+ key_string, symtab));
+ die_if_error(ovsdb_atom_from_string(&value, &column->type.value,
+ value_string, symtab));
+
+ ovsdb_datum_init_empty(&datum);
+ ovsdb_datum_add_unsafe(&datum, &key, &value, &column->type);
+
+ ovsdb_atom_destroy(&key, column->type.key.type);
+ ovsdb_atom_destroy(&value, column->type.value.type);
+
+ ovsdb_datum_union(&datum, ovsdb_idl_read(row, column),
+ &column->type, false);
+ ovsdb_idl_txn_verify(row, column);
+ ovsdb_idl_txn_write(row, column, &datum);
+ } else {
+ struct ovsdb_datum datum;
+
+ die_if_error(ovsdb_datum_from_string(&datum, &column->type,
+ value_string, symtab));
+ ovsdb_idl_txn_write(row, column, &datum);
+ }
+
+ free(key_string);
+ free(value_string);
+}
+
static void
pre_cmd_set(struct ctl_context *ctx)
{
bool must_exist = !shash_find(&ctx->options, "--if-exists");
const char *table_name = ctx->argv[1];
const char *record_id = ctx->argv[2];
- const struct ctl_table_class*table;
+ const struct ctl_table_class *table;
const struct ovsdb_idl_row *row;
int i;
command->argv = &argv[i];
}
+static void
+pre_cmd_show(struct ctl_context *ctx)
+{
+ const struct cmd_show_table *show;
+
+ for (show = cmd_show_tables; show->table; show++) {
+ size_t i;
+
+ ovsdb_idl_add_table(ctx->idl, show->table);
+ if (show->name_column) {
+ ovsdb_idl_add_column(ctx->idl, show->name_column);
+ }
+ for (i = 0; i < ARRAY_SIZE(show->columns); i++) {
+ const struct ovsdb_idl_column *column = show->columns[i];
+ if (column) {
+ ovsdb_idl_add_column(ctx->idl, column);
+ }
+ }
+ if (show->wref_table.table) {
+ ovsdb_idl_add_table(ctx->idl, show->wref_table.table);
+ }
+ if (show->wref_table.name_column) {
+ ovsdb_idl_add_column(ctx->idl, show->wref_table.name_column);
+ }
+ if (show->wref_table.wref_column) {
+ ovsdb_idl_add_column(ctx->idl, show->wref_table.wref_column);
+ }
+ }
+}
+
+static const struct cmd_show_table *
+cmd_show_find_table_by_row(const struct ovsdb_idl_row *row)
+{
+ const struct cmd_show_table *show;
+
+ for (show = cmd_show_tables; show->table; show++) {
+ if (show->table == row->table->class) {
+ return show;
+ }
+ }
+ return NULL;
+}
+
+static const struct cmd_show_table *
+cmd_show_find_table_by_name(const char *name)
+{
+ const struct cmd_show_table *show;
+
+ for (show = cmd_show_tables; show->table; show++) {
+ if (!strcmp(show->table->name, name)) {
+ return show;
+ }
+ }
+ return NULL;
+}
+
+/* Prints table entries that weak reference the 'cur_row'. */
+static void
+cmd_show_weak_ref(struct ctl_context *ctx, const struct cmd_show_table *show,
+ const struct ovsdb_idl_row *cur_row, int level)
+{
+ const struct ovsdb_idl_row *row_wref;
+ const struct ovsdb_idl_table_class *table = show->wref_table.table;
+ const struct ovsdb_idl_column *name_column
+ = show->wref_table.name_column;
+ const struct ovsdb_idl_column *wref_column
+ = show->wref_table.wref_column;
+
+ if (!table || !name_column || !wref_column) {
+ return;
+ }
+
+ for (row_wref = ovsdb_idl_first_row(ctx->idl, table); row_wref;
+ row_wref = ovsdb_idl_next_row(row_wref)) {
+ const struct ovsdb_datum *wref_datum
+ = ovsdb_idl_read(row_wref, wref_column);
+ /* If weak reference refers to the 'cur_row', prints it. */
+ if (wref_datum->n
+ && uuid_equals(&cur_row->uuid, &wref_datum->keys[0].uuid)) {
+ const struct ovsdb_datum *name_datum
+ = ovsdb_idl_read(row_wref, name_column);
+ ds_put_char_multiple(&ctx->output, ' ', (level + 1) * 4);
+ ds_put_format(&ctx->output, "%s ", table->name);
+ ovsdb_datum_to_string(name_datum, &name_column->type, &ctx->output);
+ ds_put_char(&ctx->output, '\n');
+ }
+ }
+}
+
+/* 'shown' records the tables that has been displayed by the current
+ * command to avoid duplicated prints.
+ */
+static void
+cmd_show_row(struct ctl_context *ctx, const struct ovsdb_idl_row *row,
+ int level, struct sset *shown)
+{
+ const struct cmd_show_table *show = cmd_show_find_table_by_row(row);
+ size_t i;
+
+ ds_put_char_multiple(&ctx->output, ' ', level * 4);
+ if (show && show->name_column) {
+ const struct ovsdb_datum *datum;
+
+ ds_put_format(&ctx->output, "%s ", show->table->name);
+ datum = ovsdb_idl_read(row, show->name_column);
+ ovsdb_datum_to_string(datum, &show->name_column->type, &ctx->output);
+ } else {
+ ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid));
+ }
+ ds_put_char(&ctx->output, '\n');
+
+ if (!show || sset_find(shown, show->table->name)) {
+ return;
+ }
+
+ sset_add(shown, show->table->name);
+ for (i = 0; i < ARRAY_SIZE(show->columns); i++) {
+ const struct ovsdb_idl_column *column = show->columns[i];
+ const struct ovsdb_datum *datum;
+
+ if (!column) {
+ break;
+ }
+
+ datum = ovsdb_idl_read(row, column);
+ if (column->type.key.type == OVSDB_TYPE_UUID &&
+ column->type.key.u.uuid.refTableName) {
+ const struct cmd_show_table *ref_show;
+ size_t j;
+
+ ref_show = cmd_show_find_table_by_name(
+ column->type.key.u.uuid.refTableName);
+ if (ref_show) {
+ for (j = 0; j < datum->n; j++) {
+ const struct ovsdb_idl_row *ref_row;
+
+ ref_row = ovsdb_idl_get_row_for_uuid(ctx->idl,
+ ref_show->table,
+ &datum->keys[j].uuid);
+ if (ref_row) {
+ cmd_show_row(ctx, ref_row, level + 1, shown);
+ }
+ }
+ continue;
+ }
+ } else if (ovsdb_type_is_map(&column->type) &&
+ column->type.value.type == OVSDB_TYPE_UUID &&
+ column->type.value.u.uuid.refTableName) {
+ const struct cmd_show_table *ref_show;
+ size_t j;
+
+ /* Prints the key to ref'ed table name map if the ref'ed table
+ * is also defined in 'cmd_show_tables'. */
+ ref_show = cmd_show_find_table_by_name(
+ column->type.value.u.uuid.refTableName);
+ if (ref_show && ref_show->name_column) {
+ ds_put_char_multiple(&ctx->output, ' ', (level + 1) * 4);
+ ds_put_format(&ctx->output, "%s:\n", column->name);
+ for (j = 0; j < datum->n; j++) {
+ const struct ovsdb_idl_row *ref_row;
+
+ ref_row = ovsdb_idl_get_row_for_uuid(ctx->idl,
+ ref_show->table,
+ &datum->values[j].uuid);
+
+ ds_put_char_multiple(&ctx->output, ' ', (level + 2) * 4);
+ ovsdb_atom_to_string(&datum->keys[j], column->type.key.type,
+ &ctx->output);
+ ds_put_char(&ctx->output, '=');
+ if (ref_row) {
+ const struct ovsdb_datum *ref_datum;
+
+ ref_datum = ovsdb_idl_read(ref_row,
+ ref_show->name_column);
+ ovsdb_datum_to_string(ref_datum,
+ &ref_show->name_column->type,
+ &ctx->output);
+ } else {
+ ds_put_cstr(&ctx->output, "\"<null>\"");
+ }
+ ds_put_char(&ctx->output, '\n');
+ }
+ continue;
+ }
+ }
+
+ if (!ovsdb_datum_is_default(datum, &column->type)) {
+ ds_put_char_multiple(&ctx->output, ' ', (level + 1) * 4);
+ ds_put_format(&ctx->output, "%s: ", column->name);
+ ovsdb_datum_to_string(datum, &column->type, &ctx->output);
+ ds_put_char(&ctx->output, '\n');
+ }
+ }
+ cmd_show_weak_ref(ctx, show, row, level);
+ sset_find_and_delete_assert(shown, show->table->name);
+}
+
+static void
+cmd_show(struct ctl_context *ctx)
+{
+ const struct ovsdb_idl_row *row;
+ struct sset shown = SSET_INITIALIZER(&shown);
+
+ for (row = ovsdb_idl_first_row(ctx->idl, cmd_show_tables[0].table);
+ row; row = ovsdb_idl_next_row(row)) {
+ cmd_show_row(ctx, row, 0, &shown);
+ }
+
+ ovs_assert(sset_is_empty(&shown));
+ sset_destroy(&shown);
+}
+
\f
+/* Given pointer to dynamic array 'options_p', array's current size
+ * 'allocated_options_p' and number of added options 'n_options_p',
+ * adds all command options to the array. Enlarges the array if
+ * necessary. */
+void
+ctl_add_cmd_options(struct option **options_p, size_t *n_options_p,
+ size_t *allocated_options_p, int opt_val)
+{
+ struct option *o;
+ const struct shash_node *node;
+ size_t n_existing_options = *n_options_p;
+
+ SHASH_FOR_EACH (node, &all_commands) {
+ const struct ctl_command_syntax *p = node->data;
+
+ if (p->options[0]) {
+ char *save_ptr = NULL;
+ char *name;
+ char *s;
+
+ s = xstrdup(p->options);
+ for (name = strtok_r(s, ",", &save_ptr); name != NULL;
+ name = strtok_r(NULL, ",", &save_ptr)) {
+ char *equals;
+ int has_arg;
+
+ ovs_assert(name[0] == '-' && name[1] == '-' && name[2]);
+ name += 2;
+
+ equals = strchr(name, '=');
+ if (equals) {
+ has_arg = required_argument;
+ *equals = '\0';
+ } else {
+ has_arg = no_argument;
+ }
+
+ o = find_option(name, *options_p, *n_options_p);
+ if (o) {
+ ovs_assert(o - *options_p >= n_existing_options);
+ ovs_assert(o->has_arg == has_arg);
+ } else {
+ o = add_option(options_p, n_options_p, allocated_options_p);
+ o->name = xstrdup(name);
+ o->has_arg = has_arg;
+ o->flag = NULL;
+ o->val = opt_val;
+ }
+ }
+
+ free(s);
+ }
+ }
+ o = add_option(options_p, n_options_p, allocated_options_p);
+ memset(o, 0, sizeof *o);
+}
+
/* Parses command-line input for commands. */
struct ctl_command *
ctl_parse_commands(int argc, char *argv[], struct shash *local_options,
return commands;
}
+/* Prints all registered commands. */
+void
+ctl_print_commands(void)
+{
+ const struct shash_node *node;
+
+ SHASH_FOR_EACH (node, &all_commands) {
+ const struct ctl_command_syntax *p = node->data;
+ char *options = xstrdup(p->options);
+ char *options_begin = options;
+ char *item;
+
+ for (item = strsep(&options, ","); item != NULL;
+ item = strsep(&options, ",")) {
+ if (item[0] != '\0') {
+ printf("[%s] ", item);
+ }
+ }
+ printf(",%s,", p->name);
+ print_command_arguments(p);
+ printf("\n");
+
+ free(options_begin);
+ }
+
+ exit(EXIT_SUCCESS);
+}
+
+/* Given array of options 'options', prints them. */
+void
+ctl_print_options(const struct option *options)
+{
+ for (; options->name; options++) {
+ const struct option *o = options;
+
+ printf("--%s%s\n", o->name, o->has_arg ? "=ARG" : "");
+ if (o->flag == NULL && o->val > 0 && o->val <= UCHAR_MAX) {
+ printf("-%c%s\n", o->val, o->has_arg ? " ARG" : "");
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
+
+/* Returns the default local database path. */
+char *
+ctl_default_db(void)
+{
+ static char *def;
+ if (!def) {
+ def = xasprintf("unix:%s/db.sock", ovs_rundir());
+ }
+ return def;
+}
+
/* Returns true if it looks like this set of arguments might modify the
* database, otherwise false. (Not very smart, so it's prone to false
* positives.) */
ctl_might_write_to_db(char **argv)
{
for (; *argv; argv++) {
- const struct ctl_command_syntax *p = shash_find_data(&all_commands, *argv);
+ const struct ctl_command_syntax *p = shash_find_data(&all_commands,
+ *argv);
if (p && p->mode == RW) {
return true;
}
* Freeing the transaction and the IDL is not strictly necessary, but it makes
* for a clean memory leak report from valgrind in the normal case. That makes
* it easier to notice real memory leaks. */
-void
+static void
ctl_exit(int status)
{
- if (the_idl_txn) {
- ovsdb_idl_txn_abort(the_idl_txn);
- ovsdb_idl_txn_destroy(the_idl_txn);
+ if (ctl_exit_func) {
+ ctl_exit_func(status);
}
- ovsdb_idl_destroy(the_idl);
exit(status);
}
NULL, "--if-exists,--all", RW},
{"wait-until", 2, INT_MAX, "TABLE RECORD [COLUMN[:KEY]=VALUE]...",
pre_cmd_wait_until, cmd_wait_until, NULL, "", RO},
+ {"show", 0, 0, "", pre_cmd_show, cmd_show, NULL, "", RO},
{NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, RO},
};
/* Registers the 'db_ctl_commands' to 'all_commands'. */
void
-ctl_init(void)
+ctl_init(const struct ctl_table_class tables_[],
+ const struct cmd_show_table cmd_show_tables_[],
+ void (*ctl_exit_func_)(int status))
{
+ tables = tables_;
+ cmd_show_tables = cmd_show_tables_;
+ ctl_exit_func = ctl_exit_func_;
ctl_register_commands(db_ctl_commands);
}
-/* Returns 'all_commands'. */
-const struct shash *
-ctl_get_all_commands(void)
-{
- return &all_commands;
-}
-
/* Returns the text for the database commands usage. */
const char *
ctl_get_db_cmd_usage(void)
invalidate_cache(ctx);
}
-/* Finds and returns the "struct ctl_table_class *" with 'table_name' by
- * searching the 'tables'. */
-const struct ctl_table_class *
-get_table(const char *table_name)
-{
- const struct ctl_table_class *table;
- const struct ctl_table_class *best_match = NULL;
- unsigned int best_score = 0;
-
- for (table = tables; table->class; table++) {
- unsigned int score = score_partial_match(table->class->name,
- table_name);
- if (score > best_score) {
- best_match = table;
- best_score = score;
- } else if (score == best_score) {
- best_match = NULL;
- }
- }
- if (best_match) {
- return best_match;
- } else if (best_score) {
- ctl_fatal("multiple table names match \"%s\"", table_name);
- } else {
- ctl_fatal("unknown table \"%s\"", table_name);
- }
- return NULL;
-}
-
-/* Sets the column of 'row' in 'table'. */
-void
-set_column(const struct ctl_table_class *table,
- const struct ovsdb_idl_row *row, const char *arg,
- struct ovsdb_symbol_table *symtab)
+void ctl_set_column(const char *table_name,
+ const struct ovsdb_idl_row *row, const char *arg,
+ struct ovsdb_symbol_table *symtab)
{
- const struct ovsdb_idl_column *column;
- char *key_string, *value_string;
- char *error;
-
- error = parse_column_key_value(arg, table, &column, &key_string,
- NULL, NULL, 0, &value_string);
- die_if_error(error);
- if (!value_string) {
- ctl_fatal("%s: missing value", arg);
- }
- check_mutable(row, column);
-
- if (key_string) {
- union ovsdb_atom key, value;
- struct ovsdb_datum datum;
-
- if (column->type.value.type == OVSDB_TYPE_VOID) {
- ctl_fatal("cannot specify key to set for non-map column %s",
- column->name);
- }
-
- die_if_error(ovsdb_atom_from_string(&key, &column->type.key,
- key_string, symtab));
- die_if_error(ovsdb_atom_from_string(&value, &column->type.value,
- value_string, symtab));
-
- ovsdb_datum_init_empty(&datum);
- ovsdb_datum_add_unsafe(&datum, &key, &value, &column->type);
-
- ovsdb_atom_destroy(&key, column->type.key.type);
- ovsdb_atom_destroy(&value, column->type.value.type);
-
- ovsdb_datum_union(&datum, ovsdb_idl_read(row, column),
- &column->type, false);
- ovsdb_idl_txn_verify(row, column);
- ovsdb_idl_txn_write(row, column, &datum);
- } else {
- struct ovsdb_datum datum;
-
- die_if_error(ovsdb_datum_from_string(&datum, &column->type,
- value_string, symtab));
- ovsdb_idl_txn_write(row, column, &datum);
- }
-
- free(key_string);
- free(value_string);
+ set_column(get_table(table_name), row, arg, symtab);
}