]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn: add support for --bind(-ro)= 31076/head
authorSam Leonard <sam.leonard@codethink.co.uk>
Wed, 13 Dec 2023 16:54:34 +0000 (16:54 +0000)
committerSam Leonard <sam.leonard@codethink.co.uk>
Mon, 12 Feb 2024 09:46:02 +0000 (09:46 +0000)
man/systemd-vmspawn.xml
src/vmspawn/meson.build
src/vmspawn/vmspawn-mount.c [new file with mode: 0644]
src/vmspawn/vmspawn-mount.h [new file with mode: 0644]
src/vmspawn/vmspawn-settings.h
src/vmspawn/vmspawn.c

index 184ba6e590c9e222f3f5d9a35202fc002e1aca86..0446d27630ccfc24b2ffc954319817a62827d7c4 100644 (file)
           </listitem>
         </varlistentry>
       </variablelist>
-
     </refsect2>
 
     <refsect2>
       </variablelist>
     </refsect2>
 
+    <refsect2>
+      <title>Mount Options</title>
+
+      <variablelist>
+
+        <varlistentry>
+          <term><option>--bind=</option><replaceable>PATH</replaceable></term>
+          <term><option>--bind-ro=</option><replaceable>PATH</replaceable></term>
+
+          <listitem><para>Mount a directory from the host into the virtual machine. Takes one of: a path
+          argument — in which case the specified path will be mounted from the host to the same path in the virtual machine, or
+          a colon-separated pair of paths — in which case the first specified path is the source in the host, and the
+          second path is the destination in the virtual machine. If the source path is not absolute, it is resolved
+          relative to the current working directory. The <option>--bind-ro=</option> option creates read-only bind mounts.
+          Backslash escapes are interpreted, so <literal>\:</literal> may be used to embed colons in either path.
+          This option may be specified multiple times for creating multiple independent bind mount points.</para>
+
+          <xi:include href="version-info.xml" xpointer="v256"/></listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
     <refsect2>
       <title>Credentials</title>
 
index 5f54364fac43d74611057a824ba0442b408ee157..a7b34bdbab0a9069e87a25504f55b1eac5db9353 100644 (file)
@@ -4,6 +4,7 @@ libvmspawn_core_sources = files(
         'vmspawn-settings.c',
         'vmspawn-util.c',
         'vmspawn-scope.c',
+        'vmspawn-mount.c',
 )
 libvmspawn_core = static_library(
         'vmspawn-core',
diff --git a/src/vmspawn/vmspawn-mount.c b/src/vmspawn/vmspawn-mount.c
new file mode 100644 (file)
index 0000000..ee63bda
--- /dev/null
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "alloc-util.h"
+#include "extract-word.h"
+#include "macro.h"
+#include "parse-argument.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "vmspawn-mount.h"
+
+static void runtime_mount_done(RuntimeMount *mount) {
+        assert(mount);
+
+        mount->source = mfree(mount->source);
+        mount->target = mfree(mount->target);
+}
+
+void runtime_mount_context_done(RuntimeMountContext *ctx) {
+        assert(ctx);
+
+        FOREACH_ARRAY(mount, ctx->mounts, ctx->n_mounts)
+                runtime_mount_done(mount);
+
+        free(ctx->mounts);
+}
+
+int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only) {
+        _cleanup_(runtime_mount_done) RuntimeMount mount = { .read_only = read_only };
+        _cleanup_free_ char *source_rel = NULL;
+        int r;
+
+        assert(ctx);
+
+        r = extract_first_word(&s, &source_rel, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EINVAL;
+
+        if (isempty(source_rel))
+                return -EINVAL;
+
+        r = path_make_absolute_cwd(source_rel, &mount.source);
+        if (r < 0)
+                return r;
+
+        /* virtiofsd only supports directories */
+        r = is_dir(mount.source, /* follow= */ true);
+        if (r < 0)
+                return r;
+        if (!r)
+                return -ENOTDIR;
+
+        mount.target = s ? strdup(s) : TAKE_PTR(source_rel);
+        if (!mount.target)
+                return -ENOMEM;
+
+        if (!path_is_absolute(mount.target))
+                return -EINVAL;
+
+        if (!GREEDY_REALLOC(ctx->mounts, ctx->n_mounts + 1))
+                return log_oom();
+
+        ctx->mounts[ctx->n_mounts++] = TAKE_STRUCT(mount);
+
+        return 0;
+}
diff --git a/src/vmspawn/vmspawn-mount.h b/src/vmspawn/vmspawn-mount.h
new file mode 100644 (file)
index 0000000..2ea24fd
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+
+typedef struct RuntimeMount {
+        bool read_only;
+        char *source;
+        char *target;
+} RuntimeMount;
+
+typedef struct RuntimeMountContext {
+        RuntimeMount *mounts;
+        size_t n_mounts;
+} RuntimeMountContext;
+
+void runtime_mount_context_done(RuntimeMountContext *ctx);
+int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only);
index 268a87466426f9606f635967c23b21e1a115dbab..60ea10e6de01ef45d17bc79989efce3f35a4094c 100644 (file)
@@ -5,6 +5,7 @@
 
 typedef enum SettingsMask {
         SETTING_START_MODE        = UINT64_C(1) << 0,
+        SETTING_BIND_MOUNTS       = UINT64_C(1) << 11,
         SETTING_DIRECTORY         = UINT64_C(1) << 26,
         SETTING_CREDENTIALS       = UINT64_C(1) << 30,
         _SETTING_FORCE_ENUM_WIDTH = UINT64_MAX
index def8eef73c778f1ffee71378703c1b2acfff2ff1..d5de11bfe0b2d355b984cbe244eb7efa17d9fd3e 100644 (file)
@@ -19,6 +19,7 @@
 #include "dissect-image.h"
 #include "escape.h"
 #include "event-util.h"
+#include "extract-word.h"
 #include "fileio.h"
 #include "format-util.h"
 #include "fs-util.h"
@@ -46,6 +47,7 @@
 #include "strv.h"
 #include "tmpfile-util.h"
 #include "unit-name.h"
+#include "vmspawn-mount.h"
 #include "vmspawn-scope.h"
 #include "vmspawn-settings.h"
 #include "vmspawn-util.h"
@@ -68,6 +70,7 @@ static QemuNetworkStack arg_network_stack = QEMU_NET_NONE;
 static int arg_secure_boot = -1;
 static MachineCredentialContext arg_credentials = {};
 static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U;
+static RuntimeMountContext arg_runtime_mounts = {};
 static SettingsMask arg_settings_mask = 0;
 static char *arg_firmware = NULL;
 static char *arg_runtime_directory = NULL;
@@ -84,6 +87,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done);
 STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_linux, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_initrd, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_runtime_mounts, runtime_mount_context_done);
 STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep);
 
 static int help(void) {
@@ -127,6 +131,12 @@ static int help(void) {
                "     --private-users=UIDBASE[:NUIDS]\n"
                "                            Configure the UID/GID range to map into the\n"
                "                            virtiofsd namespace\n"
+               "\n%3$sMounts:%4$s\n"
+               "     --bind=SOURCE[:TARGET]\n"
+               "                            Mount a file or directory from the host into\n"
+               "                            the VM.\n"
+               "     --bind-ro=SOURCE[:TARGET]\n"
+               "                            Similar, but creates a read-only mount\n"
                "\n%3$sCredentials:%4$s\n"
                "     --set-credential=ID:VALUE\n"
                "                            Pass a credential with literal value to the\n"
@@ -159,6 +169,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_INITRD,
                 ARG_QEMU_GUI,
                 ARG_NETWORK_USER_MODE,
+                ARG_BIND,
+                ARG_BIND_RO,
                 ARG_SECURE_BOOT,
                 ARG_PRIVATE_USERS,
                 ARG_SET_CREDENTIAL,
@@ -185,6 +197,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "qemu-gui",          no_argument,       NULL, ARG_QEMU_GUI          },
                 { "network-tap",       no_argument,       NULL, 'n'                   },
                 { "network-user-mode", no_argument,       NULL, ARG_NETWORK_USER_MODE },
+                { "bind",              required_argument, NULL, ARG_BIND              },
+                { "bind-ro",           required_argument, NULL, ARG_BIND_RO           },
                 { "secure-boot",       required_argument, NULL, ARG_SECURE_BOOT       },
                 { "private-users",     required_argument, NULL, ARG_PRIVATE_USERS     },
                 { "set-credential",    required_argument, NULL, ARG_SET_CREDENTIAL    },
@@ -212,7 +226,7 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case 'D':
-                        r = parse_path_argument(optarg, false, &arg_directory);
+                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_directory);
                         if (r < 0)
                                 return r;
 
@@ -316,6 +330,15 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_network_stack = QEMU_NET_USER;
                         break;
 
+                case ARG_BIND:
+                case ARG_BIND_RO:
+                        r = runtime_mount_parse(&arg_runtime_mounts, optarg, c == ARG_BIND_RO);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg);
+
+                        arg_settings_mask |= SETTING_BIND_MOUNTS;
+                        break;
+
                 case ARG_SECURE_BOOT:
                         r = parse_tristate(optarg, &arg_secure_boot);
                         if (r < 0)
@@ -689,7 +712,7 @@ static int find_virtiofsd(char **ret) {
         return 0;
 }
 
-static int start_virtiofsd(sd_bus *bus, const char *scope, const char *directory, char **ret_state_tempdir, char **ret_sock_name) {
+static int start_virtiofsd(sd_bus *bus, const char *scope, const char *directory, bool uidmap, char **ret_state_tempdir, char **ret_sock_name) {
         _cleanup_(rm_rf_physical_and_freep) char *state_dir = NULL;
         _cleanup_free_ char *virtiofsd = NULL, *sock_name = NULL, *scope_prefix = NULL;
         _cleanup_(socket_service_pair_done) SocketServicePair ssp = {
@@ -737,7 +760,7 @@ static int start_virtiofsd(sd_bus *bus, const char *scope, const char *directory
         if (!ssp.exec_start)
                 return log_oom();
 
-        if (arg_uid_shift != UID_INVALID) {
+        if (uidmap && arg_uid_shift != UID_INVALID) {
                 r = strv_extend(&ssp.exec_start, "--uid-map");
                 if (r < 0)
                         return log_oom();
@@ -865,7 +888,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
                 return log_oom();
 
         /* if we are going to be starting any units with state then create our runtime dir */
-        if (arg_tpm != 0 || arg_directory) {
+        if (arg_tpm != 0 || arg_directory || arg_runtime_mounts.n_mounts != 0) {
                 r = runtime_directory(&arg_runtime_directory, arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, "systemd/vmspawn");
                 if (r < 0)
                         return log_error_errno(r, "Failed to lookup runtime directory: %m");
@@ -888,7 +911,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
                 return log_oom();
 
         /* A shared memory backend might increase ram usage so only add one if actually necessary for virtiofsd. */
-        if (arg_directory) {
+        if (arg_directory || arg_runtime_mounts.n_mounts != 0) {
                 r = strv_extend(&cmdline, "-object");
                 if (r < 0)
                         return log_oom();
@@ -1088,7 +1111,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
 
         if (arg_directory) {
                 _cleanup_free_ char *sock_path = NULL, *sock_name = NULL;
-                r = start_virtiofsd(bus, trans_scope, arg_directory, &sock_path, &sock_name);
+                r = start_virtiofsd(bus, trans_scope, arg_directory, /* uidmap= */ true, &sock_path, &sock_name);
                 if (r < 0)
                         return r;
 
@@ -1117,6 +1140,38 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
         if (r < 0)
                 return log_oom();
 
+        FOREACH_ARRAY(mount, arg_runtime_mounts.mounts, arg_runtime_mounts.n_mounts) {
+                _cleanup_free_ char *sock_path = NULL, *sock_name = NULL, *clean_target = NULL;
+                r = start_virtiofsd(bus, trans_scope, mount->source, /* uidmap= */ false, &sock_path, &sock_name);
+                if (r < 0)
+                        return r;
+
+                r = strv_extend(&cmdline, "-chardev");
+                if (r < 0)
+                        return log_oom();
+
+                r = strv_extendf(&cmdline, "socket,id=%1$s,path=%2$s/%1$s", sock_name, sock_path);
+                if (r < 0)
+                        return log_oom();
+
+                r = strv_extend(&cmdline, "-device");
+                if (r < 0)
+                        return log_oom();
+
+                r = strv_extendf(&cmdline, "vhost-user-fs-pci,queue-size=1024,chardev=%1$s,tag=%1$s", sock_name);
+                if (r < 0)
+                        return log_oom();
+
+                clean_target = xescape(mount->target, "\":");
+                if (!clean_target)
+                        return log_oom();
+
+                r = strv_extendf(&arg_kernel_cmdline_extra, "systemd.mount-extra=\"%s:%s:virtiofs:%s\"",
+                                 sock_name, clean_target, mount->read_only ? "ro" : "rw");
+                if (r < 0)
+                        return log_oom();
+        }
+
         if (ARCHITECTURE_SUPPORTS_SMBIOS) {
                 _cleanup_free_ char *kcl = strv_join(arg_kernel_cmdline_extra, " ");
                 if (!kcl)
@@ -1219,6 +1274,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
                 log_debug("Executing: %s", joined);
         }
 
+        assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0);
+
         _cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL;
         _cleanup_(sd_event_unrefp) sd_event *event = NULL;
         r = sd_event_new(&event);
@@ -1376,8 +1433,6 @@ static int run(int argc, char *argv[]) {
                 }
         }
 
-        assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0);
-
         return run_virtual_machine(kvm_device_fd, vhost_device_fd);
 }