[ovs-dev] Bug#691508: [PATCH] ovs-vsctl: Allow command-specific options to mingle with global options.

Ben Pfaff blp at nicira.com
Mon Oct 29 16:34:37 UTC 2012


Until now, a command like "ovs-vsctl --may-exist add-br br0" yielded a
confusing error message.  Users had to realize that the correct form was
"ovs-vsctl -- --may-exist add-br br0", but instead they often reported a
bug or gave up in frustration.  Even though the behavior was documented, it
was counterintuitive.

This commit allows command-specific options to be mixed with global
options, making both forms of the command listed above equally acceptable.

CC: 691508 at bugs.debian.org
Reported-by: Adam Heath <doogie at brainfood.com>
Signed-off-by: Ben Pfaff <blp at nicira.com>
---
 AUTHORS                  |    1 +
 tests/ovs-vsctl.at       |   13 ++++-
 utilities/ovs-vsctl.8.in |   11 ++--
 utilities/ovs-vsctl.c    |  131 +++++++++++++++++++++++++++++++++++++++++----
 4 files changed, 137 insertions(+), 19 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index 4687865..83e6bb5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -81,6 +81,7 @@ The following additional people are mentioned in commit logs as having
 provided helpful bug reports or suggestions.
 
 Aaron M. Ucko           ucko at debian.org
+Adam Heath              doogie at brainfood.com
 Ahmed Bilal             numan252 at gmail.com
 Alan Shieh              ashieh at nicira.com
 Alban Browaeys          prahal at yahoo.com
diff --git a/tests/ovs-vsctl.at b/tests/ovs-vsctl.at
index e903619..cb12f31 100644
--- a/tests/ovs-vsctl.at
+++ b/tests/ovs-vsctl.at
@@ -15,7 +15,7 @@ dnl RUN_OVS_VSCTL(COMMAND, ...)
 dnl
 dnl Executes each ovs-vsctl COMMAND.
 m4_define([RUN_OVS_VSCTL],
-  [m4_foreach([command], [$@], [ovs-vsctl --timeout=5 --no-wait -vreconnect:emer --db=unix:socket -- command
+  [m4_foreach([command], [$@], [ovs-vsctl --timeout=5 --no-wait -vreconnect:emer --db=unix:socket command
 ])])
 m4_define([RUN_OVS_VSCTL_ONELINE],
   [m4_foreach([command], [$@], [ovs-vsctl --timeout=5 --no-wait -vreconnect:emer --db=unix:socket --oneline -- command
@@ -655,6 +655,17 @@ AT_CLEANUP
 AT_SETUP([database commands -- negative checks])
 AT_KEYWORDS([ovs-vsctl])
 OVS_VSCTL_SETUP
+
+AT_CHECK([ovs-vsctl --may-exist],
+  [1], [ignore], [ovs-vsctl: missing command name (use --help for help)
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([ovs-vsctl --may-exist --],
+  [1], [ignore], [ovs-vsctl: missing command name (use --help for help)
+], [OVS_VSCTL_CLEANUP])
+AT_CHECK([ovs-vsctl -- --may-exist],
+  [1], [ignore], [ovs-vsctl: missing command name (use --help for help)
+], [OVS_VSCTL_CLEANUP])
+
 AT_CHECK([RUN_OVS_VSCTL([add-br br0])],
   [0], [ignore], [], [OVS_VSCTL_CLEANUP])
 AT_CHECK([RUN_OVS_VSCTL([add-br br1])], 
diff --git a/utilities/ovs-vsctl.8.in b/utilities/ovs-vsctl.8.in
index 1b80d05..8f70d6b 100644
--- a/utilities/ovs-vsctl.8.in
+++ b/utilities/ovs-vsctl.8.in
@@ -43,9 +43,9 @@ implemented as a single atomic transaction against the database.
 The \fBovs\-vsctl\fR command line begins with global options (see
 \fBOPTIONS\fR below for details).  The global options are followed by
 one or more commands.  Each command should begin with \fB\-\-\fR by
-itself as a command-line argument, to separate it from the global
-options and following commands.  (If the first command does not have
-any options, then the first \fB\-\-\fR may be omitted.)  The command
+itself as a command-line argument, to separate it from the following
+commands.  (The \fB\-\-\fR before the first command is optional.)  The
+command
 itself starts with command-specific options, if any, followed by the
 command name and any arguments.  See \fBEXAMPLES\fR below for syntax
 examples.
@@ -769,10 +769,9 @@ Delete bridge \fBbr0\fR, reporting an error if it does not exist:
 .IP
 .B "ovs\-vsctl del\-br br0"
 .PP
-Delete bridge \fBbr0\fR if it exists (the \fB\-\-\fR is required to
-separate \fBdel\-br\fR's options from the global options):
+Delete bridge \fBbr0\fR if it exists:
 .IP
-.B "ovs\-vsctl \-\- \-\-if\-exists del\-br br0"
+.B "ovs\-vsctl \-\-if\-exists del\-br br0"
 .PP
 Set the \fBqos\fR column of the \fBPort\fR record for \fBeth0\fR to
 point to a new \fBQoS\fR record, which in turn points with its queue 0
diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c
index fda3a89..1745418 100644
--- a/utilities/ovs-vsctl.c
+++ b/utilities/ovs-vsctl.c
@@ -131,12 +131,14 @@ static void vsctl_exit(int status) NO_RETURN;
 static void vsctl_fatal(const char *, ...) PRINTF_FORMAT(1, 2) NO_RETURN;
 static char *default_db(void);
 static void usage(void) NO_RETURN;
-static void parse_options(int argc, char *argv[]);
+static void parse_options(int argc, char *argv[], struct shash *local_options);
 static bool might_write_to_db(char **argv);
 
 static struct vsctl_command *parse_commands(int argc, char *argv[],
+                                            struct shash *local_options,
                                             size_t *n_commandsp);
-static void parse_command(int argc, char *argv[], struct vsctl_command *);
+static void parse_command(int argc, char *argv[], struct shash *local_options,
+                          struct vsctl_command *);
 static const struct vsctl_command_syntax *find_command(const char *name);
 static void run_prerequisites(struct vsctl_command[], size_t n_commands,
                               struct ovsdb_idl *);
@@ -159,6 +161,7 @@ main(int argc, char *argv[])
     extern struct vlog_module VLM_reconnect;
     struct ovsdb_idl *idl;
     struct vsctl_command *commands;
+    struct shash local_options;
     unsigned int seqno;
     size_t n_commands;
     char *args;
@@ -174,8 +177,10 @@ main(int argc, char *argv[])
     VLOG(might_write_to_db(argv) ? VLL_INFO : VLL_DBG, "Called as %s", args);
 
     /* Parse command line. */
-    parse_options(argc, argv);
-    commands = parse_commands(argc - optind, argv + optind, &n_commands);
+    shash_init(&local_options);
+    parse_options(argc, argv, &local_options);
+    commands = parse_commands(argc - optind, argv + optind, &local_options,
+                              &n_commands);
 
     if (timeout) {
         time_alarm(timeout);
@@ -208,8 +213,32 @@ main(int argc, char *argv[])
     }
 }
 
+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)++];
+}
+
 static void
-parse_options(int argc, char *argv[])
+parse_options(int argc, char *argv[], struct shash *local_options)
 {
     enum {
         OPT_DB = UCHAR_MAX + 1,
@@ -218,10 +247,11 @@ parse_options(int argc, char *argv[])
         OPT_NO_WAIT,
         OPT_DRY_RUN,
         OPT_PEER_CA_CERT,
+        OPT_LOCAL,
         VLOG_OPTION_ENUMS,
         TABLE_OPTION_ENUMS
     };
-    static struct option long_options[] = {
+    static const struct option global_long_options[] = {
         {"db", required_argument, NULL, OPT_DB},
         {"no-syslog", no_argument, NULL, OPT_NO_SYSLOG},
         {"no-wait", no_argument, NULL, OPT_NO_WAIT},
@@ -236,18 +266,75 @@ parse_options(int argc, char *argv[])
         {"peer-ca-cert", required_argument, NULL, OPT_PEER_CA_CERT},
         {NULL, 0, NULL, 0},
     };
+    const int n_global_long_options = ARRAY_SIZE(global_long_options) - 1;
     char *tmp, *short_options;
 
-    tmp = long_options_to_short_options(long_options);
+    const struct vsctl_command_syntax *p;
+    struct option *options, *o;
+    size_t allocated_options;
+    size_t n_options;
+    size_t i;
+
+    tmp = long_options_to_short_options(global_long_options);
     short_options = xasprintf("+%s", tmp);
     free(tmp);
 
+    /* We want to parse both global and command-specific options here, but
+     * getopt_long() isn't too convenient for the job.  We copy our global
+     * options into a dynamic array, then append all of the command-specific
+     * options. */
+    options = xmemdup(global_long_options, sizeof global_long_options);
+    allocated_options = ARRAY_SIZE(global_long_options);
+    n_options = n_global_long_options;
+    for (p = all_commands; p->name; p++) {
+        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;
+
+                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, n_options);
+                if (o) {
+                    assert(o - options >= n_global_long_options);
+                    assert(o->has_arg == has_arg);
+                } else {
+                    o = add_option(&options, &n_options, &allocated_options);
+                    o->name = xstrdup(name);
+                    o->has_arg = has_arg;
+                    o->flag = NULL;
+                    o->val = OPT_LOCAL;
+                }
+            }
+
+            free(s);
+        }
+    }
+    o = add_option(&options, &n_options, &allocated_options);
+    memset(o, 0, sizeof *o);
+
     table_style.format = TF_LIST;
 
     for (;;) {
+        int idx;
         int c;
 
-        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        c = getopt_long(argc, argv, short_options, options, &idx);
         if (c == -1) {
             break;
         }
@@ -273,6 +360,16 @@ parse_options(int argc, char *argv[])
             dry_run = true;
             break;
 
+        case OPT_LOCAL:
+            if (shash_find(local_options, options[idx].name)) {
+                vsctl_fatal("'%s' option specified multiple times",
+                            options[idx].name);
+            }
+            shash_add_nocopy(local_options,
+                             xasprintf("--%s", options[idx].name),
+                             optarg ? xstrdup(optarg) : NULL);
+            break;
+
         case 'h':
             usage();
 
@@ -309,10 +406,16 @@ parse_options(int argc, char *argv[])
     if (!db) {
         db = default_db();
     }
+
+    for (i = n_global_long_options; options[i].name; i++) {
+        free(CONST_CAST(char *, options[i].name));
+    }
+    free(options);
 }
 
 static struct vsctl_command *
-parse_commands(int argc, char *argv[], size_t *n_commandsp)
+parse_commands(int argc, char *argv[], struct shash *local_options,
+               size_t *n_commandsp)
 {
     struct vsctl_command *commands;
     size_t n_commands, allocated_commands;
@@ -333,8 +436,10 @@ parse_commands(int argc, char *argv[], size_t *n_commandsp)
                         shash_moved(&c->options);
                     }
                 }
-                parse_command(i - start, &argv[start],
+                parse_command(i - start, &argv[start], local_options,
                               &commands[n_commands++]);
+            } else if (!shash_is_empty(local_options)) {
+                vsctl_fatal("missing command name (use --help for help)");
             }
             start = i + 1;
         }
@@ -347,7 +452,8 @@ parse_commands(int argc, char *argv[], size_t *n_commandsp)
 }
 
 static void
-parse_command(int argc, char *argv[], struct vsctl_command *command)
+parse_command(int argc, char *argv[], struct shash *local_options,
+              struct vsctl_command *command)
 {
     const struct vsctl_command_syntax *p;
     struct shash_node *node;
@@ -355,6 +461,7 @@ parse_command(int argc, char *argv[], struct vsctl_command *command)
     int i;
 
     shash_init(&command->options);
+    shash_swap(local_options, &command->options);
     for (i = 0; i < argc; i++) {
         const char *option = argv[i];
         const char *equals;
@@ -379,7 +486,7 @@ parse_command(int argc, char *argv[], struct vsctl_command *command)
         shash_add_nocopy(&command->options, key, value);
     }
     if (i == argc) {
-        vsctl_fatal("missing command name");
+        vsctl_fatal("missing command name (use --help for help)");
     }
 
     p = find_command(argv[i]);
-- 
1.7.2.5



More information about the dev mailing list