Merge pull request #31801 from flatcar-hub/krnowak/sysext-config

systemd-sysext: Add support for env vars, ephemeral layers and some fixes
This commit is contained in:
Luca Boccassi 2024-03-26 09:23:19 +00:00 committed by GitHub
commit b1d18b96c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 665 additions and 64 deletions

View file

@ -378,6 +378,13 @@ All tools:
`$SYSTEMD_CONFEXT_HIERARCHIES` works for confext images and supports the
systemd-confext multi-call functionality of sysext.
* `$SYSTEMD_SYSEXT_MUTABLE_MODE` — this variable may be used to override the
default mutability mode for hierarchies managed by `systemd-sysext`. It takes
the same values the `--mutable=` command line switch does. Note that the
command line still overrides the effect of the environment
variable. Similarly, `$SYSTEMD_CONFEXT_MUTABLE_MODE` works for confext images
and supports the systemd-confext multi-call functionality of sysext.
`systemd-tmpfiles`:
* `$SYSTEMD_TMPFILES_FORCE_SUBVOL` — if unset, `v`/`q`/`Q` lines will create

View file

@ -1077,7 +1077,7 @@ manpages = [
['systemd-sysext',
'8',
['systemd-confext', 'systemd-confext.service', 'systemd-sysext.service'],
''],
'ENABLE_SYSEXT'],
['systemd-system-update-generator', '8', [], ''],
['systemd-system.conf',
'5',

View file

@ -3,7 +3,7 @@
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-sysext"
<refentry id="systemd-sysext" conditional='ENABLE_SYSEXT'
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
@ -221,21 +221,30 @@
<para>The following modes are supported:
<orderedlist>
<listitem><para><option>disabled</option>: Force immutable mode even if write routing
directories exist below <filename>/var/lib/extensions.mutable/</filename>.
This is the default.</para></listitem>
<listitem><para><option>auto</option>: Automatic mode. Mutability is disabled by default
and only enabled if a corresponding write routing directory exists below
<filename>/var/lib/extensions.mutable/</filename>.</para></listitem>
<listitem><para><option>disabled</option>: Force immutable mode even if write routing directories exist
below <filename>/var/lib/extensions.mutable/</filename>. This is the default.</para></listitem>
<listitem><para><option>auto</option>: Automatic mode. Mutability is disabled by default and only
enabled if a corresponding write routing directory exists below
<filename>/var/lib/extensions.mutable/</filename>.</para></listitem>
<listitem><para><option>enabled</option>: Force mutable mode and automatically create write routing
directories below <filename>/var/lib/extensions.mutable/</filename> when required.</para></listitem>
directories below <filename>/var/lib/extensions.mutable/</filename> when required.</para></listitem>
<listitem><para><option>import</option>: Force immutable mode like <option>disabled</option> above, but
merge the contents of directories below <filename>/var/lib/extensions.mutable/</filename> into the host
file system.</para></listitem>
merge the contents of directories below <filename>/var/lib/extensions.mutable/</filename> into the host
file system.</para></listitem>
<listitem><para><option>ephemeral</option>: Force mutable mode like <option>enabled</option> above, but
instead of using write routing directory below <filename>/var/lib/extensions.mutable/</filename>,
<command>systemd-sysext</command> will use empty ephemeral directories. This means that the
modifications made in the merged hierarchies will be gone when the hierarchies are
unmerged.</para></listitem>
<listitem><para><option>ephemeral-import</option>: Force mutable mode like <option>ephemeral</option>
above, but instead of ignoring the contents of write routing directories under
<filename>/var/lib/extensions.mutable/</filename>, merge them into the host file system, like
<option>import</option> does.</para></listitem>
</orderedlist>
See "Options" below on specifying modes using the <option>--mutable=</option> command line option.</para>
<para>Mutable mode routes writes to subdirectories in <filename>/var/lib/extensions.mutable/</filename>.
<para>With exception of the ephemeral mode, the mutable mode routes writes to subdirectories in
<filename>/var/lib/extensions.mutable/</filename>.
<simplelist type="horiz">
<member>Writes to <filename>/usr/</filename> are directed to <filename>/var/lib/extensions.mutable/usr/</filename></member>,
<member>writes to <filename>/opt/</filename> are directed to <filename>/var/lib/extensions.mutable/opt/</filename>, and</member>
@ -253,10 +262,11 @@
</simplelist>
to route writes back to the original base directory hierarchy.</para>
<para> Alternatively, a temporary file system may be mounted to
<para>Alternatively, a temporary file system may be mounted to
<filename>/var/lib/extensions.mutable/</filename>, or symlinks in
<filename>/var/lib/extensions.mutable/</filename> may point to sub-directories on a temporary
file system (e.g. below <filename>/tmp/</filename>) to only allow ephemeral changes.</para>
<filename>/var/lib/extensions.mutable/</filename> may point to sub-directories on a temporary file system
(e.g. below <filename>/tmp/</filename>) to only allow ephemeral changes. Note that this is not the same
as ephemeral mode, because the temporary file system will still exist after unmerging.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</refsect1>
@ -403,6 +413,22 @@
<filename>/var/lib/extensions.mutable/</filename> also merged into the host file system.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>ephemeral</option></term>
<listitem><para>force mutable mode, but with contents of write routing directories in
<filename>/var/lib/extensions.mutable/</filename> being ignored, and modifications of the host
file system being discarded after unmerge.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>ephemeral-import</option></term>
<listitem><para>force mutable mode, with contents of write routing directories in
<filename>/var/lib/extensions.mutable/</filename> being merged into the host file system, but
with the modifications made to the host file system being discarded after unmerge.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
</variablelist>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>

View file

@ -56,6 +56,8 @@ typedef enum MutableMode {
MUTABLE_NO,
MUTABLE_AUTO,
MUTABLE_IMPORT,
MUTABLE_EPHEMERAL,
MUTABLE_EPHEMERAL_IMPORT,
_MUTABLE_MAX,
_MUTABLE_INVALID = -EINVAL,
} MutableMode;
@ -75,6 +77,8 @@ static MutableMode arg_mutable = MUTABLE_NO;
/* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */
static ImageClass arg_image_class = IMAGE_SYSEXT;
static const char *mutable_extensions_base_dir = "/var/lib/extensions.mutable";
STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
@ -90,6 +94,7 @@ static const struct {
const char *level_env;
const char *scope_env;
const char *name_env;
const char *mode_env;
const ImagePolicy *default_image_policy;
unsigned long default_mount_flags;
} image_class_info[_IMAGE_CLASS_MAX] = {
@ -102,6 +107,7 @@ static const struct {
.level_env = "SYSEXT_LEVEL",
.scope_env = "SYSEXT_SCOPE",
.name_env = "SYSTEMD_SYSEXT_HIERARCHIES",
.mode_env = "SYSTEMD_SYSEXT_MUTABLE_MODE",
.default_image_policy = &image_policy_sysext,
.default_mount_flags = MS_RDONLY|MS_NODEV,
},
@ -114,11 +120,36 @@ static const struct {
.level_env = "CONFEXT_LEVEL",
.scope_env = "CONFEXT_SCOPE",
.name_env = "SYSTEMD_CONFEXT_HIERARCHIES",
.mode_env = "SYSTEMD_CONFEXT_MUTABLE_MODE",
.default_image_policy = &image_policy_confext,
.default_mount_flags = MS_RDONLY|MS_NODEV|MS_NOSUID|MS_NOEXEC,
}
};
static int parse_mutable_mode(const char *p) {
int r;
assert(p);
if (streq(p, "auto"))
return MUTABLE_AUTO;
if (streq(p, "import"))
return MUTABLE_IMPORT;
if (streq(p, "ephemeral"))
return MUTABLE_EPHEMERAL;
if (streq(p, "ephemeral-import"))
return MUTABLE_EPHEMERAL_IMPORT;
r = parse_boolean(p);
if (r < 0)
return r;
return r ? MUTABLE_YES : MUTABLE_NO;
}
static int is_our_mount_point(
ImageClass image_class,
const char *p) {
@ -636,17 +667,6 @@ static char *hierarchy_as_single_path_component(const char *hierarchy) {
return TAKE_PTR(dir_name);
}
static char *determine_mutable_directory_path_for_hierarchy(const char *hierarchy) {
_cleanup_free_ char *dir_name = NULL;
assert(hierarchy);
dir_name = hierarchy_as_single_path_component(hierarchy);
if (!dir_name)
return NULL;
return path_join("/var/lib/extensions.mutable", dir_name);
}
static int paths_on_same_fs(const char *path1, const char *path2) {
struct stat st1, st2;
@ -748,8 +768,13 @@ static int resolve_hierarchy(const char *hierarchy, char **ret_resolved_hierarch
return 0;
}
static int resolve_mutable_directory(const char *hierarchy, char **ret_resolved_mutable_directory) {
_cleanup_free_ char *path = NULL, *resolved_path = NULL;
static int resolve_mutable_directory(
const char *hierarchy,
const char *workspace,
char **ret_resolved_mutable_directory) {
_cleanup_free_ char *path = NULL, *resolved_path = NULL, *dir_name = NULL;
const char *root = arg_root, *base = mutable_extensions_base_dir;
int r;
assert(hierarchy);
@ -761,14 +786,25 @@ static int resolve_mutable_directory(const char *hierarchy, char **ret_resolved_
return 0;
}
path = determine_mutable_directory_path_for_hierarchy(hierarchy);
if (IN_SET(arg_mutable, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) {
/* We create mutable directory inside the temporary tmpfs workspace, which is a fixed
* location that ignores arg_root. */
root = NULL;
base = workspace;
}
dir_name = hierarchy_as_single_path_component(hierarchy);
if (!dir_name)
return log_oom();
path = path_join(base, dir_name);
if (!path)
return log_oom();
if (arg_mutable == MUTABLE_YES) {
if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) {
_cleanup_free_ char *path_in_root = NULL;
path_in_root = path_join(arg_root, path);
path_in_root = path_join(root, path);
if (!path_in_root)
return log_oom();
@ -777,7 +813,7 @@ static int resolve_mutable_directory(const char *hierarchy, char **ret_resolved_
return log_error_errno(r, "Failed to create a directory '%s': %m", path_in_root);
}
r = chase(path, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL);
r = chase(path, root, CHASE_PREFIX_ROOT, &resolved_path, NULL);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to resolve mutable directory '%s': %m", path);
@ -785,7 +821,7 @@ static int resolve_mutable_directory(const char *hierarchy, char **ret_resolved_
return 0;
}
static int overlayfs_paths_new(const char *hierarchy, OverlayFSPaths **ret_op) {
static int overlayfs_paths_new(const char *hierarchy, const char *workspace_path, OverlayFSPaths **ret_op) {
_cleanup_free_ char *hierarchy_copy = NULL, *resolved_hierarchy = NULL, *resolved_mutable_directory = NULL;
int r;
@ -799,7 +835,7 @@ static int overlayfs_paths_new(const char *hierarchy, OverlayFSPaths **ret_op) {
r = resolve_hierarchy(hierarchy, &resolved_hierarchy);
if (r < 0)
return r;
r = resolve_mutable_directory(hierarchy, &resolved_mutable_directory);
r = resolve_mutable_directory(hierarchy, workspace_path, &resolved_mutable_directory);
if (r < 0)
return r;
@ -818,6 +854,79 @@ static int overlayfs_paths_new(const char *hierarchy, OverlayFSPaths **ret_op) {
return 0;
}
static int resolved_paths_equal(const char *resolved_a, const char *resolved_b) {
/* Returns true if paths are of the same entry, false if not, <0 on error. */
if (path_equal(resolved_a, resolved_b))
return 1;
if (!resolved_a || !resolved_b)
return 0;
return inode_same(resolved_a, resolved_b, 0);
}
static int maybe_import_mutable_directory(OverlayFSPaths *op) {
int r;
assert(op);
/* If importing mutable layer and it actually exists and is not a hierarchy itself, add it just below
* the meta path */
if (arg_mutable != MUTABLE_IMPORT || !op->resolved_mutable_directory)
return 0;
r = resolved_paths_equal(op->resolved_hierarchy, op->resolved_mutable_directory);
if (r < 0)
return log_error_errno(r, "Failed to check equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory);
if (r > 0)
return log_error_errno(SYNTHETIC_ERRNO(ELOOP), "Not importing mutable directory for hierarchy %s as a lower dir, because it points to the hierarchy itself", op->hierarchy);
r = strv_extend(&op->lower_dirs, op->resolved_mutable_directory);
if (r < 0)
return log_oom();
return 0;
}
static int maybe_import_ignored_mutable_directory(OverlayFSPaths *op) {
_cleanup_free_ char *dir_name = NULL, *path = NULL, *resolved_path = NULL;
int r;
assert(op);
/* If importing the ignored mutable layer and it actually exists and is not a hierarchy itself, add
* it just below the meta path */
if (arg_mutable != MUTABLE_EPHEMERAL_IMPORT)
return 0;
dir_name = hierarchy_as_single_path_component(op->hierarchy);
if (!dir_name)
return log_oom();
path = path_join(mutable_extensions_base_dir, dir_name);
if (!path)
return log_oom();
r = chase(path, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL);
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to resolve mutable directory '%s': %m", path);
r = resolved_paths_equal(op->resolved_hierarchy, resolved_path);
if (r < 0)
return log_error_errno(r, "Failed to check equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory);
if (r > 0)
return log_error_errno(SYNTHETIC_ERRNO(ELOOP), "Not importing mutable directory for hierarchy %s as a lower dir, because it points to the hierarchy itself", op->hierarchy);
r = strv_consume(&op->lower_dirs, TAKE_PTR(resolved_path));
if (r < 0)
return log_oom();
return 0;
}
static int determine_top_lower_dirs(OverlayFSPaths *op, const char *meta_path) {
int r;
@ -829,12 +938,13 @@ static int determine_top_lower_dirs(OverlayFSPaths *op, const char *meta_path) {
if (r < 0)
return log_oom();
/* If importing mutable layer and it actually exists, add it just below the meta path */
if (arg_mutable == MUTABLE_IMPORT && op->resolved_mutable_directory) {
r = strv_extend(&op->lower_dirs, op->resolved_mutable_directory);
if (r < 0)
return r;
}
r = maybe_import_mutable_directory(op);
if (r < 0)
return r;
r = maybe_import_ignored_mutable_directory(op);
if (r < 0)
return r;
return 0;
}
@ -898,7 +1008,12 @@ static int hierarchy_as_lower_dir(OverlayFSPaths *op) {
}
if (arg_mutable == MUTABLE_IMPORT) {
log_debug("Mutability for host hierarchy '%s' is disabled, so it will be a lowerdir", op->resolved_hierarchy);
log_debug("Mutability for host hierarchy '%s' is disabled, so host hierarchy will be a lowerdir", op->resolved_hierarchy);
return 0;
}
if (arg_mutable == MUTABLE_EPHEMERAL_IMPORT) {
log_debug("Mutability for host hierarchy '%s' is ephemeral, so host hierarchy will be a lowerdir", op->resolved_hierarchy);
return 0;
}
@ -907,13 +1022,9 @@ static int hierarchy_as_lower_dir(OverlayFSPaths *op) {
return 0;
}
if (path_equal(op->resolved_hierarchy, op->resolved_mutable_directory)) {
log_debug("Host hierarchy '%s' will serve as upperdir.", op->resolved_hierarchy);
return 1;
}
r = inode_same(op->resolved_hierarchy, op->resolved_mutable_directory, 0);
r = resolved_paths_equal(op->resolved_hierarchy, op->resolved_mutable_directory);
if (r < 0)
return log_error_errno(r, "Failed to check inode equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory);
return log_error_errno(r, "Failed to check equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory);
if (r > 0) {
log_debug("Host hierarchy '%s' will serve as upperdir.", op->resolved_hierarchy);
return 1;
@ -933,7 +1044,7 @@ static int determine_bottom_lower_dirs(OverlayFSPaths *op) {
if (!r) {
r = strv_extend(&op->lower_dirs, op->resolved_hierarchy);
if (r < 0)
return r;
return log_oom();
}
return 0;
@ -1116,6 +1227,10 @@ static int write_work_dir_file(ImageClass image_class, const char *meta_path, co
if (!work_dir)
return 0;
/* Do not store work dir path for ephemeral mode, it will be gone once this process is done. */
if (IN_SET(arg_mutable, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT))
return 0;
work_dir_in_root = path_startswith(work_dir, empty_to_root(arg_root));
if (!work_dir_in_root)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Workdir '%s' must not be outside root '%s'", work_dir, empty_to_root(arg_root));
@ -1209,7 +1324,8 @@ static int merge_hierarchy(
char **extensions,
char **paths,
const char *meta_path,
const char *overlay_path) {
const char *overlay_path,
const char *workspace_path) {
_cleanup_(overlayfs_paths_freep) OverlayFSPaths *op = NULL;
size_t extensions_used = 0;
@ -1220,8 +1336,9 @@ static int merge_hierarchy(
assert(paths);
assert(meta_path);
assert(overlay_path);
assert(workspace_path);
r = overlayfs_paths_new(hierarchy, &op);
r = overlayfs_paths_new(hierarchy, workspace_path, &op);
if (r < 0)
return r;
@ -1530,7 +1647,7 @@ static int merge_subprocess(
/* Create overlayfs mounts for all hierarchies */
STRV_FOREACH(h, hierarchies) {
_cleanup_free_ char *meta_path = NULL, *overlay_path = NULL;
_cleanup_free_ char *meta_path = NULL, *overlay_path = NULL, *merge_hierarchy_workspace = NULL;
meta_path = path_join(workspace, "meta", *h); /* The place where to store metadata about this instance */
if (!meta_path)
@ -1540,6 +1657,11 @@ static int merge_subprocess(
if (!overlay_path)
return log_oom();
/* Temporary directory for merge_hierarchy needs, like ephemeral directories. */
merge_hierarchy_workspace = path_join(workspace, "mh_workspace", *h);
if (!merge_hierarchy_workspace)
return log_oom();
r = merge_hierarchy(
image_class,
*h,
@ -1547,7 +1669,8 @@ static int merge_subprocess(
extensions,
paths,
meta_path,
overlay_path);
overlay_path,
merge_hierarchy_workspace);
if (r < 0)
return r;
}
@ -1615,9 +1738,10 @@ static int merge(ImageClass image_class,
r = wait_for_terminate_and_check("(sd-merge)", pid, WAIT_LOG_ABNORMAL);
if (r < 0)
return r;
if (r == 123) /* exit code 123 means: didn't do anything */
return 0;
if (r > 0)
return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Failed to merge hierarchies");
r = need_reload(image_class, hierarchies, no_reload);
if (r < 0)
@ -1996,6 +2120,8 @@ static int verb_help(int argc, char **argv, void *userdata) {
" -h --help Show this help\n"
" --version Show package version\n"
"\n%3$sOptions:%4$s\n"
" --mutable=yes|no|auto|import|ephemeral|ephemeral-import\n"
" Specify a mutability mode of the merged hierarchy\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers and footers\n"
" --root=PATH Operate relative to root path\n"
@ -2109,16 +2235,10 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_MUTABLE:
if (streq(optarg, "auto"))
arg_mutable = MUTABLE_AUTO;
else if (streq(optarg, "import"))
arg_mutable = MUTABLE_IMPORT;
else {
r = parse_boolean(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse argument to --mutable=: %s", optarg);
arg_mutable = r ? MUTABLE_YES : MUTABLE_NO;
}
r = parse_mutable_mode(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse argument to --mutable=: %s", optarg);
arg_mutable = r;
break;
case '?':
@ -2153,12 +2273,23 @@ static int sysext_main(int argc, char *argv[]) {
}
static int run(int argc, char *argv[]) {
const char* env_var;
int r;
log_setup();
arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT;
env_var = getenv(image_class_info[arg_image_class].mode_env);
if (env_var) {
r = parse_mutable_mode(env_var);
if (r < 0)
log_warning("Failed to parse %s environment variable value '%s'. Ignoring.",
image_class_info[arg_image_class].mode_env, env_var);
else
arg_mutable = r;
}
r = parse_argv(argc, argv);
if (r <= 0)
return r;

View file

@ -808,9 +808,23 @@ prep_root() {
local r=${1}; shift
local h=${1}; shift
if [[ -d ${r} ]]; then
die "${r@Q} is being reused as a root, possibly a result of copy-pasting some test case and forgetting to rename the root directory name"
fi
mkdir -p "${r}${h}" "${r}/usr/lib" "${r}/var/lib/extensions" "${r}/var/lib/extensions.mutable"
}
prep_env() {
local mode=${1}; shift
export SYSTEMD_SYSEXT_MUTABLE_MODE=${mode}
}
drop_env() {
unset -v SYSTEMD_SYSEXT_MUTABLE_MODE
}
gen_os_release() {
local r=${1}; shift
@ -946,6 +960,7 @@ check_usual_suspects_after_unmerge() {
check_usual_suspects "${r}" "${h}" "after unmerge" "${@}"
}
drop_env
#
# no extension data in /var/lib/extensions.mutable/…, read-only hierarchy,
@ -978,6 +993,7 @@ check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only after unmerge"
#
# no extension data in /var/lib/extensions.mutable/…, mutable hierarchy,
# mutability disabled by default
@ -1307,7 +1323,7 @@ ln -sfTr "${real_ext_dir}" "${ext_data_path}"
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge || die "expected merge to fail"
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge && die "expected merge to fail"
#
@ -1476,6 +1492,427 @@ SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" u
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
#
# /var/lib/extensions.mutable/… does not exist, but mutability is enabled
# through an env var
#
# mutable merged
#
fake_root=${fake_roots_dir}/enabled-env-var
hierarchy=/usr
prep_env "yes"
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
test ! -d "${ext_data_path}" || die "extensions.mutable should not exist"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
test -d "${ext_data_path}" || die "extensions.mutable should exist now"
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable is not stored in expected location"
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable disappeared from writable storage after unmerge"
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
drop_env
#
# /var/lib/extensions.mutable/… does not exist, auto-mutability through an env
# var
#
# read-only merged
#
fake_root=${fake_roots_dir}/read-only-auto-env-var
hierarchy=/usr
prep_env "auto"
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=auto merge
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
drop_env
#
# extension data in /var/lib/extensions.mutable/…, read-only hierarchy,
# auto-mutability through an env var
#
# mutable merged
#
fake_root=${fake_roots_dir}/auto-mutable-env-var
hierarchy=/usr
prep_env "auto"
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
prep_ext_mut "${ext_data_path}"
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h u
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable is not stored in expected location"
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
test -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable disappeared from writable storage after unmerge"
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
drop_env
#
# extension data in /var/lib/extensions.mutable/…, read-only hierarchy,
# mutability disabled through an env var
#
# read-only merged
#
fake_root=${fake_roots_dir}/env-var-disabled
hierarchy=/usr
prep_env "no"
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
prep_ext_mut "${ext_data_path}"
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
touch "${fake_root}${hierarchy}/should-be-read-only" && die "${fake_root}${hierarchy} is not read-only"
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
drop_env
#
# /var/lib/extensions.mutable/… exists, but it's imported instead through an
# env var
#
# read-only merged
#
fake_root=${fake_roots_dir}/imported-env-var
hierarchy=/usr
prep_env "import"
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
prep_ext_mut "${ext_data_path}"
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
touch "${fake_root}${hierarchy}/should-still-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h u
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
drop_env
#
# extension data in /var/lib/extensions.mutable/…, read-only hierarchy,
# mutability enabled through an env var, but overridden with a command-line
# option
#
# read-only merged
#
fake_root=${fake_roots_dir}/env-var-overridden
hierarchy=/usr
prep_env "yes"
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
prep_ext_mut "${ext_data_path}"
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=no merge
touch "${fake_root}${hierarchy}/should-be-read-only" && die "${fake_root}${hierarchy} is not read-only"
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
drop_env
#
# extension data in /var/lib/extensions.mutable/…, read-only hierarchy,
# ephemeral mutability, so extension data contents are ignored
#
# mutable merged
#
fake_root=${fake_roots_dir}/ephemeral
hierarchy=/usr
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
prep_ext_mut "${ext_data_path}"
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=ephemeral merge
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
test ! -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable should not be stored in extension data"
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
test ! -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable should not appear in writable storage after unmerge"
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
#
# extension data in /var/lib/extensions.mutable/…, read-only hierarchy,
# ephemeral mutability through an env var, so extension data contents are
# ignored
#
# mutable merged
#
fake_root=${fake_roots_dir}/ephemeral-env-var
hierarchy=/usr
prep_env "ephemeral"
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
prep_ext_mut "${ext_data_path}"
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h
test ! -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable should not be stored in extension data"
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
test ! -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable should not appear in writable storage after unmerge"
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
drop_env
#
# extension data in /var/lib/extensions.mutable/…, read-only hierarchy,
# ephemeral import mutability, so extension data contents are imported too
#
# mutable merged
#
fake_root=${fake_roots_dir}/ephemeral-import
hierarchy=/usr
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
prep_ext_mut "${ext_data_path}"
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=ephemeral-import merge
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h u
test ! -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable should not be stored in extension data"
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
test ! -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable should not appear in writable storage after unmerge"
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
#
# extension data in /var/lib/extensions.mutable/…, read-only hierarchy,
# ephemeral mutability through an env var, so extension data contents are
# imported too
#
# mutable merged
#
fake_root=${fake_roots_dir}/ephemeral-import-env-var
hierarchy=/usr
prep_env "ephemeral-import"
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
prep_ext_mut "${ext_data_path}"
prep_ro_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-fail-on-read-only-fs" && die "${fake_root}${hierarchy} is not read-only"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" merge
touch "${fake_root}${hierarchy}/now-is-mutable" || die "${fake_root}${hierarchy} is not mutable"
check_usual_suspects_after_merge "${fake_root}" "${hierarchy}" e h u
test ! -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable should not be stored in extension data"
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" unmerge
check_usual_suspects_after_unmerge "${fake_root}" "${hierarchy}" h
test ! -f "${ext_data_path}/now-is-mutable" || die "now-is-mutable should not appear in writable storage after unmerge"
test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did not disappear from hierarchy after unmerge"
drop_env
#
# extension data pointing to mutable hierarchy, ephemeral import mutability
#
# expecting a failure here
#
fake_root=${fake_roots_dir}/ephemeral-import-self
hierarchy=/usr
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
real_ext_dir="${fake_root}${hierarchy}"
prep_ext_mut "${real_ext_dir}"
ln -sfTr "${real_ext_dir}" "${ext_data_path}"
prep_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-succeed-on-read-only-fs" || die "${fake_root}${hierarchy} is not mutable"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=ephemeral-import merge && die 'expected merge to fail'
#
# extension data pointing to mutable hierarchy, import mutability
#
# expecting a failure here
#
fake_root=${fake_roots_dir}/import-self
hierarchy=/usr
prep_root "${fake_root}" "${hierarchy}"
gen_os_release "${fake_root}"
gen_test_ext_image "${fake_root}" "${hierarchy}"
ext_data_path=$(hierarchy_ext_mut_path "${fake_root}" "${hierarchy}")
real_ext_dir="${fake_root}${hierarchy}"
prep_ext_mut "${real_ext_dir}"
ln -sfTr "${real_ext_dir}" "${ext_data_path}"
prep_hierarchy "${fake_root}" "${hierarchy}"
touch "${fake_root}${hierarchy}/should-succeed-on-read-only-fs" || die "${fake_root}${hierarchy} is not mutable"
# run systemd-sysext
SYSTEMD_SYSEXT_HIERARCHIES="${hierarchy}" systemd-sysext --root="${fake_root}" --mutable=import merge && die 'expected merge to fail'
#
# done
#