qmp-client: eagerly enqueue qmp_capabilities on connect, drop the handshake state machine
QEMU's QMP greeting is an unsolicited, informational, server-initiated advertisement
— it doesn't gate commands. The server accepts (and pipelines) commands the instant
the socket is open. We were using it as a trigger to build qmp_capabilities and
blocking callers via qmp_client_ensure_running() until the reply came back.
Build qmp_capabilities inside qmp_client_connect_fd() instead and let the JsonStream
output queue preserve FIFO ordering between it and any user command a subsequent
invoke() enqueues. That satisfies QEMU's only ordering requirement (cap must precede
other commands) without any blocking in the send path.
Fallout:
- Collapse QmpClientState to {RUNNING, DISCONNECTED}. The three HANDSHAKE_* states
and the QMP_CLIENT_STATE_IS_HANDSHAKE() macro go away.
- Drop qmp_client_dispatch_handshake(); fold greeting-drop into dispatch_reply as
a one-line shape check.
- Drop qmp_client_ensure_running() and its qmp_client_send() call site. send()
now only refuses when state == DISCONNECTED.
- The qmp_capabilities reply lands on an ordinary slot whose callback logs a
protocol-level error and force-disconnects if cap negotiation failed, matching
the old EPROTO behaviour at the same observable boundary.
- qmp_client_phase() no longer special-cases the old handshake states; it maps
directly to READING / AWAITING_REPLY based on whether slots are in flight.
Test updates:
- qmp_client_first_invoke_with_fd → qmp_client_invoke_with_fd. The scenario it
was pinned to (push_fd+invoke staging order) has been structurally impossible
since the QmpClientArgs rework in
8ad4adcb6f; eager-cap removes it a second
way. The test now covers end-to-end fd-passing on the first invoke, accepting
either recv carrying the single SCM_RIGHTS fd (AF_UNIX absorbs non-scm skbs
forward into the next scm-bearing skb's recv, so the fd may surface with cap
or add-fd depending on kernel scheduling — QEMU's FIFO fd queue handles either).
- qmp_client_invoke_failure_closes_fds restructured around the new invariant:
invoke no longer blocks and no longer returns ENOTCONN for a dead peer, so
the fd-leak assertion moves to "still open while the JsonStream queue owns it,
closed on client teardown" and the nested block is flattened into an explicit
qmp_client_unref().