diff --git a/hmp.c b/hmp.c index 6d86fe3d1c..aae13c093b 100644 --- a/hmp.c +++ b/hmp.c @@ -306,6 +306,49 @@ out: qapi_free_VncInfo(info); } +void hmp_info_spice(Monitor *mon) +{ + SpiceChannelList *chan; + SpiceInfo *info; + + 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, " auth: %s\n", info->auth); + monitor_printf(mon, " compiled: %s\n", info->compiled_version); + + 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); + } + } + +out: + qapi_free_SpiceInfo(info); +} + void hmp_quit(Monitor *mon, const QDict *qdict) { monitor_suspend(mon); diff --git a/hmp.h b/hmp.h index 7713348cfd..09f3a01cee 100644 --- a/hmp.h +++ b/hmp.h @@ -29,6 +29,7 @@ void hmp_info_cpus(Monitor *mon); void hmp_info_block(Monitor *mon); void hmp_info_blockstats(Monitor *mon); void hmp_info_vnc(Monitor *mon); +void hmp_info_spice(Monitor *mon); void hmp_quit(Monitor *mon, const QDict *qdict); void hmp_stop(Monitor *mon, const QDict *qdict); void hmp_system_reset(Monitor *mon, const QDict *qdict); diff --git a/monitor.c b/monitor.c index d7c72bbe0d..fc3da382f0 100644 --- a/monitor.c +++ b/monitor.c @@ -2857,8 +2857,7 @@ static const mon_cmd_t info_cmds[] = { .args_type = "", .params = "", .help = "show the spice server status", - .user_print = do_info_spice_print, - .mhandler.info_new = do_info_spice, + .mhandler.info = hmp_info_spice, }, #endif { @@ -2965,16 +2964,6 @@ static const mon_cmd_t qmp_query_cmds[] = { .user_print = do_pci_info_print, .mhandler.info_new = do_pci_info, }, -#if defined(CONFIG_SPICE) - { - .name = "spice", - .args_type = "", - .params = "", - .help = "show the spice server status", - .user_print = do_info_spice_print, - .mhandler.info_new = do_info_spice, - }, -#endif { .name = "balloon", .args_type = "", diff --git a/qapi-schema.json b/qapi-schema.json index 23be143a88..087a46305e 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -584,6 +584,80 @@ ## { 'command': 'query-vnc', 'returns': 'VncInfo' } +## +# @SpiceChannel +# +# Information about a SPICE client channel. +# +# @host: The host name of the client. QEMU tries to resolve this to a DNS name +# when possible. +# +# @family: 'ipv6' if the client is connected via IPv6 and TCP +# 'ipv4' if the client is connected via IPv4 and TCP +# 'unix' if the client is connected via a unix domain socket +# 'unknown' otherwise +# +# @port: The client's port number. +# +# @connection-id: SPICE connection id number. All channels with the same id +# belong to the same SPICE session. +# +# @connection-type: SPICE channel type number. "1" is the main control channel, +# filter for this one if you want track spice sessions only +# +# @channel-id: SPICE channel ID number. Usually "0", might be different needed +# when multiple channels of the same type exist, such as multiple +# display channels in a multihead setup +# +# @tls: true if the channel is encrypted, false otherwise. +# +# Since: 0.14.0 +## +{ 'type': 'SpiceChannel', + 'data': {'host': 'str', 'family': 'str', 'port': 'str', + 'connection-id': 'int', 'channel-type': 'int', 'channel-id': 'int', + 'tls': 'bool'} } + +## +# @SpiceInfo +# +# Information about the SPICE session. +# +# @enabled: true if the SPICE server is enabled, false otherwise +# +# @host: #optional The hostname the SPICE server is bound to. This depends on +# the name resolution on the host and may be an IP address. +# +# @port: #optional The SPICE server's port number. +# +# @compiled-version: #optional SPICE server version. +# +# @tls-port: #optional The SPICE server's TLS port number. +# +# @auth: #optional the current authentication type used by the server +# 'none' if no authentication is being used +# 'spice' (TODO: describe) +# +# @channels: a list of @SpiceChannel for each active spice channel +# +# Since: 0.14.0 +## +{ 'type': 'SpiceInfo', + 'data': {'enabled': 'bool', '*host': 'str', '*port': 'int', + '*tls-port': 'int', '*auth': 'str', '*compiled-version': 'str', + '*channels': ['SpiceChannel']} } + +## +# @query-spice +# +# Returns information about the current SPICE server +# +# Returns: @SpiceInfo +# +# Since: 0.14.0 +## +{ 'command': 'query-spice', 'returns': 'SpiceInfo' } + ## # @quit: # diff --git a/qmp-commands.hx b/qmp-commands.hx index 0953d3f6be..e99c240f1a 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -1817,6 +1817,14 @@ Example: EQMP +#if defined(CONFIG_SPICE) + { + .name = "query-spice", + .args_type = "", + .mhandler.cmd_new = qmp_marshal_input_query_spice, + }, +#endif + SQMP query-name ---------- diff --git a/qmp.c b/qmp.c index 3a58cfe35f..511dd624b5 100644 --- a/qmp.c +++ b/qmp.c @@ -105,3 +105,15 @@ VncInfo *qmp_query_vnc(Error **errp) return NULL; }; #endif + +#ifndef CONFIG_SPICE +/* If SPICE support is enabled, the "true" query-spice command is + defined in the SPICE subsystem. Also note that we use a small + trick to maintain query-spice's original behavior, which is not + to be available in the namespace if SPICE is not compiled in */ +SpiceInfo *qmp_query_spice(Error **errp) +{ + error_set(errp, QERR_COMMAND_NOT_FOUND, "query-spice"); + return NULL; +}; +#endif diff --git a/ui/spice-core.c b/ui/spice-core.c index b33366e5d7..f556849ef9 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -27,6 +27,7 @@ #include "qemu-queue.h" #include "qemu-x509.h" #include "qemu_socket.h" +#include "qmp-commands.h" #include "qint.h" #include "qbool.h" #include "qstring.h" @@ -194,22 +195,6 @@ static void add_channel_info(QDict *dict, SpiceChannelEventInfo *info) qdict_put(dict, "tls", qbool_from_int(tls)); } -static QList *channel_list_get(void) -{ - ChannelList *item; - QList *list; - QDict *dict; - - list = qlist_new(); - QTAILQ_FOREACH(item, &channel_list, link) { - dict = qdict_new(); - add_addr_info(dict, &item->info->paddr, item->info->plen); - add_channel_info(dict, item->info); - qlist_append(list, dict); - } - return list; -} - static void channel_event(int event, SpiceChannelEventInfo *info) { static const int qevent[] = { @@ -351,98 +336,90 @@ static const char *wan_compression_names[] = { /* functions for the rest of qemu */ -static void info_spice_iter(QObject *obj, void *opaque) +static SpiceChannelList *qmp_query_spice_channels(void) { - QDict *client; - Monitor *mon = opaque; + SpiceChannelList *cur_item = NULL, *head = NULL; + ChannelList *item; - client = qobject_to_qdict(obj); - monitor_printf(mon, "Channel:\n"); - monitor_printf(mon, " address: %s:%s%s\n", - qdict_get_str(client, "host"), - qdict_get_str(client, "port"), - qdict_get_bool(client, "tls") ? " [tls]" : ""); - monitor_printf(mon, " session: %" PRId64 "\n", - qdict_get_int(client, "connection-id")); - monitor_printf(mon, " channel: %d:%d\n", - (int)qdict_get_int(client, "channel-type"), - (int)qdict_get_int(client, "channel-id")); + QTAILQ_FOREACH(item, &channel_list, link) { + SpiceChannelList *chan; + char host[NI_MAXHOST], port[NI_MAXSERV]; + + chan = g_malloc0(sizeof(*chan)); + chan->value = g_malloc0(sizeof(*chan->value)); + + getnameinfo(&item->info->paddr, item->info->plen, + host, sizeof(host), port, sizeof(port), + NI_NUMERICHOST | NI_NUMERICSERV); + chan->value->host = g_strdup(host); + chan->value->port = g_strdup(port); + chan->value->family = g_strdup(inet_strfamily(item->info->paddr.sa_family)); + + chan->value->connection_id = item->info->connection_id; + chan->value->channel_type = item->info->type; + chan->value->channel_id = item->info->id; + chan->value->tls = item->info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS; + + /* XXX: waiting for the qapi to support GSList */ + if (!cur_item) { + head = cur_item = chan; + } else { + cur_item->next = chan; + cur_item = chan; + } + } + + return head; } -void do_info_spice_print(Monitor *mon, const QObject *data) -{ - QDict *server; - QList *channels; - const char *host; - int port; - - server = qobject_to_qdict(data); - if (qdict_get_bool(server, "enabled") == 0) { - monitor_printf(mon, "Server: disabled\n"); - return; - } - - monitor_printf(mon, "Server:\n"); - host = qdict_get_str(server, "host"); - port = qdict_get_try_int(server, "port", -1); - if (port != -1) { - monitor_printf(mon, " address: %s:%d\n", host, port); - } - port = qdict_get_try_int(server, "tls-port", -1); - if (port != -1) { - monitor_printf(mon, " address: %s:%d [tls]\n", host, port); - } - monitor_printf(mon, " auth: %s\n", qdict_get_str(server, "auth")); - monitor_printf(mon, " compiled: %s\n", - qdict_get_str(server, "compiled-version")); - - channels = qdict_get_qlist(server, "channels"); - if (qlist_empty(channels)) { - monitor_printf(mon, "Channels: none\n"); - } else { - qlist_iter(channels, info_spice_iter, mon); - } -} - -void do_info_spice(Monitor *mon, QObject **ret_data) +SpiceInfo *qmp_query_spice(Error **errp) { QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); - QDict *server; - QList *clist; - const char *addr; int port, tls_port; + const char *addr; + SpiceInfo *info; char version_string[20]; /* 12 = |255.255.255\0| is the max */ + info = g_malloc0(sizeof(*info)); + if (!spice_server) { - *ret_data = qobject_from_jsonf("{ 'enabled': false }"); - return; + info->enabled = false; + return info; } + info->enabled = true; + addr = qemu_opt_get(opts, "addr"); port = qemu_opt_get_number(opts, "port", 0); tls_port = qemu_opt_get_number(opts, "tls-port", 0); - clist = channel_list_get(); - server = qdict_new(); - qdict_put(server, "enabled", qbool_from_int(true)); - qdict_put(server, "auth", qstring_from_str(auth)); - qdict_put(server, "host", qstring_from_str(addr ? addr : "0.0.0.0")); + info->has_auth = true; + info->auth = g_strdup(auth); + + info->has_host = true; + info->host = g_strdup(addr ? addr : "0.0.0.0"); + + info->has_compiled_version = true; snprintf(version_string, sizeof(version_string), "%d.%d.%d", (SPICE_SERVER_VERSION & 0xff0000) >> 16, (SPICE_SERVER_VERSION & 0xff00) >> 8, SPICE_SERVER_VERSION & 0xff); - qdict_put(server, "compiled-version", qstring_from_str(version_string)); + info->compiled_version = g_strdup(version_string); + if (port) { - qdict_put(server, "port", qint_from_int(port)); + info->has_port = true; + info->port = port; } if (tls_port) { - qdict_put(server, "tls-port", qint_from_int(tls_port)); - } - if (clist) { - qdict_put(server, "channels", clist); + info->has_tls_port = true; + info->tls_port = tls_port; } - *ret_data = QOBJECT(server); + /* for compatibility with the original command */ + info->has_channels = true; + info->channels = qmp_query_spice_channels(); + + return info; } static void migration_state_notifier(Notifier *notifier, void *data)