mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-07-27 04:13:53 -06:00

The "special_features" field / parameter holds the subset of schema features that are for internal code use. Specifically 'DEPRECATED' and 'UNSTABLE'. This special casing of internal features is going to be removed, so prepare for that by renaming to 'features'. Using a fixed size type is also best practice for bit fields. Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> Message-ID: <20250205123550.2754387-3-berrange@redhat.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Markus Armbruster <armbru@redhat.com>
296 lines
8.3 KiB
C
296 lines
8.3 KiB
C
/*
|
|
* Core Definitions for QAPI/QMP Dispatch
|
|
*
|
|
* Copyright IBM, Corp. 2011
|
|
*
|
|
* Authors:
|
|
* Anthony Liguori <aliguori@us.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
|
|
* See the COPYING.LIB file in the top-level directory.
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
|
|
#include "block/aio.h"
|
|
#include "qapi/compat-policy.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/qmp-registry.h"
|
|
#include "qobject/qdict.h"
|
|
#include "qobject/qjson.h"
|
|
#include "qapi/qobject-input-visitor.h"
|
|
#include "qapi/qobject-output-visitor.h"
|
|
#include "qobject/qbool.h"
|
|
#include "qemu/coroutine.h"
|
|
#include "qemu/main-loop.h"
|
|
|
|
Visitor *qobject_input_visitor_new_qmp(QObject *obj)
|
|
{
|
|
Visitor *v = qobject_input_visitor_new(obj);
|
|
|
|
visit_set_policy(v, &compat_policy);
|
|
return v;
|
|
}
|
|
|
|
Visitor *qobject_output_visitor_new_qmp(QObject **result)
|
|
{
|
|
Visitor *v = qobject_output_visitor_new(result);
|
|
|
|
visit_set_policy(v, &compat_policy);
|
|
return v;
|
|
}
|
|
|
|
static QDict *qmp_dispatch_check_obj(QDict *dict, bool allow_oob,
|
|
Error **errp)
|
|
{
|
|
const char *exec_key = NULL;
|
|
const QDictEntry *ent;
|
|
const char *arg_name;
|
|
const QObject *arg_obj;
|
|
|
|
for (ent = qdict_first(dict); ent;
|
|
ent = qdict_next(dict, ent)) {
|
|
arg_name = qdict_entry_key(ent);
|
|
arg_obj = qdict_entry_value(ent);
|
|
|
|
if (!strcmp(arg_name, "execute")
|
|
|| (!strcmp(arg_name, "exec-oob") && allow_oob)) {
|
|
if (qobject_type(arg_obj) != QTYPE_QSTRING) {
|
|
error_setg(errp, "QMP input member '%s' must be a string",
|
|
arg_name);
|
|
return NULL;
|
|
}
|
|
if (exec_key) {
|
|
error_setg(errp, "QMP input member '%s' clashes with '%s'",
|
|
arg_name, exec_key);
|
|
return NULL;
|
|
}
|
|
exec_key = arg_name;
|
|
} else if (!strcmp(arg_name, "arguments")) {
|
|
if (qobject_type(arg_obj) != QTYPE_QDICT) {
|
|
error_setg(errp,
|
|
"QMP input member 'arguments' must be an object");
|
|
return NULL;
|
|
}
|
|
} else if (!strcmp(arg_name, "id")) {
|
|
continue;
|
|
} else {
|
|
error_setg(errp, "QMP input member '%s' is unexpected",
|
|
arg_name);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (!exec_key) {
|
|
error_setg(errp, "QMP input lacks member 'execute'");
|
|
return NULL;
|
|
}
|
|
|
|
return dict;
|
|
}
|
|
|
|
QDict *qmp_error_response(Error *err)
|
|
{
|
|
QDict *rsp;
|
|
|
|
rsp = qdict_from_jsonf_nofail("{ 'error': { 'class': %s, 'desc': %s } }",
|
|
QapiErrorClass_str(error_get_class(err)),
|
|
error_get_pretty(err));
|
|
error_free(err);
|
|
return rsp;
|
|
}
|
|
|
|
/*
|
|
* Does @qdict look like a command to be run out-of-band?
|
|
*/
|
|
bool qmp_is_oob(const QDict *dict)
|
|
{
|
|
return qdict_haskey(dict, "exec-oob")
|
|
&& !qdict_haskey(dict, "execute");
|
|
}
|
|
|
|
typedef struct QmpDispatchBH {
|
|
const QmpCommand *cmd;
|
|
Monitor *cur_mon;
|
|
QDict *args;
|
|
QObject **ret;
|
|
Error **errp;
|
|
Coroutine *co;
|
|
} QmpDispatchBH;
|
|
|
|
static void do_qmp_dispatch_bh(void *opaque)
|
|
{
|
|
QmpDispatchBH *data = opaque;
|
|
|
|
assert(monitor_cur() == NULL);
|
|
monitor_set_cur(qemu_coroutine_self(), data->cur_mon);
|
|
data->cmd->fn(data->args, data->ret, data->errp);
|
|
monitor_set_cur(qemu_coroutine_self(), NULL);
|
|
aio_co_wake(data->co);
|
|
}
|
|
|
|
/*
|
|
* Runs outside of coroutine context for OOB commands, but in coroutine
|
|
* context for everything else.
|
|
*/
|
|
QDict *coroutine_mixed_fn qmp_dispatch(const QmpCommandList *cmds, QObject *request,
|
|
bool allow_oob, Monitor *cur_mon)
|
|
{
|
|
Error *err = NULL;
|
|
bool oob;
|
|
const char *command;
|
|
QDict *args;
|
|
const QmpCommand *cmd;
|
|
QDict *dict;
|
|
QObject *id;
|
|
QObject *ret = NULL;
|
|
QDict *rsp = NULL;
|
|
|
|
dict = qobject_to(QDict, request);
|
|
if (!dict) {
|
|
id = NULL;
|
|
error_setg(&err, "QMP input must be a JSON object");
|
|
goto out;
|
|
}
|
|
|
|
id = qdict_get(dict, "id");
|
|
|
|
if (!qmp_dispatch_check_obj(dict, allow_oob, &err)) {
|
|
goto out;
|
|
}
|
|
|
|
command = qdict_get_try_str(dict, "execute");
|
|
oob = false;
|
|
if (!command) {
|
|
assert(allow_oob);
|
|
command = qdict_get_str(dict, "exec-oob");
|
|
oob = true;
|
|
}
|
|
cmd = qmp_find_command(cmds, command);
|
|
if (cmd == NULL) {
|
|
error_set(&err, ERROR_CLASS_COMMAND_NOT_FOUND,
|
|
"The command %s has not been found", command);
|
|
goto out;
|
|
}
|
|
if (!compat_policy_input_ok(cmd->features, &compat_policy,
|
|
ERROR_CLASS_COMMAND_NOT_FOUND,
|
|
"command", command, &err)) {
|
|
goto out;
|
|
}
|
|
if (!cmd->enabled) {
|
|
error_set(&err, ERROR_CLASS_COMMAND_NOT_FOUND,
|
|
"Command %s has been disabled%s%s",
|
|
command,
|
|
cmd->disable_reason ? ": " : "",
|
|
cmd->disable_reason ?: "");
|
|
goto out;
|
|
}
|
|
if (oob && !(cmd->options & QCO_ALLOW_OOB)) {
|
|
error_setg(&err, "The command %s does not support OOB",
|
|
command);
|
|
goto out;
|
|
}
|
|
|
|
if (!qmp_command_available(cmd, &err)) {
|
|
goto out;
|
|
}
|
|
|
|
if (!qdict_haskey(dict, "arguments")) {
|
|
args = qdict_new();
|
|
} else {
|
|
args = qdict_get_qdict(dict, "arguments");
|
|
qobject_ref(args);
|
|
}
|
|
|
|
assert(!(oob && qemu_in_coroutine()));
|
|
assert(monitor_cur() == NULL);
|
|
if (!!(cmd->options & QCO_COROUTINE) == qemu_in_coroutine()) {
|
|
if (qemu_in_coroutine()) {
|
|
/*
|
|
* 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(), qemu_coroutine_self());
|
|
qemu_coroutine_yield();
|
|
}
|
|
|
|
monitor_set_cur(qemu_coroutine_self(), cur_mon);
|
|
cmd->fn(args, &ret, &err);
|
|
monitor_set_cur(qemu_coroutine_self(), NULL);
|
|
|
|
if (qemu_in_coroutine()) {
|
|
/*
|
|
* 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(),
|
|
qemu_coroutine_self());
|
|
qemu_coroutine_yield();
|
|
}
|
|
} else {
|
|
/*
|
|
* Actual context doesn't match the one the command needs.
|
|
*
|
|
* Case 1: we are in coroutine context, but command does not
|
|
* have QCO_COROUTINE. We need to drop out of coroutine
|
|
* context for executing it.
|
|
*
|
|
* Case 2: we are outside coroutine context, but command has
|
|
* QCO_COROUTINE. Can't actually happen, because we get here
|
|
* outside coroutine context only when executing a command
|
|
* out of band, and OOB commands never have QCO_COROUTINE.
|
|
*/
|
|
assert(!oob && qemu_in_coroutine() && !(cmd->options & QCO_COROUTINE));
|
|
|
|
QmpDispatchBH data = {
|
|
.cur_mon = cur_mon,
|
|
.cmd = cmd,
|
|
.args = args,
|
|
.ret = &ret,
|
|
.errp = &err,
|
|
.co = qemu_coroutine_self(),
|
|
};
|
|
aio_bh_schedule_oneshot(iohandler_get_aio_context(), do_qmp_dispatch_bh,
|
|
&data);
|
|
qemu_coroutine_yield();
|
|
}
|
|
qobject_unref(args);
|
|
if (err) {
|
|
/* or assert(!ret) after reviewing all handlers: */
|
|
qobject_unref(ret);
|
|
goto out;
|
|
}
|
|
|
|
if (cmd->options & QCO_NO_SUCCESS_RESP) {
|
|
g_assert(!ret);
|
|
return NULL;
|
|
} else if (!ret) {
|
|
/*
|
|
* When the command's schema has no 'returns', cmd->fn()
|
|
* leaves @ret null. The QMP spec calls for an empty object
|
|
* then; supply it.
|
|
*/
|
|
ret = QOBJECT(qdict_new());
|
|
}
|
|
|
|
rsp = qdict_new();
|
|
qdict_put_obj(rsp, "return", ret);
|
|
|
|
out:
|
|
if (err) {
|
|
assert(!rsp);
|
|
rsp = qmp_error_response(err);
|
|
}
|
|
|
|
assert(rsp);
|
|
|
|
if (id) {
|
|
qdict_put_obj(rsp, "id", qobject_ref(id));
|
|
}
|
|
|
|
return rsp;
|
|
}
|