]> git.proxmox.com Git - mirror_ovs.git/blobdiff - lib/db-ctl-base.c
cirrus: Use FreeBSD 12.2.
[mirror_ovs.git] / lib / db-ctl-base.c
index 6e622ebc4ea5aa8d240462d368d3f1b3ba441557..e95c77da2cf60a03e6024c89ae92d1b21ee8b525 100644 (file)
@@ -72,10 +72,10 @@ static const struct ovsdb_idl_table_class *idl_classes;
 static size_t n_classes;
 
 static struct shash all_commands = SHASH_INITIALIZER(&all_commands);
-static const struct ovsdb_idl_table_class *get_table(const char *table_name);
-static void set_column(const struct ovsdb_idl_table_class *,
-                       const struct ovsdb_idl_row *, const char *,
-                       struct ovsdb_symbol_table *);
+static char *get_table(const char *, const struct ovsdb_idl_table_class **);
+static char *set_column(const struct ovsdb_idl_table_class *,
+                        const struct ovsdb_idl_row *, const char *,
+                        struct ovsdb_symbol_table *);
 
 \f
 static struct option *
@@ -186,14 +186,6 @@ print_command_arguments(const struct ctl_command_syntax *command)
     free(output);
 }
 
-static void
-die_if_error(char *error)
-{
-    if (error) {
-        ctl_fatal("%s", error);
-    }
-}
-
 static int
 to_lower_and_underscores(unsigned c)
 {
@@ -222,13 +214,18 @@ score_partial_match(const char *name, const char *s)
     return *s == '\0' ? score : 0;
 }
 
-static struct ovsdb_symbol *
-create_symbol(struct ovsdb_symbol_table *symtab, const char *id, bool *newp)
+/* Returns NULL and sets 'symbolp' and 'newp' if symbol was created
+ * successfully. Otherwise returns a malloc()'ed error message on failure. */
+static char * OVS_WARN_UNUSED_RESULT
+create_symbol(struct ovsdb_symbol_table *symtab, const char *id,
+              struct ovsdb_symbol **symbolp, bool *newp)
 {
     struct ovsdb_symbol *symbol;
 
+    ovs_assert(symbolp);
+
     if (id[0] != '@') {
-        ctl_fatal("row id \"%s\" does not begin with \"@\"", id);
+        return xasprintf("row id \"%s\" does not begin with \"@\"", id);
     }
 
     if (newp) {
@@ -237,11 +234,12 @@ create_symbol(struct ovsdb_symbol_table *symtab, const char *id, bool *newp)
 
     symbol = ovsdb_symbol_table_insert(symtab, id);
     if (symbol->created) {
-        ctl_fatal("row id \"%s\" may only be specified on one --id option",
-                  id);
+        return xasprintf("row id \"%s\" may only be specified on one --id "
+                         "option", id);
     }
     symbol->created = true;
-    return symbol;
+    *symbolp = symbol;
+    return NULL;
 }
 
 static bool
@@ -271,8 +269,11 @@ record_id_equals(const union ovsdb_atom *name, enum ovsdb_atomic_type type,
 static const struct ovsdb_idl_row *
 get_row_by_id(struct ctl_context *ctx,
               const struct ovsdb_idl_table_class *table,
-              const struct ctl_row_id *id, const char *record_id)
+              const struct ctl_row_id *id, const char *record_id,
+              bool *multiple_matches)
 {
+    ovs_assert(multiple_matches);
+    *multiple_matches = false;
 
     if (!id->name_column) {
         return NULL;
@@ -330,8 +331,8 @@ get_row_by_id(struct ctl_context *ctx,
         /* If the name equals 'record_id', take it. */
         if (record_id_equals(name, name_type, record_id)) {
             if (referrer) {
-                ctl_fatal("multiple rows in %s match \"%s\"",
-                          id_table->name, record_id);
+                *multiple_matches = true;
+                return NULL;
             }
             referrer = row;
         }
@@ -357,14 +358,16 @@ get_row_by_id(struct ctl_context *ctx,
     return final;
 }
 
-const struct ovsdb_idl_row *
+char * OVS_WARN_UNUSED_RESULT
 ctl_get_row(struct ctl_context *ctx,
             const struct ovsdb_idl_table_class *table, const char *record_id,
-            bool must_exist)
+            bool must_exist, const struct ovsdb_idl_row **rowp)
 {
     const struct ovsdb_idl_row *row = NULL;
     struct uuid uuid;
 
+    ovs_assert(rowp);
+
     if (uuid_from_string(&uuid, record_id)) {
         row = ovsdb_idl_get_row_for_uuid(ctx->idl, table, &uuid);
     }
@@ -380,8 +383,18 @@ ctl_get_row(struct ctl_context *ctx,
         const struct ctl_table_class *ctl_class
             = &ctl_classes[table - idl_classes];
         for (int i = 0; i < ARRAY_SIZE(ctl_class->row_ids); i++) {
-            row = get_row_by_id(ctx, table, &ctl_class->row_ids[i],
-                                record_id);
+            const struct ctl_row_id *id = &ctl_class->row_ids[i];
+            bool multiple_matches;
+
+            row = get_row_by_id(ctx, table, id, record_id, &multiple_matches);
+            if (multiple_matches) {
+                const struct ovsdb_idl_class *class =
+                    ovsdb_idl_get_class(ctx->idl);
+                const struct ovsdb_idl_table_class *table_class =
+                    ovsdb_idl_table_class_from_column(class, id->name_column);
+                return xasprintf("multiple rows in %s match \"%s\"",
+                                 table_class->name, record_id);
+            }
             if (row) {
                 break;
             }
@@ -396,66 +409,85 @@ ctl_get_row(struct ctl_context *ctx,
                 if (!row) {
                     row = r;
                 } else {
-                    ctl_fatal("%s contains 2 or more rows whose UUIDs begin "
-                              "with %s: at least "UUID_FMT" and "UUID_FMT,
-                              table->name, record_id,
-                              UUID_ARGS(&row->uuid),
-                              UUID_ARGS(&r->uuid));
+                    return xasprintf("%s contains 2 or more rows whose UUIDs "
+                                     "begin with %s: at least "UUID_FMT" "
+                                     "and "UUID_FMT, table->name, record_id,
+                                     UUID_ARGS(&row->uuid),
+                                     UUID_ARGS(&r->uuid));
                 }
             }
         }
     }
     if (must_exist && !row) {
-        ctl_fatal("no row \"%s\" in table %s", record_id, table->name);
+        return xasprintf("no row \"%s\" in table %s", record_id, table->name);
     }
-    return row;
+
+    *rowp = row;
+    return NULL;
 }
 
 static char *
 get_column(const struct ovsdb_idl_table_class *table, const char *column_name,
            const struct ovsdb_idl_column **columnp)
 {
+    struct sset best_matches = SSET_INITIALIZER(&best_matches);
     const struct ovsdb_idl_column *best_match = NULL;
     unsigned int best_score = 0;
-    size_t i;
 
-    for (i = 0; i < table->n_columns; i++) {
+    for (size_t i = 0; i < table->n_columns; i++) {
         const struct ovsdb_idl_column *column = &table->columns[i];
         unsigned int score = score_partial_match(column->name, column_name);
-        if (score > best_score) {
+        if (score && score >= best_score) {
+            if (score > best_score) {
+                sset_clear(&best_matches);
+            }
+            sset_add(&best_matches, column->name);
             best_match = column;
             best_score = score;
-        } else if (score == best_score) {
-            best_match = NULL;
         }
     }
 
-    *columnp = best_match;
-    if (best_match) {
-        return NULL;
-    } else if (best_score) {
-        return xasprintf("%s contains more than one column whose name "
-                         "matches \"%s\"", table->name, column_name);
+    char *error = NULL;
+    *columnp = NULL;
+    if (!best_match) {
+        error = xasprintf("%s does not contain a column whose name matches "
+                          "\"%s\"", table->name, column_name);
+    } else if (sset_count(&best_matches) == 1) {
+        *columnp = best_match;
     } else {
-        return xasprintf("%s does not contain a column whose name matches "
-                         "\"%s\"", table->name, column_name);
+        char *matches = sset_join(&best_matches, ", ", "");
+        error = xasprintf("%s contains more than one column "
+                          "whose name matches \"%s\": %s",
+                          table->name, column_name, matches);
+        free(matches);
     }
+    sset_destroy(&best_matches);
+    return error;
 }
 
-static void
+static char * OVS_WARN_UNUSED_RESULT
 pre_get_column(struct ctl_context *ctx,
                const struct ovsdb_idl_table_class *table,
                const char *column_name,
                const struct ovsdb_idl_column **columnp)
 {
-    die_if_error(get_column(table, column_name, columnp));
+    char *error = get_column(table, column_name, columnp);
+    if (error) {
+        return error;
+    }
     ovsdb_idl_add_column(ctx->idl, *columnp);
+    return NULL;
 }
 
-static const struct ovsdb_idl_table_class *
-pre_get_table(struct ctl_context *ctx, const char *table_name)
+static char * OVS_WARN_UNUSED_RESULT
+pre_get_table(struct ctl_context *ctx, const char *table_name,
+              const struct ovsdb_idl_table_class **tablep)
 {
-    const struct ovsdb_idl_table_class *table = get_table(table_name);
+    const struct ovsdb_idl_table_class *table;
+    char *error = get_table(table_name, &table);
+    if (error) {
+        return error;
+    }
     ovsdb_idl_add_table(ctx->idl, table);
 
     const struct ctl_table_class *ctl = &ctl_classes[table - idl_classes];
@@ -469,7 +501,10 @@ pre_get_table(struct ctl_context *ctx, const char *table_name)
         }
     }
 
-    return table;
+    if (tablep) {
+        *tablep = table;
+    }
+    return NULL;
 }
 
 static char *
@@ -612,35 +647,43 @@ parse_column_key_value(const char *arg,
     return error;
 }
 
-static const struct ovsdb_idl_column *
-pre_parse_column_key_value(struct ctl_context *ctx,
-                           const char *arg,
+static char * OVS_WARN_UNUSED_RESULT
+pre_parse_column_key_value(struct ctl_context *ctx, const char *arg,
                            const struct ovsdb_idl_table_class *table)
 {
     const struct ovsdb_idl_column *column;
     const char *p;
-    char *column_name;
+    char *column_name = NULL;
+    char *error;
 
     p = arg;
-    die_if_error(ovsdb_token_parse(&p, &column_name));
+    error = ovsdb_token_parse(&p, &column_name);
+    if (error) {
+        goto out;
+    }
     if (column_name[0] == '\0') {
-        ctl_fatal("%s: missing column name", arg);
+        error = xasprintf("%s: missing column name", arg);
+        goto out;
     }
 
-    pre_get_column(ctx, table, column_name, &column);
+    error = pre_get_column(ctx, table, column_name, &column);
+out:
     free(column_name);
 
-    return column;
+    return error;
 }
 
-static void
+/* Checks if the 'column' is mutable. Returns NULL if it is mutable, or a
+ * malloc()'ed error message otherwise. */
+static char * OVS_WARN_UNUSED_RESULT
 check_mutable(const struct ovsdb_idl_row *row,
               const struct ovsdb_idl_column *column)
 {
     if (!ovsdb_idl_is_mutable(row, column)) {
-        ctl_fatal("cannot modify read-only column %s in table %s",
-                  column->name, row->table->class_->name);
+        return xasprintf("cannot modify read-only column %s in table %s",
+                         column->name, row->table->class_->name);
     }
+    return NULL;
 }
 
 #define RELOPS                                  \
@@ -655,7 +698,9 @@ check_mutable(const struct ovsdb_idl_row *row,
     RELOP(RELOP_SET_LT, "{<}")                  \
     RELOP(RELOP_SET_GT, "{>}")                  \
     RELOP(RELOP_SET_LE, "{<=}")                 \
-    RELOP(RELOP_SET_GE, "{>=}")
+    RELOP(RELOP_SET_GE, "{>=}")                 \
+    RELOP(RELOP_SET_IN, "{in}")                 \
+    RELOP(RELOP_SET_NOT_IN, "{not-in}")
 
 enum relop {
 #define RELOP(ENUM, STRING) ENUM,
@@ -668,7 +713,8 @@ is_set_operator(enum relop op)
 {
     return (op == RELOP_SET_EQ || op == RELOP_SET_NE ||
             op == RELOP_SET_LT || op == RELOP_SET_GT ||
-            op == RELOP_SET_LE || op == RELOP_SET_GE);
+            op == RELOP_SET_LE || op == RELOP_SET_GE ||
+            op == RELOP_SET_IN || op == RELOP_SET_NOT_IN);
 }
 
 static bool
@@ -696,19 +742,26 @@ evaluate_relop(const struct ovsdb_datum *a, const struct ovsdb_datum *b,
     case RELOP_SET_GT:
         return a->n > b->n && ovsdb_datum_includes_all(b, a, type);
     case RELOP_SET_LE:
+    case RELOP_SET_IN:
         return ovsdb_datum_includes_all(a, b, type);
     case RELOP_SET_GE:
         return ovsdb_datum_includes_all(b, a, type);
+    case RELOP_SET_NOT_IN:
+        return ovsdb_datum_excludes_all(a, b, type);
 
     default:
         OVS_NOT_REACHED();
     }
 }
 
-static bool
-is_condition_satisfied(const struct ovsdb_idl_table_class *table,
-                       const struct ovsdb_idl_row *row, const char *arg,
-                       struct ovsdb_symbol_table *symtab)
+/* Checks if given row satisfies the specified condition. Returns the result of
+ * evaluating the condition in 'satisfied' flag and NULL as a return value on
+ * success. On failure returns a malloc()'ed error message and 'satisfied'
+ * value is not modified. */
+static char * OVS_WARN_UNUSED_RESULT
+check_condition(const struct ovsdb_idl_table_class *table,
+                const struct ovsdb_idl_row *row, const char *arg,
+                struct ovsdb_symbol_table *symtab, bool *satisfied)
 {
     static const char *operators[] = {
 #define RELOP(ENUM, STRING) STRING,
@@ -718,18 +771,24 @@ is_condition_satisfied(const struct ovsdb_idl_table_class *table,
 
     const struct ovsdb_idl_column *column;
     const struct ovsdb_datum *have_datum;
-    char *key_string, *value_string;
+    char *key_string = NULL;
+    char *value_string = NULL;
     struct ovsdb_type type;
     int operator;
     bool retval;
     char *error;
 
+    ovs_assert(satisfied);
+
     error = parse_column_key_value(arg, table, &column, &key_string,
                                    &operator, operators, ARRAY_SIZE(operators),
                                    &value_string);
-    die_if_error(error);
+    if (error) {
+        goto out;
+    }
     if (!value_string) {
-        ctl_fatal("%s: missing value", arg);
+        error = xasprintf("%s: missing value", arg);
+        goto out;
     }
 
     type = column->type;
@@ -742,16 +801,23 @@ is_condition_satisfied(const struct ovsdb_idl_table_class *table,
         unsigned int idx;
 
         if (column->type.value.type == OVSDB_TYPE_VOID) {
-            ctl_fatal("cannot specify key to check for non-map column %s",
-                      column->name);
+            error = xasprintf("cannot specify key to check for non-map column "
+                              "%s", column->name);
+            goto out;
         }
 
-        die_if_error(ovsdb_atom_from_string(&want_key, NULL, &column->type.key,
-                                            key_string, symtab));
+        error = ovsdb_atom_from_string(&want_key, NULL, &column->type.key,
+                                       key_string, symtab);
+        if (error) {
+            goto out;
+        }
 
         type.key = type.value;
         type.value.type = OVSDB_TYPE_VOID;
-        die_if_error(ovsdb_datum_from_string(&b, &type, value_string, symtab));
+        error = ovsdb_datum_from_string(&b, &type, value_string, symtab);
+        if (error) {
+            goto out;
+        }
 
         idx = ovsdb_datum_find_key(have_datum,
                                    &want_key, column->type.key.type);
@@ -778,16 +844,21 @@ is_condition_satisfied(const struct ovsdb_idl_table_class *table,
     } else {
         struct ovsdb_datum want_datum;
 
-        die_if_error(ovsdb_datum_from_string(&want_datum, &column->type,
-                                             value_string, symtab));
+        error = ovsdb_datum_from_string(&want_datum, &column->type,
+                                        value_string, symtab);
+        if (error) {
+            goto out;
+        }
         retval = evaluate_relop(have_datum, &want_datum, &type, operator);
         ovsdb_datum_destroy(&want_datum, &column->type);
     }
 
+    *satisfied = retval;
+out:
     free(key_string);
     free(value_string);
 
-    return retval;
+    return error;
 }
 
 static void
@@ -815,14 +886,20 @@ pre_cmd_get(struct ctl_context *ctx)
                   "possibly erroneous");
     }
 
-    table = pre_get_table(ctx, table_name);
+    ctx->error = pre_get_table(ctx, table_name, &table);
+    if (ctx->error) {
+        return;
+    }
     for (i = 3; i < ctx->argc; i++) {
         if (!strcasecmp(ctx->argv[i], "_uuid")
             || !strcasecmp(ctx->argv[i], "-uuid")) {
             continue;
         }
 
-        pre_parse_column_key_value(ctx, ctx->argv[i], table);
+        ctx->error = pre_parse_column_key_value(ctx, ctx->argv[i], table);
+        if (ctx->error) {
+            return;
+        }
     }
 }
 
@@ -839,23 +916,34 @@ cmd_get(struct ctl_context *ctx)
     int i;
 
     if (id && !must_exist) {
-        ctl_fatal("--if-exists and --id may not be specified together");
+        ctl_error(ctx, "--if-exists and --id may not be specified together");
+        return;
     }
 
-    table = get_table(table_name);
-    row = ctl_get_row(ctx, table, record_id, must_exist);
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = ctl_get_row(ctx, table, record_id, must_exist, &row);
+    if (ctx->error) {
+        return;
+    }
     if (!row) {
         return;
     }
 
     if (id) {
-        struct ovsdb_symbol *symbol;
-        bool new;
+        struct ovsdb_symbol *symbol = NULL;
+        bool new = false;
 
-        symbol = create_symbol(ctx->symtab, id, &new);
+        ctx->error = create_symbol(ctx->symtab, id, &symbol, &new);
+        if (ctx->error) {
+            return;
+        }
         if (!new) {
-            ctl_fatal("row id \"%s\" specified on \"get\" command was used "
-                      "before it was defined", id);
+            ctl_error(ctx, "row id \"%s\" specified on \"get\" command was "
+                      "used before it was defined", id);
+            return;
         }
         symbol->uuid = row->uuid;
 
@@ -877,9 +965,11 @@ cmd_get(struct ctl_context *ctx)
             continue;
         }
 
-        die_if_error(parse_column_key_value(ctx->argv[i], table,
-                                            &column, &key_string,
-                                            NULL, NULL, 0, NULL));
+        ctx->error = parse_column_key_value(ctx->argv[i], table, &column,
+                                            &key_string, NULL, NULL, 0, NULL);
+        if (ctx->error) {
+            return;
+        }
 
         ovsdb_idl_txn_verify(row, column);
         datum = ovsdb_idl_read(row, column);
@@ -888,21 +978,30 @@ cmd_get(struct ctl_context *ctx)
             unsigned int idx;
 
             if (column->type.value.type == OVSDB_TYPE_VOID) {
-                ctl_fatal("cannot specify key to get for non-map column %s",
+                ctl_error(ctx,
+                          "cannot specify key to get for non-map column %s",
                           column->name);
+                free(key_string);
+                return;
             }
 
-            die_if_error(ovsdb_atom_from_string(&key, NULL,
-                                                &column->type.key,
-                                                key_string, ctx->symtab));
+            ctx->error = ovsdb_atom_from_string(&key, NULL, &column->type.key,
+                                                key_string, ctx->symtab);
+            if (ctx->error) {
+                free(key_string);
+                return;
+            }
 
             idx = ovsdb_datum_find_key(datum, &key,
                                        column->type.key.type);
             if (idx == UINT_MAX) {
                 if (must_exist) {
-                    ctl_fatal("no key \"%s\" in %s record \"%s\" column %s",
-                              key_string, table->name, record_id,
-                              column->name);
+                    ctl_error(
+                        ctx, "no key \"%s\" in %s record \"%s\" column %s",
+                        key_string, table->name, record_id, column->name);
+                    free(key_string);
+                    ovsdb_atom_destroy(&key, column->type.key.type);
+                    return;
                 }
             } else {
                 ovsdb_atom_to_string(&datum->values[idx],
@@ -918,7 +1017,8 @@ cmd_get(struct ctl_context *ctx)
     }
 }
 
-static void
+/* Returns NULL on success or malloc()'ed error message on failure. */
+static char * OVS_WARN_UNUSED_RESULT
 parse_column_names(const char *column_names,
                    const struct ovsdb_idl_table_class *table,
                    const struct ovsdb_idl_column ***columnsp,
@@ -951,7 +1051,12 @@ parse_column_names(const char *column_names,
             if (!strcasecmp(column_name, "_uuid")) {
                 column = NULL;
             } else {
-                die_if_error(get_column(table, column_name, &column));
+                char *error = get_column(table, column_name, &column);
+                if (error) {
+                    free(columns);
+                    free(s);
+                    return error;
+                }
             }
             if (n_columns >= allocated_columns) {
                 columns = x2nrealloc(columns, &allocated_columns,
@@ -962,14 +1067,15 @@ parse_column_names(const char *column_names,
         free(s);
 
         if (!n_columns) {
-            ctl_fatal("must specify at least one column name");
+            return xstrdup("must specify at least one column name");
         }
     }
     *columnsp = columns;
     *n_columnsp = n_columns;
+    return NULL;
 }
 
-static void
+static char * OVS_WARN_UNUSED_RESULT
 pre_list_columns(struct ctl_context *ctx,
                  const struct ovsdb_idl_table_class *table,
                  const char *column_names)
@@ -977,14 +1083,19 @@ pre_list_columns(struct ctl_context *ctx,
     const struct ovsdb_idl_column **columns;
     size_t n_columns;
     size_t i;
+    char *error;
 
-    parse_column_names(column_names, table, &columns, &n_columns);
+    error = parse_column_names(column_names, table, &columns, &n_columns);
+    if (error) {
+        return error;
+    }
     for (i = 0; i < n_columns; i++) {
         if (columns[i]) {
             ovsdb_idl_add_column(ctx->idl, columns[i]);
         }
     }
     free(columns);
+    return NULL;
 }
 
 static void
@@ -994,8 +1105,14 @@ pre_cmd_list(struct ctl_context *ctx)
     const char *table_name = ctx->argv[1];
     const struct ovsdb_idl_table_class *table;
 
-    table = pre_get_table(ctx, table_name);
-    pre_list_columns(ctx, table, column_names);
+    ctx->error = pre_get_table(ctx, table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = pre_list_columns(ctx, table, column_names);
+    if (ctx->error) {
+        return;
+    }
 }
 
 static struct table *
@@ -1066,13 +1183,26 @@ cmd_list(struct ctl_context *ctx)
     size_t n_columns;
     int i;
 
-    table = get_table(table_name);
-    parse_column_names(column_names, table, &columns, &n_columns);
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = parse_column_names(column_names, table, &columns, &n_columns);
+    if (ctx->error) {
+        return;
+    }
     out = ctx->table = list_make_table(columns, n_columns);
     if (ctx->argc > 2) {
         for (i = 2; i < ctx->argc; i++) {
-            list_record(ctl_get_row(ctx, table, ctx->argv[i], must_exist),
-                        columns, n_columns, out);
+            const struct ovsdb_idl_row *row;
+
+            ctx->error = ctl_get_row(ctx, table, ctx->argv[i], must_exist,
+                                     &row);
+            if (ctx->error) {
+                free(columns);
+                return;
+            }
+            list_record(row, columns, n_columns, out);
         }
     } else {
         const struct ovsdb_idl_row *row;
@@ -1085,32 +1215,42 @@ cmd_list(struct ctl_context *ctx)
     free(columns);
 }
 
-/* Finds and returns the "struct ovsdb_idl_table_class *" with 'table_name' by
- * searching the tables in these schema. */
-static const struct ovsdb_idl_table_class *
-get_table(const char *table_name)
+/* Finds the "struct ovsdb_idl_table_class *" with 'table_name' by searching
+ * the tables in these schema. Returns NULL and sets 'tablep' on success, or a
+ * malloc()'ed error message on failure. */
+static char * OVS_WARN_UNUSED_RESULT
+get_table(const char *table_name, const struct ovsdb_idl_table_class **tablep)
 {
+    struct sset best_matches = SSET_INITIALIZER(&best_matches);
     const struct ovsdb_idl_table_class *best_match = NULL;
     unsigned int best_score = 0;
 
     for (const struct ovsdb_idl_table_class *table = idl_classes;
          table < &idl_classes[n_classes]; table++) {
         unsigned int score = score_partial_match(table->name, table_name);
-        if (score > best_score) {
+        if (score && score >= best_score) {
+            if (score > best_score) {
+                sset_clear(&best_matches);
+            }
+            sset_add(&best_matches, table->name);
             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);
+
+    char *error = NULL;
+    if (!best_match) {
+        error = xasprintf("unknown table \"%s\"", table_name);
+    } else if (sset_count(&best_matches) == 1) {
+        *tablep = best_match;
     } else {
-        ctl_fatal("unknown table \"%s\"", table_name);
+        char *matches = sset_join(&best_matches, ", ", "");
+        error = xasprintf("\"%s\" matches multiple table names: %s",
+                          table_name, matches);
+        free(matches);
     }
-    return NULL;
+    sset_destroy(&best_matches);
+    return error;
 }
 
 static void
@@ -1121,10 +1261,19 @@ pre_cmd_find(struct ctl_context *ctx)
     const struct ovsdb_idl_table_class *table;
     int i;
 
-    table = pre_get_table(ctx, table_name);
-    pre_list_columns(ctx, table, column_names);
+    ctx->error = pre_get_table(ctx, table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = pre_list_columns(ctx, table, column_names);
+    if (ctx->error) {
+        return;
+    }
     for (i = 2; i < ctx->argc; i++) {
-        pre_parse_column_key_value(ctx, ctx->argv[i], table);
+        ctx->error = pre_parse_column_key_value(ctx, ctx->argv[i], table);
+        if (ctx->error) {
+            return;
+        }
     }
 }
 
@@ -1139,16 +1288,29 @@ cmd_find(struct ctl_context *ctx)
     struct table *out;
     size_t n_columns;
 
-    table = get_table(table_name);
-    parse_column_names(column_names, table, &columns, &n_columns);
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = parse_column_names(column_names, table, &columns, &n_columns);
+    if (ctx->error) {
+        return;
+    }
     out = ctx->table = list_make_table(columns, n_columns);
     for (row = ovsdb_idl_first_row(ctx->idl, table); row;
          row = ovsdb_idl_next_row(row)) {
         int i;
 
         for (i = 2; i < ctx->argc; i++) {
-            if (!is_condition_satisfied(table, row, ctx->argv[i],
-                                        ctx->symtab)) {
+            bool satisfied = false;
+
+            ctx->error = check_condition(table, row, ctx->argv[i],
+                                         ctx->symtab, &satisfied);
+            if (ctx->error) {
+                free(columns);
+                return;
+            }
+            if (!satisfied) {
                 goto next_row;
             }
         }
@@ -1159,37 +1321,52 @@ cmd_find(struct ctl_context *ctx)
     free(columns);
 }
 
-/* Sets the column of 'row' in 'table'. */
-static void
+/* Sets the column of 'row' in 'table'. Returns NULL on success or a
+ * malloc()'ed error message on failure. */
+static char * OVS_WARN_UNUSED_RESULT
 set_column(const struct ovsdb_idl_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 *key_string = NULL;
+    char *value_string = NULL;
     char *error;
 
     error = parse_column_key_value(arg, table, &column, &key_string,
                                    NULL, NULL, 0, &value_string);
-    die_if_error(error);
+    if (error) {
+        goto out;
+    }
     if (!value_string) {
-        ctl_fatal("%s: missing value", arg);
+        error = xasprintf("%s: missing value", arg);
+        goto out;
+    }
+    error = check_mutable(row, column);
+    if (error) {
+        goto out;
     }
-    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);
+            error = xasprintf("cannot specify key to set for non-map column "
+                              "%s", column->name);
+            goto out;
         }
 
-        die_if_error(ovsdb_atom_from_string(&key, NULL, &column->type.key,
-                                            key_string, symtab));
-        die_if_error(ovsdb_atom_from_string(&value, NULL, &column->type.value,
-                                            value_string, symtab));
+        error = ovsdb_atom_from_string(&key, NULL, &column->type.key,
+                                       key_string, symtab);
+        if (error) {
+            goto out;
+        }
+        error = ovsdb_atom_from_string(&value, NULL, &column->type.value,
+                                       value_string, symtab);
+        if (error) {
+            goto out;
+        }
 
         ovsdb_datum_init_empty(&datum);
         ovsdb_datum_add_unsafe(&datum, &key, &value, &column->type, NULL);
@@ -1204,13 +1381,19 @@ set_column(const struct ovsdb_idl_table_class *table,
     } else {
         struct ovsdb_datum datum;
 
-        die_if_error(ovsdb_datum_from_string(&datum, &column->type,
-                                             value_string, symtab));
+        error = ovsdb_datum_from_string(&datum, &column->type,
+                                        value_string, symtab);
+        if (error) {
+            goto out;
+        }
         ovsdb_idl_txn_write(row, column, &datum);
     }
 
+out:
     free(key_string);
     free(value_string);
+
+    return error;
 }
 
 static void
@@ -1220,9 +1403,15 @@ pre_cmd_set(struct ctl_context *ctx)
     const struct ovsdb_idl_table_class *table;
     int i;
 
-    table = pre_get_table(ctx, table_name);
+    ctx->error = pre_get_table(ctx, table_name, &table);
+    if (ctx->error) {
+        return;
+    }
     for (i = 3; i < ctx->argc; i++) {
-        pre_parse_column_key_value(ctx, ctx->argv[i], table);
+        ctx->error = pre_parse_column_key_value(ctx, ctx->argv[i], table);
+        if (ctx->error) {
+            return;
+        }
     }
 }
 
@@ -1236,14 +1425,23 @@ cmd_set(struct ctl_context *ctx)
     const struct ovsdb_idl_row *row;
     int i;
 
-    table = get_table(table_name);
-    row = ctl_get_row(ctx, table, record_id, must_exist);
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = ctl_get_row(ctx, table, record_id, must_exist, &row);
+    if (ctx->error) {
+        return;
+    }
     if (!row) {
         return;
     }
 
     for (i = 3; i < ctx->argc; i++) {
-        set_column(table, row, ctx->argv[i], ctx->symtab);
+        ctx->error = set_column(table, row, ctx->argv[i], ctx->symtab);
+        if (ctx->error) {
+            return;
+        }
     }
 
     invalidate_cache(ctx);
@@ -1257,8 +1455,14 @@ pre_cmd_add(struct ctl_context *ctx)
     const struct ovsdb_idl_table_class *table;
     const struct ovsdb_idl_column *column;
 
-    table = pre_get_table(ctx, table_name);
-    pre_get_column(ctx, table, column_name, &column);
+    ctx->error = pre_get_table(ctx, table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = pre_get_column(ctx, table, column_name, &column);
+    if (ctx->error) {
+        return;
+    }
 }
 
 static void
@@ -1275,13 +1479,25 @@ cmd_add(struct ctl_context *ctx)
     struct ovsdb_datum old;
     int i;
 
-    table = get_table(table_name);
-    die_if_error(get_column(table, column_name, &column));
-    row = ctl_get_row(ctx, table, record_id, must_exist);
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = get_column(table, column_name, &column);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = ctl_get_row(ctx, table, record_id, must_exist, &row);
+    if (ctx->error) {
+        return;
+    }
     if (!row) {
         return;
     }
-    check_mutable(row, column);
+    ctx->error = check_mutable(row, column);
+    if (ctx->error) {
+        return;
+    }
 
     type = &column->type;
     ovsdb_datum_clone(&old, ovsdb_idl_read(row, column), &column->type);
@@ -1292,17 +1508,23 @@ cmd_add(struct ctl_context *ctx)
         add_type = *type;
         add_type.n_min = 1;
         add_type.n_max = UINT_MAX;
-        die_if_error(ovsdb_datum_from_string(&add, &add_type, ctx->argv[i],
-                                             ctx->symtab));
+        ctx->error = ovsdb_datum_from_string(&add, &add_type, ctx->argv[i],
+                                             ctx->symtab);
+        if (ctx->error) {
+            ovsdb_datum_destroy(&old, &column->type);
+            return;
+        }
         ovsdb_datum_union(&old, &add, type, false);
         ovsdb_datum_destroy(&add, type);
     }
     if (old.n > type->n_max) {
-        ctl_fatal("\"add\" operation would put %u %s in column %s of "
+        ctl_error(ctx, "\"add\" operation would put %u %s in column %s of "
                   "table %s but the maximum number is %u",
                   old.n,
                   type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
                   column->name, table->name, type->n_max);
+        ovsdb_datum_destroy(&old, &column->type);
+        return;
     }
     ovsdb_idl_txn_verify(row, column);
     ovsdb_idl_txn_write(row, column, &old);
@@ -1318,8 +1540,14 @@ pre_cmd_remove(struct ctl_context *ctx)
     const struct ovsdb_idl_table_class *table;
     const struct ovsdb_idl_column *column;
 
-    table = pre_get_table(ctx, table_name);
-    pre_get_column(ctx, table, column_name, &column);
+    ctx->error = pre_get_table(ctx, table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = pre_get_column(ctx, table, column_name, &column);
+    if (ctx->error) {
+        return;
+    }
 }
 
 static void
@@ -1336,13 +1564,25 @@ cmd_remove(struct ctl_context *ctx)
     struct ovsdb_datum old;
     int i;
 
-    table = get_table(table_name);
-    die_if_error(get_column(table, column_name, &column));
-    row = ctl_get_row(ctx, table, record_id, must_exist);
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = get_column(table, column_name, &column);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = ctl_get_row(ctx, table, record_id, must_exist, &row);
+    if (ctx->error) {
+        return;
+    }
     if (!row) {
         return;
     }
-    check_mutable(row, column);
+    ctx->error = check_mutable(row, column);
+    if (ctx->error) {
+        return;
+    }
 
     type = &column->type;
     ovsdb_datum_clone(&old, ovsdb_idl_read(row, column), &column->type);
@@ -1361,21 +1601,29 @@ cmd_remove(struct ctl_context *ctx)
             if (ovsdb_type_is_map(&rm_type)) {
                 rm_type.value.type = OVSDB_TYPE_VOID;
                 free(error);
-                die_if_error(ovsdb_datum_from_string(
-                                                     &rm, &rm_type, ctx->argv[i], ctx->symtab));
+                ctx->error = ovsdb_datum_from_string(&rm, &rm_type,
+                                                     ctx->argv[i],
+                                                     ctx->symtab);
+                if (ctx->error) {
+                    ovsdb_datum_destroy(&old, &column->type);
+                    return;
+                }
             } else {
-                ctl_fatal("%s", error);
+                ctx->error = error;
+                ovsdb_datum_destroy(&old, &column->type);
+                return;
             }
         }
         ovsdb_datum_subtract(&old, type, &rm, &rm_type);
         ovsdb_datum_destroy(&rm, &rm_type);
     }
     if (old.n < type->n_min) {
-        ctl_fatal("\"remove\" operation would put %u %s in column %s of "
-                  "table %s but the minimum number is %u",
-                  old.n,
+        ctl_error(ctx, "\"remove\" operation would put %u %s in column %s of "
+                  "table %s but the minimum number is %u", old.n,
                   type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs",
                   column->name, table->name, type->n_min);
+        ovsdb_datum_destroy(&old, &column->type);
+        return;
     }
     ovsdb_idl_txn_verify(row, column);
     ovsdb_idl_txn_write(row, column, &old);
@@ -1390,11 +1638,17 @@ pre_cmd_clear(struct ctl_context *ctx)
     const struct ovsdb_idl_table_class *table;
     int i;
 
-    table = pre_get_table(ctx, table_name);
+    ctx->error = pre_get_table(ctx, table_name, &table);
+    if (ctx->error) {
+        return;
+    }
     for (i = 3; i < ctx->argc; i++) {
         const struct ovsdb_idl_column *column;
 
-        pre_get_column(ctx, table, ctx->argv[i], &column);
+        ctx->error = pre_get_column(ctx, table, ctx->argv[i], &column);
+        if (ctx->error) {
+            return;
+        }
     }
 }
 
@@ -1408,8 +1662,14 @@ cmd_clear(struct ctl_context *ctx)
     const struct ovsdb_idl_row *row;
     int i;
 
-    table = get_table(table_name);
-    row = ctl_get_row(ctx, table, record_id, must_exist);
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = ctl_get_row(ctx, table, record_id, must_exist, &row);
+    if (ctx->error) {
+        return;
+    }
     if (!row) {
         return;
     }
@@ -1419,14 +1679,21 @@ cmd_clear(struct ctl_context *ctx)
         const struct ovsdb_type *type;
         struct ovsdb_datum datum;
 
-        die_if_error(get_column(table, ctx->argv[i], &column));
-        check_mutable(row, column);
+        ctx->error = get_column(table, ctx->argv[i], &column);
+        if (ctx->error) {
+            return;
+        }
+        ctx->error = check_mutable(row, column);
+        if (ctx->error) {
+            return;
+        }
 
         type = &column->type;
         if (type->n_min > 0) {
-            ctl_fatal("\"clear\" operation cannot be applied to column %s "
-                      "of table %s, which is not allowed to be empty",
+            ctl_error(ctx, "\"clear\" operation cannot be applied to column "
+                      "%s of table %s, which is not allowed to be empty",
                       column->name, table->name);
+            return;
         }
 
         ovsdb_datum_init_empty(&datum);
@@ -1443,7 +1710,10 @@ pre_create(struct ctl_context *ctx)
     const char *table_name = ctx->argv[1];
     const struct ovsdb_idl_table_class *table;
 
-    table = get_table(table_name);
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
     if (!id && !table->is_root) {
         VLOG_WARN("applying \"create\" command to table %s without --id "
                   "option will have no effect", table->name);
@@ -1455,13 +1725,22 @@ cmd_create(struct ctl_context *ctx)
 {
     const char *id = shash_find_data(&ctx->options, "--id");
     const char *table_name = ctx->argv[1];
-    const struct ovsdb_idl_table_class *table = get_table(table_name);
+    const struct ovsdb_idl_table_class *table;
     const struct ovsdb_idl_row *row;
-    const struct uuid *uuid;
+    const struct uuid *uuid = NULL;
     int i;
 
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
     if (id) {
-        struct ovsdb_symbol *symbol = create_symbol(ctx->symtab, id, NULL);
+        struct ovsdb_symbol *symbol = NULL;
+
+        ctx->error = create_symbol(ctx->symtab, id, &symbol, NULL);
+        if (ctx->error) {
+            return;
+        }
         if (table->is_root) {
             /* This table is in the root set, meaning that rows created in it
              * won't disappear even if they are unreferenced, so disable
@@ -1469,13 +1748,14 @@ cmd_create(struct ctl_context *ctx)
             symbol->strong_ref = true;
         }
         uuid = &symbol->uuid;
-    } else {
-        uuid = NULL;
     }
 
     row = ovsdb_idl_txn_insert(ctx->txn, table, uuid);
     for (i = 2; i < ctx->argc; i++) {
-        set_column(table, row, ctx->argv[i], ctx->symtab);
+        ctx->error = set_column(table, row, ctx->argv[i], ctx->symtab);
+        if (ctx->error) {
+            return;
+        }
     }
     ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid));
 }
@@ -1511,7 +1791,10 @@ pre_cmd_destroy(struct ctl_context *ctx)
 {
     const char *table_name = ctx->argv[1];
 
-    pre_get_table(ctx, table_name);
+    ctx->error = pre_get_table(ctx, table_name, NULL);
+    if (ctx->error) {
+        return;
+    }
 }
 
 static void
@@ -1523,14 +1806,21 @@ cmd_destroy(struct ctl_context *ctx)
     const struct ovsdb_idl_table_class *table;
     int i;
 
-    table = get_table(table_name);
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
 
     if (delete_all && ctx->argc > 2) {
-        ctl_fatal("--all and records argument should not be specified together");
+        ctl_error(ctx, "--all and records argument should not be specified "
+                  "together");
+        return;
     }
 
     if (delete_all && !must_exist) {
-        ctl_fatal("--all and --if-exists should not be specified together");
+        ctl_error(ctx, "--all and --if-exists should not be specified "
+                  "together");
+        return;
     }
 
     if (delete_all) {
@@ -1547,7 +1837,11 @@ cmd_destroy(struct ctl_context *ctx)
         for (i = 2; i < ctx->argc; i++) {
             const struct ovsdb_idl_row *row;
 
-            row = ctl_get_row(ctx, table, ctx->argv[i], must_exist);
+            ctx->error = ctl_get_row(ctx, table, ctx->argv[i], must_exist,
+                                     &row);
+            if (ctx->error) {
+                return;
+            }
             if (row) {
                 ovsdb_idl_txn_delete(row);
             }
@@ -1563,10 +1857,16 @@ pre_cmd_wait_until(struct ctl_context *ctx)
     const struct ovsdb_idl_table_class *table;
     int i;
 
-    table = pre_get_table(ctx, table_name);
+    ctx->error = pre_get_table(ctx, table_name, &table);
+    if (ctx->error) {
+        return;
+    }
 
     for (i = 3; i < ctx->argc; i++) {
-        pre_parse_column_key_value(ctx, ctx->argv[i], table);
+        ctx->error = pre_parse_column_key_value(ctx, ctx->argv[i], table);
+        if (ctx->error) {
+            return;
+        }
     }
 }
 
@@ -1579,16 +1879,28 @@ cmd_wait_until(struct ctl_context *ctx)
     const struct ovsdb_idl_row *row;
     int i;
 
-    table = get_table(table_name);
-
-    row = ctl_get_row(ctx, table, record_id, false);
+    ctx->error = get_table(table_name, &table);
+    if (ctx->error) {
+        return;
+    }
+    ctx->error = ctl_get_row(ctx, table, record_id, false, &row);
+    if (ctx->error) {
+        return;
+    }
     if (!row) {
         ctx->try_again = true;
         return;
     }
 
     for (i = 3; i < ctx->argc; i++) {
-        if (!is_condition_satisfied(table, row, ctx->argv[i], ctx->symtab)) {
+        bool satisfied;
+
+        ctx->error = check_condition(table, row, ctx->argv[i], ctx->symtab,
+                                     &satisfied);
+        if (ctx->error) {
+            return;
+        }
+        if (!satisfied) {
             ctx->try_again = true;
             return;
         }
@@ -1596,7 +1908,7 @@ cmd_wait_until(struct ctl_context *ctx)
 }
 
 /* Parses one command. */
-static void
+static char * OVS_WARN_UNUSED_RESULT
 parse_command(int argc, char *argv[], struct shash *local_options,
               struct ctl_command *command)
 {
@@ -1604,6 +1916,7 @@ parse_command(int argc, char *argv[], struct shash *local_options,
     struct shash_node *node;
     int n_arg;
     int i;
+    char *error;
 
     shash_init(&command->options);
     shash_swap(local_options, &command->options);
@@ -1626,17 +1939,23 @@ parse_command(int argc, char *argv[], struct shash *local_options,
         }
 
         if (shash_find(&command->options, key)) {
-            ctl_fatal("'%s' option specified multiple times", argv[i]);
+            free(key);
+            free(value);
+            error = xasprintf("'%s' option specified multiple times", argv[i]);
+            goto error;
         }
         shash_add_nocopy(&command->options, key, value);
     }
     if (i == argc) {
-        ctl_fatal("missing command name (use --help for help)");
+        error = xstrdup("missing command name (use --help for help)");
+        goto error;
     }
 
     p = shash_find_data(&all_commands, argv[i]);
     if (!p) {
-        ctl_fatal("unknown command '%s'; use --help for help", argv[i]);
+        error = xasprintf("unknown command '%s'; use --help for help",
+                          argv[i]);
+        goto error;
     }
 
     SHASH_FOR_EACH (node, &command->options) {
@@ -1644,43 +1963,54 @@ parse_command(int argc, char *argv[], struct shash *local_options,
         int end = s ? s[strlen(node->name)] : EOF;
 
         if (!strchr("=,? ", end)) {
-            ctl_fatal("'%s' command has no '%s' option",
-                      argv[i], node->name);
+            error = xasprintf("'%s' command has no '%s' option",
+                              argv[i], node->name);
+            goto error;
         }
         if (end != '?' && (end == '=') != (node->data != NULL)) {
             if (end == '=') {
-                ctl_fatal("missing argument to '%s' option on '%s' "
-                          "command", node->name, argv[i]);
+                error = xasprintf("missing argument to '%s' option on '%s' "
+                                  "command", node->name, argv[i]);
+                goto error;
             } else {
-                ctl_fatal("'%s' option on '%s' does not accept an "
-                          "argument", node->name, argv[i]);
+                error = xasprintf("'%s' option on '%s' does not accept an "
+                                  "argument", node->name, argv[i]);
+                goto error;
             }
         }
     }
 
     n_arg = argc - i - 1;
     if (n_arg < p->min_args) {
-        ctl_fatal("'%s' command requires at least %d arguments",
-                  p->name, p->min_args);
+        error = xasprintf("'%s' command requires at least %d arguments",
+                          p->name, p->min_args);
+        goto error;
     } else if (n_arg > p->max_args) {
         int j;
 
         for (j = i + 1; j < argc; j++) {
             if (argv[j][0] == '-') {
-                ctl_fatal("'%s' command takes at most %d arguments "
-                          "(note that options must precede command "
-                          "names and follow a \"--\" argument)",
-                          p->name, p->max_args);
+                error = xasprintf("'%s' command takes at most %d arguments "
+                                  "(note that options must precede command "
+                                  "names and follow a \"--\" argument)",
+                                  p->name, p->max_args);
+                goto error;
             }
         }
 
-        ctl_fatal("'%s' command takes at most %d arguments",
-                  p->name, p->max_args);
+        error = xasprintf("'%s' command takes at most %d arguments",
+                          p->name, p->max_args);
+        goto error;
     }
 
     command->syntax = p;
     command->argc = n_arg + 1;
     command->argv = &argv[i];
+    return NULL;
+
+error:
+    shash_destroy_free_data(&command->options);
+    return error;
 }
 
 static void
@@ -1949,17 +2279,21 @@ ctl_add_cmd_options(struct option **options_p, size_t *n_options_p,
 }
 
 /* Parses command-line input for commands. */
-struct ctl_command *
+char *
 ctl_parse_commands(int argc, char *argv[], struct shash *local_options,
-                   size_t *n_commandsp)
+                   struct ctl_command **commandsp, size_t *n_commandsp)
 {
     struct ctl_command *commands;
     size_t n_commands, allocated_commands;
     int i, start;
+    char *error;
 
     commands = NULL;
     n_commands = allocated_commands = 0;
 
+    *commandsp = NULL;
+    *n_commandsp = 0;
+
     for (start = i = 0; i <= argc; i++) {
         if (i == argc || !strcmp(argv[i], "--")) {
             if (i > start) {
@@ -1972,19 +2306,32 @@ ctl_parse_commands(int argc, char *argv[], struct shash *local_options,
                         shash_moved(&c->options);
                     }
                 }
-                parse_command(i - start, &argv[start], local_options,
-                              &commands[n_commands++]);
+                error = parse_command(i - start, &argv[start], local_options,
+                                      &commands[n_commands]);
+                if (error) {
+                    struct ctl_command *c;
+
+                    for (c = commands; c < &commands[n_commands]; c++) {
+                        shash_destroy_free_data(&c->options);
+                    }
+                    free(commands);
+
+                    return error;
+                }
+
+                n_commands++;
             } else if (!shash_is_empty(local_options)) {
-                ctl_fatal("missing command name (use --help for help)");
+                return xstrdup("missing command name (use --help for help)");
             }
             start = i + 1;
         }
     }
     if (!n_commands) {
-        ctl_fatal("missing command name (use --help for help)");
+        return xstrdup("missing command name (use --help for help)");
     }
+    *commandsp = commands;
     *n_commandsp = n_commands;
-    return commands;
+    return NULL;
 }
 
 /* Prints all registered commands. */
@@ -2056,6 +2403,25 @@ ctl_might_write_to_db(const struct ctl_command *commands, size_t n)
     return false;
 }
 
+/* Report an error while running in the command context. Caller should return
+ * to its caller immediately after reporting the error. */
+void
+ctl_error(struct ctl_context *ctx, const char *format, ...)
+{
+    va_list args;
+
+    ovs_assert(ctx);
+
+    if (ctx->error) {
+        VLOG_ERR("Discarding unhandled error: %s", ctx->error);
+        free(ctx->error);
+    }
+
+    va_start(args, format);
+    ctx->error = xvasprintf(format, args);
+    va_end(args);
+}
+
 void
 ctl_fatal(const char *format, ...)
 {
@@ -2241,6 +2607,7 @@ ctl_context_init_command(struct ctl_context *ctx,
     ds_swap(&ctx->output, &command->output);
     ctx->table = command->table;
     ctx->try_again = false;
+    ctx->error = NULL;
 }
 
 /* Initializes the entire 'ctx'. */
@@ -2266,6 +2633,8 @@ ctl_context_done_command(struct ctl_context *ctx,
 {
     ds_swap(&ctx->output, &command->output);
     command->table = ctx->table;
+    free(ctx->error);
+    ctx->error = NULL;
 }
 
 /* Finishes up with 'ctx'.
@@ -2282,9 +2651,21 @@ ctl_context_done(struct ctl_context *ctx,
     invalidate_cache(ctx);
 }
 
-void ctl_set_column(const char *table_name,
-                    const struct ovsdb_idl_row *row, const char *arg,
-                    struct ovsdb_symbol_table *symtab)
+char * OVS_WARN_UNUSED_RESULT
+ctl_set_column(const char *table_name, const struct ovsdb_idl_row *row,
+               const char *arg, struct ovsdb_symbol_table *symtab)
 {
-    set_column(get_table(table_name), row, arg, symtab);
+    const struct ovsdb_idl_table_class *table;
+    char *error;
+
+    error = get_table(table_name, &table);
+    if (error) {
+        return error;
+    }
+    error = set_column(table, row, arg, symtab);
+    if (error) {
+        return error;
+    }
+
+    return NULL;
 }