--- /dev/null
+/* 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;
+}
#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"
#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"
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;
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) {
" --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"
ARG_INITRD,
ARG_QEMU_GUI,
ARG_NETWORK_USER_MODE,
+ ARG_BIND,
+ ARG_BIND_RO,
ARG_SECURE_BOOT,
ARG_PRIVATE_USERS,
ARG_SET_CREDENTIAL,
{ "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 },
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;
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)
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 = {
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();
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");
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();
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;
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)
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);
}
}
- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0);
-
return run_virtual_machine(kvm_device_fd, vhost_device_fd);
}