
On 09/09/2010 09:07 PM, Daniel P. Berrange wrote:
On Thu, Sep 09, 2010 at 08:52:13AM -0400, Chris Lalancette wrote:
On 09/09/10 - 04:52:25PM, Lai Jiangshan wrote:
On 09/07/2010 09:22 PM, Chris Lalancette wrote:
On 09/07/10 - 04:08:13PM, Lai Jiangshan wrote:
Hi, Chris,
I saw virDomainQemuMonitorCommand() in libvirt-qemu.c, I think it will help me to send arbitrary qemu-monitor command to qemu via libvirtd.
But how can I use virDomainQemuMonitorCommand()? Can I use it by just using current tools(virsh or other) without writing any code?
Unfortunately, no. There is a bug in the current virsh command that prevents it from properly parsing the command-lines necessary to send monitor commands to the qemu monitor. Until we fix that bug, we won't push the support into virsh.
Thanks,
We need this feature, could you tell me the detail of the bug, we will try to fix it or do assists.
I've outlined it before on this list, but the gist of it is that the way that virsh parses command-line arguments loses the formatting. Thus, if you were enter a command like:
# virsh qemu-monitor-command f13guest "info cpus"
Then virsh main() will get 4 arguments:
argv[0] = "virsh"; argv[1] = "qemu-monitor-command"; argv[2] = "f13guest"; argv[3] = "info cpus";
So far, all is good. However, during the parsing of these command-line arguments, virsh takes all of these arguments and smashes them back together as a single string:
command = "virsh qemu-monitor-command f13guest info cpus";
And then it reparses the whole thing. Notice that we've lost the quoting, though, so now it's an invalid command.
The problem is further complicated by some of the other features of virsh, including the support for separating multiple commands with semicolons. For example, the following is a legal command:
# virsh 'define D.xml; dumpxml D'
In any case, the answer is probably to re-write the command-line parsing of virsh to not lose quoting. I have not had time to do this, so if you have the time to look at it and make it work, patches are definitely appreciated!
While re-writing the command line mashing to not loose quotes would be nice, the more obvious fix is to not mash all the args back into a string at all. The virsh command ultimately wants char **argv, and we already have char **argv. So doing a char ** -> char * -> char ** conversion is just insanity.
Daniel
I did it as your suggested! Thanks a lot, see the following patch: Subject: [PATCH] virsh: rework command parsing Old virsh command parsing mashes all the args back into a string and miss the quotes, this patch fixes it. It is also needed for introducing qemu-monitor-command. This patch split the command-parsing into 2 phrases: 1) parse command string and make it into <args, argv> style arguments. 2) parse <args, argv> style arguments and make it into vshCmd structure. The first step is needed when we use virsh as a shell. And the usage was changed: old: virsh [options] [commands] new: virsh [options]... [commands string] virsh [options]... [<command> [command options]...] So we still support commands like: # virsh "define D.xml; dumpxml D" "define D.xml; dumpxml D" was parsed as a commands-string. and support commands like: # virsh qemu-monitor-command f13guest "info cpus" we will not mash them into a string, we just skip the step 1 parsing and goto the step 2 parsing directly. But we don't support the command like: # virsh "define D.xml; dumpxml" D "define D.xml; dumpxml" was parsed as a command-name, but we have no such command-name. Misc changes: 1) support escape '\' 2) a better quoting support, the following commands are now supported: virsh # dumpxml --"update-cpu" vm1 virsh # dumpxml --update-cpu vm"1" 3) better handling the boolean options, in old code the following commands are equivalent: virsh # dumpxml --update-cpu=vm1 virsh # dumpxml --update-cpu vm1 after this patch applied, the first one will become illegal. Signed-off-by: Lai Jiangshan <laijs@cn.fujitsu.com> --- virsh.c | 351 ++++++++++++++++++++++++++++++---------------------------------- 1 file changed, 170 insertions(+), 181 deletions(-) --- libvirt-0.8.3.old/tools/virsh.c 2010-08-04 19:35:58.000000000 +0800 +++ libvirt-0.8.3/tools/virsh.c 2010-09-13 14:34:15.000000000 +0800 @@ -10159,90 +10159,166 @@ vshCommandRun(vshControl *ctl, const vsh * Command string parsing * --------------- */ -#define VSH_TK_ERROR -1 -#define VSH_TK_NONE 0 -#define VSH_TK_OPTION 1 -#define VSH_TK_DATA 2 -#define VSH_TK_END 3 -static int -vshCommandGetToken(vshControl *ctl, char *str, char **end, char **res) +static char * +vshCmdStrGetArg(vshControl *ctl, char *str, char **end, int *last_arg) { - int tk = VSH_TK_NONE; - int quote = FALSE; - int sz = 0; + int quote = 0; char *p = str; - char *tkstr = NULL; + char *argstr = NULL; + char *res = NULL; *end = NULL; + *last_arg = 0; while (p && *p && (*p == ' ' || *p == '\t')) p++; if (p == NULL || *p == '\0') - return VSH_TK_END; + return NULL; + if (*p == ';') { - *end = ++p; /* = \0 or begin of next command */ - return VSH_TK_END; + *end = p + 1; /* = \0 or begin of next command */ + return NULL; } + + res = argstr = p; while (*p) { - /* end of token is blank space or ';' */ - if ((quote == FALSE && (*p == ' ' || *p == '\t')) || *p == ';') + if (*p == '\\') { /* escape */ + p++; + if (*p == '\0') + break; + } else if (*p == '"') { /* quote */ + quote = !quote; + p++; + continue; + } else if ((!quote && (*p == ' ' || *p == '\t')) || *p == ';') break; - /* end of option name could be '=' */ - if (tk == VSH_TK_OPTION && *p == '=') { - p++; /* skip '=' */ - break; - } + *argstr++ = *p++; + } + + if (*p != '\0') { + if (*p == ';') + *last_arg = 1; + *end = p + 1; /* skip the \0 braught by *argstr = '\0' */ + } else + *end = p; + *argstr = '\0'; + + if (quote) { + vshError(ctl, "%s", _("missing \"")); + return NULL; + } + + return res; +} - if (tk == VSH_TK_NONE) { - if (*p == '-' && *(p + 1) == '-' && *(p + 2) +static vshCmd * +vshCommandParseArgv(vshControl *ctl, int args, char *argv[]) +{ + vshCmdOpt *first = NULL, *last = NULL; + const vshCmdDef *cmd; + vshCmd *c; + int i; + int data_ct = 0; + + if (!(cmd = vshCmddefSearch(argv[0]))) { + vshError(ctl, _("unknown command: '%s'"), argv[0]); + goto syntaxError; /* ... or ignore this command only? */ + } + + for (i = 1; i < args; i++) { + vshCmdOpt *arg; + const vshCmdOptDef *opt; + char *p, *q; + + p = vshStrdup(ctl, argv[i]); + if (*p == '-' && *(p + 1) == '-' && *(p + 2) && c_isalnum(*(p + 2))) { - tk = VSH_TK_OPTION; - p += 2; - } else { - tk = VSH_TK_DATA; - if (*p == '"') { - quote = TRUE; - p++; - } else { - quote = FALSE; + q = strchr(p + 2, '='); + if (q) { + *q = '\0'; + q = vshStrdup(ctl, q + 1); + } + + if (!(opt = vshCmddefGetOption(cmd, p + 2))) { + vshError(ctl, + _("command '%s' doesn't support option --%s"), + cmd->name, p); + VIR_FREE(p); + VIR_FREE(q); + goto syntaxError; + } + VIR_FREE(p); + + if (opt->type == VSH_OT_BOOL) { + if (q) { + vshError(ctl, _("invalid '=' after option --%s"), + opt->name); + VIR_FREE(q); + goto syntaxError; } + } else if (!q) { + i++; + if (i == args) { + vshError(ctl, + _("expected syntax: --%s <%s>"), + opt->name, + opt->type == + VSH_OT_INT ? _("number") : _("string")); + goto syntaxError; + } + q = vshStrdup(ctl, argv[i]); + } + } else { + q = p; + if (!(opt = vshCmddefGetData(cmd, data_ct++))) { + vshError(ctl, _("unexpected data '%s'"), q); + VIR_FREE(q); + goto syntaxError; } - tkstr = p; /* begin of token */ - } else if (quote && *p == '"') { - quote = FALSE; - p++; - break; /* end of "..." token */ } - p++; - sz++; + + arg = vshMalloc(ctl, sizeof(vshCmdOpt)); + arg->def = opt; + arg->data = q; + arg->next = NULL; + + if (!first) + first = arg; + if (last) + last->next = arg; + last = arg; + + vshDebug(ctl, 4, "%s: %s(%s): %s\n", cmd->name, opt->name, + arg->data != p ? _("OPTION") : _("DATA"), arg->data); } - if (quote) { - vshError(ctl, "%s", _("missing \"")); - return VSH_TK_ERROR; + + c = vshMalloc(ctl, sizeof(vshCmd)); + + c->opts = first; + c->def = cmd; + c->next = NULL; + + if (!vshCommandCheckOpts(ctl, c)) { + VIR_FREE(c); + goto syntaxError; } - if (tkstr == NULL || *tkstr == '\0' || p == NULL) - return VSH_TK_END; - if (sz == 0) - return VSH_TK_END; - - *res = vshMalloc(ctl, sz + 1); - memcpy(*res, tkstr, sz); - *(*res + sz) = '\0'; + return c; - *end = p; - return tk; +syntaxError: + if (first) + vshCommandOptFree(first); + return NULL; } + static int vshCommandParse(vshControl *ctl, char *cmdstr) { char *str; - char *tkdata = NULL; vshCmd *clast = NULL; - vshCmdOpt *first = NULL; if (ctl->cmd) { vshCommandFree(ctl->cmd); @@ -10254,130 +10330,42 @@ vshCommandParse(vshControl *ctl, char *c str = cmdstr; while (str && *str) { - vshCmdOpt *last = NULL; - const vshCmdDef *cmd = NULL; - int tk = VSH_TK_NONE; - int data_ct = 0; - - first = NULL; - - while (tk != VSH_TK_END) { - char *end = NULL; - const vshCmdOptDef *opt = NULL; - - tkdata = NULL; - - /* get token */ - tk = vshCommandGetToken(ctl, str, &end, &tkdata); - - str = end; - - if (tk == VSH_TK_END) { - VIR_FREE(tkdata); - break; - } - if (tk == VSH_TK_ERROR) + vshCmd *c; + int args = 0; + char *argv[20]; + char *arg; + int last_arg = 0; + + while ((arg = vshCmdStrGetArg(ctl, str, &str, &last_arg)) != NULL) { + if (args == sizeof(argv) / sizeof(argv[0])) { + vshError(ctl, _("too many args")); goto syntaxError; - - if (cmd == NULL) { - /* first token must be command name */ - if (tk != VSH_TK_DATA) { - vshError(ctl, - _("unexpected token (command name): '%s'"), - tkdata); - goto syntaxError; - } - if (!(cmd = vshCmddefSearch(tkdata))) { - vshError(ctl, _("unknown command: '%s'"), tkdata); - goto syntaxError; /* ... or ignore this command only? */ - } - VIR_FREE(tkdata); - } else if (tk == VSH_TK_OPTION) { - if (!(opt = vshCmddefGetOption(cmd, tkdata))) { - vshError(ctl, - _("command '%s' doesn't support option --%s"), - cmd->name, tkdata); - goto syntaxError; - } - VIR_FREE(tkdata); /* option name */ - - if (opt->type != VSH_OT_BOOL) { - /* option data */ - tk = vshCommandGetToken(ctl, str, &end, &tkdata); - str = end; - if (tk == VSH_TK_ERROR) - goto syntaxError; - if (tk != VSH_TK_DATA) { - vshError(ctl, - _("expected syntax: --%s <%s>"), - opt->name, - opt->type == - VSH_OT_INT ? _("number") : _("string")); - goto syntaxError; - } - } - } else if (tk == VSH_TK_DATA) { - if (!(opt = vshCmddefGetData(cmd, data_ct++))) { - vshError(ctl, _("unexpected data '%s'"), tkdata); - goto syntaxError; - } - } - 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, 4, "%s: %s(%s): %s\n", - cmd->name, - opt->name, - tk == VSH_TK_OPTION ? _("OPTION") : _("DATA"), - arg->data); } - if (!str) + argv[args++] = arg; + if (last_arg) break; } + if (args == 0) + continue; - /* command parsed -- allocate new struct for the command */ - if (cmd) { - vshCmd *c = vshMalloc(ctl, sizeof(vshCmd)); - - c->opts = first; - c->def = cmd; - c->next = NULL; - - if (!vshCommandCheckOpts(ctl, c)) { - VIR_FREE(c); - goto syntaxError; - } - - if (!ctl->cmd) - ctl->cmd = c; - if (clast) - clast->next = c; - clast = c; - } + c = vshCommandParseArgv(ctl, args, argv); + if (!c) + goto syntaxError; + + if (!ctl->cmd) + ctl->cmd = c; + if (clast) + clast->next = c; + clast = c; } return TRUE; - syntaxError: +syntaxError: if (ctl->cmd) { vshCommandFree(ctl->cmd); ctl->cmd = NULL; } - if (first) - vshCommandOptFree(first); - VIR_FREE(tkdata); return FALSE; } @@ -10936,7 +10924,8 @@ static void vshUsage(void) { const vshCmdDef *cmd; - fprintf(stdout, _("\n%s [options] [commands]\n\n" + fprintf(stdout, _("\n%s [options]... [commands string]" + "\n%s [options]... [<command> [command options]...]\n\n" " options:\n" " -c | --connect <uri> hypervisor connection URI\n" " -r | --readonly connect readonly\n" @@ -10946,7 +10935,7 @@ vshUsage(void) " -t | --timing print timing information\n" " -l | --log <file> output logging to file\n" " -v | --version program version\n\n" - " commands (non interactive mode):\n"), progname); + " commands (non interactive mode):\n"), progname, progname); for (cmd = commands; cmd->name; cmd++) fprintf(stdout, @@ -11066,25 +11055,25 @@ vshParseArgv(vshControl *ctl, int argc, if (argc > end) { /* parse command */ - char *cmdstr; - int sz = 0, ret; + int ret = TRUE; ctl->imode = FALSE; - for (i = end; i < argc; i++) - sz += strlen(argv[i]) + 1; /* +1 is for blank space between items */ - - cmdstr = vshCalloc(ctl, sz + 1, 1); - - for (i = end; i < argc; i++) { - strncat(cmdstr, argv[i], sz); - sz -= strlen(argv[i]); - strncat(cmdstr, " ", sz--); + if (argc - end == 1) { + char *cmdstr = vshStrdup(ctl, argv[end]); + vshDebug(ctl, 2, "commands: \"%s\"\n", cmdstr); + ret = vshCommandParse(ctl, cmdstr); + VIR_FREE(cmdstr); + } else { + if (ctl->cmd) { + vshCommandFree(ctl->cmd); + ctl->cmd = NULL; + } + ctl->cmd = vshCommandParseArgv(ctl, argc - end, argv + end); + if (!ctl->cmd) + ret = FALSE; } - vshDebug(ctl, 2, "command: \"%s\"\n", cmdstr); - ret = vshCommandParse(ctl, cmdstr); - VIR_FREE(cmdstr); return ret; } return TRUE;