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) <brauner@kernel.org>
This commit is contained in:
Christian Brauner
2026-05-06 10:30:17 +02:00
parent 0e911e4164
commit a34ad7f7b0
3 changed files with 242 additions and 0 deletions
+1
View File
@@ -6,6 +6,7 @@ endif
vmspawn_sources = files(
'vmspawn.c',
'vmspawn-bind-volume.c',
'vmspawn-qemu-config.c',
'vmspawn-qmp.c',
'vmspawn-varlink.c',
+202
View File
@@ -0,0 +1,202 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <sys/stat.h>
#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;
}
+39
View File
@@ -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="<provider>:<volume>" (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);