From: Christian Brauner Date: Fri, 1 May 2026 11:34:54 +0000 (+0200) Subject: vmspawn: add vmspawn-bind-volume glue X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a34ad7f7b01ba39f2a7fd4a3b1ba6b1eff49c6f2;p=thirdparty%2Fsystemd.git vmspawn: add vmspawn-bind-volume glue This is vmspawn's per-backend code for the StorageProvider integration. Other backends (future systemd-nspawn, future service-manager BindVolume=) consume the same shared parser and Acquire helper but each provides its own attach/detach glue; this is vmspawn's. - disk_type_from_bind_volume_config() turns the opaque BindVolume 'config' field (e.g. "scsi-cd") into a DiskType. Empty defaults to virtio-blk to match the --bind-volume CLI grammar. - vmspawn_bind_volume_acquire() takes a parsed BindVolume, calls storage_acquire_volume() for the fd, and builds a DriveInfo ready for vmspawn_qmp_setup_drives() (boot) or vmspawn_qmp_add_block_device() (hotplug). Rejects directory-typed volumes (vmspawn block devices need a regular file or a host block device). - vmspawn_bind_volume_attach_fd() is the runtime path: takes a fd that was already pushed across by an AddStorage caller plus the name+config it specified, builds the DriveInfo with QMP_DRIVE_REMOVABLE set and a varlink link, and dispatches to vmspawn_qmp_add_block_device(). Reply is delivered asynchronously by the existing on_add_device_add_complete() callback. - vmspawn_bind_volume_prepare_boot() is a thin loop the boot-time path uses to populate DriveInfos. Signed-off-by: Christian Brauner (Amutable) --- diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build index 6d08755fedf..6bc31c77c69 100644 --- a/src/vmspawn/meson.build +++ b/src/vmspawn/meson.build @@ -6,6 +6,7 @@ endif vmspawn_sources = files( 'vmspawn.c', + 'vmspawn-bind-volume.c', 'vmspawn-qemu-config.c', 'vmspawn-qmp.c', 'vmspawn-varlink.c', diff --git a/src/vmspawn/vmspawn-bind-volume.c b/src/vmspawn/vmspawn-bind-volume.c new file mode 100644 index 00000000000..d67fc61de02 --- /dev/null +++ b/src/vmspawn/vmspawn-bind-volume.c @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-varlink.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "log.h" +#include "runtime-scope.h" +#include "stat-util.h" +#include "storage-util.h" +#include "string-util.h" +#include "vmspawn-bind-volume.h" +#include "vmspawn-qmp.h" + +DiskType disk_type_from_bind_volume_config(const char *config) { + if (isempty(config)) + return DISK_TYPE_VIRTIO_BLK; + return disk_type_from_string(config); +} + +int vmspawn_bind_volume_acquire( + RuntimeScope scope, + const BindVolume *v, + bool removable, + sd_varlink *link, + DriveInfo **ret, + char **reterr_error_id) { + + _cleanup_(storage_acquire_reply_done) StorageAcquireReply reply = STORAGE_ACQUIRE_REPLY_INIT; + _cleanup_(drive_info_unrefp) DriveInfo *d = NULL; + _cleanup_free_ char *err = NULL; + int r; + + assert(v); + assert(ret); + + DiskType dt = disk_type_from_bind_volume_config(v->config); + if (dt < 0) { + r = dt; + goto fail; + } + + r = storage_acquire_volume(scope, v, /* allow_interactive_auth= */ false, &err, &reply); + if (r < 0) + goto fail; + + if (reply.type == VOLUME_DIR) { + r = log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Directory volumes are not supported for vmspawn block devices."); + goto fail; + } + + struct stat st; + if (fstat(reply.fd, &st) < 0) { + r = -errno; + goto fail; + } + r = stat_verify_regular_or_block(&st); + if (r < 0) + goto fail; + + d = drive_info_new(); + if (!d) { + r = -ENOMEM; + goto fail; + } + + d->id = strjoin(v->provider, ":", v->volume); + d->disk_driver = strdup(ASSERT_PTR(qemu_device_driver_to_string(dt))); + d->format = strdup("raw"); + d->path = strdup(v->volume); + if (!d->id || !d->disk_driver || !d->format || !d->path) { + r = -ENOMEM; + goto fail; + } + + d->disk_type = dt; + d->fd = TAKE_FD(reply.fd); + + if (reply.type == VOLUME_BLK || S_ISBLK(st.st_mode)) + d->flags |= QMP_DRIVE_BLOCK_DEVICE; + if (reply.read_only > 0 || dt == DISK_TYPE_VIRTIO_SCSI_CDROM) + d->flags |= QMP_DRIVE_READ_ONLY; + if (removable) + d->flags |= QMP_DRIVE_REMOVABLE; + d->link = sd_varlink_ref(link); + + *ret = TAKE_PTR(d); + return 0; + +fail: + if (reterr_error_id) + *reterr_error_id = TAKE_PTR(err); + return r; +} + +/* Takes ownership of fd unconditionally — it is closed on every error path too. */ +int vmspawn_bind_volume_attach_fd( + VmspawnQmpBridge *bridge, + sd_varlink *link, + int fd, + const char *name, + const char *config) { + + _cleanup_close_ int owned_fd = fd; + int r; + + assert(bridge); + assert(link); + assert(fd >= 0); + assert(name); + + DiskType dt = disk_type_from_bind_volume_config(config); + if (dt < 0) + return dt; + + struct stat st; + if (fstat(owned_fd, &st) < 0) + return -errno; + r = stat_verify_regular_or_block(&st); + if (r < 0) + return r; + + _cleanup_(drive_info_unrefp) DriveInfo *d = drive_info_new(); + if (!d) + return -ENOMEM; + + d->id = strdup(name); + d->disk_driver = strdup(ASSERT_PTR(qemu_device_driver_to_string(dt))); + d->format = strdup("raw"); + d->path = strdup(name); + if (!d->id || !d->disk_driver || !d->format || !d->path) + return -ENOMEM; + + int oflags = fcntl(owned_fd, F_GETFL); + if (oflags < 0) + return -errno; + + d->disk_type = dt; + d->fd = TAKE_FD(owned_fd); + if (S_ISBLK(st.st_mode)) + d->flags |= QMP_DRIVE_BLOCK_DEVICE; + if (dt == DISK_TYPE_VIRTIO_SCSI_CDROM || (oflags & O_ACCMODE_STRICT) == O_RDONLY) + d->flags |= QMP_DRIVE_READ_ONLY; + d->flags |= QMP_DRIVE_REMOVABLE; + d->link = sd_varlink_ref(link); + + return vmspawn_qmp_add_block_device(bridge, TAKE_PTR(d)); +} + +void bind_volumes_done(BindVolumes *bv) { + assert(bv); + FOREACH_ARRAY(v, bv->items, bv->n_items) + bind_volume_free(*v); + bv->items = mfree(bv->items); + bv->n_items = 0; +} + +int vmspawn_bind_volume_prepare_boot( + RuntimeScope scope, + const BindVolumes *bv, + DriveInfos *drives) { + + int r; + + assert(bv); + assert(drives); + + if (bv->n_items == 0) + return 0; + + if (!GREEDY_REALLOC(drives->drives, drives->n_drives + bv->n_items)) + return log_oom(); + + FOREACH_ARRAY(it, bv->items, bv->n_items) { + BindVolume *v = *it; + _cleanup_(drive_info_unrefp) DriveInfo *d = NULL; + _cleanup_free_ char *error_id = NULL; + + r = vmspawn_bind_volume_acquire( + scope, v, + /* removable= */ false, + /* link= */ NULL, + &d, &error_id); + if (r < 0) { + if (error_id) + return log_error_errno(r, + "Failed to acquire storage volume '%s:%s' (%s): %m", + v->provider, v->volume, error_id); + return log_error_errno(r, + "Failed to acquire storage volume '%s:%s': %m", + v->provider, v->volume); + } + + drives->drives[drives->n_drives++] = TAKE_PTR(d); + } + + return 0; +} diff --git a/src/vmspawn/vmspawn-bind-volume.h b/src/vmspawn/vmspawn-bind-volume.h new file mode 100644 index 00000000000..23b3ff52f3c --- /dev/null +++ b/src/vmspawn/vmspawn-bind-volume.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "machine-util.h" +#include "shared-forward.h" +#include "vmspawn-qmp.h" + +/* Empty/NULL defaults to virtio-blk; otherwise delegates to disk_type_from_string(). */ +DiskType disk_type_from_bind_volume_config(const char *config); + +/* Acquires the volume and builds a DriveInfo with id=":" (the + * bridge-visible name; QMP-side names are still allocated by add_block_device). */ +int vmspawn_bind_volume_acquire( + RuntimeScope scope, + const BindVolume *v, + bool removable, + sd_varlink *link, + DriveInfo **ret, + char **reterr_error_id); + +typedef struct BindVolumes { + BindVolume **items; + size_t n_items; +} BindVolumes; + +void bind_volumes_done(BindVolumes *bv); + +int vmspawn_bind_volume_prepare_boot( + RuntimeScope scope, + const BindVolumes *bv, + DriveInfos *drives); + +/* Takes ownership of fd unconditionally. */ +int vmspawn_bind_volume_attach_fd( + VmspawnQmpBridge *bridge, + sd_varlink *link, + int fd, + const char *name, + const char *config);