[libvirt] [PATCHv2 0/4] Another attempt at improving virsh autocompletion

Version 2: Electric Boogaloo Version 1: https://www.redhat.com/archives/libvir-list/2014-March/msg01898.html This version of the patch introduces the following new things: - Tests (a whole bunch of them, in fact)! - A new `complete` command to run get newline-separated completion results from the command line - Support for completing partial quotes (e.g. `virsh complete "fake-command ab \"i "`) - Passing the syntax checks (sorry about that) A brief overview of the patch set follows: 1. Extract parsing logic from the vshCommandParse so that it can be used elsewhere. The new method returns states and sets passed in pointers. Calling methods can interpret these states and deal with them as needed (completion ignores many, while vshCommandParse throws errors). 2. Implement (and test!) an improved completion engine with support for virsh quoting rules, flags, positional arguments, no duplication, and more. 3. Add (and test!) a method for retrieve a global vshControl object should readline be enabled. This allows for "smart completion" of options like "domain". 4. Extract the domain listing code from virsh-domain-monitor, and move it to virsh-completer. Implement a domain completer, which is then used for all the cases of "domain" options (note that it current does not have any flags specified for which commands should list active vs inactive domains, as this commit is mainly to allow people to test out "smart completion") Solly Ross (4): Improve virsh autocompletion (extract parser) Improve virsh autocompletion (base framework) Improve virsh autocompletion (global ctl object) Improve virsh autocompletion (domain completer) po/POTFILES.in | 1 + tests/virshtest.c | 268 +++++++++++++ tools/Makefile.am | 3 +- tools/virsh-completer.c | 355 +++++++++++++++++ tools/virsh-completer.h | 85 +++++ tools/virsh-domain-monitor.c | 287 +------------- tools/virsh-domain.c | 72 ++++ tools/virsh-snapshot.c | 11 + tools/virsh.c | 880 +++++++++++++++++++++++++++++++++++++------ tools/virsh.h | 20 + 10 files changed, 1582 insertions(+), 400 deletions(-) create mode 100644 tools/virsh-completer.c create mode 100644 tools/virsh-completer.h -- 1.8.3.2

This commit extracts the parsing logic from vshCommandParse so that it can be used by other methods. The vshCommandParse 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 vshCommandParse. Depending on what happens, instead of erroring or continuing the loop, it returns a value from an enum indicating the current state. It also modifies several arguments as appropriate. Then, a caller can choose to deal with the state, or may simply ignore the state when convenient (vshCommandParse deals with the state, so as to continue providing its previous functionality, while a completer could choose to ignore states involving unknown options, for example). --- tools/virsh.c | 338 +++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 228 insertions(+), 110 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index f2e4c4a..4f87c20 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -1110,7 +1110,8 @@ 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 +1139,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 +1150,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 +1162,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); @@ -1883,7 +1887,7 @@ typedef enum { typedef struct _vshCommandParser vshCommandParser; struct _vshCommandParser { vshCommandToken(*getNextArg)(vshControl *, vshCommandParser *, - char **); + char **, bool); /* vshCommandStringGetArg() */ char *pos; /* vshCommandArgvGetArg() */ @@ -1891,12 +1895,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, raise_err); + + 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, raise_err); + + 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 +2044,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 +2055,85 @@ 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 +2174,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; } @@ -2089,7 +2201,8 @@ vshCommandParse(vshControl *ctl, vshCommandParser *parser) */ static vshCommandToken ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) -vshCommandArgvGetArg(vshControl *ctl, vshCommandParser *parser, char **res) +vshCommandArgvGetArg(vshControl *ctl, vshCommandParser *parser, + char **res, bool raise_err ATTRIBUTE_UNUSED) { if (parser->arg_pos == parser->arg_end) { *res = NULL; @@ -2121,7 +2234,8 @@ vshCommandArgvParse(vshControl *ctl, int nargs, char **argv) */ static vshCommandToken ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) -vshCommandStringGetArg(vshControl *ctl, vshCommandParser *parser, char **res) +vshCommandStringGetArg(vshControl *ctl, vshCommandParser *parser, + char **res, bool raise_err) { bool single_quote = false; bool double_quote = false; @@ -2158,7 +2272,9 @@ vshCommandStringGetArg(vshControl *ctl, vshCommandParser *parser, char **res) */ p++; if (*p == '\0') { - vshError(ctl, "%s", _("dangling \\")); + if (raise_err) + vshError(ctl, "%s", _("dangling \\")); + return VSH_TK_ERROR; } } else if (!single_quote && *p == '"') { /* double quote */ @@ -2171,7 +2287,9 @@ vshCommandStringGetArg(vshControl *ctl, vshCommandParser *parser, char **res) sz++; } if (double_quote) { - vshError(ctl, "%s", _("missing \"")); + if (raise_err) + vshError(ctl, "%s", _("missing \"")); + return VSH_TK_ERROR; } -- 1.8.3.2

On 04/01/2014 07:34 PM, Solly Ross wrote: I finally took time to look at this patch again. Sorry for the extreme delay. Overall, this patch looks decent; I'll probably touch up the findings I make below and apply it if the rest of the series is okay.
This commit extracts the parsing logic from vshCommandParse so that it can be used by other methods. The vshCommandParse 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 vshCommandParse. Depending on what happens, instead of erroring or continuing the loop, it returns a value from an enum indicating the current state. It also modifies several arguments as appropriate.
Then, a caller can choose to deal with the state, or may simply ignore the state when convenient (vshCommandParse deals with the state, so as to continue providing its previous functionality, while a completer could choose to ignore states involving unknown options, for example). --- tools/virsh.c | 338 +++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 228 insertions(+), 110 deletions(-)
+ VSH_LINE_STATE_CMD_DONE, + VSH_LINE_STATE_LINE_DONE, +} vshLineExtractionState; + +static vshLineExtractionState
These days, we usually have two lines between functions.
+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;
Static variables are 0-initialized by default; they also make this non-thread-safe (although I'm not sure that it matters, since our parser is single-threaded). If you made them static in order to preserve values between successive callers, that makes me wonder if they should have been parameters instead.
+ 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, raise_err); + + if (tok_type == VSH_TK_ERROR) { + ret = VSH_LINE_STATE_TOK_ERR; + *opt = NULL;
Redundant assignment.
+ goto cleanup; + } else if (tok_type == VSH_TK_END) { + ret = VSH_LINE_STATE_LINE_DONE; + *opt = NULL;
and again
+ goto cleanup; + } else if (tok_type == VSH_TK_SUBCMD_END) { + *opt = NULL;
and again
+ //*cmd = NULL;
Why is this commented out?
+ 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 */
This comment appears like it no longer applies.
+ *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, raise_err); + + 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 +2044,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 +2055,85 @@ 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++;
I'm not quite sure what values state should have. Instead of feeding back an int each time, would it be better to pass an enum value from the previous call, where the initial call is the value that indicates start of line? Then you don't need a static variable in the helper function, but instead use the fact that the previous indication returned VSH_LINE_STATE_DATA_ONLY as the state argument for the next iteration to look for a data argument, for example.
- 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) {
Indentation is off.
+ if (!opt) { /* we're actually at the end of the line */ + VIR_FREE(tkdata); + break; + } else { /* we have an flag without an arg */
s/an flag/a flag/
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
s/find/fine/
+ * otherwise we got some unexpected + * positional arguments */ + vshError(ctl, _("unexpected data '%s'"), tkdata); goto syntaxError;
... -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org

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. This commit also introduces a helper macro called VSH_STRING_COMPLETER to aid in the writing of completers which have a fixed set of strings. A call of `VSH_STRING_COMPLETER(ctl, SomeOption, "string1", "string2")` will generate a function `vshCompleteSomeOption`, suitable for use as a custom completer. The macro may accept up to 63 different strings (a limitation of C VA_ARGS macros). Additionally, definition of vshStrndup (which calls virStrndup) was introduced, following the vsh-prefixed memory-related functions. Furthermore, two new commands have been introduced: - 'complete' takes either a quoted partial line, or and unquoted partial line following a '--'. It returns a newline-separated list of potential completions as expected by readline. It is useful for writing commandline completers (bash completion, etc) and for testing. - 'fake-command' takes a series of flags and data arguments, and prints those arguments which are present. It is used to test the completion in cases not possible to cover with 'echo'. Finally, a suite a tests have been introduced. --- tests/virshtest.c | 255 ++++++++++++++++++++++++ tools/Makefile.am | 3 +- tools/virsh-completer.c | 49 +++++ tools/virsh-completer.h | 73 +++++++ tools/virsh.c | 502 ++++++++++++++++++++++++++++++++++++++++++++++-- tools/virsh.h | 12 ++ 6 files changed, 878 insertions(+), 16 deletions(-) create mode 100644 tools/virsh-completer.c create mode 100644 tools/virsh-completer.h diff --git a/tests/virshtest.c b/tests/virshtest.c index 3fdae92..0c698fa 100644 --- a/tests/virshtest.c +++ b/tests/virshtest.c @@ -153,6 +153,189 @@ Memory size: 8192000 KiB\n\ return testCompareOutputLit(exp, NULL, argv); } +# if WITH_READLINE +/* completion tests */ + +static int testPartialCommandCompletionSing(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", "li", NULL }; + const char *exp = "list\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testPartialCommandCompletionMult(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", "l", NULL }; + const char *exp = "l\nlxc-enter-namespace\nlist\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testPartialBoolFlagCompletion(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "list --n", NULL }; + const char *exp = "--n\n--no-autostart\n--name\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testBlankBoolFlagCompletion(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "list --", NULL }; + const char *exp = "--\n--inactive\n--all\n--transient\n--persistent\n\ +--with-snapshot\n--without-snapshot\n--state-running\n\ +--state-paused\n--state-shutoff\n--state-other\n\ +--autostart\n--no-autostart\n--with-managed-save\n\ +--without-managed-save\n--uuid\n--name\n--table\n\ +--managed-save\n--title\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testNoCompleterDoesntFileComplete(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "attach-disk --serial ", NULL }; + const char *exp = "\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testFileCompletion(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "fake-command --file ", + NULL }; + char *actual = NULL; + int res = -1; + + /* this may fail if you have a lot of files in the current + * directory. 4096 was not enough, but 4096^2 was on my system */ + if (virtTestCaptureProgramOutput(argv, &actual, 4096*4096) < 0) + goto cleanup; + + /* just check if we return something here */ + if (actual && strlen(actual)) + res = 0; + + cleanup: + VIR_FREE(actual); + return res; +} + +static int testUsedFlagIsntCompletedAgain(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "fake-command --string1 ab --str", NULL }; + const char *exp = "--string\n--string2\n--string3\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testMultStrArgsCompletion(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "fake-command --string1 ", NULL }; + const char *exp = "value\nvalue1\nvalue2\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testMultDataArgsCompletion(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "fake-command ab ", NULL }; + const char *exp = "\n--abool\n\"i e f\"\n\"i g h\"\n--string1\n--string2\ +\n--string3\n--file\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testDataSpaceComplAreQuoted(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "fake-command ab ", NULL }; + const char *exp = "\n--abool\n\"i e f\"\n\"i g h\"\n--string1\n--string2\ +\n--string3\n--file\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testStrSpaceComplAreQuoted(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "fake-command --string2 ", NULL }; + const char *exp = "\"value \n\"value a\"\n\"value b\"\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +/* Note: partial completion is a bit funky because readline treats spaces as + * "new token", so we only need to complete part of the word. Also, it ignores + * quotes, so if we already have a quote at the beginning of a token, our + * completion shouldn't have a quote */ +static int testPartialStrQuoteCompletion(const void *data ATTRIBUTE_UNUSED) +{ + int res = 0; + const char *const argv1[] = { VIRSH_CUSTOM, "complete", + "fake-command --string2 \"value ", NULL }; + const char *exp1 = "\na\"\nb\"\n\n"; + const char *const argv2[] = { VIRSH_CUSTOM, "complete", + "fake-command --string2 \"va", NULL }; + const char *exp2 = "value \nvalue a\"\nvalue b\"\n\n"; + + res = testCompareOutputLit(exp1, NULL, argv1); + res += testCompareOutputLit(exp2, NULL, argv2); + + return res; +} + +static int testPartialDataQuoteCompletion(const void *data ATTRIBUTE_UNUSED) +{ + int res = 0; + const char *const argv1[] = { VIRSH_CUSTOM, "complete", + "fake-command ab \"i ", NULL }; + const char *exp1 = "\ne f\"\ng h\"\n\n"; + const char *const argv2[] = { VIRSH_CUSTOM, "complete", + "fake-command ab \"i e", NULL }; + const char *exp2 = "e f\"\n\n"; + + res = testCompareOutputLit(exp1, NULL, argv1); + res += testCompareOutputLit(exp2, NULL, argv2); + + return res; +} + +static int testArgvCompletesRepeatedly(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "echo hi ", NULL }; + + const char *exp = "\n--shell\n--xml\n--str\n--hi\n\ +hello\nbonjour\nshalom\n#!\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testRepeatedCompletionRequests(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, + "complete 'l'; complete 'li'", NULL }; + const char *exp = "l\nlxc-enter-namespace\nlist\n\nlist\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testCompletionIgnoresSubcmd(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "list --all; echo h", NULL }; + const char *exp = "hello\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +static int testCompletionInArgvMode(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "--", "echo", "--shell", "a", "", NULL }; + const char *exp = "\n--xml\n--str\n--hi\nhello\nbonjour\nshalom\n#!\n\n"; + return testCompareOutputLit(exp, NULL, argv); +} + +# endif /* WITH_READLINE */ + + static int testCompareDominfoByID(const void *data ATTRIBUTE_UNUSED) { const char *const argv[] = { VIRSH_CUSTOM, "dominfo", "2", NULL }; @@ -322,6 +505,78 @@ mymain(void) testCompareDomstateByName, NULL) != 0) ret = -1; +# if WITH_READLINE + /* test completion */ + if (virtTestRun("virsh completion (command with only one result)", + testPartialCommandCompletionSing, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (command with multiple results)", + testPartialCommandCompletionMult, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (boolean flag with parital name)", + testPartialBoolFlagCompletion, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (boolean flag with only --)", + testBlankBoolFlagCompletion, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (null completer)", + testNoCompleterDoesntFileComplete, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (file completion)", + testFileCompletion, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (no reusing flags)", + testUsedFlagIsntCompletedAgain, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (multiple string flags)", + testMultStrArgsCompletion, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (multiple data flags)", + testMultDataArgsCompletion, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (repeated argv)", + testArgvCompletesRepeatedly, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (spaces in data completions)", + testDataSpaceComplAreQuoted, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (spaces in flag completions)", + testStrSpaceComplAreQuoted, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (partial quoted data)", + testPartialDataQuoteCompletion, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (partial quoted flag args)", + testPartialStrQuoteCompletion, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (repeated completion requests)", + testRepeatedCompletionRequests, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (completion ignores previous subcmd)", + testCompletionIgnoresSubcmd, NULL) != 0) + ret = -1; + + if (virtTestRun("virsh completion (completion command in argv mode)", + testCompletionInArgvMode, NULL) != 0) + ret = -1; + +# endif /* WITH_READLINE */ + /* It's a bit awkward listing result before argument, but that's a * limitation of C99 vararg macros. */ # define DO_TEST(i, result, ...) \ 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..3e6c8f8 --- /dev/null +++ b/tools/virsh-completer.c @@ -0,0 +1,49 @@ +/* + * virsh-completer.c: 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 <stdarg.h> + +#include "conf/domain_conf.h" +#include "viralloc.h" + +/* Utils - General */ +char ** +vshVarArgsToStringList(vshControl *ctl, unsigned int count, ...) +{ + va_list ap; + char **strs; + size_t i; + + if (count == 0) + return NULL; + + va_start(ap, count); + + strs = vshCalloc(ctl, count, sizeof(char*)); + for (i = 0; i < count; i++) + strs[i] = vshStrdup(ctl, va_arg(ap, char*)); + + va_end(ap); + + return strs; +}; diff --git a/tools/virsh-completer.h b/tools/virsh-completer.h new file mode 100644 index 0000000..a565c5d --- /dev/null +++ b/tools/virsh-completer.h @@ -0,0 +1,73 @@ +/* + * 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" + +/* 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) \ + { \ + virCheckFlags(0, NULL); \ + return vshVarArgsToStringList(ctl, __VA_NARGS__(__VA_ARGS__), \ + __VA_ARGS__); \ + } + +char ** vshVarArgsToStringList(vshControl *ctl, unsigned int count, ...); + +#endif /* VIRSH_COMPLETER_H */ diff --git a/tools/virsh.c b/tools/virsh.c index 4f87c20..808a125 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -85,6 +85,7 @@ #include "virsh-secret.h" #include "virsh-snapshot.h" #include "virsh-volume.h" +#include "virsh-completer.h" /* Gnulib doesn't guarantee SA_SIGINFO support. */ #ifndef SA_SIGINFO @@ -139,6 +140,19 @@ _vshStrdup(vshControl *ctl, const char *s, const char *filename, int line) /* Poison the raw allocating identifiers in favor of our vsh variants. */ #define strdup use_vshStrdup_instead_of_strdup +char *_vshStrncpy(vshControl *ctl, char *dest, const char *src, size_t n, + size_t destsize, const char *filename, int line) +{ + char *res = virStrncpy(dest, src, n, destsize); + + if (res) + return res; + + vshError(ctl, _("%s: %d: failed to strncpy %zu characters"), + filename, line, n); + exit(EXIT_FAILURE); +} + int vshNameSorter(const void *a, const void *b) { @@ -916,6 +930,9 @@ static const vshCmdInfo info_echo[] = { {.name = NULL} }; +/* say hello in English, French, Hebrew, and Shell ;-) */ +VSH_STRING_COMPLETER(NULL, EchoString, "hello", "bonjour", "shalom", "#!"); + static const vshCmdOptDef opts_echo[] = { {.name = "shell", .type = VSH_OT_BOOL, @@ -935,6 +952,7 @@ static const vshCmdOptDef opts_echo[] = { }, {.name = "string", .type = VSH_OT_ARGV, + .completer = vshCompleteEchoString, .help = N_("arguments to echo") }, {.name = NULL} @@ -998,6 +1016,80 @@ cmdEcho(vshControl *ctl, const vshCmd *cmd) } /* + * "fake-command" command + */ +static const vshCmdInfo info_fake_command[] = { + {.name = "help", + .data = N_("a fake, no-op command") + }, + {.name = "desc", + .data = N_("Do absolutely nothing! Used for testing completion") + }, + {.name = NULL} +}; + +VSH_STRING_COMPLETER(NULL, FakeCommandStr1, "value1", "value2"); +VSH_STRING_COMPLETER(NULL, FakeCommandStr2, "value a", "value b"); +VSH_STRING_COMPLETER(NULL, FakeCommandData1, "ab", "cd"); +VSH_STRING_COMPLETER(NULL, FakeCommandData2, "i e f", "i g h"); + +static const vshCmdOptDef opts_fake_command[] = { + {.name = "abool", + .type = VSH_OT_BOOL, + .help = N_("a boolean flag") + }, + {.name = "data1", + .type = VSH_OT_DATA, + .completer = vshCompleteFakeCommandData1, + .help = N_("some data") + }, + {.name = "data2", + .type = VSH_OT_DATA, + .completer = vshCompleteFakeCommandData2, + .help = N_("some more data") + }, + {.name = "string1", + .type = VSH_OT_STRING, + .completer = vshCompleteFakeCommandStr1, + .help = N_("a string") + }, + {.name = "string2", + .type = VSH_OT_STRING, + .completer = vshCompleteFakeCommandStr2, + .help = N_("another string") + }, + {.name = "string3", + .type = VSH_OT_STRING, + .help = N_("another string") + }, + {.name = "file", + .type = VSH_OT_STRING, + .completer_flags = VSH_COMPLETE_AS_FILE, + .help = N_("a string") + }, + {.name = NULL} +}; + +/* Used for testing completion + */ +static bool +cmdFakeCommand(vshControl *ctl ATTRIBUTE_UNUSED, + const vshCmd *cmd ATTRIBUTE_UNUSED) +{ + vshCmdOpt *opt = cmd->opts; + if (opt) { + do { + if (opt->data) + vshPrint(NULL, "%s: %s\n", opt->def->name, opt->data); + else + vshPrint(NULL, "%s: true\n", opt->def->name); + } while ((opt = opt->next)); + } + + return true; +} + +/* * "quit" command */ static const vshCmdInfo info_quit[] = { @@ -1931,7 +2023,9 @@ vshExtractLinePart(vshControl *ctl, vshCommandParser *parser, if (tok_type == VSH_TK_ERROR) { ret = VSH_LINE_STATE_TOK_ERR; - *opt = NULL; + *tok_out = vshStrdup(ctl, tok); + /* attempt to determine the option anyway... */ + *opt = vshCmddefGetData(*cmd, opts_need_arg, opts_seen); goto cleanup; } else if (tok_type == VSH_TK_END) { ret = VSH_LINE_STATE_LINE_DONE; @@ -2274,6 +2368,8 @@ vshCommandStringGetArg(vshControl *ctl, vshCommandParser *parser, if (*p == '\0') { if (raise_err) vshError(ctl, "%s", _("dangling \\")); + else + *(*res + sz) = '\0'; return VSH_TK_ERROR; } @@ -2289,6 +2385,8 @@ vshCommandStringGetArg(vshControl *ctl, vshCommandParser *parser, if (double_quote) { if (raise_err) vshError(ctl, "%s", _("missing \"")); + else + *(*res + sz) = '\0'; return VSH_TK_ERROR; } @@ -3014,27 +3112,160 @@ 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; + static bool continue_from_error = false; + static char *last_tok = NULL; + static bool data_only = false; + vshCommandParser parser; if (!state) { - /* determine command name */ - char *p; - char *cmdname; + char *tok = NULL; + vshLineExtractionState line_state; + int data_only_track = 0; - 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; + VIR_FREE(last_tok); + continue_from_error = false; + data_only = false; - cmdname = vshCalloc(NULL, (p - rl_line_buffer) + 1, 1); - memcpy(cmdname, rl_line_buffer, p - rl_line_buffer); + /* reset the parser */ + memset(&parser, 0, sizeof(vshCommandParser)); + parser.pos = rl_line_buffer; + parser.getNextArg = vshCommandStringGetArg; - cmd = vshCmddefSearch(cmdname); - list_index = 0; - len = strlen(text); - VIR_FREE(cmdname); + 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; + } + + + if (line_state == VSH_LINE_STATE_CMD_DONE) { + cmd = NULL; + data_only_track = 0; + } else if (line_state == VSH_LINE_STATE_DATA_ONLY) { + data_only_track++; + } else if (data_only_track) { + data_only_track++; + } + + VIR_FREE(tok); + + line_state = vshExtractLinePart(NULL, &parser, &opts_seen, + &opts_need_arg, &tok, &cmd, + &curr_opt, false, 1); + } + + if (data_only_track && (data_only_track > 1 || len == 0)) + data_only = true; + + if (line_state == VSH_LINE_STATE_TOK_ERR) { + /* we're here either because of a dangling + * backslash or a missing quote */ + continue_from_error = true; + last_tok = tok; + } else { + VIR_FREE(tok); + } + + if (last_arg_opt && len > 0) { + if (last_arg_opt->type != VSH_OT_DATA && + last_arg_opt->type != VSH_OT_ARGV) { + if (text[0] == '-' && !text[1]) { + /* this ensures that completion on '-' works properly */ + int opt_ind = -1; + for (opt_ind = 0; cmd->opts[opt_ind].name; opt_ind++) { + if (last_arg_opt == &cmd->opts[opt_ind]) + break; + } + opts_seen &= ~(1 << opt_ind); + opts_need_arg &= ~(1 << opt_ind); + last_arg_opt = NULL; + } else { + 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) @@ -3043,14 +3274,146 @@ vshReadlineOptionsGenerator(const char *text, int state) if (!cmd->opts) return NULL; + if (waiting_for_flag_arg) { + char* res; + if (continue_from_error) + res = vshDelegateToCustomCompleter(curr_opt, last_tok, substate); + else + res = vshDelegateToCustomCompleter(curr_opt, text, substate); + + substate++; + /* if we're in a flag's argument, we don't + * want to show other flags */ + + if (res && strchr(res, ' ')) { + /* quote matches with spaces */ + char *orig = res; + int orig_len = strlen(orig); + res = vshMalloc(NULL, orig_len + 3); + snprintf(res, orig_len + 3, "\"%s\"", orig); + VIR_FREE(orig); + } + + if (res && continue_from_error) { + char *orig = res; + int orig_len = strlen(orig); + if (strchr(last_tok, ' ')) { + int part_len = strlen(last_tok); + char *start_pos = orig + part_len; + int new_len; + + if (len == 0) + start_pos++; + + new_len = strlen(start_pos); + + res = vshMalloc(NULL, orig_len - part_len + 1); + vshStrncpy(NULL, res, start_pos, new_len, new_len + 1); + } else if (res[0] == '"') { + /* if we don't have a space, that we're actually + * completing normally so far -- we just need to + * remove the initial quote */ + res = vshMalloc(NULL, orig_len); + vshStrncpy(NULL, res, orig + 1, orig_len - 1, orig_len); + } + VIR_FREE(orig); + } + + 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 */ + if (continue_from_error) + res = vshDelegateToCustomCompleter(opt, last_tok, substate); + else + res = vshDelegateToCustomCompleter(opt, text, substate); + substate++; + if (res) { + if (strchr(res, ' ')) { + /* quote matches with spaces */ + char *orig = res; + int orig_len = strlen(orig); + res = vshMalloc(NULL, orig_len + 3); + snprintf(res, orig_len + 3, "\"%s\"", orig); + VIR_FREE(orig); + } + + if (continue_from_error) { + char *orig = res; + int orig_len = strlen(orig); + if (strchr(last_tok, ' ')) { + int part_len = strlen(last_tok); + char *start_pos = orig + part_len; + int new_len; + + if (len == 0) + start_pos++; + + new_len = strlen(start_pos); + + res = vshMalloc(NULL, orig_len - part_len + 1); + vshStrncpy(NULL, res, start_pos, new_len, new_len + 1); + } else if (res[0] == '"') { + /* if we don't have a space, that we're actually + * completing normally so far -- we just need to + * remove the initial quote */ + res = vshMalloc(NULL, orig_len); + vshStrncpy(NULL, res, orig + 1, orig_len - 1, orig_len); + } + VIR_FREE(orig); + } + + 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 || continue_from_error) && text[0] != '-') + return NULL; + else { + list_index++; + continue; + } + } + } + list_index++; - if (opt->type == VSH_OT_DATA || opt->type == VSH_OT_ARGV) - /* ignore non --option */ + /* don't complete flags if we're past a -- */ + if (data_only) + continue; + + /* don't complete flags if we're in the middle of + * completing a quoted data string */ + if (continue_from_error) + continue; + + /* 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) { @@ -3078,6 +3441,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; } @@ -3096,7 +3463,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 */ @@ -3165,6 +3533,96 @@ vshReadline(vshControl *ctl ATTRIBUTE_UNUSED, const char *prompt) return readline(prompt); } + +/* + * "complete" command + */ +static const vshCmdInfo info_complete[] = { + {.name = "help", + .data = N_("complete the given command") + }, + {.name = "desc", + .data = N_("Complete the given input as if " + "readline was doing tab-completion " + "in the interactive shell") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_complete[] = { + {.name = "input", + .type = VSH_OT_DATA, + .flags = VSH_OFLAG_REQ, + .help = N_("pass in the line to be completed in quotes"), + }, + {.name = "rest", + .type = VSH_OT_ARGV, + .flags = VSH_OFLAG_REQ, + .help = N_("pass in the line to be completed following " + " a '--' without quotes") + }, + {.name = NULL} +}; + +static bool +cmdComplete(vshControl *ctl, const vshCmd *cmd) +{ + const char *input; + const char *full_line; + const vshCmdOpt *opt = NULL; + virBuffer input_buff = VIR_BUFFER_INITIALIZER; + int line_len; + const char *text; + char **matches; + char *match; + + if (vshCommandOptStringReq(ctl, cmd, "input", &input) < 0) + return false; + + virBufferAdd(&input_buff, input, -1); + + while ((opt = vshCommandOptArgv(cmd, opt))) { + virBufferAddLit(&input_buff, " "); + if (opt->data[0]) + virBufferAdd(&input_buff, opt->data, -1); + } + + if (virBufferError(&input_buff)) { + vshPrint(ctl, "%s", _("Failed to allocate XML buffer")); + return false; + } + + full_line = virBufferContentAndReset(&input_buff); + line_len = strlen(full_line); + + /* intialize the readline line buffer */ + rl_extend_line_buffer(line_len); + rl_line_buffer = vshStrdup(ctl, full_line); + + text = strrchr(rl_line_buffer, ' '); + if (!text) + text = rl_line_buffer; + else + text += 1; // skip the space + + matches = vshReadlineCompletion(text, (int) (text - rl_line_buffer), + line_len); + + if (matches) { + size_t i; + for (i = 0; (match = matches[i]); i++) { + vshPrint(ctl, "%s\n", match); + VIR_FREE(match); /* readline normally frees matches itself */ + } + + VIR_FREE(matches); + } + + VIR_FREE(full_line); + return true; +} + + #else /* !WITH_READLINE */ static int @@ -3603,12 +4061,26 @@ static const vshCmdDef virshCmds[] = { .info = info_cd, .flags = VSH_CMD_FLAG_NOCONNECT }, +#if WITH_READLINE + {.name = "complete", + .handler = cmdComplete, + .opts = opts_complete, + .info = info_complete, + .flags = VSH_CMD_FLAG_NOCONNECT, + }, +#endif /* WITH_READLINE */ {.name = "connect", .handler = cmdConnect, .opts = opts_connect, .info = info_connect, .flags = VSH_CMD_FLAG_NOCONNECT }, + {.name = "fake-command", + .handler = cmdFakeCommand, + .opts = opts_fake_command, + .info = info_fake_command, + .flags = VSH_CMD_FLAG_NOCONNECT + }, {.name = "echo", .handler = cmdEcho, .opts = opts_echo, diff --git a/tools/virsh.h b/tools/virsh.h index 3e0251b..227e4e8 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 * @@ -411,6 +416,13 @@ char *_vshStrdup(vshControl *ctl, const char *s, const char *filename, # define realloc use_vshRealloc_instead_of_realloc # define strdup use_vshStrdup_instead_of_strdup +# define vshStrncpy(_ctl, _dest, _src, _n, _destsize) \ + _vshStrncpy(_ctl, _dest, _src, _n, _destsize, __FILE__, __LINE__) + +char *_vshStrncpy(vshControl *ctl, char *dest, const char *src, size_t n, + size_t destsize, const char *filename, int line); + + /* Macros to help dealing with mutually exclusive options. */ /* VSH_EXCLUSIVE_OPTIONS_EXPR: -- 1.8.3.2

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. --- tests/virshtest.c | 13 +++++++++++++ tools/virsh.c | 52 +++++++++++++++++++++++++++++++++++++++++++++++----- tools/virsh.h | 8 ++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) diff --git a/tests/virshtest.c b/tests/virshtest.c index 0c698fa..d068e5e 100644 --- a/tests/virshtest.c +++ b/tests/virshtest.c @@ -333,6 +333,15 @@ static int testCompletionInArgvMode(const void *data ATTRIBUTE_UNUSED) return testCompareOutputLit(exp, NULL, argv); } +static int testCompletionRequiringCtl(const void *data ATTRIBUTE_UNUSED) +{ + const char *const argv[] = { VIRSH_CUSTOM, "complete", + "fake-command --string3 ", NULL }; + const char *exp = ("test:///home/directxman12/dev/libvirt" + "/tests/../examples/xml/test/testnode.xml\n\n"); + return testCompareOutputLit(exp, NULL, argv); +} + # endif /* WITH_READLINE */ @@ -575,6 +584,10 @@ mymain(void) testCompletionInArgvMode, NULL) != 0) ret = -1; + if (virtTestRun("virsh completion (completer requiring control object)", + testCompletionRequiringCtl, NULL) != 0) + ret = -1; + # endif /* WITH_READLINE */ /* It's a bit awkward listing result before argument, but that's a diff --git a/tools/virsh.c b/tools/virsh.c index 808a125..71076dc 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -96,6 +96,16 @@ 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 @@ -1033,6 +1043,21 @@ VSH_STRING_COMPLETER(NULL, FakeCommandStr2, "value a", "value b"); VSH_STRING_COMPLETER(NULL, FakeCommandData1, "ab", "cd"); VSH_STRING_COMPLETER(NULL, FakeCommandData2, "i e f", "i g h"); +static char ** +vshCompleteFakeCommandStr3(unsigned int flags) +{ + virCheckFlags(0, NULL); + + vshControl *ctl = vshGetCompleterCtl(); + char *uri = virConnectGetURI(ctl->conn); + char **res = vshCalloc(NULL, 2, sizeof(char*)); + + res[0] = uri; + res[1] = NULL; + + return res; +} + static const vshCmdOptDef opts_fake_command[] = { {.name = "abool", .type = VSH_OT_BOOL, @@ -1060,6 +1085,7 @@ static const vshCmdOptDef opts_fake_command[] = { }, {.name = "string3", .type = VSH_OT_STRING, + .completer = vshCompleteFakeCommandStr3, .help = N_("another string") }, {.name = "file", @@ -3112,7 +3138,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; @@ -3125,6 +3151,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); } @@ -3276,10 +3306,13 @@ vshReadlineOptionsGenerator(const char *text, int state) if (waiting_for_flag_arg) { char* res; + bool may_connect = !(cmd->flags & VSH_CMD_FLAG_NOCONNECT); if (continue_from_error) - res = vshDelegateToCustomCompleter(curr_opt, last_tok, substate); + res = vshDelegateToCustomCompleter(curr_opt, may_connect, + last_tok, substate); else - res = vshDelegateToCustomCompleter(curr_opt, text, substate); + res = vshDelegateToCustomCompleter(curr_opt, may_connect, + text, substate); substate++; /* if we're in a flag's argument, we don't @@ -3343,10 +3376,14 @@ vshReadlineOptionsGenerator(const char *text, int state) /* we don't need to ignore args without custom completers, * since vshDelegateToCustomCompleter will do this for us */ + bool may_connect = !(cmd->flags & VSH_CMD_FLAG_NOCONNECT); if (continue_from_error) - res = vshDelegateToCustomCompleter(opt, last_tok, substate); + res = vshDelegateToCustomCompleter(opt, may_connect, + last_tok, substate); else - res = vshDelegateToCustomCompleter(opt, text, substate); + res = vshDelegateToCustomCompleter(opt, may_connect, + text, substate); + substate++; if (res) { if (strchr(res, ' ')) { @@ -4134,9 +4171,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 227e4e8..38f3663 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

This patch introduces a custom completer for domains, and sets it to be used for all of the "domain" arguments. In order to facilitate this, the functions involved in retrieve in a list of domains were moved from virsh-domain-monitor to virsh-completer, where they can be included by any file that needs to use them. --- po/POTFILES.in | 1 + tools/virsh-completer.c | 306 +++++++++++++++++++++++++++++++++++++++++++ tools/virsh-completer.h | 12 ++ tools/virsh-domain-monitor.c | 287 ++-------------------------------------- tools/virsh-domain.c | 72 ++++++++++ tools/virsh-snapshot.c | 11 ++ 6 files changed, 414 insertions(+), 275 deletions(-) diff --git a/po/POTFILES.in b/po/POTFILES.in index 5a4112a..13f4041 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -226,6 +226,7 @@ src/xenxs/xen_xm.c tools/libvirt-guests.sh.in tools/virsh.c tools/virsh.h +tools/virsh-completer.c tools/virsh-console.c tools/virsh-domain-monitor.c tools/virsh-domain.c diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index 3e6c8f8..43afab7 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -47,3 +47,309 @@ vshVarArgsToStringList(vshControl *ctl, unsigned int count, ...) return strs; }; + +/* 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; + size_t i; + + 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 (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 index a565c5d..06fa98e 100644 --- a/tools/virsh-completer.h +++ b/tools/virsh-completer.h @@ -70,4 +70,16 @@ char ** vshVarArgsToStringList(vshControl *ctl, unsigned int count, ...); +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 18d551a..50cb920 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 d9aa4fa..ce17fa4 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") @@ -4640,6 +4665,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") @@ -4788,6 +4814,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") @@ -4831,6 +4858,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") @@ -4917,6 +4945,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") @@ -4998,6 +5027,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") @@ -5041,6 +5071,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") @@ -5263,6 +5294,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") @@ -5301,6 +5333,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") @@ -5512,6 +5545,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") @@ -5621,6 +5655,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") @@ -5908,6 +5943,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") @@ -6027,6 +6063,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") @@ -6348,6 +6385,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") @@ -6682,6 +6720,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") @@ -6739,6 +6778,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") @@ -6911,6 +6951,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") @@ -7068,6 +7109,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") @@ -7106,6 +7148,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") @@ -7208,6 +7251,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") @@ -7323,6 +7367,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") @@ -7420,6 +7465,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") @@ -7522,6 +7568,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") @@ -7721,6 +7768,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") @@ -7868,6 +7916,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") @@ -8160,6 +8209,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") @@ -8289,6 +8339,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") @@ -8431,6 +8482,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") @@ -8617,6 +8669,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") @@ -8653,6 +8706,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") @@ -8694,6 +8748,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") @@ -8803,6 +8858,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") @@ -9073,6 +9129,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") @@ -9127,6 +9184,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") @@ -9189,6 +9247,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") @@ -9241,6 +9300,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") @@ -9285,6 +9345,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") @@ -9472,6 +9533,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") @@ -9545,6 +9607,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") @@ -9605,6 +9668,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") @@ -9768,6 +9832,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") @@ -9869,6 +9934,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") @@ -9971,6 +10037,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") @@ -10341,6 +10408,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") @@ -10456,6 +10524,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") @@ -11041,6 +11110,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") }, @@ -11193,6 +11263,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") @@ -11344,6 +11415,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 3ecb3de..97eb264 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

On 04/01/2014 07:34 PM, Solly Ross wrote:
Version 2: Electric Boogaloo Version 1: https://www.redhat.com/archives/libvir-list/2014-March/msg01898.html
This version of the patch introduces the following new things:
- Tests (a whole bunch of them, in fact)!
Apologies for letting this one fall off my radar; does it still apply to the latest libvirt.git, or does it need rebasing?
- A new `complete` command to run get newline-separated completion results from the command line
- Support for completing partial quotes (e.g. `virsh complete "fake-command ab \"i "`)
I'd love to get this in for 1.2.6, now that 1.2.5 is out the door. -- Eric Blake eblake redhat com +1-919-301-3266 Libvirt virtualization library http://libvirt.org
participants (2)
-
Eric Blake
-
Solly Ross