[libvirt] [PATCH 1/5] Improve virsh autocompletion (extract parser)

Solly Ross sross at redhat.com
Sun Mar 30 02:36:09 UTC 2014


This commit extracts the parsing logic from vshCommnadParse
so that it can be used by other methods.  The vshCommnadParse
method is designed to parse full commands and error out on
unknown information, so it is not suitable to simply use it
for autocompletion.  Instead, the logic has been extracted.

The new method essentially performs one pass of the loop previously
done by vshCommnadParse.  Depending on what happens, instead of erroring
or continuing the loop, it returns a value from an enum indicating the
current state.  It also modifieds several arguments as appropriate.

Then, a caller can choose to deal with the state, or may simply ignore
the state when convinient (vshCommandParse deals with the state,
so as to continue providing its previous functionality, while a
completer could choose to ingore states involving unknown options,
for example).
---
 outgoing/0000-cover-letter.patch                   |   68 +
 ...prove-virsh-autocompletion-extract-parser.patch |  428 ++++++
 ...prove-virsh-autocompletion-base-framework.patch |  278 ++++
 ...ve-virsh-autocompletion-global-ctl-object.patch |  116 ++
 ...ove-virsh-autocompletion-domain-completer.patch | 1479 ++++++++++++++++++++
 ...virsh-autocompletion-enum-completer-macro.patch |  131 ++
 tools/virsh.c                                      |  319 +++--
 7 files changed, 2714 insertions(+), 105 deletions(-)
 create mode 100644 outgoing/0000-cover-letter.patch
 create mode 100644 outgoing/0001-Improve-virsh-autocompletion-extract-parser.patch
 create mode 100644 outgoing/0002-Improve-virsh-autocompletion-base-framework.patch
 create mode 100644 outgoing/0003-Improve-virsh-autocompletion-global-ctl-object.patch
 create mode 100644 outgoing/0004-Improve-virsh-autocompletion-domain-completer.patch
 create mode 100644 outgoing/0005-Improve-virsh-autocompletion-enum-completer-macro.patch

diff --git a/outgoing/0000-cover-letter.patch b/outgoing/0000-cover-letter.patch
new file mode 100644
index 0000000..09bc1d4
--- /dev/null
+++ b/outgoing/0000-cover-letter.patch
@@ -0,0 +1,68 @@
+From b383e21683e1d883e72feb6b84edb8725a27a378 Mon Sep 17 00:00:00 2001
+From: Solly Ross <sross at redhat.com>
+Date: Sat, 29 Mar 2014 21:20:55 -0400
+Subject: [PATCH 0/5] Another attempt at improving virsh autocompletion
+
+This patch set constitutes another attempt at improving
+the readline-based autocompletion in virsh.  I attempted
+to take into account some of the comments from the most
+recent previous attempt by Tomas Meszaros (exo at tty.sk).
+
+Specifically, the core completion engine is based around
+logic extracted from vshCommandParse.  When I wrote the
+core completion logic, I noticed that I was duplicating
+a significant chunk of the logic from vshCommandParse,
+so I attempted to refactor the core logic into a separate
+function which is used by both the completion and
+vshCommandParse.  Essentially, the way it works is that
+it performs one iteration of the inner loop from
+vshCommandParse.  However, instead of throwing errors,
+it returns a value from an enum, and sets pointers.
+Then, a calling function may choose to ignore some
+or all of the states (such as the completion function),
+or may throw errors or do work based on the states
+(such as vshCommandParse).
+
+This is perhaps potentially the most "dangerous" and
+controversial part of the patch series, so I figured
+I'd ask for input before going to far.
+
+The other significant changes in the patch set are as such:
+
+- readline no longer automatically falls back to file completion,
+  although it can be explicitly requested for an option
+
+- flags will no longer be suggested if they were already used
+
+- when readline support is enabled, a "global" vshControl object
+  is available via a macro->function so that completers can
+  perform smart completion
+
+Note that the include domain completer patch is lacking flags
+to indicate which commands should only look at active or inactive
+commands.  It is included simply for the purposes of testing
+the completion, and will undergo revision once people are ok
+with the previous commits.
+
+Solly Ross (5):
+  Improve virsh autocompletion (extract parser)
+  Improve virsh autocompletion (base framework)
+  Improve virsh autocompletion (global ctl object)
+  Improve virsh autocompletion (domain completer)
+  Improve virsh autocompletion (enum completer macro)
+
+ tools/Makefile.am            |   3 +-
+ tools/virsh-completer.c      | 353 +++++++++++++++++++++++++++++
+ tools/virsh-completer.h      |  86 +++++++
+ tools/virsh-domain-monitor.c | 287 +-----------------------
+ tools/virsh-domain.c         |  72 ++++++
+ tools/virsh-snapshot.c       |  11 +
+ tools/virsh.c                | 517 +++++++++++++++++++++++++++++++++----------
+ tools/virsh.h                |  13 ++
+ 8 files changed, 947 insertions(+), 395 deletions(-)
+ create mode 100644 tools/virsh-completer.c
+ create mode 100644 tools/virsh-completer.h
+
+-- 
+1.8.3.2
+
diff --git a/outgoing/0001-Improve-virsh-autocompletion-extract-parser.patch b/outgoing/0001-Improve-virsh-autocompletion-extract-parser.patch
new file mode 100644
index 0000000..887a8d7
--- /dev/null
+++ b/outgoing/0001-Improve-virsh-autocompletion-extract-parser.patch
@@ -0,0 +1,428 @@
+From c47d285c090318765208b97ea533c730e51f01ab Mon Sep 17 00:00:00 2001
+From: Solly Ross <sross at redhat.com>
+Date: Fri, 28 Mar 2014 16:19:54 -0400
+Subject: [PATCH 1/5] Improve virsh autocompletion (extract parser)
+
+This commit extracts the parsing logic from vshCommnadParse
+so that it can be used by other methods.  The vshCommnadParse
+method is designed to parse full commands and error out on
+unknown information, so it is not suitable to simply use it
+for autocompletion.  Instead, the logic has been extracted.
+
+The new method essentially performs one pass of the loop previously
+done by vshCommnadParse.  Depending on what happens, instead of erroring
+or continuing the loop, it returns a value from an enum indicating the
+current state.  It also modifieds several arguments as appropriate.
+
+Then, a caller can choose to deal with the state, or may simply ignore
+the state when convinient (vshCommandParse deals with the state,
+so as to continue providing its previous functionality, while a
+completer could choose to ingore states involving unknown options,
+for example).
+---
+ tools/virsh.c | 319 +++++++++++++++++++++++++++++++++++++++-------------------
+ 1 file changed, 214 insertions(+), 105 deletions(-)
+
+diff --git a/tools/virsh.c b/tools/virsh.c
+index 02835d9..5325f6f 100644
+--- a/tools/virsh.c
++++ b/tools/virsh.c
+@@ -1110,7 +1110,7 @@ static vshCmdOptDef helpopt = {
+ };
+ static const vshCmdOptDef *
+ vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name,
+-                   uint32_t *opts_seen, int *opt_index, char **optstr)
++                   uint32_t *opts_seen, int *opt_index, char **optstr, bool raise_err)
+ {
+     size_t i;
+     const vshCmdOptDef *ret = NULL;
+@@ -1138,8 +1138,9 @@ vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name,
+                 if ((value = strchr(name, '='))) {
+                     *value = '\0';
+                     if (*optstr) {
+-                        vshError(ctl, _("invalid '=' after option --%s"),
+-                                 opt->name);
++                        if (raise_err)
++                            vshError(ctl, _("invalid '=' after option --%s"),
++                                     opt->name);
+                         goto cleanup;
+                     }
+                     if (VIR_STRDUP(*optstr, value + 1) < 0)
+@@ -1148,7 +1149,8 @@ vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name,
+                 continue;
+             }
+             if ((*opts_seen & (1 << i)) && opt->type != VSH_OT_ARGV) {
+-                vshError(ctl, _("option --%s already seen"), name);
++                if (raise_err)
++                    vshError(ctl, _("option --%s already seen"), name);
+                 goto cleanup;
+             }
+             *opts_seen |= 1 << i;
+@@ -1159,8 +1161,9 @@ vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name,
+     }
+ 
+     if (STRNEQ(cmd->name, "help")) {
+-        vshError(ctl, _("command '%s' doesn't support option --%s"),
+-                 cmd->name, name);
++        if (raise_err)
++            vshError(ctl, _("command '%s' doesn't support option --%s"),
++                     cmd->name, name);
+     }
+ cleanup:
+     VIR_FREE(alias);
+@@ -1891,12 +1894,146 @@ struct _vshCommandParser {
+     char **arg_end;
+ };
+ 
++typedef enum {
++    VSH_LINE_STATE_DATA_ONLY,
++    VSH_LINE_STATE_IN_PROGRESS,
++    VSH_LINE_STATE_INVALID_EQUALS,
++    VSH_LINE_STATE_UNEXPECTED_DATA,
++    VSH_LINE_STATE_UNKNOWN_OPT,
++    VSH_LINE_STATE_BAD_OPTS,
++    VSH_LINE_STATE_UNKNOWN_CMD,
++    VSH_LINE_STATE_TOK_ERR,
++    VSH_LINE_STATE_CMD_DONE,
++    VSH_LINE_STATE_LINE_DONE,
++} vshLineExtractionState;
++
++static vshLineExtractionState
++vshExtractLinePart(vshControl *ctl, vshCommandParser *parser,
++                   uint32_t *opts_seen, uint32_t *opts_need_arg,
++                   char **tok_out, const vshCmdDef **cmd,
++                   const vshCmdOptDef **opt, bool raise_err, int state)
++{
++    static bool data_only = false;
++    static uint32_t opts_required = 0;
++    vshCommandToken tok_type;
++    char *tok = NULL;
++    vshLineExtractionState ret = VSH_LINE_STATE_IN_PROGRESS;
++
++    if (!state) {
++        data_only = false;
++        opts_required = 0;
++    }
++
++    *opt = NULL;
++
++    tok_type = parser->getNextArg(ctl, parser, &tok);
++
++    if (tok_type == VSH_TK_ERROR) {
++        ret = VSH_LINE_STATE_TOK_ERR;
++        *opt = NULL;
++        goto cleanup;
++    } else if (tok_type == VSH_TK_END) {
++        ret = VSH_LINE_STATE_LINE_DONE;
++        *opt = NULL;
++        goto cleanup;
++    } else if (tok_type == VSH_TK_SUBCMD_END) {
++        *opt = NULL;
++        //*cmd = NULL;
++        ret = VSH_LINE_STATE_CMD_DONE;
++        goto cleanup;
++    }
++
++    if (*cmd == NULL) {
++        *cmd = vshCmddefSearch(tok);
++        if (!*cmd) {
++            ret = VSH_LINE_STATE_UNKNOWN_CMD;
++            *tok_out = vshStrdup(ctl, tok);
++            goto cleanup;
++        }
++
++        if (vshCmddefOptParse(*cmd, opts_need_arg, &opts_required) < 0)
++            ret = VSH_LINE_STATE_BAD_OPTS;
++        else
++            ret = VSH_LINE_STATE_IN_PROGRESS;
++    } else if (data_only) {
++        goto get_data;
++    } else if (tok[0] == '-' && tok[1] == '-' &&
++               c_isalnum(tok[2])) {
++        char *optstr = strchr(tok + 2, '=');
++        int opt_index = 0;
++
++        if (optstr) {
++            *optstr = '\0'; /* convert the '=' to '\0' */
++            optstr = vshStrdup(ctl, optstr + 1);
++        }
++        /* Special case 'help' to ignore all spurious options */
++        *opt = vshCmddefGetOption(ctl, *cmd, tok + 2,
++                                  opts_seen, &opt_index,
++                                  &optstr, raise_err);
++        if (!*opt) {
++            VIR_FREE(optstr);
++            *tok_out = vshStrdup(ctl, tok);
++            ret = VSH_LINE_STATE_UNKNOWN_OPT;
++            goto cleanup;
++        }
++
++        VIR_FREE(tok);
++
++        if ((*opt)->type != VSH_OT_BOOL) {
++            if (optstr)
++                tok = optstr;
++            else
++                tok_type = parser->getNextArg(ctl, parser, &tok);
++
++            if (tok_type == VSH_TK_ERROR) {
++                ret = VSH_LINE_STATE_TOK_ERR;
++                *tok_out = vshStrdup(ctl, tok);
++            } else if (tok_type == VSH_TK_END) {
++                ret = VSH_LINE_STATE_LINE_DONE;
++            } else if (tok_type == VSH_TK_SUBCMD_END) {
++                ret = VSH_LINE_STATE_CMD_DONE;
++            } else {
++                ret = VSH_LINE_STATE_IN_PROGRESS;
++                *tok_out = vshStrdup(ctl, tok);
++            }
++
++            if ((*opt)->type != VSH_OT_ARGV)
++                *opts_need_arg &= ~(1 << opt_index);
++        } else {
++            if (optstr) {
++                ret = VSH_LINE_STATE_INVALID_EQUALS;
++                VIR_FREE(optstr);
++            } else {
++                ret = VSH_LINE_STATE_IN_PROGRESS;
++            }
++        }
++    } else if (tok[0] == '-' && tok[1] == '-' && !tok[2]) {
++        ret = VSH_LINE_STATE_DATA_ONLY;
++        data_only = true;
++    } else {
++get_data:
++        *opt = vshCmddefGetData(*cmd, opts_need_arg, opts_seen);
++        if (!*opt)
++            ret = VSH_LINE_STATE_UNEXPECTED_DATA;
++        else
++            ret = VSH_LINE_STATE_IN_PROGRESS;
++
++        *tok_out = vshStrdup(ctl, tok);
++    }
++
++cleanup:
++    VIR_FREE(tok);
++    return ret;
++}
++
++
+ static bool
+ vshCommandParse(vshControl *ctl, vshCommandParser *parser)
+ {
+     char *tkdata = NULL;
+     vshCmd *clast = NULL;
+     vshCmdOpt *first = NULL;
++    int state = 0;
+ 
+     if (ctl->cmd) {
+         vshCommandFree(ctl->cmd);
+@@ -1906,11 +2043,10 @@ vshCommandParse(vshControl *ctl, vshCommandParser *parser)
+     while (1) {
+         vshCmdOpt *last = NULL;
+         const vshCmdDef *cmd = NULL;
+-        vshCommandToken tk;
+-        bool data_only = false;
+         uint32_t opts_need_arg = 0;
+         uint32_t opts_required = 0;
+         uint32_t opts_seen = 0;
++        vshLineExtractionState line_state;
+ 
+         first = NULL;
+ 
+@@ -1918,112 +2054,83 @@ vshCommandParse(vshControl *ctl, vshCommandParser *parser)
+             const vshCmdOptDef *opt = NULL;
+ 
+             tkdata = NULL;
+-            tk = parser->getNextArg(ctl, parser, &tkdata);
+ 
+-            if (tk == VSH_TK_ERROR)
+-                goto syntaxError;
+-            if (tk != VSH_TK_ARG) {
+-                VIR_FREE(tkdata);
+-                break;
+-            }
++            line_state = vshExtractLinePart(ctl, parser, &opts_seen,
++                                            &opts_need_arg, &tkdata,
++                                            &cmd, &opt, true, state);
++            state++;
+ 
+-            if (cmd == NULL) {
+-                /* first token must be command name */
+-                if (!(cmd = vshCmddefSearch(tkdata))) {
+-                    vshError(ctl, _("unknown command: '%s'"), tkdata);
+-                    goto syntaxError;   /* ... or ignore this command only? */
++            if (line_state == VSH_LINE_STATE_TOK_ERR) {
++                if (opt) { /* we got some funky syntax in an option */
++                    vshError(ctl,
++                             _("expected syntax: --%s <%s>"),
++                             opt->name,
++                             opt->type ==
++                             VSH_OT_INT ? _("number") : _("string"));
+                 }
+-                if (vshCmddefOptParse(cmd, &opts_need_arg,
+-                                      &opts_required) < 0) {
++                /* otherwise, we're here because the tokenizer couldn't
++                 * even start reading */
++                goto syntaxError;
++            } else if (line_state == VSH_LINE_STATE_LINE_DONE ||
++                        line_state == VSH_LINE_STATE_CMD_DONE) {
++                if (!opt) { /* we're actually at the end of the line */
++                    VIR_FREE(tkdata);
++                    break;
++                } else { /* we have an flag without an arg */
+                     vshError(ctl,
+-                             _("internal error: bad options in command: '%s'"),
+-                             tkdata);
++                             _("expected syntax: --%s <%s>"),
++                             opt->name,
++                             opt->type ==
++                             VSH_OT_INT ? _("number") : _("string"));
+                     goto syntaxError;
+                 }
+-                VIR_FREE(tkdata);
+-            } else if (data_only) {
+-                goto get_data;
+-            } else if (tkdata[0] == '-' && tkdata[1] == '-' &&
+-                       c_isalnum(tkdata[2])) {
+-                char *optstr = strchr(tkdata + 2, '=');
+-                int opt_index = 0;
+-
+-                if (optstr) {
+-                    *optstr = '\0'; /* convert the '=' to '\0' */
+-                    optstr = vshStrdup(ctl, optstr + 1);
+-                }
+-                /* Special case 'help' to ignore all spurious options */
+-                if (!(opt = vshCmddefGetOption(ctl, cmd, tkdata + 2,
+-                                               &opts_seen, &opt_index,
+-                                               &optstr))) {
+-                    VIR_FREE(optstr);
+-                    if (STREQ(cmd->name, "help"))
+-                        continue;
++            } else if (line_state == VSH_LINE_STATE_UNEXPECTED_DATA) {
++                if (STRNEQ(cmd->name, "help")) {
++                    /* anything's find after help, but
++                     * otherwise we got some unexpected
++                     * positional arguments */
++                    vshError(ctl, _("unexpected data '%s'"), tkdata);
+                     goto syntaxError;
+                 }
+-                VIR_FREE(tkdata);
+-
+-                if (opt->type != VSH_OT_BOOL) {
+-                    /* option data */
+-                    if (optstr)
+-                        tkdata = optstr;
+-                    else
+-                        tk = parser->getNextArg(ctl, parser, &tkdata);
+-                    if (tk == VSH_TK_ERROR)
+-                        goto syntaxError;
+-                    if (tk != VSH_TK_ARG) {
+-                        vshError(ctl,
+-                                 _("expected syntax: --%s <%s>"),
+-                                 opt->name,
+-                                 opt->type ==
+-                                 VSH_OT_INT ? _("number") : _("string"));
+-                        goto syntaxError;
+-                    }
+-                    if (opt->type != VSH_OT_ARGV)
+-                        opts_need_arg &= ~(1 << opt_index);
+-                } else {
++            } else if (line_state == VSH_LINE_STATE_UNKNOWN_OPT) {
++                if (STREQ(cmd->name, "help"))
++                    continue;
++                goto syntaxError;
++            } else if (line_state == VSH_LINE_STATE_BAD_OPTS) {
++                vshError(ctl,
++                         _("internal error: bad options in command: '%s'"),
++                         tkdata);
++                goto syntaxError;
++            } else if (line_state == VSH_LINE_STATE_UNKNOWN_CMD) {
++                vshError(ctl, _("unknown command: '%s'"), tkdata);
++                goto syntaxError;   /* ... or ignore this command only? */
++            } else if (line_state == VSH_LINE_STATE_INVALID_EQUALS) {
++                vshError(ctl, _("invalid '=' after option --%s"),
++                         opt->name);
++                goto syntaxError;
++            } else if (line_state == VSH_LINE_STATE_IN_PROGRESS) {
++                if (opt) {
++                    /* save option */
++                    vshCmdOpt *arg = vshMalloc(ctl, sizeof(vshCmdOpt));
++
++                    arg->def = opt;
++                    arg->data = tkdata;
++                    arg->next = NULL;
+                     tkdata = NULL;
+-                    if (optstr) {
+-                        vshError(ctl, _("invalid '=' after option --%s"),
+-                                 opt->name);
+-                        VIR_FREE(optstr);
+-                        goto syntaxError;
+-                    }
+-                }
+-            } else if (tkdata[0] == '-' && tkdata[1] == '-' &&
+-                       tkdata[2] == '\0') {
+-                data_only = true;
+-                continue;
+-            } else {
+-get_data:
+-                /* Special case 'help' to ignore spurious data */
+-                if (!(opt = vshCmddefGetData(cmd, &opts_need_arg,
+-                                             &opts_seen)) &&
+-                     STRNEQ(cmd->name, "help")) {
+-                    vshError(ctl, _("unexpected data '%s'"), tkdata);
+-                    goto syntaxError;
++
++                    if (!first)
++                        first = arg;
++                    if (last)
++                        last->next = arg;
++                    last = arg;
++
++                    vshDebug(ctl, VSH_ERR_INFO, "%s: %s(%s): %s\n",
++                             cmd->name,
++                             opt->name,
++                             opt->type != VSH_OT_BOOL ? _("optdata") : _("bool"),
++                             opt->type != VSH_OT_BOOL ? arg->data : _("(none)"));
+                 }
+-            }
+-            if (opt) {
+-                /* save option */
+-                vshCmdOpt *arg = vshMalloc(ctl, sizeof(vshCmdOpt));
+-
+-                arg->def = opt;
+-                arg->data = tkdata;
+-                arg->next = NULL;
+-                tkdata = NULL;
+-
+-                if (!first)
+-                    first = arg;
+-                if (last)
+-                    last->next = arg;
+-                last = arg;
+-
+-                vshDebug(ctl, VSH_ERR_INFO, "%s: %s(%s): %s\n",
+-                         cmd->name,
+-                         opt->name,
+-                         opt->type != VSH_OT_BOOL ? _("optdata") : _("bool"),
+-                         opt->type != VSH_OT_BOOL ? arg->data : _("(none)"));
++                VIR_FREE(tkdata);
+             }
+         }
+ 
+@@ -2064,9 +2171,11 @@ get_data:
+             if (clast)
+                 clast->next = c;
+             clast = c;
++
++            cmd = NULL;
+         }
+ 
+-        if (tk == VSH_TK_END)
++        if (line_state == VSH_LINE_STATE_LINE_DONE)
+             break;
+     }
+ 
+-- 
+1.8.3.2
+
diff --git a/outgoing/0002-Improve-virsh-autocompletion-base-framework.patch b/outgoing/0002-Improve-virsh-autocompletion-base-framework.patch
new file mode 100644
index 0000000..fbbb906
--- /dev/null
+++ b/outgoing/0002-Improve-virsh-autocompletion-base-framework.patch
@@ -0,0 +1,278 @@
+From 9434661ad95421d9eb0891fe830b1a340b7ab218 Mon Sep 17 00:00:00 2001
+From: Solly Ross <sross at redhat.com>
+Date: Tue, 25 Mar 2014 14:51:10 -0400
+Subject: [PATCH 2/5] Improve virsh autocompletion (base framework)
+
+Previously, virsh was able to complete initial command names,
+as well as the names of flag options.  However, this completion
+was clunky for a number of reasons:
+
+- it would provide a flag as an option for completion
+  even if it had already been used
+
+- it did not support completion of positional arguments
+
+- it would fall back to filename completion, even when
+  a file was not an appropriate argument
+
+This commit improves virsh autocompletion by actually parsing
+the line using the extracted parsing logic mentioned in the previous
+commit.  This allows for proper completion of positional arguments
+and flag arguments, as well as preventing a flag from being passed
+multiple times.
+
+Additionally, it removes the default behavior of falling back to filename
+completion when no matches are found.  Now, the VSH_COMPLETE_AS_FILE
+completer_flag may be passed with a null completer function to use
+this functionality.  Otherwise, no completion options are provided.
+
+Note that this commit does not actually introduce any completers or
+completer_flags; it just provides the base framework for using these.
+---
+ tools/virsh.c | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
+ tools/virsh.h |   5 ++
+ 2 files changed, 167 insertions(+), 14 deletions(-)
+
+diff --git a/tools/virsh.c b/tools/virsh.c
+index 5325f6f..02ed78b 100644
+--- a/tools/virsh.c
++++ b/tools/virsh.c
+@@ -3005,27 +3005,123 @@ vshReadlineCommandGenerator(const char *text, int state)
+ }
+ 
+ static char *
++vshDelegateToCustomCompleter(const vshCmdOptDef *opt,
++                             const char *text, int state)
++{
++    static int list_index;
++    static char **completions = NULL;
++    int len = strlen(text);
++    char* val;
++
++    if (!state) {
++        if (!opt)
++            return NULL;
++
++        if (opt->completer) {
++            list_index = 0;
++            completions = opt->completer(opt->completer_flags);
++        }
++        /* otherwise, we fall back to the logic involving file
++         * completion below */
++    }
++
++    if (!completions) {
++        if (!opt->completer && opt->completer_flags & VSH_COMPLETE_AS_FILE)
++            return rl_filename_completion_function(text, state);
++        else
++            return NULL;
++    }
++
++    while ((val = completions[list_index])) {
++        list_index++;
++
++        if (len && STRNEQLEN(val, text, len)) {
++            /* we need to free this explicitly
++             * since it's not getting sent
++             * to readline (frees values sent
++             * to it) */
++            VIR_FREE(val);
++            continue;
++        }
++
++       return val;
++    }
++
++    VIR_FREE(completions);
++    return NULL;
++}
++
++static char *
+ vshReadlineOptionsGenerator(const char *text, int state)
+ {
+     static int list_index, len;
+     static const vshCmdDef *cmd = NULL;
+     const char *name;
++    static int substate;
++    static uint32_t opts_seen = 0;
++    static uint32_t opts_need_arg = 0;
++    static const vshCmdOptDef *curr_opt = NULL;
++    static const vshCmdOptDef *last_arg_opt = NULL;
++    static bool waiting_for_flag_arg = false;
++    vshCommandParser parser;
+ 
+     if (!state) {
+-        /* determine command name */
+-        char *p;
+-        char *cmdname;
++        char *tok = NULL;
++        vshLineExtractionState line_state;
++        //bool waiting_for_next_command = false;
+ 
+-        if (!(p = strchr(rl_line_buffer, ' ')))
+-            return NULL;
++        len = strlen(text);
++        cmd = NULL;
++        opts_seen = 0;
++        opts_need_arg = 0;
++        curr_opt = NULL;
++        list_index = 0;
++        substate = 0;
++        waiting_for_flag_arg = false;
++
++        /* reset the parser */
++        memset(&parser, 0, sizeof(vshCommandParser));
++        parser.pos = rl_line_buffer;
++        parser.getNextArg = vshCommandStringGetArg;
++
++        line_state = vshExtractLinePart(NULL, &parser, &opts_seen,
++                                        &opts_need_arg, &tok, &cmd,
++                                        &curr_opt, false, 0);
++
++        while (line_state != VSH_LINE_STATE_LINE_DONE &&
++                line_state != VSH_LINE_STATE_TOK_ERR) {
++
++            /* if we have an opt and a tok, we're in a data
++             * arg or a flag with an arg */
++            if (line_state == VSH_LINE_STATE_IN_PROGRESS &&
++                    curr_opt && tok) {
++                last_arg_opt = curr_opt;
++            } else {
++                last_arg_opt = NULL;
++            }
+ 
+-        cmdname = vshCalloc(NULL, (p - rl_line_buffer) + 1, 1);
+-        memcpy(cmdname, rl_line_buffer, p - rl_line_buffer);
++            if (line_state == VSH_LINE_STATE_CMD_DONE)
++                cmd = NULL;
+ 
+-        cmd = vshCmddefSearch(cmdname);
+-        list_index = 0;
+-        len = strlen(text);
+-        VIR_FREE(cmdname);
++            VIR_FREE(tok);
++            line_state = vshExtractLinePart(NULL, &parser, &opts_seen,
++                                            &opts_need_arg, &tok, &cmd,
++                                            &curr_opt, false, 1);
++        }
++
++        VIR_FREE(tok);
++
++        if (last_arg_opt && len > 0) {
++            if (last_arg_opt->type != VSH_OT_DATA &&
++                    last_arg_opt->type != VSH_OT_ARGV)
++                curr_opt = last_arg_opt;
++        }
++
++        /* if we have an opt that wasn't reset, we're still waiting
++         * for a flag argument (ditto if we still have a last_arg_opt
++         * and we have current text) */
++        if (curr_opt)
++            waiting_for_flag_arg = true;
+     }
+ 
+     if (!cmd)
+@@ -3034,14 +3130,61 @@ vshReadlineOptionsGenerator(const char *text, int state)
+     if (!cmd->opts)
+         return NULL;
+ 
++    if (waiting_for_flag_arg) {
++        char* res = vshDelegateToCustomCompleter(curr_opt, text, substate);
++        substate++;
++        /* if we're in a flag's argument, we don't
++         * want to show other flags */
++        return res;
++    }
++
+     while ((name = cmd->opts[list_index].name)) {
+         const vshCmdOptDef *opt = &cmd->opts[list_index];
++        const bool was_parsed = opts_seen & (1 << list_index);
+         char *res;
+ 
++        if (opt->type == VSH_OT_DATA || opt->type == VSH_OT_ARGV) {
++            bool in_completion = (was_parsed && last_arg_opt == opt &&
++                                    len > 0);
++            if (!in_completion && ffs(opts_need_arg) - 1 != list_index) {
++                list_index++;
++                continue;
++            }
++
++            /* skip positional args when we have the start of a flag */
++            if (len > 0 && text[0] == '-') {
++                list_index++;
++                continue;
++            }
++
++            /* we don't need to ignore args without custom completers,
++             * since vshDelegateToCustomCompleter will do this for us */
++            res = vshDelegateToCustomCompleter(opt, text, substate);
++            substate++;
++            if (res) {
++                return res;
++            } else {
++                /* if we're already in the middle of completing
++                 * something with a custom completer, there's no
++                 * need to show the other options */
++                if (len > 0 && text[0] != '-')
++                    return NULL;
++                else {
++                    list_index++;
++                    continue;
++                }
++            }
++        }
++
++
+         list_index++;
+ 
+-        if (opt->type == VSH_OT_DATA || opt->type == VSH_OT_ARGV)
+-            /* ignore non --option */
++        /* ignore flags we've already parsed */
++        if (was_parsed)
++            continue;
++
++        /* skip if we've already started completing a data arg */
++        if (len && text[0] != '-')
+             continue;
+ 
+         if (len > 2) {
+@@ -3069,6 +3212,10 @@ vshReadlineCompletion(const char *text, int start,
+     else
+         /* commands options */
+         matches = rl_completion_matches(text, vshReadlineOptionsGenerator);
++
++    /* tell the readline that we're ok with having no matches,
++     * so it shouldn't try to use its default completion function */
++    rl_attempted_completion_over = 1;
+     return matches;
+ }
+ 
+@@ -3087,7 +3234,8 @@ vshReadlineInit(vshControl *ctl)
+      */
+     rl_readline_name = (char *) "virsh";
+ 
+-    /* Tell the completer that we want a crack first. */
++    /* tell the completer that we want to handle generating
++     * potential matches */
+     rl_attempted_completion_function = vshReadlineCompletion;
+ 
+     /* Limit the total size of the history buffer */
+diff --git a/tools/virsh.h b/tools/virsh.h
+index 3e0251b..113d1e5 100644
+--- a/tools/virsh.h
++++ b/tools/virsh.h
+@@ -175,6 +175,11 @@ struct _vshCmdOptDef {
+     unsigned int completer_flags;   /* option completer flags */
+ };
+ 
++/* a completer_flag, which, in the absence of a completer
++ * function, tells the completer to use the built-in 
++ * readline file completer */
++# define VSH_COMPLETE_AS_FILE (1 << 8)
++
+ /*
+  * vshCmdOpt - command options
+  *
+-- 
+1.8.3.2
+
diff --git a/outgoing/0003-Improve-virsh-autocompletion-global-ctl-object.patch b/outgoing/0003-Improve-virsh-autocompletion-global-ctl-object.patch
new file mode 100644
index 0000000..822ae9d
--- /dev/null
+++ b/outgoing/0003-Improve-virsh-autocompletion-global-ctl-object.patch
@@ -0,0 +1,116 @@
+From 152698f10e39b2e8c13b14763483df9ba6c8783f Mon Sep 17 00:00:00 2001
+From: Solly Ross <sross at redhat.com>
+Date: Wed, 26 Mar 2014 16:16:40 -0400
+Subject: [PATCH 3/5] Improve virsh autocompletion (global ctl object)
+
+This patch introduces a way for completers to retrieve the
+current vshControl object by using the vshGetCompleterCtl()
+macro.  When readline is enabled, the main method stores the
+vshControl pointer in a variable that is file-global to virsh.c.
+Then, that pointer can be retrieved by the _vshGetCompleterCtl()
+function, which is mapped to by the macro.  If readline is not
+enabled, the macro simply maps to NULL.
+---
+ tools/virsh.c | 28 +++++++++++++++++++++++++---
+ tools/virsh.h |  8 ++++++++
+ 2 files changed, 33 insertions(+), 3 deletions(-)
+
+diff --git a/tools/virsh.c b/tools/virsh.c
+index 02ed78b..faf065a 100644
+--- a/tools/virsh.c
++++ b/tools/virsh.c
+@@ -95,6 +95,15 @@ static char *progname;
+ 
+ static const vshCmdGrp cmdGroups[];
+ 
++#if WITH_READLINE
++static vshControl *completer_ctl;
++
++vshControl *
++_vshGetCompleterCtl(void) {
++    return completer_ctl;
++}
++#endif
++
+ /* Bypass header poison */
+ #undef strdup
+ 
+@@ -3005,7 +3014,7 @@ vshReadlineCommandGenerator(const char *text, int state)
+ }
+ 
+ static char *
+-vshDelegateToCustomCompleter(const vshCmdOptDef *opt,
++vshDelegateToCustomCompleter(const vshCmdOptDef *opt, bool reconnect,
+                              const char *text, int state)
+ {
+     static int list_index;
+@@ -3018,6 +3027,10 @@ vshDelegateToCustomCompleter(const vshCmdOptDef *opt,
+             return NULL;
+ 
+         if (opt->completer) {
++            vshControl *ctl = completer_ctl;
++            if ((!ctl->conn || disconnected) && reconnect)
++                vshReconnect(ctl);
++
+             list_index = 0;
+             completions = opt->completer(opt->completer_flags);
+         }
+@@ -3131,7 +3144,9 @@ vshReadlineOptionsGenerator(const char *text, int state)
+         return NULL;
+ 
+     if (waiting_for_flag_arg) {
+-        char* res = vshDelegateToCustomCompleter(curr_opt, text, substate);
++        bool may_connect = !(cmd->flags & VSH_CMD_FLAG_NOCONNECT);
++        char* res = vshDelegateToCustomCompleter(curr_opt, may_connect,
++                                                 text, substate);
+         substate++;
+         /* if we're in a flag's argument, we don't
+          * want to show other flags */
+@@ -3159,7 +3174,9 @@ vshReadlineOptionsGenerator(const char *text, int state)
+ 
+             /* we don't need to ignore args without custom completers,
+              * since vshDelegateToCustomCompleter will do this for us */
+-            res = vshDelegateToCustomCompleter(opt, text, substate);
++            bool may_connect = !(cmd->flags & VSH_CMD_FLAG_NOCONNECT);
++            res = vshDelegateToCustomCompleter(opt, may_connect,
++                                               text, substate);
+             substate++;
+             if (res) {
+                 return res;
+@@ -3801,9 +3818,14 @@ int
+ main(int argc, char **argv)
+ {
+     vshControl _ctl, *ctl = &_ctl;
++
+     const char *defaultConn;
+     bool ret = true;
+ 
++#if WITH_READLINE
++    completer_ctl = ctl;
++#endif
++
+     memset(ctl, 0, sizeof(vshControl));
+     ctl->imode = true;          /* default is interactive mode */
+     ctl->log_fd = -1;           /* Initialize log file descriptor */
+diff --git a/tools/virsh.h b/tools/virsh.h
+index 113d1e5..a8977ab 100644
+--- a/tools/virsh.h
++++ b/tools/virsh.h
+@@ -180,6 +180,14 @@ struct _vshCmdOptDef {
+  * readline file completer */
+ # define VSH_COMPLETE_AS_FILE (1 << 8)
+ 
++vshControl * _vshGetCompleterCtl(void);
++
++#if WITH_READLINE
++# define vshGetCompleterCtl() _vshGetCompleterCtl()
++#else
++# define vshGetCompleterCtl() NULL
++#endif
++
+ /*
+  * vshCmdOpt - command options
+  *
+-- 
+1.8.3.2
+
diff --git a/outgoing/0004-Improve-virsh-autocompletion-domain-completer.patch b/outgoing/0004-Improve-virsh-autocompletion-domain-completer.patch
new file mode 100644
index 0000000..64a2427
--- /dev/null
+++ b/outgoing/0004-Improve-virsh-autocompletion-domain-completer.patch
@@ -0,0 +1,1479 @@
+From 19c478fad10813a26fe485e3e03290b0b0224111 Mon Sep 17 00:00:00 2001
+From: Solly Ross <sross at redhat.com>
+Date: Wed, 26 Mar 2014 14:30:21 -0400
+Subject: [PATCH 4/5] Improve virsh autocompletion (domain completer)
+
+This patch introduces a custom completer for domains,
+and sets it to be used for all of the "domain" arguments.
+---
+ tools/Makefile.am            |   3 +-
+ tools/virsh-completer.c      | 332 +++++++++++++++++++++++++++++++++++++++++++
+ tools/virsh-completer.h      |  39 +++++
+ tools/virsh-domain-monitor.c | 287 ++-----------------------------------
+ tools/virsh-domain.c         |  72 ++++++++++
+ tools/virsh-snapshot.c       |  11 ++
+ 6 files changed, 468 insertions(+), 276 deletions(-)
+ create mode 100644 tools/virsh-completer.c
+ create mode 100644 tools/virsh-completer.h
+
+diff --git a/tools/Makefile.am b/tools/Makefile.am
+index 6847f13..e660720 100644
+--- a/tools/Makefile.am
++++ b/tools/Makefile.am
+@@ -53,7 +53,7 @@ EXTRA_DIST = \
+ 	virsh-network.c virsh-nodedev.c			\
+ 	virsh-nwfilter.c virsh-pool.c			\
+ 	virsh-secret.c virsh-snapshot.c			\
+-	virsh-volume.c
++	virsh-volume.c virsh-completer.c
+ 
+ 
+ 
+@@ -191,6 +191,7 @@ virsh_SOURCES =							\
+ 		virsh-secret.c virsh-secret.h			\
+ 		virsh-snapshot.c virsh-snapshot.h		\
+ 		virsh-volume.c virsh-volume.h			\
++		virsh-completer.c virsh-completer.h		\
+ 		$(NULL)
+ 
+ virsh_LDFLAGS = \
+diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c
+new file mode 100644
+index 0000000..b2e7567
+--- /dev/null
++++ b/tools/virsh-completer.c
+@@ -0,0 +1,332 @@
++/*
++ * virsh-completer.h: Common custom completion utilities
++ *
++ * Copyright (C) 2005, 2007-2014 Red Hat, Inc.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library.  If not, see
++ * <http://www.gnu.org/licenses/>.
++ */
++
++#include <config.h>
++#include "virsh-completer.h"
++
++#include "conf/domain_conf.h"
++#include "viralloc.h"
++
++
++/* Utils - Domain Listing */
++/* compare domains, pack NULLed ones at the end*/
++static int
++vshDomainSorter(const void *a, const void *b)
++{
++    virDomainPtr *da = (virDomainPtr *) a;
++    virDomainPtr *db = (virDomainPtr *) b;
++    unsigned int ida;
++    unsigned int idb;
++    unsigned int inactive = (unsigned int) -1;
++
++    if (*da && !*db)
++        return -1;
++
++    if (!*da)
++        return *db != NULL;
++
++    ida = virDomainGetID(*da);
++    idb = virDomainGetID(*db);
++
++    if (ida == inactive && idb == inactive)
++        return vshStrcasecmp(virDomainGetName(*da), virDomainGetName(*db));
++
++    if (ida != inactive && idb != inactive) {
++        if (ida > idb)
++            return 1;
++        else if (ida < idb)
++            return -1;
++    }
++
++    if (ida != inactive)
++        return -1;
++    else
++        return 1;
++}
++
++void
++vshDomainListFree(vshDomainListPtr domlist)
++{
++    size_t i;
++
++    if (domlist && domlist->domains) {
++        for (i = 0; i < domlist->ndomains; i++) {
++            if (domlist->domains[i])
++                virDomainFree(domlist->domains[i]);
++        }
++        VIR_FREE(domlist->domains);
++    }
++    VIR_FREE(domlist);
++}
++
++vshDomainListPtr
++vshDomainListCollect(vshControl *ctl, unsigned int flags)
++{
++    vshDomainListPtr list = vshMalloc(ctl, sizeof(*list));
++    size_t i;
++    int ret;
++    int *ids = NULL;
++    int nids = 0;
++    char **names = NULL;
++    int nnames = 0;
++    virDomainPtr dom;
++    bool success = false;
++    size_t deleted = 0;
++    int persistent;
++    int autostart;
++    int state;
++    int nsnap;
++    int mansave;
++
++    /* try the list with flags support (0.9.13 and later) */
++    if ((ret = virConnectListAllDomains(ctl->conn, &list->domains,
++                                        flags)) >= 0) {
++        list->ndomains = ret;
++        goto finished;
++    }
++
++    /* check if the command is actually supported */
++    if (last_error && last_error->code == VIR_ERR_NO_SUPPORT) {
++        vshResetLibvirtError();
++        goto fallback;
++    }
++
++    if (last_error && last_error->code ==  VIR_ERR_INVALID_ARG) {
++        /* try the new API again but mask non-guaranteed flags */
++        unsigned int newflags = flags & (VIR_CONNECT_LIST_DOMAINS_ACTIVE |
++                                         VIR_CONNECT_LIST_DOMAINS_INACTIVE);
++
++        vshResetLibvirtError();
++        if ((ret = virConnectListAllDomains(ctl->conn, &list->domains,
++                                            newflags)) >= 0) {
++            list->ndomains = ret;
++            goto filter;
++        }
++    }
++
++    /* there was an error during the first or second call */
++    vshError(ctl, "%s", _("Failed to list domains"));
++    goto cleanup;
++
++
++fallback:
++    /* fall back to old method (0.9.12 and older) */
++    vshResetLibvirtError();
++
++    /* list active domains, if necessary */
++    if (!VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_ACTIVE) ||
++        VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_ACTIVE)) {
++        if ((nids = virConnectNumOfDomains(ctl->conn)) < 0) {
++            vshError(ctl, "%s", _("Failed to list active domains"));
++            goto cleanup;
++        }
++
++        if (nids) {
++            ids = vshMalloc(ctl, sizeof(int) * nids);
++
++            if ((nids = virConnectListDomains(ctl->conn, ids, nids)) < 0) {
++                vshError(ctl, "%s", _("Failed to list active domains"));
++                goto cleanup;
++            }
++        }
++    }
++
++    if (!VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_ACTIVE) ||
++        VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_INACTIVE)) {
++        if ((nnames = virConnectNumOfDefinedDomains(ctl->conn)) < 0) {
++            vshError(ctl, "%s", _("Failed to list inactive domains"));
++            goto cleanup;
++        }
++
++        if (nnames) {
++            names = vshMalloc(ctl, sizeof(char *) * nnames);
++
++            if ((nnames = virConnectListDefinedDomains(ctl->conn, names,
++                                                      nnames)) < 0) {
++                vshError(ctl, "%s", _("Failed to list inactive domains"));
++                goto cleanup;
++            }
++        }
++    }
++
++    list->domains = vshMalloc(ctl, sizeof(virDomainPtr) * (nids + nnames));
++    list->ndomains = 0;
++
++    /* get active domains */
++    for (i = 0; i < nids; i++) {
++        if (!(dom = virDomainLookupByID(ctl->conn, ids[i])))
++            continue;
++        list->domains[list->ndomains++] = dom;
++    }
++
++    /* get inactive domains */
++    for (i = 0; i < nnames; i++) {
++        if (!(dom = virDomainLookupByName(ctl->conn, names[i])))
++            continue;
++        list->domains[list->ndomains++] = dom;
++    }
++
++    /* truncate domains that weren't found */
++    deleted = (nids + nnames) - list->ndomains;
++
++filter:
++    /* filter list the list if the list was acquired by fallback means */
++    for (i = 0; i < list->ndomains; i++) {
++        dom = list->domains[i];
++
++        /* persistence filter */
++        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_PERSISTENT)) {
++            if ((persistent = virDomainIsPersistent(dom)) < 0) {
++                vshError(ctl, "%s", _("Failed to get domain persistence info"));
++                goto cleanup;
++            }
++
++            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_PERSISTENT) && persistent) ||
++                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_TRANSIENT) && !persistent)))
++                goto remove_entry;
++        }
++
++        /* domain state filter */
++        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_STATE)) {
++            if (virDomainGetState(dom, &state, NULL, 0) < 0) {
++                vshError(ctl, "%s", _("Failed to get domain state"));
++                goto cleanup;
++            }
++
++            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_RUNNING) &&
++                   state == VIR_DOMAIN_RUNNING) ||
++                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_PAUSED) &&
++                   state == VIR_DOMAIN_PAUSED) ||
++                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_SHUTOFF) &&
++                   state == VIR_DOMAIN_SHUTOFF) ||
++                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_OTHER) &&
++                   (state != VIR_DOMAIN_RUNNING &&
++                    state != VIR_DOMAIN_PAUSED &&
++                    state != VIR_DOMAIN_SHUTOFF))))
++                goto remove_entry;
++        }
++
++        /* autostart filter */
++        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_AUTOSTART)) {
++            if (virDomainGetAutostart(dom, &autostart) < 0) {
++                vshError(ctl, "%s", _("Failed to get domain autostart state"));
++                goto cleanup;
++            }
++
++            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_AUTOSTART) && autostart) ||
++                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_AUTOSTART) && !autostart)))
++                goto remove_entry;
++        }
++
++        /* managed save filter */
++        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_MANAGEDSAVE)) {
++            if ((mansave = virDomainHasManagedSaveImage(dom, 0)) < 0) {
++                vshError(ctl, "%s",
++                         _("Failed to check for managed save image"));
++                goto cleanup;
++            }
++
++            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_MANAGEDSAVE) && mansave) ||
++                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_MANAGEDSAVE) && !mansave)))
++                goto remove_entry;
++        }
++
++        /* snapshot filter */
++        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_SNAPSHOT)) {
++            if ((nsnap = virDomainSnapshotNum(dom, 0)) < 0) {
++                vshError(ctl, "%s", _("Failed to get snapshot count"));
++                goto cleanup;
++            }
++            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT) && nsnap > 0) ||
++                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_SNAPSHOT) && nsnap == 0)))
++                goto remove_entry;
++        }
++
++        /* the domain matched all filters, it may stay */
++        continue;
++
++remove_entry:
++        /* the domain has to be removed as it failed one of the filters */
++        virDomainFree(list->domains[i]);
++        list->domains[i] = NULL;
++        deleted++;
++    }
++
++finished:
++    /* sort the list */
++    if (list->domains && list->ndomains)
++        qsort(list->domains, list->ndomains, sizeof(*list->domains),
++              vshDomainSorter);
++
++    /* truncate the list if filter simulation deleted entries */
++    if (deleted)
++        VIR_SHRINK_N(list->domains, list->ndomains, deleted);
++
++    success = true;
++
++cleanup:
++    for (i = 0; nnames != -1 && i < nnames; i++)
++        VIR_FREE(names[i]);
++
++    if (!success) {
++        vshDomainListFree(list);
++        list = NULL;
++    }
++
++    VIR_FREE(names);
++    VIR_FREE(ids);
++    return list;
++}
++
++
++/* Common completers */
++char **
++vshCompleteDomain(unsigned int flags)
++{
++    vshControl *ctl = vshGetCompleterCtl();
++    vshDomainListPtr dom_list = NULL;
++    virDomainPtr *domains = NULL;
++    char **names = NULL;
++
++    if (!ctl)
++        return NULL;
++
++    if (flags == 0)
++        flags = VIR_CONNECT_LIST_DOMAINS_ACTIVE |
++                VIR_CONNECT_LIST_DOMAINS_INACTIVE;
++
++    dom_list = vshDomainListCollect(ctl, flags);
++
++    if (!dom_list)
++        return NULL;
++
++    domains = dom_list->domains;
++    names = vshCalloc(ctl, dom_list->ndomains+1, sizeof(char*));
++
++    for (int i = 0; i < dom_list->ndomains; i++)
++        names[i] = vshStrdup(ctl, virDomainGetName(domains[i]));
++
++    names[dom_list->ndomains] = NULL;
++
++    vshDomainListFree(dom_list);
++
++    return names;
++};
++
+diff --git a/tools/virsh-completer.h b/tools/virsh-completer.h
+new file mode 100644
+index 0000000..828669b
+--- /dev/null
++++ b/tools/virsh-completer.h
+@@ -0,0 +1,39 @@
++/*
++ * virsh-completer.h: Common custom completion utilities
++ *
++ * Copyright (C) 2005, 2007-2014 Red Hat, Inc.
++ *
++ * This library is free software; you can redistribute it and/or
++ * modify it under the terms of the GNU Lesser General Public
++ * License as published by the Free Software Foundation; either
++ * version 2.1 of the License, or (at your option) any later version.
++ *
++ * This library is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
++ * Lesser General Public License for more details.
++ *
++ * You should have received a copy of the GNU Lesser General Public
++ * License along with this library.  If not, see
++ * <http://www.gnu.org/licenses/>.
++ */
++
++#ifndef VIRSH_COMPLETER_H
++# define VIRSH_COMPLETER_H
++
++# include "virsh.h"
++
++struct vshDomainList {
++    virDomainPtr *domains;
++    size_t ndomains;
++};
++typedef struct vshDomainList *vshDomainListPtr;
++
++void vshDomainListFree(vshDomainListPtr domlist);
++
++vshDomainListPtr vshDomainListCollect(vshControl *ctl, unsigned int flags);
++
++char ** vshCompleteDomain(unsigned int flags);
++
++#endif /* VIRSH_COMPLETER_H */
++
+diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c
+index f474422..c97f7c8 100644
+--- a/tools/virsh-domain-monitor.c
++++ b/tools/virsh-domain-monitor.c
+@@ -25,6 +25,7 @@
+ 
+ #include <config.h>
+ #include "virsh-domain-monitor.h"
++#include "virsh-completer.h"
+ 
+ #include <libxml/parser.h>
+ #include <libxml/tree.h>
+@@ -268,6 +269,7 @@ static const vshCmdOptDef opts_dommemstat[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "period",
+@@ -392,6 +394,7 @@ static const vshCmdOptDef opts_domblkinfo[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "device",
+@@ -447,6 +450,7 @@ static const vshCmdOptDef opts_domblklist[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "inactive",
+@@ -569,6 +573,7 @@ static const vshCmdOptDef opts_domiflist[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "inactive",
+@@ -674,6 +679,7 @@ static const vshCmdOptDef opts_domif_getlink[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "interface",
+@@ -789,6 +795,7 @@ static const vshCmdOptDef opts_domcontrol[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = NULL}
+@@ -842,6 +849,7 @@ static const vshCmdOptDef opts_domblkstat[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "device",
+@@ -1032,6 +1040,7 @@ static const vshCmdOptDef opts_domifstat[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "interface",
+@@ -1109,6 +1118,7 @@ static const vshCmdOptDef opts_domblkerror[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id, or uuid")
+     },
+     {.name = NULL}
+@@ -1174,6 +1184,7 @@ static const vshCmdOptDef opts_dominfo[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = NULL}
+@@ -1316,6 +1327,7 @@ static const vshCmdOptDef opts_domstate[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "reason",
+@@ -1368,281 +1380,6 @@ static const vshCmdInfo info_list[] = {
+     {.name = NULL}
+ };
+ 
+-/* compare domains, pack NULLed ones at the end*/
+-static int
+-vshDomainSorter(const void *a, const void *b)
+-{
+-    virDomainPtr *da = (virDomainPtr *) a;
+-    virDomainPtr *db = (virDomainPtr *) b;
+-    unsigned int ida;
+-    unsigned int idb;
+-    unsigned int inactive = (unsigned int) -1;
+-
+-    if (*da && !*db)
+-        return -1;
+-
+-    if (!*da)
+-        return *db != NULL;
+-
+-    ida = virDomainGetID(*da);
+-    idb = virDomainGetID(*db);
+-
+-    if (ida == inactive && idb == inactive)
+-        return vshStrcasecmp(virDomainGetName(*da), virDomainGetName(*db));
+-
+-    if (ida != inactive && idb != inactive) {
+-        if (ida > idb)
+-            return 1;
+-        else if (ida < idb)
+-            return -1;
+-    }
+-
+-    if (ida != inactive)
+-        return -1;
+-    else
+-        return 1;
+-}
+-
+-struct vshDomainList {
+-    virDomainPtr *domains;
+-    size_t ndomains;
+-};
+-typedef struct vshDomainList *vshDomainListPtr;
+-
+-static void
+-vshDomainListFree(vshDomainListPtr domlist)
+-{
+-    size_t i;
+-
+-    if (domlist && domlist->domains) {
+-        for (i = 0; i < domlist->ndomains; i++) {
+-            if (domlist->domains[i])
+-                virDomainFree(domlist->domains[i]);
+-        }
+-        VIR_FREE(domlist->domains);
+-    }
+-    VIR_FREE(domlist);
+-}
+-
+-static vshDomainListPtr
+-vshDomainListCollect(vshControl *ctl, unsigned int flags)
+-{
+-    vshDomainListPtr list = vshMalloc(ctl, sizeof(*list));
+-    size_t i;
+-    int ret;
+-    int *ids = NULL;
+-    int nids = 0;
+-    char **names = NULL;
+-    int nnames = 0;
+-    virDomainPtr dom;
+-    bool success = false;
+-    size_t deleted = 0;
+-    int persistent;
+-    int autostart;
+-    int state;
+-    int nsnap;
+-    int mansave;
+-
+-    /* try the list with flags support (0.9.13 and later) */
+-    if ((ret = virConnectListAllDomains(ctl->conn, &list->domains,
+-                                        flags)) >= 0) {
+-        list->ndomains = ret;
+-        goto finished;
+-    }
+-
+-    /* check if the command is actually supported */
+-    if (last_error && last_error->code == VIR_ERR_NO_SUPPORT) {
+-        vshResetLibvirtError();
+-        goto fallback;
+-    }
+-
+-    if (last_error && last_error->code ==  VIR_ERR_INVALID_ARG) {
+-        /* try the new API again but mask non-guaranteed flags */
+-        unsigned int newflags = flags & (VIR_CONNECT_LIST_DOMAINS_ACTIVE |
+-                                         VIR_CONNECT_LIST_DOMAINS_INACTIVE);
+-
+-        vshResetLibvirtError();
+-        if ((ret = virConnectListAllDomains(ctl->conn, &list->domains,
+-                                            newflags)) >= 0) {
+-            list->ndomains = ret;
+-            goto filter;
+-        }
+-    }
+-
+-    /* there was an error during the first or second call */
+-    vshError(ctl, "%s", _("Failed to list domains"));
+-    goto cleanup;
+-
+-
+-fallback:
+-    /* fall back to old method (0.9.12 and older) */
+-    vshResetLibvirtError();
+-
+-    /* list active domains, if necessary */
+-    if (!VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_ACTIVE) ||
+-        VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_ACTIVE)) {
+-        if ((nids = virConnectNumOfDomains(ctl->conn)) < 0) {
+-            vshError(ctl, "%s", _("Failed to list active domains"));
+-            goto cleanup;
+-        }
+-
+-        if (nids) {
+-            ids = vshMalloc(ctl, sizeof(int) * nids);
+-
+-            if ((nids = virConnectListDomains(ctl->conn, ids, nids)) < 0) {
+-                vshError(ctl, "%s", _("Failed to list active domains"));
+-                goto cleanup;
+-            }
+-        }
+-    }
+-
+-    if (!VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_ACTIVE) ||
+-        VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_INACTIVE)) {
+-        if ((nnames = virConnectNumOfDefinedDomains(ctl->conn)) < 0) {
+-            vshError(ctl, "%s", _("Failed to list inactive domains"));
+-            goto cleanup;
+-        }
+-
+-        if (nnames) {
+-            names = vshMalloc(ctl, sizeof(char *) * nnames);
+-
+-            if ((nnames = virConnectListDefinedDomains(ctl->conn, names,
+-                                                      nnames)) < 0) {
+-                vshError(ctl, "%s", _("Failed to list inactive domains"));
+-                goto cleanup;
+-            }
+-        }
+-    }
+-
+-    list->domains = vshMalloc(ctl, sizeof(virDomainPtr) * (nids + nnames));
+-    list->ndomains = 0;
+-
+-    /* get active domains */
+-    for (i = 0; i < nids; i++) {
+-        if (!(dom = virDomainLookupByID(ctl->conn, ids[i])))
+-            continue;
+-        list->domains[list->ndomains++] = dom;
+-    }
+-
+-    /* get inactive domains */
+-    for (i = 0; i < nnames; i++) {
+-        if (!(dom = virDomainLookupByName(ctl->conn, names[i])))
+-            continue;
+-        list->domains[list->ndomains++] = dom;
+-    }
+-
+-    /* truncate domains that weren't found */
+-    deleted = (nids + nnames) - list->ndomains;
+-
+-filter:
+-    /* filter list the list if the list was acquired by fallback means */
+-    for (i = 0; i < list->ndomains; i++) {
+-        dom = list->domains[i];
+-
+-        /* persistence filter */
+-        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_PERSISTENT)) {
+-            if ((persistent = virDomainIsPersistent(dom)) < 0) {
+-                vshError(ctl, "%s", _("Failed to get domain persistence info"));
+-                goto cleanup;
+-            }
+-
+-            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_PERSISTENT) && persistent) ||
+-                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_TRANSIENT) && !persistent)))
+-                goto remove_entry;
+-        }
+-
+-        /* domain state filter */
+-        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_STATE)) {
+-            if (virDomainGetState(dom, &state, NULL, 0) < 0) {
+-                vshError(ctl, "%s", _("Failed to get domain state"));
+-                goto cleanup;
+-            }
+-
+-            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_RUNNING) &&
+-                   state == VIR_DOMAIN_RUNNING) ||
+-                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_PAUSED) &&
+-                   state == VIR_DOMAIN_PAUSED) ||
+-                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_SHUTOFF) &&
+-                   state == VIR_DOMAIN_SHUTOFF) ||
+-                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_OTHER) &&
+-                   (state != VIR_DOMAIN_RUNNING &&
+-                    state != VIR_DOMAIN_PAUSED &&
+-                    state != VIR_DOMAIN_SHUTOFF))))
+-                goto remove_entry;
+-        }
+-
+-        /* autostart filter */
+-        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_AUTOSTART)) {
+-            if (virDomainGetAutostart(dom, &autostart) < 0) {
+-                vshError(ctl, "%s", _("Failed to get domain autostart state"));
+-                goto cleanup;
+-            }
+-
+-            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_AUTOSTART) && autostart) ||
+-                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_AUTOSTART) && !autostart)))
+-                goto remove_entry;
+-        }
+-
+-        /* managed save filter */
+-        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_MANAGEDSAVE)) {
+-            if ((mansave = virDomainHasManagedSaveImage(dom, 0)) < 0) {
+-                vshError(ctl, "%s",
+-                         _("Failed to check for managed save image"));
+-                goto cleanup;
+-            }
+-
+-            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_MANAGEDSAVE) && mansave) ||
+-                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_MANAGEDSAVE) && !mansave)))
+-                goto remove_entry;
+-        }
+-
+-        /* snapshot filter */
+-        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_SNAPSHOT)) {
+-            if ((nsnap = virDomainSnapshotNum(dom, 0)) < 0) {
+-                vshError(ctl, "%s", _("Failed to get snapshot count"));
+-                goto cleanup;
+-            }
+-            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT) && nsnap > 0) ||
+-                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_SNAPSHOT) && nsnap == 0)))
+-                goto remove_entry;
+-        }
+-
+-        /* the domain matched all filters, it may stay */
+-        continue;
+-
+-remove_entry:
+-        /* the domain has to be removed as it failed one of the filters */
+-        virDomainFree(list->domains[i]);
+-        list->domains[i] = NULL;
+-        deleted++;
+-    }
+-
+-finished:
+-    /* sort the list */
+-    if (list->domains && list->ndomains)
+-        qsort(list->domains, list->ndomains, sizeof(*list->domains),
+-              vshDomainSorter);
+-
+-    /* truncate the list if filter simulation deleted entries */
+-    if (deleted)
+-        VIR_SHRINK_N(list->domains, list->ndomains, deleted);
+-
+-    success = true;
+-
+-cleanup:
+-    for (i = 0; nnames != -1 && i < nnames; i++)
+-        VIR_FREE(names[i]);
+-
+-    if (!success) {
+-        vshDomainListFree(list);
+-        list = NULL;
+-    }
+-
+-    VIR_FREE(names);
+-    VIR_FREE(ids);
+-    return list;
+-}
+-
+ static const vshCmdOptDef opts_list[] = {
+     {.name = "inactive",
+      .type = VSH_OT_BOOL,
+diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c
+index cb6bf63..66fd9f3 100644
+--- a/tools/virsh-domain.c
++++ b/tools/virsh-domain.c
+@@ -25,6 +25,7 @@
+ 
+ #include <config.h>
+ #include "virsh-domain.h"
++#include "virsh-completer.h"
+ 
+ #include <fcntl.h>
+ #include <poll.h>
+@@ -161,6 +162,7 @@ static const vshCmdInfo info_attach_device[] = {
+ 
+ static const vshCmdOptDef opts_attach_device[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -263,6 +265,7 @@ static const vshCmdInfo info_attach_disk[] = {
+ 
+ static const vshCmdOptDef opts_attach_disk[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -703,6 +706,7 @@ static const vshCmdInfo info_attach_interface[] = {
+ 
+ static const vshCmdOptDef opts_attach_interface[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -960,6 +964,7 @@ static const vshCmdInfo info_autostart[] = {
+ 
+ static const vshCmdOptDef opts_autostart[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -1016,6 +1021,7 @@ static const vshCmdInfo info_blkdeviotune[] = {
+ 
+ static const vshCmdOptDef opts_blkdeviotune[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -1240,6 +1246,7 @@ static const vshCmdInfo info_blkiotune[] = {
+ 
+ static const vshCmdOptDef opts_blkiotune[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -1567,6 +1574,7 @@ static const vshCmdInfo info_block_commit[] = {
+ 
+ static const vshCmdOptDef opts_block_commit[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -1735,6 +1743,7 @@ static const vshCmdInfo info_block_copy[] = {
+ 
+ static const vshCmdOptDef opts_block_copy[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -1930,6 +1939,7 @@ static const vshCmdInfo info_block_job[] = {
+ 
+ static const vshCmdOptDef opts_block_job[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -2031,6 +2041,7 @@ static const vshCmdInfo info_block_pull[] = {
+ 
+ static const vshCmdOptDef opts_block_pull[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -2186,6 +2197,7 @@ static const vshCmdInfo info_block_resize[] = {
+ 
+ static const vshCmdOptDef opts_block_resize[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -2256,6 +2268,7 @@ static const vshCmdInfo info_console[] = {
+ 
+ static const vshCmdOptDef opts_console[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -2353,6 +2366,7 @@ static const vshCmdInfo info_domif_setlink[] = {
+ 
+ static const vshCmdOptDef opts_domif_setlink[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -2540,6 +2554,7 @@ static const vshCmdInfo info_domiftune[] = {
+ 
+ static const vshCmdOptDef opts_domiftune[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -2727,6 +2742,7 @@ static const vshCmdInfo info_suspend[] = {
+ 
+ static const vshCmdOptDef opts_suspend[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -2773,6 +2789,7 @@ static const vshCmdInfo info_dom_pm_suspend[] = {
+ 
+ static const vshCmdOptDef opts_dom_pm_suspend[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -2857,6 +2874,7 @@ static const vshCmdInfo info_dom_pm_wakeup[] = {
+ 
+ static const vshCmdOptDef opts_dom_pm_wakeup[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -2906,6 +2924,7 @@ static const vshCmdInfo info_undefine[] = {
+ 
+ static const vshCmdOptDef opts_undefine[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name or uuid")
+@@ -3299,6 +3318,7 @@ static const vshCmdInfo info_start[] = {
+ 
+ static const vshCmdOptDef opts_start[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("name of the inactive domain")
+@@ -3482,6 +3502,7 @@ static const vshCmdOptDef opts_save[] = {
+      .help = N_("avoid file system cache when saving")
+     },
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -3951,6 +3972,7 @@ static const vshCmdOptDef opts_managedsave[] = {
+      .help = N_("avoid file system cache when saving")
+     },
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -4071,6 +4093,7 @@ static const vshCmdInfo info_managedsaveremove[] = {
+ 
+ static const vshCmdOptDef opts_managedsaveremove[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -4130,6 +4153,7 @@ static const vshCmdInfo info_schedinfo[] = {
+ 
+ static const vshCmdOptDef opts_schedinfo[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -4473,6 +4497,7 @@ static const vshCmdOptDef opts_dump[] = {
+      .help = N_("reset the domain after core dump")
+     },
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -4603,6 +4628,7 @@ static const vshCmdInfo info_screenshot[] = {
+ 
+ static const vshCmdOptDef opts_screenshot[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -4751,6 +4777,7 @@ static const vshCmdInfo info_resume[] = {
+ 
+ static const vshCmdOptDef opts_resume[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -4794,6 +4821,7 @@ static const vshCmdInfo info_shutdown[] = {
+ 
+ static const vshCmdOptDef opts_shutdown[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -4880,6 +4908,7 @@ static const vshCmdInfo info_reboot[] = {
+ 
+ static const vshCmdOptDef opts_reboot[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -4961,6 +4990,7 @@ static const vshCmdInfo info_reset[] = {
+ 
+ static const vshCmdOptDef opts_reset[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -5004,6 +5034,7 @@ static const vshCmdInfo info_domjobinfo[] = {
+ 
+ static const vshCmdOptDef opts_domjobinfo[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -5226,6 +5257,7 @@ static const vshCmdInfo info_domjobabort[] = {
+ 
+ static const vshCmdOptDef opts_domjobabort[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -5264,6 +5296,7 @@ static const vshCmdInfo info_vcpucount[] = {
+ 
+ static const vshCmdOptDef opts_vcpucount[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -5475,6 +5508,7 @@ static const vshCmdInfo info_vcpuinfo[] = {
+ 
+ static const vshCmdOptDef opts_vcpuinfo[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -5584,6 +5618,7 @@ static const vshCmdInfo info_vcpupin[] = {
+ 
+ static const vshCmdOptDef opts_vcpupin[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -5871,6 +5906,7 @@ static const vshCmdInfo info_emulatorpin[] = {
+ 
+ static const vshCmdOptDef opts_emulatorpin[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -5990,6 +6026,7 @@ static const vshCmdInfo info_setvcpus[] = {
+ 
+ static const vshCmdOptDef opts_setvcpus[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -6311,6 +6348,7 @@ static const vshCmdInfo info_cpu_stats[] = {
+ 
+ static const vshCmdOptDef opts_cpu_stats[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -6645,6 +6683,7 @@ static const vshCmdInfo info_destroy[] = {
+ 
+ static const vshCmdOptDef opts_destroy[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -6702,6 +6741,7 @@ static const vshCmdInfo info_desc[] = {
+ 
+ static const vshCmdOptDef opts_desc[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -6874,6 +6914,7 @@ static const vshCmdInfo info_metadata[] = {
+ 
+ static const vshCmdOptDef opts_metadata[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -7031,6 +7072,7 @@ static const vshCmdInfo info_inject_nmi[] = {
+ 
+ static const vshCmdOptDef opts_inject_nmi[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -7069,6 +7111,7 @@ static const vshCmdInfo info_send_key[] = {
+ 
+ static const vshCmdOptDef opts_send_key[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -7171,6 +7214,7 @@ static const vshCmdInfo info_send_process_signal[] = {
+ 
+ static const vshCmdOptDef opts_send_process_signal[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -7286,6 +7330,7 @@ static const vshCmdInfo info_setmem[] = {
+ 
+ static const vshCmdOptDef opts_setmem[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -7383,6 +7428,7 @@ static const vshCmdInfo info_setmaxmem[] = {
+ 
+ static const vshCmdOptDef opts_setmaxmem[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -7485,6 +7531,7 @@ static const vshCmdInfo info_memtune[] = {
+ 
+ static const vshCmdOptDef opts_memtune[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -7684,6 +7731,7 @@ static const vshCmdInfo info_numatune[] = {
+ 
+ static const vshCmdOptDef opts_numatune[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -7831,6 +7879,7 @@ static const vshCmdInfo info_qemu_monitor_command[] = {
+ 
+ static const vshCmdOptDef opts_qemu_monitor_command[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -7981,6 +8030,7 @@ static const vshCmdInfo info_qemu_agent_command[] = {
+ 
+ static const vshCmdOptDef opts_qemu_agent_command[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -8110,6 +8160,7 @@ static const vshCmdInfo info_lxc_enter_namespace[] = {
+ 
+ static const vshCmdOptDef opts_lxc_enter_namespace[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -8252,6 +8303,7 @@ static const vshCmdInfo info_dumpxml[] = {
+ 
+ static const vshCmdOptDef opts_dumpxml[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -8438,6 +8490,7 @@ static const vshCmdInfo info_domname[] = {
+ 
+ static const vshCmdOptDef opts_domname[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain id or uuid")
+@@ -8474,6 +8527,7 @@ static const vshCmdInfo info_domid[] = {
+ 
+ static const vshCmdOptDef opts_domid[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name or uuid")
+@@ -8515,6 +8569,7 @@ static const vshCmdInfo info_domuuid[] = {
+ 
+ static const vshCmdOptDef opts_domuuid[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain id or name")
+@@ -8620,6 +8675,7 @@ static const vshCmdOptDef opts_migrate[] = {
+      .help = N_("abort on soft errors during migration")
+     },
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -8887,6 +8943,7 @@ static const vshCmdInfo info_migrate_setmaxdowntime[] = {
+ 
+ static const vshCmdOptDef opts_migrate_setmaxdowntime[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -8941,6 +8998,7 @@ static const vshCmdInfo info_migrate_compcache[] = {
+ 
+ static const vshCmdOptDef opts_migrate_compcache[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -9003,6 +9061,7 @@ static const vshCmdInfo info_migrate_setspeed[] = {
+ 
+ static const vshCmdOptDef opts_migrate_setspeed[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -9055,6 +9114,7 @@ static const vshCmdInfo info_migrate_getspeed[] = {
+ 
+ static const vshCmdOptDef opts_migrate_getspeed[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -9099,6 +9159,7 @@ static const vshCmdInfo info_domdisplay[] = {
+ 
+ static const vshCmdOptDef opts_domdisplay[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -9286,6 +9347,7 @@ static const vshCmdInfo info_vncdisplay[] = {
+ 
+ static const vshCmdOptDef opts_vncdisplay[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -9359,6 +9421,7 @@ static const vshCmdInfo info_ttyconsole[] = {
+ 
+ static const vshCmdOptDef opts_ttyconsole[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -9419,6 +9482,7 @@ static const vshCmdInfo info_domhostname[] = {
+ 
+ static const vshCmdOptDef opts_domhostname[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -9582,6 +9646,7 @@ static const vshCmdInfo info_detach_device[] = {
+ 
+ static const vshCmdOptDef opts_detach_device[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -9683,6 +9748,7 @@ static const vshCmdInfo info_update_device[] = {
+ 
+ static const vshCmdOptDef opts_update_device[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -9785,6 +9851,7 @@ static const vshCmdInfo info_detach_interface[] = {
+ 
+ static const vshCmdOptDef opts_detach_interface[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -10155,6 +10222,7 @@ static const vshCmdInfo info_detach_disk[] = {
+ 
+ static const vshCmdOptDef opts_detach_disk[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -10270,6 +10338,7 @@ static const vshCmdInfo info_edit[] = {
+ 
+ static const vshCmdOptDef opts_edit[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -10855,6 +10924,7 @@ static const vshCmdInfo info_event[] = {
+ 
+ static const vshCmdOptDef opts_event[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .help = N_("filter by domain name, id, or uuid")
+     },
+@@ -11007,6 +11077,7 @@ static const vshCmdInfo info_change_media[] = {
+ 
+ static const vshCmdOptDef opts_change_media[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+@@ -11158,6 +11229,7 @@ static const vshCmdInfo info_domfstrim[] = {
+ 
+ static const vshCmdOptDef opts_domfstrim[] = {
+     {.name = "domain",
++     .completer = vshCompleteDomain,
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
+      .help = N_("domain name, id or uuid")
+diff --git a/tools/virsh-snapshot.c b/tools/virsh-snapshot.c
+index 6beb25a..f199642 100644
+--- a/tools/virsh-snapshot.c
++++ b/tools/virsh-snapshot.c
+@@ -25,6 +25,7 @@
+ 
+ #include <config.h>
+ #include "virsh-snapshot.h"
++#include "virsh-completer.h"
+ 
+ #include <assert.h>
+ 
+@@ -125,6 +126,7 @@ static const vshCmdOptDef opts_snapshot_create[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "xmlfile",
+@@ -330,6 +332,7 @@ static const vshCmdOptDef opts_snapshot_create_as[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "name",
+@@ -526,6 +529,7 @@ static const vshCmdOptDef opts_snapshot_edit[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "snapshotname",
+@@ -648,6 +652,7 @@ static const vshCmdOptDef opts_snapshot_current[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "name",
+@@ -884,6 +889,7 @@ static const vshCmdOptDef opts_snapshot_info[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "snapshotname",
+@@ -1441,6 +1447,7 @@ static const vshCmdOptDef opts_snapshot_list[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "parent",
+@@ -1705,6 +1712,7 @@ static const vshCmdOptDef opts_snapshot_dumpxml[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "snapshotname",
+@@ -1773,6 +1781,7 @@ static const vshCmdOptDef opts_snapshot_parent[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "snapshotname",
+@@ -1841,6 +1850,7 @@ static const vshCmdOptDef opts_snapshot_revert[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "snapshotname",
+@@ -1934,6 +1944,7 @@ static const vshCmdOptDef opts_snapshot_delete[] = {
+     {.name = "domain",
+      .type = VSH_OT_DATA,
+      .flags = VSH_OFLAG_REQ,
++     .completer = vshCompleteDomain,
+      .help = N_("domain name, id or uuid")
+     },
+     {.name = "snapshotname",
+-- 
+1.8.3.2
+
diff --git a/outgoing/0005-Improve-virsh-autocompletion-enum-completer-macro.patch b/outgoing/0005-Improve-virsh-autocompletion-enum-completer-macro.patch
new file mode 100644
index 0000000..bfa9915
--- /dev/null
+++ b/outgoing/0005-Improve-virsh-autocompletion-enum-completer-macro.patch
@@ -0,0 +1,131 @@
+From b383e21683e1d883e72feb6b84edb8725a27a378 Mon Sep 17 00:00:00 2001
+From: Solly Ross <sross at redhat.com>
+Date: Wed, 26 Mar 2014 16:14:10 -0400
+Subject: [PATCH 5/5] Improve virsh autocompletion (enum completer macro)
+
+This patch adds a utility macro which generates completers
+for enum-type string options (i.e. those options which may
+be one of a static set of strings).
+
+Instead of having to manually implement a completers for such
+options, the macro may be used as such:
+`VSH_STRING_COMPLETER(ctl, SomeOption, "string1", "string2")`,
+which generates a method `vshCompleteSomeOption` which returns
+"string 1" and "string 2" as completion option.  The macro will
+work with up to 63 different strings.
+---
+ tools/virsh-completer.c | 23 ++++++++++++++++++++++-
+ tools/virsh-completer.h | 49 ++++++++++++++++++++++++++++++++++++++++++++++++-
+ 2 files changed, 70 insertions(+), 2 deletions(-)
+
+diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c
+index b2e7567..00346e4 100644
+--- a/tools/virsh-completer.c
++++ b/tools/virsh-completer.c
+@@ -21,9 +21,31 @@
+ #include <config.h>
+ #include "virsh-completer.h"
+ 
++#include <stdarg.h>
++
+ #include "conf/domain_conf.h"
+ #include "viralloc.h"
+ 
++/* Utils - General */
++char **
++vshVarArgsToStringList(vshControl *ctl, unsigned int count, ...)
++{
++    va_list ap;
++    char **strs;
++
++    if (count == 0)
++        return NULL;
++
++    va_start(ap, count);
++
++    strs = vshCalloc(ctl, count, sizeof(char*));
++    for (int i = 0; i < count; i++)
++        strs[i] = vshStrdup(ctl, va_arg(ap, char*));
++
++    va_end(ap);
++
++    return strs;
++};
+ 
+ /* Utils - Domain Listing */
+ /* compare domains, pack NULLed ones at the end*/
+@@ -329,4 +351,3 @@ vshCompleteDomain(unsigned int flags)
+ 
+     return names;
+ };
+-
+diff --git a/tools/virsh-completer.h b/tools/virsh-completer.h
+index 828669b..7afd7b5 100644
+--- a/tools/virsh-completer.h
++++ b/tools/virsh-completer.h
+@@ -23,6 +23,53 @@
+ 
+ # include "virsh.h"
+ 
++/* Utils - General */
++/* __VA_NARGS__:
++ *
++ * This macro determine the length (up to 63) of
++ * __VA_ARGS__ arguments passed to a macro.
++ */
++
++/* inspired by
++ * https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s */
++# define __VA_NARGS__(...) \
++             __VA_NARGS_FLATTEN__(__VA_ARGS__,INV_NUM_SEQ())
++#define __VA_NARGS_FLATTEN__(...) \
++             __VA_NARGS_IMPL__(__VA_ARGS__)
++#define __VA_NARGS_IMPL__( \
++    _1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16, \
++    _17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
++    _31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44, \
++    _45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58, \
++    _59,_60,_61,_62,_63, N, ...) N
++#define INV_NUM_SEQ() \
++    63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46, \
++    45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28, \
++    27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10, \
++    9,8,7,6,5,4,3,2,1,0
++
++/* VSH_STRING_COMPLETER:
++ *
++ * @ctl: a vshControl* or NULL
++ * @name: the name of the completer (unquoted)
++ * @__VA_ARGS__: the options as strings
++ *
++ * This macro creates a vshComplete[name] function
++ * suitable to for use as a custom option completer.
++ * The completer will return an array of strings with
++ * the values specified.
++ */
++# define VSH_STRING_COMPLETER(ctl, name, ...) \
++    static char ** \
++    vshComplete ## name (unsigned int flags ATTRIBUTE_UNUSED) \
++    { \
++        return vshVarArgsToStringList(ctl, __VA_NARGS__(__VA_ARGS__), \
++                                      __VA_ARGS__); \
++    }
++
++char ** vshVarArgsToStringList(vshControl *ctl, unsigned int count, ...);
++
++/* Utils - Domain */
+ struct vshDomainList {
+     virDomainPtr *domains;
+     size_t ndomains;
+@@ -33,7 +80,7 @@ void vshDomainListFree(vshDomainListPtr domlist);
+ 
+ vshDomainListPtr vshDomainListCollect(vshControl *ctl, unsigned int flags);
+ 
++/* Common Completers */
+ char ** vshCompleteDomain(unsigned int flags);
+ 
+ #endif /* VIRSH_COMPLETER_H */
+-
+-- 
+1.8.3.2
+
diff --git a/tools/virsh.c b/tools/virsh.c
index f2e4c4a..0273abe 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -1110,7 +1110,7 @@ static vshCmdOptDef helpopt = {
 };
 static const vshCmdOptDef *
 vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name,
-                   uint32_t *opts_seen, int *opt_index, char **optstr)
+                   uint32_t *opts_seen, int *opt_index, char **optstr, bool raise_err)
 {
     size_t i;
     const vshCmdOptDef *ret = NULL;
@@ -1138,8 +1138,9 @@ vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name,
                 if ((value = strchr(name, '='))) {
                     *value = '\0';
                     if (*optstr) {
-                        vshError(ctl, _("invalid '=' after option --%s"),
-                                 opt->name);
+                        if (raise_err)
+                            vshError(ctl, _("invalid '=' after option --%s"),
+                                     opt->name);
                         goto cleanup;
                     }
                     if (VIR_STRDUP(*optstr, value + 1) < 0)
@@ -1148,7 +1149,8 @@ vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name,
                 continue;
             }
             if ((*opts_seen & (1 << i)) && opt->type != VSH_OT_ARGV) {
-                vshError(ctl, _("option --%s already seen"), name);
+                if (raise_err)
+                    vshError(ctl, _("option --%s already seen"), name);
                 goto cleanup;
             }
             *opts_seen |= 1 << i;
@@ -1159,8 +1161,9 @@ vshCmddefGetOption(vshControl *ctl, const vshCmdDef *cmd, const char *name,
     }
 
     if (STRNEQ(cmd->name, "help")) {
-        vshError(ctl, _("command '%s' doesn't support option --%s"),
-                 cmd->name, name);
+        if (raise_err)
+            vshError(ctl, _("command '%s' doesn't support option --%s"),
+                     cmd->name, name);
     }
  cleanup:
     VIR_FREE(alias);
@@ -1891,12 +1894,146 @@ struct _vshCommandParser {
     char **arg_end;
 };
 
+typedef enum {
+    VSH_LINE_STATE_DATA_ONLY,
+    VSH_LINE_STATE_IN_PROGRESS,
+    VSH_LINE_STATE_INVALID_EQUALS,
+    VSH_LINE_STATE_UNEXPECTED_DATA,
+    VSH_LINE_STATE_UNKNOWN_OPT,
+    VSH_LINE_STATE_BAD_OPTS,
+    VSH_LINE_STATE_UNKNOWN_CMD,
+    VSH_LINE_STATE_TOK_ERR,
+    VSH_LINE_STATE_CMD_DONE,
+    VSH_LINE_STATE_LINE_DONE,
+} vshLineExtractionState;
+
+static vshLineExtractionState
+vshExtractLinePart(vshControl *ctl, vshCommandParser *parser,
+                   uint32_t *opts_seen, uint32_t *opts_need_arg,
+                   char **tok_out, const vshCmdDef **cmd,
+                   const vshCmdOptDef **opt, bool raise_err, int state)
+{
+    static bool data_only = false;
+    static uint32_t opts_required = 0;
+    vshCommandToken tok_type;
+    char *tok = NULL;
+    vshLineExtractionState ret = VSH_LINE_STATE_IN_PROGRESS;
+
+    if (!state) {
+        data_only = false;
+        opts_required = 0;
+    }
+
+    *opt = NULL;
+
+    tok_type = parser->getNextArg(ctl, parser, &tok);
+
+    if (tok_type == VSH_TK_ERROR) {
+        ret = VSH_LINE_STATE_TOK_ERR;
+        *opt = NULL;
+        goto cleanup;
+    } else if (tok_type == VSH_TK_END) {
+        ret = VSH_LINE_STATE_LINE_DONE;
+        *opt = NULL;
+        goto cleanup;
+    } else if (tok_type == VSH_TK_SUBCMD_END) {
+        *opt = NULL;
+        //*cmd = NULL;
+        ret = VSH_LINE_STATE_CMD_DONE;
+        goto cleanup;
+    }
+
+    if (*cmd == NULL) {
+        *cmd = vshCmddefSearch(tok);
+        if (!*cmd) {
+            ret = VSH_LINE_STATE_UNKNOWN_CMD;
+            *tok_out = vshStrdup(ctl, tok);
+            goto cleanup;
+        }
+
+        if (vshCmddefOptParse(*cmd, opts_need_arg, &opts_required) < 0)
+            ret = VSH_LINE_STATE_BAD_OPTS;
+        else
+            ret = VSH_LINE_STATE_IN_PROGRESS;
+    } else if (data_only) {
+        goto get_data;
+    } else if (tok[0] == '-' && tok[1] == '-' &&
+               c_isalnum(tok[2])) {
+        char *optstr = strchr(tok + 2, '=');
+        int opt_index = 0;
+
+        if (optstr) {
+            *optstr = '\0'; /* convert the '=' to '\0' */
+            optstr = vshStrdup(ctl, optstr + 1);
+        }
+        /* Special case 'help' to ignore all spurious options */
+        *opt = vshCmddefGetOption(ctl, *cmd, tok + 2,
+                                  opts_seen, &opt_index,
+                                  &optstr, raise_err);
+        if (!*opt) {
+            VIR_FREE(optstr);
+            *tok_out = vshStrdup(ctl, tok);
+            ret = VSH_LINE_STATE_UNKNOWN_OPT;
+            goto cleanup;
+        }
+
+        VIR_FREE(tok);
+
+        if ((*opt)->type != VSH_OT_BOOL) {
+            if (optstr)
+                tok = optstr;
+            else
+                tok_type = parser->getNextArg(ctl, parser, &tok);
+
+            if (tok_type == VSH_TK_ERROR) {
+                ret = VSH_LINE_STATE_TOK_ERR;
+                *tok_out = vshStrdup(ctl, tok);
+            } else if (tok_type == VSH_TK_END) {
+                ret = VSH_LINE_STATE_LINE_DONE;
+            } else if (tok_type == VSH_TK_SUBCMD_END) {
+                ret = VSH_LINE_STATE_CMD_DONE;
+            } else {
+                ret = VSH_LINE_STATE_IN_PROGRESS;
+                *tok_out = vshStrdup(ctl, tok);
+            }
+
+            if ((*opt)->type != VSH_OT_ARGV)
+                *opts_need_arg &= ~(1 << opt_index);
+        } else {
+            if (optstr) {
+                ret = VSH_LINE_STATE_INVALID_EQUALS;
+                VIR_FREE(optstr);
+            } else {
+                ret = VSH_LINE_STATE_IN_PROGRESS;
+            }
+        }
+    } else if (tok[0] == '-' && tok[1] == '-' && !tok[2]) {
+        ret = VSH_LINE_STATE_DATA_ONLY;
+        data_only = true;
+    } else {
+ get_data:
+        *opt = vshCmddefGetData(*cmd, opts_need_arg, opts_seen);
+        if (!*opt)
+            ret = VSH_LINE_STATE_UNEXPECTED_DATA;
+        else
+            ret = VSH_LINE_STATE_IN_PROGRESS;
+
+        *tok_out = vshStrdup(ctl, tok);
+    }
+
+ cleanup:
+    VIR_FREE(tok);
+    return ret;
+}
+
+
 static bool
 vshCommandParse(vshControl *ctl, vshCommandParser *parser)
 {
     char *tkdata = NULL;
     vshCmd *clast = NULL;
     vshCmdOpt *first = NULL;
+    int state = 0;
 
     if (ctl->cmd) {
         vshCommandFree(ctl->cmd);
@@ -1906,11 +2043,10 @@ vshCommandParse(vshControl *ctl, vshCommandParser *parser)
     while (1) {
         vshCmdOpt *last = NULL;
         const vshCmdDef *cmd = NULL;
-        vshCommandToken tk;
-        bool data_only = false;
         uint32_t opts_need_arg = 0;
         uint32_t opts_required = 0;
         uint32_t opts_seen = 0;
+        vshLineExtractionState line_state;
 
         first = NULL;
 
@@ -1918,112 +2054,83 @@ vshCommandParse(vshControl *ctl, vshCommandParser *parser)
             const vshCmdOptDef *opt = NULL;
 
             tkdata = NULL;
-            tk = parser->getNextArg(ctl, parser, &tkdata);
 
-            if (tk == VSH_TK_ERROR)
-                goto syntaxError;
-            if (tk != VSH_TK_ARG) {
-                VIR_FREE(tkdata);
-                break;
-            }
+            line_state = vshExtractLinePart(ctl, parser, &opts_seen,
+                                            &opts_need_arg, &tkdata,
+                                            &cmd, &opt, true, state);
+            state++;
 
-            if (cmd == NULL) {
-                /* first token must be command name */
-                if (!(cmd = vshCmddefSearch(tkdata))) {
-                    vshError(ctl, _("unknown command: '%s'"), tkdata);
-                    goto syntaxError;   /* ... or ignore this command only? */
+            if (line_state == VSH_LINE_STATE_TOK_ERR) {
+                if (opt) { /* we got some funky syntax in an option */
+                    vshError(ctl,
+                             _("expected syntax: --%s <%s>"),
+                             opt->name,
+                             opt->type ==
+                             VSH_OT_INT ? _("number") : _("string"));
                 }
-                if (vshCmddefOptParse(cmd, &opts_need_arg,
-                                      &opts_required) < 0) {
+                /* otherwise, we're here because the tokenizer couldn't
+                 * even start reading */
+                goto syntaxError;
+            } else if (line_state == VSH_LINE_STATE_LINE_DONE ||
+                        line_state == VSH_LINE_STATE_CMD_DONE) {
+                if (!opt) { /* we're actually at the end of the line */
+                    VIR_FREE(tkdata);
+                    break;
+                } else { /* we have an flag without an arg */
                     vshError(ctl,
-                             _("internal error: bad options in command: '%s'"),
-                             tkdata);
+                             _("expected syntax: --%s <%s>"),
+                             opt->name,
+                             opt->type ==
+                             VSH_OT_INT ? _("number") : _("string"));
                     goto syntaxError;
                 }
-                VIR_FREE(tkdata);
-            } else if (data_only) {
-                goto get_data;
-            } else if (tkdata[0] == '-' && tkdata[1] == '-' &&
-                       c_isalnum(tkdata[2])) {
-                char *optstr = strchr(tkdata + 2, '=');
-                int opt_index = 0;
-
-                if (optstr) {
-                    *optstr = '\0'; /* convert the '=' to '\0' */
-                    optstr = vshStrdup(ctl, optstr + 1);
-                }
-                /* Special case 'help' to ignore all spurious options */
-                if (!(opt = vshCmddefGetOption(ctl, cmd, tkdata + 2,
-                                               &opts_seen, &opt_index,
-                                               &optstr))) {
-                    VIR_FREE(optstr);
-                    if (STREQ(cmd->name, "help"))
-                        continue;
+            } else if (line_state == VSH_LINE_STATE_UNEXPECTED_DATA) {
+                if (STRNEQ(cmd->name, "help")) {
+                    /* anything's find after help, but
+                     * otherwise we got some unexpected
+                     * positional arguments */
+                    vshError(ctl, _("unexpected data '%s'"), tkdata);
                     goto syntaxError;
                 }
-                VIR_FREE(tkdata);
-
-                if (opt->type != VSH_OT_BOOL) {
-                    /* option data */
-                    if (optstr)
-                        tkdata = optstr;
-                    else
-                        tk = parser->getNextArg(ctl, parser, &tkdata);
-                    if (tk == VSH_TK_ERROR)
-                        goto syntaxError;
-                    if (tk != VSH_TK_ARG) {
-                        vshError(ctl,
-                                 _("expected syntax: --%s <%s>"),
-                                 opt->name,
-                                 opt->type ==
-                                 VSH_OT_INT ? _("number") : _("string"));
-                        goto syntaxError;
-                    }
-                    if (opt->type != VSH_OT_ARGV)
-                        opts_need_arg &= ~(1 << opt_index);
-                } else {
+            } else if (line_state == VSH_LINE_STATE_UNKNOWN_OPT) {
+                if (STREQ(cmd->name, "help"))
+                    continue;
+                goto syntaxError;
+            } else if (line_state == VSH_LINE_STATE_BAD_OPTS) {
+                vshError(ctl,
+                         _("internal error: bad options in command: '%s'"),
+                         tkdata);
+                goto syntaxError;
+            } else if (line_state == VSH_LINE_STATE_UNKNOWN_CMD) {
+                vshError(ctl, _("unknown command: '%s'"), tkdata);
+                goto syntaxError;   /* ... or ignore this command only? */
+            } else if (line_state == VSH_LINE_STATE_INVALID_EQUALS) {
+                vshError(ctl, _("invalid '=' after option --%s"),
+                         opt->name);
+                goto syntaxError;
+            } else if (line_state == VSH_LINE_STATE_IN_PROGRESS) {
+                if (opt) {
+                    /* save option */
+                    vshCmdOpt *arg = vshMalloc(ctl, sizeof(vshCmdOpt));
+
+                    arg->def = opt;
+                    arg->data = tkdata;
+                    arg->next = NULL;
                     tkdata = NULL;
-                    if (optstr) {
-                        vshError(ctl, _("invalid '=' after option --%s"),
-                                 opt->name);
-                        VIR_FREE(optstr);
-                        goto syntaxError;
-                    }
-                }
-            } else if (tkdata[0] == '-' && tkdata[1] == '-' &&
-                       tkdata[2] == '\0') {
-                data_only = true;
-                continue;
-            } else {
- get_data:
-                /* Special case 'help' to ignore spurious data */
-                if (!(opt = vshCmddefGetData(cmd, &opts_need_arg,
-                                             &opts_seen)) &&
-                     STRNEQ(cmd->name, "help")) {
-                    vshError(ctl, _("unexpected data '%s'"), tkdata);
-                    goto syntaxError;
+
+                    if (!first)
+                        first = arg;
+                    if (last)
+                        last->next = arg;
+                    last = arg;
+
+                    vshDebug(ctl, VSH_ERR_INFO, "%s: %s(%s): %s\n",
+                             cmd->name,
+                             opt->name,
+                             opt->type != VSH_OT_BOOL ? _("optdata") : _("bool"),
+                             opt->type != VSH_OT_BOOL ? arg->data : _("(none)"));
                 }
-            }
-            if (opt) {
-                /* save option */
-                vshCmdOpt *arg = vshMalloc(ctl, sizeof(vshCmdOpt));
-
-                arg->def = opt;
-                arg->data = tkdata;
-                arg->next = NULL;
-                tkdata = NULL;
-
-                if (!first)
-                    first = arg;
-                if (last)
-                    last->next = arg;
-                last = arg;
-
-                vshDebug(ctl, VSH_ERR_INFO, "%s: %s(%s): %s\n",
-                         cmd->name,
-                         opt->name,
-                         opt->type != VSH_OT_BOOL ? _("optdata") : _("bool"),
-                         opt->type != VSH_OT_BOOL ? arg->data : _("(none)"));
+                VIR_FREE(tkdata);
             }
         }
 
@@ -2064,9 +2171,11 @@ vshCommandParse(vshControl *ctl, vshCommandParser *parser)
             if (clast)
                 clast->next = c;
             clast = c;
+
+            cmd = NULL;
         }
 
-        if (tk == VSH_TK_END)
+        if (line_state == VSH_LINE_STATE_LINE_DONE)
             break;
     }
 
-- 
1.8.3.2




More information about the libvir-list mailing list