Add QMP commands for monitor lifecycle management:
- monitor-add: Create a QMP monitor on an existing chardev at runtime.
The chardev must exist and not already have a frontend attached
(enforced by qemu_chr_fe_init()). The new monitor starts in
capability negotiation mode.
- monitor-remove: Remove a QMP monitor. The removal sequence is:
1. Mark dead and remove from mon_list under monitor_lock. This
must happen before disconnecting chardev handlers to prevent
event broadcast from calling monitor_flush_locked() after the
gcontext reset, which would create an out_watch on the wrong
GMainContext (see monitor_cancel_out_watch()).
2. Cancel any pending out_watch while gcontext still points to the
correct context.
3. Disconnect chardev handlers. For self-removal, preserve
gcontext by passing the iothread GMainContext and keep the
chardev connection open so the command response can be flushed.
For normal removal, pass context=NULL and close the connection.
4. Drain pending requests from any in-flight monitor_qmp_read().
5. For self-removal, defer destruction to the dispatcher (the
response must be flushed first). Otherwise destroy immediately
via monitor_qmp_destroy().
- query-monitors: Introspect all active monitors with their id, mode,
and chardev name.
The motivating use case is systemd-vmspawn: when an external client
requests raw QMP access, vmspawn can create an independent QMP session
on demand rather than pre-allocating spare monitors at launch or
building an id-rewriting proxy.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
monitor/qmp-cmds-control.c | 105 +++++++++++++++++++++++++++++++++++++++++++++
qapi/control.json | 97 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 202 insertions(+)
diff --git a/monitor/qmp-cmds-control.c b/monitor/qmp-cmds-control.c
index 150ca9f5cb..33d02c8fb6 100644
--- a/monitor/qmp-cmds-control.c
+++ b/monitor/qmp-cmds-control.c
@@ -219,3 +219,108 @@ SchemaInfoList *qmp_query_qmp_schema(Error **errp)
}
return schema;
}
+
+void qmp_monitor_add(const char *id, const char *chardev,
+ bool has_pretty, bool pretty, Error **errp)
+{
+ Chardev *chr;
+
+ /* Reject duplicate monitor id */
+ if (monitor_find_by_id(id)) {
+ error_setg(errp, "monitor '%s' already exists", id);
+ return;
+ }
+
+ chr = qemu_chr_find(chardev);
+ if (!chr) {
+ error_setg(errp, "chardev '%s' not found", chardev);
+ return;
+ }
+
+ monitor_init_qmp(chr, has_pretty && pretty, id, errp);
+}
+
+void qmp_monitor_remove(const char *id, Error **errp)
+{
+ Monitor *mon;
+ MonitorQMP *qmp_mon;
+ bool self_remove;
+
+ mon = monitor_find_by_id(id);
+ if (!mon) {
+ error_setg(errp, "monitor '%s' not found", id);
+ return;
+ }
+
+ if (!mon->is_qmp) {
+ error_setg(errp, "monitor '%s' is not a QMP monitor", id);
+ return;
+ }
+
+ qmp_mon = container_of(mon, MonitorQMP, common);
+
+ if (qatomic_read(&qmp_mon->setup_pending)) {
+ error_setg(errp, "monitor '%s' is still initializing", id);
+ return;
+ }
+
+ self_remove = monitor_qmp_dispatcher_is_servicing(qmp_mon);
+
+ /* Remove from mon_list before chardev disconnect. */
+ WITH_QEMU_LOCK_GUARD(&monitor_lock) {
+ mon->dead = true;
+ QTAILQ_REMOVE(&mon_list, mon, entry);
+ }
+
+ /* Cancel out_watch while gcontext still points to the right ctx. */
+ WITH_QEMU_LOCK_GUARD(&mon->mon_lock) {
+ monitor_cancel_out_watch(mon);
+ }
+
+ /*
+ * Clear chardev handlers. Self-removal preserves gcontext so the
+ * response flush creates out_watch on the correct GMainContext.
+ */
+ if (self_remove) {
+ GMainContext *ctx = mon->use_io_thread
+ ? iothread_get_g_main_context(mon_iothread) : NULL;
+
+ qemu_chr_fe_set_handlers(&mon->chr, NULL, NULL, NULL, NULL,
+ NULL, ctx, false);
+ } else {
+ qemu_chr_fe_set_handlers(&mon->chr, NULL, NULL, NULL, NULL,
+ NULL, NULL, true);
+ }
+
+ /* Drain requests from any in-flight monitor_qmp_read(). */
+ monitor_qmp_drain_queue(qmp_mon);
+
+ /* Self-removal: defer destruction so the response is flushed first. */
+ if (self_remove) {
+ return;
+ }
+
+ monitor_qmp_destroy(qmp_mon);
+ monitor_fdsets_cleanup();
+}
+
+MonitorInfoList *qmp_query_monitors(Error **errp)
+{
+ MonitorInfoList *list = NULL;
+ Monitor *mon;
+
+ WITH_QEMU_LOCK_GUARD(&monitor_lock) {
+ QTAILQ_FOREACH(mon, &mon_list, entry) {
+ MonitorInfo *info = g_new0(MonitorInfo, 1);
+ Chardev *chr = qemu_chr_fe_get_driver(&mon->chr);
+
+ info->id = g_strdup(mon->id);
+ info->mode = mon->is_qmp ? MONITOR_MODE_CONTROL
+ : MONITOR_MODE_READLINE;
+ info->chardev = g_strdup(chr ? chr->label : "unknown");
+ QAPI_LIST_PREPEND(list, info);
+ }
+ }
+
+ return list;
+}
diff --git a/qapi/control.json b/qapi/control.json
index 9a5302193d..eb4a30b59e 100644
--- a/qapi/control.json
+++ b/qapi/control.json
@@ -211,3 +211,100 @@
'*pretty': 'bool',
'chardev': 'str'
} }
+
+##
+# @monitor-add:
+#
+# Add a QMP monitor on an existing character device backend.
+#
+# The chardev must already exist (created via chardev-add or CLI).
+# The monitor begins in capability negotiation mode -- the first
+# client to connect receives the QMP greeting.
+#
+# @id: Monitor identifier, must be unique among monitors
+#
+# @chardev: Name of the character device backend to attach to
+#
+# @pretty: Enable pretty-printing of QMP responses (default: false)
+#
+# Errors:
+# - If @id is already in use
+# - If @chardev does not exist
+#
+# Since: 11.1
+#
+# .. qmp-example::
+#
+# -> { "execute": "monitor-add",
+# "arguments": { "id": "extra-qmp",
+# "chardev": "qmp-extra" } }
+# <- { "return": {} }
+##
+{ 'command': 'monitor-add',
+ 'data': { 'id': 'str',
+ 'chardev': 'str',
+ '*pretty': 'bool' } }
+
+##
+# @monitor-remove:
+#
+# Remove a QMP monitor.
+#
+# The underlying chardev is NOT removed -- use chardev-remove
+# separately if desired.
+#
+# If a client is currently connected, the connection is dropped.
+#
+# @id: Monitor identifier
+#
+# Errors:
+# - If @id does not exist
+# - If the monitor is not a QMP monitor
+#
+# Since: 11.1
+#
+# .. qmp-example::
+#
+# -> { "execute": "monitor-remove",
+# "arguments": { "id": "extra-qmp" } }
+# <- { "return": {} }
+##
+{ 'command': 'monitor-remove',
+ 'data': { 'id': 'str' } }
+
+##
+# @MonitorInfo:
+#
+# Information about a QMP/HMP monitor.
+#
+# @id: Monitor identifier. Always present for QMP monitors (auto-
+# generated if not explicitly set). Absent for HMP monitors.
+#
+# @mode: Monitor mode (readline or control)
+#
+# @chardev: Name of the attached character device
+#
+# Since: 11.1
+##
+{ 'struct': 'MonitorInfo',
+ 'data': { '*id': 'str',
+ 'mode': 'MonitorMode',
+ 'chardev': 'str' } }
+
+##
+# @query-monitors:
+#
+# Return information about all active monitors.
+#
+# Returns: a list of @MonitorInfo for each active monitor
+#
+# Since: 11.1
+#
+# .. qmp-example::
+#
+# -> { "execute": "query-monitors" }
+# <- { "return": [ { "id": "mon0", "mode": "control",
+# "chardev": "compat_monitor0" } ] }
+##
+{ 'command': 'query-monitors',
+ 'returns': ['MonitorInfo'] }
--
2.47.3
© 2016 - 2026 Red Hat, Inc.