From: Christian Brauner Date: Thu, 16 Apr 2026 20:44:58 +0000 (+0200) Subject: vmspawn: add QmpClient userdata and VmspawnQmpBridge.setup_done flag X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=882049362d2967c40ba22956cd24b3d3b09e98df;p=thirdparty%2Fsystemd.git vmspawn: add QmpClient userdata and VmspawnQmpBridge.setup_done flag Add qmp_client_set_userdata()/qmp_client_get_userdata() accessors mirroring the sd_varlink API, and wire up the VmspawnQmpBridge as userdata on the QmpClient so that command callbacks can retrieve it. Add a setup_done flag to VmspawnQmpBridge, set by on_cont_complete() when the VM has booted and all boot-time device setup is finished. This lets command callbacks differentiate boot-time errors (fatal — exit event loop) from runtime errors (recoverable — log and continue). Signed-off-by: Christian Brauner (Amutable) --- diff --git a/src/shared/qmp-client.c b/src/shared/qmp-client.c index cc51259cd12..ad8f8bb24ac 100644 --- a/src/shared/qmp-client.c +++ b/src/shared/qmp-client.c @@ -49,6 +49,8 @@ struct QmpClient { QmpClientState state; sd_json_variant *current; /* most recently parsed message, pending dispatch */ + + void *userdata; }; static void qmp_slot_hash_func(const QmpSlot *p, struct siphash *state) { @@ -508,6 +510,21 @@ bool qmp_client_is_disconnected(QmpClient *c) { return c->state == QMP_CLIENT_DISCONNECTED; } +void* qmp_client_set_userdata(QmpClient *c, void *userdata) { + void *old; + + assert(c); + + old = c->userdata; + c->userdata = userdata; + return old; +} + +void* qmp_client_get_userdata(QmpClient *c) { + assert(c); + return c->userdata; +} + /* Map our state to the transport phase used for POLLIN / salvage / timeout decisions. */ static JsonStreamPhase qmp_client_phase(void *userdata) { QmpClient *c = ASSERT_PTR(userdata); diff --git a/src/shared/qmp-client.h b/src/shared/qmp-client.h index 7a477f7e4fa..8f784731280 100644 --- a/src/shared/qmp-client.h +++ b/src/shared/qmp-client.h @@ -54,6 +54,9 @@ bool qmp_client_is_idle(QmpClient *c); /* True iff the connection is dead. Stable terminal state — once set, it stays set. */ bool qmp_client_is_disconnected(QmpClient *c); +void* qmp_client_set_userdata(QmpClient *c, void *userdata); +void* qmp_client_get_userdata(QmpClient *c); + /* Async send. Returns 0 on send (callback will fire later), negative errno on failure. If * ret_slot is non-NULL, returns a reference to a QmpSlot which can be used to cancel the call * (by unreffing it before the reply arrives). */ diff --git a/src/vmspawn/vmspawn-qmp.c b/src/vmspawn/vmspawn-qmp.c index f69d2a5e7c6..f0f987082c6 100644 --- a/src/vmspawn/vmspawn-qmp.c +++ b/src/vmspawn/vmspawn-qmp.c @@ -1042,6 +1042,8 @@ static int on_cont_complete( int error, void *userdata) { + VmspawnQmpBridge *bridge = ASSERT_PTR(qmp_client_get_userdata(client)); + assert(client); if (error < 0) { @@ -1049,6 +1051,8 @@ static int on_cont_complete( return sd_event_exit(qmp_client_get_event(client), error); } + /* VM is running — all boot-time device setup has completed. */ + bridge->setup_done = true; return 0; } diff --git a/src/vmspawn/vmspawn-qmp.h b/src/vmspawn/vmspawn-qmp.h index 8f8c26fb03e..15949c40a5e 100644 --- a/src/vmspawn/vmspawn-qmp.h +++ b/src/vmspawn/vmspawn-qmp.h @@ -28,6 +28,7 @@ typedef struct VmspawnQmpBridge { QmpClient *qmp; Hashmap *pending_jobs; /* job_id (string, owned) -> PendingJob* */ VmspawnQmpFeatureFlags features; + bool setup_done; } VmspawnQmpBridge; VmspawnQmpBridge* vmspawn_qmp_bridge_free(VmspawnQmpBridge *b); diff --git a/src/vmspawn/vmspawn-varlink.c b/src/vmspawn/vmspawn-varlink.c index 4a11b6fd4e1..98d3fc73e91 100644 --- a/src/vmspawn/vmspawn-varlink.c +++ b/src/vmspawn/vmspawn-varlink.c @@ -387,6 +387,7 @@ int vmspawn_varlink_setup( ctx->bridge = bridge; qmp_client_bind_event(ctx->bridge->qmp, on_qmp_event, ctx); qmp_client_bind_disconnect(ctx->bridge->qmp, on_qmp_disconnect, ctx); + qmp_client_set_userdata(ctx->bridge->qmp, ctx->bridge); log_debug("Varlink control server listening on %s", listen_address);