Monitor patches for 2018-07-03

-----BEGIN PGP SIGNATURE-----
 
 iQIcBAABAgAGBQJbO+iNAAoJEDhwtADrkYZTX9gQAJFT0DPtUYQsqbYAhrUIJo/a
 lT5OAnMnMou6Qu0XHjSYrqdZu7Ega00rvyeYgtB54lmLcOVrjAxF0SwcPcoOuz7K
 O9o00ZmwuFPUugJuYBLDRqJryejbZTCGSOg3cp+YlV6naW6Omck97iu7G/MZWLvB
 FJvIIUaPIAGklRhSodhC+yePp1PNbfGVcpDjAxn4e1UQ/2M6gwSFx9iPbNl2WotN
 CXjRHmkB4ZcxwcDtzzFLwkXTDkryZE27sOgFjDG5xFWO/oCS+Px7a0vME8YM+j2r
 eNa93g16ZohNqHb9t6GarlqrqNqbQF2t6JAPpmlIX5qb3Dtb87MQ/ExdN+FJ4btD
 IfBkTnXbs7qkZpConRlrhdd2J01ChnCdcmddUeenlq+8mIMjQJXH2RCwRVKyoZYs
 4Z74XWy2A+xMJgjqY9WO7E4jy4QVDSckKIm6je/O8fXzXWxthi4H1/2EN5nAv1hY
 zNxP/SW0vpqiR/cVQAr2N6VGz2/m9cWQIf0NwFc765gypPBbCL5HLrb+/xC86Bi3
 ylg9YlTw84oXtpSauTYu3oE989iDndu/s9BEUe5fW5g+8azs+h5SHVTwq0Y8dzSZ
 n2FsGxr76BlOIu5i1oIYNUUhAoU3txddueT4SNHE5Iaz1x6fAKnMP2hxoo5DWDzn
 1Rzgq4ATB8H+V6Og942n
 =/2SW
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/armbru/tags/pull-monitor-2018-07-03-v2' into staging

Monitor patches for 2018-07-03

# gpg: Signature made Tue 03 Jul 2018 22:20:13 BST
# gpg:                using RSA key 3870B400EB918653
# gpg: Good signature from "Markus Armbruster <armbru@redhat.com>"
# gpg:                 aka "Markus Armbruster <armbru@pond.sub.org>"
# Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867  4E5F 3870 B400 EB91 8653

* remotes/armbru/tags/pull-monitor-2018-07-03-v2: (32 commits)
  qapi: Polish command flags documentation in qapi-code-gen.txt
  monitor: Improve some comments
  qmp: Clean up capability negotiation after commit 02130314d8
  qobject: Let qobject_from_jsonf() fail instead of abort
  qmp: Switch timestamp_put() to qdict_from_jsonf_nofail()
  qmp: Add some comments around null responses
  qmp: Simplify monitor_qmp_respond()
  qmp: Replace get_qmp_greeting() by qmp_greeting()
  qmp: Replace monitor_json_emitter{,raw}() by qmp_{queue,send}_response()
  qmp: Use QDict * instead of QObject * for response objects
  qmp: De-duplicate error response building
  qobject: New qdict_from_jsonf_nofail()
  monitor: Peel off @mon_global wrapper
  monitor: Rename use_io_thr to use_io_thread
  qmp: Don't let JSON errors jump the queue
  qmp: Don't let malformed in-band commands jump the queue
  tests/qmp-test: Demonstrate QMP errors jumping the queue
  qmp: Simplify code around monitor_qmp_dispatch_one()
  qmp: Always free QMPRequest with qmp_request_free()
  qmp: Revert change to handle_qmp_command tracepoint
  ...

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2018-07-05 11:25:14 +01:00
commit 4fd1cbaf14
16 changed files with 511 additions and 528 deletions

View File

@ -624,62 +624,48 @@ its return value.
In rare cases, QAPI cannot express a type-safe representation of a In rare cases, QAPI cannot express a type-safe representation of a
corresponding Client JSON Protocol command. You then have to suppress corresponding Client JSON Protocol command. You then have to suppress
generation of a marshalling function by including a key 'gen' with generation of a marshalling function by including a key 'gen' with
boolean value false, and instead write your own function. Please try boolean value false, and instead write your own function. For
to avoid adding new commands that rely on this, and instead use example:
type-safe unions. For an example of this usage:
{ 'command': 'netdev_add', { 'command': 'netdev_add',
'data': {'type': 'str', 'id': 'str'}, 'data': {'type': 'str', 'id': 'str'},
'gen': false } 'gen': false }
Please try to avoid adding new commands that rely on this, and instead
use type-safe unions.
Normally, the QAPI schema is used to describe synchronous exchanges, Normally, the QAPI schema is used to describe synchronous exchanges,
where a response is expected. But in some cases, the action of a where a response is expected. But in some cases, the action of a
command is expected to change state in a way that a successful command is expected to change state in a way that a successful
response is not possible (although the command will still return a response is not possible (although the command will still return a
normal dictionary error on failure). When a successful reply is not normal dictionary error on failure). When a successful reply is not
possible, the command expression should include the optional key possible, the command expression includes the optional key
'success-response' with boolean value false. So far, only QGA makes 'success-response' with boolean value false. So far, only QGA makes
use of this member. use of this member.
A command can be declared to support Out-Of-Band (OOB) execution. By Key 'allow-oob' declares whether the command supports out-of-band
default, commands do not support OOB. To declare a command that (OOB) execution. It defaults to false. For example:
supports it, the schema includes an extra 'allow-oob' field. For
example:
{ 'command': 'migrate_recover', { 'command': 'migrate_recover',
'data': { 'uri': 'str' }, 'allow-oob': true } 'data': { 'uri': 'str' }, 'allow-oob': true }
To execute a command with out-of-band priority, the client specifies See qmp-spec.txt for out-of-band execution syntax and semantics.
the "control" field in the request, with "run-oob" set to
true. Example:
=> { "execute": "command-support-oob", Commands supporting out-of-band execution can still be executed
"arguments": { ... }, in-band.
"control": { "run-oob": true } }
<= { "return": { } }
Without it, even the commands that support out-of-band execution will When a command is executed in-band, its handler runs in the main
still be run in-band. thread with the BQL held.
Under normal QMP command execution, the following apply to each When a command is executed out-of-band, its handler runs in a
command: dedicated monitor I/O thread with the BQL *not* held.
- They are executed in order, An OOB-capable command handler must satisfy the following conditions:
- They run only in main thread of QEMU,
- They run with the BQL held.
When a command is executed with OOB, the following changes occur: - It terminates quickly.
- It does not invoke system calls that may block.
- They can be completed before a pending in-band command,
- They run in a dedicated monitor thread,
- They run with the BQL not held.
OOB command handlers must satisfy the following conditions:
- It terminates quickly,
- It does not invoke system calls that may block,
- It does not access guest RAM that may block when userfaultfd is - It does not access guest RAM that may block when userfaultfd is
enabled for postcopy live migration, enabled for postcopy live migration.
- It takes only "fast" locks, i.e. all critical sections protected by - It takes only "fast" locks, i.e. all critical sections protected by
any lock it takes also satisfy the conditions for OOB command any lock it takes also satisfy the conditions for OOB command
handler code. handler code.
@ -688,17 +674,18 @@ The restrictions on locking limit access to shared state. Such access
requires synchronization, but OOB commands can't take the BQL or any requires synchronization, but OOB commands can't take the BQL or any
other "slow" lock. other "slow" lock.
If in doubt, do not implement OOB execution support. When in doubt, do not implement OOB execution support.
A command may use the optional 'allow-preconfig' key to permit its execution Key 'allow-preconfig' declares whether the command is available before
at early runtime configuration stage (preconfig runstate). the machine is built. It defaults to false. For example:
If not specified then a command defaults to 'allow-preconfig': false.
An example of declaring a command that is enabled during preconfig:
{ 'command': 'qmp_capabilities', { 'command': 'qmp_capabilities',
'data': { '*enable': [ 'QMPCapability' ] }, 'data': { '*enable': [ 'QMPCapability' ] },
'allow-preconfig': true } 'allow-preconfig': true }
QMP is available before the machine is built only when QEMU was
started with --preconfig.
=== Events === === Events ===
Usage: { 'event': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT, Usage: { 'event': STRING, '*data': COMPLEX-TYPE-NAME-OR-DICT,

View File

@ -52,13 +52,14 @@ Escape character is '^]'.
"QMP": { "QMP": {
"version": { "version": {
"qemu": { "qemu": {
"micro": 50, "micro": 0,
"minor": 6, "minor": 0,
"major": 1 "major": 3
}, },
"package": "" "package": "v3.0.0"
}, },
"capabilities": [ "capabilities": [
"oob"
] ]
} }
} }

View File

@ -77,52 +77,65 @@ The greeting message format is:
is the same of the query-version command) is the same of the query-version command)
- The "capabilities" member specify the availability of features beyond the - The "capabilities" member specify the availability of features beyond the
baseline specification; the order of elements in this array has no baseline specification; the order of elements in this array has no
particular significance, so a client must search the entire array particular significance.
when looking for a particular capability
2.2.1 Capabilities 2.2.1 Capabilities
------------------ ------------------
Currently supported capabilities are: Currently supported capabilities are:
- "oob": the QMP server supports "Out-Of-Band" (OOB) command - "oob": the QMP server supports "out-of-band" (OOB) command
execution. For more details, please see the "run-oob" parameter in execution, as described in section "2.3.1 Out-of-band execution".
the "Issuing Commands" section below. Not all commands allow this
"oob" execution. The "query-qmp-schema" command can be used to
inspect which commands support "oob" execution.
QMP clients can get a list of supported QMP capabilities of the QMP
server in the greeting message mentioned above. By default, all the
capabilities are off. To enable any QMP capabilities, the QMP client
needs to send the "qmp_capabilities" command with an extra parameter
for the requested capabilities.
2.3 Issuing Commands 2.3 Issuing Commands
-------------------- --------------------
The format for command execution is: The format for command execution is:
{ "execute": json-string, "arguments": json-object, "id": json-value, { "execute": json-string, "arguments": json-object, "id": json-value }
"control": json-object }
or
{ "exec-oob": json-string, "arguments": json-object, "id": json-value }
Where, Where,
- The "execute" member identifies the command to be executed by the Server - The "execute" or "exec-oob" member identifies the command to be
executed by the server. The latter requests out-of-band execution.
- The "arguments" member is used to pass any arguments required for the - The "arguments" member is used to pass any arguments required for the
execution of the command, it is optional when no arguments are execution of the command, it is optional when no arguments are
required. Each command documents what contents will be considered required. Each command documents what contents will be considered
valid when handling the json-argument valid when handling the json-argument
- The "id" member is a transaction identification associated with the - The "id" member is a transaction identification associated with the
command execution. It is required for all commands if the OOB - command execution, it is optional and will be part of the response
capability was enabled at startup, and optional otherwise. The same if provided. The "id" member can be any json-value. A json-number
"id" field will be part of the response if provided. The "id" member incremented for each successive command works fine.
can be any json-value, although most clients merely use a
json-number incremented for each successive command 2.3.1 Out-of-band execution
- The "control" member is optional, and currently only used for ---------------------------
out-of-band execution. The handling or response of an "oob" command
can overtake prior in-band commands. To enable "oob" handling of a The server normally reads, executes and responds to one command after
particular command, just provide a control field with: { "control": the other. The client therefore receives command responses in issue
{ "run-oob": true } } order.
With out-of-band execution enabled via capability negotiation (section
4.), the server reads and queues commands as they arrive. It executes
commands from the queue one after the other. Commands executed
out-of-band jump the queue: the command get executed right away,
possibly overtaking prior in-band commands. The client may therefore
receive such a command's response before responses from prior in-band
commands.
To be able to match responses back to their commands, the client needs
to pass "id" with out-of-band commands. Passing it with all commands
is recommended for clients that accept capability "oob".
If the client sends in-band commands faster than the server can
execute them, the server will eventually drop commands to limit the
queue length. The sever sends event COMMAND_DROPPED then.
Only a few commands support out-of-band execution. The ones that do
have "allow-oob": true in output of query-qmp-schema.
2.4 Commands Responses 2.4 Commands Responses
---------------------- ----------------------
@ -223,12 +236,13 @@ This section provides some examples of real QMP usage, in all of them
3.1 Server greeting 3.1 Server greeting
------------------- -------------------
S: { "QMP": { "version": { "qemu": { "micro": 50, "minor": 6, "major": 1 }, S: { "QMP": {"version": {"qemu": {"micro": 0, "minor": 0, "major": 3},
"package": ""}, "capabilities": []}} "package": "v3.0.0"}, "capabilities": ["oob"] } }
3.2 Client QMP negotiation 3.2 Capabilities negotiation
-------------------------- ----------------------------
C: { "execute": "qmp_capabilities" }
C: { "execute": "qmp_capabilities", "arguments": { "enable": ["oob"] } }
S: { "return": {}} S: { "return": {}}
3.3 Simple 'stop' execution 3.3 Simple 'stop' execution
@ -255,6 +269,15 @@ S: { "error": { "class": "GenericError", "desc": "Invalid JSON syntax" } }
S: { "timestamp": { "seconds": 1258551470, "microseconds": 802384 }, S: { "timestamp": { "seconds": 1258551470, "microseconds": 802384 },
"event": "POWERDOWN" } "event": "POWERDOWN" }
3.7 Out-of-band execution
-------------------------
C: { "exec-oob": "migrate-pause", "id": 42 }
S: { "id": 42,
"error": { "class": "GenericError",
"desc": "migrate-pause is currently only supported during postcopy-active state" } }
4. Capabilities Negotiation 4. Capabilities Negotiation
=========================== ===========================

View File

@ -41,15 +41,15 @@ void qmp_register_command(QmpCommandList *cmds, const char *name,
QmpCommandFunc *fn, QmpCommandOptions options); QmpCommandFunc *fn, QmpCommandOptions options);
void qmp_unregister_command(QmpCommandList *cmds, const char *name); void qmp_unregister_command(QmpCommandList *cmds, const char *name);
QmpCommand *qmp_find_command(QmpCommandList *cmds, const char *name); QmpCommand *qmp_find_command(QmpCommandList *cmds, const char *name);
QObject *qmp_dispatch(QmpCommandList *cmds, QObject *request);
void qmp_disable_command(QmpCommandList *cmds, const char *name); void qmp_disable_command(QmpCommandList *cmds, const char *name);
void qmp_enable_command(QmpCommandList *cmds, const char *name); void qmp_enable_command(QmpCommandList *cmds, const char *name);
bool qmp_command_is_enabled(const QmpCommand *cmd); bool qmp_command_is_enabled(const QmpCommand *cmd);
const char *qmp_command_name(const QmpCommand *cmd); const char *qmp_command_name(const QmpCommand *cmd);
bool qmp_has_success_response(const QmpCommand *cmd); bool qmp_has_success_response(const QmpCommand *cmd);
QObject *qmp_build_error_object(Error *err); QDict *qmp_error_response(Error *err);
QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp); QDict *qmp_dispatch(QmpCommandList *cmds, QObject *request,
bool allow_oob);
bool qmp_is_oob(QDict *dict); bool qmp_is_oob(QDict *dict);
typedef void (*qmp_cmd_callback_fn)(QmpCommand *cmd, void *opaque); typedef void (*qmp_cmd_callback_fn)(QmpCommand *cmd, void *opaque);

View File

@ -19,6 +19,8 @@ QObject *qobject_from_jsonf(const char *string, ...) GCC_FMT_ATTR(1, 2);
QObject *qobject_from_jsonv(const char *string, va_list *ap, Error **errp) QObject *qobject_from_jsonv(const char *string, va_list *ap, Error **errp)
GCC_FMT_ATTR(1, 0); GCC_FMT_ATTR(1, 0);
QDict *qdict_from_jsonf_nofail(const char *string, ...) GCC_FMT_ATTR(1, 2);
QString *qobject_to_json(const QObject *obj); QString *qobject_to_json(const QObject *obj);
QString *qobject_to_json_pretty(const QObject *obj); QString *qobject_to_json_pretty(const QObject *obj);

519
monitor.c
View File

@ -169,14 +169,16 @@ typedef struct {
JSONMessageParser parser; JSONMessageParser parser;
/* /*
* When a client connects, we're in capabilities negotiation mode. * When a client connects, we're in capabilities negotiation mode.
* When command qmp_capabilities succeeds, we go into command * @commands is &qmp_cap_negotiation_commands then. When command
* mode. * qmp_capabilities succeeds, we go into command mode, and
* @command becomes &qmp_commands.
*/ */
QmpCommandList *commands; QmpCommandList *commands;
bool qmp_caps[QMP_CAPABILITY__MAX]; bool capab_offered[QMP_CAPABILITY__MAX]; /* capabilities offered */
bool capab[QMP_CAPABILITY__MAX]; /* offered and accepted */
/* /*
* Protects qmp request/response queue. Please take monitor_lock * Protects qmp request/response queue.
* first when used together. * Take monitor_lock first when you need both.
*/ */
QemuMutex qmp_queue_lock; QemuMutex qmp_queue_lock;
/* Input queue that holds all the parsed QMP requests */ /* Input queue that holds all the parsed QMP requests */
@ -207,11 +209,11 @@ struct Monitor {
int flags; int flags;
int suspend_cnt; /* Needs to be accessed atomically */ int suspend_cnt; /* Needs to be accessed atomically */
bool skip_flush; bool skip_flush;
bool use_io_thr; bool use_io_thread;
/* /*
* State used only in the thread "owning" the monitor. * State used only in the thread "owning" the monitor.
* If @use_io_thr, this is mon_global.mon_iothread. * If @use_io_thread, this is @mon_iothread.
* Else, it's the main thread. * Else, it's the main thread.
* These members can be safely accessed without locks. * These members can be safely accessed without locks.
*/ */
@ -231,7 +233,7 @@ struct Monitor {
QemuMutex mon_lock; QemuMutex mon_lock;
/* /*
* Fields that are protected by the per-monitor lock. * Members that are protected by the per-monitor lock
*/ */
QLIST_HEAD(, mon_fd_t) fds; QLIST_HEAD(, mon_fd_t) fds;
QString *outbuf; QString *outbuf;
@ -240,22 +242,26 @@ struct Monitor {
int mux_out; int mux_out;
}; };
/* Let's add monitor global variables to this struct. */ /* Shared monitor I/O thread */
static struct { IOThread *mon_iothread;
IOThread *mon_iothread;
/* Bottom half to dispatch the requests received from IO thread */ /* Bottom half to dispatch the requests received from I/O thread */
QEMUBH *qmp_dispatcher_bh; QEMUBH *qmp_dispatcher_bh;
/* Bottom half to deliver the responses back to clients */
QEMUBH *qmp_respond_bh; /* Bottom half to deliver the responses back to clients */
} mon_global; QEMUBH *qmp_respond_bh;
struct QMPRequest { struct QMPRequest {
/* Owner of the request */ /* Owner of the request */
Monitor *mon; Monitor *mon;
/* "id" field of the request */ /* "id" field of the request */
QObject *id; QObject *id;
/* Request object to be handled */ /*
* Request object to be handled or Error to be reported
* (exactly one of them is non-null)
*/
QObject *req; QObject *req;
Error *err;
/* /*
* Whether we need to resume the monitor afterward. This flag is * Whether we need to resume the monitor afterward. This flag is
* used to emulate the old QMP server behavior that the current * used to emulate the old QMP server behavior that the current
@ -298,9 +304,9 @@ static inline bool monitor_is_qmp(const Monitor *mon)
} }
/** /**
* Whether @mon is using readline? Note: not all HMP monitors use * Is @mon is using readline?
* readline, e.g., gdbserver has a non-interactive HMP monitor, so * Note: not all HMP monitors use readline, e.g., gdbserver has a
* readline is not used there. * non-interactive HMP monitor, so readline is not used there.
*/ */
static inline bool monitor_uses_readline(const Monitor *mon) static inline bool monitor_uses_readline(const Monitor *mon)
{ {
@ -314,14 +320,12 @@ static inline bool monitor_is_hmp_non_interactive(const Monitor *mon)
/* /*
* Return the clock to use for recording an event's time. * 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(). * Beware: result is invalid before configure_accelerator().
*/ */
static inline QEMUClockType monitor_get_event_clock(void) static inline QEMUClockType monitor_get_event_clock(void)
{ {
/*
* This allows us to perform tests on the monitor queues to verify
* that the rate limits are enforced.
*/
return qtest_enabled() ? QEMU_CLOCK_VIRTUAL : QEMU_CLOCK_REALTIME; return qtest_enabled() ? QEMU_CLOCK_VIRTUAL : QEMU_CLOCK_REALTIME;
} }
@ -360,10 +364,11 @@ static void qmp_request_free(QMPRequest *req)
{ {
qobject_unref(req->id); qobject_unref(req->id);
qobject_unref(req->req); qobject_unref(req->req);
error_free(req->err);
g_free(req); g_free(req);
} }
/* Must with the mon->qmp.qmp_queue_lock held */ /* Caller must hold mon->qmp.qmp_queue_lock */
static void monitor_qmp_cleanup_req_queue_locked(Monitor *mon) static void monitor_qmp_cleanup_req_queue_locked(Monitor *mon)
{ {
while (!g_queue_is_empty(mon->qmp.qmp_requests)) { while (!g_queue_is_empty(mon->qmp.qmp_requests)) {
@ -371,11 +376,11 @@ static void monitor_qmp_cleanup_req_queue_locked(Monitor *mon)
} }
} }
/* Must with the mon->qmp.qmp_queue_lock held */ /* Caller must hold the mon->qmp.qmp_queue_lock */
static void monitor_qmp_cleanup_resp_queue_locked(Monitor *mon) static void monitor_qmp_cleanup_resp_queue_locked(Monitor *mon)
{ {
while (!g_queue_is_empty(mon->qmp.qmp_responses)) { while (!g_queue_is_empty(mon->qmp.qmp_responses)) {
qobject_unref((QObject *)g_queue_pop_head(mon->qmp.qmp_responses)); qobject_unref((QDict *)g_queue_pop_head(mon->qmp.qmp_responses));
} }
} }
@ -402,7 +407,7 @@ static gboolean monitor_unblocked(GIOChannel *chan, GIOCondition cond,
return FALSE; return FALSE;
} }
/* Called with mon->mon_lock held. */ /* Caller must hold mon->mon_lock */
static void monitor_flush_locked(Monitor *mon) static void monitor_flush_locked(Monitor *mon)
{ {
int rc; int rc;
@ -499,9 +504,9 @@ int monitor_fprintf(FILE *stream, const char *fmt, ...)
return 0; return 0;
} }
static void monitor_json_emitter_raw(Monitor *mon, static void qmp_send_response(Monitor *mon, QDict *rsp)
QObject *data)
{ {
QObject *data = QOBJECT(rsp);
QString *json; QString *json;
json = mon->flags & MONITOR_USE_PRETTY ? qobject_to_json_pretty(data) : json = mon->flags & MONITOR_USE_PRETTY ? qobject_to_json_pretty(data) :
@ -514,37 +519,35 @@ static void monitor_json_emitter_raw(Monitor *mon,
qobject_unref(json); qobject_unref(json);
} }
static void monitor_json_emitter(Monitor *mon, QObject *data) static void qmp_queue_response(Monitor *mon, QDict *rsp)
{ {
if (mon->use_io_thr) { if (mon->use_io_thread) {
/* /*
* If using IO thread, we need to queue the item so that IO * Push a reference to the response queue. The I/O thread
* thread will do the rest for us. Take refcount so that * drains that queue and emits.
* caller won't free the data (which will be finally freed in
* responder thread).
*/ */
qemu_mutex_lock(&mon->qmp.qmp_queue_lock); qemu_mutex_lock(&mon->qmp.qmp_queue_lock);
g_queue_push_tail(mon->qmp.qmp_responses, qobject_ref(data)); g_queue_push_tail(mon->qmp.qmp_responses, qobject_ref(rsp));
qemu_mutex_unlock(&mon->qmp.qmp_queue_lock); qemu_mutex_unlock(&mon->qmp.qmp_queue_lock);
qemu_bh_schedule(mon_global.qmp_respond_bh); qemu_bh_schedule(qmp_respond_bh);
} else { } else {
/* /*
* If not using monitor IO thread, then we are in main thread. * Not using monitor I/O thread, i.e. we are in the main thread.
* Do the emission right away. * Emit right away.
*/ */
monitor_json_emitter_raw(mon, data); qmp_send_response(mon, rsp);
} }
} }
struct QMPResponse { struct QMPResponse {
Monitor *mon; Monitor *mon;
QObject *data; QDict *data;
}; };
typedef struct QMPResponse QMPResponse; typedef struct QMPResponse QMPResponse;
static QObject *monitor_qmp_response_pop_one(Monitor *mon) static QDict *monitor_qmp_response_pop_one(Monitor *mon)
{ {
QObject *data; QDict *data;
qemu_mutex_lock(&mon->qmp.qmp_queue_lock); qemu_mutex_lock(&mon->qmp.qmp_queue_lock);
data = g_queue_pop_head(mon->qmp.qmp_responses); data = g_queue_pop_head(mon->qmp.qmp_responses);
@ -555,10 +558,10 @@ static QObject *monitor_qmp_response_pop_one(Monitor *mon)
static void monitor_qmp_response_flush(Monitor *mon) static void monitor_qmp_response_flush(Monitor *mon)
{ {
QObject *data; QDict *data;
while ((data = monitor_qmp_response_pop_one(mon))) { while ((data = monitor_qmp_response_pop_one(mon))) {
monitor_json_emitter_raw(mon, data); qmp_send_response(mon, data);
qobject_unref(data); qobject_unref(data);
} }
} }
@ -570,7 +573,7 @@ static void monitor_qmp_response_flush(Monitor *mon)
static bool monitor_qmp_response_pop_any(QMPResponse *response) static bool monitor_qmp_response_pop_any(QMPResponse *response)
{ {
Monitor *mon; Monitor *mon;
QObject *data = NULL; QDict *data = NULL;
qemu_mutex_lock(&monitor_lock); qemu_mutex_lock(&monitor_lock);
QTAILQ_FOREACH(mon, &mon_list, entry) { QTAILQ_FOREACH(mon, &mon_list, entry) {
@ -590,7 +593,7 @@ static void monitor_qmp_bh_responder(void *opaque)
QMPResponse response; QMPResponse response;
while (monitor_qmp_response_pop_any(&response)) { while (monitor_qmp_response_pop_any(&response)) {
monitor_json_emitter_raw(response.mon, response.data); qmp_send_response(response.mon, response.data);
qobject_unref(response.data); qobject_unref(response.data);
} }
} }
@ -606,8 +609,9 @@ static MonitorQAPIEventConf monitor_qapi_event_conf[QAPI_EVENT__MAX] = {
}; };
/* /*
* Emits the event to every monitor instance, @event is only used for trace * Broadcast an event to all monitors.
* Called with monitor_lock held. * @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) static void monitor_qapi_event_emit(QAPIEvent event, QDict *qdict)
{ {
@ -617,7 +621,7 @@ static void monitor_qapi_event_emit(QAPIEvent event, QDict *qdict)
QTAILQ_FOREACH(mon, &mon_list, entry) { QTAILQ_FOREACH(mon, &mon_list, entry) {
if (monitor_is_qmp(mon) if (monitor_is_qmp(mon)
&& mon->qmp.commands != &qmp_cap_negotiation_commands) { && mon->qmp.commands != &qmp_cap_negotiation_commands) {
monitor_json_emitter(mon, QOBJECT(qdict)); qmp_queue_response(mon, qdict);
} }
} }
} }
@ -762,7 +766,7 @@ static void monitor_qapi_event_init(void)
static void handle_hmp_command(Monitor *mon, const char *cmdline); static void handle_hmp_command(Monitor *mon, const char *cmdline);
static void monitor_data_init(Monitor *mon, bool skip_flush, static void monitor_data_init(Monitor *mon, bool skip_flush,
bool use_io_thr) bool use_io_thread)
{ {
memset(mon, 0, sizeof(Monitor)); memset(mon, 0, sizeof(Monitor));
qemu_mutex_init(&mon->mon_lock); qemu_mutex_init(&mon->mon_lock);
@ -771,7 +775,7 @@ static void monitor_data_init(Monitor *mon, bool skip_flush,
/* Use *mon_cmds by default. */ /* Use *mon_cmds by default. */
mon->cmd_table = mon_cmds; mon->cmd_table = mon_cmds;
mon->skip_flush = skip_flush; mon->skip_flush = skip_flush;
mon->use_io_thr = use_io_thr; mon->use_io_thread = use_io_thread;
mon->qmp.qmp_requests = g_queue_new(); mon->qmp.qmp_requests = g_queue_new();
mon->qmp.qmp_responses = g_queue_new(); mon->qmp.qmp_responses = g_queue_new();
} }
@ -976,8 +980,7 @@ static int parse_cmdline(const char *cmdline,
} }
/* /*
* Returns true if the command can be executed in preconfig mode * Can command @cmd be executed in preconfig state?
* i.e. it has the 'p' flag.
*/ */
static bool cmd_can_preconfig(const mon_cmd_t *cmd) static bool cmd_can_preconfig(const mon_cmd_t *cmd)
{ {
@ -1246,96 +1249,56 @@ static void monitor_init_qmp_commands(void)
qmp_marshal_qmp_capabilities, QCO_ALLOW_PRECONFIG); qmp_marshal_qmp_capabilities, QCO_ALLOW_PRECONFIG);
} }
static bool qmp_cap_enabled(Monitor *mon, QMPCapability cap)
{
return mon->qmp.qmp_caps[cap];
}
static bool qmp_oob_enabled(Monitor *mon) static bool qmp_oob_enabled(Monitor *mon)
{ {
return qmp_cap_enabled(mon, QMP_CAPABILITY_OOB); return mon->qmp.capab[QMP_CAPABILITY_OOB];
} }
static void qmp_caps_check(Monitor *mon, QMPCapabilityList *list, static void monitor_qmp_caps_reset(Monitor *mon)
Error **errp)
{ {
for (; list; list = list->next) { memset(mon->qmp.capab_offered, 0, sizeof(mon->qmp.capab_offered));
assert(list->value < QMP_CAPABILITY__MAX); memset(mon->qmp.capab, 0, sizeof(mon->qmp.capab));
switch (list->value) { mon->qmp.capab_offered[QMP_CAPABILITY_OOB] = mon->use_io_thread;
case QMP_CAPABILITY_OOB:
if (!mon->use_io_thr) {
/*
* Out-Of-Band only works with monitors that are
* running on dedicated IOThread.
*/
error_setg(errp, "This monitor does not support "
"Out-Of-Band (OOB)");
return;
}
break;
default:
break;
}
}
}
/* This function should only be called after capabilities are checked. */
static void qmp_caps_apply(Monitor *mon, QMPCapabilityList *list)
{
for (; list; list = list->next) {
mon->qmp.qmp_caps[list->value] = true;
}
} }
/* /*
* Return true if check successful, or false otherwise. When false is * Accept QMP capabilities in @list for @mon.
* returned, detailed error will be in errp if provided. * On success, set mon->qmp.capab[], and return true.
* On error, set @errp, and return false.
*/ */
static bool qmp_cmd_oob_check(Monitor *mon, QDict *req, Error **errp) static bool qmp_caps_accept(Monitor *mon, QMPCapabilityList *list,
Error **errp)
{ {
const char *command; GString *unavailable = NULL;
QmpCommand *cmd; bool capab[QMP_CAPABILITY__MAX];
command = qdict_get_try_str(req, "execute"); memset(capab, 0, sizeof(capab));
if (!command) {
error_setg(errp, "Command field 'execute' missing"); for (; list; list = list->next) {
if (!mon->qmp.capab_offered[list->value]) {
if (!unavailable) {
unavailable = g_string_new(QMPCapability_str(list->value));
} else {
g_string_append_printf(unavailable, ", %s",
QMPCapability_str(list->value));
}
}
capab[list->value] = true;
}
if (unavailable) {
error_setg(errp, "Capability %s not available", unavailable->str);
g_string_free(unavailable, true);
return false; return false;
} }
cmd = qmp_find_command(mon->qmp.commands, command); memcpy(mon->qmp.capab, capab, sizeof(capab));
if (!cmd) {
if (mon->qmp.commands == &qmp_cap_negotiation_commands) {
error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
"Expecting capabilities negotiation "
"with 'qmp_capabilities'");
} else {
error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
"The command %s has not been found", command);
}
return false;
}
if (qmp_is_oob(req)) {
if (!qmp_oob_enabled(mon)) {
error_setg(errp, "Please enable Out-Of-Band first "
"for the session during capabilities negotiation");
return false;
}
if (!(cmd->options & QCO_ALLOW_OOB)) {
error_setg(errp, "The command %s does not support OOB",
command);
return false;
}
}
return true; return true;
} }
void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable, void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable,
Error **errp) Error **errp)
{ {
Error *local_err = NULL;
if (cur_mon->qmp.commands == &qmp_commands) { if (cur_mon->qmp.commands == &qmp_commands) {
error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
"Capabilities negotiation is already complete, command " "Capabilities negotiation is already complete, command "
@ -1343,19 +1306,8 @@ void qmp_qmp_capabilities(bool has_enable, QMPCapabilityList *enable,
return; return;
} }
/* Enable QMP capabilities provided by the client if applicable. */ if (!qmp_caps_accept(cur_mon, enable, errp)) {
if (has_enable) { return;
qmp_caps_check(cur_mon, enable, &local_err);
if (local_err) {
/*
* Failed check on any of the capabilities will fail the
* entire command (and thus not apply any of the other
* capabilities that were also requested).
*/
error_propagate(errp, local_err);
return;
}
qmp_caps_apply(cur_mon, enable);
} }
cur_mon->qmp.commands = &qmp_commands; cur_mon->qmp.commands = &qmp_commands;
@ -2264,7 +2216,7 @@ void qmp_getfd(const char *fdname, Error **errp)
tmp_fd = monfd->fd; tmp_fd = monfd->fd;
monfd->fd = fd; monfd->fd = fd;
qemu_mutex_unlock(&cur_mon->mon_lock); qemu_mutex_unlock(&cur_mon->mon_lock);
/* Make sure close() is out of critical section */ /* Make sure close() is outside critical section */
close(tmp_fd); close(tmp_fd);
return; return;
} }
@ -2293,7 +2245,7 @@ void qmp_closefd(const char *fdname, Error **errp)
g_free(monfd->name); g_free(monfd->name);
g_free(monfd); g_free(monfd);
qemu_mutex_unlock(&cur_mon->mon_lock); qemu_mutex_unlock(&cur_mon->mon_lock);
/* Make sure close() is out of critical section */ /* Make sure close() is outside critical section */
close(tmp_fd); close(tmp_fd);
return; return;
} }
@ -4137,78 +4089,53 @@ static int monitor_can_read(void *opaque)
} }
/* /*
* 1. This function takes ownership of rsp, err, and id. * Emit QMP response @rsp with ID @id to @mon.
* 2. rsp, err, and id may be NULL. * Null @rsp can only happen for commands with QCO_NO_SUCCESS_RESP.
* 3. If err != NULL then rsp must be NULL. * Nothing is emitted then.
*/ */
static void monitor_qmp_respond(Monitor *mon, QObject *rsp, static void monitor_qmp_respond(Monitor *mon, QDict *rsp, QObject *id)
Error *err, QObject *id)
{ {
QDict *qdict = NULL;
if (err) {
assert(!rsp);
qdict = qdict_new();
qdict_put_obj(qdict, "error", qmp_build_error_object(err));
error_free(err);
rsp = QOBJECT(qdict);
}
if (rsp) { if (rsp) {
if (id) { if (id) {
qdict_put_obj(qobject_to(QDict, rsp), "id", qobject_ref(id)); qdict_put_obj(rsp, "id", qobject_ref(id));
} }
monitor_json_emitter(mon, rsp); qmp_queue_response(mon, rsp);
} }
qobject_unref(id);
qobject_unref(rsp);
} }
/* static void monitor_qmp_dispatch(Monitor *mon, QObject *req, QObject *id)
* Dispatch one single QMP request. The function will free the req_obj
* and objects inside it before return.
*/
static void monitor_qmp_dispatch_one(QMPRequest *req_obj)
{ {
Monitor *mon, *old_mon; Monitor *old_mon;
QObject *req, *rsp = NULL, *id; QDict *rsp;
bool need_resume; QDict *error;
req = req_obj->req;
mon = req_obj->mon;
id = req_obj->id;
need_resume = req_obj->need_resume;
g_free(req_obj);
if (trace_event_get_state_backends(TRACE_HANDLE_QMP_COMMAND)) {
QString *req_json = qobject_to_json(req);
trace_handle_qmp_command(mon, qstring_get_str(req_json));
qobject_unref(req_json);
}
old_mon = cur_mon; old_mon = cur_mon;
cur_mon = mon; cur_mon = mon;
rsp = qmp_dispatch(mon->qmp.commands, req); rsp = qmp_dispatch(mon->qmp.commands, req, qmp_oob_enabled(mon));
cur_mon = old_mon; cur_mon = old_mon;
/* Respond if necessary */ if (mon->qmp.commands == &qmp_cap_negotiation_commands) {
monitor_qmp_respond(mon, rsp, NULL, id); error = qdict_get_qdict(rsp, "error");
if (error
/* This pairs with the monitor_suspend() in handle_qmp_command(). */ && !g_strcmp0(qdict_get_try_str(error, "class"),
if (need_resume) { QapiErrorClass_str(ERROR_CLASS_COMMAND_NOT_FOUND))) {
monitor_resume(mon); /* Provide a more useful error message */
qdict_del(error, "desc");
qdict_put_str(error, "desc", "Expecting capabilities negotiation"
" with 'qmp_capabilities'");
}
} }
qobject_unref(req); monitor_qmp_respond(mon, rsp, id);
qobject_unref(rsp);
} }
/* /*
* Pop one QMP request from monitor queues, return NULL if not found. * 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 * We are using round-robin fashion to pop the request, to avoid
* processing commands only on a very busy monitor. To achieve that, * processing commands only on a very busy monitor. To achieve that,
* when we process one request on a specific monitor, we put that * when we process one request on a specific monitor, we put that
@ -4247,13 +4174,30 @@ static QMPRequest *monitor_qmp_requests_pop_any(void)
static void monitor_qmp_bh_dispatcher(void *data) static void monitor_qmp_bh_dispatcher(void *data)
{ {
QMPRequest *req_obj = monitor_qmp_requests_pop_any(); QMPRequest *req_obj = monitor_qmp_requests_pop_any();
QDict *rsp;
if (req_obj) { if (!req_obj) {
trace_monitor_qmp_cmd_in_band(qobject_get_try_str(req_obj->id) ?: ""); return;
monitor_qmp_dispatch_one(req_obj);
/* Reschedule instead of looping so the main loop stays responsive */
qemu_bh_schedule(mon_global.qmp_dispatcher_bh);
} }
if (req_obj->req) {
trace_monitor_qmp_cmd_in_band(qobject_get_try_str(req_obj->id) ?: "");
monitor_qmp_dispatch(req_obj->mon, req_obj->req, req_obj->id);
} else {
assert(req_obj->err);
rsp = qmp_error_response(req_obj->err);
monitor_qmp_respond(req_obj->mon, rsp, NULL);
qobject_unref(rsp);
}
if (req_obj->need_resume) {
/* Pairs with the monitor_suspend() in handle_qmp_command() */
monitor_resume(req_obj->mon);
}
qmp_request_free(req_obj);
/* Reschedule instead of looping so the main loop stays responsive */
qemu_bh_schedule(qmp_dispatcher_bh);
} }
#define QMP_REQ_QUEUE_LEN_MAX (8) #define QMP_REQ_QUEUE_LEN_MAX (8)
@ -4261,7 +4205,7 @@ static void monitor_qmp_bh_dispatcher(void *data)
static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens) static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens)
{ {
QObject *req, *id = NULL; QObject *req, *id = NULL;
QDict *qdict = NULL; QDict *qdict;
MonitorQMP *mon_qmp = container_of(parser, MonitorQMP, parser); MonitorQMP *mon_qmp = container_of(parser, MonitorQMP, parser);
Monitor *mon = container_of(mon_qmp, Monitor, qmp); Monitor *mon = container_of(mon_qmp, Monitor, qmp);
Error *err = NULL; Error *err = NULL;
@ -4272,46 +4216,34 @@ static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens)
/* json_parser_parse_err() sucks: can fail without setting @err */ /* json_parser_parse_err() sucks: can fail without setting @err */
error_setg(&err, QERR_JSON_PARSING); error_setg(&err, QERR_JSON_PARSING);
} }
if (err) {
goto err; qdict = qobject_to(QDict, req);
if (qdict) {
id = qobject_ref(qdict_get(qdict, "id"));
qdict_del(qdict, "id");
} /* else will fail qmp_dispatch() */
if (trace_event_get_state_backends(TRACE_HANDLE_QMP_COMMAND)) {
QString *req_json = qobject_to_json(req);
trace_handle_qmp_command(mon, qstring_get_str(req_json));
qobject_unref(req_json);
} }
/* Check against the request in general layout */ if (qdict && qmp_is_oob(qdict)) {
qdict = qmp_dispatch_check_obj(req, &err); /* OOB commands are executed immediately */
if (!qdict) { trace_monitor_qmp_cmd_out_of_band(qobject_get_try_str(id)
goto err; ?: "");
} monitor_qmp_dispatch(mon, req, id);
return;
/* Check against OOB specific */
if (!qmp_cmd_oob_check(mon, qdict, &err)) {
goto err;
}
id = qdict_get(qdict, "id");
/* When OOB is enabled, the "id" field is mandatory. */
if (qmp_oob_enabled(mon) && !id) {
error_setg(&err, "Out-Of-Band capability requires that "
"every command contains an 'id' field");
goto err;
} }
req_obj = g_new0(QMPRequest, 1); req_obj = g_new0(QMPRequest, 1);
req_obj->mon = mon; req_obj->mon = mon;
req_obj->id = qobject_ref(id); req_obj->id = id;
req_obj->req = req; req_obj->req = req;
req_obj->err = err;
req_obj->need_resume = false; req_obj->need_resume = false;
qdict_del(qdict, "id");
if (qmp_is_oob(qdict)) {
/* Out-Of-Band (OOB) requests are executed directly in parser. */
trace_monitor_qmp_cmd_out_of_band(qobject_get_try_str(req_obj->id)
?: "");
monitor_qmp_dispatch_one(req_obj);
return;
}
/* Protect qmp_requests and fetching its length. */ /* Protect qmp_requests and fetching its length. */
qemu_mutex_lock(&mon->qmp.qmp_queue_lock); qemu_mutex_lock(&mon->qmp.qmp_queue_lock);
@ -4328,6 +4260,12 @@ static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens)
/* Drop the request if queue is full. */ /* Drop the request if queue is full. */
if (mon->qmp.qmp_requests->length >= QMP_REQ_QUEUE_LEN_MAX) { if (mon->qmp.qmp_requests->length >= QMP_REQ_QUEUE_LEN_MAX) {
qemu_mutex_unlock(&mon->qmp.qmp_queue_lock); qemu_mutex_unlock(&mon->qmp.qmp_queue_lock);
/*
* FIXME @id's scope is just @mon, and broadcasting it is
* wrong. If another monitor's client has a command with
* the same ID in flight, the event will incorrectly claim
* that command was dropped.
*/
qapi_event_send_command_dropped(id, qapi_event_send_command_dropped(id,
COMMAND_DROP_REASON_QUEUE_FULL, COMMAND_DROP_REASON_QUEUE_FULL,
&error_abort); &error_abort);
@ -4345,12 +4283,7 @@ static void handle_qmp_command(JSONMessageParser *parser, GQueue *tokens)
qemu_mutex_unlock(&mon->qmp.qmp_queue_lock); qemu_mutex_unlock(&mon->qmp.qmp_queue_lock);
/* Kick the dispatcher routine */ /* Kick the dispatcher routine */
qemu_bh_schedule(mon_global.qmp_dispatcher_bh); qemu_bh_schedule(qmp_dispatcher_bh);
return;
err:
monitor_qmp_respond(mon, NULL, err, NULL);
qobject_unref(req);
} }
static void monitor_qmp_read(void *opaque, const uint8_t *buf, int size) static void monitor_qmp_read(void *opaque, const uint8_t *buf, int size)
@ -4400,10 +4333,10 @@ int monitor_suspend(Monitor *mon)
if (monitor_is_qmp(mon)) { if (monitor_is_qmp(mon)) {
/* /*
* Kick iothread to make sure this takes effect. It'll be * Kick I/O thread to make sure this takes effect. It'll be
* evaluated again in prepare() of the watch object. * evaluated again in prepare() of the watch object.
*/ */
aio_notify(iothread_get_aio_context(mon_global.mon_iothread)); aio_notify(iothread_get_aio_context(mon_iothread));
} }
trace_monitor_suspend(mon, 1); trace_monitor_suspend(mon, 1);
@ -4419,11 +4352,11 @@ void monitor_resume(Monitor *mon)
if (atomic_dec_fetch(&mon->suspend_cnt) == 0) { if (atomic_dec_fetch(&mon->suspend_cnt) == 0) {
if (monitor_is_qmp(mon)) { if (monitor_is_qmp(mon)) {
/* /*
* For QMP monitors that are running in IOThread, let's * For QMP monitors that are running in the I/O thread,
* kick the thread in case it's sleeping. * let's kick the thread in case it's sleeping.
*/ */
if (mon->use_io_thr) { if (mon->use_io_thread) {
aio_notify(iothread_get_aio_context(mon_global.mon_iothread)); aio_notify(iothread_get_aio_context(mon_iothread));
} }
} else { } else {
assert(mon->rs); assert(mon->rs);
@ -4433,7 +4366,7 @@ void monitor_resume(Monitor *mon)
trace_monitor_suspend(mon, -1); trace_monitor_suspend(mon, -1);
} }
static QObject *get_qmp_greeting(Monitor *mon) static QDict *qmp_greeting(Monitor *mon)
{ {
QList *cap_list = qlist_new(); QList *cap_list = qlist_new();
QObject *ver = NULL; QObject *ver = NULL;
@ -4442,33 +4375,27 @@ static QObject *get_qmp_greeting(Monitor *mon)
qmp_marshal_query_version(NULL, &ver, NULL); qmp_marshal_query_version(NULL, &ver, NULL);
for (cap = 0; cap < QMP_CAPABILITY__MAX; cap++) { for (cap = 0; cap < QMP_CAPABILITY__MAX; cap++) {
if (!mon->use_io_thr && cap == QMP_CAPABILITY_OOB) { if (mon->qmp.capab_offered[cap]) {
/* Monitors that are not using IOThread won't support OOB */ qlist_append_str(cap_list, QMPCapability_str(cap));
continue;
} }
qlist_append_str(cap_list, QMPCapability_str(cap));
} }
return qobject_from_jsonf("{'QMP': {'version': %p, 'capabilities': %p}}", return qdict_from_jsonf_nofail(
ver, cap_list); "{'QMP': {'version': %p, 'capabilities': %p}}",
} ver, cap_list);
static void monitor_qmp_caps_reset(Monitor *mon)
{
memset(mon->qmp.qmp_caps, 0, sizeof(mon->qmp.qmp_caps));
} }
static void monitor_qmp_event(void *opaque, int event) static void monitor_qmp_event(void *opaque, int event)
{ {
QObject *data; QDict *data;
Monitor *mon = opaque; Monitor *mon = opaque;
switch (event) { switch (event) {
case CHR_EVENT_OPENED: case CHR_EVENT_OPENED:
mon->qmp.commands = &qmp_cap_negotiation_commands; mon->qmp.commands = &qmp_cap_negotiation_commands;
monitor_qmp_caps_reset(mon); monitor_qmp_caps_reset(mon);
data = get_qmp_greeting(mon); data = qmp_greeting(mon);
monitor_json_emitter(mon, data); qmp_queue_response(mon, data);
qobject_unref(data); qobject_unref(data);
mon_refcount++; mon_refcount++;
break; break;
@ -4561,36 +4488,35 @@ static void sortcmdlist(void)
static GMainContext *monitor_get_io_context(void) static GMainContext *monitor_get_io_context(void)
{ {
return iothread_get_g_main_context(mon_global.mon_iothread); return iothread_get_g_main_context(mon_iothread);
} }
static AioContext *monitor_get_aio_context(void) static AioContext *monitor_get_aio_context(void)
{ {
return iothread_get_aio_context(mon_global.mon_iothread); return iothread_get_aio_context(mon_iothread);
} }
static void monitor_iothread_init(void) static void monitor_iothread_init(void)
{ {
mon_global.mon_iothread = iothread_create("mon_iothread", mon_iothread = iothread_create("mon_iothread", &error_abort);
&error_abort);
/* /*
* This MUST be on main loop thread since we have commands that * The dispatcher BH must run in the main loop thread, since we
* have assumption to be run on main loop thread. It would be * have commands assuming that context. It would be nice to get
* nice that one day we can remove this assumption in the future. * rid of those assumptions.
*/ */
mon_global.qmp_dispatcher_bh = aio_bh_new(iohandler_get_aio_context(), qmp_dispatcher_bh = aio_bh_new(iohandler_get_aio_context(),
monitor_qmp_bh_dispatcher, monitor_qmp_bh_dispatcher,
NULL); NULL);
/* /*
* Unlike the dispatcher BH, this must be run on the monitor IO * The responder BH must be run in the monitor I/O thread, so that
* thread, so that monitors that are using IO thread will make * monitors that are using the I/O thread have their output
* sure read/write operations are all done on the IO thread. * written by the I/O thread.
*/ */
mon_global.qmp_respond_bh = aio_bh_new(monitor_get_aio_context(), qmp_respond_bh = aio_bh_new(monitor_get_aio_context(),
monitor_qmp_bh_responder, monitor_qmp_bh_responder,
NULL); NULL);
} }
void monitor_init_globals(void) void monitor_init_globals(void)
@ -4655,16 +4581,12 @@ static void monitor_qmp_setup_handlers_bh(void *opaque)
Monitor *mon = opaque; Monitor *mon = opaque;
GMainContext *context; GMainContext *context;
if (mon->use_io_thr) { if (mon->use_io_thread) {
/* /* Use @mon_iothread context */
* When use_io_thr is set, we use the global shared dedicated
* IO thread for this monitor to handle input/output.
*/
context = monitor_get_io_context(); context = monitor_get_io_context();
/* We should have inited globals before reaching here. */
assert(context); assert(context);
} else { } else {
/* The default main loop, which is the main thread */ /* Use default main loop context */
context = NULL; context = NULL;
} }
@ -4681,12 +4603,12 @@ void monitor_init(Chardev *chr, int flags)
if (use_oob) { if (use_oob) {
if (CHARDEV_IS_MUX(chr)) { if (CHARDEV_IS_MUX(chr)) {
error_report("Monitor Out-Of-Band is not supported with " error_report("Monitor out-of-band is not supported with "
"MUX typed chardev backend"); "MUX typed chardev backend");
exit(1); exit(1);
} }
if (use_readline) { if (use_readline) {
error_report("Monitor Out-Of-band is only supported by QMP"); error_report("Monitor out-of-band is only supported by QMP");
exit(1); exit(1);
} }
} }
@ -4706,7 +4628,7 @@ void monitor_init(Chardev *chr, int flags)
if (monitor_is_qmp(mon)) { if (monitor_is_qmp(mon)) {
qemu_chr_fe_set_echo(&mon->chr, true); qemu_chr_fe_set_echo(&mon->chr, true);
json_message_parser_init(&mon->qmp.parser, handle_qmp_command); json_message_parser_init(&mon->qmp.parser, handle_qmp_command);
if (mon->use_io_thr) { if (mon->use_io_thread) {
/* /*
* Make sure the old iowatch is gone. It's possible when * Make sure the old iowatch is gone. It's possible when
* e.g. the chardev is in client mode, with wait=on. * e.g. the chardev is in client mode, with wait=on.
@ -4714,15 +4636,12 @@ void monitor_init(Chardev *chr, int flags)
remove_fd_in_watch(chr); remove_fd_in_watch(chr);
/* /*
* We can't call qemu_chr_fe_set_handlers() directly here * We can't call qemu_chr_fe_set_handlers() directly here
* since during the procedure the chardev will be active * since chardev might be running in the monitor I/O
* and running in monitor iothread, while we'll still do * thread. Schedule a bottom half.
* something before returning from it, which is a possible
* race too. To avoid that, we just create a BH to setup
* the handlers.
*/ */
aio_bh_schedule_oneshot(monitor_get_aio_context(), aio_bh_schedule_oneshot(monitor_get_aio_context(),
monitor_qmp_setup_handlers_bh, mon); monitor_qmp_setup_handlers_bh, mon);
/* We'll add this to mon_list in the BH when setup done */ /* The bottom half will add @mon to @mon_list */
return; return;
} else { } else {
qemu_chr_fe_set_handlers(&mon->chr, monitor_can_read, qemu_chr_fe_set_handlers(&mon->chr, monitor_can_read,
@ -4742,22 +4661,20 @@ void monitor_cleanup(void)
Monitor *mon, *next; Monitor *mon, *next;
/* /*
* We need to explicitly stop the iothread (but not destroy it), * We need to explicitly stop the I/O thread (but not destroy it),
* cleanup the monitor resources, then destroy the iothread since * clean up the monitor resources, then destroy the I/O thread since
* we need to unregister from chardev below in * we need to unregister from chardev below in
* monitor_data_destroy(), and chardev is not thread-safe yet * monitor_data_destroy(), and chardev is not thread-safe yet
*/ */
iothread_stop(mon_global.mon_iothread); iothread_stop(mon_iothread);
/* /*
* After we have IOThread to send responses, it's possible that * Flush all response queues. Note that even after this flush,
* when we stop the IOThread there are still replies queued in the * data may remain in output buffers.
* responder queue. Flush all of them. Note that even after this
* flush it's still possible that out buffer is not flushed.
* It'll be done in below monitor_flush() as the last resort.
*/ */
monitor_qmp_bh_responder(NULL); monitor_qmp_bh_responder(NULL);
/* Flush output buffers and destroy monitors */
qemu_mutex_lock(&monitor_lock); qemu_mutex_lock(&monitor_lock);
QTAILQ_FOREACH_SAFE(mon, &mon_list, entry, next) { QTAILQ_FOREACH_SAFE(mon, &mon_list, entry, next) {
QTAILQ_REMOVE(&mon_list, mon, entry); QTAILQ_REMOVE(&mon_list, mon, entry);
@ -4767,14 +4684,14 @@ void monitor_cleanup(void)
} }
qemu_mutex_unlock(&monitor_lock); qemu_mutex_unlock(&monitor_lock);
/* QEMUBHs needs to be deleted before destroying the IOThread. */ /* QEMUBHs needs to be deleted before destroying the I/O thread */
qemu_bh_delete(mon_global.qmp_dispatcher_bh); qemu_bh_delete(qmp_dispatcher_bh);
mon_global.qmp_dispatcher_bh = NULL; qmp_dispatcher_bh = NULL;
qemu_bh_delete(mon_global.qmp_respond_bh); qemu_bh_delete(qmp_respond_bh);
mon_global.qmp_respond_bh = NULL; qmp_respond_bh = NULL;
iothread_destroy(mon_global.mon_iothread); iothread_destroy(mon_iothread);
mon_global.mon_iothread = NULL; mon_iothread = NULL;
} }
QemuOptsList qemu_mon_opts = { QemuOptsList qemu_mon_opts = {

View File

@ -46,7 +46,7 @@
# Enumeration of capabilities to be advertised during initial client # Enumeration of capabilities to be advertised during initial client
# connection, used for agreeing on particular QMP extension behaviors. # connection, used for agreeing on particular QMP extension behaviors.
# #
# @oob: QMP ability to support Out-Of-Band requests. # @oob: QMP ability to support out-of-band requests.
# (Please refer to qmp-spec.txt for more information on OOB) # (Please refer to qmp-spec.txt for more information on OOB)
# #
# Since: 2.12 # Since: 2.12
@ -3454,6 +3454,9 @@
# only be dropped when the oob capability is enabled. # only be dropped when the oob capability is enabled.
# #
# @id: The dropped command's "id" field. # @id: The dropped command's "id" field.
# FIXME Broken by design. Events are broadcast to all monitors. If
# another monitor's client has a command with the same ID in flight,
# the event will incorrectly claim that command was dropped.
# #
# @reason: The reason why the command is dropped. # @reason: The reason why the command is dropped.
# #
@ -3469,24 +3472,6 @@
{ 'event': 'COMMAND_DROPPED' , { 'event': 'COMMAND_DROPPED' ,
'data': { 'id': 'any', 'reason': 'CommandDropReason' } } 'data': { 'id': 'any', 'reason': 'CommandDropReason' } }
##
# @x-oob-test:
#
# Test OOB functionality. When sending this command with lock=true,
# it'll try to hang the dispatcher. When sending it with lock=false,
# it'll try to notify the locked thread to continue. Note: it should
# only be used by QMP test program rather than anything else.
#
# Since: 2.12
#
# Example:
#
# { "execute": "x-oob-test",
# "arguments": { "lock": true } }
##
{ 'command': 'x-oob-test', 'data' : { 'lock': 'bool' },
'allow-oob': true }
## ##
# @set-numa-node: # @set-numa-node:
# #

View File

@ -20,13 +20,14 @@
#include "qapi/qmp/qbool.h" #include "qapi/qmp/qbool.h"
#include "sysemu/sysemu.h" #include "sysemu/sysemu.h"
QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp) static QDict *qmp_dispatch_check_obj(const QObject *request, bool allow_oob,
Error **errp)
{ {
const char *exec_key = NULL;
const QDictEntry *ent; const QDictEntry *ent;
const char *arg_name; const char *arg_name;
const QObject *arg_obj; const QObject *arg_obj;
bool has_exec_key = false; QDict *dict;
QDict *dict = NULL;
dict = qobject_to(QDict, request); dict = qobject_to(QDict, request);
if (!dict) { if (!dict) {
@ -39,27 +40,25 @@ QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp)
arg_name = qdict_entry_key(ent); arg_name = qdict_entry_key(ent);
arg_obj = qdict_entry_value(ent); arg_obj = qdict_entry_value(ent);
if (!strcmp(arg_name, "execute")) { if (!strcmp(arg_name, "execute")
|| (!strcmp(arg_name, "exec-oob") && allow_oob)) {
if (qobject_type(arg_obj) != QTYPE_QSTRING) { if (qobject_type(arg_obj) != QTYPE_QSTRING) {
error_setg(errp, error_setg(errp, "QMP input member '%s' must be a string",
"QMP input member 'execute' must be a string"); arg_name);
return NULL; return NULL;
} }
has_exec_key = true; 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")) { } else if (!strcmp(arg_name, "arguments")) {
if (qobject_type(arg_obj) != QTYPE_QDICT) { if (qobject_type(arg_obj) != QTYPE_QDICT) {
error_setg(errp, error_setg(errp,
"QMP input member 'arguments' must be an object"); "QMP input member 'arguments' must be an object");
return NULL; return NULL;
} }
} else if (!strcmp(arg_name, "id")) {
continue;
} else if (!strcmp(arg_name, "control")) {
if (qobject_type(arg_obj) != QTYPE_QDICT) {
error_setg(errp,
"QMP input member 'control' must be a dict");
return NULL;
}
} else { } else {
error_setg(errp, "QMP input member '%s' is unexpected", error_setg(errp, "QMP input member '%s' is unexpected",
arg_name); arg_name);
@ -67,7 +66,7 @@ QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp)
} }
} }
if (!has_exec_key) { if (!exec_key) {
error_setg(errp, "QMP input lacks member 'execute'"); error_setg(errp, "QMP input lacks member 'execute'");
return NULL; return NULL;
} }
@ -76,20 +75,27 @@ QDict *qmp_dispatch_check_obj(const QObject *request, Error **errp)
} }
static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request, static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request,
Error **errp) bool allow_oob, Error **errp)
{ {
Error *local_err = NULL; Error *local_err = NULL;
bool oob;
const char *command; const char *command;
QDict *args, *dict; QDict *args, *dict;
QmpCommand *cmd; QmpCommand *cmd;
QObject *ret = NULL; QObject *ret = NULL;
dict = qmp_dispatch_check_obj(request, errp); dict = qmp_dispatch_check_obj(request, allow_oob, errp);
if (!dict) { if (!dict) {
return NULL; return NULL;
} }
command = qdict_get_str(dict, "execute"); 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); cmd = qmp_find_command(cmds, command);
if (cmd == NULL) { if (cmd == NULL) {
error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND, error_set(errp, ERROR_CLASS_COMMAND_NOT_FOUND,
@ -101,6 +107,11 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request,
command); command);
return NULL; return NULL;
} }
if (oob && !(cmd->options & QCO_ALLOW_OOB)) {
error_setg(errp, "The command %s does not support OOB",
command);
return false;
}
if (runstate_check(RUN_STATE_PRECONFIG) && if (runstate_check(RUN_STATE_PRECONFIG) &&
!(cmd->options & QCO_ALLOW_PRECONFIG)) { !(cmd->options & QCO_ALLOW_PRECONFIG)) {
@ -122,6 +133,7 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request,
} else if (cmd->options & QCO_NO_SUCCESS_RESP) { } else if (cmd->options & QCO_NO_SUCCESS_RESP) {
g_assert(!ret); g_assert(!ret);
} else if (!ret) { } else if (!ret) {
/* TODO turn into assertion */
ret = QOBJECT(qdict_new()); ret = QOBJECT(qdict_new());
} }
@ -130,53 +142,44 @@ static QObject *do_qmp_dispatch(QmpCommandList *cmds, QObject *request,
return ret; return ret;
} }
QObject *qmp_build_error_object(Error *err) QDict *qmp_error_response(Error *err)
{ {
return qobject_from_jsonf("{ 'class': %s, 'desc': %s }", QDict *rsp;
QapiErrorClass_str(error_get_class(err)),
error_get_pretty(err)); 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;
} }
/* /*
* Detect whether a request should be run out-of-band, by quickly * Does @qdict look like a command to be run out-of-band?
* peeking at whether we have: { "control": { "run-oob": true } }. By
* default commands are run in-band.
*/ */
bool qmp_is_oob(QDict *dict) bool qmp_is_oob(QDict *dict)
{ {
QBool *bool_obj; return qdict_haskey(dict, "exec-oob")
&& !qdict_haskey(dict, "execute");
dict = qdict_get_qdict(dict, "control");
if (!dict) {
return false;
}
bool_obj = qobject_to(QBool, qdict_get(dict, "run-oob"));
if (!bool_obj) {
return false;
}
return qbool_get_bool(bool_obj);
} }
QObject *qmp_dispatch(QmpCommandList *cmds, QObject *request) QDict *qmp_dispatch(QmpCommandList *cmds, QObject *request,
bool allow_oob)
{ {
Error *err = NULL; Error *err = NULL;
QObject *ret; QObject *ret;
QDict *rsp; QDict *rsp;
ret = do_qmp_dispatch(cmds, request, &err); ret = do_qmp_dispatch(cmds, request, allow_oob, &err);
rsp = qdict_new();
if (err) { if (err) {
qdict_put_obj(rsp, "error", qmp_build_error_object(err)); rsp = qmp_error_response(err);
error_free(err);
} else if (ret) { } else if (ret) {
rsp = qdict_new();
qdict_put_obj(rsp, "return", ret); qdict_put_obj(rsp, "return", ret);
} else { } else {
qobject_unref(rsp); /* Can only happen for commands with QCO_NO_SUCCESS_RESP */
return NULL; rsp = NULL;
} }
return QOBJECT(rsp); return rsp;
} }

View File

@ -34,15 +34,15 @@ QMPEventFuncEmit qmp_event_get_func_emit(void)
static void timestamp_put(QDict *qdict) static void timestamp_put(QDict *qdict)
{ {
int err; int err;
QObject *obj; QDict *ts;
qemu_timeval tv; qemu_timeval tv;
err = qemu_gettimeofday(&tv); err = qemu_gettimeofday(&tv);
/* Put -1 to indicate failure of getting host time */ /* Put -1 to indicate failure of getting host time */
obj = qobject_from_jsonf("{ 'seconds': %lld, 'microseconds': %lld }", ts = qdict_from_jsonf_nofail("{ 'seconds': %lld, 'microseconds': %lld }",
err < 0 ? -1LL : (long long)tv.tv_sec, err < 0 ? -1LL : (long long)tv.tv_sec,
err < 0 ? -1LL : (long long)tv.tv_usec); err < 0 ? -1LL : (long long)tv.tv_usec);
qdict_put_obj(qdict, "timestamp", obj); qdict_put(qdict, "timestamp", ts);
} }
/* /*

View File

@ -545,7 +545,7 @@ fail:
#endif #endif
} }
static int send_response(GAState *s, QObject *payload) static int send_response(GAState *s, QDict *payload)
{ {
const char *buf; const char *buf;
QString *payload_qstr, *response_qstr; QString *payload_qstr, *response_qstr;
@ -553,7 +553,7 @@ static int send_response(GAState *s, QObject *payload)
g_assert(payload && s->channel); g_assert(payload && s->channel);
payload_qstr = qobject_to_json(payload); payload_qstr = qobject_to_json(QOBJECT(payload));
if (!payload_qstr) { if (!payload_qstr) {
return -EINVAL; return -EINVAL;
} }
@ -581,12 +581,12 @@ static int send_response(GAState *s, QObject *payload)
static void process_command(GAState *s, QDict *req) static void process_command(GAState *s, QDict *req)
{ {
QObject *rsp = NULL; QDict *rsp;
int ret; int ret;
g_assert(req); g_assert(req);
g_debug("processing command"); g_debug("processing command");
rsp = qmp_dispatch(&ga_commands, QOBJECT(req)); rsp = qmp_dispatch(&ga_commands, QOBJECT(req), false);
if (rsp) { if (rsp) {
ret = send_response(s, rsp); ret = send_response(s, rsp);
if (ret < 0) { if (ret < 0) {
@ -610,15 +610,13 @@ static void process_event(JSONMessageParser *parser, GQueue *tokens)
qdict = qobject_to(QDict, json_parser_parse_err(tokens, NULL, &err)); qdict = qobject_to(QDict, json_parser_parse_err(tokens, NULL, &err));
if (err || !qdict) { if (err || !qdict) {
qobject_unref(qdict); qobject_unref(qdict);
qdict = qdict_new();
if (!err) { if (!err) {
g_warning("failed to parse event: unknown error"); g_warning("failed to parse event: unknown error");
error_setg(&err, QERR_JSON_PARSING); error_setg(&err, QERR_JSON_PARSING);
} else { } else {
g_warning("failed to parse event: %s", error_get_pretty(err)); g_warning("failed to parse event: %s", error_get_pretty(err));
} }
qdict_put_obj(qdict, "error", qmp_build_error_object(err)); qdict = qmp_error_response(err);
error_free(err);
} }
/* handle host->guest commands */ /* handle host->guest commands */
@ -627,13 +625,11 @@ static void process_event(JSONMessageParser *parser, GQueue *tokens)
} else { } else {
if (!qdict_haskey(qdict, "error")) { if (!qdict_haskey(qdict, "error")) {
qobject_unref(qdict); qobject_unref(qdict);
qdict = qdict_new();
g_warning("unrecognized payload format"); g_warning("unrecognized payload format");
error_setg(&err, QERR_UNSUPPORTED); error_setg(&err, QERR_UNSUPPORTED);
qdict_put_obj(qdict, "error", qmp_build_error_object(err)); qdict = qmp_error_response(err);
error_free(err);
} }
ret = send_response(s, QOBJECT(qdict)); ret = send_response(s, qdict);
if (ret < 0) { if (ret < 0) {
g_warning("error sending error response: %s", strerror(-ret)); g_warning("error sending error response: %s", strerror(-ret));
} }

16
qmp.c
View File

@ -737,19 +737,3 @@ MemoryInfo *qmp_query_memory_size_summary(Error **errp)
return mem_info; return mem_info;
} }
static QemuSemaphore x_oob_test_sem;
static void __attribute__((constructor)) x_oob_test_init(void)
{
qemu_sem_init(&x_oob_test_sem, 0);
}
void qmp_x_oob_test(bool lock, Error **errp)
{
if (lock) {
qemu_sem_wait(&x_oob_test_sem);
} else {
qemu_sem_post(&x_oob_test_sem);
}
}

View File

@ -59,10 +59,6 @@ QObject *qobject_from_json(const char *string, Error **errp)
return qobject_from_jsonv(string, NULL, errp); return qobject_from_jsonv(string, NULL, errp);
} }
/*
* IMPORTANT: This function aborts on error, thus it must not
* be used with untrusted arguments.
*/
QObject *qobject_from_jsonf(const char *string, ...) QObject *qobject_from_jsonf(const char *string, ...)
{ {
QObject *obj; QObject *obj;
@ -72,7 +68,24 @@ QObject *qobject_from_jsonf(const char *string, ...)
obj = qobject_from_jsonv(string, &ap, &error_abort); obj = qobject_from_jsonv(string, &ap, &error_abort);
va_end(ap); va_end(ap);
assert(obj != NULL); return obj;
}
/*
* Parse @string as JSON object with %-escapes interpolated.
* Abort on error. Do not use with untrusted @string.
* Return the resulting QDict. It is never null.
*/
QDict *qdict_from_jsonf_nofail(const char *string, ...)
{
QDict *obj;
va_list ap;
va_start(ap, string);
obj = qobject_to(QDict, qobject_from_jsonv(string, &ap, &error_abort));
va_end(ap);
assert(obj);
return obj; return obj;
} }

View File

@ -144,7 +144,7 @@
{ 'command': 'boxed-struct', 'boxed': true, 'data': 'UserDefZero' } { 'command': 'boxed-struct', 'boxed': true, 'data': 'UserDefZero' }
{ 'command': 'boxed-union', 'data': 'UserDefNativeListUnion', 'boxed': true } { 'command': 'boxed-union', 'data': 'UserDefNativeListUnion', 'boxed': true }
# Smoke test on Out-Of-Band and allow-preconfig-test # Smoke test on out-of-band and allow-preconfig-test
{ 'command': 'test-flags-command', 'allow-oob': true, 'allow-preconfig': true } { 'command': 'test-flags-command', 'allow-oob': true, 'allow-preconfig': true }
# For testing integer range flattening in opts-visitor. The following schema # For testing integer range flattening in opts-visitor. The following schema

View File

@ -135,16 +135,65 @@ static void test_qmp_protocol(void)
qtest_quit(qts); qtest_quit(qts);
} }
/* Tests for Out-Of-Band support. */ /* Out-of-band tests */
char tmpdir[] = "/tmp/qmp-test-XXXXXX";
char *fifo_name;
static void setup_blocking_cmd(void)
{
if (!mkdtemp(tmpdir)) {
g_error("mkdtemp: %s", strerror(errno));
}
fifo_name = g_strdup_printf("%s/fifo", tmpdir);
if (mkfifo(fifo_name, 0666)) {
g_error("mkfifo: %s", strerror(errno));
}
}
static void cleanup_blocking_cmd(void)
{
unlink(fifo_name);
rmdir(tmpdir);
}
static void send_cmd_that_blocks(QTestState *s, const char *id)
{
qtest_async_qmp(s, "{ 'execute': 'blockdev-add', 'id': %s,"
" 'arguments': {"
" 'driver': 'blkdebug', 'node-name': %s,"
" 'config': %s,"
" 'image': { 'driver': 'null-co' } } }",
id, id, fifo_name);
}
static void unblock_blocked_cmd(void)
{
int fd = open(fifo_name, O_WRONLY);
g_assert(fd >= 0);
close(fd);
}
static void send_oob_cmd_that_fails(QTestState *s, const char *id)
{
qtest_async_qmp(s, "{ 'exec-oob': 'migrate-pause', 'id': %s }", id);
}
static void recv_cmd_id(QTestState *s, const char *id)
{
QDict *resp = qtest_qmp_receive(s);
g_assert_cmpstr(qdict_get_try_str(resp, "id"), ==, id);
qobject_unref(resp);
}
static void test_qmp_oob(void) static void test_qmp_oob(void)
{ {
QTestState *qts; QTestState *qts;
QDict *resp, *q; QDict *resp, *q;
int acks = 0;
const QListEntry *entry; const QListEntry *entry;
QList *capabilities; QList *capabilities;
QString *qstr; QString *qstr;
const char *cmd_id;
qts = qtest_init_without_qmp_handshake(true, common_args); qts = qtest_init_without_qmp_handshake(true, common_args);
@ -179,43 +228,33 @@ static void test_qmp_oob(void)
* Try any command that does not support OOB but with OOB flag. We * Try any command that does not support OOB but with OOB flag. We
* should get failure. * should get failure.
*/ */
resp = qtest_qmp(qts, resp = qtest_qmp(qts, "{ 'exec-oob': 'query-cpus' }");
"{ 'execute': 'query-cpus',"
" 'control': { 'run-oob': true } }");
g_assert(qdict_haskey(resp, "error")); g_assert(qdict_haskey(resp, "error"));
qobject_unref(resp); qobject_unref(resp);
/* /* OOB command overtakes slow in-band command */
* First send the "x-oob-test" command with lock=true and setup_blocking_cmd();
* oob=false, it should hang the dispatcher and main thread; send_cmd_that_blocks(qts, "ib-blocks-1");
* later, we send another lock=false with oob=true to continue qtest_async_qmp(qts, "{ 'execute': 'query-name', 'id': 'ib-quick-1' }");
* that thread processing. Finally we should receive replies from send_oob_cmd_that_fails(qts, "oob-1");
* both commands. recv_cmd_id(qts, "oob-1");
*/ unblock_blocked_cmd();
qtest_async_qmp(qts, recv_cmd_id(qts, "ib-blocks-1");
"{ 'execute': 'x-oob-test'," recv_cmd_id(qts, "ib-quick-1");
" 'arguments': { 'lock': true }, "
" 'id': 'lock-cmd'}");
qtest_async_qmp(qts,
"{ 'execute': 'x-oob-test', "
" 'arguments': { 'lock': false }, "
" 'control': { 'run-oob': true }, "
" 'id': 'unlock-cmd' }");
/* Ignore all events. Wait for 2 acks */ /* Even malformed in-band command fails in-band */
while (acks < 2) { send_cmd_that_blocks(qts, "blocks-2");
resp = qtest_qmp_receive(qts); qtest_async_qmp(qts, "{ 'id': 'err-2' }");
cmd_id = qdict_get_str(resp, "id"); unblock_blocked_cmd();
if (!g_strcmp0(cmd_id, "lock-cmd") || recv_cmd_id(qts, "blocks-2");
!g_strcmp0(cmd_id, "unlock-cmd")) { recv_cmd_id(qts, "err-2");
acks++; cleanup_blocking_cmd();
}
qobject_unref(resp);
}
qtest_quit(qts); qtest_quit(qts);
} }
/* Query smoke tests */
static int query_error_class(const char *cmd) static int query_error_class(const char *cmd)
{ {
static struct { static struct {
@ -392,6 +431,8 @@ static void add_query_tests(QmpSchema *schema)
} }
} }
/* Preconfig tests */
static void test_qmp_preconfig(void) static void test_qmp_preconfig(void)
{ {
QDict *rsp, *ret; QDict *rsp, *ret;

View File

@ -227,6 +227,38 @@ static void test_qga_ping(gconstpointer fix)
qobject_unref(ret); qobject_unref(ret);
} }
static void test_qga_invalid_id(gconstpointer fix)
{
const TestFixture *fixture = fix;
QDict *ret, *error;
const char *class;
ret = qmp_fd(fixture->fd, "{'execute': 'guest-ping', 'id': 1}");
g_assert_nonnull(ret);
error = qdict_get_qdict(ret, "error");
class = qdict_get_try_str(error, "class");
g_assert_cmpstr(class, ==, "GenericError");
qobject_unref(ret);
}
static void test_qga_invalid_oob(gconstpointer fix)
{
const TestFixture *fixture = fix;
QDict *ret, *error;
const char *class;
ret = qmp_fd(fixture->fd, "{'exec-oob': 'guest-ping'}");
g_assert_nonnull(ret);
error = qdict_get_qdict(ret, "error");
class = qdict_get_try_str(error, "class");
g_assert_cmpstr(class, ==, "GenericError");
qobject_unref(ret);
}
static void test_qga_invalid_args(gconstpointer fix) static void test_qga_invalid_args(gconstpointer fix)
{ {
const TestFixture *fixture = fix; const TestFixture *fixture = fix;
@ -982,6 +1014,8 @@ int main(int argc, char **argv)
g_test_add_data_func("/qga/file-ops", &fix, test_qga_file_ops); g_test_add_data_func("/qga/file-ops", &fix, test_qga_file_ops);
g_test_add_data_func("/qga/file-write-read", &fix, test_qga_file_write_read); g_test_add_data_func("/qga/file-write-read", &fix, test_qga_file_write_read);
g_test_add_data_func("/qga/get-time", &fix, test_qga_get_time); g_test_add_data_func("/qga/get-time", &fix, test_qga_get_time);
g_test_add_data_func("/qga/invalid-id", &fix, test_qga_invalid_id);
g_test_add_data_func("/qga/invalid-oob", &fix, test_qga_invalid_oob);
g_test_add_data_func("/qga/invalid-cmd", &fix, test_qga_invalid_cmd); g_test_add_data_func("/qga/invalid-cmd", &fix, test_qga_invalid_cmd);
g_test_add_data_func("/qga/invalid-args", &fix, test_qga_invalid_args); g_test_add_data_func("/qga/invalid-args", &fix, test_qga_invalid_args);
g_test_add_data_func("/qga/fsfreeze-status", &fix, g_test_add_data_func("/qga/fsfreeze-status", &fix,

View File

@ -110,13 +110,13 @@ __org_qemu_x_Union1 *qmp___org_qemu_x_command(__org_qemu_x_EnumList *a,
static void test_dispatch_cmd(void) static void test_dispatch_cmd(void)
{ {
QDict *req = qdict_new(); QDict *req = qdict_new();
QObject *resp; QDict *resp;
qdict_put_str(req, "execute", "user_def_cmd"); qdict_put_str(req, "execute", "user_def_cmd");
resp = qmp_dispatch(&qmp_commands, QOBJECT(req)); resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
assert(resp != NULL); assert(resp != NULL);
assert(!qdict_haskey(qobject_to(QDict, resp), "error")); assert(!qdict_haskey(resp, "error"));
qobject_unref(resp); qobject_unref(resp);
qobject_unref(req); qobject_unref(req);
@ -127,13 +127,13 @@ static void test_dispatch_cmd_failure(void)
{ {
QDict *req = qdict_new(); QDict *req = qdict_new();
QDict *args = qdict_new(); QDict *args = qdict_new();
QObject *resp; QDict *resp;
qdict_put_str(req, "execute", "user_def_cmd2"); qdict_put_str(req, "execute", "user_def_cmd2");
resp = qmp_dispatch(&qmp_commands, QOBJECT(req)); resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
assert(resp != NULL); assert(resp != NULL);
assert(qdict_haskey(qobject_to(QDict, resp), "error")); assert(qdict_haskey(resp, "error"));
qobject_unref(resp); qobject_unref(resp);
qobject_unref(req); qobject_unref(req);
@ -145,9 +145,9 @@ static void test_dispatch_cmd_failure(void)
qdict_put_str(req, "execute", "user_def_cmd"); qdict_put_str(req, "execute", "user_def_cmd");
resp = qmp_dispatch(&qmp_commands, QOBJECT(req)); resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
assert(resp != NULL); assert(resp != NULL);
assert(qdict_haskey(qobject_to(QDict, resp), "error")); assert(qdict_haskey(resp, "error"));
qobject_unref(resp); qobject_unref(resp);
qobject_unref(req); qobject_unref(req);
@ -155,18 +155,15 @@ static void test_dispatch_cmd_failure(void)
static QObject *test_qmp_dispatch(QDict *req) static QObject *test_qmp_dispatch(QDict *req)
{ {
QObject *resp_obj;
QDict *resp; QDict *resp;
QObject *ret; QObject *ret;
resp_obj = qmp_dispatch(&qmp_commands, QOBJECT(req)); resp = qmp_dispatch(&qmp_commands, QOBJECT(req), false);
assert(resp_obj);
resp = qobject_to(QDict, resp_obj);
assert(resp && !qdict_haskey(resp, "error")); assert(resp && !qdict_haskey(resp, "error"));
ret = qdict_get(resp, "return"); ret = qdict_get(resp, "return");
assert(ret); assert(ret);
qobject_ref(ret); qobject_ref(ret);
qobject_unref(resp_obj); qobject_unref(resp);
return ret; return ret;
} }