]> git.ipfire.org Git - thirdparty/systemd.git/commit
vmspawn: implement io.systemd.MachineInstance.ReplaceStorage
authorChristian Brauner <brauner@kernel.org>
Fri, 8 May 2026 08:52:07 +0000 (10:52 +0200)
committerChristian Brauner <brauner@kernel.org>
Tue, 12 May 2026 20:54:07 +0000 (22:54 +0200)
commitb9155f789c280b8ca19390fb742fde8f1776ef77
treed329e29d8cac53bb8547fcd8fd79ef642e148d06
parent3063448b51b4fcfcdd51f015db9260292ce70fab
vmspawn: implement io.systemd.MachineInstance.ReplaceStorage

Wire up the runtime hot-swap Varlink method. The signature mirrors
AddStorage minus 'config': the device frontend (virtio-blk,
virtio-scsi, nvme, scsi-cd) doesn't change, only the backing file
behind it. Read-only/read-write may flip based on the new fd's
O_ACCMODE; scsi-cd is forced read-only to match the boot-time policy.

QMP sequence (entry: vmspawn_qmp_replace_block_device):

  add-fd                          → on_replace_observe_stage
  blockdev-add (new file)         → on_replace_blockdev_add_complete
  remove-fd (new fdset)           → on_replace_observe_stage
  blockdev-reopen (format)        → on_replace_blockdev_reopen_complete
                                    [commit + fire trailing del]
  blockdev-del (old file)         → on_replace_old_blockdev_del_complete

The reopen options must be a superset of every option that
qmp_build_blockdev_add_format() may emit, otherwise reopen rejects
'Cannot reset option X to default'. The 'file' field is a string
reference to the new file node — case 3 of the schema in
qemu/qapi/block-core.json:5034-5040 ("the current child is replaced
with that other node"). The format node's qmp_node_name is preserved
so the device frontend's drive=<X> binding does not move.

ReplaceCtx tracks the per-call state with a refcount mirroring the
add-stage drive-info pattern. On any pre-commit failure replace_fail
tears down whatever new-side state we created on the wire and replies
on drive->link via reply_qmp_error (disconnect → NotConnected). On
post-commit del failure we log a warning, leak the orphan, and reply
success — the swap itself succeeded and the leak resolves at VM exit.

file_generation is bumped before issuing blockdev-add so failed
attempts cannot collide on node-name when the user retries.

Errors:
  NoSuchStorage     - drive not in the registry
  StorageImmutable  - drive lacks QMP_DRIVE_REMOVABLE (boot-time)
  EBUSY             - add still pending or another replace/remove in flight
  NotConnected      - QMP transport disconnect during the chain
  EIO               - QEMU rejected blockdev-reopen

Also gates RemoveStorage on REPLACE_PENDING so a device_del cannot
race a mid-flight blockdev-reopen on the same drive.

Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
src/vmspawn/vmspawn-qmp.c
src/vmspawn/vmspawn-qmp.h
src/vmspawn/vmspawn-varlink.c