mirror of
https://github.com/qemu/qemu.git
synced 2024-11-23 19:03:38 +08:00
0143840771
The various ACL related commands are obsolete now that the QAuthZ framework for authorization is fully integrated throughout QEMU network services. These only ever worked with VNC and were never used by libvirt. Mark it as deprecated with no direct replacement to be provided. Authorization is now provided by using 'object_add' together with the 'tls-authz' or 'sasl-authz' parameters to the VNC server, and equivalent for other network services. Reviewed-by: Juan Quintela <quintela@redhat.com> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> Message-id: 20190227145755.26556-3-berrange@redhat.com Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
4731 lines
128 KiB
C
4731 lines
128 KiB
C
/*
|
|
* QEMU monitor
|
|
*
|
|
* Copyright (c) 2003-2004 Fabrice Bellard
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/units.h"
|
|
#include <dirent.h>
|
|
#include "cpu.h"
|
|
#include "hw/hw.h"
|
|
#include "monitor/qdev.h"
|
|
#include "hw/usb.h"
|
|
#include "hw/pci/pci.h"
|
|
#include "sysemu/watchdog.h"
|
|
#include "hw/loader.h"
|
|
#include "exec/gdbstub.h"
|
|
#include "net/net.h"
|
|
#include "net/slirp.h"
|
|
#include "chardev/char-fe.h"
|
|
#include "chardev/char-io.h"
|
|
#include "chardev/char-mux.h"
|
|
#include "ui/qemu-spice.h"
|
|
#include "sysemu/numa.h"
|
|
#include "monitor/monitor.h"
|
|
#include "qemu/config-file.h"
|
|
#include "qemu/readline.h"
|
|
#include "ui/console.h"
|
|
#include "ui/input.h"
|
|
#include "sysemu/block-backend.h"
|
|
#include "audio/audio.h"
|
|
#include "disas/disas.h"
|
|
#include "sysemu/balloon.h"
|
|
#include "qemu/timer.h"
|
|
#include "sysemu/hw_accel.h"
|
|
#include "authz/list.h"
|
|
#include "qapi/util.h"
|
|
#include "sysemu/tpm.h"
|
|
#include "qapi/qmp/qdict.h"
|
|
#include "qapi/qmp/qerror.h"
|
|
#include "qapi/qmp/qnum.h"
|
|
#include "qapi/qmp/qstring.h"
|
|
#include "qapi/qmp/qjson.h"
|
|
#include "qapi/qmp/json-parser.h"
|
|
#include "qapi/qmp/qlist.h"
|
|
#include "qom/object_interfaces.h"
|
|
#include "trace-root.h"
|
|
#include "trace/control.h"
|
|
#include "monitor/hmp-target.h"
|
|
#ifdef CONFIG_TRACE_SIMPLE
|
|
#include "trace/simple.h"
|
|
#endif
|
|
#include "exec/memory.h"
|
|
#include "exec/exec-all.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/option.h"
|
|
#include "hmp.h"
|
|
#include "qemu/thread.h"
|
|
#include "block/qapi.h"
|
|
#include "qapi/qapi-commands.h"
|
|
#include "qapi/qapi-emit-events.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/qmp-event.h"
|
|
#include "qapi/qapi-introspect.h"
|
|
#include "sysemu/qtest.h"
|
|
#include "sysemu/cpus.h"
|
|
#include "sysemu/iothread.h"
|
|
#include "qemu/cutils.h"
|
|
#include "tcg/tcg.h"
|
|
|
|
#if defined(TARGET_S390X)
|
|
#include "hw/s390x/storage-keys.h"
|
|
#include "hw/s390x/storage-attributes.h"
|
|
#endif
|
|
|
|
/*
|
|
* Supported types:
|
|
*
|
|
* '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.
|
|
* Restriction: only lists with empty desc are supported
|
|
* TODO lift the restriction
|
|
* 'i' 32 bit integer
|
|
* 'l' target long (32 or 64 bit)
|
|
* 'M' Non-negative target long (32 or 64 bit), in user mode the
|
|
* value is multiplied by 2^20 (think Mebibyte)
|
|
* 'o' octets (aka bytes)
|
|
* user mode accepts an optional E, e, P, p, T, t, G, g, M, m,
|
|
* K, k suffix, which multiplies the value by 2^60 for suffixes E
|
|
* and e, 2^50 for suffixes P and p, 2^40 for suffixes T and t,
|
|
* 2^30 for suffixes G and g, 2^20 for M and m, 2^10 for K and k
|
|
* 'T' double
|
|
* user mode accepts an optional ms, us, ns suffix,
|
|
* which divides the value by 1e3, 1e6, 1e9, respectively
|
|
* '/' optional gdb-like print format (like "/10x")
|
|
*
|
|
* '?' optional type (for all types, except '/')
|
|
* '.' other form of optional type (for 'i' and 'l')
|
|
* 'b' boolean
|
|
* user mode accepts "on" or "off"
|
|
* '-' optional parameter (eg. '-f')
|
|
*
|
|
*/
|
|
|
|
typedef struct mon_cmd_t {
|
|
const char *name;
|
|
const char *args_type;
|
|
const char *params;
|
|
const char *help;
|
|
const char *flags; /* p=preconfig */
|
|
void (*cmd)(Monitor *mon, const QDict *qdict);
|
|
/* @sub_table is a list of 2nd level of commands. If it does not exist,
|
|
* cmd should be used. If it exists, sub_table[?].cmd should be
|
|
* used, and cmd of 1st level plays the role of help function.
|
|
*/
|
|
struct mon_cmd_t *sub_table;
|
|
void (*command_completion)(ReadLineState *rs, int nb_args, const char *str);
|
|
} mon_cmd_t;
|
|
|
|
/* file descriptors passed via SCM_RIGHTS */
|
|
typedef struct mon_fd_t mon_fd_t;
|
|
struct mon_fd_t {
|
|
char *name;
|
|
int fd;
|
|
QLIST_ENTRY(mon_fd_t) next;
|
|
};
|
|
|
|
/* file descriptor associated with a file descriptor set */
|
|
typedef struct MonFdsetFd MonFdsetFd;
|
|
struct MonFdsetFd {
|
|
int fd;
|
|
bool removed;
|
|
char *opaque;
|
|
QLIST_ENTRY(MonFdsetFd) next;
|
|
};
|
|
|
|
/* file descriptor set containing fds passed via SCM_RIGHTS */
|
|
typedef struct MonFdset MonFdset;
|
|
struct MonFdset {
|
|
int64_t id;
|
|
QLIST_HEAD(, MonFdsetFd) fds;
|
|
QLIST_HEAD(, MonFdsetFd) dup_fds;
|
|
QLIST_ENTRY(MonFdset) next;
|
|
};
|
|
|
|
typedef struct {
|
|
JSONMessageParser parser;
|
|
/*
|
|
* When a client connects, we're in capabilities negotiation mode.
|
|
* @commands is &qmp_cap_negotiation_commands then. When command
|
|
* qmp_capabilities succeeds, we go into command mode, and
|
|
* @command becomes &qmp_commands.
|
|
*/
|
|
QmpCommandList *commands;
|
|
bool capab_offered[QMP_CAPABILITY__MAX]; /* capabilities offered */
|
|
bool capab[QMP_CAPABILITY__MAX]; /* offered and accepted */
|
|
/*
|
|
* Protects qmp request/response queue.
|
|
* Take monitor_lock first when you need both.
|
|
*/
|
|
QemuMutex qmp_queue_lock;
|
|
/* Input queue that holds all the parsed QMP requests */
|
|
GQueue *qmp_requests;
|
|
} MonitorQMP;
|
|
|
|
/*
|
|
* To prevent flooding clients, events can be throttled. The
|
|
* throttling is calculated globally, rather than per-Monitor
|
|
* instance.
|
|
*/
|
|
typedef struct MonitorQAPIEventState {
|
|
QAPIEvent event; /* Throttling state for this event type and... */
|
|
QDict *data; /* ... data, see qapi_event_throttle_equal() */
|
|
QEMUTimer *timer; /* Timer for handling delayed events */
|
|
QDict *qdict; /* Delayed event (if any) */
|
|
} MonitorQAPIEventState;
|
|
|
|
typedef struct {
|
|
int64_t rate; /* Minimum time (in ns) between two events */
|
|
} MonitorQAPIEventConf;
|
|
|
|
struct Monitor {
|
|
CharBackend chr;
|
|
int reset_seen;
|
|
int flags;
|
|
int suspend_cnt; /* Needs to be accessed atomically */
|
|
bool skip_flush;
|
|
bool use_io_thread;
|
|
|
|
/*
|
|
* State used only in the thread "owning" the monitor.
|
|
* If @use_io_thread, this is @mon_iothread.
|
|
* Else, it's the main thread.
|
|
* These members can be safely accessed without locks.
|
|
*/
|
|
ReadLineState *rs;
|
|
|
|
MonitorQMP qmp;
|
|
gchar *mon_cpu_path;
|
|
BlockCompletionFunc *password_completion_cb;
|
|
void *password_opaque;
|
|
mon_cmd_t *cmd_table;
|
|
QTAILQ_ENTRY(Monitor) entry;
|
|
|
|
/*
|
|
* The per-monitor lock. We can't access guest memory when holding
|
|
* the lock.
|
|
*/
|
|
QemuMutex mon_lock;
|
|
|
|
/*
|
|
* Members that are protected by the per-monitor lock
|
|
*/
|
|
QLIST_HEAD(, mon_fd_t) fds;
|
|
QString *outbuf;
|
|
guint out_watch;
|
|
/* Read under either BQL or mon_lock, written with BQL+mon_lock. */
|
|
int mux_out;
|
|
};
|
|
|
|
/* Shared monitor I/O thread */
|
|
IOThread *mon_iothread;
|
|
|
|
/* Bottom half to dispatch the requests received from I/O thread */
|
|
QEMUBH *qmp_dispatcher_bh;
|
|
|
|
struct QMPRequest {
|
|
/* Owner of the request */
|
|
Monitor *mon;
|
|
/* "id" field of the request */
|
|
QObject *id;
|
|
/*
|
|
* Request object to be handled or Error to be reported
|
|
* (exactly one of them is non-null)
|
|
*/
|
|
QObject *req;
|
|
Error *err;
|
|
};
|
|
typedef struct QMPRequest QMPRequest;
|
|
|
|
/* QMP checker flags */
|
|
#define QMP_ACCEPT_UNKNOWNS 1
|
|
|
|
/* Protects mon_list, monitor_qapi_event_state, monitor_destroyed. */
|
|
static QemuMutex monitor_lock;
|
|
static GHashTable *monitor_qapi_event_state;
|
|
static QTAILQ_HEAD(, Monitor) mon_list;
|
|
static bool monitor_destroyed;
|
|
|
|
/* Protects mon_fdsets */
|
|
static QemuMutex mon_fdsets_lock;
|
|
static QLIST_HEAD(, MonFdset) mon_fdsets;
|
|
|
|
static int mon_refcount;
|
|
|
|
static mon_cmd_t mon_cmds[];
|
|
static mon_cmd_t info_cmds[];
|
|
|
|
QmpCommandList qmp_commands, qmp_cap_negotiation_commands;
|
|
|
|
__thread Monitor *cur_mon;
|
|
|
|
static void monitor_command_cb(void *opaque, const char *cmdline,
|
|
void *readline_opaque);
|
|
|
|
/**
|
|
* Is @mon a QMP monitor?
|
|
*/
|
|
static inline bool monitor_is_qmp(const Monitor *mon)
|
|
{
|
|
return (mon->flags & MONITOR_USE_CONTROL);
|
|
}
|
|
|
|
/**
|
|
* Is @mon is using readline?
|
|
* Note: not all HMP monitors use readline, e.g., gdbserver has a
|
|
* non-interactive HMP monitor, so readline is not used there.
|
|
*/
|
|
static inline bool monitor_uses_readline(const Monitor *mon)
|
|
{
|
|
return mon->flags & MONITOR_USE_READLINE;
|
|
}
|
|
|
|
static inline bool monitor_is_hmp_non_interactive(const Monitor *mon)
|
|
{
|
|
return !monitor_is_qmp(mon) && !monitor_uses_readline(mon);
|
|
}
|
|
|
|
/*
|
|
* Return the clock to use for recording an event's time.
|
|
* It's QEMU_CLOCK_REALTIME, except for qtests it's
|
|
* QEMU_CLOCK_VIRTUAL, to support testing rate limits.
|
|
* Beware: result is invalid before configure_accelerator().
|
|
*/
|
|
static inline QEMUClockType monitor_get_event_clock(void)
|
|
{
|
|
return qtest_enabled() ? QEMU_CLOCK_VIRTUAL : QEMU_CLOCK_REALTIME;
|
|
}
|
|
|
|
/**
|
|
* Is the current monitor, if any, a QMP monitor?
|
|
*/
|
|
bool monitor_cur_is_qmp(void)
|
|
{
|
|
return cur_mon && monitor_is_qmp(cur_mon);
|
|
}
|
|
|
|
void monitor_read_command(Monitor *mon, int show_prompt)
|
|
{
|
|
if (!mon->rs)
|
|
return;
|
|
|
|
readline_start(mon->rs, "(qemu) ", 0, monitor_command_cb, NULL);
|
|
if (show_prompt)
|
|
readline_show_prompt(mon->rs);
|
|
}
|
|
|
|
int monitor_read_password(Monitor *mon, ReadLineFunc *readline_func,
|
|
void *opaque)
|
|
{
|
|
if (mon->rs) {
|
|
readline_start(mon->rs, "Password: ", 1, readline_func, opaque);
|
|
/* prompt is printed on return from the command handler */
|
|
return 0;
|
|
} else {
|
|
monitor_printf(mon, "terminal does not support password prompting\n");
|
|
return -ENOTTY;
|
|
}
|
|
}
|
|
|
|
static void qmp_request_free(QMPRequest *req)
|
|
{
|
|
qobject_unref(req->id);
|
|
qobject_unref(req->req);
|
|
error_free(req->err);
|
|
g_free(req);
|
|
}
|
|
|
|
/* Caller must hold mon->qmp.qmp_queue_lock */
|
|
static void monitor_qmp_cleanup_req_queue_locked(Monitor *mon)
|
|
{
|
|
while (!g_queue_is_empty(mon->qmp.qmp_requests)) {
|
|
qmp_request_free(g_queue_pop_head(mon->qmp.qmp_requests));
|
|
}
|
|
}
|
|
|
|
static void monitor_qmp_cleanup_queues(Monitor *mon)
|
|
{
|
|
qemu_mutex_lock(&mon->qmp.qmp_queue_lock);
|
|
monitor_qmp_cleanup_req_queue_locked(mon);
|
|
qemu_mutex_unlock(&mon->qmp.qmp_queue_lock);
|
|
}
|
|
|
|
|
|
static void monitor_flush_locked(Monitor *mon);
|
|
|
|
static gboolean monitor_unblocked(GIOChannel *chan, GIOCondition cond,
|
|
void *opaque)
|
|
{
|
|
Monitor *mon = opaque;
|
|
|
|
qemu_mutex_lock(&mon->mon_lock);
|
|
mon->out_watch = 0;
|
|
monitor_flush_locked(mon);
|
|
qemu_mutex_unlock(&mon->mon_lock);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Caller must hold mon->mon_lock */
|
|
static void monitor_flush_locked(Monitor *mon)
|
|
{
|
|
int rc;
|
|
size_t len;
|
|
const char *buf;
|
|
|
|
if (mon->skip_flush) {
|
|
return;
|
|
}
|
|
|
|
buf = qstring_get_str(mon->outbuf);
|
|
len = qstring_get_length(mon->outbuf);
|
|
|
|
if (len && !mon->mux_out) {
|
|
rc = qemu_chr_fe_write(&mon->chr, (const uint8_t *) buf, len);
|
|
if ((rc < 0 && errno != EAGAIN) || (rc == len)) {
|
|
/* all flushed or error */
|
|
qobject_unref(mon->outbuf);
|
|
mon->outbuf = qstring_new();
|
|
return;
|
|
}
|
|
if (rc > 0) {
|
|
/* partial write */
|
|
QString *tmp = qstring_from_str(buf + rc);
|
|
qobject_unref(mon->outbuf);
|
|
mon->outbuf = tmp;
|
|
}
|
|
if (mon->out_watch == 0) {
|
|
mon->out_watch =
|
|
qemu_chr_fe_add_watch(&mon->chr, G_IO_OUT | G_IO_HUP,
|
|
monitor_unblocked, mon);
|
|
}
|
|
}
|
|
}
|
|
|
|
void monitor_flush(Monitor *mon)
|
|
{
|
|
qemu_mutex_lock(&mon->mon_lock);
|
|
monitor_flush_locked(mon);
|
|
qemu_mutex_unlock(&mon->mon_lock);
|
|
}
|
|
|
|
/* flush at every end of line */
|
|
static void monitor_puts(Monitor *mon, const char *str)
|
|
{
|
|
char c;
|
|
|
|
qemu_mutex_lock(&mon->mon_lock);
|
|
for(;;) {
|
|
c = *str++;
|
|
if (c == '\0')
|
|
break;
|
|
if (c == '\n') {
|
|
qstring_append_chr(mon->outbuf, '\r');
|
|
}
|
|
qstring_append_chr(mon->outbuf, c);
|
|
if (c == '\n') {
|
|
monitor_flush_locked(mon);
|
|
}
|
|
}
|
|
qemu_mutex_unlock(&mon->mon_lock);
|
|
}
|
|
|
|
void monitor_vprintf(Monitor *mon, const char *fmt, va_list ap)
|
|
{
|
|
char *buf;
|
|
|
|
if (!mon)
|
|
return;
|
|
|
|
if (monitor_is_qmp(mon)) {
|
|
return;
|
|
}
|
|
|
|
buf = g_strdup_vprintf(fmt, ap);
|
|
monitor_puts(mon, buf);
|
|
g_free(buf);
|
|
}
|
|
|
|
void monitor_printf(Monitor *mon, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
monitor_vprintf(mon, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
int monitor_fprintf(FILE *stream, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
monitor_vprintf((Monitor *)stream, fmt, ap);
|
|
va_end(ap);
|
|
return 0;
|
|
}
|
|
|
|
static void qmp_send_response(Monitor *mon, const QDict *rsp)
|
|
{
|
|
const QObject *data = QOBJECT(rsp);
|
|
QString *json;
|
|
|
|
json = mon->flags & MONITOR_USE_PRETTY ? qobject_to_json_pretty(data) :
|
|
qobject_to_json(data);
|
|
assert(json != NULL);
|
|
|
|
qstring_append_chr(json, '\n');
|
|
monitor_puts(mon, qstring_get_str(json));
|
|
|
|
qobject_unref(json);
|
|
}
|
|
|
|
static MonitorQAPIEventConf monitor_qapi_event_conf[QAPI_EVENT__MAX] = {
|
|
/* Limit guest-triggerable events to 1 per second */
|
|
[QAPI_EVENT_RTC_CHANGE] = { 1000 * SCALE_MS },
|
|
[QAPI_EVENT_WATCHDOG] = { 1000 * SCALE_MS },
|
|
[QAPI_EVENT_BALLOON_CHANGE] = { 1000 * SCALE_MS },
|
|
[QAPI_EVENT_QUORUM_REPORT_BAD] = { 1000 * SCALE_MS },
|
|
[QAPI_EVENT_QUORUM_FAILURE] = { 1000 * SCALE_MS },
|
|
[QAPI_EVENT_VSERPORT_CHANGE] = { 1000 * SCALE_MS },
|
|
};
|
|
|
|
/*
|
|
* Broadcast an event to all monitors.
|
|
* @qdict is the event object. Its member "event" must match @event.
|
|
* Caller must hold monitor_lock.
|
|
*/
|
|
static void monitor_qapi_event_emit(QAPIEvent event, QDict *qdict)
|
|
{
|
|
Monitor *mon;
|
|
|
|
trace_monitor_protocol_event_emit(event, qdict);
|
|
QTAILQ_FOREACH(mon, &mon_list, entry) {
|
|
if (monitor_is_qmp(mon)
|
|
&& mon->qmp.commands != &qmp_cap_negotiation_commands) {
|
|
qmp_send_response(mon, qdict);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void monitor_qapi_event_handler(void *opaque);
|
|
|
|
/*
|
|
* Queue a new event for emission to Monitor instances,
|
|
* applying any rate limiting if required.
|
|
*/
|
|
static void
|
|
monitor_qapi_event_queue_no_reenter(QAPIEvent event, QDict *qdict)
|
|
{
|
|
MonitorQAPIEventConf *evconf;
|
|
MonitorQAPIEventState *evstate;
|
|
|
|
assert(event < QAPI_EVENT__MAX);
|
|
evconf = &monitor_qapi_event_conf[event];
|
|
trace_monitor_protocol_event_queue(event, qdict, evconf->rate);
|
|
|
|
qemu_mutex_lock(&monitor_lock);
|
|
|
|
if (!evconf->rate) {
|
|
/* Unthrottled event */
|
|
monitor_qapi_event_emit(event, qdict);
|
|
} else {
|
|
QDict *data = qobject_to(QDict, qdict_get(qdict, "data"));
|
|
MonitorQAPIEventState key = { .event = event, .data = data };
|
|
|
|
evstate = g_hash_table_lookup(monitor_qapi_event_state, &key);
|
|
assert(!evstate || timer_pending(evstate->timer));
|
|
|
|
if (evstate) {
|
|
/*
|
|
* Timer is pending for (at least) evconf->rate ns after
|
|
* last send. Store event for sending when timer fires,
|
|
* replacing a prior stored event if any.
|
|
*/
|
|
qobject_unref(evstate->qdict);
|
|
evstate->qdict = qobject_ref(qdict);
|
|
} else {
|
|
/*
|
|
* Last send was (at least) evconf->rate ns ago.
|
|
* Send immediately, and arm the timer to call
|
|
* monitor_qapi_event_handler() in evconf->rate ns. Any
|
|
* events arriving before then will be delayed until then.
|
|
*/
|
|
int64_t now = qemu_clock_get_ns(monitor_get_event_clock());
|
|
|
|
monitor_qapi_event_emit(event, qdict);
|
|
|
|
evstate = g_new(MonitorQAPIEventState, 1);
|
|
evstate->event = event;
|
|
evstate->data = qobject_ref(data);
|
|
evstate->qdict = NULL;
|
|
evstate->timer = timer_new_ns(monitor_get_event_clock(),
|
|
monitor_qapi_event_handler,
|
|
evstate);
|
|
g_hash_table_add(monitor_qapi_event_state, evstate);
|
|
timer_mod_ns(evstate->timer, now + evconf->rate);
|
|
}
|
|
}
|
|
|
|
qemu_mutex_unlock(&monitor_lock);
|
|
}
|
|
|
|
void qapi_event_emit(QAPIEvent event, QDict *qdict)
|
|
{
|
|
/*
|
|
* monitor_qapi_event_queue_no_reenter() is not reentrant: it
|
|
* would deadlock on monitor_lock. Work around by queueing
|
|
* events in thread-local storage.
|
|
* TODO: remove this, make it re-enter safe.
|
|
*/
|
|
typedef struct MonitorQapiEvent {
|
|
QAPIEvent event;
|
|
QDict *qdict;
|
|
QSIMPLEQ_ENTRY(MonitorQapiEvent) entry;
|
|
} MonitorQapiEvent;
|
|
static __thread QSIMPLEQ_HEAD(, MonitorQapiEvent) event_queue;
|
|
static __thread bool reentered;
|
|
MonitorQapiEvent *ev;
|
|
|
|
if (!reentered) {
|
|
QSIMPLEQ_INIT(&event_queue);
|
|
}
|
|
|
|
ev = g_new(MonitorQapiEvent, 1);
|
|
ev->qdict = qobject_ref(qdict);
|
|
ev->event = event;
|
|
QSIMPLEQ_INSERT_TAIL(&event_queue, ev, entry);
|
|
if (reentered) {
|
|
return;
|
|
}
|
|
|
|
reentered = true;
|
|
|
|
while ((ev = QSIMPLEQ_FIRST(&event_queue)) != NULL) {
|
|
QSIMPLEQ_REMOVE_HEAD(&event_queue, entry);
|
|
monitor_qapi_event_queue_no_reenter(ev->event, ev->qdict);
|
|
qobject_unref(ev->qdict);
|
|
g_free(ev);
|
|
}
|
|
|
|
reentered = false;
|
|
}
|
|
|
|
/*
|
|
* This function runs evconf->rate ns after sending a throttled
|
|
* event.
|
|
* If another event has since been stored, send it.
|
|
*/
|
|
static void monitor_qapi_event_handler(void *opaque)
|
|
{
|
|
MonitorQAPIEventState *evstate = opaque;
|
|
MonitorQAPIEventConf *evconf = &monitor_qapi_event_conf[evstate->event];
|
|
|
|
trace_monitor_protocol_event_handler(evstate->event, evstate->qdict);
|
|
qemu_mutex_lock(&monitor_lock);
|
|
|
|
if (evstate->qdict) {
|
|
int64_t now = qemu_clock_get_ns(monitor_get_event_clock());
|
|
|
|
monitor_qapi_event_emit(evstate->event, evstate->qdict);
|
|
qobject_unref(evstate->qdict);
|
|
evstate->qdict = NULL;
|
|
timer_mod_ns(evstate->timer, now + evconf->rate);
|
|
} else {
|
|
g_hash_table_remove(monitor_qapi_event_state, evstate);
|
|
qobject_unref(evstate->data);
|
|
timer_free(evstate->timer);
|
|
g_free(evstate);
|
|
}
|
|
|
|
qemu_mutex_unlock(&monitor_lock);
|
|
}
|
|
|
|
static unsigned int qapi_event_throttle_hash(const void *key)
|
|
{
|
|
const MonitorQAPIEventState *evstate = key;
|
|
unsigned int hash = evstate->event * 255;
|
|
|
|
if (evstate->event == QAPI_EVENT_VSERPORT_CHANGE) {
|
|
hash += g_str_hash(qdict_get_str(evstate->data, "id"));
|
|
}
|
|
|
|
if (evstate->event == QAPI_EVENT_QUORUM_REPORT_BAD) {
|
|
hash += g_str_hash(qdict_get_str(evstate->data, "node-name"));
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
static gboolean qapi_event_throttle_equal(const void *a, const void *b)
|
|
{
|
|
const MonitorQAPIEventState *eva = a;
|
|
const MonitorQAPIEventState *evb = b;
|
|
|
|
if (eva->event != evb->event) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (eva->event == QAPI_EVENT_VSERPORT_CHANGE) {
|
|
return !strcmp(qdict_get_str(eva->data, "id"),
|
|
qdict_get_str(evb->data, "id"));
|
|
}
|
|
|
|
if (eva->event == QAPI_EVENT_QUORUM_REPORT_BAD) {
|
|
return !strcmp(qdict_get_str(eva->data, "node-name"),
|
|
qdict_get_str(evb->data, "node-name"));
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void monitor_qapi_event_init(void)
|
|
{
|
|
monitor_qapi_event_state = g_hash_table_new(qapi_event_throttle_hash,
|
|
qapi_event_throttle_equal);
|
|
}
|
|
|
|
static void handle_hmp_command(Monitor *mon, const char *cmdline);
|
|
|
|
static void monitor_iothread_init(void);
|
|
|
|
static void monitor_data_init(Monitor *mon, bool skip_flush,
|
|
bool use_io_thread)
|
|
{
|
|
if (use_io_thread && !mon_iothread) {
|
|
monitor_iothread_init();
|
|
}
|
|
memset(mon, 0, sizeof(Monitor));
|
|
qemu_mutex_init(&mon->mon_lock);
|
|
qemu_mutex_init(&mon->qmp.qmp_queue_lock);
|
|
mon->outbuf = qstring_new();
|
|
/* Use *mon_cmds by default. */
|
|
mon->cmd_table = mon_cmds;
|
|
mon->skip_flush = skip_flush;
|
|
mon->use_io_thread = use_io_thread;
|
|
mon->qmp.qmp_requests = g_queue_new();
|
|
}
|
|
|
|
static void monitor_data_destroy(Monitor *mon)
|
|
{
|
|
g_free(mon->mon_cpu_path);
|
|
qemu_chr_fe_deinit(&mon->chr, false);
|
|
if (monitor_is_qmp(mon)) {
|
|
json_message_parser_destroy(&mon->qmp.parser);
|
|
}
|
|
readline_free(mon->rs);
|
|
qobject_unref(mon->outbuf);
|
|
qemu_mutex_destroy(&mon->mon_lock);
|
|
qemu_mutex_destroy(&mon->qmp.qmp_queue_lock);
|
|
monitor_qmp_cleanup_req_queue_locked(mon);
|
|
g_queue_free(mon->qmp.qmp_requests);
|
|
}
|
|
|
|
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;
|
|
|
|
monitor_data_init(&hmp, true, false);
|
|
|
|
old_mon = cur_mon;
|
|
cur_mon = &hmp;
|
|
|
|
if (has_cpu_index) {
|
|
int ret = monitor_set_cpu(cpu_index);
|
|
if (ret < 0) {
|
|
cur_mon = old_mon;
|
|
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index",
|
|
"a CPU number");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
handle_hmp_command(&hmp, command_line);
|
|
cur_mon = old_mon;
|
|
|
|
qemu_mutex_lock(&hmp.mon_lock);
|
|
if (qstring_get_length(hmp.outbuf) > 0) {
|
|
output = g_strdup(qstring_get_str(hmp.outbuf));
|
|
} else {
|
|
output = g_strdup("");
|
|
}
|
|
qemu_mutex_unlock(&hmp.mon_lock);
|
|
|
|
out:
|
|
monitor_data_destroy(&hmp);
|
|
return output;
|
|
}
|
|
|
|
static int compare_cmd(const char *name, const char *list)
|
|
{
|
|
const char *p, *pstart;
|
|
int len;
|
|
len = strlen(name);
|
|
p = list;
|
|
for(;;) {
|
|
pstart = p;
|
|
p = qemu_strchrnul(p, '|');
|
|
if ((p - pstart) == len && !memcmp(pstart, name, len))
|
|
return 1;
|
|
if (*p == '\0')
|
|
break;
|
|
p++;
|
|
}
|
|
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:
|
|
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 != '\"') {
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Can command @cmd be executed in preconfig state?
|
|
*/
|
|
static bool cmd_can_preconfig(const mon_cmd_t *cmd)
|
|
{
|
|
if (!cmd->flags) {
|
|
return false;
|
|
}
|
|
|
|
return strchr(cmd->flags, 'p');
|
|
}
|
|
|
|
static void help_cmd_dump_one(Monitor *mon,
|
|
const mon_cmd_t *cmd,
|
|
char **prefix_args,
|
|
int prefix_args_nb)
|
|
{
|
|
int i;
|
|
|
|
if (runstate_check(RUN_STATE_PRECONFIG) && !cmd_can_preconfig(cmd)) {
|
|
return;
|
|
}
|
|
|
|
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,
|
|
char **args, int nb_args, int arg_index)
|
|
{
|
|
const mon_cmd_t *cmd;
|
|
size_t i;
|
|
|
|
/* 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) &&
|
|
((!runstate_check(RUN_STATE_PRECONFIG) ||
|
|
cmd_can_preconfig(cmd)))) {
|
|
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);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Command not found */
|
|
monitor_printf(mon, "unknown command: '");
|
|
for (i = 0; i <= arg_index; i++) {
|
|
monitor_printf(mon, "%s%s", args[i], i == arg_index ? "'\n" : " ");
|
|
}
|
|
}
|
|
|
|
static void help_cmd(Monitor *mon, const char *name)
|
|
{
|
|
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)
|
|
{
|
|
help_cmd(mon, qdict_get_try_str(qdict, "name"));
|
|
}
|
|
|
|
static void hmp_trace_event(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *tp_name = qdict_get_str(qdict, "name");
|
|
bool new_state = qdict_get_bool(qdict, "option");
|
|
bool has_vcpu = qdict_haskey(qdict, "vcpu");
|
|
int vcpu = qdict_get_try_int(qdict, "vcpu", 0);
|
|
Error *local_err = NULL;
|
|
|
|
if (vcpu < 0) {
|
|
monitor_printf(mon, "argument vcpu must be positive");
|
|
return;
|
|
}
|
|
|
|
qmp_trace_event_set_state(tp_name, new_state, true, true, has_vcpu, vcpu, &local_err);
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_TRACE_SIMPLE
|
|
static void hmp_trace_file(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *op = qdict_get_try_str(qdict, "op");
|
|
const char *arg = qdict_get_try_str(qdict, "arg");
|
|
|
|
if (!op) {
|
|
st_print_trace_file_status((FILE *)mon, &monitor_fprintf);
|
|
} else if (!strcmp(op, "on")) {
|
|
st_set_trace_file_enabled(true);
|
|
} else if (!strcmp(op, "off")) {
|
|
st_set_trace_file_enabled(false);
|
|
} else if (!strcmp(op, "flush")) {
|
|
st_flush_trace_buffer();
|
|
} else if (!strcmp(op, "set")) {
|
|
if (arg) {
|
|
st_set_trace_file(arg);
|
|
}
|
|
} else {
|
|
monitor_printf(mon, "unexpected argument \"%s\"\n", op);
|
|
help_cmd(mon, "trace-file");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void hmp_info_help(Monitor *mon, const QDict *qdict)
|
|
{
|
|
help_cmd(mon, "info");
|
|
}
|
|
|
|
static void query_commands_cb(QmpCommand *cmd, void *opaque)
|
|
{
|
|
CommandInfoList *info, **list = opaque;
|
|
|
|
if (!cmd->enabled) {
|
|
return;
|
|
}
|
|
|
|
info = g_malloc0(sizeof(*info));
|
|
info->value = g_malloc0(sizeof(*info->value));
|
|
info->value->name = g_strdup(cmd->name);
|
|
info->next = *list;
|
|
*list = info;
|
|
}
|
|
|
|
CommandInfoList *qmp_query_commands(Error **errp)
|
|
{
|
|
CommandInfoList *list = NULL;
|
|
|
|
qmp_for_each_command(cur_mon->qmp.commands, query_commands_cb, &list);
|
|
|
|
return list;
|
|
}
|
|
|
|
EventInfoList *qmp_query_events(Error **errp)
|
|
{
|
|
/*
|
|
* TODO This deprecated command is the only user of
|
|
* QAPIEvent_str() and QAPIEvent_lookup[]. When the command goes,
|
|
* they should go, too.
|
|
*/
|
|
EventInfoList *info, *ev_list = NULL;
|
|
QAPIEvent e;
|
|
|
|
for (e = 0 ; e < QAPI_EVENT__MAX ; e++) {
|
|
const char *event_name = QAPIEvent_str(e);
|
|
assert(event_name != NULL);
|
|
info = g_malloc0(sizeof(*info));
|
|
info->value = g_malloc0(sizeof(*info->value));
|
|
info->value->name = g_strdup(event_name);
|
|
|
|
info->next = ev_list;
|
|
ev_list = info;
|
|
}
|
|
|
|
return ev_list;
|
|
}
|
|
|
|
/*
|
|
* Minor hack: generated marshalling suppressed for this command
|
|
* ('gen': false in the schema) so we can parse the JSON string
|
|
* directly into QObject instead of first parsing it with
|
|
* visit_type_SchemaInfoList() into a SchemaInfoList, then marshal it
|
|
* to QObject with generated output marshallers, every time. Instead,
|
|
* we do it in test-qobject-input-visitor.c, just to make sure
|
|
* qapi-gen.py's output actually conforms to the schema.
|
|
*/
|
|
static void qmp_query_qmp_schema(QDict *qdict, QObject **ret_data,
|
|
Error **errp)
|
|
{
|
|
*ret_data = qobject_from_qlit(&qmp_schema_qlit);
|
|
}
|
|
|
|
static void monitor_init_qmp_commands(void)
|
|
{
|
|
/*
|
|
* Two command lists:
|
|
* - qmp_commands contains all QMP commands
|
|
* - qmp_cap_negotiation_commands contains just
|
|
* "qmp_capabilities", to enforce capability negotiation
|
|
*/
|
|
|
|
qmp_init_marshal(&qmp_commands);
|
|
|
|
qmp_register_command(&qmp_commands, "query-qmp-schema",
|
|
qmp_query_qmp_schema, QCO_ALLOW_PRECONFIG);
|
|
qmp_register_command(&qmp_commands, "device_add", qmp_device_add,
|
|
QCO_NO_OPTIONS);
|
|
qmp_register_command(&qmp_commands, "netdev_add", qmp_netdev_add,
|
|
QCO_NO_OPTIONS);
|
|
|
|
QTAILQ_INIT(&qmp_cap_negotiation_commands);
|
|
qmp_register_command(&qmp_cap_negotiation_commands, "qmp_capabilities",
|
|
qmp_marshal_qmp_capabilities, QCO_ALLOW_PRECONFIG);
|
|
}
|
|
|
|
static bool qmp_oob_enabled(Monitor *mon)
|
|
{
|
|
return mon->qmp.capab[QMP_CAPABILITY_OOB];
|
|
}
|
|
|
|
static void monitor_qmp_caps_reset(Monitor *mon)
|
|
{
|
|
memset(mon->qmp.capab_offered, 0, sizeof(mon->qmp.capab_offered));
|
|
memset(mon->qmp.capab, 0, sizeof(mon->qmp.capab));
|
|
mon->qmp.capab_offered[QMP_CAPABILITY_OOB] = mon->use_io_thread;
|
|
}
|
|
|
|
/*
|
|
* Accept QMP capabilities in @list for @mon.
|
|
* On success, set mon->qmp.capab[], and return true.
|
|
* On error, set @errp, and return false.
|
|
*/
|
|
static bool qmp_caps_accept(Monitor *mon, QMPCapabilityList *list,
|
|
Error **errp)
|
|
{
|
|
GString *unavailable = NULL;
|
|
bool capab[QMP_CAPABILITY__MAX];
|
|
|
|
memset(capab, 0, sizeof(capab));
|
|
|
|
for (; list; list = list->next) {
|
|
if (!mon->qmp.capab_offered[list->value]) {
|
|
if (!unavailable) {
|
|
unavailable = g_string_new(QMPCapability_str(list->value));
|
|
} else {
|
|
g_string_append_printf(unavailable, ", %s",
|
|
QMPCapability_str(list->value));
|
|
}
|
|
}
|
|
capab[list->value] = true;
|
|
}
|
|
|
|
if (unavailable) {
|
|
error_setg(errp, "Capability %s not available", unavailable->str);
|
|
g_string_free(unavailable, true);
|
|
return false;
|
|
}
|
|
|
|
memcpy(mon->qmp.capab, capab, sizeof(capab));
|
|
return true;
|
|
}
|
|
|
|
void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable,
|
|
Error **errp)
|
|
{
|
|
if (cur_mon->qmp.commands == &qmp_commands) {
|
|
error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
|
|
"Capabilities negotiation is already complete, command "
|
|
"ignored");
|
|
return;
|
|
}
|
|
|
|
if (!qmp_caps_accept(cur_mon, enable, errp)) {
|
|
return;
|
|
}
|
|
|
|
cur_mon->qmp.commands = &qmp_commands;
|
|
}
|
|
|
|
/* Set the current CPU defined by the user. Callers must hold BQL. */
|
|
int monitor_set_cpu(int cpu_index)
|
|
{
|
|
CPUState *cpu;
|
|
|
|
cpu = qemu_get_cpu(cpu_index);
|
|
if (cpu == NULL) {
|
|
return -1;
|
|
}
|
|
g_free(cur_mon->mon_cpu_path);
|
|
cur_mon->mon_cpu_path = object_get_canonical_path(OBJECT(cpu));
|
|
return 0;
|
|
}
|
|
|
|
/* Callers must hold BQL. */
|
|
static CPUState *mon_get_cpu_sync(bool synchronize)
|
|
{
|
|
CPUState *cpu;
|
|
|
|
if (cur_mon->mon_cpu_path) {
|
|
cpu = (CPUState *) object_resolve_path_type(cur_mon->mon_cpu_path,
|
|
TYPE_CPU, NULL);
|
|
if (!cpu) {
|
|
g_free(cur_mon->mon_cpu_path);
|
|
cur_mon->mon_cpu_path = NULL;
|
|
}
|
|
}
|
|
if (!cur_mon->mon_cpu_path) {
|
|
if (!first_cpu) {
|
|
return NULL;
|
|
}
|
|
monitor_set_cpu(first_cpu->cpu_index);
|
|
cpu = first_cpu;
|
|
}
|
|
if (synchronize) {
|
|
cpu_synchronize_state(cpu);
|
|
}
|
|
return cpu;
|
|
}
|
|
|
|
CPUState *mon_get_cpu(void)
|
|
{
|
|
return mon_get_cpu_sync(true);
|
|
}
|
|
|
|
CPUArchState *mon_get_cpu_env(void)
|
|
{
|
|
CPUState *cs = mon_get_cpu();
|
|
|
|
return cs ? cs->env_ptr : NULL;
|
|
}
|
|
|
|
int monitor_get_cpu_index(void)
|
|
{
|
|
CPUState *cs = mon_get_cpu_sync(false);
|
|
|
|
return cs ? cs->cpu_index : UNASSIGNED_CPU_INDEX;
|
|
}
|
|
|
|
static void hmp_info_registers(Monitor *mon, const QDict *qdict)
|
|
{
|
|
bool all_cpus = qdict_get_try_bool(qdict, "cpustate_all", false);
|
|
CPUState *cs;
|
|
|
|
if (all_cpus) {
|
|
CPU_FOREACH(cs) {
|
|
monitor_printf(mon, "\nCPU#%d\n", cs->cpu_index);
|
|
cpu_dump_state(cs, (FILE *)mon, monitor_fprintf, CPU_DUMP_FPU);
|
|
}
|
|
} else {
|
|
cs = mon_get_cpu();
|
|
|
|
if (!cs) {
|
|
monitor_printf(mon, "No CPU available\n");
|
|
return;
|
|
}
|
|
|
|
cpu_dump_state(cs, (FILE *)mon, monitor_fprintf, CPU_DUMP_FPU);
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_TCG
|
|
static void hmp_info_jit(Monitor *mon, const QDict *qdict)
|
|
{
|
|
if (!tcg_enabled()) {
|
|
error_report("JIT information is only available with accel=tcg");
|
|
return;
|
|
}
|
|
|
|
dump_exec_info((FILE *)mon, monitor_fprintf);
|
|
dump_drift_info((FILE *)mon, monitor_fprintf);
|
|
}
|
|
|
|
static void hmp_info_opcount(Monitor *mon, const QDict *qdict)
|
|
{
|
|
dump_opcount_info((FILE *)mon, monitor_fprintf);
|
|
}
|
|
#endif
|
|
|
|
static void hmp_info_sync_profile(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int64_t max = qdict_get_try_int(qdict, "max", 10);
|
|
bool mean = qdict_get_try_bool(qdict, "mean", false);
|
|
bool coalesce = !qdict_get_try_bool(qdict, "no_coalesce", false);
|
|
enum QSPSortBy sort_by;
|
|
|
|
sort_by = mean ? QSP_SORT_BY_AVG_WAIT_TIME : QSP_SORT_BY_TOTAL_WAIT_TIME;
|
|
qsp_report((FILE *)mon, monitor_fprintf, max, sort_by, coalesce);
|
|
}
|
|
|
|
static void hmp_info_history(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int i;
|
|
const char *str;
|
|
|
|
if (!mon->rs)
|
|
return;
|
|
i = 0;
|
|
for(;;) {
|
|
str = readline_get_history(mon->rs, i);
|
|
if (!str)
|
|
break;
|
|
monitor_printf(mon, "%d: '%s'\n", i, str);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static void hmp_info_cpustats(Monitor *mon, const QDict *qdict)
|
|
{
|
|
CPUState *cs = mon_get_cpu();
|
|
|
|
if (!cs) {
|
|
monitor_printf(mon, "No CPU available\n");
|
|
return;
|
|
}
|
|
cpu_dump_statistics(cs, (FILE *)mon, &monitor_fprintf, 0);
|
|
}
|
|
|
|
static void hmp_info_trace_events(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *name = qdict_get_try_str(qdict, "name");
|
|
bool has_vcpu = qdict_haskey(qdict, "vcpu");
|
|
int vcpu = qdict_get_try_int(qdict, "vcpu", 0);
|
|
TraceEventInfoList *events;
|
|
TraceEventInfoList *elem;
|
|
Error *local_err = NULL;
|
|
|
|
if (name == NULL) {
|
|
name = "*";
|
|
}
|
|
if (vcpu < 0) {
|
|
monitor_printf(mon, "argument vcpu must be positive");
|
|
return;
|
|
}
|
|
|
|
events = qmp_trace_event_get_state(name, has_vcpu, vcpu, &local_err);
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
return;
|
|
}
|
|
|
|
for (elem = events; elem != NULL; elem = elem->next) {
|
|
monitor_printf(mon, "%s : state %u\n",
|
|
elem->value->name,
|
|
elem->value->state == TRACE_EVENT_STATE_ENABLED ? 1 : 0);
|
|
}
|
|
qapi_free_TraceEventInfoList(events);
|
|
}
|
|
|
|
void qmp_client_migrate_info(const char *protocol, const char *hostname,
|
|
bool has_port, int64_t port,
|
|
bool has_tls_port, int64_t tls_port,
|
|
bool has_cert_subject, const char *cert_subject,
|
|
Error **errp)
|
|
{
|
|
if (strcmp(protocol, "spice") == 0) {
|
|
if (!qemu_using_spice(errp)) {
|
|
return;
|
|
}
|
|
|
|
if (!has_port && !has_tls_port) {
|
|
error_setg(errp, QERR_MISSING_PARAMETER, "port/tls-port");
|
|
return;
|
|
}
|
|
|
|
if (qemu_spice_migrate_info(hostname,
|
|
has_port ? port : -1,
|
|
has_tls_port ? tls_port : -1,
|
|
cert_subject)) {
|
|
error_setg(errp, QERR_UNDEFINED_ERROR);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
|
|
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "protocol", "spice");
|
|
}
|
|
|
|
static void hmp_logfile(Monitor *mon, const QDict *qdict)
|
|
{
|
|
Error *err = NULL;
|
|
|
|
qemu_set_log_filename(qdict_get_str(qdict, "filename"), &err);
|
|
if (err) {
|
|
error_report_err(err);
|
|
}
|
|
}
|
|
|
|
static void hmp_log(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int mask;
|
|
const char *items = qdict_get_str(qdict, "items");
|
|
|
|
if (!strcmp(items, "none")) {
|
|
mask = 0;
|
|
} else {
|
|
mask = qemu_str_to_log_mask(items);
|
|
if (!mask) {
|
|
help_cmd(mon, "log");
|
|
return;
|
|
}
|
|
}
|
|
qemu_set_log(mask);
|
|
}
|
|
|
|
static void hmp_singlestep(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *option = qdict_get_try_str(qdict, "option");
|
|
if (!option || !strcmp(option, "on")) {
|
|
singlestep = 1;
|
|
} else if (!strcmp(option, "off")) {
|
|
singlestep = 0;
|
|
} else {
|
|
monitor_printf(mon, "unexpected option %s\n", option);
|
|
}
|
|
}
|
|
|
|
static void hmp_gdbserver(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *device = qdict_get_try_str(qdict, "device");
|
|
if (!device)
|
|
device = "tcp::" DEFAULT_GDBSTUB_PORT;
|
|
if (gdbserver_start(device) < 0) {
|
|
monitor_printf(mon, "Could not open gdbserver on device '%s'\n",
|
|
device);
|
|
} else if (strcmp(device, "none") == 0) {
|
|
monitor_printf(mon, "Disabled gdbserver\n");
|
|
} else {
|
|
monitor_printf(mon, "Waiting for gdb connection on device '%s'\n",
|
|
device);
|
|
}
|
|
}
|
|
|
|
static void hmp_watchdog_action(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *action = qdict_get_str(qdict, "action");
|
|
if (select_watchdog_action(action) == -1) {
|
|
monitor_printf(mon, "Unknown watchdog action '%s'\n", action);
|
|
}
|
|
}
|
|
|
|
static void monitor_printc(Monitor *mon, int c)
|
|
{
|
|
monitor_printf(mon, "'");
|
|
switch(c) {
|
|
case '\'':
|
|
monitor_printf(mon, "\\'");
|
|
break;
|
|
case '\\':
|
|
monitor_printf(mon, "\\\\");
|
|
break;
|
|
case '\n':
|
|
monitor_printf(mon, "\\n");
|
|
break;
|
|
case '\r':
|
|
monitor_printf(mon, "\\r");
|
|
break;
|
|
default:
|
|
if (c >= 32 && c <= 126) {
|
|
monitor_printf(mon, "%c", c);
|
|
} else {
|
|
monitor_printf(mon, "\\x%02x", c);
|
|
}
|
|
break;
|
|
}
|
|
monitor_printf(mon, "'");
|
|
}
|
|
|
|
static void memory_dump(Monitor *mon, int count, int format, int wsize,
|
|
hwaddr addr, int is_physical)
|
|
{
|
|
int l, line_size, i, max_digits, len;
|
|
uint8_t buf[16];
|
|
uint64_t v;
|
|
CPUState *cs = mon_get_cpu();
|
|
|
|
if (!cs && (format == 'i' || !is_physical)) {
|
|
monitor_printf(mon, "Can not dump without CPU\n");
|
|
return;
|
|
}
|
|
|
|
if (format == 'i') {
|
|
monitor_disas(mon, cs, addr, count, is_physical);
|
|
return;
|
|
}
|
|
|
|
len = wsize * count;
|
|
if (wsize == 1)
|
|
line_size = 8;
|
|
else
|
|
line_size = 16;
|
|
max_digits = 0;
|
|
|
|
switch(format) {
|
|
case 'o':
|
|
max_digits = DIV_ROUND_UP(wsize * 8, 3);
|
|
break;
|
|
default:
|
|
case 'x':
|
|
max_digits = (wsize * 8) / 4;
|
|
break;
|
|
case 'u':
|
|
case 'd':
|
|
max_digits = DIV_ROUND_UP(wsize * 8 * 10, 33);
|
|
break;
|
|
case 'c':
|
|
wsize = 1;
|
|
break;
|
|
}
|
|
|
|
while (len > 0) {
|
|
if (is_physical)
|
|
monitor_printf(mon, TARGET_FMT_plx ":", addr);
|
|
else
|
|
monitor_printf(mon, TARGET_FMT_lx ":", (target_ulong)addr);
|
|
l = len;
|
|
if (l > line_size)
|
|
l = line_size;
|
|
if (is_physical) {
|
|
AddressSpace *as = cs ? cs->as : &address_space_memory;
|
|
MemTxResult r = address_space_read(as, addr,
|
|
MEMTXATTRS_UNSPECIFIED, buf, l);
|
|
if (r != MEMTX_OK) {
|
|
monitor_printf(mon, " Cannot access memory\n");
|
|
break;
|
|
}
|
|
} else {
|
|
if (cpu_memory_rw_debug(cs, addr, buf, l, 0) < 0) {
|
|
monitor_printf(mon, " Cannot access memory\n");
|
|
break;
|
|
}
|
|
}
|
|
i = 0;
|
|
while (i < l) {
|
|
switch(wsize) {
|
|
default:
|
|
case 1:
|
|
v = ldub_p(buf + i);
|
|
break;
|
|
case 2:
|
|
v = lduw_p(buf + i);
|
|
break;
|
|
case 4:
|
|
v = (uint32_t)ldl_p(buf + i);
|
|
break;
|
|
case 8:
|
|
v = ldq_p(buf + i);
|
|
break;
|
|
}
|
|
monitor_printf(mon, " ");
|
|
switch(format) {
|
|
case 'o':
|
|
monitor_printf(mon, "%#*" PRIo64, max_digits, v);
|
|
break;
|
|
case 'x':
|
|
monitor_printf(mon, "0x%0*" PRIx64, max_digits, v);
|
|
break;
|
|
case 'u':
|
|
monitor_printf(mon, "%*" PRIu64, max_digits, v);
|
|
break;
|
|
case 'd':
|
|
monitor_printf(mon, "%*" PRId64, max_digits, v);
|
|
break;
|
|
case 'c':
|
|
monitor_printc(mon, v);
|
|
break;
|
|
}
|
|
i += wsize;
|
|
}
|
|
monitor_printf(mon, "\n");
|
|
addr += l;
|
|
len -= l;
|
|
}
|
|
}
|
|
|
|
static void hmp_memory_dump(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int count = qdict_get_int(qdict, "count");
|
|
int format = qdict_get_int(qdict, "format");
|
|
int size = qdict_get_int(qdict, "size");
|
|
target_long addr = qdict_get_int(qdict, "addr");
|
|
|
|
memory_dump(mon, count, format, size, addr, 0);
|
|
}
|
|
|
|
static void hmp_physical_memory_dump(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int count = qdict_get_int(qdict, "count");
|
|
int format = qdict_get_int(qdict, "format");
|
|
int size = qdict_get_int(qdict, "size");
|
|
hwaddr addr = qdict_get_int(qdict, "addr");
|
|
|
|
memory_dump(mon, count, format, size, addr, 1);
|
|
}
|
|
|
|
static void *gpa2hva(MemoryRegion **p_mr, hwaddr addr, Error **errp)
|
|
{
|
|
MemoryRegionSection mrs = memory_region_find(get_system_memory(),
|
|
addr, 1);
|
|
|
|
if (!mrs.mr) {
|
|
error_setg(errp, "No memory is mapped at address 0x%" HWADDR_PRIx, addr);
|
|
return NULL;
|
|
}
|
|
|
|
if (!memory_region_is_ram(mrs.mr) && !memory_region_is_romd(mrs.mr)) {
|
|
error_setg(errp, "Memory at address 0x%" HWADDR_PRIx "is not RAM", addr);
|
|
memory_region_unref(mrs.mr);
|
|
return NULL;
|
|
}
|
|
|
|
*p_mr = mrs.mr;
|
|
return qemu_map_ram_ptr(mrs.mr->ram_block, mrs.offset_within_region);
|
|
}
|
|
|
|
static void hmp_gpa2hva(Monitor *mon, const QDict *qdict)
|
|
{
|
|
hwaddr addr = qdict_get_int(qdict, "addr");
|
|
Error *local_err = NULL;
|
|
MemoryRegion *mr = NULL;
|
|
void *ptr;
|
|
|
|
ptr = gpa2hva(&mr, addr, &local_err);
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
return;
|
|
}
|
|
|
|
monitor_printf(mon, "Host virtual address for 0x%" HWADDR_PRIx
|
|
" (%s) is %p\n",
|
|
addr, mr->name, ptr);
|
|
|
|
memory_region_unref(mr);
|
|
}
|
|
|
|
#ifdef CONFIG_LINUX
|
|
static uint64_t vtop(void *ptr, Error **errp)
|
|
{
|
|
uint64_t pinfo;
|
|
uint64_t ret = -1;
|
|
uintptr_t addr = (uintptr_t) ptr;
|
|
uintptr_t pagesize = getpagesize();
|
|
off_t offset = addr / pagesize * sizeof(pinfo);
|
|
int fd;
|
|
|
|
fd = open("/proc/self/pagemap", O_RDONLY);
|
|
if (fd == -1) {
|
|
error_setg_errno(errp, errno, "Cannot open /proc/self/pagemap");
|
|
return -1;
|
|
}
|
|
|
|
/* Force copy-on-write if necessary. */
|
|
atomic_add((uint8_t *)ptr, 0);
|
|
|
|
if (pread(fd, &pinfo, sizeof(pinfo), offset) != sizeof(pinfo)) {
|
|
error_setg_errno(errp, errno, "Cannot read pagemap");
|
|
goto out;
|
|
}
|
|
if ((pinfo & (1ull << 63)) == 0) {
|
|
error_setg(errp, "Page not present");
|
|
goto out;
|
|
}
|
|
ret = ((pinfo & 0x007fffffffffffffull) * pagesize) | (addr & (pagesize - 1));
|
|
|
|
out:
|
|
close(fd);
|
|
return ret;
|
|
}
|
|
|
|
static void hmp_gpa2hpa(Monitor *mon, const QDict *qdict)
|
|
{
|
|
hwaddr addr = qdict_get_int(qdict, "addr");
|
|
Error *local_err = NULL;
|
|
MemoryRegion *mr = NULL;
|
|
void *ptr;
|
|
uint64_t physaddr;
|
|
|
|
ptr = gpa2hva(&mr, addr, &local_err);
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
return;
|
|
}
|
|
|
|
physaddr = vtop(ptr, &local_err);
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
} else {
|
|
monitor_printf(mon, "Host physical address for 0x%" HWADDR_PRIx
|
|
" (%s) is 0x%" PRIx64 "\n",
|
|
addr, mr->name, (uint64_t) physaddr);
|
|
}
|
|
|
|
memory_region_unref(mr);
|
|
}
|
|
#endif
|
|
|
|
static void do_print(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int format = qdict_get_int(qdict, "format");
|
|
hwaddr val = qdict_get_int(qdict, "val");
|
|
|
|
switch(format) {
|
|
case 'o':
|
|
monitor_printf(mon, "%#" HWADDR_PRIo, val);
|
|
break;
|
|
case 'x':
|
|
monitor_printf(mon, "%#" HWADDR_PRIx, val);
|
|
break;
|
|
case 'u':
|
|
monitor_printf(mon, "%" HWADDR_PRIu, val);
|
|
break;
|
|
default:
|
|
case 'd':
|
|
monitor_printf(mon, "%" HWADDR_PRId, val);
|
|
break;
|
|
case 'c':
|
|
monitor_printc(mon, val);
|
|
break;
|
|
}
|
|
monitor_printf(mon, "\n");
|
|
}
|
|
|
|
static void hmp_sum(Monitor *mon, const QDict *qdict)
|
|
{
|
|
uint32_t addr;
|
|
uint16_t sum;
|
|
uint32_t start = qdict_get_int(qdict, "start");
|
|
uint32_t size = qdict_get_int(qdict, "size");
|
|
|
|
sum = 0;
|
|
for(addr = start; addr < (start + size); addr++) {
|
|
uint8_t val = address_space_ldub(&address_space_memory, addr,
|
|
MEMTXATTRS_UNSPECIFIED, NULL);
|
|
/* BSD sum algorithm ('sum' Unix command) */
|
|
sum = (sum >> 1) | (sum << 15);
|
|
sum += val;
|
|
}
|
|
monitor_printf(mon, "%05d\n", sum);
|
|
}
|
|
|
|
static int mouse_button_state;
|
|
|
|
static void hmp_mouse_move(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int dx, dy, dz, button;
|
|
const char *dx_str = qdict_get_str(qdict, "dx_str");
|
|
const char *dy_str = qdict_get_str(qdict, "dy_str");
|
|
const char *dz_str = qdict_get_try_str(qdict, "dz_str");
|
|
|
|
dx = strtol(dx_str, NULL, 0);
|
|
dy = strtol(dy_str, NULL, 0);
|
|
qemu_input_queue_rel(NULL, INPUT_AXIS_X, dx);
|
|
qemu_input_queue_rel(NULL, INPUT_AXIS_Y, dy);
|
|
|
|
if (dz_str) {
|
|
dz = strtol(dz_str, NULL, 0);
|
|
if (dz != 0) {
|
|
button = (dz > 0) ? INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
|
|
qemu_input_queue_btn(NULL, button, true);
|
|
qemu_input_event_sync();
|
|
qemu_input_queue_btn(NULL, button, false);
|
|
}
|
|
}
|
|
qemu_input_event_sync();
|
|
}
|
|
|
|
static void hmp_mouse_button(Monitor *mon, const QDict *qdict)
|
|
{
|
|
static uint32_t bmap[INPUT_BUTTON__MAX] = {
|
|
[INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON,
|
|
[INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON,
|
|
[INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON,
|
|
};
|
|
int button_state = qdict_get_int(qdict, "button_state");
|
|
|
|
if (mouse_button_state == button_state) {
|
|
return;
|
|
}
|
|
qemu_input_update_buttons(NULL, bmap, mouse_button_state, button_state);
|
|
qemu_input_event_sync();
|
|
mouse_button_state = button_state;
|
|
}
|
|
|
|
static void hmp_ioport_read(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int size = qdict_get_int(qdict, "size");
|
|
int addr = qdict_get_int(qdict, "addr");
|
|
int has_index = qdict_haskey(qdict, "index");
|
|
uint32_t val;
|
|
int suffix;
|
|
|
|
if (has_index) {
|
|
int index = qdict_get_int(qdict, "index");
|
|
cpu_outb(addr & IOPORTS_MASK, index & 0xff);
|
|
addr++;
|
|
}
|
|
addr &= 0xffff;
|
|
|
|
switch(size) {
|
|
default:
|
|
case 1:
|
|
val = cpu_inb(addr);
|
|
suffix = 'b';
|
|
break;
|
|
case 2:
|
|
val = cpu_inw(addr);
|
|
suffix = 'w';
|
|
break;
|
|
case 4:
|
|
val = cpu_inl(addr);
|
|
suffix = 'l';
|
|
break;
|
|
}
|
|
monitor_printf(mon, "port%c[0x%04x] = %#0*x\n",
|
|
suffix, addr, size * 2, val);
|
|
}
|
|
|
|
static void hmp_ioport_write(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int size = qdict_get_int(qdict, "size");
|
|
int addr = qdict_get_int(qdict, "addr");
|
|
int val = qdict_get_int(qdict, "val");
|
|
|
|
addr &= IOPORTS_MASK;
|
|
|
|
switch (size) {
|
|
default:
|
|
case 1:
|
|
cpu_outb(addr, val);
|
|
break;
|
|
case 2:
|
|
cpu_outw(addr, val);
|
|
break;
|
|
case 4:
|
|
cpu_outl(addr, val);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void hmp_boot_set(Monitor *mon, const QDict *qdict)
|
|
{
|
|
Error *local_err = NULL;
|
|
const char *bootdevice = qdict_get_str(qdict, "bootdevice");
|
|
|
|
qemu_boot_set(bootdevice, &local_err);
|
|
if (local_err) {
|
|
error_report_err(local_err);
|
|
} else {
|
|
monitor_printf(mon, "boot device list now set to %s\n", bootdevice);
|
|
}
|
|
}
|
|
|
|
static void hmp_info_mtree(Monitor *mon, const QDict *qdict)
|
|
{
|
|
bool flatview = qdict_get_try_bool(qdict, "flatview", false);
|
|
bool dispatch_tree = qdict_get_try_bool(qdict, "dispatch_tree", false);
|
|
bool owner = qdict_get_try_bool(qdict, "owner", false);
|
|
|
|
mtree_info((fprintf_function)monitor_printf, mon, flatview, dispatch_tree,
|
|
owner);
|
|
}
|
|
|
|
static void hmp_info_numa(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int i;
|
|
NumaNodeMem *node_mem;
|
|
CpuInfoList *cpu_list, *cpu;
|
|
|
|
cpu_list = qmp_query_cpus(&error_abort);
|
|
node_mem = g_new0(NumaNodeMem, nb_numa_nodes);
|
|
|
|
query_numa_node_mem(node_mem);
|
|
monitor_printf(mon, "%d nodes\n", nb_numa_nodes);
|
|
for (i = 0; i < nb_numa_nodes; i++) {
|
|
monitor_printf(mon, "node %d cpus:", i);
|
|
for (cpu = cpu_list; cpu; cpu = cpu->next) {
|
|
if (cpu->value->has_props && cpu->value->props->has_node_id &&
|
|
cpu->value->props->node_id == i) {
|
|
monitor_printf(mon, " %" PRIi64, cpu->value->CPU);
|
|
}
|
|
}
|
|
monitor_printf(mon, "\n");
|
|
monitor_printf(mon, "node %d size: %" PRId64 " MB\n", i,
|
|
node_mem[i].node_mem >> 20);
|
|
monitor_printf(mon, "node %d plugged: %" PRId64 " MB\n", i,
|
|
node_mem[i].node_plugged_mem >> 20);
|
|
}
|
|
qapi_free_CpuInfoList(cpu_list);
|
|
g_free(node_mem);
|
|
}
|
|
|
|
#ifdef CONFIG_PROFILER
|
|
|
|
int64_t dev_time;
|
|
|
|
static void hmp_info_profile(Monitor *mon, const QDict *qdict)
|
|
{
|
|
static int64_t last_cpu_exec_time;
|
|
int64_t cpu_exec_time;
|
|
int64_t delta;
|
|
|
|
cpu_exec_time = tcg_cpu_exec_time();
|
|
delta = cpu_exec_time - last_cpu_exec_time;
|
|
|
|
monitor_printf(mon, "async time %" PRId64 " (%0.3f)\n",
|
|
dev_time, dev_time / (double)NANOSECONDS_PER_SECOND);
|
|
monitor_printf(mon, "qemu time %" PRId64 " (%0.3f)\n",
|
|
delta, delta / (double)NANOSECONDS_PER_SECOND);
|
|
last_cpu_exec_time = cpu_exec_time;
|
|
dev_time = 0;
|
|
}
|
|
#else
|
|
static void hmp_info_profile(Monitor *mon, const QDict *qdict)
|
|
{
|
|
monitor_printf(mon, "Internal profiler not compiled\n");
|
|
}
|
|
#endif
|
|
|
|
/* Capture support */
|
|
static QLIST_HEAD (capture_list_head, CaptureState) capture_head;
|
|
|
|
static void hmp_info_capture(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int i;
|
|
CaptureState *s;
|
|
|
|
for (s = capture_head.lh_first, i = 0; s; s = s->entries.le_next, ++i) {
|
|
monitor_printf(mon, "[%d]: ", i);
|
|
s->ops.info (s->opaque);
|
|
}
|
|
}
|
|
|
|
static void hmp_stopcapture(Monitor *mon, const QDict *qdict)
|
|
{
|
|
int i;
|
|
int n = qdict_get_int(qdict, "n");
|
|
CaptureState *s;
|
|
|
|
for (s = capture_head.lh_first, i = 0; s; s = s->entries.le_next, ++i) {
|
|
if (i == n) {
|
|
s->ops.destroy (s->opaque);
|
|
QLIST_REMOVE (s, entries);
|
|
g_free (s);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void hmp_wavcapture(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *path = qdict_get_str(qdict, "path");
|
|
int has_freq = qdict_haskey(qdict, "freq");
|
|
int freq = qdict_get_try_int(qdict, "freq", -1);
|
|
int has_bits = qdict_haskey(qdict, "bits");
|
|
int bits = qdict_get_try_int(qdict, "bits", -1);
|
|
int has_channels = qdict_haskey(qdict, "nchannels");
|
|
int nchannels = qdict_get_try_int(qdict, "nchannels", -1);
|
|
CaptureState *s;
|
|
|
|
s = g_malloc0 (sizeof (*s));
|
|
|
|
freq = has_freq ? freq : 44100;
|
|
bits = has_bits ? bits : 16;
|
|
nchannels = has_channels ? nchannels : 2;
|
|
|
|
if (wav_start_capture (s, path, freq, bits, nchannels)) {
|
|
monitor_printf(mon, "Failed to add wave capture\n");
|
|
g_free (s);
|
|
return;
|
|
}
|
|
QLIST_INSERT_HEAD (&capture_head, s, entries);
|
|
}
|
|
|
|
static QAuthZList *find_auth(Monitor *mon, const char *name)
|
|
{
|
|
Object *obj;
|
|
Object *container;
|
|
|
|
container = object_get_objects_root();
|
|
obj = object_resolve_path_component(container, name);
|
|
if (!obj) {
|
|
monitor_printf(mon, "acl: unknown list '%s'\n", name);
|
|
return NULL;
|
|
}
|
|
|
|
return QAUTHZ_LIST(obj);
|
|
}
|
|
|
|
static bool warn_acl;
|
|
static void hmp_warn_acl(void)
|
|
{
|
|
if (warn_acl) {
|
|
return;
|
|
}
|
|
error_report("The acl_show, acl_reset, acl_policy, acl_add, acl_remove "
|
|
"commands are deprecated with no replacement. Authorization "
|
|
"for VNC should be performed using the pluggable QAuthZ "
|
|
"objects");
|
|
warn_acl = true;
|
|
}
|
|
|
|
static void hmp_acl_show(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *aclname = qdict_get_str(qdict, "aclname");
|
|
QAuthZList *auth = find_auth(mon, aclname);
|
|
QAuthZListRuleList *rules;
|
|
size_t i = 0;
|
|
|
|
hmp_warn_acl();
|
|
|
|
if (!auth) {
|
|
return;
|
|
}
|
|
|
|
monitor_printf(mon, "policy: %s\n",
|
|
QAuthZListPolicy_str(auth->policy));
|
|
|
|
rules = auth->rules;
|
|
while (rules) {
|
|
QAuthZListRule *rule = rules->value;
|
|
i++;
|
|
monitor_printf(mon, "%zu: %s %s\n", i,
|
|
QAuthZListPolicy_str(rule->policy),
|
|
rule->match);
|
|
rules = rules->next;
|
|
}
|
|
}
|
|
|
|
static void hmp_acl_reset(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *aclname = qdict_get_str(qdict, "aclname");
|
|
QAuthZList *auth = find_auth(mon, aclname);
|
|
|
|
hmp_warn_acl();
|
|
|
|
if (!auth) {
|
|
return;
|
|
}
|
|
|
|
auth->policy = QAUTHZ_LIST_POLICY_DENY;
|
|
qapi_free_QAuthZListRuleList(auth->rules);
|
|
auth->rules = NULL;
|
|
monitor_printf(mon, "acl: removed all rules\n");
|
|
}
|
|
|
|
static void hmp_acl_policy(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *aclname = qdict_get_str(qdict, "aclname");
|
|
const char *policy = qdict_get_str(qdict, "policy");
|
|
QAuthZList *auth = find_auth(mon, aclname);
|
|
int val;
|
|
Error *err = NULL;
|
|
|
|
hmp_warn_acl();
|
|
|
|
if (!auth) {
|
|
return;
|
|
}
|
|
|
|
val = qapi_enum_parse(&QAuthZListPolicy_lookup,
|
|
policy,
|
|
QAUTHZ_LIST_POLICY_DENY,
|
|
&err);
|
|
if (err) {
|
|
error_free(err);
|
|
monitor_printf(mon, "acl: unknown policy '%s', "
|
|
"expected 'deny' or 'allow'\n", policy);
|
|
} else {
|
|
auth->policy = val;
|
|
if (auth->policy == QAUTHZ_LIST_POLICY_ALLOW) {
|
|
monitor_printf(mon, "acl: policy set to 'allow'\n");
|
|
} else {
|
|
monitor_printf(mon, "acl: policy set to 'deny'\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
static QAuthZListFormat hmp_acl_get_format(const char *match)
|
|
{
|
|
if (strchr(match, '*')) {
|
|
return QAUTHZ_LIST_FORMAT_GLOB;
|
|
} else {
|
|
return QAUTHZ_LIST_FORMAT_EXACT;
|
|
}
|
|
}
|
|
|
|
static void hmp_acl_add(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *aclname = qdict_get_str(qdict, "aclname");
|
|
const char *match = qdict_get_str(qdict, "match");
|
|
const char *policystr = qdict_get_str(qdict, "policy");
|
|
int has_index = qdict_haskey(qdict, "index");
|
|
int index = qdict_get_try_int(qdict, "index", -1);
|
|
QAuthZList *auth = find_auth(mon, aclname);
|
|
Error *err = NULL;
|
|
QAuthZListPolicy policy;
|
|
QAuthZListFormat format;
|
|
size_t i = 0;
|
|
|
|
hmp_warn_acl();
|
|
|
|
if (!auth) {
|
|
return;
|
|
}
|
|
|
|
policy = qapi_enum_parse(&QAuthZListPolicy_lookup,
|
|
policystr,
|
|
QAUTHZ_LIST_POLICY_DENY,
|
|
&err);
|
|
if (err) {
|
|
error_free(err);
|
|
monitor_printf(mon, "acl: unknown policy '%s', "
|
|
"expected 'deny' or 'allow'\n", policystr);
|
|
return;
|
|
}
|
|
|
|
format = hmp_acl_get_format(match);
|
|
|
|
if (has_index && index == 0) {
|
|
monitor_printf(mon, "acl: unable to add acl entry\n");
|
|
return;
|
|
}
|
|
|
|
if (has_index) {
|
|
i = qauthz_list_insert_rule(auth, match, policy,
|
|
format, index - 1, &err);
|
|
} else {
|
|
i = qauthz_list_append_rule(auth, match, policy,
|
|
format, &err);
|
|
}
|
|
if (err) {
|
|
monitor_printf(mon, "acl: unable to add rule: %s",
|
|
error_get_pretty(err));
|
|
error_free(err);
|
|
} else {
|
|
monitor_printf(mon, "acl: added rule at position %zu\n", i + 1);
|
|
}
|
|
}
|
|
|
|
static void hmp_acl_remove(Monitor *mon, const QDict *qdict)
|
|
{
|
|
const char *aclname = qdict_get_str(qdict, "aclname");
|
|
const char *match = qdict_get_str(qdict, "match");
|
|
QAuthZList *auth = find_auth(mon, aclname);
|
|
ssize_t i = 0;
|
|
|
|
hmp_warn_acl();
|
|
|
|
if (!auth) {
|
|
return;
|
|
}
|
|
|
|
i = qauthz_list_delete_rule(auth, match);
|
|
if (i >= 0) {
|
|
monitor_printf(mon, "acl: removed rule at position %zu\n", i + 1);
|
|
} else {
|
|
monitor_printf(mon, "acl: no matching acl entry\n");
|
|
}
|
|
}
|
|
|
|
void qmp_getfd(const char *fdname, Error **errp)
|
|
{
|
|
mon_fd_t *monfd;
|
|
int fd, tmp_fd;
|
|
|
|
fd = qemu_chr_fe_get_msgfd(&cur_mon->chr);
|
|
if (fd == -1) {
|
|
error_setg(errp, QERR_FD_NOT_SUPPLIED);
|
|
return;
|
|
}
|
|
|
|
if (qemu_isdigit(fdname[0])) {
|
|
close(fd);
|
|
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "fdname",
|
|
"a name not starting with a digit");
|
|
return;
|
|
}
|
|
|
|
qemu_mutex_lock(&cur_mon->mon_lock);
|
|
QLIST_FOREACH(monfd, &cur_mon->fds, next) {
|
|
if (strcmp(monfd->name, fdname) != 0) {
|
|
continue;
|
|
}
|
|
|
|
tmp_fd = monfd->fd;
|
|
monfd->fd = fd;
|
|
qemu_mutex_unlock(&cur_mon->mon_lock);
|
|
/* Make sure close() is outside critical section */
|
|
close(tmp_fd);
|
|
return;
|
|
}
|
|
|
|
monfd = g_malloc0(sizeof(mon_fd_t));
|
|
monfd->name = g_strdup(fdname);
|
|
monfd->fd = fd;
|
|
|
|
QLIST_INSERT_HEAD(&cur_mon->fds, monfd, next);
|
|
qemu_mutex_unlock(&cur_mon->mon_lock);
|
|
}
|
|
|
|
void qmp_closefd(const char *fdname, Error **errp)
|
|
{
|
|
mon_fd_t *monfd;
|
|
int tmp_fd;
|
|
|
|
qemu_mutex_lock(&cur_mon->mon_lock);
|
|
QLIST_FOREACH(monfd, &cur_mon->fds, next) {
|
|
if (strcmp(monfd->name, fdname) != 0) {
|
|
continue;
|
|
}
|
|
|
|
QLIST_REMOVE(monfd, next);
|
|
tmp_fd = monfd->fd;
|
|
g_free(monfd->name);
|
|
g_free(monfd);
|
|
qemu_mutex_unlock(&cur_mon->mon_lock);
|
|
/* Make sure close() is outside critical section */
|
|
close(tmp_fd);
|
|
return;
|
|
}
|
|
|
|
qemu_mutex_unlock(&cur_mon->mon_lock);
|
|
error_setg(errp, QERR_FD_NOT_FOUND, fdname);
|
|
}
|
|
|
|
int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp)
|
|
{
|
|
mon_fd_t *monfd;
|
|
|
|
qemu_mutex_lock(&mon->mon_lock);
|
|
QLIST_FOREACH(monfd, &mon->fds, next) {
|
|
int fd;
|
|
|
|
if (strcmp(monfd->name, fdname) != 0) {
|
|
continue;
|
|
}
|
|
|
|
fd = monfd->fd;
|
|
|
|
/* caller takes ownership of fd */
|
|
QLIST_REMOVE(monfd, next);
|
|
g_free(monfd->name);
|
|
g_free(monfd);
|
|
qemu_mutex_unlock(&mon->mon_lock);
|
|
|
|
return fd;
|
|
}
|
|
|
|
qemu_mutex_unlock(&mon->mon_lock);
|
|
error_setg(errp, "File descriptor named '%s' has not been found", fdname);
|
|
return -1;
|
|
}
|
|
|
|
static void monitor_fdset_cleanup(MonFdset *mon_fdset)
|
|
{
|
|
MonFdsetFd *mon_fdset_fd;
|
|
MonFdsetFd *mon_fdset_fd_next;
|
|
|
|
QLIST_FOREACH_SAFE(mon_fdset_fd, &mon_fdset->fds, next, mon_fdset_fd_next) {
|
|
if ((mon_fdset_fd->removed ||
|
|
(QLIST_EMPTY(&mon_fdset->dup_fds) && mon_refcount == 0)) &&
|
|
runstate_is_running()) {
|
|
close(mon_fdset_fd->fd);
|
|
g_free(mon_fdset_fd->opaque);
|
|
QLIST_REMOVE(mon_fdset_fd, next);
|
|
g_free(mon_fdset_fd);
|
|
}
|
|
}
|
|
|
|
if (QLIST_EMPTY(&mon_fdset->fds) && QLIST_EMPTY(&mon_fdset->dup_fds)) {
|
|
QLIST_REMOVE(mon_fdset, next);
|
|
g_free(mon_fdset);
|
|
}
|
|
}
|
|
|
|
static void monitor_fdsets_cleanup(void)
|
|
{
|
|
MonFdset *mon_fdset;
|
|
MonFdset *mon_fdset_next;
|
|
|
|
qemu_mutex_lock(&mon_fdsets_lock);
|
|
QLIST_FOREACH_SAFE(mon_fdset, &mon_fdsets, next, mon_fdset_next) {
|
|
monitor_fdset_cleanup(mon_fdset);
|
|
}
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
}
|
|
|
|
AddfdInfo *qmp_add_fd(bool has_fdset_id, int64_t fdset_id, bool has_opaque,
|
|
const char *opaque, Error **errp)
|
|
{
|
|
int fd;
|
|
Monitor *mon = cur_mon;
|
|
AddfdInfo *fdinfo;
|
|
|
|
fd = qemu_chr_fe_get_msgfd(&mon->chr);
|
|
if (fd == -1) {
|
|
error_setg(errp, QERR_FD_NOT_SUPPLIED);
|
|
goto error;
|
|
}
|
|
|
|
fdinfo = monitor_fdset_add_fd(fd, has_fdset_id, fdset_id,
|
|
has_opaque, opaque, errp);
|
|
if (fdinfo) {
|
|
return fdinfo;
|
|
}
|
|
|
|
error:
|
|
if (fd != -1) {
|
|
close(fd);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void qmp_remove_fd(int64_t fdset_id, bool has_fd, int64_t fd, Error **errp)
|
|
{
|
|
MonFdset *mon_fdset;
|
|
MonFdsetFd *mon_fdset_fd;
|
|
char fd_str[60];
|
|
|
|
qemu_mutex_lock(&mon_fdsets_lock);
|
|
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) {
|
|
if (mon_fdset->id != fdset_id) {
|
|
continue;
|
|
}
|
|
QLIST_FOREACH(mon_fdset_fd, &mon_fdset->fds, next) {
|
|
if (has_fd) {
|
|
if (mon_fdset_fd->fd != fd) {
|
|
continue;
|
|
}
|
|
mon_fdset_fd->removed = true;
|
|
break;
|
|
} else {
|
|
mon_fdset_fd->removed = true;
|
|
}
|
|
}
|
|
if (has_fd && !mon_fdset_fd) {
|
|
goto error;
|
|
}
|
|
monitor_fdset_cleanup(mon_fdset);
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
return;
|
|
}
|
|
|
|
error:
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
if (has_fd) {
|
|
snprintf(fd_str, sizeof(fd_str), "fdset-id:%" PRId64 ", fd:%" PRId64,
|
|
fdset_id, fd);
|
|
} else {
|
|
snprintf(fd_str, sizeof(fd_str), "fdset-id:%" PRId64, fdset_id);
|
|
}
|
|
error_setg(errp, QERR_FD_NOT_FOUND, fd_str);
|
|
}
|
|
|
|
FdsetInfoList *qmp_query_fdsets(Error **errp)
|
|
{
|
|
MonFdset *mon_fdset;
|
|
MonFdsetFd *mon_fdset_fd;
|
|
FdsetInfoList *fdset_list = NULL;
|
|
|
|
qemu_mutex_lock(&mon_fdsets_lock);
|
|
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) {
|
|
FdsetInfoList *fdset_info = g_malloc0(sizeof(*fdset_info));
|
|
FdsetFdInfoList *fdsetfd_list = NULL;
|
|
|
|
fdset_info->value = g_malloc0(sizeof(*fdset_info->value));
|
|
fdset_info->value->fdset_id = mon_fdset->id;
|
|
|
|
QLIST_FOREACH(mon_fdset_fd, &mon_fdset->fds, next) {
|
|
FdsetFdInfoList *fdsetfd_info;
|
|
|
|
fdsetfd_info = g_malloc0(sizeof(*fdsetfd_info));
|
|
fdsetfd_info->value = g_malloc0(sizeof(*fdsetfd_info->value));
|
|
fdsetfd_info->value->fd = mon_fdset_fd->fd;
|
|
if (mon_fdset_fd->opaque) {
|
|
fdsetfd_info->value->has_opaque = true;
|
|
fdsetfd_info->value->opaque = g_strdup(mon_fdset_fd->opaque);
|
|
} else {
|
|
fdsetfd_info->value->has_opaque = false;
|
|
}
|
|
|
|
fdsetfd_info->next = fdsetfd_list;
|
|
fdsetfd_list = fdsetfd_info;
|
|
}
|
|
|
|
fdset_info->value->fds = fdsetfd_list;
|
|
|
|
fdset_info->next = fdset_list;
|
|
fdset_list = fdset_info;
|
|
}
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
|
|
return fdset_list;
|
|
}
|
|
|
|
AddfdInfo *monitor_fdset_add_fd(int fd, bool has_fdset_id, int64_t fdset_id,
|
|
bool has_opaque, const char *opaque,
|
|
Error **errp)
|
|
{
|
|
MonFdset *mon_fdset = NULL;
|
|
MonFdsetFd *mon_fdset_fd;
|
|
AddfdInfo *fdinfo;
|
|
|
|
qemu_mutex_lock(&mon_fdsets_lock);
|
|
if (has_fdset_id) {
|
|
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) {
|
|
/* Break if match found or match impossible due to ordering by ID */
|
|
if (fdset_id <= mon_fdset->id) {
|
|
if (fdset_id < mon_fdset->id) {
|
|
mon_fdset = NULL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mon_fdset == NULL) {
|
|
int64_t fdset_id_prev = -1;
|
|
MonFdset *mon_fdset_cur = QLIST_FIRST(&mon_fdsets);
|
|
|
|
if (has_fdset_id) {
|
|
if (fdset_id < 0) {
|
|
error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "fdset-id",
|
|
"a non-negative value");
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
return NULL;
|
|
}
|
|
/* Use specified fdset ID */
|
|
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) {
|
|
mon_fdset_cur = mon_fdset;
|
|
if (fdset_id < mon_fdset_cur->id) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* Use first available fdset ID */
|
|
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) {
|
|
mon_fdset_cur = mon_fdset;
|
|
if (fdset_id_prev == mon_fdset_cur->id - 1) {
|
|
fdset_id_prev = mon_fdset_cur->id;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
mon_fdset = g_malloc0(sizeof(*mon_fdset));
|
|
if (has_fdset_id) {
|
|
mon_fdset->id = fdset_id;
|
|
} else {
|
|
mon_fdset->id = fdset_id_prev + 1;
|
|
}
|
|
|
|
/* The fdset list is ordered by fdset ID */
|
|
if (!mon_fdset_cur) {
|
|
QLIST_INSERT_HEAD(&mon_fdsets, mon_fdset, next);
|
|
} else if (mon_fdset->id < mon_fdset_cur->id) {
|
|
QLIST_INSERT_BEFORE(mon_fdset_cur, mon_fdset, next);
|
|
} else {
|
|
QLIST_INSERT_AFTER(mon_fdset_cur, mon_fdset, next);
|
|
}
|
|
}
|
|
|
|
mon_fdset_fd = g_malloc0(sizeof(*mon_fdset_fd));
|
|
mon_fdset_fd->fd = fd;
|
|
mon_fdset_fd->removed = false;
|
|
if (has_opaque) {
|
|
mon_fdset_fd->opaque = g_strdup(opaque);
|
|
}
|
|
QLIST_INSERT_HEAD(&mon_fdset->fds, mon_fdset_fd, next);
|
|
|
|
fdinfo = g_malloc0(sizeof(*fdinfo));
|
|
fdinfo->fdset_id = mon_fdset->id;
|
|
fdinfo->fd = mon_fdset_fd->fd;
|
|
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
return fdinfo;
|
|
}
|
|
|
|
int monitor_fdset_get_fd(int64_t fdset_id, int flags)
|
|
{
|
|
#ifdef _WIN32
|
|
return -ENOENT;
|
|
#else
|
|
MonFdset *mon_fdset;
|
|
MonFdsetFd *mon_fdset_fd;
|
|
int mon_fd_flags;
|
|
int ret;
|
|
|
|
qemu_mutex_lock(&mon_fdsets_lock);
|
|
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) {
|
|
if (mon_fdset->id != fdset_id) {
|
|
continue;
|
|
}
|
|
QLIST_FOREACH(mon_fdset_fd, &mon_fdset->fds, next) {
|
|
mon_fd_flags = fcntl(mon_fdset_fd->fd, F_GETFL);
|
|
if (mon_fd_flags == -1) {
|
|
ret = -errno;
|
|
goto out;
|
|
}
|
|
|
|
if ((flags & O_ACCMODE) == (mon_fd_flags & O_ACCMODE)) {
|
|
ret = mon_fdset_fd->fd;
|
|
goto out;
|
|
}
|
|
}
|
|
ret = -EACCES;
|
|
goto out;
|
|
}
|
|
ret = -ENOENT;
|
|
|
|
out:
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
return ret;
|
|
#endif
|
|
}
|
|
|
|
int monitor_fdset_dup_fd_add(int64_t fdset_id, int dup_fd)
|
|
{
|
|
MonFdset *mon_fdset;
|
|
MonFdsetFd *mon_fdset_fd_dup;
|
|
|
|
qemu_mutex_lock(&mon_fdsets_lock);
|
|
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) {
|
|
if (mon_fdset->id != fdset_id) {
|
|
continue;
|
|
}
|
|
QLIST_FOREACH(mon_fdset_fd_dup, &mon_fdset->dup_fds, next) {
|
|
if (mon_fdset_fd_dup->fd == dup_fd) {
|
|
goto err;
|
|
}
|
|
}
|
|
mon_fdset_fd_dup = g_malloc0(sizeof(*mon_fdset_fd_dup));
|
|
mon_fdset_fd_dup->fd = dup_fd;
|
|
QLIST_INSERT_HEAD(&mon_fdset->dup_fds, mon_fdset_fd_dup, next);
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
return 0;
|
|
}
|
|
|
|
err:
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
return -1;
|
|
}
|
|
|
|
static int monitor_fdset_dup_fd_find_remove(int dup_fd, bool remove)
|
|
{
|
|
MonFdset *mon_fdset;
|
|
MonFdsetFd *mon_fdset_fd_dup;
|
|
|
|
qemu_mutex_lock(&mon_fdsets_lock);
|
|
QLIST_FOREACH(mon_fdset, &mon_fdsets, next) {
|
|
QLIST_FOREACH(mon_fdset_fd_dup, &mon_fdset->dup_fds, next) {
|
|
if (mon_fdset_fd_dup->fd == dup_fd) {
|
|
if (remove) {
|
|
QLIST_REMOVE(mon_fdset_fd_dup, next);
|
|
if (QLIST_EMPTY(&mon_fdset->dup_fds)) {
|
|
monitor_fdset_cleanup(mon_fdset);
|
|
}
|
|
goto err;
|
|
} else {
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
return mon_fdset->id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
err:
|
|
qemu_mutex_unlock(&mon_fdsets_lock);
|
|
return -1;
|
|
}
|
|
|
|
int monitor_fdset_dup_fd_find(int dup_fd)
|
|
{
|
|
return monitor_fdset_dup_fd_find_remove(dup_fd, false);
|
|
}
|
|
|
|
void monitor_fdset_dup_fd_remove(int dup_fd)
|
|
{
|
|
monitor_fdset_dup_fd_find_remove(dup_fd, true);
|
|
}
|
|
|
|
int monitor_fd_param(Monitor *mon, const char *fdname, Error **errp)
|
|
{
|
|
int fd;
|
|
Error *local_err = NULL;
|
|
|
|
if (!qemu_isdigit(fdname[0]) && mon) {
|
|
fd = monitor_get_fd(mon, fdname, &local_err);
|
|
} else {
|
|
fd = qemu_parse_fd(fdname);
|
|
if (fd == -1) {
|
|
error_setg(&local_err, "Invalid file descriptor number '%s'",
|
|
fdname);
|
|
}
|
|
}
|
|
if (local_err) {
|
|
error_propagate(errp, local_err);
|
|
assert(fd == -1);
|
|
} else {
|
|
assert(fd != -1);
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
/* Please update hmp-commands.hx when adding or changing commands */
|
|
static mon_cmd_t info_cmds[] = {
|
|
#include "hmp-commands-info.h"
|
|
{ NULL, NULL, },
|
|
};
|
|
|
|
/* mon_cmds and info_cmds would be sorted at runtime */
|
|
static mon_cmd_t mon_cmds[] = {
|
|
#include "hmp-commands.h"
|
|
{ NULL, NULL, },
|
|
};
|
|
|
|
/*******************************************************************/
|
|
|
|
static const char *pch;
|
|
static sigjmp_buf expr_env;
|
|
|
|
|
|
static void GCC_FMT_ATTR(2, 3) QEMU_NORETURN
|
|
expr_error(Monitor *mon, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
monitor_vprintf(mon, fmt, ap);
|
|
monitor_printf(mon, "\n");
|
|
va_end(ap);
|
|
siglongjmp(expr_env, 1);
|
|
}
|
|
|
|
/* return 0 if OK, -1 if not found */
|
|
static int get_monitor_def(target_long *pval, const char *name)
|
|
{
|
|
const MonitorDef *md = target_monitor_defs();
|
|
CPUState *cs = mon_get_cpu();
|
|
void *ptr;
|
|
uint64_t tmp = 0;
|
|
int ret;
|
|
|
|
if (cs == NULL || md == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
for(; md->name != NULL; md++) {
|
|
if (compare_cmd(name, md->name)) {
|
|
if (md->get_value) {
|
|
*pval = md->get_value(md, md->offset);
|
|
} else {
|
|
CPUArchState *env = mon_get_cpu_env();
|
|
ptr = (uint8_t *)env + md->offset;
|
|
switch(md->type) {
|
|
case MD_I32:
|
|
*pval = *(int32_t *)ptr;
|
|
break;
|
|
case MD_TLONG:
|
|
*pval = *(target_long *)ptr;
|
|
break;
|
|
default:
|
|
*pval = 0;
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ret = target_get_monitor_def(cs, name, &tmp);
|
|
if (!ret) {
|
|
*pval = (target_long) tmp;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void next(void)
|
|
{
|
|
if (*pch != '\0') {
|
|
pch++;
|
|
while (qemu_isspace(*pch))
|
|
pch++;
|
|
}
|
|
}
|
|
|
|
static int64_t expr_sum(Monitor *mon);
|
|
|
|
static int64_t expr_unary(Monitor *mon)
|
|
{
|
|
int64_t n;
|
|
char *p;
|
|
int ret;
|
|
|
|
switch(*pch) {
|
|
case '+':
|
|
next();
|
|
n = expr_unary(mon);
|
|
break;
|
|
case '-':
|
|
next();
|
|
n = -expr_unary(mon);
|
|
break;
|
|
case '~':
|
|
next();
|
|
n = ~expr_unary(mon);
|
|
break;
|
|
case '(':
|
|
next();
|
|
n = expr_sum(mon);
|
|
if (*pch != ')') {
|
|
expr_error(mon, "')' expected");
|
|
}
|
|
next();
|
|
break;
|
|
case '\'':
|
|
pch++;
|
|
if (*pch == '\0')
|
|
expr_error(mon, "character constant expected");
|
|
n = *pch;
|
|
pch++;
|
|
if (*pch != '\'')
|
|
expr_error(mon, "missing terminating \' character");
|
|
next();
|
|
break;
|
|
case '$':
|
|
{
|
|
char buf[128], *q;
|
|
target_long reg=0;
|
|
|
|
pch++;
|
|
q = buf;
|
|
while ((*pch >= 'a' && *pch <= 'z') ||
|
|
(*pch >= 'A' && *pch <= 'Z') ||
|
|
(*pch >= '0' && *pch <= '9') ||
|
|
*pch == '_' || *pch == '.') {
|
|
if ((q - buf) < sizeof(buf) - 1)
|
|
*q++ = *pch;
|
|
pch++;
|
|
}
|
|
while (qemu_isspace(*pch))
|
|
pch++;
|
|
*q = 0;
|
|
ret = get_monitor_def(®, buf);
|
|
if (ret < 0)
|
|
expr_error(mon, "unknown register");
|
|
n = reg;
|
|
}
|
|
break;
|
|
case '\0':
|
|
expr_error(mon, "unexpected end of expression");
|
|
n = 0;
|
|
break;
|
|
default:
|
|
errno = 0;
|
|
n = strtoull(pch, &p, 0);
|
|
if (errno == ERANGE) {
|
|
expr_error(mon, "number too large");
|
|
}
|
|
if (pch == p) {
|
|
expr_error(mon, "invalid char '%c' in expression", *p);
|
|
}
|
|
pch = p;
|
|
while (qemu_isspace(*pch))
|
|
pch++;
|
|
break;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
|
|
static int64_t expr_prod(Monitor *mon)
|
|
{
|
|
int64_t val, val2;
|
|
int op;
|
|
|
|
val = expr_unary(mon);
|
|
for(;;) {
|
|
op = *pch;
|
|
if (op != '*' && op != '/' && op != '%')
|
|
break;
|
|
next();
|
|
val2 = expr_unary(mon);
|
|
switch(op) {
|
|
default:
|
|
case '*':
|
|
val *= val2;
|
|
break;
|
|
case '/':
|
|
case '%':
|
|
if (val2 == 0)
|
|
expr_error(mon, "division by zero");
|
|
if (op == '/')
|
|
val /= val2;
|
|
else
|
|
val %= val2;
|
|
break;
|
|
}
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static int64_t expr_logic(Monitor *mon)
|
|
{
|
|
int64_t val, val2;
|
|
int op;
|
|
|
|
val = expr_prod(mon);
|
|
for(;;) {
|
|
op = *pch;
|
|
if (op != '&' && op != '|' && op != '^')
|
|
break;
|
|
next();
|
|
val2 = expr_prod(mon);
|
|
switch(op) {
|
|
default:
|
|
case '&':
|
|
val &= val2;
|
|
break;
|
|
case '|':
|
|
val |= val2;
|
|
break;
|
|
case '^':
|
|
val ^= val2;
|
|
break;
|
|
}
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static int64_t expr_sum(Monitor *mon)
|
|
{
|
|
int64_t val, val2;
|
|
int op;
|
|
|
|
val = expr_logic(mon);
|
|
for(;;) {
|
|
op = *pch;
|
|
if (op != '+' && op != '-')
|
|
break;
|
|
next();
|
|
val2 = expr_logic(mon);
|
|
if (op == '+')
|
|
val += val2;
|
|
else
|
|
val -= val2;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static int get_expr(Monitor *mon, int64_t *pval, const char **pp)
|
|
{
|
|
pch = *pp;
|
|
if (sigsetjmp(expr_env, 0)) {
|
|
*pp = pch;
|
|
return -1;
|
|
}
|
|
while (qemu_isspace(*pch))
|
|
pch++;
|
|
*pval = expr_sum(mon);
|
|
*pp = pch;
|
|
return 0;
|
|
}
|
|
|
|
static int get_double(Monitor *mon, double *pval, const char **pp)
|
|
{
|
|
const char *p = *pp;
|
|
char *tailp;
|
|
double d;
|
|
|
|
d = strtod(p, &tailp);
|
|
if (tailp == p) {
|
|
monitor_printf(mon, "Number expected\n");
|
|
return -1;
|
|
}
|
|
if (d != d || d - d != 0) {
|
|
/* NaN or infinity */
|
|
monitor_printf(mon, "Bad number\n");
|
|
return -1;
|
|
}
|
|
*pval = d;
|
|
*pp = tailp;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Store the command-name in cmdname, and return a pointer to
|
|
* the remaining of the command string.
|
|
*/
|
|
static const char *get_command_name(const char *cmdline,
|
|
char *cmdname, size_t nlen)
|
|
{
|
|
size_t len;
|
|
const char *p, *pstart;
|
|
|
|
p = cmdline;
|
|
while (qemu_isspace(*p))
|
|
p++;
|
|
if (*p == '\0')
|
|
return NULL;
|
|
pstart = p;
|
|
while (*p != '\0' && *p != '/' && !qemu_isspace(*p))
|
|
p++;
|
|
len = p - pstart;
|
|
if (len > nlen - 1)
|
|
len = nlen - 1;
|
|
memcpy(cmdname, pstart, len);
|
|
cmdname[len] = '\0';
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* Read key of 'type' into 'key' and return the current
|
|
* 'type' pointer.
|
|
*/
|
|
static char *key_get_info(const char *type, char **key)
|
|
{
|
|
size_t len;
|
|
char *p, *str;
|
|
|
|
if (*type == ',')
|
|
type++;
|
|
|
|
p = strchr(type, ':');
|
|
if (!p) {
|
|
*key = NULL;
|
|
return NULL;
|
|
}
|
|
len = p - type;
|
|
|
|
str = g_malloc(len + 1);
|
|
memcpy(str, type, len);
|
|
str[len] = '\0';
|
|
|
|
*key = str;
|
|
return ++p;
|
|
}
|
|
|
|
static int default_fmt_format = 'x';
|
|
static int default_fmt_size = 4;
|
|
|
|
static int is_valid_option(const char *c, const char *typestr)
|
|
{
|
|
char option[3];
|
|
|
|
option[0] = '-';
|
|
option[1] = *c;
|
|
option[2] = '\0';
|
|
|
|
typestr = strstr(typestr, option);
|
|
return (typestr != NULL);
|
|
}
|
|
|
|
static const mon_cmd_t *search_dispatch_table(const mon_cmd_t *disp_table,
|
|
const char *cmdname)
|
|
{
|
|
const mon_cmd_t *cmd;
|
|
|
|
for (cmd = disp_table; cmd->name != NULL; cmd++) {
|
|
if (compare_cmd(cmdname, cmd->name)) {
|
|
return cmd;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Parse command name from @cmdp according to command table @table.
|
|
* If blank, return NULL.
|
|
* Else, if no valid command can be found, report to @mon, and return
|
|
* NULL.
|
|
* Else, change @cmdp to point right behind the name, and return its
|
|
* command table entry.
|
|
* Do not assume the return value points into @table! It doesn't when
|
|
* the command is found in a sub-command table.
|
|
*/
|
|
static const mon_cmd_t *monitor_parse_command(Monitor *mon,
|
|
const char *cmdp_start,
|
|
const char **cmdp,
|
|
mon_cmd_t *table)
|
|
{
|
|
const char *p;
|
|
const mon_cmd_t *cmd;
|
|
char cmdname[256];
|
|
|
|
/* extract the command name */
|
|
p = get_command_name(*cmdp, cmdname, sizeof(cmdname));
|
|
if (!p)
|
|
return NULL;
|
|
|
|
cmd = search_dispatch_table(table, cmdname);
|
|
if (!cmd) {
|
|
monitor_printf(mon, "unknown command: '%.*s'\n",
|
|
(int)(p - cmdp_start), cmdp_start);
|
|
return NULL;
|
|
}
|
|
if (runstate_check(RUN_STATE_PRECONFIG) && !cmd_can_preconfig(cmd)) {
|
|
monitor_printf(mon, "Command '%.*s' not available with -preconfig "
|
|
"until after exit_preconfig.\n",
|
|
(int)(p - cmdp_start), cmdp_start);
|
|
return NULL;
|
|
}
|
|
|
|
/* filter out following useless space */
|
|
while (qemu_isspace(*p)) {
|
|
p++;
|
|
}
|
|
|
|
*cmdp = p;
|
|
/* search sub command */
|
|
if (cmd->sub_table != NULL && *p != '\0') {
|
|
return monitor_parse_command(mon, cmdp_start, cmdp, cmd->sub_table);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
/*
|
|
* Parse arguments for @cmd.
|
|
* If it can't be parsed, report to @mon, and return NULL.
|
|
* Else, insert command arguments into a QDict, and return it.
|
|
* Note: On success, caller has to free the QDict structure.
|
|
*/
|
|
|
|
static QDict *monitor_parse_arguments(Monitor *mon,
|
|
const char **endp,
|
|
const mon_cmd_t *cmd)
|
|
{
|
|
const char *typestr;
|
|
char *key;
|
|
int c;
|
|
const char *p = *endp;
|
|
char buf[1024];
|
|
QDict *qdict = qdict_new();
|
|
|
|
/* parse the parameters */
|
|
typestr = cmd->args_type;
|
|
for(;;) {
|
|
typestr = key_get_info(typestr, &key);
|
|
if (!typestr)
|
|
break;
|
|
c = *typestr;
|
|
typestr++;
|
|
switch(c) {
|
|
case 'F':
|
|
case 'B':
|
|
case 's':
|
|
{
|
|
int ret;
|
|
|
|
while (qemu_isspace(*p))
|
|
p++;
|
|
if (*typestr == '?') {
|
|
typestr++;
|
|
if (*p == '\0') {
|
|
/* no optional string: NULL argument */
|
|
break;
|
|
}
|
|
}
|
|
ret = get_str(buf, sizeof(buf), &p);
|
|
if (ret < 0) {
|
|
switch(c) {
|
|
case 'F':
|
|
monitor_printf(mon, "%s: filename expected\n",
|
|
cmd->name);
|
|
break;
|
|
case 'B':
|
|
monitor_printf(mon, "%s: block device name expected\n",
|
|
cmd->name);
|
|
break;
|
|
default:
|
|
monitor_printf(mon, "%s: string expected\n", cmd->name);
|
|
break;
|
|
}
|
|
goto fail;
|
|
}
|
|
qdict_put_str(qdict, key, buf);
|
|
}
|
|
break;
|
|
case 'O':
|
|
{
|
|
QemuOptsList *opts_list;
|
|
QemuOpts *opts;
|
|
|
|
opts_list = qemu_find_opts(key);
|
|
if (!opts_list || opts_list->desc->name) {
|
|
goto bad_type;
|
|
}
|
|
while (qemu_isspace(*p)) {
|
|
p++;
|
|
}
|
|
if (!*p)
|
|
break;
|
|
if (get_str(buf, sizeof(buf), &p) < 0) {
|
|
goto fail;
|
|
}
|
|
opts = qemu_opts_parse_noisily(opts_list, buf, true);
|
|
if (!opts) {
|
|
goto fail;
|
|
}
|
|
qemu_opts_to_qdict(opts, qdict);
|
|
qemu_opts_del(opts);
|
|
}
|
|
break;
|
|
case '/':
|
|
{
|
|
int count, format, size;
|
|
|
|
while (qemu_isspace(*p))
|
|
p++;
|
|
if (*p == '/') {
|
|
/* format found */
|
|
p++;
|
|
count = 1;
|
|
if (qemu_isdigit(*p)) {
|
|
count = 0;
|
|
while (qemu_isdigit(*p)) {
|
|
count = count * 10 + (*p - '0');
|
|
p++;
|
|
}
|
|
}
|
|
size = -1;
|
|
format = -1;
|
|
for(;;) {
|
|
switch(*p) {
|
|
case 'o':
|
|
case 'd':
|
|
case 'u':
|
|
case 'x':
|
|
case 'i':
|
|
case 'c':
|
|
format = *p++;
|
|
break;
|
|
case 'b':
|
|
size = 1;
|
|
p++;
|
|
break;
|
|
case 'h':
|
|
size = 2;
|
|
p++;
|
|
break;
|
|
case 'w':
|
|
size = 4;
|
|
p++;
|
|
break;
|
|
case 'g':
|
|
case 'L':
|
|
size = 8;
|
|
p++;
|
|
break;
|
|
default:
|
|
goto next;
|
|
}
|
|
}
|
|
next:
|
|
if (*p != '\0' && !qemu_isspace(*p)) {
|
|
monitor_printf(mon, "invalid char in format: '%c'\n",
|
|
*p);
|
|
goto fail;
|
|
}
|
|
if (format < 0)
|
|
format = default_fmt_format;
|
|
if (format != 'i') {
|
|
/* for 'i', not specifying a size gives -1 as size */
|
|
if (size < 0)
|
|
size = default_fmt_size;
|
|
default_fmt_size = size;
|
|
}
|
|
default_fmt_format = format;
|
|
} else {
|
|
count = 1;
|
|
format = default_fmt_format;
|
|
if (format != 'i') {
|
|
size = default_fmt_size;
|
|
} else {
|
|
size = -1;
|
|
}
|
|
}
|
|
qdict_put_int(qdict, "count", count);
|
|
qdict_put_int(qdict, "format", format);
|
|
qdict_put_int(qdict, "size", size);
|
|
}
|
|
break;
|
|
case 'i':
|
|
case 'l':
|
|
case 'M':
|
|
{
|
|
int64_t val;
|
|
|
|
while (qemu_isspace(*p))
|
|
p++;
|
|
if (*typestr == '?' || *typestr == '.') {
|
|
if (*typestr == '?') {
|
|
if (*p == '\0') {
|
|
typestr++;
|
|
break;
|
|
}
|
|
} else {
|
|
if (*p == '.') {
|
|
p++;
|
|
while (qemu_isspace(*p))
|
|
p++;
|
|
} else {
|
|
typestr++;
|
|
break;
|
|
}
|
|
}
|
|
typestr++;
|
|
}
|
|
if (get_expr(mon, &val, &p))
|
|
goto fail;
|
|
/* Check if 'i' is greater than 32-bit */
|
|
if ((c == 'i') && ((val >> 32) & 0xffffffff)) {
|
|
monitor_printf(mon, "\'%s\' has failed: ", cmd->name);
|
|
monitor_printf(mon, "integer is for 32-bit values\n");
|
|
goto fail;
|
|
} else if (c == 'M') {
|
|
if (val < 0) {
|
|
monitor_printf(mon, "enter a positive value\n");
|
|
goto fail;
|
|
}
|
|
val *= MiB;
|
|
}
|
|
qdict_put_int(qdict, key, val);
|
|
}
|
|
break;
|
|
case 'o':
|
|
{
|
|
int ret;
|
|
uint64_t val;
|
|
const char *end;
|
|
|
|
while (qemu_isspace(*p)) {
|
|
p++;
|
|
}
|
|
if (*typestr == '?') {
|
|
typestr++;
|
|
if (*p == '\0') {
|
|
break;
|
|
}
|
|
}
|
|
ret = qemu_strtosz_MiB(p, &end, &val);
|
|
if (ret < 0 || val > INT64_MAX) {
|
|
monitor_printf(mon, "invalid size\n");
|
|
goto fail;
|
|
}
|
|
qdict_put_int(qdict, key, val);
|
|
p = end;
|
|
}
|
|
break;
|
|
case 'T':
|
|
{
|
|
double val;
|
|
|
|
while (qemu_isspace(*p))
|
|
p++;
|
|
if (*typestr == '?') {
|
|
typestr++;
|
|
if (*p == '\0') {
|
|
break;
|
|
}
|
|
}
|
|
if (get_double(mon, &val, &p) < 0) {
|
|
goto fail;
|
|
}
|
|
if (p[0] && p[1] == 's') {
|
|
switch (*p) {
|
|
case 'm':
|
|
val /= 1e3; p += 2; break;
|
|
case 'u':
|
|
val /= 1e6; p += 2; break;
|
|
case 'n':
|
|
val /= 1e9; p += 2; break;
|
|
}
|
|
}
|
|
if (*p && !qemu_isspace(*p)) {
|
|
monitor_printf(mon, "Unknown unit suffix\n");
|
|
goto fail;
|
|
}
|
|
qdict_put(qdict, key, qnum_from_double(val));
|
|
}
|
|
break;
|
|
case 'b':
|
|
{
|
|
const char *beg;
|
|
bool val;
|
|
|
|
while (qemu_isspace(*p)) {
|
|
p++;
|
|
}
|
|
beg = p;
|
|
while (qemu_isgraph(*p)) {
|
|
p++;
|
|
}
|
|
if (p - beg == 2 && !memcmp(beg, "on", p - beg)) {
|
|
val = true;
|
|
} else if (p - beg == 3 && !memcmp(beg, "off", p - beg)) {
|
|
val = false;
|
|
} else {
|
|
monitor_printf(mon, "Expected 'on' or 'off'\n");
|
|
goto fail;
|
|
}
|
|
qdict_put_bool(qdict, key, val);
|
|
}
|
|
break;
|
|
case '-':
|
|
{
|
|
const char *tmp = p;
|
|
int skip_key = 0;
|
|
/* option */
|
|
|
|
c = *typestr++;
|
|
if (c == '\0')
|
|
goto bad_type;
|
|
while (qemu_isspace(*p))
|
|
p++;
|
|
if (*p == '-') {
|
|
p++;
|
|
if(c != *p) {
|
|
if(!is_valid_option(p, typestr)) {
|
|
|
|
monitor_printf(mon, "%s: unsupported option -%c\n",
|
|
cmd->name, *p);
|
|
goto fail;
|
|
} else {
|
|
skip_key = 1;
|
|
}
|
|
}
|
|
if(skip_key) {
|
|
p = tmp;
|
|
} else {
|
|
/* has option */
|
|
p++;
|
|
qdict_put_bool(qdict, key, true);
|
|
}
|
|
}
|
|
}
|
|
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",
|
|
cmd->name);
|
|
goto fail;
|
|
}
|
|
qdict_put_str(qdict, key, p);
|
|
p += len;
|
|
}
|
|
break;
|
|
default:
|
|
bad_type:
|
|
monitor_printf(mon, "%s: unknown type '%c'\n", cmd->name, c);
|
|
goto fail;
|
|
}
|
|
g_free(key);
|
|
key = NULL;
|
|
}
|
|
/* check that all arguments were parsed */
|
|
while (qemu_isspace(*p))
|
|
p++;
|
|
if (*p != '\0') {
|
|
monitor_printf(mon, "%s: extraneous characters at the end of line\n",
|
|
cmd->name);
|
|
goto fail;
|
|
}
|
|
|
|
return qdict;
|
|
|
|
fail:
|
|
qobject_unref(qdict);
|
|
g_free(key);
|
|
return NULL;
|
|
}
|
|
|
|
static void handle_hmp_command(Monitor *mon, const char *cmdline)
|
|
{
|
|
QDict *qdict;
|
|
const mon_cmd_t *cmd;
|
|
const char *cmd_start = cmdline;
|
|
|
|
trace_handle_hmp_command(mon, cmdline);
|
|
|
|
cmd = monitor_parse_command(mon, cmdline, &cmdline, mon->cmd_table);
|
|
if (!cmd) {
|
|
return;
|
|
}
|
|
|
|
qdict = monitor_parse_arguments(mon, &cmdline, cmd);
|
|
if (!qdict) {
|
|
while (cmdline > cmd_start && qemu_isspace(cmdline[-1])) {
|
|
cmdline--;
|
|
}
|
|
monitor_printf(mon, "Try \"help %.*s\" for more information\n",
|
|
(int)(cmdline - cmd_start), cmd_start);
|
|
return;
|
|
}
|
|
|
|
cmd->cmd(mon, qdict);
|
|
qobject_unref(qdict);
|
|
}
|
|
|
|
static void cmd_completion(Monitor *mon, const char *name, const char *list)
|
|
{
|
|
const char *p, *pstart;
|
|
char cmd[128];
|
|
int len;
|
|
|
|
p = list;
|
|
for(;;) {
|
|
pstart = p;
|
|
p = qemu_strchrnul(p, '|');
|
|
len = p - pstart;
|
|
if (len > sizeof(cmd) - 2)
|
|
len = sizeof(cmd) - 2;
|
|
memcpy(cmd, pstart, len);
|
|
cmd[len] = '\0';
|
|
if (name[0] == '\0' || !strncmp(name, cmd, strlen(name))) {
|
|
readline_add_completion(mon->rs, cmd);
|
|
}
|
|
if (*p == '\0')
|
|
break;
|
|
p++;
|
|
}
|
|
}
|
|
|
|
static void file_completion(Monitor *mon, const char *input)
|
|
{
|
|
DIR *ffs;
|
|
struct dirent *d;
|
|
char path[1024];
|
|
char file[1024], file_prefix[1024];
|
|
int input_path_len;
|
|
const char *p;
|
|
|
|
p = strrchr(input, '/');
|
|
if (!p) {
|
|
input_path_len = 0;
|
|
pstrcpy(file_prefix, sizeof(file_prefix), input);
|
|
pstrcpy(path, sizeof(path), ".");
|
|
} else {
|
|
input_path_len = p - input + 1;
|
|
memcpy(path, input, input_path_len);
|
|
if (input_path_len > sizeof(path) - 1)
|
|
input_path_len = sizeof(path) - 1;
|
|
path[input_path_len] = '\0';
|
|
pstrcpy(file_prefix, sizeof(file_prefix), p + 1);
|
|
}
|
|
|
|
ffs = opendir(path);
|
|
if (!ffs)
|
|
return;
|
|
for(;;) {
|
|
struct stat sb;
|
|
d = readdir(ffs);
|
|
if (!d)
|
|
break;
|
|
|
|
if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (strstart(d->d_name, file_prefix, NULL)) {
|
|
memcpy(file, input, input_path_len);
|
|
if (input_path_len < sizeof(file))
|
|
pstrcpy(file + input_path_len, sizeof(file) - input_path_len,
|
|
d->d_name);
|
|
/* stat the file to find out if it's a directory.
|
|
* In that case add a slash to speed up typing long paths
|
|
*/
|
|
if (stat(file, &sb) == 0 && S_ISDIR(sb.st_mode)) {
|
|
pstrcat(file, sizeof(file), "/");
|
|
}
|
|
readline_add_completion(mon->rs, file);
|
|
}
|
|
}
|
|
closedir(ffs);
|
|
}
|
|
|
|
static const char *next_arg_type(const char *typestr)
|
|
{
|
|
const char *p = strchr(typestr, ':');
|
|
return (p != NULL ? ++p : typestr);
|
|
}
|
|
|
|
static void add_completion_option(ReadLineState *rs, const char *str,
|
|
const char *option)
|
|
{
|
|
if (!str || !option) {
|
|
return;
|
|
}
|
|
if (!strncmp(option, str, strlen(str))) {
|
|
readline_add_completion(rs, option);
|
|
}
|
|
}
|
|
|
|
void chardev_add_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
size_t len;
|
|
ChardevBackendInfoList *list, *start;
|
|
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
|
|
start = list = qmp_query_chardev_backends(NULL);
|
|
while (list) {
|
|
const char *chr_name = list->value->name;
|
|
|
|
if (!strncmp(chr_name, str, len)) {
|
|
readline_add_completion(rs, chr_name);
|
|
}
|
|
list = list->next;
|
|
}
|
|
qapi_free_ChardevBackendInfoList(start);
|
|
}
|
|
|
|
void netdev_add_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
size_t len;
|
|
int i;
|
|
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
for (i = 0; i < NET_CLIENT_DRIVER__MAX; i++) {
|
|
add_completion_option(rs, str, NetClientDriver_str(i));
|
|
}
|
|
}
|
|
|
|
void device_add_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
GSList *list, *elt;
|
|
size_t len;
|
|
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
list = elt = object_class_get_list(TYPE_DEVICE, false);
|
|
while (elt) {
|
|
const char *name;
|
|
DeviceClass *dc = OBJECT_CLASS_CHECK(DeviceClass, elt->data,
|
|
TYPE_DEVICE);
|
|
name = object_class_get_name(OBJECT_CLASS(dc));
|
|
|
|
if (dc->user_creatable
|
|
&& !strncmp(name, str, len)) {
|
|
readline_add_completion(rs, name);
|
|
}
|
|
elt = elt->next;
|
|
}
|
|
g_slist_free(list);
|
|
}
|
|
|
|
void object_add_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
GSList *list, *elt;
|
|
size_t len;
|
|
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
list = elt = object_class_get_list(TYPE_USER_CREATABLE, false);
|
|
while (elt) {
|
|
const char *name;
|
|
|
|
name = object_class_get_name(OBJECT_CLASS(elt->data));
|
|
if (!strncmp(name, str, len) && strcmp(name, TYPE_USER_CREATABLE)) {
|
|
readline_add_completion(rs, name);
|
|
}
|
|
elt = elt->next;
|
|
}
|
|
g_slist_free(list);
|
|
}
|
|
|
|
static void peripheral_device_del_completion(ReadLineState *rs,
|
|
const char *str, size_t len)
|
|
{
|
|
Object *peripheral = container_get(qdev_get_machine(), "/peripheral");
|
|
GSList *list, *item;
|
|
|
|
list = qdev_build_hotpluggable_device_list(peripheral);
|
|
if (!list) {
|
|
return;
|
|
}
|
|
|
|
for (item = list; item; item = g_slist_next(item)) {
|
|
DeviceState *dev = item->data;
|
|
|
|
if (dev->id && !strncmp(str, dev->id, len)) {
|
|
readline_add_completion(rs, dev->id);
|
|
}
|
|
}
|
|
|
|
g_slist_free(list);
|
|
}
|
|
|
|
void chardev_remove_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
size_t len;
|
|
ChardevInfoList *list, *start;
|
|
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
|
|
start = list = qmp_query_chardev(NULL);
|
|
while (list) {
|
|
ChardevInfo *chr = list->value;
|
|
|
|
if (!strncmp(chr->label, str, len)) {
|
|
readline_add_completion(rs, chr->label);
|
|
}
|
|
list = list->next;
|
|
}
|
|
qapi_free_ChardevInfoList(start);
|
|
}
|
|
|
|
static void ringbuf_completion(ReadLineState *rs, const char *str)
|
|
{
|
|
size_t len;
|
|
ChardevInfoList *list, *start;
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
|
|
start = list = qmp_query_chardev(NULL);
|
|
while (list) {
|
|
ChardevInfo *chr_info = list->value;
|
|
|
|
if (!strncmp(chr_info->label, str, len)) {
|
|
Chardev *chr = qemu_chr_find(chr_info->label);
|
|
if (chr && CHARDEV_IS_RINGBUF(chr)) {
|
|
readline_add_completion(rs, chr_info->label);
|
|
}
|
|
}
|
|
list = list->next;
|
|
}
|
|
qapi_free_ChardevInfoList(start);
|
|
}
|
|
|
|
void ringbuf_write_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
ringbuf_completion(rs, str);
|
|
}
|
|
|
|
void device_del_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
size_t len;
|
|
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
peripheral_device_del_completion(rs, str, len);
|
|
}
|
|
|
|
void object_del_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
ObjectPropertyInfoList *list, *start;
|
|
size_t len;
|
|
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
|
|
start = list = qmp_qom_list("/objects", NULL);
|
|
while (list) {
|
|
ObjectPropertyInfo *info = list->value;
|
|
|
|
if (!strncmp(info->type, "child<", 5)
|
|
&& !strncmp(info->name, str, len)) {
|
|
readline_add_completion(rs, info->name);
|
|
}
|
|
list = list->next;
|
|
}
|
|
qapi_free_ObjectPropertyInfoList(start);
|
|
}
|
|
|
|
void sendkey_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
int i;
|
|
char *sep;
|
|
size_t len;
|
|
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
sep = strrchr(str, '-');
|
|
if (sep) {
|
|
str = sep + 1;
|
|
}
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
for (i = 0; i < Q_KEY_CODE__MAX; i++) {
|
|
if (!strncmp(str, QKeyCode_str(i), len)) {
|
|
readline_add_completion(rs, QKeyCode_str(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
void set_link_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
size_t len;
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
if (nb_args == 2) {
|
|
NetClientState *ncs[MAX_QUEUE_NUM];
|
|
int count, i;
|
|
count = qemu_find_net_clients_except(NULL, ncs,
|
|
NET_CLIENT_DRIVER_NONE,
|
|
MAX_QUEUE_NUM);
|
|
for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) {
|
|
const char *name = ncs[i]->name;
|
|
if (!strncmp(str, name, len)) {
|
|
readline_add_completion(rs, name);
|
|
}
|
|
}
|
|
} else if (nb_args == 3) {
|
|
add_completion_option(rs, str, "on");
|
|
add_completion_option(rs, str, "off");
|
|
}
|
|
}
|
|
|
|
void netdev_del_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
int len, count, i;
|
|
NetClientState *ncs[MAX_QUEUE_NUM];
|
|
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
count = qemu_find_net_clients_except(NULL, ncs, NET_CLIENT_DRIVER_NIC,
|
|
MAX_QUEUE_NUM);
|
|
for (i = 0; i < MIN(count, MAX_QUEUE_NUM); i++) {
|
|
QemuOpts *opts;
|
|
const char *name = ncs[i]->name;
|
|
if (strncmp(str, name, len)) {
|
|
continue;
|
|
}
|
|
opts = qemu_opts_find(qemu_find_opts_err("netdev", NULL), name);
|
|
if (opts) {
|
|
readline_add_completion(rs, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void info_trace_events_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
size_t len;
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
if (nb_args == 2) {
|
|
TraceEventIter iter;
|
|
TraceEvent *ev;
|
|
char *pattern = g_strdup_printf("%s*", str);
|
|
trace_event_iter_init(&iter, pattern);
|
|
while ((ev = trace_event_iter_next(&iter)) != NULL) {
|
|
readline_add_completion(rs, trace_event_get_name(ev));
|
|
}
|
|
g_free(pattern);
|
|
}
|
|
}
|
|
|
|
void trace_event_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
size_t len;
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
if (nb_args == 2) {
|
|
TraceEventIter iter;
|
|
TraceEvent *ev;
|
|
char *pattern = g_strdup_printf("%s*", str);
|
|
trace_event_iter_init(&iter, pattern);
|
|
while ((ev = trace_event_iter_next(&iter)) != NULL) {
|
|
readline_add_completion(rs, trace_event_get_name(ev));
|
|
}
|
|
g_free(pattern);
|
|
} else if (nb_args == 3) {
|
|
add_completion_option(rs, str, "on");
|
|
add_completion_option(rs, str, "off");
|
|
}
|
|
}
|
|
|
|
void watchdog_action_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
int i;
|
|
|
|
if (nb_args != 2) {
|
|
return;
|
|
}
|
|
readline_set_completion_index(rs, strlen(str));
|
|
for (i = 0; i < WATCHDOG_ACTION__MAX; i++) {
|
|
add_completion_option(rs, str, WatchdogAction_str(i));
|
|
}
|
|
}
|
|
|
|
void migrate_set_capability_completion(ReadLineState *rs, int nb_args,
|
|
const char *str)
|
|
{
|
|
size_t len;
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
if (nb_args == 2) {
|
|
int i;
|
|
for (i = 0; i < MIGRATION_CAPABILITY__MAX; i++) {
|
|
const char *name = MigrationCapability_str(i);
|
|
if (!strncmp(str, name, len)) {
|
|
readline_add_completion(rs, name);
|
|
}
|
|
}
|
|
} else if (nb_args == 3) {
|
|
add_completion_option(rs, str, "on");
|
|
add_completion_option(rs, str, "off");
|
|
}
|
|
}
|
|
|
|
void migrate_set_parameter_completion(ReadLineState *rs, int nb_args,
|
|
const char *str)
|
|
{
|
|
size_t len;
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
if (nb_args == 2) {
|
|
int i;
|
|
for (i = 0; i < MIGRATION_PARAMETER__MAX; i++) {
|
|
const char *name = MigrationParameter_str(i);
|
|
if (!strncmp(str, name, len)) {
|
|
readline_add_completion(rs, name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void vm_completion(ReadLineState *rs, const char *str)
|
|
{
|
|
size_t len;
|
|
BlockDriverState *bs;
|
|
BdrvNextIterator it;
|
|
|
|
len = strlen(str);
|
|
readline_set_completion_index(rs, len);
|
|
|
|
for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
|
|
SnapshotInfoList *snapshots, *snapshot;
|
|
AioContext *ctx = bdrv_get_aio_context(bs);
|
|
bool ok = false;
|
|
|
|
aio_context_acquire(ctx);
|
|
if (bdrv_can_snapshot(bs)) {
|
|
ok = bdrv_query_snapshot_info_list(bs, &snapshots, NULL) == 0;
|
|
}
|
|
aio_context_release(ctx);
|
|
if (!ok) {
|
|
continue;
|
|
}
|
|
|
|
snapshot = snapshots;
|
|
while (snapshot) {
|
|
char *completion = snapshot->value->name;
|
|
if (!strncmp(str, completion, len)) {
|
|
readline_add_completion(rs, completion);
|
|
}
|
|
completion = snapshot->value->id;
|
|
if (!strncmp(str, completion, len)) {
|
|
readline_add_completion(rs, completion);
|
|
}
|
|
snapshot = snapshot->next;
|
|
}
|
|
qapi_free_SnapshotInfoList(snapshots);
|
|
}
|
|
|
|
}
|
|
|
|
void delvm_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
if (nb_args == 2) {
|
|
vm_completion(rs, str);
|
|
}
|
|
}
|
|
|
|
void loadvm_completion(ReadLineState *rs, int nb_args, const char *str)
|
|
{
|
|
if (nb_args == 2) {
|
|
vm_completion(rs, str);
|
|
}
|
|
}
|
|
|
|
static void monitor_find_completion_by_table(Monitor *mon,
|
|
const mon_cmd_t *cmd_table,
|
|
char **args,
|
|
int nb_args)
|
|
{
|
|
const char *cmdname;
|
|
int i;
|
|
const char *ptype, *old_ptype, *str, *name;
|
|
const mon_cmd_t *cmd;
|
|
BlockBackend *blk = NULL;
|
|
|
|
if (nb_args <= 1) {
|
|
/* command completion */
|
|
if (nb_args == 0)
|
|
cmdname = "";
|
|
else
|
|
cmdname = args[0];
|
|
readline_set_completion_index(mon->rs, strlen(cmdname));
|
|
for (cmd = cmd_table; cmd->name != NULL; cmd++) {
|
|
if (!runstate_check(RUN_STATE_PRECONFIG) ||
|
|
cmd_can_preconfig(cmd)) {
|
|
cmd_completion(mon, cmdname, cmd->name);
|
|
}
|
|
}
|
|
} else {
|
|
/* find the command */
|
|
for (cmd = cmd_table; cmd->name != NULL; cmd++) {
|
|
if (compare_cmd(args[0], cmd->name) &&
|
|
(!runstate_check(RUN_STATE_PRECONFIG) ||
|
|
cmd_can_preconfig(cmd))) {
|
|
break;
|
|
}
|
|
}
|
|
if (!cmd->name) {
|
|
return;
|
|
}
|
|
|
|
if (cmd->sub_table) {
|
|
/* do the job again */
|
|
monitor_find_completion_by_table(mon, cmd->sub_table,
|
|
&args[1], nb_args - 1);
|
|
return;
|
|
}
|
|
if (cmd->command_completion) {
|
|
cmd->command_completion(mon->rs, nb_args, args[nb_args - 1]);
|
|
return;
|
|
}
|
|
|
|
ptype = next_arg_type(cmd->args_type);
|
|
for(i = 0; i < nb_args - 2; i++) {
|
|
if (*ptype != '\0') {
|
|
ptype = next_arg_type(ptype);
|
|
while (*ptype == '?')
|
|
ptype = next_arg_type(ptype);
|
|
}
|
|
}
|
|
str = args[nb_args - 1];
|
|
old_ptype = NULL;
|
|
while (*ptype == '-' && old_ptype != ptype) {
|
|
old_ptype = ptype;
|
|
ptype = next_arg_type(ptype);
|
|
}
|
|
switch(*ptype) {
|
|
case 'F':
|
|
/* file completion */
|
|
readline_set_completion_index(mon->rs, strlen(str));
|
|
file_completion(mon, str);
|
|
break;
|
|
case 'B':
|
|
/* block device name completion */
|
|
readline_set_completion_index(mon->rs, strlen(str));
|
|
while ((blk = blk_next(blk)) != NULL) {
|
|
name = blk_name(blk);
|
|
if (str[0] == '\0' ||
|
|
!strncmp(name, str, strlen(str))) {
|
|
readline_add_completion(mon->rs, name);
|
|
}
|
|
}
|
|
break;
|
|
case 's':
|
|
case 'S':
|
|
if (!strcmp(cmd->name, "help|?")) {
|
|
monitor_find_completion_by_table(mon, cmd_table,
|
|
&args[1], nb_args - 1);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void monitor_find_completion(void *opaque,
|
|
const char *cmdline)
|
|
{
|
|
Monitor *mon = opaque;
|
|
char *args[MAX_ARGS];
|
|
int nb_args, len;
|
|
|
|
/* 1. parse the cmdline */
|
|
if (parse_cmdline(cmdline, &nb_args, args) < 0) {
|
|
return;
|
|
}
|
|
|
|
/* 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:
|
|
free_cmdline_args(args, nb_args);
|
|
}
|
|
|
|
static int monitor_can_read(void *opaque)
|
|
{
|
|
Monitor *mon = opaque;
|
|
|
|
return !atomic_mb_read(&mon->suspend_cnt);
|
|
}
|
|
|
|
/*
|
|
* Emit QMP response @rsp with ID @id to @mon.
|
|
* Null @rsp can only happen for commands with QCO_NO_SUCCESS_RESP.
|
|
* Nothing is emitted then.
|
|
*/
|
|
static void monitor_qmp_respond(Monitor *mon, QDict *rsp, QObject *id)
|
|
{
|
|
if (rsp) {
|
|
if (id) {
|
|
qdict_put_obj(rsp, "id", qobject_ref(id));
|
|
}
|
|
|
|
qmp_send_response(mon, rsp);
|
|
}
|
|
}
|
|
|
|
static void monitor_qmp_dispatch(Monitor *mon, QObject *req, QObject *id)
|
|
{
|
|
Monitor *old_mon;
|
|
QDict *rsp;
|
|
QDict *error;
|
|
|
|
old_mon = cur_mon;
|
|
cur_mon = mon;
|
|
|
|
rsp = qmp_dispatch(mon->qmp.commands, req, qmp_oob_enabled(mon));
|
|
|
|
cur_mon = old_mon;
|
|
|
|
if (mon->qmp.commands == &qmp_cap_negotiation_commands) {
|
|
error = qdict_get_qdict(rsp, "error");
|
|
if (error
|
|
&& !g_strcmp0(qdict_get_try_str(error, "class"),
|
|
QapiErrorClass_str(ERROR_CLASS_COMMAND_NOT_FOUND))) {
|
|
/* Provide a more useful error message */
|
|
qdict_del(error, "desc");
|
|
qdict_put_str(error, "desc", "Expecting capabilities negotiation"
|
|
" with 'qmp_capabilities'");
|
|
}
|
|
}
|
|
|
|
monitor_qmp_respond(mon, rsp, id);
|
|
qobject_unref(rsp);
|
|
}
|
|
|
|
/*
|
|
* Pop a QMP request from a monitor request queue.
|
|
* Return the request, or NULL all request queues are empty.
|
|
* We are using round-robin fashion to pop the request, to avoid
|
|
* processing commands only on a very busy monitor. To achieve that,
|
|
* when we process one request on a specific monitor, we put that
|
|
* monitor to the end of mon_list queue.
|
|
*
|
|
* Note: if the function returned with non-NULL, then the caller will
|
|
* be with mon->qmp.qmp_queue_lock held, and the caller is responsible
|
|
* to release it.
|
|
*/
|
|
static QMPRequest *monitor_qmp_requests_pop_any_with_lock(void)
|
|
{
|
|
QMPRequest *req_obj = NULL;
|
|
Monitor *mon;
|
|
|
|
qemu_mutex_lock(&monitor_lock);
|
|
|
|
QTAILQ_FOREACH(mon, &mon_list, entry) {
|
|
qemu_mutex_lock(&mon->qmp.qmp_queue_lock);
|
|
req_obj = g_queue_pop_head(mon->qmp.qmp_requests);
|
|
if (req_obj) {
|
|
/* With the lock of corresponding queue held */
|
|
break;
|
|
}
|
|
qemu_mutex_unlock(&mon->qmp.qmp_queue_lock);
|
|
}
|
|
|
|
if (req_obj) {
|
|
/*
|
|
* We found one request on the monitor. Degrade this monitor's
|
|
* priority to lowest by re-inserting it to end of queue.
|
|
*/
|
|
QTAILQ_REMOVE(&mon_list, mon, entry);
|
|
QTAILQ_INSERT_TAIL(&mon_list, mon, entry);
|
|
}
|
|
|
|
qemu_mutex_unlock(&monitor_lock);
|
|
|
|
return req_obj;
|
|
}
|
|
|
|
static void monitor_qmp_bh_dispatcher(void *data)
|
|
{
|
|
QMPRequest *req_obj = monitor_qmp_requests_pop_any_with_lock();
|
|
QDict *rsp;
|
|
bool need_resume;
|
|
Monitor *mon;
|
|
|
|
if (!req_obj) {
|
|
return;
|
|
}
|
|
|
|
mon = req_obj->mon;
|
|
/* qmp_oob_enabled() might change after "qmp_capabilities" */
|
|
need_resume = !qmp_oob_enabled(mon) ||
|
|
mon->qmp.qmp_requests->length == QMP_REQ_QUEUE_LEN_MAX - 1;
|
|
qemu_mutex_unlock(&mon->qmp.qmp_queue_lock);
|
|
if (req_obj->req) {
|
|
trace_monitor_qmp_cmd_in_band(qobject_get_try_str(req_obj->id) ?: "");
|
|
monitor_qmp_dispatch(mon, req_obj->req, req_obj->id);
|
|
} else {
|
|
assert(req_obj->err);
|
|
rsp = qmp_error_response(req_obj->err);
|
|
req_obj->err = NULL;
|
|
monitor_qmp_respond(mon, rsp, NULL);
|
|
qobject_unref(rsp);
|
|
}
|
|
|
|
if (need_resume) {
|
|
/* Pairs with the monitor_suspend() in handle_qmp_command() */
|
|
monitor_resume(mon);
|
|
}
|
|
qmp_request_free(req_obj);
|
|
|
|
/* Reschedule instead of looping so the main loop stays responsive */
|
|
qemu_bh_schedule(qmp_dispatcher_bh);
|
|
}
|
|
|
|
static void handle_qmp_command(void *opaque, QObject *req, Error *err)
|
|
{
|
|
Monitor *mon = opaque;
|
|
QObject *id = NULL;
|
|
QDict *qdict;
|
|
QMPRequest *req_obj;
|
|
|
|
assert(!req != !err);
|
|
|
|
qdict = qobject_to(QDict, req);
|
|
if (qdict) {
|
|
id = qobject_ref(qdict_get(qdict, "id"));
|
|
qdict_del(qdict, "id");
|
|
} /* else will fail qmp_dispatch() */
|
|
|
|
if (req && trace_event_get_state_backends(TRACE_HANDLE_QMP_COMMAND)) {
|
|
QString *req_json = qobject_to_json(req);
|
|
trace_handle_qmp_command(mon, qstring_get_str(req_json));
|
|
qobject_unref(req_json);
|
|
}
|
|
|
|
if (qdict && qmp_is_oob(qdict)) {
|
|
/* OOB commands are executed immediately */
|
|
trace_monitor_qmp_cmd_out_of_band(qobject_get_try_str(id)
|
|
?: "");
|
|
monitor_qmp_dispatch(mon, req, id);
|
|
qobject_unref(req);
|
|
qobject_unref(id);
|
|
return;
|
|
}
|
|
|
|
req_obj = g_new0(QMPRequest, 1);
|
|
req_obj->mon = mon;
|
|
req_obj->id = id;
|
|
req_obj->req = req;
|
|
req_obj->err = err;
|
|
|
|
/* Protect qmp_requests and fetching its length. */
|
|
qemu_mutex_lock(&mon->qmp.qmp_queue_lock);
|
|
|
|
/*
|
|
* Suspend the monitor when we can't queue more requests after
|
|
* this one. Dequeuing in monitor_qmp_bh_dispatcher() will resume
|
|
* it. Note that when OOB is disabled, we queue at most one
|
|
* command, for backward compatibility.
|
|
*/
|
|
if (!qmp_oob_enabled(mon) ||
|
|
mon->qmp.qmp_requests->length == QMP_REQ_QUEUE_LEN_MAX - 1) {
|
|
monitor_suspend(mon);
|
|
}
|
|
|
|
/*
|
|
* Put the request to the end of queue so that requests will be
|
|
* handled in time order. Ownership for req_obj, req, id,
|
|
* etc. will be delivered to the handler side.
|
|
*/
|
|
assert(mon->qmp.qmp_requests->length < QMP_REQ_QUEUE_LEN_MAX);
|
|
g_queue_push_tail(mon->qmp.qmp_requests, req_obj);
|
|
qemu_mutex_unlock(&mon->qmp.qmp_queue_lock);
|
|
|
|
/* Kick the dispatcher routine */
|
|
qemu_bh_schedule(qmp_dispatcher_bh);
|
|
}
|
|
|
|
static void monitor_qmp_read(void *opaque, const uint8_t *buf, int size)
|
|
{
|
|
Monitor *mon = opaque;
|
|
|
|
json_message_parser_feed(&mon->qmp.parser, (const char *) buf, size);
|
|
}
|
|
|
|
static void monitor_read(void *opaque, const uint8_t *buf, int size)
|
|
{
|
|
Monitor *old_mon = cur_mon;
|
|
int i;
|
|
|
|
cur_mon = opaque;
|
|
|
|
if (cur_mon->rs) {
|
|
for (i = 0; i < size; i++)
|
|
readline_handle_byte(cur_mon->rs, buf[i]);
|
|
} else {
|
|
if (size == 0 || buf[size - 1] != 0)
|
|
monitor_printf(cur_mon, "corrupted command\n");
|
|
else
|
|
handle_hmp_command(cur_mon, (char *)buf);
|
|
}
|
|
|
|
cur_mon = old_mon;
|
|
}
|
|
|
|
static void monitor_command_cb(void *opaque, const char *cmdline,
|
|
void *readline_opaque)
|
|
{
|
|
Monitor *mon = opaque;
|
|
|
|
monitor_suspend(mon);
|
|
handle_hmp_command(mon, cmdline);
|
|
monitor_resume(mon);
|
|
}
|
|
|
|
int monitor_suspend(Monitor *mon)
|
|
{
|
|
if (monitor_is_hmp_non_interactive(mon)) {
|
|
return -ENOTTY;
|
|
}
|
|
|
|
atomic_inc(&mon->suspend_cnt);
|
|
|
|
if (mon->use_io_thread) {
|
|
/*
|
|
* Kick I/O thread to make sure this takes effect. It'll be
|
|
* evaluated again in prepare() of the watch object.
|
|
*/
|
|
aio_notify(iothread_get_aio_context(mon_iothread));
|
|
}
|
|
|
|
trace_monitor_suspend(mon, 1);
|
|
return 0;
|
|
}
|
|
|
|
static void monitor_accept_input(void *opaque)
|
|
{
|
|
Monitor *mon = opaque;
|
|
|
|
qemu_chr_fe_accept_input(&mon->chr);
|
|
}
|
|
|
|
void monitor_resume(Monitor *mon)
|
|
{
|
|
if (monitor_is_hmp_non_interactive(mon)) {
|
|
return;
|
|
}
|
|
|
|
if (atomic_dec_fetch(&mon->suspend_cnt) == 0) {
|
|
AioContext *ctx;
|
|
|
|
if (mon->use_io_thread) {
|
|
ctx = iothread_get_aio_context(mon_iothread);
|
|
} else {
|
|
ctx = qemu_get_aio_context();
|
|
}
|
|
|
|
if (!monitor_is_qmp(mon)) {
|
|
assert(mon->rs);
|
|
readline_show_prompt(mon->rs);
|
|
}
|
|
|
|
aio_bh_schedule_oneshot(ctx, monitor_accept_input, mon);
|
|
}
|
|
|
|
trace_monitor_suspend(mon, -1);
|
|
}
|
|
|
|
static QDict *qmp_greeting(Monitor *mon)
|
|
{
|
|
QList *cap_list = qlist_new();
|
|
QObject *ver = NULL;
|
|
QMPCapability cap;
|
|
|
|
qmp_marshal_query_version(NULL, &ver, NULL);
|
|
|
|
for (cap = 0; cap < QMP_CAPABILITY__MAX; cap++) {
|
|
if (mon->qmp.capab_offered[cap]) {
|
|
qlist_append_str(cap_list, QMPCapability_str(cap));
|
|
}
|
|
}
|
|
|
|
return qdict_from_jsonf_nofail(
|
|
"{'QMP': {'version': %p, 'capabilities': %p}}",
|
|
ver, cap_list);
|
|
}
|
|
|
|
static void monitor_qmp_event(void *opaque, int event)
|
|
{
|
|
QDict *data;
|
|
Monitor *mon = opaque;
|
|
|
|
switch (event) {
|
|
case CHR_EVENT_OPENED:
|
|
mon->qmp.commands = &qmp_cap_negotiation_commands;
|
|
monitor_qmp_caps_reset(mon);
|
|
data = qmp_greeting(mon);
|
|
qmp_send_response(mon, data);
|
|
qobject_unref(data);
|
|
mon_refcount++;
|
|
break;
|
|
case CHR_EVENT_CLOSED:
|
|
/*
|
|
* Note: this is only useful when the output of the chardev
|
|
* backend is still open. For example, when the backend is
|
|
* stdio, it's possible that stdout is still open when stdin
|
|
* is closed.
|
|
*/
|
|
monitor_qmp_cleanup_queues(mon);
|
|
json_message_parser_destroy(&mon->qmp.parser);
|
|
json_message_parser_init(&mon->qmp.parser, handle_qmp_command,
|
|
mon, NULL);
|
|
mon_refcount--;
|
|
monitor_fdsets_cleanup();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void monitor_event(void *opaque, int event)
|
|
{
|
|
Monitor *mon = opaque;
|
|
|
|
switch (event) {
|
|
case CHR_EVENT_MUX_IN:
|
|
qemu_mutex_lock(&mon->mon_lock);
|
|
mon->mux_out = 0;
|
|
qemu_mutex_unlock(&mon->mon_lock);
|
|
if (mon->reset_seen) {
|
|
readline_restart(mon->rs);
|
|
monitor_resume(mon);
|
|
monitor_flush(mon);
|
|
} else {
|
|
atomic_mb_set(&mon->suspend_cnt, 0);
|
|
}
|
|
break;
|
|
|
|
case CHR_EVENT_MUX_OUT:
|
|
if (mon->reset_seen) {
|
|
if (atomic_mb_read(&mon->suspend_cnt) == 0) {
|
|
monitor_printf(mon, "\n");
|
|
}
|
|
monitor_flush(mon);
|
|
monitor_suspend(mon);
|
|
} else {
|
|
atomic_inc(&mon->suspend_cnt);
|
|
}
|
|
qemu_mutex_lock(&mon->mon_lock);
|
|
mon->mux_out = 1;
|
|
qemu_mutex_unlock(&mon->mon_lock);
|
|
break;
|
|
|
|
case CHR_EVENT_OPENED:
|
|
monitor_printf(mon, "QEMU %s monitor - type 'help' for more "
|
|
"information\n", QEMU_VERSION);
|
|
if (!mon->mux_out) {
|
|
readline_restart(mon->rs);
|
|
readline_show_prompt(mon->rs);
|
|
}
|
|
mon->reset_seen = 1;
|
|
mon_refcount++;
|
|
break;
|
|
|
|
case CHR_EVENT_CLOSED:
|
|
mon_refcount--;
|
|
monitor_fdsets_cleanup();
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int
|
|
compare_mon_cmd(const void *a, const void *b)
|
|
{
|
|
return strcmp(((const mon_cmd_t *)a)->name,
|
|
((const mon_cmd_t *)b)->name);
|
|
}
|
|
|
|
static void sortcmdlist(void)
|
|
{
|
|
int array_num;
|
|
int elem_size = sizeof(mon_cmd_t);
|
|
|
|
array_num = sizeof(mon_cmds)/elem_size-1;
|
|
qsort((void *)mon_cmds, array_num, elem_size, compare_mon_cmd);
|
|
|
|
array_num = sizeof(info_cmds)/elem_size-1;
|
|
qsort((void *)info_cmds, array_num, elem_size, compare_mon_cmd);
|
|
}
|
|
|
|
static void monitor_iothread_init(void)
|
|
{
|
|
mon_iothread = iothread_create("mon_iothread", &error_abort);
|
|
}
|
|
|
|
void monitor_init_globals(void)
|
|
{
|
|
monitor_init_qmp_commands();
|
|
monitor_qapi_event_init();
|
|
sortcmdlist();
|
|
qemu_mutex_init(&monitor_lock);
|
|
qemu_mutex_init(&mon_fdsets_lock);
|
|
|
|
/*
|
|
* The dispatcher BH must run in the main loop thread, since we
|
|
* have commands assuming that context. It would be nice to get
|
|
* rid of those assumptions.
|
|
*/
|
|
qmp_dispatcher_bh = aio_bh_new(iohandler_get_aio_context(),
|
|
monitor_qmp_bh_dispatcher,
|
|
NULL);
|
|
}
|
|
|
|
/* These functions just adapt the readline interface in a typesafe way. We
|
|
* could cast function pointers but that discards compiler checks.
|
|
*/
|
|
static void GCC_FMT_ATTR(2, 3) monitor_readline_printf(void *opaque,
|
|
const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
monitor_vprintf(opaque, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void monitor_readline_flush(void *opaque)
|
|
{
|
|
monitor_flush(opaque);
|
|
}
|
|
|
|
/*
|
|
* Print to current monitor if we have one, else to stream.
|
|
* TODO should return int, so callers can calculate width, but that
|
|
* requires surgery to monitor_vprintf(). Left for another day.
|
|
*/
|
|
void monitor_vfprintf(FILE *stream, const char *fmt, va_list ap)
|
|
{
|
|
if (cur_mon && !monitor_cur_is_qmp()) {
|
|
monitor_vprintf(cur_mon, fmt, ap);
|
|
} else {
|
|
vfprintf(stream, fmt, ap);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print to current monitor if we have one, else to stderr.
|
|
* TODO should return int, so callers can calculate width, but that
|
|
* requires surgery to monitor_vprintf(). Left for another day.
|
|
*/
|
|
void error_vprintf(const char *fmt, va_list ap)
|
|
{
|
|
monitor_vfprintf(stderr, fmt, ap);
|
|
}
|
|
|
|
void error_vprintf_unless_qmp(const char *fmt, va_list ap)
|
|
{
|
|
if (cur_mon && !monitor_cur_is_qmp()) {
|
|
monitor_vprintf(cur_mon, fmt, ap);
|
|
} else if (!cur_mon) {
|
|
vfprintf(stderr, fmt, ap);
|
|
}
|
|
}
|
|
|
|
static void monitor_list_append(Monitor *mon)
|
|
{
|
|
qemu_mutex_lock(&monitor_lock);
|
|
/*
|
|
* This prevents inserting new monitors during monitor_cleanup().
|
|
* A cleaner solution would involve the main thread telling other
|
|
* threads to terminate, waiting for their termination.
|
|
*/
|
|
if (!monitor_destroyed) {
|
|
QTAILQ_INSERT_HEAD(&mon_list, mon, entry);
|
|
mon = NULL;
|
|
}
|
|
qemu_mutex_unlock(&monitor_lock);
|
|
|
|
if (mon) {
|
|
monitor_data_destroy(mon);
|
|
g_free(mon);
|
|
}
|
|
}
|
|
|
|
static void monitor_qmp_setup_handlers_bh(void *opaque)
|
|
{
|
|
Monitor *mon = opaque;
|
|
GMainContext *context;
|
|
|
|
assert(mon->use_io_thread);
|
|
context = iothread_get_g_main_context(mon_iothread);
|
|
assert(context);
|
|
qemu_chr_fe_set_handlers(&mon->chr, monitor_can_read, monitor_qmp_read,
|
|
monitor_qmp_event, NULL, mon, context, true);
|
|
monitor_list_append(mon);
|
|
}
|
|
|
|
void monitor_init(Chardev *chr, int flags)
|
|
{
|
|
Monitor *mon = g_malloc(sizeof(*mon));
|
|
bool use_readline = flags & MONITOR_USE_READLINE;
|
|
|
|
/* Note: we run QMP monitor in I/O thread when @chr supports that */
|
|
monitor_data_init(mon, false,
|
|
(flags & MONITOR_USE_CONTROL)
|
|
&& qemu_chr_has_feature(chr,
|
|
QEMU_CHAR_FEATURE_GCONTEXT));
|
|
|
|
qemu_chr_fe_init(&mon->chr, chr, &error_abort);
|
|
mon->flags = flags;
|
|
if (use_readline) {
|
|
mon->rs = readline_init(monitor_readline_printf,
|
|
monitor_readline_flush,
|
|
mon,
|
|
monitor_find_completion);
|
|
monitor_read_command(mon, 0);
|
|
}
|
|
|
|
if (monitor_is_qmp(mon)) {
|
|
qemu_chr_fe_set_echo(&mon->chr, true);
|
|
json_message_parser_init(&mon->qmp.parser, handle_qmp_command,
|
|
mon, NULL);
|
|
if (mon->use_io_thread) {
|
|
/*
|
|
* Make sure the old iowatch is gone. It's possible when
|
|
* e.g. the chardev is in client mode, with wait=on.
|
|
*/
|
|
remove_fd_in_watch(chr);
|
|
/*
|
|
* We can't call qemu_chr_fe_set_handlers() directly here
|
|
* since chardev might be running in the monitor I/O
|
|
* thread. Schedule a bottom half.
|
|
*/
|
|
aio_bh_schedule_oneshot(iothread_get_aio_context(mon_iothread),
|
|
monitor_qmp_setup_handlers_bh, mon);
|
|
/* The bottom half will add @mon to @mon_list */
|
|
return;
|
|
} else {
|
|
qemu_chr_fe_set_handlers(&mon->chr, monitor_can_read,
|
|
monitor_qmp_read, monitor_qmp_event,
|
|
NULL, mon, NULL, true);
|
|
}
|
|
} else {
|
|
qemu_chr_fe_set_handlers(&mon->chr, monitor_can_read, monitor_read,
|
|
monitor_event, NULL, mon, NULL, true);
|
|
}
|
|
|
|
monitor_list_append(mon);
|
|
}
|
|
|
|
void monitor_cleanup(void)
|
|
{
|
|
/*
|
|
* We need to explicitly stop the I/O thread (but not destroy it),
|
|
* clean up the monitor resources, then destroy the I/O thread since
|
|
* we need to unregister from chardev below in
|
|
* monitor_data_destroy(), and chardev is not thread-safe yet
|
|
*/
|
|
if (mon_iothread) {
|
|
iothread_stop(mon_iothread);
|
|
}
|
|
|
|
/* Flush output buffers and destroy monitors */
|
|
qemu_mutex_lock(&monitor_lock);
|
|
monitor_destroyed = true;
|
|
while (!QTAILQ_EMPTY(&mon_list)) {
|
|
Monitor *mon = QTAILQ_FIRST(&mon_list);
|
|
QTAILQ_REMOVE(&mon_list, mon, entry);
|
|
/* Permit QAPI event emission from character frontend release */
|
|
qemu_mutex_unlock(&monitor_lock);
|
|
monitor_flush(mon);
|
|
monitor_data_destroy(mon);
|
|
qemu_mutex_lock(&monitor_lock);
|
|
g_free(mon);
|
|
}
|
|
qemu_mutex_unlock(&monitor_lock);
|
|
|
|
/* QEMUBHs needs to be deleted before destroying the I/O thread */
|
|
qemu_bh_delete(qmp_dispatcher_bh);
|
|
qmp_dispatcher_bh = NULL;
|
|
if (mon_iothread) {
|
|
iothread_destroy(mon_iothread);
|
|
mon_iothread = NULL;
|
|
}
|
|
}
|
|
|
|
QemuOptsList qemu_mon_opts = {
|
|
.name = "mon",
|
|
.implied_opt_name = "chardev",
|
|
.head = QTAILQ_HEAD_INITIALIZER(qemu_mon_opts.head),
|
|
.desc = {
|
|
{
|
|
.name = "mode",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "chardev",
|
|
.type = QEMU_OPT_STRING,
|
|
},{
|
|
.name = "pretty",
|
|
.type = QEMU_OPT_BOOL,
|
|
},
|
|
{ /* end of list */ }
|
|
},
|
|
};
|
|
|
|
HotpluggableCPUList *qmp_query_hotpluggable_cpus(Error **errp)
|
|
{
|
|
MachineState *ms = MACHINE(qdev_get_machine());
|
|
MachineClass *mc = MACHINE_GET_CLASS(ms);
|
|
|
|
if (!mc->has_hotpluggable_cpus) {
|
|
error_setg(errp, QERR_FEATURE_DISABLED, "query-hotpluggable-cpus");
|
|
return NULL;
|
|
}
|
|
|
|
return machine_query_hotpluggable_cpus(ms);
|
|
}
|