diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml
index 184ba6e590c..0446d27630c 100644
--- a/man/systemd-vmspawn.xml
+++ b/man/systemd-vmspawn.xml
@@ -262,7 +262,6 @@
-
@@ -290,6 +289,28 @@
+
+ Mount Options
+
+
+
+
+ PATH
+ PATH
+
+ 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 creates read-only bind mounts.
+ Backslash escapes are interpreted, so \: may be used to embed colons in either path.
+ This option may be specified multiple times for creating multiple independent bind mount points.
+
+
+
+
+
+
Credentials
diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build
index 5f54364fac4..a7b34bdbab0 100644
--- a/src/vmspawn/meson.build
+++ b/src/vmspawn/meson.build
@@ -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
index 00000000000..ee63bda96c7
--- /dev/null
+++ b/src/vmspawn/vmspawn-mount.c
@@ -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
index 00000000000..2ea24fd0354
--- /dev/null
+++ b/src/vmspawn/vmspawn-mount.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include
+#include
+
+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);
diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h
index 268a8746642..60ea10e6de0 100644
--- a/src/vmspawn/vmspawn-settings.h
+++ b/src/vmspawn/vmspawn-settings.h
@@ -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
diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c
index def8eef73c7..d5de11bfe0b 100644
--- a/src/vmspawn/vmspawn.c
+++ b/src/vmspawn/vmspawn.c
@@ -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);
}