vmspawn: add support for --bind(-ro)=

This commit is contained in:
Sam Leonard 2023-12-13 16:54:34 +00:00
parent 7fe9c9909d
commit a8f940c4b5
No known key found for this signature in database
GPG key ID: 96850F0978CE78F0
6 changed files with 173 additions and 9 deletions

View file

@ -262,7 +262,6 @@
</listitem>
</varlistentry>
</variablelist>
</refsect2>
<refsect2>
@ -290,6 +289,28 @@
</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>

View 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',

View file

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

View file

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

View 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

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