diff options
Diffstat (limited to 'monitor')
-rw-r--r-- | monitor/hmp-cmds.c | 2167 | ||||
-rw-r--r-- | monitor/hmp.c | 1487 | ||||
-rw-r--r-- | monitor/meson.build | 9 | ||||
-rw-r--r-- | monitor/misc.c | 1981 | ||||
-rw-r--r-- | monitor/monitor-internal.h | 193 | ||||
-rw-r--r-- | monitor/monitor.c | 778 | ||||
-rw-r--r-- | monitor/qmp-cmds-control.c | 222 | ||||
-rw-r--r-- | monitor/qmp-cmds.c | 468 | ||||
-rw-r--r-- | monitor/qmp.c | 538 | ||||
-rw-r--r-- | monitor/trace-events | 19 | ||||
-rw-r--r-- | monitor/trace.h | 1 |
11 files changed, 7863 insertions, 0 deletions
diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c new file mode 100644 index 000000000..9c91bf93e --- /dev/null +++ b/monitor/hmp-cmds.c @@ -0,0 +1,2167 @@ +/* + * Human Monitor Interface commands + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "monitor/hmp.h" +#include "net/net.h" +#include "net/eth.h" +#include "chardev/char.h" +#include "sysemu/block-backend.h" +#include "sysemu/runstate.h" +#include "qemu/config-file.h" +#include "qemu/option.h" +#include "qemu/timer.h" +#include "qemu/sockets.h" +#include "qemu/help_option.h" +#include "monitor/monitor-internal.h" +#include "qapi/error.h" +#include "qapi/clone-visitor.h" +#include "qapi/opts-visitor.h" +#include "qapi/qapi-builtin-visit.h" +#include "qapi/qapi-commands-block.h" +#include "qapi/qapi-commands-char.h" +#include "qapi/qapi-commands-control.h" +#include "qapi/qapi-commands-machine.h" +#include "qapi/qapi-commands-migration.h" +#include "qapi/qapi-commands-misc.h" +#include "qapi/qapi-commands-net.h" +#include "qapi/qapi-commands-pci.h" +#include "qapi/qapi-commands-rocker.h" +#include "qapi/qapi-commands-run-state.h" +#include "qapi/qapi-commands-tpm.h" +#include "qapi/qapi-commands-ui.h" +#include "qapi/qapi-visit-net.h" +#include "qapi/qapi-visit-migration.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qerror.h" +#include "qapi/string-input-visitor.h" +#include "qapi/string-output-visitor.h" +#include "qom/object_interfaces.h" +#include "ui/console.h" +#include "qemu/cutils.h" +#include "qemu/error-report.h" +#include "hw/intc/intc.h" +#include "migration/snapshot.h" +#include "migration/misc.h" + +#ifdef CONFIG_SPICE +#include <spice/enums.h> +#endif + +bool hmp_handle_error(Monitor *mon, Error *err) +{ + if (err) { + error_reportf_err(err, "Error: "); + return true; + } + return false; +} + +/* + * Produce a strList from a comma separated list. + * A NULL or empty input string return NULL. + */ +static strList *strList_from_comma_list(const char *in) +{ + strList *res = NULL; + strList **tail = &res; + + while (in && in[0]) { + char *comma = strchr(in, ','); + char *value; + + if (comma) { + value = g_strndup(in, comma - in); + in = comma + 1; /* skip the , */ + } else { + value = g_strdup(in); + in = NULL; + } + QAPI_LIST_APPEND(tail, value); + } + + return res; +} + +void hmp_info_name(Monitor *mon, const QDict *qdict) +{ + NameInfo *info; + + info = qmp_query_name(NULL); + if (info->has_name) { + monitor_printf(mon, "%s\n", info->name); + } + qapi_free_NameInfo(info); +} + +void hmp_info_version(Monitor *mon, const QDict *qdict) +{ + VersionInfo *info; + + info = qmp_query_version(NULL); + + monitor_printf(mon, "%" PRId64 ".%" PRId64 ".%" PRId64 "%s\n", + info->qemu->major, info->qemu->minor, info->qemu->micro, + info->package); + + qapi_free_VersionInfo(info); +} + +void hmp_info_kvm(Monitor *mon, const QDict *qdict) +{ + KvmInfo *info; + + info = qmp_query_kvm(NULL); + monitor_printf(mon, "kvm support: "); + if (info->present) { + monitor_printf(mon, "%s\n", info->enabled ? "enabled" : "disabled"); + } else { + monitor_printf(mon, "not compiled\n"); + } + + qapi_free_KvmInfo(info); +} + +void hmp_info_status(Monitor *mon, const QDict *qdict) +{ + StatusInfo *info; + + info = qmp_query_status(NULL); + + monitor_printf(mon, "VM status: %s%s", + info->running ? "running" : "paused", + info->singlestep ? " (single step mode)" : ""); + + if (!info->running && info->status != RUN_STATE_PAUSED) { + monitor_printf(mon, " (%s)", RunState_str(info->status)); + } + + monitor_printf(mon, "\n"); + + qapi_free_StatusInfo(info); +} + +void hmp_info_uuid(Monitor *mon, const QDict *qdict) +{ + UuidInfo *info; + + info = qmp_query_uuid(NULL); + monitor_printf(mon, "%s\n", info->UUID); + qapi_free_UuidInfo(info); +} + +void hmp_info_chardev(Monitor *mon, const QDict *qdict) +{ + ChardevInfoList *char_info, *info; + + char_info = qmp_query_chardev(NULL); + for (info = char_info; info; info = info->next) { + monitor_printf(mon, "%s: filename=%s\n", info->value->label, + info->value->filename); + } + + qapi_free_ChardevInfoList(char_info); +} + +void hmp_info_mice(Monitor *mon, const QDict *qdict) +{ + MouseInfoList *mice_list, *mouse; + + mice_list = qmp_query_mice(NULL); + if (!mice_list) { + monitor_printf(mon, "No mouse devices connected\n"); + return; + } + + for (mouse = mice_list; mouse; mouse = mouse->next) { + monitor_printf(mon, "%c Mouse #%" PRId64 ": %s%s\n", + mouse->value->current ? '*' : ' ', + mouse->value->index, mouse->value->name, + mouse->value->absolute ? " (absolute)" : ""); + } + + qapi_free_MouseInfoList(mice_list); +} + +static char *SocketAddress_to_str(SocketAddress *addr) +{ + switch (addr->type) { + case SOCKET_ADDRESS_TYPE_INET: + return g_strdup_printf("tcp:%s:%s", + addr->u.inet.host, + addr->u.inet.port); + case SOCKET_ADDRESS_TYPE_UNIX: + return g_strdup_printf("unix:%s", + addr->u.q_unix.path); + case SOCKET_ADDRESS_TYPE_FD: + return g_strdup_printf("fd:%s", addr->u.fd.str); + case SOCKET_ADDRESS_TYPE_VSOCK: + return g_strdup_printf("tcp:%s:%s", + addr->u.vsock.cid, + addr->u.vsock.port); + default: + return g_strdup("unknown address type"); + } +} + +void hmp_info_migrate(Monitor *mon, const QDict *qdict) +{ + MigrationInfo *info; + + info = qmp_query_migrate(NULL); + + migration_global_dump(mon); + + if (info->blocked_reasons) { + strList *reasons = info->blocked_reasons; + monitor_printf(mon, "Outgoing migration blocked:\n"); + while (reasons) { + monitor_printf(mon, " %s\n", reasons->value); + reasons = reasons->next; + } + } + + if (info->has_status) { + monitor_printf(mon, "Migration status: %s", + MigrationStatus_str(info->status)); + if (info->status == MIGRATION_STATUS_FAILED && + info->has_error_desc) { + monitor_printf(mon, " (%s)\n", info->error_desc); + } else { + monitor_printf(mon, "\n"); + } + + monitor_printf(mon, "total time: %" PRIu64 " ms\n", + info->total_time); + if (info->has_expected_downtime) { + monitor_printf(mon, "expected downtime: %" PRIu64 " ms\n", + info->expected_downtime); + } + if (info->has_downtime) { + monitor_printf(mon, "downtime: %" PRIu64 " ms\n", + info->downtime); + } + if (info->has_setup_time) { + monitor_printf(mon, "setup: %" PRIu64 " ms\n", + info->setup_time); + } + } + + if (info->has_ram) { + monitor_printf(mon, "transferred ram: %" PRIu64 " kbytes\n", + info->ram->transferred >> 10); + monitor_printf(mon, "throughput: %0.2f mbps\n", + info->ram->mbps); + monitor_printf(mon, "remaining ram: %" PRIu64 " kbytes\n", + info->ram->remaining >> 10); + monitor_printf(mon, "total ram: %" PRIu64 " kbytes\n", + info->ram->total >> 10); + monitor_printf(mon, "duplicate: %" PRIu64 " pages\n", + info->ram->duplicate); + monitor_printf(mon, "skipped: %" PRIu64 " pages\n", + info->ram->skipped); + monitor_printf(mon, "normal: %" PRIu64 " pages\n", + info->ram->normal); + monitor_printf(mon, "normal bytes: %" PRIu64 " kbytes\n", + info->ram->normal_bytes >> 10); + monitor_printf(mon, "dirty sync count: %" PRIu64 "\n", + info->ram->dirty_sync_count); + monitor_printf(mon, "page size: %" PRIu64 " kbytes\n", + info->ram->page_size >> 10); + monitor_printf(mon, "multifd bytes: %" PRIu64 " kbytes\n", + info->ram->multifd_bytes >> 10); + monitor_printf(mon, "pages-per-second: %" PRIu64 "\n", + info->ram->pages_per_second); + + if (info->ram->dirty_pages_rate) { + monitor_printf(mon, "dirty pages rate: %" PRIu64 " pages\n", + info->ram->dirty_pages_rate); + } + if (info->ram->postcopy_requests) { + monitor_printf(mon, "postcopy request count: %" PRIu64 "\n", + info->ram->postcopy_requests); + } + } + + if (info->has_disk) { + monitor_printf(mon, "transferred disk: %" PRIu64 " kbytes\n", + info->disk->transferred >> 10); + monitor_printf(mon, "remaining disk: %" PRIu64 " kbytes\n", + info->disk->remaining >> 10); + monitor_printf(mon, "total disk: %" PRIu64 " kbytes\n", + info->disk->total >> 10); + } + + if (info->has_xbzrle_cache) { + monitor_printf(mon, "cache size: %" PRIu64 " bytes\n", + info->xbzrle_cache->cache_size); + monitor_printf(mon, "xbzrle transferred: %" PRIu64 " kbytes\n", + info->xbzrle_cache->bytes >> 10); + monitor_printf(mon, "xbzrle pages: %" PRIu64 " pages\n", + info->xbzrle_cache->pages); + monitor_printf(mon, "xbzrle cache miss: %" PRIu64 " pages\n", + info->xbzrle_cache->cache_miss); + monitor_printf(mon, "xbzrle cache miss rate: %0.2f\n", + info->xbzrle_cache->cache_miss_rate); + monitor_printf(mon, "xbzrle encoding rate: %0.2f\n", + info->xbzrle_cache->encoding_rate); + monitor_printf(mon, "xbzrle overflow: %" PRIu64 "\n", + info->xbzrle_cache->overflow); + } + + if (info->has_compression) { + monitor_printf(mon, "compression pages: %" PRIu64 " pages\n", + info->compression->pages); + monitor_printf(mon, "compression busy: %" PRIu64 "\n", + info->compression->busy); + monitor_printf(mon, "compression busy rate: %0.2f\n", + info->compression->busy_rate); + monitor_printf(mon, "compressed size: %" PRIu64 " kbytes\n", + info->compression->compressed_size >> 10); + monitor_printf(mon, "compression rate: %0.2f\n", + info->compression->compression_rate); + } + + if (info->has_cpu_throttle_percentage) { + monitor_printf(mon, "cpu throttle percentage: %" PRIu64 "\n", + info->cpu_throttle_percentage); + } + + if (info->has_postcopy_blocktime) { + monitor_printf(mon, "postcopy blocktime: %u\n", + info->postcopy_blocktime); + } + + if (info->has_postcopy_vcpu_blocktime) { + Visitor *v; + char *str; + v = string_output_visitor_new(false, &str); + visit_type_uint32List(v, NULL, &info->postcopy_vcpu_blocktime, + &error_abort); + visit_complete(v, &str); + monitor_printf(mon, "postcopy vcpu blocktime: %s\n", str); + g_free(str); + visit_free(v); + } + if (info->has_socket_address) { + SocketAddressList *addr; + + monitor_printf(mon, "socket address: [\n"); + + for (addr = info->socket_address; addr; addr = addr->next) { + char *s = SocketAddress_to_str(addr->value); + monitor_printf(mon, "\t%s\n", s); + g_free(s); + } + monitor_printf(mon, "]\n"); + } + + if (info->has_vfio) { + monitor_printf(mon, "vfio device transferred: %" PRIu64 " kbytes\n", + info->vfio->transferred >> 10); + } + + qapi_free_MigrationInfo(info); +} + +void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict) +{ + MigrationCapabilityStatusList *caps, *cap; + + caps = qmp_query_migrate_capabilities(NULL); + + if (caps) { + for (cap = caps; cap; cap = cap->next) { + monitor_printf(mon, "%s: %s\n", + MigrationCapability_str(cap->value->capability), + cap->value->state ? "on" : "off"); + } + } + + qapi_free_MigrationCapabilityStatusList(caps); +} + +void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict) +{ + MigrationParameters *params; + + params = qmp_query_migrate_parameters(NULL); + + if (params) { + monitor_printf(mon, "%s: %" PRIu64 " ms\n", + MigrationParameter_str(MIGRATION_PARAMETER_ANNOUNCE_INITIAL), + params->announce_initial); + monitor_printf(mon, "%s: %" PRIu64 " ms\n", + MigrationParameter_str(MIGRATION_PARAMETER_ANNOUNCE_MAX), + params->announce_max); + monitor_printf(mon, "%s: %" PRIu64 "\n", + MigrationParameter_str(MIGRATION_PARAMETER_ANNOUNCE_ROUNDS), + params->announce_rounds); + monitor_printf(mon, "%s: %" PRIu64 " ms\n", + MigrationParameter_str(MIGRATION_PARAMETER_ANNOUNCE_STEP), + params->announce_step); + assert(params->has_compress_level); + monitor_printf(mon, "%s: %u\n", + MigrationParameter_str(MIGRATION_PARAMETER_COMPRESS_LEVEL), + params->compress_level); + assert(params->has_compress_threads); + monitor_printf(mon, "%s: %u\n", + MigrationParameter_str(MIGRATION_PARAMETER_COMPRESS_THREADS), + params->compress_threads); + assert(params->has_compress_wait_thread); + monitor_printf(mon, "%s: %s\n", + MigrationParameter_str(MIGRATION_PARAMETER_COMPRESS_WAIT_THREAD), + params->compress_wait_thread ? "on" : "off"); + assert(params->has_decompress_threads); + monitor_printf(mon, "%s: %u\n", + MigrationParameter_str(MIGRATION_PARAMETER_DECOMPRESS_THREADS), + params->decompress_threads); + assert(params->has_throttle_trigger_threshold); + monitor_printf(mon, "%s: %u\n", + MigrationParameter_str(MIGRATION_PARAMETER_THROTTLE_TRIGGER_THRESHOLD), + params->throttle_trigger_threshold); + assert(params->has_cpu_throttle_initial); + monitor_printf(mon, "%s: %u\n", + MigrationParameter_str(MIGRATION_PARAMETER_CPU_THROTTLE_INITIAL), + params->cpu_throttle_initial); + assert(params->has_cpu_throttle_increment); + monitor_printf(mon, "%s: %u\n", + MigrationParameter_str(MIGRATION_PARAMETER_CPU_THROTTLE_INCREMENT), + params->cpu_throttle_increment); + assert(params->has_cpu_throttle_tailslow); + monitor_printf(mon, "%s: %s\n", + MigrationParameter_str(MIGRATION_PARAMETER_CPU_THROTTLE_TAILSLOW), + params->cpu_throttle_tailslow ? "on" : "off"); + assert(params->has_max_cpu_throttle); + monitor_printf(mon, "%s: %u\n", + MigrationParameter_str(MIGRATION_PARAMETER_MAX_CPU_THROTTLE), + params->max_cpu_throttle); + assert(params->has_tls_creds); + monitor_printf(mon, "%s: '%s'\n", + MigrationParameter_str(MIGRATION_PARAMETER_TLS_CREDS), + params->tls_creds); + assert(params->has_tls_hostname); + monitor_printf(mon, "%s: '%s'\n", + MigrationParameter_str(MIGRATION_PARAMETER_TLS_HOSTNAME), + params->tls_hostname); + assert(params->has_max_bandwidth); + monitor_printf(mon, "%s: %" PRIu64 " bytes/second\n", + MigrationParameter_str(MIGRATION_PARAMETER_MAX_BANDWIDTH), + params->max_bandwidth); + assert(params->has_downtime_limit); + monitor_printf(mon, "%s: %" PRIu64 " ms\n", + MigrationParameter_str(MIGRATION_PARAMETER_DOWNTIME_LIMIT), + params->downtime_limit); + assert(params->has_x_checkpoint_delay); + monitor_printf(mon, "%s: %u ms\n", + MigrationParameter_str(MIGRATION_PARAMETER_X_CHECKPOINT_DELAY), + params->x_checkpoint_delay); + assert(params->has_block_incremental); + monitor_printf(mon, "%s: %s\n", + MigrationParameter_str(MIGRATION_PARAMETER_BLOCK_INCREMENTAL), + params->block_incremental ? "on" : "off"); + monitor_printf(mon, "%s: %u\n", + MigrationParameter_str(MIGRATION_PARAMETER_MULTIFD_CHANNELS), + params->multifd_channels); + monitor_printf(mon, "%s: %s\n", + MigrationParameter_str(MIGRATION_PARAMETER_MULTIFD_COMPRESSION), + MultiFDCompression_str(params->multifd_compression)); + monitor_printf(mon, "%s: %" PRIu64 " bytes\n", + MigrationParameter_str(MIGRATION_PARAMETER_XBZRLE_CACHE_SIZE), + params->xbzrle_cache_size); + monitor_printf(mon, "%s: %" PRIu64 "\n", + MigrationParameter_str(MIGRATION_PARAMETER_MAX_POSTCOPY_BANDWIDTH), + params->max_postcopy_bandwidth); + monitor_printf(mon, "%s: '%s'\n", + MigrationParameter_str(MIGRATION_PARAMETER_TLS_AUTHZ), + params->tls_authz); + + if (params->has_block_bitmap_mapping) { + const BitmapMigrationNodeAliasList *bmnal; + + monitor_printf(mon, "%s:\n", + MigrationParameter_str( + MIGRATION_PARAMETER_BLOCK_BITMAP_MAPPING)); + + for (bmnal = params->block_bitmap_mapping; + bmnal; + bmnal = bmnal->next) + { + const BitmapMigrationNodeAlias *bmna = bmnal->value; + const BitmapMigrationBitmapAliasList *bmbal; + + monitor_printf(mon, " '%s' -> '%s'\n", + bmna->node_name, bmna->alias); + + for (bmbal = bmna->bitmaps; bmbal; bmbal = bmbal->next) { + const BitmapMigrationBitmapAlias *bmba = bmbal->value; + + monitor_printf(mon, " '%s' -> '%s'\n", + bmba->name, bmba->alias); + } + } + } + } + + qapi_free_MigrationParameters(params); +} + + +#ifdef CONFIG_VNC +/* Helper for hmp_info_vnc_clients, _servers */ +static void hmp_info_VncBasicInfo(Monitor *mon, VncBasicInfo *info, + const char *name) +{ + monitor_printf(mon, " %s: %s:%s (%s%s)\n", + name, + info->host, + info->service, + NetworkAddressFamily_str(info->family), + info->websocket ? " (Websocket)" : ""); +} + +/* Helper displaying and auth and crypt info */ +static void hmp_info_vnc_authcrypt(Monitor *mon, const char *indent, + VncPrimaryAuth auth, + VncVencryptSubAuth *vencrypt) +{ + monitor_printf(mon, "%sAuth: %s (Sub: %s)\n", indent, + VncPrimaryAuth_str(auth), + vencrypt ? VncVencryptSubAuth_str(*vencrypt) : "none"); +} + +static void hmp_info_vnc_clients(Monitor *mon, VncClientInfoList *client) +{ + while (client) { + VncClientInfo *cinfo = client->value; + + hmp_info_VncBasicInfo(mon, qapi_VncClientInfo_base(cinfo), "Client"); + monitor_printf(mon, " x509_dname: %s\n", + cinfo->has_x509_dname ? + cinfo->x509_dname : "none"); + monitor_printf(mon, " sasl_username: %s\n", + cinfo->has_sasl_username ? + cinfo->sasl_username : "none"); + + client = client->next; + } +} + +static void hmp_info_vnc_servers(Monitor *mon, VncServerInfo2List *server) +{ + while (server) { + VncServerInfo2 *sinfo = server->value; + hmp_info_VncBasicInfo(mon, qapi_VncServerInfo2_base(sinfo), "Server"); + hmp_info_vnc_authcrypt(mon, " ", sinfo->auth, + sinfo->has_vencrypt ? &sinfo->vencrypt : NULL); + server = server->next; + } +} + +void hmp_info_vnc(Monitor *mon, const QDict *qdict) +{ + VncInfo2List *info2l, *info2l_head; + Error *err = NULL; + + info2l = qmp_query_vnc_servers(&err); + info2l_head = info2l; + if (hmp_handle_error(mon, err)) { + return; + } + if (!info2l) { + monitor_printf(mon, "None\n"); + return; + } + + while (info2l) { + VncInfo2 *info = info2l->value; + monitor_printf(mon, "%s:\n", info->id); + hmp_info_vnc_servers(mon, info->server); + hmp_info_vnc_clients(mon, info->clients); + if (!info->server) { + /* The server entry displays its auth, we only + * need to display in the case of 'reverse' connections + * where there's no server. + */ + hmp_info_vnc_authcrypt(mon, " ", info->auth, + info->has_vencrypt ? &info->vencrypt : NULL); + } + if (info->has_display) { + monitor_printf(mon, " Display: %s\n", info->display); + } + info2l = info2l->next; + } + + qapi_free_VncInfo2List(info2l_head); + +} +#endif + +#ifdef CONFIG_SPICE +void hmp_info_spice(Monitor *mon, const QDict *qdict) +{ + SpiceChannelList *chan; + SpiceInfo *info; + const char *channel_name; + const char * const channel_names[] = { + [SPICE_CHANNEL_MAIN] = "main", + [SPICE_CHANNEL_DISPLAY] = "display", + [SPICE_CHANNEL_INPUTS] = "inputs", + [SPICE_CHANNEL_CURSOR] = "cursor", + [SPICE_CHANNEL_PLAYBACK] = "playback", + [SPICE_CHANNEL_RECORD] = "record", + [SPICE_CHANNEL_TUNNEL] = "tunnel", + [SPICE_CHANNEL_SMARTCARD] = "smartcard", + [SPICE_CHANNEL_USBREDIR] = "usbredir", + [SPICE_CHANNEL_PORT] = "port", +#if 0 + /* minimum spice-protocol is 0.12.3, webdav was added in 0.12.7, + * no easy way to #ifdef (SPICE_CHANNEL_* is a enum). Disable + * as quick fix for build failures with older versions. */ + [SPICE_CHANNEL_WEBDAV] = "webdav", +#endif + }; + + info = qmp_query_spice(NULL); + + if (!info->enabled) { + monitor_printf(mon, "Server: disabled\n"); + goto out; + } + + monitor_printf(mon, "Server:\n"); + if (info->has_port) { + monitor_printf(mon, " address: %s:%" PRId64 "\n", + info->host, info->port); + } + if (info->has_tls_port) { + monitor_printf(mon, " address: %s:%" PRId64 " [tls]\n", + info->host, info->tls_port); + } + monitor_printf(mon, " migrated: %s\n", + info->migrated ? "true" : "false"); + monitor_printf(mon, " auth: %s\n", info->auth); + monitor_printf(mon, " compiled: %s\n", info->compiled_version); + monitor_printf(mon, " mouse-mode: %s\n", + SpiceQueryMouseMode_str(info->mouse_mode)); + + if (!info->has_channels || info->channels == NULL) { + monitor_printf(mon, "Channels: none\n"); + } else { + for (chan = info->channels; chan; chan = chan->next) { + monitor_printf(mon, "Channel:\n"); + monitor_printf(mon, " address: %s:%s%s\n", + chan->value->host, chan->value->port, + chan->value->tls ? " [tls]" : ""); + monitor_printf(mon, " session: %" PRId64 "\n", + chan->value->connection_id); + monitor_printf(mon, " channel: %" PRId64 ":%" PRId64 "\n", + chan->value->channel_type, chan->value->channel_id); + + channel_name = "unknown"; + if (chan->value->channel_type > 0 && + chan->value->channel_type < ARRAY_SIZE(channel_names) && + channel_names[chan->value->channel_type]) { + channel_name = channel_names[chan->value->channel_type]; + } + + monitor_printf(mon, " channel name: %s\n", channel_name); + } + } + +out: + qapi_free_SpiceInfo(info); +} +#endif + +void hmp_info_balloon(Monitor *mon, const QDict *qdict) +{ + BalloonInfo *info; + Error *err = NULL; + + info = qmp_query_balloon(&err); + if (hmp_handle_error(mon, err)) { + return; + } + + monitor_printf(mon, "balloon: actual=%" PRId64 "\n", info->actual >> 20); + + qapi_free_BalloonInfo(info); +} + +static void hmp_info_pci_device(Monitor *mon, const PciDeviceInfo *dev) +{ + PciMemoryRegionList *region; + + monitor_printf(mon, " Bus %2" PRId64 ", ", dev->bus); + monitor_printf(mon, "device %3" PRId64 ", function %" PRId64 ":\n", + dev->slot, dev->function); + monitor_printf(mon, " "); + + if (dev->class_info->has_desc) { + monitor_printf(mon, "%s", dev->class_info->desc); + } else { + monitor_printf(mon, "Class %04" PRId64, dev->class_info->q_class); + } + + monitor_printf(mon, ": PCI device %04" PRIx64 ":%04" PRIx64 "\n", + dev->id->vendor, dev->id->device); + if (dev->id->has_subsystem_vendor && dev->id->has_subsystem) { + monitor_printf(mon, " PCI subsystem %04" PRIx64 ":%04" PRIx64 "\n", + dev->id->subsystem_vendor, dev->id->subsystem); + } + + if (dev->has_irq) { + monitor_printf(mon, " IRQ %" PRId64 ", pin %c\n", + dev->irq, (char)('A' + dev->irq_pin - 1)); + } + + if (dev->has_pci_bridge) { + monitor_printf(mon, " BUS %" PRId64 ".\n", + dev->pci_bridge->bus->number); + monitor_printf(mon, " secondary bus %" PRId64 ".\n", + dev->pci_bridge->bus->secondary); + monitor_printf(mon, " subordinate bus %" PRId64 ".\n", + dev->pci_bridge->bus->subordinate); + + monitor_printf(mon, " IO range [0x%04"PRIx64", 0x%04"PRIx64"]\n", + dev->pci_bridge->bus->io_range->base, + dev->pci_bridge->bus->io_range->limit); + + monitor_printf(mon, + " memory range [0x%08"PRIx64", 0x%08"PRIx64"]\n", + dev->pci_bridge->bus->memory_range->base, + dev->pci_bridge->bus->memory_range->limit); + + monitor_printf(mon, " prefetchable memory range " + "[0x%08"PRIx64", 0x%08"PRIx64"]\n", + dev->pci_bridge->bus->prefetchable_range->base, + dev->pci_bridge->bus->prefetchable_range->limit); + } + + for (region = dev->regions; region; region = region->next) { + uint64_t addr, size; + + addr = region->value->address; + size = region->value->size; + + monitor_printf(mon, " BAR%" PRId64 ": ", region->value->bar); + + if (!strcmp(region->value->type, "io")) { + monitor_printf(mon, "I/O at 0x%04" PRIx64 + " [0x%04" PRIx64 "].\n", + addr, addr + size - 1); + } else { + monitor_printf(mon, "%d bit%s memory at 0x%08" PRIx64 + " [0x%08" PRIx64 "].\n", + region->value->mem_type_64 ? 64 : 32, + region->value->prefetch ? " prefetchable" : "", + addr, addr + size - 1); + } + } + + monitor_printf(mon, " id \"%s\"\n", dev->qdev_id); + + if (dev->has_pci_bridge) { + if (dev->pci_bridge->has_devices) { + PciDeviceInfoList *cdev; + for (cdev = dev->pci_bridge->devices; cdev; cdev = cdev->next) { + hmp_info_pci_device(mon, cdev->value); + } + } + } +} + +static int hmp_info_pic_foreach(Object *obj, void *opaque) +{ + InterruptStatsProvider *intc; + InterruptStatsProviderClass *k; + Monitor *mon = opaque; + + if (object_dynamic_cast(obj, TYPE_INTERRUPT_STATS_PROVIDER)) { + intc = INTERRUPT_STATS_PROVIDER(obj); + k = INTERRUPT_STATS_PROVIDER_GET_CLASS(obj); + if (k->print_info) { + k->print_info(intc, mon); + } else { + monitor_printf(mon, "Interrupt controller information not available for %s.\n", + object_get_typename(obj)); + } + } + + return 0; +} + +void hmp_info_pic(Monitor *mon, const QDict *qdict) +{ + object_child_foreach_recursive(object_get_root(), + hmp_info_pic_foreach, mon); +} + +void hmp_info_pci(Monitor *mon, const QDict *qdict) +{ + PciInfoList *info_list, *info; + Error *err = NULL; + + info_list = qmp_query_pci(&err); + if (err) { + monitor_printf(mon, "PCI devices not supported\n"); + error_free(err); + return; + } + + for (info = info_list; info; info = info->next) { + PciDeviceInfoList *dev; + + for (dev = info->value->devices; dev; dev = dev->next) { + hmp_info_pci_device(mon, dev->value); + } + } + + qapi_free_PciInfoList(info_list); +} + +void hmp_info_tpm(Monitor *mon, const QDict *qdict) +{ +#ifdef CONFIG_TPM + TPMInfoList *info_list, *info; + Error *err = NULL; + unsigned int c = 0; + TPMPassthroughOptions *tpo; + TPMEmulatorOptions *teo; + + info_list = qmp_query_tpm(&err); + if (err) { + monitor_printf(mon, "TPM device not supported\n"); + error_free(err); + return; + } + + if (info_list) { + monitor_printf(mon, "TPM device:\n"); + } + + for (info = info_list; info; info = info->next) { + TPMInfo *ti = info->value; + monitor_printf(mon, " tpm%d: model=%s\n", + c, TpmModel_str(ti->model)); + + monitor_printf(mon, " \\ %s: type=%s", + ti->id, TpmType_str(ti->options->type)); + + switch (ti->options->type) { + case TPM_TYPE_PASSTHROUGH: + tpo = ti->options->u.passthrough.data; + monitor_printf(mon, "%s%s%s%s", + tpo->has_path ? ",path=" : "", + tpo->has_path ? tpo->path : "", + tpo->has_cancel_path ? ",cancel-path=" : "", + tpo->has_cancel_path ? tpo->cancel_path : ""); + break; + case TPM_TYPE_EMULATOR: + teo = ti->options->u.emulator.data; + monitor_printf(mon, ",chardev=%s", teo->chardev); + break; + case TPM_TYPE__MAX: + break; + } + monitor_printf(mon, "\n"); + c++; + } + qapi_free_TPMInfoList(info_list); +#else + monitor_printf(mon, "TPM device not supported\n"); +#endif /* CONFIG_TPM */ +} + +void hmp_quit(Monitor *mon, const QDict *qdict) +{ + monitor_suspend(mon); + qmp_quit(NULL); +} + +void hmp_stop(Monitor *mon, const QDict *qdict) +{ + qmp_stop(NULL); +} + +void hmp_sync_profile(Monitor *mon, const QDict *qdict) +{ + const char *op = qdict_get_try_str(qdict, "op"); + + if (op == NULL) { + bool on = qsp_is_enabled(); + + monitor_printf(mon, "sync-profile is %s\n", on ? "on" : "off"); + return; + } + if (!strcmp(op, "on")) { + qsp_enable(); + } else if (!strcmp(op, "off")) { + qsp_disable(); + } else if (!strcmp(op, "reset")) { + qsp_reset(); + } else { + Error *err = NULL; + + error_setg(&err, QERR_INVALID_PARAMETER, op); + hmp_handle_error(mon, err); + } +} + +void hmp_system_reset(Monitor *mon, const QDict *qdict) +{ + qmp_system_reset(NULL); +} + +void hmp_system_powerdown(Monitor *mon, const QDict *qdict) +{ + qmp_system_powerdown(NULL); +} + +void hmp_exit_preconfig(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + + qmp_x_exit_preconfig(&err); + hmp_handle_error(mon, err); +} + +void hmp_cpu(Monitor *mon, const QDict *qdict) +{ + int64_t cpu_index; + + /* XXX: drop the monitor_set_cpu() usage when all HMP commands that + use it are converted to the QAPI */ + cpu_index = qdict_get_int(qdict, "index"); + if (monitor_set_cpu(mon, cpu_index) < 0) { + monitor_printf(mon, "invalid CPU index\n"); + } +} + +void hmp_memsave(Monitor *mon, const QDict *qdict) +{ + uint32_t size = qdict_get_int(qdict, "size"); + const char *filename = qdict_get_str(qdict, "filename"); + uint64_t addr = qdict_get_int(qdict, "val"); + Error *err = NULL; + int cpu_index = monitor_get_cpu_index(mon); + + if (cpu_index < 0) { + monitor_printf(mon, "No CPU available\n"); + return; + } + + qmp_memsave(addr, size, filename, true, cpu_index, &err); + hmp_handle_error(mon, err); +} + +void hmp_pmemsave(Monitor *mon, const QDict *qdict) +{ + uint32_t size = qdict_get_int(qdict, "size"); + const char *filename = qdict_get_str(qdict, "filename"); + uint64_t addr = qdict_get_int(qdict, "val"); + Error *err = NULL; + + qmp_pmemsave(addr, size, filename, &err); + hmp_handle_error(mon, err); +} + +void hmp_ringbuf_write(Monitor *mon, const QDict *qdict) +{ + const char *chardev = qdict_get_str(qdict, "device"); + const char *data = qdict_get_str(qdict, "data"); + Error *err = NULL; + + qmp_ringbuf_write(chardev, data, false, 0, &err); + + hmp_handle_error(mon, err); +} + +void hmp_ringbuf_read(Monitor *mon, const QDict *qdict) +{ + uint32_t size = qdict_get_int(qdict, "size"); + const char *chardev = qdict_get_str(qdict, "device"); + char *data; + Error *err = NULL; + int i; + + data = qmp_ringbuf_read(chardev, size, false, 0, &err); + if (hmp_handle_error(mon, err)) { + return; + } + + for (i = 0; data[i]; i++) { + unsigned char ch = data[i]; + + if (ch == '\\') { + monitor_printf(mon, "\\\\"); + } else if ((ch < 0x20 && ch != '\n' && ch != '\t') || ch == 0x7F) { + monitor_printf(mon, "\\u%04X", ch); + } else { + monitor_printf(mon, "%c", ch); + } + + } + monitor_printf(mon, "\n"); + g_free(data); +} + +void hmp_cont(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + + qmp_cont(&err); + hmp_handle_error(mon, err); +} + +void hmp_system_wakeup(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + + qmp_system_wakeup(&err); + hmp_handle_error(mon, err); +} + +void hmp_nmi(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + + qmp_inject_nmi(&err); + hmp_handle_error(mon, err); +} + +void hmp_set_link(Monitor *mon, const QDict *qdict) +{ + const char *name = qdict_get_str(qdict, "name"); + bool up = qdict_get_bool(qdict, "up"); + Error *err = NULL; + + qmp_set_link(name, up, &err); + hmp_handle_error(mon, err); +} + +void hmp_balloon(Monitor *mon, const QDict *qdict) +{ + int64_t value = qdict_get_int(qdict, "value"); + Error *err = NULL; + + qmp_balloon(value, &err); + hmp_handle_error(mon, err); +} + +void hmp_loadvm(Monitor *mon, const QDict *qdict) +{ + int saved_vm_running = runstate_is_running(); + const char *name = qdict_get_str(qdict, "name"); + Error *err = NULL; + + vm_stop(RUN_STATE_RESTORE_VM); + + if (load_snapshot(name, NULL, false, NULL, &err) && saved_vm_running) { + vm_start(); + } + hmp_handle_error(mon, err); +} + +void hmp_savevm(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + + save_snapshot(qdict_get_try_str(qdict, "name"), + true, NULL, false, NULL, &err); + hmp_handle_error(mon, err); +} + +void hmp_delvm(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + const char *name = qdict_get_str(qdict, "name"); + + delete_snapshot(name, false, NULL, &err); + hmp_handle_error(mon, err); +} + +void hmp_announce_self(Monitor *mon, const QDict *qdict) +{ + const char *interfaces_str = qdict_get_try_str(qdict, "interfaces"); + const char *id = qdict_get_try_str(qdict, "id"); + AnnounceParameters *params = QAPI_CLONE(AnnounceParameters, + migrate_announce_params()); + + qapi_free_strList(params->interfaces); + params->interfaces = strList_from_comma_list(interfaces_str); + params->has_interfaces = params->interfaces != NULL; + params->id = g_strdup(id); + params->has_id = !!params->id; + qmp_announce_self(params, NULL); + qapi_free_AnnounceParameters(params); +} + +void hmp_migrate_cancel(Monitor *mon, const QDict *qdict) +{ + qmp_migrate_cancel(NULL); +} + +void hmp_migrate_continue(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + const char *state = qdict_get_str(qdict, "state"); + int val = qapi_enum_parse(&MigrationStatus_lookup, state, -1, &err); + + if (val >= 0) { + qmp_migrate_continue(val, &err); + } + + hmp_handle_error(mon, err); +} + +void hmp_migrate_incoming(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + const char *uri = qdict_get_str(qdict, "uri"); + + qmp_migrate_incoming(uri, &err); + + hmp_handle_error(mon, err); +} + +void hmp_migrate_recover(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + const char *uri = qdict_get_str(qdict, "uri"); + + qmp_migrate_recover(uri, &err); + + hmp_handle_error(mon, err); +} + +void hmp_migrate_pause(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + + qmp_migrate_pause(&err); + + hmp_handle_error(mon, err); +} + + +void hmp_migrate_set_capability(Monitor *mon, const QDict *qdict) +{ + const char *cap = qdict_get_str(qdict, "capability"); + bool state = qdict_get_bool(qdict, "state"); + Error *err = NULL; + MigrationCapabilityStatusList *caps = NULL; + MigrationCapabilityStatus *value; + int val; + + val = qapi_enum_parse(&MigrationCapability_lookup, cap, -1, &err); + if (val < 0) { + goto end; + } + + value = g_malloc0(sizeof(*value)); + value->capability = val; + value->state = state; + QAPI_LIST_PREPEND(caps, value); + qmp_migrate_set_capabilities(caps, &err); + qapi_free_MigrationCapabilityStatusList(caps); + +end: + hmp_handle_error(mon, err); +} + +void hmp_migrate_set_parameter(Monitor *mon, const QDict *qdict) +{ + const char *param = qdict_get_str(qdict, "parameter"); + const char *valuestr = qdict_get_str(qdict, "value"); + Visitor *v = string_input_visitor_new(valuestr); + MigrateSetParameters *p = g_new0(MigrateSetParameters, 1); + uint64_t valuebw = 0; + uint64_t cache_size; + Error *err = NULL; + int val, ret; + + val = qapi_enum_parse(&MigrationParameter_lookup, param, -1, &err); + if (val < 0) { + goto cleanup; + } + + switch (val) { + case MIGRATION_PARAMETER_COMPRESS_LEVEL: + p->has_compress_level = true; + visit_type_uint8(v, param, &p->compress_level, &err); + break; + case MIGRATION_PARAMETER_COMPRESS_THREADS: + p->has_compress_threads = true; + visit_type_uint8(v, param, &p->compress_threads, &err); + break; + case MIGRATION_PARAMETER_COMPRESS_WAIT_THREAD: + p->has_compress_wait_thread = true; + visit_type_bool(v, param, &p->compress_wait_thread, &err); + break; + case MIGRATION_PARAMETER_DECOMPRESS_THREADS: + p->has_decompress_threads = true; + visit_type_uint8(v, param, &p->decompress_threads, &err); + break; + case MIGRATION_PARAMETER_THROTTLE_TRIGGER_THRESHOLD: + p->has_throttle_trigger_threshold = true; + visit_type_uint8(v, param, &p->throttle_trigger_threshold, &err); + break; + case MIGRATION_PARAMETER_CPU_THROTTLE_INITIAL: + p->has_cpu_throttle_initial = true; + visit_type_uint8(v, param, &p->cpu_throttle_initial, &err); + break; + case MIGRATION_PARAMETER_CPU_THROTTLE_INCREMENT: + p->has_cpu_throttle_increment = true; + visit_type_uint8(v, param, &p->cpu_throttle_increment, &err); + break; + case MIGRATION_PARAMETER_CPU_THROTTLE_TAILSLOW: + p->has_cpu_throttle_tailslow = true; + visit_type_bool(v, param, &p->cpu_throttle_tailslow, &err); + break; + case MIGRATION_PARAMETER_MAX_CPU_THROTTLE: + p->has_max_cpu_throttle = true; + visit_type_uint8(v, param, &p->max_cpu_throttle, &err); + break; + case MIGRATION_PARAMETER_TLS_CREDS: + p->has_tls_creds = true; + p->tls_creds = g_new0(StrOrNull, 1); + p->tls_creds->type = QTYPE_QSTRING; + visit_type_str(v, param, &p->tls_creds->u.s, &err); + break; + case MIGRATION_PARAMETER_TLS_HOSTNAME: + p->has_tls_hostname = true; + p->tls_hostname = g_new0(StrOrNull, 1); + p->tls_hostname->type = QTYPE_QSTRING; + visit_type_str(v, param, &p->tls_hostname->u.s, &err); + break; + case MIGRATION_PARAMETER_TLS_AUTHZ: + p->has_tls_authz = true; + p->tls_authz = g_new0(StrOrNull, 1); + p->tls_authz->type = QTYPE_QSTRING; + visit_type_str(v, param, &p->tls_authz->u.s, &err); + break; + case MIGRATION_PARAMETER_MAX_BANDWIDTH: + p->has_max_bandwidth = true; + /* + * Can't use visit_type_size() here, because it + * defaults to Bytes rather than Mebibytes. + */ + ret = qemu_strtosz_MiB(valuestr, NULL, &valuebw); + if (ret < 0 || valuebw > INT64_MAX + || (size_t)valuebw != valuebw) { + error_setg(&err, "Invalid size %s", valuestr); + break; + } + p->max_bandwidth = valuebw; + break; + case MIGRATION_PARAMETER_DOWNTIME_LIMIT: + p->has_downtime_limit = true; + visit_type_size(v, param, &p->downtime_limit, &err); + break; + case MIGRATION_PARAMETER_X_CHECKPOINT_DELAY: + p->has_x_checkpoint_delay = true; + visit_type_uint32(v, param, &p->x_checkpoint_delay, &err); + break; + case MIGRATION_PARAMETER_BLOCK_INCREMENTAL: + p->has_block_incremental = true; + visit_type_bool(v, param, &p->block_incremental, &err); + break; + case MIGRATION_PARAMETER_MULTIFD_CHANNELS: + p->has_multifd_channels = true; + visit_type_uint8(v, param, &p->multifd_channels, &err); + break; + case MIGRATION_PARAMETER_MULTIFD_COMPRESSION: + p->has_multifd_compression = true; + visit_type_MultiFDCompression(v, param, &p->multifd_compression, + &err); + break; + case MIGRATION_PARAMETER_MULTIFD_ZLIB_LEVEL: + p->has_multifd_zlib_level = true; + visit_type_uint8(v, param, &p->multifd_zlib_level, &err); + break; + case MIGRATION_PARAMETER_MULTIFD_ZSTD_LEVEL: + p->has_multifd_zstd_level = true; + visit_type_uint8(v, param, &p->multifd_zstd_level, &err); + break; + case MIGRATION_PARAMETER_XBZRLE_CACHE_SIZE: + p->has_xbzrle_cache_size = true; + if (!visit_type_size(v, param, &cache_size, &err)) { + break; + } + if (cache_size > INT64_MAX || (size_t)cache_size != cache_size) { + error_setg(&err, "Invalid size %s", valuestr); + break; + } + p->xbzrle_cache_size = cache_size; + break; + case MIGRATION_PARAMETER_MAX_POSTCOPY_BANDWIDTH: + p->has_max_postcopy_bandwidth = true; + visit_type_size(v, param, &p->max_postcopy_bandwidth, &err); + break; + case MIGRATION_PARAMETER_ANNOUNCE_INITIAL: + p->has_announce_initial = true; + visit_type_size(v, param, &p->announce_initial, &err); + break; + case MIGRATION_PARAMETER_ANNOUNCE_MAX: + p->has_announce_max = true; + visit_type_size(v, param, &p->announce_max, &err); + break; + case MIGRATION_PARAMETER_ANNOUNCE_ROUNDS: + p->has_announce_rounds = true; + visit_type_size(v, param, &p->announce_rounds, &err); + break; + case MIGRATION_PARAMETER_ANNOUNCE_STEP: + p->has_announce_step = true; + visit_type_size(v, param, &p->announce_step, &err); + break; + case MIGRATION_PARAMETER_BLOCK_BITMAP_MAPPING: + error_setg(&err, "The block-bitmap-mapping parameter can only be set " + "through QMP"); + break; + default: + assert(0); + } + + if (err) { + goto cleanup; + } + + qmp_migrate_set_parameters(p, &err); + + cleanup: + qapi_free_MigrateSetParameters(p); + visit_free(v); + hmp_handle_error(mon, err); +} + +void hmp_client_migrate_info(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + const char *protocol = qdict_get_str(qdict, "protocol"); + const char *hostname = qdict_get_str(qdict, "hostname"); + bool has_port = qdict_haskey(qdict, "port"); + int port = qdict_get_try_int(qdict, "port", -1); + bool has_tls_port = qdict_haskey(qdict, "tls-port"); + int tls_port = qdict_get_try_int(qdict, "tls-port", -1); + const char *cert_subject = qdict_get_try_str(qdict, "cert-subject"); + + qmp_client_migrate_info(protocol, hostname, + has_port, port, has_tls_port, tls_port, + !!cert_subject, cert_subject, &err); + hmp_handle_error(mon, err); +} + +void hmp_migrate_start_postcopy(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + qmp_migrate_start_postcopy(&err); + hmp_handle_error(mon, err); +} + +void hmp_x_colo_lost_heartbeat(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + + qmp_x_colo_lost_heartbeat(&err); + hmp_handle_error(mon, err); +} + +void hmp_set_password(Monitor *mon, const QDict *qdict) +{ + const char *protocol = qdict_get_str(qdict, "protocol"); + const char *password = qdict_get_str(qdict, "password"); + const char *connected = qdict_get_try_str(qdict, "connected"); + Error *err = NULL; + + qmp_set_password(protocol, password, !!connected, connected, &err); + hmp_handle_error(mon, err); +} + +void hmp_expire_password(Monitor *mon, const QDict *qdict) +{ + const char *protocol = qdict_get_str(qdict, "protocol"); + const char *whenstr = qdict_get_str(qdict, "time"); + Error *err = NULL; + + qmp_expire_password(protocol, whenstr, &err); + hmp_handle_error(mon, err); +} + + +#ifdef CONFIG_VNC +static void hmp_change_read_arg(void *opaque, const char *password, + void *readline_opaque) +{ + qmp_change_vnc_password(password, NULL); + monitor_read_command(opaque, 1); +} +#endif + +void hmp_change(Monitor *mon, const QDict *qdict) +{ + const char *device = qdict_get_str(qdict, "device"); + const char *target = qdict_get_str(qdict, "target"); + const char *arg = qdict_get_try_str(qdict, "arg"); + const char *read_only = qdict_get_try_str(qdict, "read-only-mode"); + BlockdevChangeReadOnlyMode read_only_mode = 0; + Error *err = NULL; + +#ifdef CONFIG_VNC + if (strcmp(device, "vnc") == 0) { + if (read_only) { + monitor_printf(mon, + "Parameter 'read-only-mode' is invalid for VNC\n"); + return; + } + if (strcmp(target, "passwd") == 0 || + strcmp(target, "password") == 0) { + if (!arg) { + MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, common); + monitor_read_password(hmp_mon, hmp_change_read_arg, NULL); + return; + } else { + qmp_change_vnc_password(arg, &err); + } + } else { + monitor_printf(mon, "Expected 'password' after 'vnc'\n"); + } + } else +#endif + { + if (read_only) { + read_only_mode = + qapi_enum_parse(&BlockdevChangeReadOnlyMode_lookup, + read_only, + BLOCKDEV_CHANGE_READ_ONLY_MODE_RETAIN, &err); + if (err) { + goto end; + } + } + + qmp_blockdev_change_medium(true, device, false, NULL, target, + !!arg, arg, !!read_only, read_only_mode, + &err); + } + +end: + hmp_handle_error(mon, err); +} + +typedef struct HMPMigrationStatus { + QEMUTimer *timer; + Monitor *mon; + bool is_block_migration; +} HMPMigrationStatus; + +static void hmp_migrate_status_cb(void *opaque) +{ + HMPMigrationStatus *status = opaque; + MigrationInfo *info; + + info = qmp_query_migrate(NULL); + if (!info->has_status || info->status == MIGRATION_STATUS_ACTIVE || + info->status == MIGRATION_STATUS_SETUP) { + if (info->has_disk) { + int progress; + + if (info->disk->remaining) { + progress = info->disk->transferred * 100 / info->disk->total; + } else { + progress = 100; + } + + monitor_printf(status->mon, "Completed %d %%\r", progress); + monitor_flush(status->mon); + } + + timer_mod(status->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000); + } else { + if (status->is_block_migration) { + monitor_printf(status->mon, "\n"); + } + if (info->has_error_desc) { + error_report("%s", info->error_desc); + } + monitor_resume(status->mon); + timer_free(status->timer); + g_free(status); + } + + qapi_free_MigrationInfo(info); +} + +void hmp_migrate(Monitor *mon, const QDict *qdict) +{ + bool detach = qdict_get_try_bool(qdict, "detach", false); + bool blk = qdict_get_try_bool(qdict, "blk", false); + bool inc = qdict_get_try_bool(qdict, "inc", false); + bool resume = qdict_get_try_bool(qdict, "resume", false); + const char *uri = qdict_get_str(qdict, "uri"); + Error *err = NULL; + + qmp_migrate(uri, !!blk, blk, !!inc, inc, + false, false, true, resume, &err); + if (hmp_handle_error(mon, err)) { + return; + } + + if (!detach) { + HMPMigrationStatus *status; + + if (monitor_suspend(mon) < 0) { + monitor_printf(mon, "terminal does not allow synchronous " + "migration, continuing detached\n"); + return; + } + + status = g_malloc0(sizeof(*status)); + status->mon = mon; + status->is_block_migration = blk || inc; + status->timer = timer_new_ms(QEMU_CLOCK_REALTIME, hmp_migrate_status_cb, + status); + timer_mod(status->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME)); + } +} + +void hmp_netdev_add(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + QemuOpts *opts; + const char *type = qdict_get_try_str(qdict, "type"); + + if (type && is_help_option(type)) { + show_netdevs(); + return; + } + opts = qemu_opts_from_qdict(qemu_find_opts("netdev"), qdict, &err); + if (err) { + goto out; + } + + netdev_add(opts, &err); + if (err) { + qemu_opts_del(opts); + } + +out: + hmp_handle_error(mon, err); +} + +void hmp_netdev_del(Monitor *mon, const QDict *qdict) +{ + const char *id = qdict_get_str(qdict, "id"); + Error *err = NULL; + + qmp_netdev_del(id, &err); + hmp_handle_error(mon, err); +} + +void hmp_object_add(Monitor *mon, const QDict *qdict) +{ + const char *options = qdict_get_str(qdict, "object"); + Error *err = NULL; + + user_creatable_add_from_str(options, &err); + hmp_handle_error(mon, err); +} + +void hmp_getfd(Monitor *mon, const QDict *qdict) +{ + const char *fdname = qdict_get_str(qdict, "fdname"); + Error *err = NULL; + + qmp_getfd(fdname, &err); + hmp_handle_error(mon, err); +} + +void hmp_closefd(Monitor *mon, const QDict *qdict) +{ + const char *fdname = qdict_get_str(qdict, "fdname"); + Error *err = NULL; + + qmp_closefd(fdname, &err); + hmp_handle_error(mon, err); +} + +void hmp_sendkey(Monitor *mon, const QDict *qdict) +{ + const char *keys = qdict_get_str(qdict, "keys"); + KeyValue *v = NULL; + KeyValueList *head = NULL, **tail = &head; + int has_hold_time = qdict_haskey(qdict, "hold-time"); + int hold_time = qdict_get_try_int(qdict, "hold-time", -1); + Error *err = NULL; + const char *separator; + int keyname_len; + + while (1) { + separator = qemu_strchrnul(keys, '-'); + keyname_len = separator - keys; + + /* Be compatible with old interface, convert user inputted "<" */ + if (keys[0] == '<' && keyname_len == 1) { + keys = "less"; + keyname_len = 4; + } + + v = g_malloc0(sizeof(*v)); + + if (strstart(keys, "0x", NULL)) { + char *endp; + int value = strtoul(keys, &endp, 0); + assert(endp <= keys + keyname_len); + if (endp != keys + keyname_len) { + goto err_out; + } + v->type = KEY_VALUE_KIND_NUMBER; + v->u.number.data = value; + } else { + int idx = index_from_key(keys, keyname_len); + if (idx == Q_KEY_CODE__MAX) { + goto err_out; + } + v->type = KEY_VALUE_KIND_QCODE; + v->u.qcode.data = idx; + } + QAPI_LIST_APPEND(tail, v); + v = NULL; + + if (!*separator) { + break; + } + keys = separator + 1; + } + + qmp_send_key(head, has_hold_time, hold_time, &err); + hmp_handle_error(mon, err); + +out: + qapi_free_KeyValue(v); + qapi_free_KeyValueList(head); + return; + +err_out: + monitor_printf(mon, "invalid parameter: %.*s\n", keyname_len, keys); + goto out; +} + +void coroutine_fn +hmp_screendump(Monitor *mon, const QDict *qdict) +{ + const char *filename = qdict_get_str(qdict, "filename"); + const char *id = qdict_get_try_str(qdict, "device"); + int64_t head = qdict_get_try_int(qdict, "head", 0); + Error *err = NULL; + + qmp_screendump(filename, id != NULL, id, id != NULL, head, &err); + hmp_handle_error(mon, err); +} + +void hmp_chardev_add(Monitor *mon, const QDict *qdict) +{ + const char *args = qdict_get_str(qdict, "args"); + Error *err = NULL; + QemuOpts *opts; + + opts = qemu_opts_parse_noisily(qemu_find_opts("chardev"), args, true); + if (opts == NULL) { + error_setg(&err, "Parsing chardev args failed"); + } else { + qemu_chr_new_from_opts(opts, NULL, &err); + qemu_opts_del(opts); + } + hmp_handle_error(mon, err); +} + +void hmp_chardev_change(Monitor *mon, const QDict *qdict) +{ + const char *args = qdict_get_str(qdict, "args"); + const char *id; + Error *err = NULL; + ChardevBackend *backend = NULL; + ChardevReturn *ret = NULL; + QemuOpts *opts = qemu_opts_parse_noisily(qemu_find_opts("chardev"), args, + true); + if (!opts) { + error_setg(&err, "Parsing chardev args failed"); + goto end; + } + + id = qdict_get_str(qdict, "id"); + if (qemu_opts_id(opts)) { + error_setg(&err, "Unexpected 'id' parameter"); + goto end; + } + + backend = qemu_chr_parse_opts(opts, &err); + if (!backend) { + goto end; + } + + ret = qmp_chardev_change(id, backend, &err); + +end: + qapi_free_ChardevReturn(ret); + qapi_free_ChardevBackend(backend); + qemu_opts_del(opts); + hmp_handle_error(mon, err); +} + +void hmp_chardev_remove(Monitor *mon, const QDict *qdict) +{ + Error *local_err = NULL; + + qmp_chardev_remove(qdict_get_str(qdict, "id"), &local_err); + hmp_handle_error(mon, local_err); +} + +void hmp_chardev_send_break(Monitor *mon, const QDict *qdict) +{ + Error *local_err = NULL; + + qmp_chardev_send_break(qdict_get_str(qdict, "id"), &local_err); + hmp_handle_error(mon, local_err); +} + +void hmp_object_del(Monitor *mon, const QDict *qdict) +{ + const char *id = qdict_get_str(qdict, "id"); + Error *err = NULL; + + user_creatable_del(id, &err); + hmp_handle_error(mon, err); +} + +void hmp_info_memory_devices(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + MemoryDeviceInfoList *info_list = qmp_query_memory_devices(&err); + MemoryDeviceInfoList *info; + VirtioPMEMDeviceInfo *vpi; + VirtioMEMDeviceInfo *vmi; + MemoryDeviceInfo *value; + PCDIMMDeviceInfo *di; + SgxEPCDeviceInfo *se; + + for (info = info_list; info; info = info->next) { + value = info->value; + + if (value) { + switch (value->type) { + case MEMORY_DEVICE_INFO_KIND_DIMM: + case MEMORY_DEVICE_INFO_KIND_NVDIMM: + di = value->type == MEMORY_DEVICE_INFO_KIND_DIMM ? + value->u.dimm.data : value->u.nvdimm.data; + monitor_printf(mon, "Memory device [%s]: \"%s\"\n", + MemoryDeviceInfoKind_str(value->type), + di->id ? di->id : ""); + monitor_printf(mon, " addr: 0x%" PRIx64 "\n", di->addr); + monitor_printf(mon, " slot: %" PRId64 "\n", di->slot); + monitor_printf(mon, " node: %" PRId64 "\n", di->node); + monitor_printf(mon, " size: %" PRIu64 "\n", di->size); + monitor_printf(mon, " memdev: %s\n", di->memdev); + monitor_printf(mon, " hotplugged: %s\n", + di->hotplugged ? "true" : "false"); + monitor_printf(mon, " hotpluggable: %s\n", + di->hotpluggable ? "true" : "false"); + break; + case MEMORY_DEVICE_INFO_KIND_VIRTIO_PMEM: + vpi = value->u.virtio_pmem.data; + monitor_printf(mon, "Memory device [%s]: \"%s\"\n", + MemoryDeviceInfoKind_str(value->type), + vpi->id ? vpi->id : ""); + monitor_printf(mon, " memaddr: 0x%" PRIx64 "\n", vpi->memaddr); + monitor_printf(mon, " size: %" PRIu64 "\n", vpi->size); + monitor_printf(mon, " memdev: %s\n", vpi->memdev); + break; + case MEMORY_DEVICE_INFO_KIND_VIRTIO_MEM: + vmi = value->u.virtio_mem.data; + monitor_printf(mon, "Memory device [%s]: \"%s\"\n", + MemoryDeviceInfoKind_str(value->type), + vmi->id ? vmi->id : ""); + monitor_printf(mon, " memaddr: 0x%" PRIx64 "\n", vmi->memaddr); + monitor_printf(mon, " node: %" PRId64 "\n", vmi->node); + monitor_printf(mon, " requested-size: %" PRIu64 "\n", + vmi->requested_size); + monitor_printf(mon, " size: %" PRIu64 "\n", vmi->size); + monitor_printf(mon, " max-size: %" PRIu64 "\n", vmi->max_size); + monitor_printf(mon, " block-size: %" PRIu64 "\n", + vmi->block_size); + monitor_printf(mon, " memdev: %s\n", vmi->memdev); + break; + case MEMORY_DEVICE_INFO_KIND_SGX_EPC: + se = value->u.sgx_epc.data; + monitor_printf(mon, "Memory device [%s]: \"%s\"\n", + MemoryDeviceInfoKind_str(value->type), + se->id ? se->id : ""); + monitor_printf(mon, " memaddr: 0x%" PRIx64 "\n", se->memaddr); + monitor_printf(mon, " size: %" PRIu64 "\n", se->size); + monitor_printf(mon, " memdev: %s\n", se->memdev); + break; + default: + g_assert_not_reached(); + } + } + } + + qapi_free_MemoryDeviceInfoList(info_list); + hmp_handle_error(mon, err); +} + +void hmp_info_iothreads(Monitor *mon, const QDict *qdict) +{ + IOThreadInfoList *info_list = qmp_query_iothreads(NULL); + IOThreadInfoList *info; + IOThreadInfo *value; + + for (info = info_list; info; info = info->next) { + value = info->value; + monitor_printf(mon, "%s:\n", value->id); + monitor_printf(mon, " thread_id=%" PRId64 "\n", value->thread_id); + monitor_printf(mon, " poll-max-ns=%" PRId64 "\n", value->poll_max_ns); + monitor_printf(mon, " poll-grow=%" PRId64 "\n", value->poll_grow); + monitor_printf(mon, " poll-shrink=%" PRId64 "\n", value->poll_shrink); + monitor_printf(mon, " aio-max-batch=%" PRId64 "\n", + value->aio_max_batch); + } + + qapi_free_IOThreadInfoList(info_list); +} + +void hmp_rocker(Monitor *mon, const QDict *qdict) +{ + const char *name = qdict_get_str(qdict, "name"); + RockerSwitch *rocker; + Error *err = NULL; + + rocker = qmp_query_rocker(name, &err); + if (hmp_handle_error(mon, err)) { + return; + } + + monitor_printf(mon, "name: %s\n", rocker->name); + monitor_printf(mon, "id: 0x%" PRIx64 "\n", rocker->id); + monitor_printf(mon, "ports: %d\n", rocker->ports); + + qapi_free_RockerSwitch(rocker); +} + +void hmp_rocker_ports(Monitor *mon, const QDict *qdict) +{ + RockerPortList *list, *port; + const char *name = qdict_get_str(qdict, "name"); + Error *err = NULL; + + list = qmp_query_rocker_ports(name, &err); + if (hmp_handle_error(mon, err)) { + return; + } + + monitor_printf(mon, " ena/ speed/ auto\n"); + monitor_printf(mon, " port link duplex neg?\n"); + + for (port = list; port; port = port->next) { + monitor_printf(mon, "%10s %-4s %-3s %2s %s\n", + port->value->name, + port->value->enabled ? port->value->link_up ? + "up" : "down" : "!ena", + port->value->speed == 10000 ? "10G" : "??", + port->value->duplex ? "FD" : "HD", + port->value->autoneg ? "Yes" : "No"); + } + + qapi_free_RockerPortList(list); +} + +void hmp_rocker_of_dpa_flows(Monitor *mon, const QDict *qdict) +{ + RockerOfDpaFlowList *list, *info; + const char *name = qdict_get_str(qdict, "name"); + uint32_t tbl_id = qdict_get_try_int(qdict, "tbl_id", -1); + Error *err = NULL; + + list = qmp_query_rocker_of_dpa_flows(name, tbl_id != -1, tbl_id, &err); + if (hmp_handle_error(mon, err)) { + return; + } + + monitor_printf(mon, "prio tbl hits key(mask) --> actions\n"); + + for (info = list; info; info = info->next) { + RockerOfDpaFlow *flow = info->value; + RockerOfDpaFlowKey *key = flow->key; + RockerOfDpaFlowMask *mask = flow->mask; + RockerOfDpaFlowAction *action = flow->action; + + if (flow->hits) { + monitor_printf(mon, "%-4d %-3d %-4" PRIu64, + key->priority, key->tbl_id, flow->hits); + } else { + monitor_printf(mon, "%-4d %-3d ", + key->priority, key->tbl_id); + } + + if (key->has_in_pport) { + monitor_printf(mon, " pport %d", key->in_pport); + if (mask->has_in_pport) { + monitor_printf(mon, "(0x%x)", mask->in_pport); + } + } + + if (key->has_vlan_id) { + monitor_printf(mon, " vlan %d", + key->vlan_id & VLAN_VID_MASK); + if (mask->has_vlan_id) { + monitor_printf(mon, "(0x%x)", mask->vlan_id); + } + } + + if (key->has_tunnel_id) { + monitor_printf(mon, " tunnel %d", key->tunnel_id); + if (mask->has_tunnel_id) { + monitor_printf(mon, "(0x%x)", mask->tunnel_id); + } + } + + if (key->has_eth_type) { + switch (key->eth_type) { + case 0x0806: + monitor_printf(mon, " ARP"); + break; + case 0x0800: + monitor_printf(mon, " IP"); + break; + case 0x86dd: + monitor_printf(mon, " IPv6"); + break; + case 0x8809: + monitor_printf(mon, " LACP"); + break; + case 0x88cc: + monitor_printf(mon, " LLDP"); + break; + default: + monitor_printf(mon, " eth type 0x%04x", key->eth_type); + break; + } + } + + if (key->has_eth_src) { + if ((strcmp(key->eth_src, "01:00:00:00:00:00") == 0) && + (mask->has_eth_src) && + (strcmp(mask->eth_src, "01:00:00:00:00:00") == 0)) { + monitor_printf(mon, " src <any mcast/bcast>"); + } else if ((strcmp(key->eth_src, "00:00:00:00:00:00") == 0) && + (mask->has_eth_src) && + (strcmp(mask->eth_src, "01:00:00:00:00:00") == 0)) { + monitor_printf(mon, " src <any ucast>"); + } else { + monitor_printf(mon, " src %s", key->eth_src); + if (mask->has_eth_src) { + monitor_printf(mon, "(%s)", mask->eth_src); + } + } + } + + if (key->has_eth_dst) { + if ((strcmp(key->eth_dst, "01:00:00:00:00:00") == 0) && + (mask->has_eth_dst) && + (strcmp(mask->eth_dst, "01:00:00:00:00:00") == 0)) { + monitor_printf(mon, " dst <any mcast/bcast>"); + } else if ((strcmp(key->eth_dst, "00:00:00:00:00:00") == 0) && + (mask->has_eth_dst) && + (strcmp(mask->eth_dst, "01:00:00:00:00:00") == 0)) { + monitor_printf(mon, " dst <any ucast>"); + } else { + monitor_printf(mon, " dst %s", key->eth_dst); + if (mask->has_eth_dst) { + monitor_printf(mon, "(%s)", mask->eth_dst); + } + } + } + + if (key->has_ip_proto) { + monitor_printf(mon, " proto %d", key->ip_proto); + if (mask->has_ip_proto) { + monitor_printf(mon, "(0x%x)", mask->ip_proto); + } + } + + if (key->has_ip_tos) { + monitor_printf(mon, " TOS %d", key->ip_tos); + if (mask->has_ip_tos) { + monitor_printf(mon, "(0x%x)", mask->ip_tos); + } + } + + if (key->has_ip_dst) { + monitor_printf(mon, " dst %s", key->ip_dst); + } + + if (action->has_goto_tbl || action->has_group_id || + action->has_new_vlan_id) { + monitor_printf(mon, " -->"); + } + + if (action->has_new_vlan_id) { + monitor_printf(mon, " apply new vlan %d", + ntohs(action->new_vlan_id)); + } + + if (action->has_group_id) { + monitor_printf(mon, " write group 0x%08x", action->group_id); + } + + if (action->has_goto_tbl) { + monitor_printf(mon, " goto tbl %d", action->goto_tbl); + } + + monitor_printf(mon, "\n"); + } + + qapi_free_RockerOfDpaFlowList(list); +} + +void hmp_rocker_of_dpa_groups(Monitor *mon, const QDict *qdict) +{ + RockerOfDpaGroupList *list, *g; + const char *name = qdict_get_str(qdict, "name"); + uint8_t type = qdict_get_try_int(qdict, "type", 9); + Error *err = NULL; + + list = qmp_query_rocker_of_dpa_groups(name, type != 9, type, &err); + if (hmp_handle_error(mon, err)) { + return; + } + + monitor_printf(mon, "id (decode) --> buckets\n"); + + for (g = list; g; g = g->next) { + RockerOfDpaGroup *group = g->value; + bool set = false; + + monitor_printf(mon, "0x%08x", group->id); + + monitor_printf(mon, " (type %s", group->type == 0 ? "L2 interface" : + group->type == 1 ? "L2 rewrite" : + group->type == 2 ? "L3 unicast" : + group->type == 3 ? "L2 multicast" : + group->type == 4 ? "L2 flood" : + group->type == 5 ? "L3 interface" : + group->type == 6 ? "L3 multicast" : + group->type == 7 ? "L3 ECMP" : + group->type == 8 ? "L2 overlay" : + "unknown"); + + if (group->has_vlan_id) { + monitor_printf(mon, " vlan %d", group->vlan_id); + } + + if (group->has_pport) { + monitor_printf(mon, " pport %d", group->pport); + } + + if (group->has_index) { + monitor_printf(mon, " index %d", group->index); + } + + monitor_printf(mon, ") -->"); + + if (group->has_set_vlan_id && group->set_vlan_id) { + set = true; + monitor_printf(mon, " set vlan %d", + group->set_vlan_id & VLAN_VID_MASK); + } + + if (group->has_set_eth_src) { + if (!set) { + set = true; + monitor_printf(mon, " set"); + } + monitor_printf(mon, " src %s", group->set_eth_src); + } + + if (group->has_set_eth_dst) { + if (!set) { + monitor_printf(mon, " set"); + } + monitor_printf(mon, " dst %s", group->set_eth_dst); + } + + if (group->has_ttl_check && group->ttl_check) { + monitor_printf(mon, " check TTL"); + } + + if (group->has_group_id && group->group_id) { + monitor_printf(mon, " group id 0x%08x", group->group_id); + } + + if (group->has_pop_vlan && group->pop_vlan) { + monitor_printf(mon, " pop vlan"); + } + + if (group->has_out_pport) { + monitor_printf(mon, " out pport %d", group->out_pport); + } + + if (group->has_group_ids) { + struct uint32List *id; + + monitor_printf(mon, " groups ["); + for (id = group->group_ids; id; id = id->next) { + monitor_printf(mon, "0x%08x", id->value); + if (id->next) { + monitor_printf(mon, ","); + } + } + monitor_printf(mon, "]"); + } + + monitor_printf(mon, "\n"); + } + + qapi_free_RockerOfDpaGroupList(list); +} + +void hmp_info_vm_generation_id(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + GuidInfo *info = qmp_query_vm_generation_id(&err); + if (info) { + monitor_printf(mon, "%s\n", info->guid); + } + hmp_handle_error(mon, err); + qapi_free_GuidInfo(info); +} + +void hmp_info_memory_size_summary(Monitor *mon, const QDict *qdict) +{ + Error *err = NULL; + MemoryInfo *info = qmp_query_memory_size_summary(&err); + if (info) { + monitor_printf(mon, "base memory: %" PRIu64 "\n", + info->base_memory); + + if (info->has_plugged_memory) { + monitor_printf(mon, "plugged memory: %" PRIu64 "\n", + info->plugged_memory); + } + + qapi_free_MemoryInfo(info); + } + hmp_handle_error(mon, err); +} diff --git a/monitor/hmp.c b/monitor/hmp.c new file mode 100644 index 000000000..b20737e63 --- /dev/null +++ b/monitor/hmp.c @@ -0,0 +1,1487 @@ +/* + * 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 <dirent.h> +#include "hw/qdev-core.h" +#include "monitor-internal.h" +#include "monitor/hmp.h" +#include "qapi/error.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qnum.h" +#include "qemu/config-file.h" +#include "qemu/ctype.h" +#include "qemu/cutils.h" +#include "qemu/log.h" +#include "qemu/option.h" +#include "qemu/units.h" +#include "sysemu/block-backend.h" +#include "sysemu/runstate.h" +#include "trace.h" + +static void monitor_command_cb(void *opaque, const char *cmdline, + void *readline_opaque) +{ + MonitorHMP *mon = opaque; + + monitor_suspend(&mon->common); + handle_hmp_command(mon, cmdline); + monitor_resume(&mon->common); +} + +void monitor_read_command(MonitorHMP *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(MonitorHMP *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->common, + "terminal does not support password prompting\n"); + return -ENOTTY; + } +} + +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 HMPCommand *cmd) +{ + if (!cmd->flags) { + return false; + } + + return strchr(cmd->flags, 'p'); +} + +static bool cmd_available(const HMPCommand *cmd) +{ + return phase_check(PHASE_MACHINE_READY) || cmd_can_preconfig(cmd); +} + +static void help_cmd_dump_one(Monitor *mon, + const HMPCommand *cmd, + char **prefix_args, + int prefix_args_nb) +{ + int i; + + if (!cmd_available(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 HMPCommand *cmds, + char **args, int nb_args, int arg_index) +{ + const HMPCommand *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 (hmp_compare_cmd(args[arg_index], cmd->name) && + cmd_available(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" : " "); + } +} + +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, hmp_cmds, args, nb_args, 0); + + free_cmdline_args(args, nb_args); +} + +/*******************************************************************/ + +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); +} + +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; + int64_t 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(mon, ®, 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 HMPCommand *search_dispatch_table(const HMPCommand *disp_table, + const char *cmdname) +{ + const HMPCommand *cmd; + + for (cmd = disp_table; cmd->name != NULL; cmd++) { + if (hmp_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 HMPCommand *monitor_parse_command(MonitorHMP *hmp_mon, + const char *cmdp_start, + const char **cmdp, + HMPCommand *table) +{ + Monitor *mon = &hmp_mon->common; + const char *p; + const HMPCommand *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 (!cmd_available(cmd)) { + monitor_printf(mon, "Command '%.*s' not available " + "until machine initialization has completed.\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(hmp_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 HMPCommand *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 hmp_info_human_readable_text(Monitor *mon, + HumanReadableText *(*handler)(Error **)) +{ + Error *err = NULL; + g_autoptr(HumanReadableText) info = handler(&err); + + if (hmp_handle_error(mon, err)) { + return; + } + + monitor_printf(mon, "%s", info->human_readable_text); +} + +static void handle_hmp_command_exec(Monitor *mon, + const HMPCommand *cmd, + QDict *qdict) +{ + if (cmd->cmd_info_hrt) { + hmp_info_human_readable_text(mon, + cmd->cmd_info_hrt); + } else { + cmd->cmd(mon, qdict); + } +} + +typedef struct HandleHmpCommandCo { + Monitor *mon; + const HMPCommand *cmd; + QDict *qdict; + bool done; +} HandleHmpCommandCo; + +static void handle_hmp_command_co(void *opaque) +{ + HandleHmpCommandCo *data = opaque; + handle_hmp_command_exec(data->mon, data->cmd, data->qdict); + monitor_set_cur(qemu_coroutine_self(), NULL); + data->done = true; +} + +void handle_hmp_command(MonitorHMP *mon, const char *cmdline) +{ + QDict *qdict; + const HMPCommand *cmd; + const char *cmd_start = cmdline; + + trace_handle_hmp_command(mon, cmdline); + + cmd = monitor_parse_command(mon, cmdline, &cmdline, hmp_cmds); + if (!cmd) { + return; + } + + if (!cmd->cmd && !cmd->cmd_info_hrt) { + /* FIXME: is it useful to try autoload modules here ??? */ + monitor_printf(&mon->common, "Command \"%.*s\" is not available.\n", + (int)(cmdline - cmd_start), cmd_start); + return; + } + + qdict = monitor_parse_arguments(&mon->common, &cmdline, cmd); + if (!qdict) { + while (cmdline > cmd_start && qemu_isspace(cmdline[-1])) { + cmdline--; + } + monitor_printf(&mon->common, "Try \"help %.*s\" for more information\n", + (int)(cmdline - cmd_start), cmd_start); + return; + } + + if (!cmd->coroutine) { + /* old_mon is non-NULL when called from qmp_human_monitor_command() */ + Monitor *old_mon = monitor_set_cur(qemu_coroutine_self(), &mon->common); + handle_hmp_command_exec(&mon->common, cmd, qdict); + monitor_set_cur(qemu_coroutine_self(), old_mon); + } else { + HandleHmpCommandCo data = { + .mon = &mon->common, + .cmd = cmd, + .qdict = qdict, + .done = false, + }; + Coroutine *co = qemu_coroutine_create(handle_hmp_command_co, &data); + monitor_set_cur(co, &mon->common); + aio_co_enter(qemu_get_aio_context(), co); + AIO_WAIT_WHILE(qemu_get_aio_context(), !data.done); + } + + qobject_unref(qdict); +} + +static void cmd_completion(MonitorHMP *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(MonitorHMP *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 monitor_find_completion_by_table(MonitorHMP *mon, + const HMPCommand *cmd_table, + char **args, + int nb_args) +{ + const char *cmdname; + int i; + const char *ptype, *old_ptype, *str, *name; + const HMPCommand *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 (cmd_available(cmd)) { + cmd_completion(mon, cmdname, cmd->name); + } + } + } else { + /* find the command */ + for (cmd = cmd_table; cmd->name != NULL; cmd++) { + if (hmp_compare_cmd(args[0], cmd->name) && + cmd_available(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) +{ + MonitorHMP *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, hmp_cmds, args, nb_args); + +cleanup: + free_cmdline_args(args, nb_args); +} + +static void monitor_read(void *opaque, const uint8_t *buf, int size) +{ + MonitorHMP *mon = container_of(opaque, MonitorHMP, common); + int i; + + if (mon->rs) { + for (i = 0; i < size; i++) { + readline_handle_byte(mon->rs, buf[i]); + } + } else { + if (size == 0 || buf[size - 1] != 0) { + monitor_printf(&mon->common, "corrupted command\n"); + } else { + handle_hmp_command(mon, (char *)buf); + } + } +} + +static void monitor_event(void *opaque, QEMUChrEvent event) +{ + Monitor *mon = opaque; + MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, common); + + 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(hmp_mon->rs); + monitor_resume(mon); + monitor_flush(mon); + } else { + qatomic_mb_set(&mon->suspend_cnt, 0); + } + break; + + case CHR_EVENT_MUX_OUT: + if (mon->reset_seen) { + if (qatomic_mb_read(&mon->suspend_cnt) == 0) { + monitor_printf(mon, "\n"); + } + monitor_flush(mon); + monitor_suspend(mon); + } else { + qatomic_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(hmp_mon->rs); + readline_show_prompt(hmp_mon->rs); + } + mon->reset_seen = 1; + mon_refcount++; + break; + + case CHR_EVENT_CLOSED: + mon_refcount--; + monitor_fdsets_cleanup(); + break; + + case CHR_EVENT_BREAK: + /* Ignored */ + break; + } +} + + +/* + * 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, ...) +{ + MonitorHMP *mon = opaque; + va_list ap; + va_start(ap, fmt); + monitor_vprintf(&mon->common, fmt, ap); + va_end(ap); +} + +static void monitor_readline_flush(void *opaque) +{ + MonitorHMP *mon = opaque; + monitor_flush(&mon->common); +} + +void monitor_init_hmp(Chardev *chr, bool use_readline, Error **errp) +{ + MonitorHMP *mon = g_new0(MonitorHMP, 1); + + if (!qemu_chr_fe_init(&mon->common.chr, chr, errp)) { + g_free(mon); + return; + } + + monitor_data_init(&mon->common, false, false, false); + + mon->use_readline = use_readline; + if (mon->use_readline) { + mon->rs = readline_init(monitor_readline_printf, + monitor_readline_flush, + mon, + monitor_find_completion); + monitor_read_command(mon, 0); + } + + qemu_chr_fe_set_handlers(&mon->common.chr, monitor_can_read, monitor_read, + monitor_event, NULL, &mon->common, NULL, true); + monitor_list_append(&mon->common); +} diff --git a/monitor/meson.build b/monitor/meson.build new file mode 100644 index 000000000..6d00985ac --- /dev/null +++ b/monitor/meson.build @@ -0,0 +1,9 @@ +qmp_ss.add(files('monitor.c', 'qmp.c', 'qmp-cmds-control.c')) + +softmmu_ss.add(files( + 'hmp-cmds.c', + 'hmp.c', +)) +softmmu_ss.add([spice_headers, files('qmp-cmds.c')]) + +specific_ss.add(when: 'CONFIG_SOFTMMU', if_true: [files('misc.c'), spice]) diff --git a/monitor/misc.c b/monitor/misc.c new file mode 100644 index 000000000..a3a6e4784 --- /dev/null +++ b/monitor/misc.c @@ -0,0 +1,1981 @@ +/* + * 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 "monitor-internal.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 "ui/qemu-spice.h" +#include "qemu/config-file.h" +#include "qemu/ctype.h" +#include "ui/console.h" +#include "ui/input.h" +#include "audio/audio.h" +#include "disas/disas.h" +#include "sysemu/balloon.h" +#include "qemu/timer.h" +#include "sysemu/hw_accel.h" +#include "sysemu/runstate.h" +#include "authz/list.h" +#include "qapi/util.h" +#include "sysemu/blockdev.h" +#include "sysemu/sysemu.h" +#include "sysemu/tcg.h" +#include "sysemu/tpm.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qerror.h" +#include "qapi/qmp/qstring.h" +#include "qom/object_interfaces.h" +#include "trace/control.h" +#include "monitor/hmp-target.h" +#include "monitor/hmp.h" +#ifdef CONFIG_TRACE_SIMPLE +#include "trace/simple.h" +#endif +#include "exec/memory.h" +#include "exec/exec-all.h" +#include "qemu/option.h" +#include "qemu/thread.h" +#include "block/qapi.h" +#include "block/block-hmp-cmds.h" +#include "qapi/qapi-commands-char.h" +#include "qapi/qapi-commands-control.h" +#include "qapi/qapi-commands-migration.h" +#include "qapi/qapi-commands-misc.h" +#include "qapi/qapi-commands-qom.h" +#include "qapi/qapi-commands-run-state.h" +#include "qapi/qapi-commands-trace.h" +#include "qapi/qapi-commands-machine.h" +#include "qapi/qapi-init-commands.h" +#include "qapi/error.h" +#include "qapi/qmp-event.h" +#include "sysemu/cpus.h" +#include "qemu/cutils.h" + +#if defined(TARGET_S390X) +#include "hw/s390x/storage-keys.h" +#include "hw/s390x/storage-attributes.h" +#endif + +/* 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; +}; + +/* Protects mon_fdsets */ +static QemuMutex mon_fdsets_lock; +static QLIST_HEAD(, MonFdset) mon_fdsets; + +static HMPCommand hmp_info_cmds[]; + +char *qmp_human_monitor_command(const char *command_line, bool has_cpu_index, + int64_t cpu_index, Error **errp) +{ + char *output = NULL; + MonitorHMP hmp = {}; + + monitor_data_init(&hmp.common, false, true, false); + + if (has_cpu_index) { + int ret = monitor_set_cpu(&hmp.common, cpu_index); + if (ret < 0) { + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "cpu-index", + "a CPU number"); + goto out; + } + } + + handle_hmp_command(&hmp, command_line); + + WITH_QEMU_LOCK_GUARD(&hmp.common.mon_lock) { + output = g_strdup(hmp.common.outbuf->str); + } + +out: + monitor_data_destroy(&hmp.common); + return output; +} + +/** + * Is @name in the '|' separated list of names @list? + */ +int hmp_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 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(); + } 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 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, "device_add", + qmp_device_add, 0, 0); + + QTAILQ_INIT(&qmp_cap_negotiation_commands); + qmp_register_command(&qmp_cap_negotiation_commands, "qmp_capabilities", + qmp_marshal_qmp_capabilities, + QCO_ALLOW_PRECONFIG, 0); +} + +/* Set the current CPU defined by the user. Callers must hold BQL. */ +int monitor_set_cpu(Monitor *mon, int cpu_index) +{ + CPUState *cpu; + + cpu = qemu_get_cpu(cpu_index); + if (cpu == NULL) { + return -1; + } + g_free(mon->mon_cpu_path); + mon->mon_cpu_path = object_get_canonical_path(OBJECT(cpu)); + return 0; +} + +/* Callers must hold BQL. */ +static CPUState *mon_get_cpu_sync(Monitor *mon, bool synchronize) +{ + CPUState *cpu = NULL; + + if (mon->mon_cpu_path) { + cpu = (CPUState *) object_resolve_path_type(mon->mon_cpu_path, + TYPE_CPU, NULL); + if (!cpu) { + g_free(mon->mon_cpu_path); + mon->mon_cpu_path = NULL; + } + } + if (!mon->mon_cpu_path) { + if (!first_cpu) { + return NULL; + } + monitor_set_cpu(mon, first_cpu->cpu_index); + cpu = first_cpu; + } + assert(cpu != NULL); + if (synchronize) { + cpu_synchronize_state(cpu); + } + return cpu; +} + +CPUState *mon_get_cpu(Monitor *mon) +{ + return mon_get_cpu_sync(mon, true); +} + +CPUArchState *mon_get_cpu_env(Monitor *mon) +{ + CPUState *cs = mon_get_cpu(mon); + + return cs ? cs->env_ptr : NULL; +} + +int monitor_get_cpu_index(Monitor *mon) +{ + CPUState *cs = mon_get_cpu_sync(mon, 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, NULL, CPU_DUMP_FPU); + } + } else { + cs = mon_get_cpu(mon); + + if (!cs) { + monitor_printf(mon, "No CPU available\n"); + return; + } + + cpu_dump_state(cs, NULL, CPU_DUMP_FPU); + } +} + +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(max, sort_by, coalesce); +} + +static void hmp_info_history(Monitor *mon, const QDict *qdict) +{ + MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, common); + int i; + const char *str; + + if (!hmp_mon->rs) { + return; + } + i = 0; + for(;;) { + str = readline_get_history(hmp_mon->rs, i); + if (!str) { + break; + } + monitor_printf(mon, "%d: '%s'\n", i, str); + i++; + } +} + +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, "Could not set up display for migration"); + 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) +{ + Error *err = NULL; + WatchdogAction action; + char *qapi_value; + + qapi_value = g_ascii_strdown(qdict_get_str(qdict, "action"), -1); + action = qapi_enum_parse(&WatchdogAction_lookup, qapi_value, -1, &err); + g_free(qapi_value); + if (err) { + hmp_handle_error(mon, err); + return; + } + qmp_watchdog_set_action(action, &error_abort); +} + +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(mon); + + 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); +} + +void *gpa2hva(MemoryRegion **p_mr, hwaddr addr, uint64_t size, Error **errp) +{ + Int128 gpa_region_size; + MemoryRegionSection mrs = memory_region_find(get_system_memory(), + addr, size); + + 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; + } + + gpa_region_size = int128_make64(size); + if (int128_lt(mrs.size, gpa_region_size)) { + error_setg(errp, "Size of memory region at 0x%" HWADDR_PRIx + " exceeded.", 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, 1, &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); +} + +static void hmp_gva2gpa(Monitor *mon, const QDict *qdict) +{ + target_ulong addr = qdict_get_int(qdict, "addr"); + MemTxAttrs attrs; + CPUState *cs = mon_get_cpu(mon); + hwaddr gpa; + + if (!cs) { + monitor_printf(mon, "No cpu\n"); + return; + } + + gpa = cpu_get_phys_page_attrs_debug(cs, addr & TARGET_PAGE_MASK, &attrs); + if (gpa == -1) { + monitor_printf(mon, "Unmapped\n"); + } else { + monitor_printf(mon, "gpa: %#" HWADDR_PRIx "\n", + gpa + (addr & ~TARGET_PAGE_MASK)); + } +} + +#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 = qemu_real_host_page_size; + 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. */ + qatomic_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, 1, &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] = 0x%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); + bool disabled = qdict_get_try_bool(qdict, "disabled", false); + + mtree_info(flatview, dispatch_tree, owner, disabled); +} + +/* 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 freq = qdict_get_try_int(qdict, "freq", 44100); + int bits = qdict_get_try_int(qdict, "bits", 16); + int nchannels = qdict_get_try_int(qdict, "nchannels", 2); + const char *audiodev = qdict_get_str(qdict, "audiodev"); + CaptureState *s; + AudioState *as = audio_state_by_name(audiodev); + + if (!as) { + monitor_printf(mon, "Audiodev '%s' not found\n", audiodev); + return; + } + + s = g_malloc0 (sizeof (*s)); + + if (wav_start_capture(as, 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); +} + +void qmp_getfd(const char *fdname, Error **errp) +{ + Monitor *cur_mon = monitor_cur(); + mon_fd_t *monfd; + int fd, tmp_fd; + + fd = qemu_chr_fe_get_msgfd(&cur_mon->chr); + if (fd == -1) { + error_setg(errp, "No file descriptor supplied via SCM_RIGHTS"); + 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_LOCK_GUARD(&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; + /* 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); +} + +void qmp_closefd(const char *fdname, Error **errp) +{ + Monitor *cur_mon = monitor_cur(); + 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, "File descriptor named '%s' not found", fdname); +} + +int monitor_get_fd(Monitor *mon, const char *fdname, Error **errp) +{ + mon_fd_t *monfd; + + QEMU_LOCK_GUARD(&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); + + return fd; + } + + 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); + } +} + +void monitor_fdsets_cleanup(void) +{ + MonFdset *mon_fdset; + MonFdset *mon_fdset_next; + + QEMU_LOCK_GUARD(&mon_fdsets_lock); + QLIST_FOREACH_SAFE(mon_fdset, &mon_fdsets, next, mon_fdset_next) { + monitor_fdset_cleanup(mon_fdset); + } +} + +AddfdInfo *qmp_add_fd(bool has_fdset_id, int64_t fdset_id, bool has_opaque, + const char *opaque, Error **errp) +{ + int fd; + Monitor *mon = monitor_cur(); + AddfdInfo *fdinfo; + + fd = qemu_chr_fe_get_msgfd(&mon->chr); + if (fd == -1) { + error_setg(errp, "No file descriptor supplied via SCM_RIGHTS"); + 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_LOCK_GUARD(&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); + return; + } + +error: + 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, "File descriptor named '%s' not found", fd_str); +} + +FdsetInfoList *qmp_query_fdsets(Error **errp) +{ + MonFdset *mon_fdset; + MonFdsetFd *mon_fdset_fd; + FdsetInfoList *fdset_list = NULL; + + QEMU_LOCK_GUARD(&mon_fdsets_lock); + QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { + FdsetInfo *fdset_info = g_malloc0(sizeof(*fdset_info)); + + fdset_info->fdset_id = mon_fdset->id; + + QLIST_FOREACH(mon_fdset_fd, &mon_fdset->fds, next) { + FdsetFdInfo *fdsetfd_info; + + fdsetfd_info = g_malloc0(sizeof(*fdsetfd_info)); + fdsetfd_info->fd = mon_fdset_fd->fd; + if (mon_fdset_fd->opaque) { + fdsetfd_info->has_opaque = true; + fdsetfd_info->opaque = g_strdup(mon_fdset_fd->opaque); + } else { + fdsetfd_info->has_opaque = false; + } + + QAPI_LIST_PREPEND(fdset_info->fds, fdsetfd_info); + } + + QAPI_LIST_PREPEND(fdset_list, fdset_info); + } + + 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_LOCK_GUARD(&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"); + 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; + + return fdinfo; +} + +int monitor_fdset_dup_fd_add(int64_t fdset_id, int flags) +{ +#ifdef _WIN32 + return -ENOENT; +#else + MonFdset *mon_fdset; + + QEMU_LOCK_GUARD(&mon_fdsets_lock); + QLIST_FOREACH(mon_fdset, &mon_fdsets, next) { + MonFdsetFd *mon_fdset_fd; + MonFdsetFd *mon_fdset_fd_dup; + int fd = -1; + int dup_fd; + int mon_fd_flags; + + 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) { + return -1; + } + + if ((flags & O_ACCMODE) == (mon_fd_flags & O_ACCMODE)) { + fd = mon_fdset_fd->fd; + break; + } + } + + if (fd == -1) { + errno = EACCES; + return -1; + } + + dup_fd = qemu_dup_flags(fd, flags); + if (dup_fd == -1) { + return -1; + } + + 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); + return dup_fd; + } + + errno = ENOENT; + return -1; +#endif +} + +static int64_t monitor_fdset_dup_fd_find_remove(int dup_fd, bool remove) +{ + MonFdset *mon_fdset; + MonFdsetFd *mon_fdset_fd_dup; + + QEMU_LOCK_GUARD(&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); + g_free(mon_fdset_fd_dup); + if (QLIST_EMPTY(&mon_fdset->dup_fds)) { + monitor_fdset_cleanup(mon_fdset); + } + return -1; + } else { + return mon_fdset->id; + } + } + } + } + + return -1; +} + +int64_t 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 HMPCommand hmp_info_cmds[] = { +#include "hmp-commands-info.h" + { NULL, NULL, }, +}; + +/* hmp_cmds and hmp_info_cmds would be sorted at runtime */ +HMPCommand hmp_cmds[] = { +#include "hmp-commands.h" + { NULL, NULL, }, +}; + +/* + * Set @pval to the value in the register identified by @name. + * return 0 if OK, -1 if not found + */ +int get_monitor_def(Monitor *mon, int64_t *pval, const char *name) +{ + const MonitorDef *md = target_monitor_defs(); + CPUState *cs = mon_get_cpu(mon); + void *ptr; + uint64_t tmp = 0; + int ret; + + if (cs == NULL || md == NULL) { + return -1; + } + + for(; md->name != NULL; md++) { + if (hmp_compare_cmd(name, md->name)) { + if (md->get_value) { + *pval = md->get_value(mon, md, md->offset); + } else { + CPUArchState *env = mon_get_cpu_env(mon); + 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 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 int qdev_add_hotpluggable_device(Object *obj, void *opaque) +{ + GSList **list = opaque; + DeviceState *dev = (DeviceState *)object_dynamic_cast(obj, TYPE_DEVICE); + + if (dev == NULL) { + return 0; + } + + if (dev->realized && object_property_get_bool(obj, "hotpluggable", NULL)) { + *list = g_slist_append(*list, dev); + } + + return 0; +} + +static GSList *qdev_build_hotpluggable_device_list(Object *peripheral) +{ + GSList *list = NULL; + + object_child_foreach(peripheral, qdev_add_hotpluggable_device, &list); + + return 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++) { + const char *name = ncs[i]->name; + if (strncmp(str, name, len)) { + continue; + } + if (ncs[i]->is_netdev) { + 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_pattern(&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_pattern(&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 int +compare_mon_cmd(const void *a, const void *b) +{ + return strcmp(((const HMPCommand *)a)->name, + ((const HMPCommand *)b)->name); +} + +static void sortcmdlist(void) +{ + qsort(hmp_cmds, ARRAY_SIZE(hmp_cmds) - 1, + sizeof(*hmp_cmds), + compare_mon_cmd); + qsort(hmp_info_cmds, ARRAY_SIZE(hmp_info_cmds) - 1, + sizeof(*hmp_info_cmds), + compare_mon_cmd); +} + +void monitor_register_hmp(const char *name, bool info, + void (*cmd)(Monitor *mon, const QDict *qdict)) +{ + HMPCommand *table = info ? hmp_info_cmds : hmp_cmds; + + while (table->name != NULL) { + if (strcmp(table->name, name) == 0) { + g_assert(table->cmd == NULL && table->cmd_info_hrt == NULL); + table->cmd = cmd; + return; + } + table++; + } + g_assert_not_reached(); +} + +void monitor_register_hmp_info_hrt(const char *name, + HumanReadableText *(*handler)(Error **errp)) +{ + HMPCommand *table = hmp_info_cmds; + + while (table->name != NULL) { + if (strcmp(table->name, name) == 0) { + g_assert(table->cmd == NULL && table->cmd_info_hrt == NULL); + table->cmd_info_hrt = handler; + return; + } + table++; + } + g_assert_not_reached(); +} + +void monitor_init_globals(void) +{ + monitor_init_globals_core(); + monitor_init_qmp_commands(); + sortcmdlist(); + qemu_mutex_init(&mon_fdsets_lock); +} diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h new file mode 100644 index 000000000..3da3f86c6 --- /dev/null +++ b/monitor/monitor-internal.h @@ -0,0 +1,193 @@ +/* + * 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. + */ + +#ifndef MONITOR_INTERNAL_H +#define MONITOR_INTERNAL_H + +#include "chardev/char-fe.h" +#include "monitor/monitor.h" +#include "qapi/qapi-types-control.h" +#include "qapi/qmp/dispatch.h" +#include "qapi/qmp/json-parser.h" +#include "qemu/readline.h" +#include "sysemu/iothread.h" + +/* + * 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 HMPCommand { + 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); + /* + * If implementing a command that takes no arguments and simply + * prints formatted data, then leave @cmd NULL, and then set + * @cmd_info_hrt to the corresponding QMP handler that returns + * the formatted text. + */ + HumanReadableText *(*cmd_info_hrt)(Error **errp); + bool coroutine; + /* + * @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 HMPCommand *sub_table; + void (*command_completion)(ReadLineState *rs, int nb_args, const char *str); +} HMPCommand; + +struct Monitor { + CharBackend chr; + int reset_seen; + int suspend_cnt; /* Needs to be accessed atomically */ + bool is_qmp; + bool skip_flush; + bool use_io_thread; + + char *mon_cpu_path; + 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; + GString *outbuf; + guint out_watch; + /* Read under either BQL or mon_lock, written with BQL+mon_lock. */ + int mux_out; +}; + +struct MonitorHMP { + Monitor common; + bool use_readline; + /* + * State used only in the thread "owning" the monitor. + * If @use_io_thread, this is @mon_iothread. (This does not actually happen + * in the current state of the code.) + * Else, it's the main thread. + * These members can be safely accessed without locks. + */ + ReadLineState *rs; +}; + +typedef struct { + Monitor common; + JSONMessageParser parser; + bool pretty; + /* + * 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. + */ + const 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; + +/** + * Is @mon a QMP monitor? + */ +static inline bool monitor_is_qmp(const Monitor *mon) +{ + return mon->is_qmp; +} + +typedef QTAILQ_HEAD(MonitorList, Monitor) MonitorList; +extern IOThread *mon_iothread; +extern Coroutine *qmp_dispatcher_co; +extern bool qmp_dispatcher_co_shutdown; +extern bool qmp_dispatcher_co_busy; +extern QmpCommandList qmp_commands, qmp_cap_negotiation_commands; +extern QemuMutex monitor_lock; +extern MonitorList mon_list; +extern int mon_refcount; + +extern HMPCommand hmp_cmds[]; + +int monitor_puts(Monitor *mon, const char *str); +void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush, + bool use_io_thread); +void monitor_data_destroy(Monitor *mon); +int monitor_can_read(void *opaque); +void monitor_list_append(Monitor *mon); +void monitor_fdsets_cleanup(void); + +void qmp_send_response(MonitorQMP *mon, const QDict *rsp); +void monitor_data_destroy_qmp(MonitorQMP *mon); +void coroutine_fn monitor_qmp_dispatcher_co(void *data); + +int get_monitor_def(Monitor *mon, int64_t *pval, const char *name); +void help_cmd(Monitor *mon, const char *name); +void handle_hmp_command(MonitorHMP *mon, const char *cmdline); +int hmp_compare_cmd(const char *name, const char *list); + +#endif diff --git a/monitor/monitor.c b/monitor/monitor.c new file mode 100644 index 000000000..21c7a6875 --- /dev/null +++ b/monitor/monitor.c @@ -0,0 +1,778 @@ +/* + * 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 "monitor-internal.h" +#include "qapi/error.h" +#include "qapi/opts-visitor.h" +#include "qapi/qapi-emit-events.h" +#include "qapi/qapi-visit-control.h" +#include "qapi/qmp/qdict.h" +#include "qemu/error-report.h" +#include "qemu/option.h" +#include "sysemu/qtest.h" +#include "trace.h" + +/* + * 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; + +/* Shared monitor I/O thread */ +IOThread *mon_iothread; + +/* Coroutine to dispatch the requests received from I/O thread */ +Coroutine *qmp_dispatcher_co; + +/* Set to true when the dispatcher coroutine should terminate */ +bool qmp_dispatcher_co_shutdown; + +/* + * qmp_dispatcher_co_busy is used for synchronisation between the + * monitor thread and the main thread to ensure that the dispatcher + * coroutine never gets scheduled a second time when it's already + * scheduled (scheduling the same coroutine twice is forbidden). + * + * It is true if the coroutine is active and processing requests. + * Additional requests may then be pushed onto mon->qmp_requests, + * and @qmp_dispatcher_co_shutdown may be set without further ado. + * @qmp_dispatcher_co_busy must not be woken up in this case. + * + * If false, you also have to set @qmp_dispatcher_co_busy to true and + * wake up @qmp_dispatcher_co after pushing the new requests. + * + * The coroutine will automatically change this variable back to false + * before it yields. Nobody else may set the variable to false. + * + * Access must be atomic for thread safety. + */ +bool qmp_dispatcher_co_busy; + +/* + * Protects mon_list, monitor_qapi_event_state, coroutine_mon, + * monitor_destroyed. + */ +QemuMutex monitor_lock; +static GHashTable *monitor_qapi_event_state; +static GHashTable *coroutine_mon; /* Maps Coroutine* to Monitor* */ + +MonitorList mon_list; +int mon_refcount; +static bool monitor_destroyed; + +Monitor *monitor_cur(void) +{ + Monitor *mon; + + qemu_mutex_lock(&monitor_lock); + mon = g_hash_table_lookup(coroutine_mon, qemu_coroutine_self()); + qemu_mutex_unlock(&monitor_lock); + + return mon; +} + +/** + * Sets a new current monitor and returns the old one. + * + * If a non-NULL monitor is set for a coroutine, another call + * resetting it to NULL is required before the coroutine terminates, + * otherwise a stale entry would remain in the hash table. + */ +Monitor *monitor_set_cur(Coroutine *co, Monitor *mon) +{ + Monitor *old_monitor = monitor_cur(); + + qemu_mutex_lock(&monitor_lock); + if (mon) { + g_hash_table_replace(coroutine_mon, co, mon); + } else { + g_hash_table_remove(coroutine_mon, co); + } + qemu_mutex_unlock(&monitor_lock); + + return old_monitor; +} + +/** + * Is the current monitor, if any, a QMP monitor? + */ +bool monitor_cur_is_qmp(void) +{ + Monitor *cur_mon = monitor_cur(); + + return cur_mon && monitor_is_qmp(cur_mon); +} + +/** + * 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 MonitorHMP *mon) +{ + return mon->use_readline; +} + +static inline bool monitor_is_hmp_non_interactive(const Monitor *mon) +{ + if (monitor_is_qmp(mon)) { + return false; + } + + return !monitor_uses_readline(container_of(mon, MonitorHMP, common)); +} + +static void monitor_flush_locked(Monitor *mon); + +static gboolean monitor_unblocked(void *do_not_use, 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 = mon->outbuf->str; + len = mon->outbuf->len; + + 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 */ + g_string_truncate(mon->outbuf, 0); + return; + } + if (rc > 0) { + /* partial write */ + g_string_erase(mon->outbuf, 0, rc); + } + 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 */ +int monitor_puts(Monitor *mon, const char *str) +{ + int i; + char c; + + qemu_mutex_lock(&mon->mon_lock); + for (i = 0; str[i]; i++) { + c = str[i]; + if (c == '\n') { + g_string_append_c(mon->outbuf, '\r'); + } + g_string_append_c(mon->outbuf, c); + if (c == '\n') { + monitor_flush_locked(mon); + } + } + qemu_mutex_unlock(&mon->mon_lock); + + return i; +} + +int monitor_vprintf(Monitor *mon, const char *fmt, va_list ap) +{ + char *buf; + int n; + + if (!mon) { + return -1; + } + + if (monitor_is_qmp(mon)) { + return -1; + } + + buf = g_strdup_vprintf(fmt, ap); + n = monitor_puts(mon, buf); + g_free(buf); + return n; +} + +int monitor_printf(Monitor *mon, const char *fmt, ...) +{ + int ret; + + va_list ap; + va_start(ap, fmt); + ret = monitor_vprintf(mon, fmt, ap); + va_end(ap); + return ret; +} + +/* + * Print to current monitor if we have one, else to stderr. + */ +int error_vprintf(const char *fmt, va_list ap) +{ + Monitor *cur_mon = monitor_cur(); + + if (cur_mon && !monitor_cur_is_qmp()) { + return monitor_vprintf(cur_mon, fmt, ap); + } + return vfprintf(stderr, fmt, ap); +} + +int error_vprintf_unless_qmp(const char *fmt, va_list ap) +{ + Monitor *cur_mon = monitor_cur(); + + if (!cur_mon) { + return vfprintf(stderr, fmt, ap); + } + if (!monitor_cur_is_qmp()) { + return monitor_vprintf(cur_mon, fmt, ap); + } + return -1; +} + + +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 }, + [QAPI_EVENT_MEMORY_DEVICE_SIZE_CHANGE] = { 1000 * SCALE_MS }, +}; + +/* + * 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; +} + +/* + * 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; + MonitorQMP *qmp_mon; + + trace_monitor_protocol_event_emit(event, qdict); + QTAILQ_FOREACH(mon, &mon_list, entry) { + if (!monitor_is_qmp(mon)) { + continue; + } + + qmp_mon = container_of(mon, MonitorQMP, common); + if (qmp_mon->commands != &qmp_cap_negotiation_commands) { + qmp_send_response(qmp_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_LOCK_GUARD(&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); + } + } +} + +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_LOCK_GUARD(&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); + } +} + +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")); + } + + if (evstate->event == QAPI_EVENT_MEMORY_DEVICE_SIZE_CHANGE) { + hash += g_str_hash(qdict_get_str(evstate->data, "qom-path")); + } + + 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")); + } + + if (eva->event == QAPI_EVENT_MEMORY_DEVICE_SIZE_CHANGE) { + return !strcmp(qdict_get_str(eva->data, "qom-path"), + qdict_get_str(evb->data, "qom-path")); + } + + return TRUE; +} + +int monitor_suspend(Monitor *mon) +{ + if (monitor_is_hmp_non_interactive(mon)) { + return -ENOTTY; + } + + qatomic_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 (qatomic_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)) { + MonitorHMP *hmp_mon = container_of(mon, MonitorHMP, common); + assert(hmp_mon->rs); + readline_show_prompt(hmp_mon->rs); + } + + aio_bh_schedule_oneshot(ctx, monitor_accept_input, mon); + } + + trace_monitor_suspend(mon, -1); +} + +int monitor_can_read(void *opaque) +{ + Monitor *mon = opaque; + + return !qatomic_mb_read(&mon->suspend_cnt); +} + +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_iothread_init(void) +{ + mon_iothread = iothread_create("mon_iothread", &error_abort); +} + +void monitor_data_init(Monitor *mon, bool is_qmp, bool skip_flush, + bool use_io_thread) +{ + if (use_io_thread && !mon_iothread) { + monitor_iothread_init(); + } + qemu_mutex_init(&mon->mon_lock); + mon->is_qmp = is_qmp; + mon->outbuf = g_string_new(NULL); + mon->skip_flush = skip_flush; + mon->use_io_thread = use_io_thread; +} + +void monitor_data_destroy(Monitor *mon) +{ + g_free(mon->mon_cpu_path); + qemu_chr_fe_deinit(&mon->chr, false); + if (monitor_is_qmp(mon)) { + monitor_data_destroy_qmp(container_of(mon, MonitorQMP, common)); + } else { + readline_free(container_of(mon, MonitorHMP, common)->rs); + } + g_string_free(mon->outbuf, true); + qemu_mutex_destroy(&mon->mon_lock); +} + +void monitor_cleanup(void) +{ + /* + * The dispatcher needs to stop before destroying the monitor and + * the I/O thread. + * + * We need to poll both qemu_aio_context and iohandler_ctx to make + * sure that the dispatcher coroutine keeps making progress and + * eventually terminates. qemu_aio_context is automatically + * polled by calling AIO_WAIT_WHILE on it, but we must poll + * iohandler_ctx manually. + * + * Letting the iothread continue while shutting down the dispatcher + * means that new requests may still be coming in. This is okay, + * we'll just leave them in the queue without sending a response + * and monitor_data_destroy() will free them. + */ + qmp_dispatcher_co_shutdown = true; + if (!qatomic_xchg(&qmp_dispatcher_co_busy, true)) { + aio_co_wake(qmp_dispatcher_co); + } + + AIO_WAIT_WHILE(qemu_get_aio_context(), + (aio_poll(iohandler_get_aio_context(), false), + qatomic_mb_read(&qmp_dispatcher_co_busy))); + + /* + * 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); + + if (mon_iothread) { + iothread_destroy(mon_iothread); + mon_iothread = NULL; + } +} + +static void monitor_qapi_event_init(void) +{ + monitor_qapi_event_state = g_hash_table_new(qapi_event_throttle_hash, + qapi_event_throttle_equal); +} + +void monitor_init_globals_core(void) +{ + monitor_qapi_event_init(); + qemu_mutex_init(&monitor_lock); + coroutine_mon = g_hash_table_new(NULL, NULL); + + /* + * 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_co = qemu_coroutine_create(monitor_qmp_dispatcher_co, NULL); + qatomic_mb_set(&qmp_dispatcher_co_busy, true); + aio_co_schedule(iohandler_get_aio_context(), qmp_dispatcher_co); +} + +int monitor_init(MonitorOptions *opts, bool allow_hmp, Error **errp) +{ + Chardev *chr; + Error *local_err = NULL; + + chr = qemu_chr_find(opts->chardev); + if (chr == NULL) { + error_setg(errp, "chardev \"%s\" not found", opts->chardev); + return -1; + } + + if (!opts->has_mode) { + opts->mode = allow_hmp ? MONITOR_MODE_READLINE : MONITOR_MODE_CONTROL; + } + + switch (opts->mode) { + case MONITOR_MODE_CONTROL: + monitor_init_qmp(chr, opts->pretty, &local_err); + break; + case MONITOR_MODE_READLINE: + if (!allow_hmp) { + error_setg(errp, "Only QMP is supported"); + return -1; + } + if (opts->pretty) { + error_setg(errp, "'pretty' is not compatible with HMP monitors"); + return -1; + } + monitor_init_hmp(chr, true, &local_err); + break; + default: + g_assert_not_reached(); + } + + if (local_err) { + error_propagate(errp, local_err); + return -1; + } + return 0; +} + +int monitor_init_opts(QemuOpts *opts, Error **errp) +{ + Visitor *v; + MonitorOptions *options; + int ret; + + v = opts_visitor_new(opts); + visit_type_MonitorOptions(v, NULL, &options, errp); + visit_free(v); + if (!options) { + return -1; + } + + ret = monitor_init(options, true, errp); + qapi_free_MonitorOptions(options); + return ret; +} + +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 */ } + }, +}; diff --git a/monitor/qmp-cmds-control.c b/monitor/qmp-cmds-control.c new file mode 100644 index 000000000..6e581713a --- /dev/null +++ b/monitor/qmp-cmds-control.c @@ -0,0 +1,222 @@ +/* + * QMP commands related to the monitor (common to sysemu and tools) + * + * 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 "monitor-internal.h" +#include "qemu-version.h" +#include "qapi/compat-policy.h" +#include "qapi/error.h" +#include "qapi/qapi-commands-control.h" +#include "qapi/qapi-commands-introspect.h" +#include "qapi/qapi-emit-events.h" +#include "qapi/qapi-introspect.h" +#include "qapi/qapi-visit-introspect.h" +#include "qapi/qobject-input-visitor.h" + +/* + * 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(MonitorQMP *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->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->capab, capab, sizeof(capab)); + return true; +} + +void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable, + Error **errp) +{ + Monitor *cur_mon = monitor_cur(); + MonitorQMP *mon; + + assert(monitor_is_qmp(cur_mon)); + mon = container_of(cur_mon, MonitorQMP, common); + + if (mon->commands == &qmp_commands) { + error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, + "Capabilities negotiation is already complete, command " + "ignored"); + return; + } + + if (!qmp_caps_accept(mon, enable, errp)) { + return; + } + + mon->commands = &qmp_commands; +} + +VersionInfo *qmp_query_version(Error **errp) +{ + VersionInfo *info = g_new0(VersionInfo, 1); + + info->qemu = g_new0(VersionTriple, 1); + info->qemu->major = QEMU_VERSION_MAJOR; + info->qemu->minor = QEMU_VERSION_MINOR; + info->qemu->micro = QEMU_VERSION_MICRO; + info->package = g_strdup(QEMU_PKGVERSION); + + return info; +} + +static void query_commands_cb(const QmpCommand *cmd, void *opaque) +{ + CommandInfo *info; + CommandInfoList **list = opaque; + + if (!cmd->enabled) { + return; + } + + info = g_malloc0(sizeof(*info)); + info->name = g_strdup(cmd->name); + QAPI_LIST_PREPEND(*list, info); +} + +CommandInfoList *qmp_query_commands(Error **errp) +{ + CommandInfoList *list = NULL; + Monitor *cur_mon = monitor_cur(); + MonitorQMP *mon; + + assert(monitor_is_qmp(cur_mon)); + mon = container_of(cur_mon, MonitorQMP, common); + + qmp_for_each_command(mon->commands, query_commands_cb, &list); + + return list; +} + +static void *split_off_generic_list(void *list, + bool (*splitp)(void *elt), + void **part) +{ + GenericList *keep = NULL, **keep_tailp = &keep; + GenericList *split = NULL, **split_tailp = &split; + GenericList *tail; + + for (tail = list; tail; tail = tail->next) { + if (splitp(tail)) { + *split_tailp = tail; + split_tailp = &tail->next; + } else { + *keep_tailp = tail; + keep_tailp = &tail->next; + } + } + + *keep_tailp = *split_tailp = NULL; + *part = split; + return keep; +} + +static bool is_in(const char *s, strList *list) +{ + strList *tail; + + for (tail = list; tail; tail = tail->next) { + if (!strcmp(tail->value, s)) { + return true; + } + } + return false; +} + +static bool is_entity_deprecated(void *link) +{ + return is_in("deprecated", ((SchemaInfoList *)link)->value->features); +} + +static bool is_member_deprecated(void *link) +{ + return is_in("deprecated", + ((SchemaInfoObjectMemberList *)link)->value->features); +} + +static SchemaInfoList *zap_deprecated(SchemaInfoList *schema) +{ + void *to_zap; + SchemaInfoList *tail; + SchemaInfo *ent; + + schema = split_off_generic_list(schema, is_entity_deprecated, &to_zap); + qapi_free_SchemaInfoList(to_zap); + + for (tail = schema; tail; tail = tail->next) { + ent = tail->value; + if (ent->meta_type == SCHEMA_META_TYPE_OBJECT) { + ent->u.object.members + = split_off_generic_list(ent->u.object.members, + is_member_deprecated, &to_zap); + qapi_free_SchemaInfoObjectMemberList(to_zap); + } + } + + return schema; +} + +SchemaInfoList *qmp_query_qmp_schema(Error **errp) +{ + QObject *obj = qobject_from_qlit(&qmp_schema_qlit); + Visitor *v = qobject_input_visitor_new(obj); + SchemaInfoList *schema = NULL; + + /* test_visitor_in_qmp_introspect() ensures this can't fail */ + visit_type_SchemaInfoList(v, NULL, &schema, &error_abort); + g_assert(schema); + + qobject_unref(obj); + visit_free(v); + + if (compat_policy.deprecated_output == COMPAT_POLICY_OUTPUT_HIDE) { + return zap_deprecated(schema); + } + return schema; +} diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c new file mode 100644 index 000000000..343353e27 --- /dev/null +++ b/monitor/qmp-cmds.c @@ -0,0 +1,468 @@ +/* + * QEMU Management Protocol commands + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "qemu/cutils.h" +#include "qemu/option.h" +#include "monitor/monitor.h" +#include "sysemu/sysemu.h" +#include "qemu/config-file.h" +#include "qemu/uuid.h" +#include "chardev/char.h" +#include "ui/qemu-spice.h" +#include "ui/console.h" +#include "sysemu/kvm.h" +#include "sysemu/runstate.h" +#include "sysemu/runstate-action.h" +#include "sysemu/blockdev.h" +#include "sysemu/block-backend.h" +#include "qapi/error.h" +#include "qapi/qapi-commands-acpi.h" +#include "qapi/qapi-commands-block.h" +#include "qapi/qapi-commands-control.h" +#include "qapi/qapi-commands-machine.h" +#include "qapi/qapi-commands-misc.h" +#include "qapi/qapi-commands-ui.h" +#include "qapi/type-helpers.h" +#include "qapi/qmp/qerror.h" +#include "exec/ramlist.h" +#include "hw/mem/memory-device.h" +#include "hw/acpi/acpi_dev_interface.h" +#include "hw/intc/intc.h" +#include "hw/rdma/rdma.h" + +NameInfo *qmp_query_name(Error **errp) +{ + NameInfo *info = g_malloc0(sizeof(*info)); + + if (qemu_name) { + info->has_name = true; + info->name = g_strdup(qemu_name); + } + + return info; +} + +KvmInfo *qmp_query_kvm(Error **errp) +{ + KvmInfo *info = g_malloc0(sizeof(*info)); + + info->enabled = kvm_enabled(); + info->present = accel_find("kvm"); + + return info; +} + +UuidInfo *qmp_query_uuid(Error **errp) +{ + UuidInfo *info = g_malloc0(sizeof(*info)); + + info->UUID = qemu_uuid_unparse_strdup(&qemu_uuid); + return info; +} + +void qmp_quit(Error **errp) +{ + shutdown_action = SHUTDOWN_ACTION_POWEROFF; + qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_QMP_QUIT); +} + +void qmp_stop(Error **errp) +{ + /* if there is a dump in background, we should wait until the dump + * finished */ + if (dump_in_progress()) { + error_setg(errp, "There is a dump in process, please wait."); + return; + } + + if (runstate_check(RUN_STATE_INMIGRATE)) { + autostart = 0; + } else { + vm_stop(RUN_STATE_PAUSED); + } +} + +void qmp_system_reset(Error **errp) +{ + qemu_system_reset_request(SHUTDOWN_CAUSE_HOST_QMP_SYSTEM_RESET); +} + +void qmp_system_powerdown(Error **errp) +{ + qemu_system_powerdown_request(); +} + +void qmp_cont(Error **errp) +{ + BlockBackend *blk; + BlockJob *job; + Error *local_err = NULL; + + /* if there is a dump in background, we should wait until the dump + * finished */ + if (dump_in_progress()) { + error_setg(errp, "There is a dump in process, please wait."); + return; + } + + if (runstate_needs_reset()) { + error_setg(errp, "Resetting the Virtual Machine is required"); + return; + } else if (runstate_check(RUN_STATE_SUSPENDED)) { + return; + } else if (runstate_check(RUN_STATE_FINISH_MIGRATE)) { + error_setg(errp, "Migration is not finalized yet"); + return; + } + + for (blk = blk_next(NULL); blk; blk = blk_next(blk)) { + blk_iostatus_reset(blk); + } + + for (job = block_job_next(NULL); job; job = block_job_next(job)) { + block_job_iostatus_reset(job); + } + + /* Continuing after completed migration. Images have been inactivated to + * allow the destination to take control. Need to get control back now. + * + * If there are no inactive block nodes (e.g. because the VM was just + * paused rather than completing a migration), bdrv_inactivate_all() simply + * doesn't do anything. */ + bdrv_invalidate_cache_all(&local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + if (runstate_check(RUN_STATE_INMIGRATE)) { + autostart = 1; + } else { + vm_start(); + } +} + +void qmp_system_wakeup(Error **errp) +{ + if (!qemu_wakeup_suspend_enabled()) { + error_setg(errp, + "wake-up from suspend is not supported by this guest"); + return; + } + + qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, errp); +} + +void qmp_set_password(const char *protocol, const char *password, + bool has_connected, const char *connected, Error **errp) +{ + int disconnect_if_connected = 0; + int fail_if_connected = 0; + int rc; + + if (has_connected) { + if (strcmp(connected, "fail") == 0) { + fail_if_connected = 1; + } else if (strcmp(connected, "disconnect") == 0) { + disconnect_if_connected = 1; + } else if (strcmp(connected, "keep") == 0) { + /* nothing */ + } else { + error_setg(errp, QERR_INVALID_PARAMETER, "connected"); + return; + } + } + + if (strcmp(protocol, "spice") == 0) { + if (!qemu_using_spice(errp)) { + return; + } + rc = qemu_spice.set_passwd(password, fail_if_connected, + disconnect_if_connected); + } else if (strcmp(protocol, "vnc") == 0) { + if (fail_if_connected || disconnect_if_connected) { + /* vnc supports "connected=keep" only */ + error_setg(errp, QERR_INVALID_PARAMETER, "connected"); + return; + } + /* Note that setting an empty password will not disable login through + * this interface. */ + rc = vnc_display_password(NULL, password); + } else { + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "protocol", + "'vnc' or 'spice'"); + return; + } + + if (rc != 0) { + error_setg(errp, "Could not set password"); + } +} + +void qmp_expire_password(const char *protocol, const char *whenstr, + Error **errp) +{ + time_t when; + int rc; + + if (strcmp(whenstr, "now") == 0) { + when = 0; + } else if (strcmp(whenstr, "never") == 0) { + when = TIME_MAX; + } else if (whenstr[0] == '+') { + when = time(NULL) + strtoull(whenstr+1, NULL, 10); + } else { + when = strtoull(whenstr, NULL, 10); + } + + if (strcmp(protocol, "spice") == 0) { + if (!qemu_using_spice(errp)) { + return; + } + rc = qemu_spice.set_pw_expire(when); + } else if (strcmp(protocol, "vnc") == 0) { + rc = vnc_display_pw_expire(NULL, when); + } else { + error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "protocol", + "'vnc' or 'spice'"); + return; + } + + if (rc != 0) { + error_setg(errp, "Could not set password expire time"); + } +} + +#ifdef CONFIG_VNC +void qmp_change_vnc_password(const char *password, Error **errp) +{ + if (vnc_display_password(NULL, password) < 0) { + error_setg(errp, "Could not set password"); + } +} +#endif + +void qmp_add_client(const char *protocol, const char *fdname, + bool has_skipauth, bool skipauth, bool has_tls, bool tls, + Error **errp) +{ + Chardev *s; + int fd; + + fd = monitor_get_fd(monitor_cur(), fdname, errp); + if (fd < 0) { + return; + } + + if (strcmp(protocol, "spice") == 0) { + if (!qemu_using_spice(errp)) { + close(fd); + return; + } + skipauth = has_skipauth ? skipauth : false; + tls = has_tls ? tls : false; + if (qemu_spice.display_add_client(fd, skipauth, tls) < 0) { + error_setg(errp, "spice failed to add client"); + close(fd); + } + return; +#ifdef CONFIG_VNC + } else if (strcmp(protocol, "vnc") == 0) { + skipauth = has_skipauth ? skipauth : false; + vnc_display_add_client(NULL, fd, skipauth); + return; +#endif + } else if ((s = qemu_chr_find(protocol)) != NULL) { + if (qemu_chr_add_client(s, fd) < 0) { + error_setg(errp, "failed to add client"); + close(fd); + return; + } + return; + } + + error_setg(errp, "protocol '%s' is invalid", protocol); + close(fd); +} + + +MemoryDeviceInfoList *qmp_query_memory_devices(Error **errp) +{ + return qmp_memory_device_list(); +} + +ACPIOSTInfoList *qmp_query_acpi_ospm_status(Error **errp) +{ + bool ambig; + ACPIOSTInfoList *head = NULL; + ACPIOSTInfoList **prev = &head; + Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, &ambig); + + if (obj) { + AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_GET_CLASS(obj); + AcpiDeviceIf *adev = ACPI_DEVICE_IF(obj); + + adevc->ospm_status(adev, &prev); + } else { + error_setg(errp, "command is not supported, missing ACPI device"); + } + + return head; +} + +MemoryInfo *qmp_query_memory_size_summary(Error **errp) +{ + MemoryInfo *mem_info = g_malloc0(sizeof(MemoryInfo)); + MachineState *ms = MACHINE(qdev_get_machine()); + + mem_info->base_memory = ms->ram_size; + + mem_info->plugged_memory = get_plugged_memory_size(); + mem_info->has_plugged_memory = + mem_info->plugged_memory != (uint64_t)-1; + + return mem_info; +} + +void qmp_display_reload(DisplayReloadOptions *arg, Error **errp) +{ + switch (arg->type) { + case DISPLAY_RELOAD_TYPE_VNC: +#ifdef CONFIG_VNC + if (arg->u.vnc.has_tls_certs && arg->u.vnc.tls_certs) { + vnc_display_reload_certs(NULL, errp); + } +#else + error_setg(errp, "vnc is invalid, missing 'CONFIG_VNC'"); +#endif + break; + default: + abort(); + } +} + +#ifdef CONFIG_PROFILER + +int64_t dev_time; + +HumanReadableText *qmp_x_query_profile(Error **errp) +{ + g_autoptr(GString) buf = g_string_new(""); + 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; + + g_string_append_printf(buf, "async time %" PRId64 " (%0.3f)\n", + dev_time, dev_time / (double)NANOSECONDS_PER_SECOND); + g_string_append_printf(buf, "qemu time %" PRId64 " (%0.3f)\n", + delta, delta / (double)NANOSECONDS_PER_SECOND); + last_cpu_exec_time = cpu_exec_time; + dev_time = 0; + + return human_readable_text_from_str(buf); +} +#else +HumanReadableText *qmp_x_query_profile(Error **errp) +{ + error_setg(errp, "Internal profiler not compiled"); + return NULL; +} +#endif + +static int qmp_x_query_rdma_foreach(Object *obj, void *opaque) +{ + RdmaProvider *rdma; + RdmaProviderClass *k; + GString *buf = opaque; + + if (object_dynamic_cast(obj, INTERFACE_RDMA_PROVIDER)) { + rdma = RDMA_PROVIDER(obj); + k = RDMA_PROVIDER_GET_CLASS(obj); + if (k->format_statistics) { + k->format_statistics(rdma, buf); + } else { + g_string_append_printf(buf, + "RDMA statistics not available for %s.\n", + object_get_typename(obj)); + } + } + + return 0; +} + +HumanReadableText *qmp_x_query_rdma(Error **errp) +{ + g_autoptr(GString) buf = g_string_new(""); + + object_child_foreach_recursive(object_get_root(), + qmp_x_query_rdma_foreach, buf); + + return human_readable_text_from_str(buf); +} + +HumanReadableText *qmp_x_query_ramblock(Error **errp) +{ + g_autoptr(GString) buf = ram_block_format(); + + return human_readable_text_from_str(buf); +} + +static int qmp_x_query_irq_foreach(Object *obj, void *opaque) +{ + InterruptStatsProvider *intc; + InterruptStatsProviderClass *k; + GString *buf = opaque; + + if (object_dynamic_cast(obj, TYPE_INTERRUPT_STATS_PROVIDER)) { + intc = INTERRUPT_STATS_PROVIDER(obj); + k = INTERRUPT_STATS_PROVIDER_GET_CLASS(obj); + uint64_t *irq_counts; + unsigned int nb_irqs, i; + if (k->get_statistics && + k->get_statistics(intc, &irq_counts, &nb_irqs)) { + if (nb_irqs > 0) { + g_string_append_printf(buf, "IRQ statistics for %s:\n", + object_get_typename(obj)); + for (i = 0; i < nb_irqs; i++) { + if (irq_counts[i] > 0) { + g_string_append_printf(buf, "%2d: %" PRId64 "\n", i, + irq_counts[i]); + } + } + } + } else { + g_string_append_printf(buf, + "IRQ statistics not available for %s.\n", + object_get_typename(obj)); + } + } + + return 0; +} + +HumanReadableText *qmp_x_query_irq(Error **errp) +{ + g_autoptr(GString) buf = g_string_new(""); + + object_child_foreach_recursive(object_get_root(), + qmp_x_query_irq_foreach, buf); + + return human_readable_text_from_str(buf); +} diff --git a/monitor/qmp.c b/monitor/qmp.c new file mode 100644 index 000000000..092c527b6 --- /dev/null +++ b/monitor/qmp.c @@ -0,0 +1,538 @@ +/* + * 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 "chardev/char-io.h" +#include "monitor-internal.h" +#include "qapi/error.h" +#include "qapi/qapi-commands-control.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qjson.h" +#include "qapi/qmp/qlist.h" +#include "trace.h" + +struct QMPRequest { + /* Owner of the request */ + MonitorQMP *mon; + /* + * 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; + +QmpCommandList qmp_commands, qmp_cap_negotiation_commands; + +static bool qmp_oob_enabled(MonitorQMP *mon) +{ + return mon->capab[QMP_CAPABILITY_OOB]; +} + +static void monitor_qmp_caps_reset(MonitorQMP *mon) +{ + memset(mon->capab_offered, 0, sizeof(mon->capab_offered)); + memset(mon->capab, 0, sizeof(mon->capab)); + mon->capab_offered[QMP_CAPABILITY_OOB] = mon->common.use_io_thread; +} + +static void qmp_request_free(QMPRequest *req) +{ + 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(MonitorQMP *mon) +{ + while (!g_queue_is_empty(mon->qmp_requests)) { + qmp_request_free(g_queue_pop_head(mon->qmp_requests)); + } +} + +static void monitor_qmp_cleanup_queue_and_resume(MonitorQMP *mon) +{ + QEMU_LOCK_GUARD(&mon->qmp_queue_lock); + + /* + * Same condition as in monitor_qmp_dispatcher_co(), but before + * removing an element from the queue (hence no `- 1`). + * Also, the queue should not be empty either, otherwise the + * monitor hasn't been suspended yet (or was already resumed). + */ + bool need_resume = (!qmp_oob_enabled(mon) || + mon->qmp_requests->length == QMP_REQ_QUEUE_LEN_MAX) + && !g_queue_is_empty(mon->qmp_requests); + + monitor_qmp_cleanup_req_queue_locked(mon); + + if (need_resume) { + /* + * handle_qmp_command() suspended the monitor because the + * request queue filled up, to be resumed when the queue has + * space again. We just emptied it; resume the monitor. + * + * Without this, the monitor would remain suspended forever + * when we get here while the monitor is suspended. An + * unfortunately timed CHR_EVENT_CLOSED can do the trick. + */ + monitor_resume(&mon->common); + } + +} + +void qmp_send_response(MonitorQMP *mon, const QDict *rsp) +{ + const QObject *data = QOBJECT(rsp); + GString *json; + + json = qobject_to_json_pretty(data, mon->pretty); + assert(json != NULL); + trace_monitor_qmp_respond(mon, json->str); + + g_string_append_c(json, '\n'); + monitor_puts(&mon->common, json->str); + + g_string_free(json, true); +} + +/* + * Emit QMP response @rsp to @mon. + * Null @rsp can only happen for commands with QCO_NO_SUCCESS_RESP. + * Nothing is emitted then. + */ +static void monitor_qmp_respond(MonitorQMP *mon, QDict *rsp) +{ + if (rsp) { + qmp_send_response(mon, rsp); + } +} + +/* + * Runs outside of coroutine context for OOB commands, but in + * coroutine context for everything else. + */ +static void monitor_qmp_dispatch(MonitorQMP *mon, QObject *req) +{ + QDict *rsp; + QDict *error; + + rsp = qmp_dispatch(mon->commands, req, qmp_oob_enabled(mon), + &mon->common); + + if (mon->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); + 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 qmp_mon->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; + MonitorQMP *qmp_mon; + + QEMU_LOCK_GUARD(&monitor_lock); + + QTAILQ_FOREACH(mon, &mon_list, entry) { + if (!monitor_is_qmp(mon)) { + continue; + } + + qmp_mon = container_of(mon, MonitorQMP, common); + qemu_mutex_lock(&qmp_mon->qmp_queue_lock); + req_obj = g_queue_pop_head(qmp_mon->qmp_requests); + if (req_obj) { + /* With the lock of corresponding queue held */ + break; + } + qemu_mutex_unlock(&qmp_mon->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); + } + + return req_obj; +} + +void coroutine_fn monitor_qmp_dispatcher_co(void *data) +{ + QMPRequest *req_obj = NULL; + QDict *rsp; + bool oob_enabled; + MonitorQMP *mon; + + while (true) { + assert(qatomic_mb_read(&qmp_dispatcher_co_busy) == true); + + /* + * Mark the dispatcher as not busy already here so that we + * don't miss any new requests coming in the middle of our + * processing. + */ + qatomic_mb_set(&qmp_dispatcher_co_busy, false); + + /* On shutdown, don't take any more requests from the queue */ + if (qmp_dispatcher_co_shutdown) { + return; + } + + while (!(req_obj = monitor_qmp_requests_pop_any_with_lock())) { + /* + * No more requests to process. Wait to be reentered from + * handle_qmp_command() when it pushes more requests, or + * from monitor_cleanup() when it requests shutdown. + */ + if (!qmp_dispatcher_co_shutdown) { + qemu_coroutine_yield(); + + /* + * busy must be set to true again by whoever + * rescheduled us to avoid double scheduling + */ + assert(qatomic_xchg(&qmp_dispatcher_co_busy, false) == true); + } + + /* + * qmp_dispatcher_co_shutdown may have changed if we + * yielded and were reentered from monitor_cleanup() + */ + if (qmp_dispatcher_co_shutdown) { + return; + } + } + + trace_monitor_qmp_in_band_dequeue(req_obj, + req_obj->mon->qmp_requests->length); + + /* + * @req_obj has a request, we hold req_obj->mon->qmp_queue_lock + */ + + mon = req_obj->mon; + + /* + * We need to resume the monitor if handle_qmp_command() + * suspended it. Two cases: + * 1. OOB enabled: mon->qmp_requests has no more space + * Resume right away, so that OOB commands can get executed while + * this request is being processed. + * 2. OOB disabled: always + * Resume only after we're done processing the request, + * We need to save qmp_oob_enabled() for later, because + * qmp_qmp_capabilities() can change it. + */ + oob_enabled = qmp_oob_enabled(mon); + if (oob_enabled + && mon->qmp_requests->length == QMP_REQ_QUEUE_LEN_MAX - 1) { + monitor_resume(&mon->common); + } + + /* + * Drop the queue mutex now, before yielding, otherwise we might + * deadlock if the main thread tries to lock it. + */ + qemu_mutex_unlock(&mon->qmp_queue_lock); + + if (qatomic_xchg(&qmp_dispatcher_co_busy, true) == true) { + /* + * Someone rescheduled us (probably because a new requests + * came in), but we didn't actually yield. Do that now, + * only to be immediately reentered and removed from the + * list of scheduled coroutines. + */ + qemu_coroutine_yield(); + } + + /* + * Move the coroutine from iohandler_ctx to qemu_aio_context for + * executing the command handler so that it can make progress if it + * involves an AIO_WAIT_WHILE(). + */ + aio_co_schedule(qemu_get_aio_context(), qmp_dispatcher_co); + qemu_coroutine_yield(); + + /* Process request */ + if (req_obj->req) { + if (trace_event_get_state(TRACE_MONITOR_QMP_CMD_IN_BAND)) { + QDict *qdict = qobject_to(QDict, req_obj->req); + QObject *id = qdict ? qdict_get(qdict, "id") : NULL; + GString *id_json; + + id_json = id ? qobject_to_json(id) : g_string_new(NULL); + trace_monitor_qmp_cmd_in_band(id_json->str); + g_string_free(id_json, true); + } + monitor_qmp_dispatch(mon, req_obj->req); + } else { + assert(req_obj->err); + trace_monitor_qmp_err_in_band(error_get_pretty(req_obj->err)); + rsp = qmp_error_response(req_obj->err); + req_obj->err = NULL; + monitor_qmp_respond(mon, rsp); + qobject_unref(rsp); + } + + if (!oob_enabled) { + monitor_resume(&mon->common); + } + + qmp_request_free(req_obj); + + /* + * Yield and reschedule so the main loop stays responsive. + * + * Move back to iohandler_ctx so that nested event loops for + * qemu_aio_context don't start new monitor commands. + */ + aio_co_schedule(iohandler_get_aio_context(), qmp_dispatcher_co); + qemu_coroutine_yield(); + } +} + +static void handle_qmp_command(void *opaque, QObject *req, Error *err) +{ + MonitorQMP *mon = opaque; + QDict *qdict = qobject_to(QDict, req); + QMPRequest *req_obj; + + assert(!req != !err); + + if (req && trace_event_get_state_backends(TRACE_HANDLE_QMP_COMMAND)) { + GString *req_json = qobject_to_json(req); + trace_handle_qmp_command(mon, req_json->str); + g_string_free(req_json, true); + } + + if (qdict && qmp_is_oob(qdict)) { + /* OOB commands are executed immediately */ + if (trace_event_get_state(TRACE_MONITOR_QMP_CMD_OUT_OF_BAND)) { + QObject *id = qdict_get(qdict, "id"); + GString *id_json; + + id_json = id ? qobject_to_json(id) : g_string_new(NULL); + trace_monitor_qmp_cmd_out_of_band(id_json->str); + g_string_free(id_json, true); + } + monitor_qmp_dispatch(mon, req); + qobject_unref(req); + return; + } + + req_obj = g_new0(QMPRequest, 1); + req_obj->mon = mon; + req_obj->req = req; + req_obj->err = err; + + /* Protect qmp_requests and fetching its length. */ + WITH_QEMU_LOCK_GUARD(&mon->qmp_queue_lock) { + + /* + * Suspend the monitor when we can't queue more requests after + * this one. Dequeuing in monitor_qmp_dispatcher_co() or + * monitor_qmp_cleanup_queue_and_resume() 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_requests->length == QMP_REQ_QUEUE_LEN_MAX - 1) { + monitor_suspend(&mon->common); + } + + /* + * Put the request to the end of queue so that requests will be + * handled in time order. Ownership for req_obj, req, + * etc. will be delivered to the handler side. + */ + trace_monitor_qmp_in_band_enqueue(req_obj, mon, + mon->qmp_requests->length); + assert(mon->qmp_requests->length < QMP_REQ_QUEUE_LEN_MAX); + g_queue_push_tail(mon->qmp_requests, req_obj); + } + + /* Kick the dispatcher routine */ + if (!qatomic_xchg(&qmp_dispatcher_co_busy, true)) { + aio_co_wake(qmp_dispatcher_co); + } +} + +static void monitor_qmp_read(void *opaque, const uint8_t *buf, int size) +{ + MonitorQMP *mon = opaque; + + json_message_parser_feed(&mon->parser, (const char *) buf, size); +} + +static QDict *qmp_greeting(MonitorQMP *mon) +{ + QList *cap_list = qlist_new(); + QObject *ver = NULL; + QDict *args; + QMPCapability cap; + + args = qdict_new(); + qmp_marshal_query_version(args, &ver, NULL); + qobject_unref(args); + + for (cap = 0; cap < QMP_CAPABILITY__MAX; cap++) { + if (mon->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, QEMUChrEvent event) +{ + QDict *data; + MonitorQMP *mon = opaque; + + switch (event) { + case CHR_EVENT_OPENED: + mon->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_queue_and_resume(mon); + json_message_parser_destroy(&mon->parser); + json_message_parser_init(&mon->parser, handle_qmp_command, + mon, NULL); + mon_refcount--; + monitor_fdsets_cleanup(); + break; + case CHR_EVENT_BREAK: + case CHR_EVENT_MUX_IN: + case CHR_EVENT_MUX_OUT: + /* Ignore */ + break; + } +} + +void monitor_data_destroy_qmp(MonitorQMP *mon) +{ + json_message_parser_destroy(&mon->parser); + qemu_mutex_destroy(&mon->qmp_queue_lock); + monitor_qmp_cleanup_req_queue_locked(mon); + g_queue_free(mon->qmp_requests); +} + +static void monitor_qmp_setup_handlers_bh(void *opaque) +{ + MonitorQMP *mon = opaque; + GMainContext *context; + + assert(mon->common.use_io_thread); + context = iothread_get_g_main_context(mon_iothread); + assert(context); + qemu_chr_fe_set_handlers(&mon->common.chr, monitor_can_read, + monitor_qmp_read, monitor_qmp_event, + NULL, &mon->common, context, true); + monitor_list_append(&mon->common); +} + +void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp) +{ + MonitorQMP *mon = g_new0(MonitorQMP, 1); + + if (!qemu_chr_fe_init(&mon->common.chr, chr, errp)) { + g_free(mon); + return; + } + qemu_chr_fe_set_echo(&mon->common.chr, true); + + /* Note: we run QMP monitor in I/O thread when @chr supports that */ + monitor_data_init(&mon->common, true, false, + qemu_chr_has_feature(chr, QEMU_CHAR_FEATURE_GCONTEXT)); + + mon->pretty = pretty; + + qemu_mutex_init(&mon->qmp_queue_lock); + mon->qmp_requests = g_queue_new(); + + json_message_parser_init(&mon->parser, handle_qmp_command, mon, NULL); + if (mon->common.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 */ + } else { + qemu_chr_fe_set_handlers(&mon->common.chr, monitor_can_read, + monitor_qmp_read, monitor_qmp_event, + NULL, &mon->common, NULL, true); + monitor_list_append(&mon->common); + } +} diff --git a/monitor/trace-events b/monitor/trace-events new file mode 100644 index 000000000..032d1220e --- /dev/null +++ b/monitor/trace-events @@ -0,0 +1,19 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# hmp.c +handle_hmp_command(void *mon, const char *cmdline) "mon %p cmdline: %s" + +# monitor.c +monitor_protocol_event_handler(uint32_t event, void *qdict) "event=%d data=%p" +monitor_protocol_event_emit(uint32_t event, void *data) "event=%d data=%p" +monitor_protocol_event_queue(uint32_t event, void *qdict, uint64_t rate) "event=%d data=%p rate=%" PRId64 +monitor_suspend(void *ptr, int cnt) "mon %p: %d" + +# qmp.c +monitor_qmp_in_band_enqueue(void *req, void *mon, unsigned len) "%p mon %p len %u" +monitor_qmp_in_band_dequeue(void *req, unsigned len) "%p len %u" +monitor_qmp_cmd_in_band(const char *id) "%s" +monitor_qmp_err_in_band(const char *desc) "%s" +monitor_qmp_cmd_out_of_band(const char *id) "%s" +monitor_qmp_respond(void *mon, const char *json) "mon %p resp: %s" +handle_qmp_command(void *mon, const char *req) "mon %p req: %s" diff --git a/monitor/trace.h b/monitor/trace.h new file mode 100644 index 000000000..f216e31be --- /dev/null +++ b/monitor/trace.h @@ -0,0 +1 @@ +#include "trace/trace-monitor.h" |