]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn: add vmspawn-bind-volume glue
authorChristian Brauner <brauner@kernel.org>
Fri, 1 May 2026 11:34:54 +0000 (13:34 +0200)
committerChristian Brauner <brauner@kernel.org>
Wed, 6 May 2026 08:30:17 +0000 (10:30 +0200)
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>
src/vmspawn/meson.build
src/vmspawn/vmspawn-bind-volume.c [new file with mode: 0644]
src/vmspawn/vmspawn-bind-volume.h [new file with mode: 0644]

index 6d08755fedf8b77a2473679df71a4a2c95e468e9..6bc31c77c692d537fcc6bd53e3f8087ebcae9e8c 100644 (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',
diff --git a/src/vmspawn/vmspawn-bind-volume.c b/src/vmspawn/vmspawn-bind-volume.c
new file mode 100644 (file)
index 0000000..d67fc61
--- /dev/null
@@ -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;
+}
diff --git a/src/vmspawn/vmspawn-bind-volume.h b/src/vmspawn/vmspawn-bind-volume.h
new file mode 100644 (file)
index 0000000..23b3ff5
--- /dev/null
@@ -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);