diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 52771226aa..449e284a91 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -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 diff --git a/man/rules/meson.build b/man/rules/meson.build index 38a0749fc7..d8862324db 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -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', diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml index df1f10aeef..5a6dffd2c5 100644 --- a/man/systemd-sysext.xml +++ b/man/systemd-sysext.xml @@ -3,7 +3,7 @@ "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"> - @@ -221,21 +221,30 @@ The following modes are supported: - : Force immutable mode even if write routing - directories exist below /var/lib/extensions.mutable/. - This is the default. - : Automatic mode. Mutability is disabled by default - and only enabled if a corresponding write routing directory exists below - /var/lib/extensions.mutable/. + : Force immutable mode even if write routing directories exist + below /var/lib/extensions.mutable/. This is the default. + : Automatic mode. Mutability is disabled by default and only + enabled if a corresponding write routing directory exists below + /var/lib/extensions.mutable/. : Force mutable mode and automatically create write routing - directories below /var/lib/extensions.mutable/ when required. + directories below /var/lib/extensions.mutable/ when required. : Force immutable mode like above, but - merge the contents of directories below /var/lib/extensions.mutable/ into the host - file system. + merge the contents of directories below /var/lib/extensions.mutable/ into the host + file system. + : Force mutable mode like above, but + instead of using write routing directory below /var/lib/extensions.mutable/, + systemd-sysext will use empty ephemeral directories. This means that the + modifications made in the merged hierarchies will be gone when the hierarchies are + unmerged. + : Force mutable mode like + above, but instead of ignoring the contents of write routing directories under + /var/lib/extensions.mutable/, merge them into the host file system, like + does. See "Options" below on specifying modes using the command line option. - Mutable mode routes writes to subdirectories in /var/lib/extensions.mutable/. + With exception of the ephemeral mode, the mutable mode routes writes to subdirectories in + /var/lib/extensions.mutable/. Writes to /usr/ are directed to /var/lib/extensions.mutable/usr/, writes to /opt/ are directed to /var/lib/extensions.mutable/opt/, and @@ -253,10 +262,11 @@ to route writes back to the original base directory hierarchy. - Alternatively, a temporary file system may be mounted to + Alternatively, a temporary file system may be mounted to /var/lib/extensions.mutable/, or symlinks in - /var/lib/extensions.mutable/ may point to sub-directories on a temporary - file system (e.g. below /tmp/) to only allow ephemeral changes. + /var/lib/extensions.mutable/ may point to sub-directories on a temporary file system + (e.g. below /tmp/) 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. @@ -403,6 +413,22 @@ /var/lib/extensions.mutable/ also merged into the host file system. + + + + force mutable mode, but with contents of write routing directories in + /var/lib/extensions.mutable/ being ignored, and modifications of the host + file system being discarded after unmerge. + + + + + + force mutable mode, with contents of write routing directories in + /var/lib/extensions.mutable/ being merged into the host file system, but + with the modifications made to the host file system being discarded after unmerge. + + diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 2329081992..b984e9b9a0 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -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; diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index 370e8f01ed..7c4a9db96d 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -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 #