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:
commit
4fd1cbaf14
@ -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,
|
||||||
|
@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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
519
monitor.c
@ -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 = {
|
||||||
|
@ -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:
|
||||||
#
|
#
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
18
qga/main.c
18
qga/main.c
@ -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
16
qmp.c
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
105
tests/qmp-test.c
105
tests/qmp-test.c
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user