From: Michael Vogt Date: Sun, 1 Feb 2026 13:07:40 +0000 (+0100) Subject: vmspawn: add --ephemeral option similar to nspawn X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6e67fc2938ad690a065a984c5813c6b5c0e48806;p=thirdparty%2Fsystemd.git vmspawn: add --ephemeral option similar to nspawn This patch adds a `--ephemeral` option to vmspawn that will turn on snapshot mode on the qemu disk passed via `--image` or the directory passed via `--directory`. For disk images it uses the native mechanism that qemu provides. For directories it (re)uses the snapshot mechanism that nspawn is using, i.e. it will create a btrfs snapshot if possible and if not falls back to a traditional directory copy. --- diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index eb0d4bff764..dc56c590587 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -125,6 +125,17 @@ + + + + + If specified, the VM is run with a temporary snapshot of its file system that is removed + immediately when the VM terminates. Only works with currently. + + Note that will not work with . + + + diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index a010245da4d..d2adc649318 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -6082,8 +6082,8 @@ static int run(int argc, char *argv[]) { goto finish; } - arg_directory = strdup(snapshot_dir); - if (!arg_directory) { + r = free_and_strdup(&arg_directory, snapshot_dir); + if (r < 0) { log_oom(); goto finish; } diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index 1cfe4ffd729..ee937c993ac 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -35,6 +35,7 @@ typedef enum SettingsMask { SETTING_START_MODE = UINT64_C(1) << 0, SETTING_MACHINE_ID = UINT64_C(1) << 6, SETTING_BIND_MOUNTS = UINT64_C(1) << 11, + SETTING_EPHEMERAL = UINT64_C(1) << 24, 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 06f6961b1a8..7f58f62b043 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -62,6 +62,7 @@ #include "random-util.h" #include "rm-rf.h" #include "signal-util.h" +#include "snapshot-util.h" #include "socket-util.h" #include "stat-util.h" #include "stdio-util.h" @@ -145,6 +146,7 @@ static char **arg_bind_user = NULL; static char *arg_bind_user_shell = NULL; static bool arg_bind_user_shell_copy = false; static char **arg_bind_user_groups = NULL; +static bool arg_ephemeral = false; static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; STATIC_DESTRUCTOR_REGISTER(arg_directory, freep); @@ -190,6 +192,7 @@ static int help(void) { " --system Interact with system manager\n" "\n%3$sImage:%4$s\n" " -D --directory=PATH Root directory for the VM\n" + " -x --ephemeral Run VM with snapshot of the disk or directory\n" " -i --image=FILE|DEVICE Root file system disk image or device for the VM\n" " --image-format=FORMAT Specify disk image format (raw, qcow2; default: raw)\n" "\n%3$sHost Configuration:%4$s\n" @@ -327,6 +330,7 @@ static int parse_argv(int argc, char *argv[]) { { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "image", required_argument, NULL, 'i' }, { "image-format", required_argument, NULL, ARG_IMAGE_FORMAT }, + { "ephemeral", no_argument, NULL, 'x' }, { "directory", required_argument, NULL, 'D' }, { "machine", required_argument, NULL, 'M' }, { "slice", required_argument, NULL, 'S' }, @@ -382,7 +386,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argv); optind = 0; - while ((c = getopt_long(argc, argv, "+hD:i:M:nqs:G:S:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "+hD:i:xM:nqs:G:S:", options, NULL)) >= 0) switch (c) { case 'h': return help(); @@ -429,6 +433,10 @@ static int parse_argv(int argc, char *argv[]) { } break; + case 'x': + arg_ephemeral = true; + break; + case ARG_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; @@ -794,6 +802,9 @@ static int parse_argv(int argc, char *argv[]) { if (!strv_isempty(arg_bind_user_groups) && strv_isempty(arg_bind_user)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-group= without --bind-user="); + if (arg_ephemeral && arg_extra_drives.n_drives > 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --ephemeral with --extra-drive="); + if (argc > optind) { arg_kernel_cmdline_extra = strv_copy(argv + optind); if (!arg_kernel_cmdline_extra) @@ -1843,6 +1854,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; _cleanup_free_ char *qemu_binary = NULL, *mem = NULL, *kernel = NULL; _cleanup_(rm_rf_physical_and_freep) char *ssh_private_key_path = NULL, *ssh_public_key_path = NULL; + _cleanup_(rm_rf_subvolume_and_freep) char *snapshot_directory = NULL; + _cleanup_(release_lock_file) LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; _cleanup_close_ int notify_sock_fd = -EBADF; _cleanup_strv_free_ char **cmdline = NULL; _cleanup_free_ int *pass_fds = NULL; @@ -2319,7 +2332,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (!escaped_image) return log_oom(); - if (strv_extendf(&cmdline, "if=none,id=vmspawn,file=%s,format=%s,discard=%s", escaped_image, image_format_to_string(arg_image_format), on_off(arg_discard_disk)) < 0) + if (strv_extendf(&cmdline, "if=none,id=vmspawn,file=%s,format=%s,discard=%s,snapshot=%s", + escaped_image, image_format_to_string(arg_image_format), on_off(arg_discard_disk), on_off(arg_ephemeral)) < 0) return log_oom(); _cleanup_free_ char *image_fn = NULL; @@ -2366,6 +2380,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (!GREEDY_REALLOC(children, n_children + 1)) return log_oom(); + if (arg_ephemeral) { + r = create_ephemeral_snapshot(arg_directory, + arg_runtime_scope, + /* read-only */ false, + &tree_global_lock, + &tree_local_lock, + &snapshot_directory); + if (r < 0) + return r; + + arg_directory = strdup(snapshot_directory); + if (!arg_directory) + return log_oom(); + } + r = start_virtiofsd( unit, arg_directory, @@ -3063,6 +3092,11 @@ static int determine_names(void) { if (r < 0) return log_error_errno(r, "Failed to extract file name from '%s': %m", arg_directory); } + /* Add a random suffix when this is an ephemeral machine, so that we can run many + * instances at once without manually having to specify -M each time. */ + if (arg_ephemeral) + if (strextendf(&arg_machine, "-%016" PRIx64, random_u64()) < 0) + return log_oom(); hostname_cleanup(arg_machine); if (!hostname_is_valid(arg_machine, 0))