return vmspawn_qmp_add_block_device(bridge, drive);
}
+/* device_del completion is just QEMU acking the request; teardown happens
+ * in vmspawn_qmp_dispatch_device_deleted() once the guest acks the eject. */
+static int on_remove_device_del_complete(
+ QmpClient *client,
+ sd_json_variant *result,
+ const char *error_desc,
+ int error,
+ void *userdata) {
+
+ _cleanup_(drive_info_unrefp) DriveInfo *drive = ASSERT_PTR(userdata);
+ _cleanup_(sd_varlink_unrefp) sd_varlink *link = TAKE_PTR(drive->link);
+
+ assert(client);
+ assert(link);
+
+ if (error < 0) {
+ /* device_del rejected: clear the pending bit so the caller can retry. */
+ drive->state &= ~BLOCK_DEVICE_STATE_REMOVE_PENDING;
+
+ return reply_qmp_error(link, error_desc, error);
+ }
+
+ return sd_varlink_reply(link, NULL);
+}
+
+int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, const char *id) {
+ int r;
+
+ assert(bridge);
+ assert(link);
+ assert(id);
+
+ DriveInfo *drive = hashmap_get(bridge->block_devices, id);
+ if (!drive)
+ return reply_qmp_error(link, "Unknown block device id", -ENOENT);
+ if (!FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_BLOCKDEV_ADDED))
+ return reply_qmp_error(link, "Block device add pending", -EBUSY);
+ if (FLAGS_SET(drive->state, BLOCK_DEVICE_STATE_REMOVE_PENDING))
+ return reply_qmp_error(link, "Block device removal pending", -EBUSY);
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *args = NULL;
+ r = sd_json_buildo(&args, SD_JSON_BUILD_PAIR_STRING("id", drive->qmp_device_id));
+ if (r < 0)
+ return sd_varlink_error_errno(link, r);
+
+ assert(!drive->link);
+ drive->link = sd_varlink_ref(link);
+ drive->state |= BLOCK_DEVICE_STATE_REMOVE_PENDING;
+
+ r = qmp_client_invoke(bridge->qmp, /* ret_slot= */ NULL, "device_del", QMP_CLIENT_ARGS(args),
+ on_remove_device_del_complete, drive_info_ref(drive));
+ if (r < 0) {
+ drive->link = sd_varlink_unref(drive->link);
+ drive->state &= ~BLOCK_DEVICE_STATE_REMOVE_PENDING;
+ drive_info_unref(drive);
+ return sd_varlink_error_errno(link, r);
+ }
+ return 0;
+}
+
+/* DEVICE_DELETED arrives once the guest has acked the eject; only then is it
+ * safe to drop the blockdev node and release the registry slot (and PCIe port). */
+int vmspawn_qmp_dispatch_device_deleted(VmspawnQmpBridge *bridge, sd_json_variant *data) {
+ assert(bridge);
+
+ if (!data)
+ return 0;
+
+ const char *qmp_device_id = sd_json_variant_string(sd_json_variant_by_key(data, "device"));
+ if (!qmp_device_id)
+ return 0;
+
+ DriveInfo *drive = hashmap_get(bridge->block_devices_by_qmp_id, qmp_device_id);
+ if (!drive)
+ return 0;
+
+ vmspawn_qmp_block_device_teardown(bridge->qmp, drive->qmp_node_name, drive->state);
+
+ assert_se(bridge_unregister_drive(bridge, drive) == drive);
+ drive_info_unref(drive);
+ return 0;
+}
+
int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *netdev_args = NULL, *device_args = NULL;
bool tap_by_fd;
typedef enum BlockDeviceStateFlags {
BLOCK_DEVICE_STATE_BLOCKDEV_ADDED = 1u << 0,
BLOCK_DEVICE_STATE_ADD_FAILED = 1u << 1, /* first error fired; suppress cascades */
+ BLOCK_DEVICE_STATE_REMOVE_PENDING = 1u << 2, /* device_del in flight; reject concurrent removes */
} BlockDeviceStateFlags;
/* Ref-counted; each of the four add-stage QMP slots holds one ref.
int vmspawn_qmp_setup_network(VmspawnQmpBridge *bridge, NetworkInfo *network);
int vmspawn_qmp_setup_virtiofs(VmspawnQmpBridge *bridge, const VirtiofsInfos *virtiofs);
int vmspawn_qmp_setup_vsock(VmspawnQmpBridge *bridge, VsockInfo *vsock);
+int vmspawn_qmp_remove_block_device(VmspawnQmpBridge *bridge, sd_varlink *link, const char *id);
+int vmspawn_qmp_dispatch_device_deleted(VmspawnQmpBridge *bridge, sd_json_variant *data);
#include "varlink-io.systemd.QemuMachineInstance.h"
#include "varlink-io.systemd.VirtualMachineInstance.h"
#include "varlink-util.h"
+#include "vmspawn-qmp.h"
#include "vmspawn-varlink.h"
DEFINE_PRIVATE_HASH_OPS_FULL(
if (streq(event, "JOB_STATUS_CHANGE"))
return dispatch_pending_job(ctx->bridge, data);
+ /* Notification still fans out below. */
+ if (streq(event, "DEVICE_DELETED"))
+ (void) vmspawn_qmp_dispatch_device_deleted(ctx->bridge, data);
+
return notify_event_subscribers(ctx, event, data);
}