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); }