vmspawn: add --bind-volume= command line option

systemd-vmspawn --bind-volume=PROVIDER:VOLUME[:CONFIG][:K=V,...]

For each --bind-volume passed at startup, vmspawn calls Acquire() on
the named StorageProvider and attaches the resulting fd to the VM as
an additional drive. The drive is identified by the user-visible name
'<provider>:<volume>' on the bridge — that is also the handle used
later when machinectl unbind-volume detaches drives at runtime
(though boot-time drives like these are NOT removable; that is the
StorageImmutable behaviour added earlier).

The colon grammar is parsed by the shared bind_volume_parse() helper.
The 3rd 'config' field selects the guest device type from the
disk_type_table[] vocabulary (virtio-blk, virtio-scsi, nvme, scsi-cd);
empty defaults to virtio-blk per the TASK grammar.

Wiring lives next to the existing --extra-drive setup: parse_argv()
appends a parsed BindVolume to arg_bind_volumes, and prepare_device_info()
hands the array to vmspawn_bind_volume_prepare_boot() which Acquires
each volume and pushes a DriveInfo onto the existing drives array.
PCIe port assignment (assign_pcie_ports()) and the QMP setup loop pick
them up automatically.

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 a34ad7f7b0
commit 413fd62dd7
2 changed files with 33 additions and 2 deletions
+1 -1
View File
@@ -38,7 +38,7 @@ _systemd_vmspawn() {
[BIND]='--bind --bind-ro'
[SSH_KEY]='--ssh-key'
[CONSOLE]='--console'
[ARG]='--cpus --ram --vsock-cid -M --machine --uuid --private-users --background --set-credential --load-credential --forward-journal-max-use --forward-journal-keep-free --forward-journal-max-file-size --forward-journal-max-files'
[ARG]='--cpus --ram --vsock-cid -M --machine --uuid --private-users --background --set-credential --load-credential --forward-journal-max-use --forward-journal-keep-free --forward-journal-max-file-size --forward-journal-max-files --bind-volume'
[IMAGE_FORMAT]='--image-format'
[IMAGE_DISK_TYPE]='--image-disk-type'
)
+32 -1
View File
@@ -88,6 +88,7 @@
#include "user-record.h"
#include "user-util.h"
#include "utf8.h"
#include "vmspawn-bind-volume.h"
#include "vmspawn-mount.h"
#include "vmspawn-qemu-config.h"
#include "vmspawn-qmp.h"
@@ -163,6 +164,7 @@ static bool arg_keep_unit = false;
static sd_id128_t arg_uuid = {};
static char **arg_kernel_cmdline_extra = NULL;
static ExtraDriveContext arg_extra_drives = {};
static BindVolumes arg_bind_volumes = {};
static char *arg_background = NULL;
static bool arg_pass_ssh_key = true;
static char *arg_ssh_key_type = NULL;
@@ -200,6 +202,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_runtime_mounts, runtime_mount_context_done);
STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep);
STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_extra_drives, extra_drive_context_done);
STATIC_DESTRUCTOR_REGISTER(arg_bind_volumes, bind_volumes_done);
STATIC_DESTRUCTOR_REGISTER(arg_background, freep);
STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep);
STATIC_DESTRUCTOR_REGISTER(arg_smbios11, strv_freep);
@@ -766,6 +769,30 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
OPTION_LONG("bind-volume", "PROVIDER:VOLUME[:CONFIG][:KEY=VALUE,...]",
"Acquire a storage volume from a StorageProvider and attach it to the VM"): {
_cleanup_(bind_volume_freep) BindVolume *bv = NULL;
r = bind_volume_parse(opts.arg, &bv);
if (r < 0)
return log_error_errno(r, "Failed to parse --bind-volume= argument '%s': %m", opts.arg);
if (disk_type_from_bind_volume_config(bv->config) < 0) {
_cleanup_free_ char *valid = NULL;
for (DiskType t = 0; t < _DISK_TYPE_MAX; t++)
if (!strextend_with_separator(&valid, ", ", disk_type_to_string(t)))
return log_oom();
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unknown device type '%s' for --bind-volume=. Valid values: %s.",
bv->config, valid);
}
if (!GREEDY_REALLOC(arg_bind_volumes.items, arg_bind_volumes.n_items + 1))
return log_oom();
arg_bind_volumes.items[arg_bind_volumes.n_items++] = TAKE_PTR(bv);
break;
}
OPTION_LONG("bind-user", "NAME", "Bind user from host to virtual machine"):
if (!valid_user_group_name(opts.arg, /* flags= */ 0))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", opts.arg);
@@ -2475,7 +2502,7 @@ static int prepare_device_info(const char *runtime_dir, MachineConfig *c) {
/* Build drive info for QMP-based setup. vmspawn opens all image files and
* passes fds to QEMU via add-fd — QEMU never needs filesystem access. */
drives->drives = new0(DriveInfo*, 1 + arg_extra_drives.n_drives);
drives->drives = new0(DriveInfo*, 1 + arg_extra_drives.n_drives + arg_bind_volumes.n_items);
if (!drives->drives)
return log_oom();
@@ -2487,6 +2514,10 @@ static int prepare_device_info(const char *runtime_dir, MachineConfig *c) {
if (r < 0)
return r;
r = vmspawn_bind_volume_prepare_boot(arg_runtime_scope, &arg_bind_volumes, drives);
if (r < 0)
return r;
return assign_pcie_ports(c);
}