1
0
mirror of https://github.com/systemd/systemd.git synced 2025-08-13 21:49:54 +03:00

Merge pull request #4693 from poettering/nspawn-ephemeral

nspawn: support ephemeral boots from images
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2016-11-22 23:15:07 -05:00
committed by GitHub
9 changed files with 193 additions and 114 deletions

View File

@ -599,8 +599,8 @@
<listitem><para>Clones a container or VM image. The arguments specify the name of the image to clone and the
name of the newly cloned image. Note that plain directory container images are cloned into btrfs subvolume
images with this command, if the underlying file system supports this. Note that cloning a container or VM
image is optimized for btrfs file systems, and might not be efficient on others, due to file system
limitations.</para>
image is optimized for file systems that support copy-on-write, and might not be efficient on others, due to
file system limitations.</para>
<para>Note that this command leaves host name, machine ID and
all other settings that could identify the instance
@ -910,7 +910,7 @@
<filename>/var/lib/machines/</filename> to make them available for
control with <command>machinectl</command>.</para>
<para>Note that many image operations are only supported,
<para>Note that some image operations are only supported,
efficient or atomic on btrfs file systems. Due to this, if the
<command>pull-tar</command>, <command>pull-raw</command>,
<command>import-tar</command>, <command>import-raw</command> and

View File

@ -181,25 +181,15 @@
<varlistentry>
<term><option>--template=</option></term>
<listitem><para>Directory or <literal>btrfs</literal>
subvolume to use as template for the container's root
directory. If this is specified and the container's root
directory (as configured by <option>--directory=</option>)
does not yet exist it is created as <literal>btrfs</literal>
subvolume and populated from this template tree. Ideally, the
specified template path refers to the root of a
<literal>btrfs</literal> subvolume, in which case a simple
copy-on-write snapshot is taken, and populating the root
directory is instant. If the specified template path does not
refer to the root of a <literal>btrfs</literal> subvolume (or
not even to a <literal>btrfs</literal> file system at all),
the tree is copied, which can be substantially more
time-consuming. Note that if this option is used the
container's root directory (in contrast to the template
directory!) must be located on a <literal>btrfs</literal> file
system, so that the <literal>btrfs</literal> subvolume may be
created. May not be specified together with
<option>--image=</option> or
<listitem><para>Directory or <literal>btrfs</literal> subvolume to use as template for the container's root
directory. If this is specified and the container's root directory (as configured by
<option>--directory=</option>) does not yet exist it is created as <literal>btrfs</literal> snapshot (if
supported) or plain directory (otherwise) and populated from this template tree. Ideally, the specified
template path refers to the root of a <literal>btrfs</literal> subvolume, in which case a simple copy-on-write
snapshot is taken, and populating the root directory is instant. If the specified template path does not refer
to the root of a <literal>btrfs</literal> subvolume (or not even to a <literal>btrfs</literal> file system at
all), the tree is copied (though possibly in a copy-on-write scheme — if the file system supports that), which
can be substantially more time-consuming. May not be specified together with <option>--image=</option> or
<option>--ephemeral</option>.</para>
<para>Note that this switch leaves host name, machine ID and
@ -211,13 +201,8 @@
<term><option>-x</option></term>
<term><option>--ephemeral</option></term>
<listitem><para>If specified, the container is run with a
temporary <literal>btrfs</literal> snapshot of its root
directory (as configured with <option>--directory=</option>),
that is removed immediately when the container terminates.
This option is only supported if the root file system is
<literal>btrfs</literal>. May not be specified together with
<option>--image=</option> or
<listitem><para>If specified, the container is run with a temporary snapshot of its file system that is removed
immediately when the container terminates. May not be specified together with
<option>--template=</option>.</para>
<para>Note that this switch leaves host name, machine ID and
all other settings that could identify the instance
@ -252,11 +237,12 @@
Partitions Specification</ulink>.</para></listitem>
</itemizedlist>
<para>Any other partitions, such as foreign partitions, swap
partitions or EFI system partitions are not mounted. May not
be specified together with <option>--directory=</option>,
<option>--template=</option> or
<option>--ephemeral</option>.</para></listitem>
<para>On GPT images, if an EFI System Partition (ESP) is discovered, it is automatically mounted to
<filename>/efi</filename> (or <filename>/boot</filename> as fallback) in case a directory by this name exists
and is empty.</para>
<para>Any other partitions, such as foreign partitions or swap partitions are not mounted. May not be specified
together with <option>--directory=</option>, <option>--template=</option>.</para></listitem>
</varlistentry>
<varlistentry>
@ -1056,14 +1042,12 @@
</example>
<example>
<title>Boot into an ephemeral <literal>btrfs</literal> snapshot of the host system</title>
<title>Boot into an ephemeral snapshot of the host system</title>
<programlisting># systemd-nspawn -D / -xb</programlisting>
<para>This runs a copy of the host system in a
<literal>btrfs</literal> snapshot which is removed immediately
when the container exits. All file system changes made during
runtime will be lost on shutdown, hence.</para>
<para>This runs a copy of the host system in a snapshot which is removed immediately when the container
exits. All file system changes made during runtime will be lost on shutdown, hence.</para>
</example>
<example>

View File

@ -20,6 +20,7 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/fs.h>
#include <linux/loop.h>
#include <stddef.h>
#include <stdio.h>
@ -38,6 +39,7 @@
#include "alloc-util.h"
#include "btrfs-ctree.h"
#include "btrfs-util.h"
#include "chattr-util.h"
#include "copy.h"
#include "fd-util.h"
#include "fileio.h"
@ -45,6 +47,7 @@
#include "macro.h"
#include "missing.h"
#include "path-util.h"
#include "rm-rf.h"
#include "selinux-util.h"
#include "smack-util.h"
#include "sparse-endian.h"
@ -1718,28 +1721,46 @@ int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlag
if (r < 0)
return r;
if (r == 0) {
bool plain_directory = false;
/* If the source isn't a proper subvolume, fail unless fallback is requested */
if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY))
return -EISDIR;
r = btrfs_subvol_make(new_path);
if (r < 0)
if (r == -ENOTTY && (flags & BTRFS_SNAPSHOT_FALLBACK_DIRECTORY)) {
/* If the destination doesn't support subvolumes, then use a plain directory, if that's requested. */
if (mkdir(new_path, 0755) < 0)
return r;
plain_directory = true;
} else if (r < 0)
return r;
r = copy_directory_fd(old_fd, new_path, true);
if (r < 0) {
(void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA);
return r;
}
if (r < 0)
goto fallback_fail;
if (flags & BTRFS_SNAPSHOT_READ_ONLY) {
r = btrfs_subvol_set_read_only(new_path, true);
if (r < 0) {
(void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA);
return r;
if (plain_directory) {
/* Plain directories have no recursive read-only flag, but something pretty close to
* it: the IMMUTABLE bit. Let's use this here, if this is requested. */
if (flags & BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE)
(void) chattr_path(new_path, FS_IMMUTABLE_FL, FS_IMMUTABLE_FL);
} else {
r = btrfs_subvol_set_read_only(new_path, true);
if (r < 0)
goto fallback_fail;
}
}
return 0;
fallback_fail:
(void) rm_rf(new_path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
return r;
}
r = extract_subvolume_name(new_path, &subvolume);

View File

@ -45,10 +45,12 @@ typedef struct BtrfsQuotaInfo {
} BtrfsQuotaInfo;
typedef enum BtrfsSnapshotFlags {
BTRFS_SNAPSHOT_FALLBACK_COPY = 1,
BTRFS_SNAPSHOT_FALLBACK_COPY = 1, /* If the source isn't a subvolume, reflink everything */
BTRFS_SNAPSHOT_READ_ONLY = 2,
BTRFS_SNAPSHOT_RECURSIVE = 4,
BTRFS_SNAPSHOT_QUOTA = 8,
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY = 16, /* If the destination doesn't support subvolumes, reflink/copy instead */
BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE = 32, /* When we can't create a subvolume, use the FS_IMMUTABLE attribute for indicating read-only */
} BtrfsSnapshotFlags;
typedef enum BtrfsRemoveFlags {

View File

@ -143,6 +143,10 @@
#define GRND_RANDOM 0x0002
#endif
#ifndef FS_NOCOW_FL
#define FS_NOCOW_FL 0x00800000
#endif
#ifndef BTRFS_IOCTL_MAGIC
#define BTRFS_IOCTL_MAGIC 0x94
#endif

View File

@ -144,12 +144,12 @@ int pull_make_local_copy(const char *final, const char *image_root, const char *
if (force_local)
(void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
r = btrfs_subvol_snapshot(final, p, BTRFS_SNAPSHOT_QUOTA);
if (r == -ENOTTY) {
r = copy_tree(final, p, false);
if (r < 0)
return log_error_errno(r, "Failed to copy image: %m");
} else if (r < 0)
r = btrfs_subvol_snapshot(final, p,
BTRFS_SNAPSHOT_QUOTA|
BTRFS_SNAPSHOT_FALLBACK_COPY|
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
BTRFS_SNAPSHOT_RECURSIVE);
if (r < 0)
return log_error_errno(r, "Failed to create local image: %m");
log_info("Created new local image '%s'.", local);

View File

@ -298,7 +298,7 @@ int mount_sysfs(const char *dest, MountSettingsMask mount_settings) {
MS_BIND|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT|extra_flags, NULL);
}
static int mkdir_userns(const char *path, mode_t mode, bool in_userns, uid_t uid_shift) {
static int mkdir_userns(const char *path, mode_t mode, MountSettingsMask mask, uid_t uid_shift) {
int r;
assert(path);
@ -307,16 +307,20 @@ static int mkdir_userns(const char *path, mode_t mode, bool in_userns, uid_t uid
if (r < 0 && errno != EEXIST)
return -errno;
if (!in_userns) {
r = lchown(path, uid_shift, uid_shift);
if (r < 0)
return -errno;
}
if ((mask & MOUNT_USE_USERNS) == 0)
return 0;
if (mask & MOUNT_IN_USERNS)
return 0;
r = lchown(path, uid_shift, uid_shift);
if (r < 0)
return -errno;
return 0;
}
static int mkdir_userns_p(const char *prefix, const char *path, mode_t mode, bool in_userns, uid_t uid_shift) {
static int mkdir_userns_p(const char *prefix, const char *path, mode_t mode, MountSettingsMask mask, uid_t uid_shift) {
const char *p, *e;
int r;
@ -343,12 +347,12 @@ static int mkdir_userns_p(const char *prefix, const char *path, mode_t mode, boo
if (prefix && path_startswith(prefix, t))
continue;
r = mkdir_userns(t, mode, in_userns, uid_shift);
r = mkdir_userns(t, mode, mask, uid_shift);
if (r < 0)
return r;
}
return mkdir_userns(path, mode, in_userns, uid_shift);
return mkdir_userns(path, mode, mask, uid_shift);
}
int mount_all(const char *dest,
@ -422,7 +426,7 @@ int mount_all(const char *dest,
if (mount_table[k].what && r > 0)
continue;
r = mkdir_userns_p(dest, where, 0755, in_userns, uid_shift);
r = mkdir_userns_p(dest, where, 0755, mount_settings, uid_shift);
if (r < 0 && r != -EEXIST) {
if (fatal)
return log_error_errno(r, "Failed to create directory %s: %m", where);

View File

@ -1143,11 +1143,6 @@ static int parse_argv(int argc, char *argv[]) {
return -EINVAL;
}
if (arg_ephemeral && arg_image) {
log_error("--ephemeral and --image= may not be combined.");
return -EINVAL;
}
if (arg_ephemeral && !IN_SET(arg_link_journal, LINK_NO, LINK_AUTO)) {
log_error("--ephemeral and --link-journal= may not be combined.");
return -EINVAL;
@ -2605,7 +2600,7 @@ static int determine_names(void) {
r = image_find(arg_machine, &i);
if (r < 0)
return log_error_errno(r, "Failed to find image for machine '%s': %m", arg_machine);
else if (r == 0) {
if (r == 0) {
log_error("No image for machine '%s': %m", arg_machine);
return -ENOENT;
}
@ -2615,14 +2610,14 @@ static int determine_names(void) {
else
r = free_and_strdup(&arg_directory, i->path);
if (r < 0)
return log_error_errno(r, "Invalid image directory: %m");
return log_oom();
if (!arg_ephemeral)
arg_read_only = arg_read_only || i->read_only;
} else
arg_directory = get_current_dir_name();
if (!arg_directory && !arg_machine) {
if (!arg_directory && !arg_image) {
log_error("Failed to determine path, please use -D or -i.");
return -EINVAL;
}
@ -2633,7 +2628,6 @@ static int determine_names(void) {
arg_machine = gethostname_malloc();
else
arg_machine = strdup(basename(arg_image ?: arg_directory));
if (!arg_machine)
return log_oom();
@ -3795,7 +3789,6 @@ static int run(int master,
l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof arg_uid_shift, 0);
if (l < 0)
return log_error_errno(errno, "Failed to read UID shift: %m");
if (l != sizeof arg_uid_shift) {
log_error("Short read while reading UID shift.");
return -EIO;
@ -4029,7 +4022,7 @@ static int run(int master,
terminate_machine(*pid);
/* Normally redundant, but better safe than sorry */
kill(*pid, SIGKILL);
(void) kill(*pid, SIGKILL);
r = wait_for_container(*pid, &container_status);
*pid = 0;
@ -4077,11 +4070,12 @@ int main(int argc, char *argv[]) {
_cleanup_fdset_free_ FDSet *fds = NULL;
int r, n_fd_passed, loop_nr = -1, ret = EXIT_SUCCESS;
char veth_name[IFNAMSIZ] = "";
bool secondary = false, remove_subvol = false;
bool secondary = false, remove_directory = false, remove_image = false;
pid_t pid = 0;
union in_addr_union exposed = {};
_cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT;
bool interactive, veth_created = false;
bool interactive, veth_created = false, remove_tmprootdir = false;
char tmprootdir[] = "/tmp/nspawn-root-XXXXXX";
log_parse_environment();
log_open();
@ -4148,7 +4142,7 @@ int main(int argc, char *argv[]) {
else
r = tempfn_random(arg_directory, "machine.", &np);
if (r < 0) {
log_error_errno(r, "Failed to generate name for snapshot: %m");
log_error_errno(r, "Failed to generate name for directory snapshot: %m");
goto finish;
}
@ -4158,7 +4152,12 @@ int main(int argc, char *argv[]) {
goto finish;
}
r = btrfs_subvol_snapshot(arg_directory, np, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA);
r = btrfs_subvol_snapshot(arg_directory, np,
(arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) |
BTRFS_SNAPSHOT_FALLBACK_COPY |
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY |
BTRFS_SNAPSHOT_RECURSIVE |
BTRFS_SNAPSHOT_QUOTA);
if (r < 0) {
log_error_errno(r, "Failed to create snapshot %s from %s: %m", np, arg_directory);
goto finish;
@ -4168,7 +4167,7 @@ int main(int argc, char *argv[]) {
arg_directory = np;
np = NULL;
remove_subvol = true;
remove_directory = true;
} else {
r = image_path_lock(arg_directory, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock);
@ -4182,7 +4181,13 @@ int main(int argc, char *argv[]) {
}
if (arg_template) {
r = btrfs_subvol_snapshot(arg_template, arg_directory, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA);
r = btrfs_subvol_snapshot(arg_template, arg_directory,
(arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) |
BTRFS_SNAPSHOT_FALLBACK_COPY |
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY |
BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE |
BTRFS_SNAPSHOT_RECURSIVE |
BTRFS_SNAPSHOT_QUOTA);
if (r == -EEXIST) {
if (!arg_quiet)
log_info("Directory %s already exists, not populating from template %s.", arg_directory, arg_template);
@ -4214,28 +4219,55 @@ int main(int argc, char *argv[]) {
}
} else {
char template[] = "/tmp/nspawn-root-XXXXXX";
assert(arg_image);
assert(!arg_template);
r = image_path_lock(arg_image, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock);
if (r == -EBUSY) {
r = log_error_errno(r, "Disk image %s is currently busy.", arg_image);
goto finish;
if (arg_ephemeral) {
_cleanup_free_ char *np = NULL;
r = tempfn_random(arg_image, "machine.", &np);
if (r < 0) {
log_error_errno(r, "Failed to generate name for image snapshot: %m");
goto finish;
}
r = image_path_lock(np, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock);
if (r < 0) {
r = log_error_errno(r, "Failed to create image lock: %m");
goto finish;
}
r = copy_file(arg_image, np, O_EXCL, arg_read_only ? 0400 : 0600, FS_NOCOW_FL);
if (r < 0) {
r = log_error_errno(r, "Failed to copy image file: %m");
goto finish;
}
free(arg_image);
arg_image = np;
np = NULL;
remove_image = true;
} else {
r = image_path_lock(arg_image, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock);
if (r == -EBUSY) {
r = log_error_errno(r, "Disk image %s is currently busy.", arg_image);
goto finish;
}
if (r < 0) {
r = log_error_errno(r, "Failed to create image lock: %m");
goto finish;
}
}
if (r < 0) {
r = log_error_errno(r, "Failed to create image lock: %m");
if (!mkdtemp(tmprootdir)) {
r = log_error_errno(errno, "Failed to create temporary directory: %m");
goto finish;
}
if (!mkdtemp(template)) {
log_error_errno(errno, "Failed to create temporary directory: %m");
r = -errno;
goto finish;
}
remove_tmprootdir = true;
arg_directory = strdup(template);
arg_directory = strdup(tmprootdir);
if (!arg_directory) {
r = log_oom();
goto finish;
@ -4255,6 +4287,10 @@ int main(int argc, char *argv[]) {
&secondary);
if (r < 0)
goto finish;
/* Now that we mounted the image, let's try to remove it again, if it is ephemeral */
if (remove_image && unlink(arg_image) >= 0)
remove_image = false;
}
r = custom_mounts_prepare();
@ -4321,20 +4357,35 @@ finish:
"STOPPING=1\nSTATUS=Terminating...");
if (pid > 0)
kill(pid, SIGKILL);
(void) kill(pid, SIGKILL);
/* Try to flush whatever is still queued in the pty */
if (master >= 0)
if (master >= 0) {
(void) copy_bytes(master, STDOUT_FILENO, (uint64_t) -1, false);
master = safe_close(master);
}
if (pid > 0)
(void) wait_for_terminate(pid, NULL);
loop_remove(loop_nr, &image_fd);
if (remove_subvol && arg_directory) {
if (remove_directory && arg_directory) {
int k;
k = btrfs_subvol_remove(arg_directory, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA);
k = rm_rf(arg_directory, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
if (k < 0)
log_warning_errno(k, "Cannot remove subvolume '%s', ignoring: %m", arg_directory);
log_warning_errno(k, "Cannot remove '%s', ignoring: %m", arg_directory);
}
if (remove_image && arg_image) {
if (unlink(arg_image) < 0)
log_warning_errno(errno, "Can't remove image file '%s', ignoring: %m", arg_image);
}
if (remove_tmprootdir) {
if (rmdir(tmprootdir) < 0)
log_debug_errno(errno, "Can't remove temporary root directory '%s', ignoring: %m", tmprootdir);
}
if (arg_machine) {

View File

@ -27,18 +27,20 @@
#include <sys/stat.h>
#include <unistd.h>
#include <linux/fs.h>
#include "alloc-util.h"
#include "btrfs-util.h"
#include "chattr-util.h"
#include "copy.h"
#include "dirent-util.h"
#include "env-util.h"
#include "fd-util.h"
#include "fs-util.h"
#include "hashmap.h"
#include "lockfile-util.h"
#include "log.h"
#include "macro.h"
#include "machine-image.h"
#include "macro.h"
#include "mkdir.h"
#include "path-util.h"
#include "rm-rf.h"
@ -607,14 +609,14 @@ int image_clone(Image *i, const char *new_name, bool read_only) {
new_path = strjoina("/var/lib/machines/", new_name);
r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA);
if (r == -EOPNOTSUPP) {
/* No btrfs snapshots supported, create a normal directory then. */
r = copy_directory(i->path, new_path, false);
if (r >= 0)
(void) chattr_path(new_path, read_only ? FS_IMMUTABLE_FL : 0, FS_IMMUTABLE_FL);
} else if (r >= 0)
r = btrfs_subvol_snapshot(i->path, new_path,
(read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) |
BTRFS_SNAPSHOT_FALLBACK_COPY |
BTRFS_SNAPSHOT_FALLBACK_DIRECTORY |
BTRFS_SNAPSHOT_FALLBACK_IMMUTABLE |
BTRFS_SNAPSHOT_RECURSIVE |
BTRFS_SNAPSHOT_QUOTA);
if (r >= 0)
/* Enable "subtree" quotas for the copy, if we didn't copy any quota from the source. */
(void) btrfs_subvol_auto_qgroup(new_path, 0, true);
@ -723,12 +725,17 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
* uses the device/inode number. This has the benefit that we
* can even lock a tree that is a mount point, correctly. */
if (path_equal(path, "/"))
return -EBUSY;
if (!path_is_absolute(path))
return -EINVAL;
if (getenv_bool("SYSTEMD_NSPAWN_LOCK") == 0) {
*local = *global = (LockFile) LOCK_FILE_INIT;
return 0;
}
if (path_equal(path, "/"))
return -EBUSY;
if (stat(path, &st) >= 0) {
if (asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0)
return -ENOMEM;
@ -746,7 +753,8 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
release_lock_file(&t);
return r;
}
}
} else
*global = (LockFile) LOCK_FILE_INIT;
*local = t;
return 0;
@ -782,6 +790,11 @@ int image_name_lock(const char *name, int operation, LockFile *ret) {
if (!image_name_is_valid(name))
return -EINVAL;
if (getenv_bool("SYSTEMD_NSPAWN_LOCK") == 0) {
*ret = (LockFile) LOCK_FILE_INIT;
return 0;
}
if (streq(name, ".host"))
return -EBUSY;