Merge remote-tracking branch 'luiz/queue/qmp' into staging

# By Wenchao Xia (15) and Stefan Weil (1)
# Via Luiz Capitulino
* luiz/queue/qmp:
  monitor: improve auto complete of "help" for single command in sub group
  monitor: allow "help" show message for single command in sub group
  monitor: support sub command in auto completion
  monitor: refine monitor_find_completion()
  monitor: support sub command in help
  monitor: refine parse_cmdline()
  monitor: code move for parse_cmdline()
  monitor: avoid direct use of global variable *mon_cmds
  monitor: split off monitor_data_init()
  monitor: call sortcmdlist() only one time
  monitor: avoid use of global *cur_mon in readline_completion()
  monitor: avoid use of global *cur_mon in monitor_find_completion()
  monitor: avoid use of global *cur_mon in block_completion_it()
  monitor: avoid use of global *cur_mon in file_completion()
  monitor: avoid use of global *cur_mon in cmd_completion()
  monitor: Add missing attributes to local function

Message-id: 1377865357-6742-1-git-send-email-lcapitulino@redhat.com
This commit is contained in:
Anthony Liguori 2013-08-30 12:26:04 -05:00
commit 4ff78e0dbc
4 changed files with 306 additions and 168 deletions

View File

@ -11,7 +11,7 @@ ETEXI
{
.name = "help|?",
.args_type = "name:s?",
.args_type = "name:S?",
.params = "[cmd]",
.help = "show the help",
.mhandler.cmd = do_help_cmd,

View File

@ -8,7 +8,8 @@
#define READLINE_MAX_COMPLETIONS 256
typedef void ReadLineFunc(Monitor *mon, const char *str, void *opaque);
typedef void ReadLineCompletionFunc(const char *cmdline);
typedef void ReadLineCompletionFunc(Monitor *mon,
const char *cmdline);
typedef struct ReadLineState {
char cmd_buf[READLINE_CMD_BUF_SIZE + 1];

464
monitor.c
View File

@ -83,6 +83,7 @@
* 'F' filename
* 'B' block device name
* 's' string (accept optional quote)
* 'S' it just appends the rest of the string (accept optional quote)
* 'O' option string of the form NAME=VALUE,...
* parsed according to QemuOptsList given by its name
* Example: 'device:O' uses qemu_device_opts.
@ -195,6 +196,7 @@ struct Monitor {
CPUState *mon_cpu;
BlockDriverCompletionFunc *password_completion_cb;
void *password_opaque;
mon_cmd_t *cmd_table;
QError *error;
QLIST_HEAD(,mon_fd_t) fds;
QLIST_ENTRY(Monitor) entry;
@ -683,14 +685,26 @@ static int do_qmp_capabilities(Monitor *mon, const QDict *params,
static void handle_user_command(Monitor *mon, const char *cmdline);
static void monitor_data_init(Monitor *mon)
{
memset(mon, 0, sizeof(Monitor));
mon->outbuf = qstring_new();
/* Use *mon_cmds by default. */
mon->cmd_table = mon_cmds;
}
static void monitor_data_destroy(Monitor *mon)
{
QDECREF(mon->outbuf);
}
char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
int64_t cpu_index, Error **errp)
{
char *output = NULL;
Monitor *old_mon, hmp;
memset(&hmp, 0, sizeof(hmp));
hmp.outbuf = qstring_new();
monitor_data_init(&hmp);
hmp.skip_flush = true;
old_mon = cur_mon;
@ -716,7 +730,7 @@ char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index,
}
out:
QDECREF(hmp.outbuf);
monitor_data_destroy(&hmp);
return output;
}
@ -740,33 +754,202 @@ static int compare_cmd(const char *name, const char *list)
return 0;
}
static int get_str(char *buf, int buf_size, const char **pp)
{
const char *p;
char *q;
int c;
q = buf;
p = *pp;
while (qemu_isspace(*p)) {
p++;
}
if (*p == '\0') {
fail:
*q = '\0';
*pp = p;
return -1;
}
if (*p == '\"') {
p++;
while (*p != '\0' && *p != '\"') {
if (*p == '\\') {
p++;
c = *p++;
switch (c) {
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case '\\':
case '\'':
case '\"':
break;
default:
qemu_printf("unsupported escape code: '\\%c'\n", c);
goto fail;
}
if ((q - buf) < buf_size - 1) {
*q++ = c;
}
} else {
if ((q - buf) < buf_size - 1) {
*q++ = *p;
}
p++;
}
}
if (*p != '\"') {
qemu_printf("unterminated string\n");
goto fail;
}
p++;
} else {
while (*p != '\0' && !qemu_isspace(*p)) {
if ((q - buf) < buf_size - 1) {
*q++ = *p;
}
p++;
}
}
*q = '\0';
*pp = p;
return 0;
}
#define MAX_ARGS 16
static void free_cmdline_args(char **args, int nb_args)
{
int i;
assert(nb_args <= MAX_ARGS);
for (i = 0; i < nb_args; i++) {
g_free(args[i]);
}
}
/*
* Parse the command line to get valid args.
* @cmdline: command line to be parsed.
* @pnb_args: location to store the number of args, must NOT be NULL.
* @args: location to store the args, which should be freed by caller, must
* NOT be NULL.
*
* Returns 0 on success, negative on failure.
*
* NOTE: this parser is an approximate form of the real command parser. Number
* of args have a limit of MAX_ARGS. If cmdline contains more, it will
* return with failure.
*/
static int parse_cmdline(const char *cmdline,
int *pnb_args, char **args)
{
const char *p;
int nb_args, ret;
char buf[1024];
p = cmdline;
nb_args = 0;
for (;;) {
while (qemu_isspace(*p)) {
p++;
}
if (*p == '\0') {
break;
}
if (nb_args >= MAX_ARGS) {
goto fail;
}
ret = get_str(buf, sizeof(buf), &p);
if (ret < 0) {
goto fail;
}
args[nb_args] = g_strdup(buf);
nb_args++;
}
*pnb_args = nb_args;
return 0;
fail:
free_cmdline_args(args, nb_args);
return -1;
}
static void help_cmd_dump_one(Monitor *mon,
const mon_cmd_t *cmd,
char **prefix_args,
int prefix_args_nb)
{
int i;
for (i = 0; i < prefix_args_nb; i++) {
monitor_printf(mon, "%s ", prefix_args[i]);
}
monitor_printf(mon, "%s %s -- %s\n", cmd->name, cmd->params, cmd->help);
}
/* @args[@arg_index] is the valid command need to find in @cmds */
static void help_cmd_dump(Monitor *mon, const mon_cmd_t *cmds,
const char *prefix, const char *name)
char **args, int nb_args, int arg_index)
{
const mon_cmd_t *cmd;
for(cmd = cmds; cmd->name != NULL; cmd++) {
if (!name || !strcmp(name, cmd->name))
monitor_printf(mon, "%s%s %s -- %s\n", prefix, cmd->name,
cmd->params, cmd->help);
/* No valid arg need to compare with, dump all in *cmds */
if (arg_index >= nb_args) {
for (cmd = cmds; cmd->name != NULL; cmd++) {
help_cmd_dump_one(mon, cmd, args, arg_index);
}
return;
}
/* Find one entry to dump */
for (cmd = cmds; cmd->name != NULL; cmd++) {
if (compare_cmd(args[arg_index], cmd->name)) {
if (cmd->sub_table) {
/* continue with next arg */
help_cmd_dump(mon, cmd->sub_table,
args, nb_args, arg_index + 1);
} else {
help_cmd_dump_one(mon, cmd, args, arg_index);
}
break;
}
}
}
static void help_cmd(Monitor *mon, const char *name)
{
if (name && !strcmp(name, "info")) {
help_cmd_dump(mon, info_cmds, "info ", NULL);
} else {
help_cmd_dump(mon, mon_cmds, "", name);
if (name && !strcmp(name, "log")) {
char *args[MAX_ARGS];
int nb_args = 0;
/* 1. parse user input */
if (name) {
/* special case for log, directly dump and return */
if (!strcmp(name, "log")) {
const QEMULogItem *item;
monitor_printf(mon, "Log items (comma separated):\n");
monitor_printf(mon, "%-10s %s\n", "none", "remove all logs");
for (item = qemu_log_items; item->mask != 0; item++) {
monitor_printf(mon, "%-10s %s\n", item->name, item->help);
}
return;
}
if (parse_cmdline(name, &nb_args, args) < 0) {
return;
}
}
/* 2. dump the contents according to parsed args */
help_cmd_dump(mon, mon->cmd_table, args, nb_args, 0);
free_cmdline_args(args, nb_args);
}
static void do_help_cmd(Monitor *mon, const QDict *qdict)
@ -3171,7 +3354,8 @@ static const MonitorDef monitor_defs[] = {
{ NULL },
};
static void expr_error(Monitor *mon, const char *fmt, ...)
static void GCC_FMT_ATTR(2, 3) QEMU_NORETURN
expr_error(Monitor *mon, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
@ -3420,71 +3604,6 @@ static int get_double(Monitor *mon, double *pval, const char **pp)
return 0;
}
static int get_str(char *buf, int buf_size, const char **pp)
{
const char *p;
char *q;
int c;
q = buf;
p = *pp;
while (qemu_isspace(*p))
p++;
if (*p == '\0') {
fail:
*q = '\0';
*pp = p;
return -1;
}
if (*p == '\"') {
p++;
while (*p != '\0' && *p != '\"') {
if (*p == '\\') {
p++;
c = *p++;
switch(c) {
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case '\\':
case '\'':
case '\"':
break;
default:
qemu_printf("unsupported escape code: '\\%c'\n", c);
goto fail;
}
if ((q - buf) < buf_size - 1) {
*q++ = c;
}
} else {
if ((q - buf) < buf_size - 1) {
*q++ = *p;
}
p++;
}
}
if (*p != '\"') {
qemu_printf("unterminated string\n");
goto fail;
}
p++;
} else {
while (*p != '\0' && !qemu_isspace(*p)) {
if ((q - buf) < buf_size - 1) {
*q++ = *p;
}
p++;
}
}
*q = '\0';
*pp = p;
return 0;
}
/*
* Store the command-name in cmdname, and return a pointer to
* the remaining of the command string.
@ -3541,8 +3660,6 @@ static char *key_get_info(const char *type, char **key)
static int default_fmt_format = 'x';
static int default_fmt_size = 4;
#define MAX_ARGS 16
static int is_valid_option(const char *c, const char *typestr)
{
char option[3];
@ -3931,6 +4048,31 @@ static const mon_cmd_t *monitor_parse_command(Monitor *mon,
}
}
break;
case 'S':
{
/* package all remaining string */
int len;
while (qemu_isspace(*p)) {
p++;
}
if (*typestr == '?') {
typestr++;
if (*p == '\0') {
/* no remaining string: NULL argument */
break;
}
}
len = strlen(p);
if (len <= 0) {
monitor_printf(mon, "%s: string expected\n",
cmdname);
break;
}
qdict_put(qdict, key, qstring_from_str(p));
p += len;
}
break;
default:
bad_type:
monitor_printf(mon, "%s: unknown type '%c'\n", cmdname, c);
@ -3984,7 +4126,7 @@ static void handle_user_command(Monitor *mon, const char *cmdline)
qdict = qdict_new();
cmd = monitor_parse_command(mon, cmdline, 0, mon_cmds, qdict);
cmd = monitor_parse_command(mon, cmdline, 0, mon->cmd_table, qdict);
if (!cmd)
goto out;
@ -4008,7 +4150,7 @@ out:
QDECREF(qdict);
}
static void cmd_completion(const char *name, const char *list)
static void cmd_completion(Monitor *mon, const char *name, const char *list)
{
const char *p, *pstart;
char cmd[128];
@ -4026,7 +4168,7 @@ static void cmd_completion(const char *name, const char *list)
memcpy(cmd, pstart, len);
cmd[len] = '\0';
if (name[0] == '\0' || !strncmp(name, cmd, strlen(name))) {
readline_add_completion(cur_mon->rs, cmd);
readline_add_completion(mon->rs, cmd);
}
if (*p == '\0')
break;
@ -4034,7 +4176,7 @@ static void cmd_completion(const char *name, const char *list)
}
}
static void file_completion(const char *input)
static void file_completion(Monitor *mon, const char *input)
{
DIR *ffs;
struct dirent *d;
@ -4057,7 +4199,7 @@ static void file_completion(const char *input)
pstrcpy(file_prefix, sizeof(file_prefix), p + 1);
}
#ifdef DEBUG_COMPLETION
monitor_printf(cur_mon, "input='%s' path='%s' prefix='%s'\n",
monitor_printf(mon, "input='%s' path='%s' prefix='%s'\n",
input, path, file_prefix);
#endif
ffs = opendir(path);
@ -4084,98 +4226,72 @@ static void file_completion(const char *input)
if (stat(file, &sb) == 0 && S_ISDIR(sb.st_mode)) {
pstrcat(file, sizeof(file), "/");
}
readline_add_completion(cur_mon->rs, file);
readline_add_completion(mon->rs, file);
}
}
closedir(ffs);
}
typedef struct MonitorBlockComplete {
Monitor *mon;
const char *input;
} MonitorBlockComplete;
static void block_completion_it(void *opaque, BlockDriverState *bs)
{
const char *name = bdrv_get_device_name(bs);
const char *input = opaque;
MonitorBlockComplete *mbc = opaque;
Monitor *mon = mbc->mon;
const char *input = mbc->input;
if (input[0] == '\0' ||
!strncmp(name, (char *)input, strlen(input))) {
readline_add_completion(cur_mon->rs, name);
readline_add_completion(mon->rs, name);
}
}
/* NOTE: this parser is an approximate form of the real command parser */
static void parse_cmdline(const char *cmdline,
int *pnb_args, char **args)
{
const char *p;
int nb_args, ret;
char buf[1024];
p = cmdline;
nb_args = 0;
for(;;) {
while (qemu_isspace(*p))
p++;
if (*p == '\0')
break;
if (nb_args >= MAX_ARGS)
break;
ret = get_str(buf, sizeof(buf), &p);
args[nb_args] = g_strdup(buf);
nb_args++;
if (ret < 0)
break;
}
*pnb_args = nb_args;
}
static const char *next_arg_type(const char *typestr)
{
const char *p = strchr(typestr, ':');
return (p != NULL ? ++p : typestr);
}
static void monitor_find_completion(const char *cmdline)
static void monitor_find_completion_by_table(Monitor *mon,
const mon_cmd_t *cmd_table,
char **args,
int nb_args)
{
const char *cmdname;
char *args[MAX_ARGS];
int nb_args, i, len;
int i;
const char *ptype, *str;
const mon_cmd_t *cmd;
MonitorBlockComplete mbs;
parse_cmdline(cmdline, &nb_args, args);
#ifdef DEBUG_COMPLETION
for(i = 0; i < nb_args; i++) {
monitor_printf(cur_mon, "arg%d = '%s'\n", i, (char *)args[i]);
}
#endif
/* if the line ends with a space, it means we want to complete the
next arg */
len = strlen(cmdline);
if (len > 0 && qemu_isspace(cmdline[len - 1])) {
if (nb_args >= MAX_ARGS) {
goto cleanup;
}
args[nb_args++] = g_strdup("");
}
if (nb_args <= 1) {
/* command completion */
if (nb_args == 0)
cmdname = "";
else
cmdname = args[0];
readline_set_completion_index(cur_mon->rs, strlen(cmdname));
for(cmd = mon_cmds; cmd->name != NULL; cmd++) {
cmd_completion(cmdname, cmd->name);
readline_set_completion_index(mon->rs, strlen(cmdname));
for (cmd = cmd_table; cmd->name != NULL; cmd++) {
cmd_completion(mon, cmdname, cmd->name);
}
} else {
/* find the command */
for (cmd = mon_cmds; cmd->name != NULL; cmd++) {
for (cmd = cmd_table; cmd->name != NULL; cmd++) {
if (compare_cmd(args[0], cmd->name)) {
break;
}
}
if (!cmd->name) {
goto cleanup;
return;
}
if (cmd->sub_table) {
/* do the job again */
return monitor_find_completion_by_table(mon, cmd->sub_table,
&args[1], nb_args - 1);
}
ptype = next_arg_type(cmd->args_type);
@ -4193,45 +4309,68 @@ static void monitor_find_completion(const char *cmdline)
switch(*ptype) {
case 'F':
/* file completion */
readline_set_completion_index(cur_mon->rs, strlen(str));
file_completion(str);
readline_set_completion_index(mon->rs, strlen(str));
file_completion(mon, str);
break;
case 'B':
/* block device name completion */
readline_set_completion_index(cur_mon->rs, strlen(str));
bdrv_iterate(block_completion_it, (void *)str);
mbs.mon = mon;
mbs.input = str;
readline_set_completion_index(mon->rs, strlen(str));
bdrv_iterate(block_completion_it, &mbs);
break;
case 's':
/* XXX: more generic ? */
if (!strcmp(cmd->name, "info")) {
readline_set_completion_index(cur_mon->rs, strlen(str));
for(cmd = info_cmds; cmd->name != NULL; cmd++) {
cmd_completion(str, cmd->name);
}
} else if (!strcmp(cmd->name, "sendkey")) {
case 'S':
if (!strcmp(cmd->name, "sendkey")) {
char *sep = strrchr(str, '-');
if (sep)
str = sep + 1;
readline_set_completion_index(cur_mon->rs, strlen(str));
readline_set_completion_index(mon->rs, strlen(str));
for (i = 0; i < Q_KEY_CODE_MAX; i++) {
cmd_completion(str, QKeyCode_lookup[i]);
cmd_completion(mon, str, QKeyCode_lookup[i]);
}
} else if (!strcmp(cmd->name, "help|?")) {
readline_set_completion_index(cur_mon->rs, strlen(str));
for (cmd = mon_cmds; cmd->name != NULL; cmd++) {
cmd_completion(str, cmd->name);
}
monitor_find_completion_by_table(mon, cmd_table,
&args[1], nb_args - 1);
}
break;
default:
break;
}
}
}
static void monitor_find_completion(Monitor *mon,
const char *cmdline)
{
char *args[MAX_ARGS];
int nb_args, len;
/* 1. parse the cmdline */
if (parse_cmdline(cmdline, &nb_args, args) < 0) {
return;
}
#ifdef DEBUG_COMPLETION
for (i = 0; i < nb_args; i++) {
monitor_printf(mon, "arg%d = '%s'\n", i, args[i]);
}
#endif
/* if the line ends with a space, it means we want to complete the
next arg */
len = strlen(cmdline);
if (len > 0 && qemu_isspace(cmdline[len - 1])) {
if (nb_args >= MAX_ARGS) {
goto cleanup;
}
args[nb_args++] = g_strdup("");
}
/* 2. auto complete according to args */
monitor_find_completion_by_table(mon, mon->cmd_table, args, nb_args);
cleanup:
for (i = 0; i < nb_args; i++) {
g_free(args[i]);
}
free_cmdline_args(args, nb_args);
}
static int monitor_can_read(void *opaque)
@ -4751,11 +4890,12 @@ void monitor_init(CharDriverState *chr, int flags)
if (is_first_init) {
monitor_protocol_event_init();
sortcmdlist();
is_first_init = 0;
}
mon = g_malloc0(sizeof(*mon));
mon->outbuf = qstring_new();
mon = g_malloc(sizeof(*mon));
monitor_data_init(mon);
mon->chr = chr;
mon->flags = flags;
@ -4780,8 +4920,6 @@ void monitor_init(CharDriverState *chr, int flags)
QLIST_INSERT_HEAD(&mon_list, mon, entry);
if (!default_mon || (flags & MONITOR_IS_DEFAULT))
default_mon = mon;
sortcmdlist();
}
static void bdrv_password_cb(Monitor *mon, const char *password, void *opaque)

View File

@ -276,7 +276,6 @@ void readline_set_completion_index(ReadLineState *rs, int index)
static void readline_completion(ReadLineState *rs)
{
Monitor *mon = cur_mon;
int len, i, j, max_width, nb_cols, max_prefix;
char *cmdline;
@ -285,7 +284,7 @@ static void readline_completion(ReadLineState *rs)
cmdline = g_malloc(rs->cmd_buf_index + 1);
memcpy(cmdline, rs->cmd_buf, rs->cmd_buf_index);
cmdline[rs->cmd_buf_index] = '\0';
rs->completion_finder(cmdline);
rs->completion_finder(rs->mon, cmdline);
g_free(cmdline);
/* no completion found */
@ -300,7 +299,7 @@ static void readline_completion(ReadLineState *rs)
if (len > 0 && rs->completions[0][len - 1] != '/')
readline_insert_char(rs, ' ');
} else {
monitor_printf(mon, "\n");
monitor_printf(rs->mon, "\n");
max_width = 0;
max_prefix = 0;
for(i = 0; i < rs->nb_completions; i++) {