[PATCH v3 3/5] qapi: add monitor-add, monitor-remove, query-monitors commands

Christian Brauner posted 5 patches 4 days, 11 hours ago
Maintainers: Markus Armbruster <armbru@redhat.com>, "Dr. David Alan Gilbert" <dave@treblig.org>, Eric Blake <eblake@redhat.com>, Thomas Huth <th.huth+qemu@posteo.eu>, "Philippe Mathieu-Daudé" <philmd@linaro.org>, "Daniel P. Berrangé" <berrange@redhat.com>, Fabiano Rosas <farosas@suse.de>, Laurent Vivier <lvivier@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>
There is a newer version of this series
[PATCH v3 3/5] qapi: add monitor-add, monitor-remove, query-monitors commands
Posted by Christian Brauner 4 days, 11 hours ago
Add QMP commands for dynamic 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 dynamically-added monitor.  CLI-created
  monitors cannot be removed.  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,
  chardev name, and whether they were dynamically added.

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 | 111 +++++++++++++++++++++++++++++++++++++++++++++
 qapi/control.json          | 104 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 215 insertions(+)

diff --git a/monitor/qmp-cmds-control.c b/monitor/qmp-cmds-control.c
index 150ca9f5cb..c0a3e03aa5 100644
--- a/monitor/qmp-cmds-control.c
+++ b/monitor/qmp-cmds-control.c
@@ -219,3 +219,114 @@ 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, true, 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;
+    }
+
+    if (!mon->dynamic) {
+        error_setg(errp, "monitor '%s' was not dynamically added", 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");
+            info->dynamic = mon->dynamic;
+            QAPI_LIST_PREPEND(list, info);
+        }
+    }
+
+    return list;
+}
diff --git a/qapi/control.json b/qapi/control.json
index 9a5302193d..fd58b57c31 100644
--- a/qapi/control.json
+++ b/qapi/control.json
@@ -211,3 +211,107 @@
       '*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:
+#     - GenericError if @id is already in use
+#     - GenericError if @chardev does not exist
+#
+# Since: 11.0
+#
+# .. 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 dynamically added QMP monitor.
+#
+# The monitor must have been created via monitor-add.  Monitors
+# created via CLI options (-mon, -qmp) cannot be removed.  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 as passed to monitor-add
+#
+# Errors:
+#     - GenericError if @id does not exist
+#     - GenericError if the monitor was not dynamically added
+#
+# Since: 11.0
+#
+# .. 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 (absent for CLI-created monitors without
+#     an explicit id)
+#
+# @mode: Monitor mode (readline or control)
+#
+# @chardev: Name of the attached character device
+#
+# @dynamic: true if created via monitor-add (removable), false if
+#     created via CLI
+#
+# Since: 11.0
+##
+{ 'struct': 'MonitorInfo',
+  'data': { '*id': 'str',
+            'mode': 'MonitorMode',
+            'chardev': 'str',
+            'dynamic': 'bool' } }
+
+##
+# @query-monitors:
+#
+# Return information about all active monitors.
+#
+# Returns: a list of @MonitorInfo for each active monitor
+#
+# Since: 11.0
+#
+# .. qmp-example::
+#
+#     -> { "execute": "query-monitors" }
+#     <- { "return": [ { "id": "mon0", "mode": "control",
+#                         "chardev": "compat_monitor0",
+#                         "dynamic": false } ] }
+##
+{ 'command': 'query-monitors',
+  'returns': ['MonitorInfo'] }

-- 
2.47.3
Re: [PATCH v3 3/5] qapi: add monitor-add, monitor-remove, query-monitors commands
Posted by Daniel P. Berrangé 4 days, 7 hours ago
On Tue, Apr 07, 2026 at 09:32:47AM +0200, Christian Brauner wrote:
> Add QMP commands for dynamic 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 dynamically-added monitor.  CLI-created
>   monitors cannot be removed.  The removal sequence is:

Why should we block removal of statically created monitors ? Our
normal approach is that the creation of objects via the command
line & QMP be indistinguishable.


> diff --git a/qapi/control.json b/qapi/control.json
> index 9a5302193d..fd58b57c31 100644
> --- a/qapi/control.json
> +++ b/qapi/control.json
> @@ -211,3 +211,107 @@
>        '*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:
> +#     - GenericError if @id is already in use
> +#     - GenericError if @chardev does not exist
> +#
> +# Since: 11.0

We're just about to release 11.0, so 11.1 is the earliest
opportunity to merge anything now.

> +#
> +# .. 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 dynamically added QMP monitor.
> +#
> +# The monitor must have been created via monitor-add.  Monitors
> +# created via CLI options (-mon, -qmp) cannot be removed.  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 as passed to monitor-add
> +#
> +# Errors:
> +#     - GenericError if @id does not exist
> +#     - GenericError if the monitor was not dynamically added

I don't think we need to include "GenericError" in the description.
We've abandoned the idea of error classes in general, so everything
is GenericError  except for a bit of historical cruft.

> +#
> +# Since: 11.0
> +#
> +# .. 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 (absent for CLI-created monitors without
> +#     an explicit id)

We can't make 'id' mandatory for the existing arg without breaking
compat, but IMHO we should allocate an auto-generated 'id' if the
user omits it.

> +#
> +# @mode: Monitor mode (readline or control)
> +#
> +# @chardev: Name of the attached character device
> +#
> +# @dynamic: true if created via monitor-add (removable), false if
> +#     created via CLI
> +#
> +# Since: 11.0
> +##
> +{ 'struct': 'MonitorInfo',
> +  'data': { '*id': 'str',
> +            'mode': 'MonitorMode',
> +            'chardev': 'str',
> +            'dynamic': 'bool' } }
> +
> +##
> +# @query-monitors:
> +#
> +# Return information about all active monitors.
> +#
> +# Returns: a list of @MonitorInfo for each active monitor
> +#
> +# Since: 11.0
> +#
> +# .. qmp-example::
> +#
> +#     -> { "execute": "query-monitors" }
> +#     <- { "return": [ { "id": "mon0", "mode": "control",
> +#                         "chardev": "compat_monitor0",
> +#                         "dynamic": false } ] }
> +##
> +{ 'command': 'query-monitors',
> +  'returns': ['MonitorInfo'] }

With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|
Re: [PATCH v3 3/5] qapi: add monitor-add, monitor-remove, query-monitors commands
Posted by Christian Brauner 3 days, 21 hours ago
On Tue, Apr 07, 2026 at 11:45:53AM +0100, Daniel P. Berrangé wrote:
> On Tue, Apr 07, 2026 at 09:32:47AM +0200, Christian Brauner wrote:
> > Add QMP commands for dynamic 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 dynamically-added monitor.  CLI-created
> >   monitors cannot be removed.  The removal sequence is:
> 
> Why should we block removal of statically created monitors ? Our
> normal approach is that the creation of objects via the command
> line & QMP be indistinguishable.

Sounds fine to me.

> 
> 
> > diff --git a/qapi/control.json b/qapi/control.json
> > index 9a5302193d..fd58b57c31 100644
> > --- a/qapi/control.json
> > +++ b/qapi/control.json
> > @@ -211,3 +211,107 @@
> >        '*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:
> > +#     - GenericError if @id is already in use
> > +#     - GenericError if @chardev does not exist
> > +#
> > +# Since: 11.0
> 
> We're just about to release 11.0, so 11.1 is the earliest
> opportunity to merge anything now.

I mostly copy-pasted this together. I honestly had no idea what the
current release cycle is.

> 
> > +#
> > +# .. 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 dynamically added QMP monitor.
> > +#
> > +# The monitor must have been created via monitor-add.  Monitors
> > +# created via CLI options (-mon, -qmp) cannot be removed.  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 as passed to monitor-add
> > +#
> > +# Errors:
> > +#     - GenericError if @id does not exist
> > +#     - GenericError if the monitor was not dynamically added
> 
> I don't think we need to include "GenericError" in the description.
> We've abandoned the idea of error classes in general, so everything
> is GenericError  except for a bit of historical cruft.

Ok.

> 
> > +#
> > +# Since: 11.0
> > +#
> > +# .. 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 (absent for CLI-created monitors without
> > +#     an explicit id)
> 
> We can't make 'id' mandatory for the existing arg without breaking
> compat, but IMHO we should allocate an auto-generated 'id' if the
> user omits it.

Ok.