diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 4d335c64ee..16b13ed628 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -525,6 +525,18 @@ int path_compare_filename(const char *a, const char *b) { return strcmp(fa, fb); } +int path_equal_or_inode_same_full(const char *a, const char *b, int flags) { + /* Returns true if paths are of the same entry, false if not, <0 on error. */ + + if (path_equal(a, b)) + return 1; + + if (!a || !b) + return 0; + + return inode_same(a, b, flags); +} + char* path_extend_internal(char **x, ...) { size_t sz, old_sz; char *q, *nx; diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 6d88c54d7f..368e000121 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -68,8 +68,9 @@ static inline bool path_equal_filename(const char *a, const char *b) { return path_compare_filename(a, b) == 0; } +int path_equal_or_inode_same_full(const char *a, const char *b, int flags); static inline bool path_equal_or_inode_same(const char *a, const char *b, int flags) { - return path_equal(a, b) || inode_same(a, b, flags) > 0; + return path_equal_or_inode_same_full(a, b, flags) > 0; } char* path_extend_internal(char **x, ...); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 79b016f9c3..7c97070546 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -44,6 +44,7 @@ #include "process-util.h" #include "rm-rf.h" #include "sort-util.h" +#include "string-table.h" #include "string-util.h" #include "terminal-util.h" #include "user-util.h" @@ -52,8 +53,8 @@ #include "verbs.h" typedef enum MutableMode { - MUTABLE_YES, MUTABLE_NO, + MUTABLE_YES, MUTABLE_AUTO, MUTABLE_IMPORT, MUTABLE_EPHEMERAL, @@ -62,6 +63,17 @@ typedef enum MutableMode { _MUTABLE_INVALID = -EINVAL, } MutableMode; +static const char* const mutable_mode_table[_MUTABLE_MAX] = { + [MUTABLE_NO] = "no", + [MUTABLE_YES] = "yes", + [MUTABLE_AUTO] = "auto", + [MUTABLE_IMPORT] = "import", + [MUTABLE_EPHEMERAL] = "ephemeral", + [MUTABLE_EPHEMERAL_IMPORT] = "ephemeral-import", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(mutable_mode, MutableMode, MUTABLE_YES); + static char **arg_hierarchies = NULL; /* "/usr" + "/opt" by default for sysext and /etc by default for confext */ static char *arg_root = NULL; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; @@ -77,7 +89,7 @@ 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"; +#define MUTABLE_EXTENSIONS_BASE_DIR "/var/lib/extensions.mutable" STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); @@ -127,27 +139,7 @@ static const struct { }; 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; + return mutable_mode_from_string(p); } static int is_our_mount_point( @@ -198,7 +190,7 @@ static int is_our_mount_point( return log_error_errno(r, "Failed to parse device major/minor stored in '%s/dev' file on '%s': %m", image_class_info[image_class].dot_directory_name, p); if (lstat(p, &st) < 0) - return log_error_errno(r, "Failed to stat %s: %m", p); + return log_error_errno(errno, "Failed to stat %s: %m", p); if (st.st_dev != dev) { log_debug("Hierarchy '%s' reports a different device major/minor than what we are seeing, assuming offline copy.", p); @@ -538,7 +530,7 @@ static int verb_status(int argc, char **argv, void *userdata) { return log_oom(); if (stat(*p, &st) < 0) - return log_error_errno(r, "Failed to stat() '%s': %m", *p); + return log_error_errno(errno, "Failed to stat() '%s': %m", *p); r = table_add_many( t, @@ -673,10 +665,10 @@ static int paths_on_same_fs(const char *path1, const char *path2) { assert(path1); assert(path2); - if (stat(path1, &st1)) + if (stat(path1, &st1) < 0) return log_error_errno(errno, "Failed to stat '%s': %m", path1); - if (stat(path2, &st2)) + if (stat(path2, &st2) < 0) return log_error_errno(errno, "Failed to stat '%s': %m", path2); return st1.st_dev == st2.st_dev; @@ -725,6 +717,7 @@ static int work_dir_for_hierarchy( typedef struct OverlayFSPaths { char *hierarchy; + mode_t hierarchy_mode; char *resolved_hierarchy; char *resolved_mutable_directory; @@ -767,13 +760,42 @@ static int resolve_hierarchy(const char *hierarchy, char **ret_resolved_hierarch return 0; } +static int mutable_directory_mode_matches_hierarchy( + const char *root_or_null, + const char *path, + mode_t hierarchy_mode) { + + _cleanup_free_ char *path_in_root = NULL; + struct stat st; + mode_t actual_mode; + + assert(path); + + path_in_root = path_join(root_or_null, path); + if (!path_in_root) + return log_oom(); + + if (stat(path_in_root, &st) < 0) { + if (errno == ENOENT) + return 0; + return log_error_errno(errno, "Failed to stat mutable directory '%s': %m", path_in_root); + } + + actual_mode = st.st_mode & 0777; + if (actual_mode != hierarchy_mode) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Mutable directory '%s' has mode %04o, ought to have mode %04o", path_in_root, actual_mode, hierarchy_mode); + + return 0; +} + static int resolve_mutable_directory( const char *hierarchy, + mode_t hierarchy_mode, 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; + const char *root = arg_root, *base = MUTABLE_EXTENSIONS_BASE_DIR; int r; assert(hierarchy); @@ -800,6 +822,15 @@ static int resolve_mutable_directory( if (!path) return log_oom(); + if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_AUTO)) { + /* If there already is a mutable directory, check if its mode matches hierarchy. Merged + * hierarchy will have the same mode as the mutable directory, so we want no surprising mode + * changes here. */ + r = mutable_directory_mode_matches_hierarchy(root, path, hierarchy_mode); + if (r < 0) + return r; + } + if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) { _cleanup_free_ char *path_in_root = NULL; @@ -822,6 +853,8 @@ static int resolve_mutable_directory( 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; + mode_t hierarchy_mode; + int r; assert (hierarchy); @@ -834,7 +867,17 @@ static int overlayfs_paths_new(const char *hierarchy, const char *workspace_path r = resolve_hierarchy(hierarchy, &resolved_hierarchy); if (r < 0) return r; - r = resolve_mutable_directory(hierarchy, workspace_path, &resolved_mutable_directory); + + if (resolved_hierarchy) { + struct stat st; + + if (stat(resolved_hierarchy, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", resolved_hierarchy); + hierarchy_mode = st.st_mode & 0777; + } else + hierarchy_mode = 0755; + + r = resolve_mutable_directory(hierarchy, hierarchy_mode, workspace_path, &resolved_mutable_directory); if (r < 0) return r; @@ -845,6 +888,7 @@ static int overlayfs_paths_new(const char *hierarchy, const char *workspace_path *op = (OverlayFSPaths) { .hierarchy = TAKE_PTR(hierarchy_copy), + .hierarchy_mode = hierarchy_mode, .resolved_hierarchy = TAKE_PTR(resolved_hierarchy), .resolved_mutable_directory = TAKE_PTR(resolved_mutable_directory), }; @@ -853,16 +897,43 @@ static int overlayfs_paths_new(const char *hierarchy, const char *workspace_path 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. */ +static int determine_used_extensions(const char *hierarchy, char **paths, char ***ret_used_paths, size_t *ret_extensions_used) { + _cleanup_strv_free_ char **used_paths = NULL; + size_t n = 0; + int r; - if (path_equal(resolved_a, resolved_b)) - return 1; + assert(hierarchy); + assert(paths); + assert(ret_used_paths); + assert(ret_extensions_used); - if (!resolved_a || !resolved_b) - return 0; + STRV_FOREACH(p, paths) { + _cleanup_free_ char *resolved = NULL; - return inode_same(resolved_a, resolved_b, 0); + r = chase(hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL); + if (r == -ENOENT) { + log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", hierarchy, *p); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", hierarchy, *p); + + r = dir_is_empty(resolved, /* ignore_hidden_or_backup= */ false); + if (r < 0) + return log_error_errno(r, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved, *p); + if (r > 0) { + log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", hierarchy, *p); + continue; + } + + r = strv_consume_with_size (&used_paths, &n, TAKE_PTR(resolved)); + if (r < 0) + return log_oom(); + } + + *ret_used_paths = TAKE_PTR(used_paths); + *ret_extensions_used = n; + return 0; } static int maybe_import_mutable_directory(OverlayFSPaths *op) { @@ -876,7 +947,7 @@ static int maybe_import_mutable_directory(OverlayFSPaths *op) { if (arg_mutable != MUTABLE_IMPORT || !op->resolved_mutable_directory) return 0; - r = resolved_paths_equal(op->resolved_hierarchy, op->resolved_mutable_directory); + r = path_equal_or_inode_same_full(op->resolved_hierarchy, op->resolved_mutable_directory, 0); 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) @@ -904,15 +975,19 @@ static int maybe_import_ignored_mutable_directory(OverlayFSPaths *op) { if (!dir_name) return log_oom(); - path = path_join(mutable_extensions_base_dir, dir_name); + 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) + if (r == -ENOENT) { + log_debug("Mutable directory for %s does not exist, not importing", op->hierarchy); + return 0; + } + if (r < 0) return log_error_errno(r, "Failed to resolve mutable directory '%s': %m", path); - r = resolved_paths_equal(op->resolved_hierarchy, resolved_path); + r = path_equal_or_inode_same_full(op->resolved_hierarchy, resolved_path, 0); 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); @@ -948,41 +1023,17 @@ static int determine_top_lower_dirs(OverlayFSPaths *op, const char *meta_path) { return 0; } -static int determine_middle_lower_dirs(OverlayFSPaths *op, char **paths, size_t *ret_extensions_used) { - size_t n = 0; +static int determine_middle_lower_dirs(OverlayFSPaths *op, char **paths) { int r; assert(op); assert(paths); - assert(ret_extensions_used); - /* Put the extensions in the middle */ - STRV_FOREACH(p, paths) { - _cleanup_free_ char *resolved = NULL; + /* The paths were already determined in determine_used_extensions, so we just take them as is. */ + r = strv_extend_strv(&op->lower_dirs, paths, false); + if (r < 0) + return log_oom (); - r = chase(op->hierarchy, *p, CHASE_PREFIX_ROOT, &resolved, NULL); - if (r == -ENOENT) { - log_debug_errno(r, "Hierarchy '%s' in extension '%s' doesn't exist, not merging.", op->hierarchy, *p); - continue; - } - if (r < 0) - return log_error_errno(r, "Failed to resolve hierarchy '%s' in extension '%s': %m", op->hierarchy, *p); - - r = dir_is_empty(resolved, /* ignore_hidden_or_backup= */ false); - if (r < 0) - return log_error_errno(r, "Failed to check if hierarchy '%s' in extension '%s' is empty: %m", resolved, *p); - if (r > 0) { - log_debug("Hierarchy '%s' in extension '%s' is empty, not merging.", op->hierarchy, *p); - continue; - } - - r = strv_consume(&op->lower_dirs, TAKE_PTR(resolved)); - if (r < 0) - return log_oom(); - ++n; - } - - *ret_extensions_used = n; return 0; } @@ -1021,7 +1072,7 @@ static int hierarchy_as_lower_dir(OverlayFSPaths *op) { return 0; } - r = resolved_paths_equal(op->resolved_hierarchy, op->resolved_mutable_directory); + r = path_equal_or_inode_same_full(op->resolved_hierarchy, op->resolved_mutable_directory, 0); 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) { @@ -1052,21 +1103,19 @@ static int determine_bottom_lower_dirs(OverlayFSPaths *op) { static int determine_lower_dirs( OverlayFSPaths *op, char **paths, - const char *meta_path, - size_t *ret_extensions_used) { + const char *meta_path) { int r; assert(op); assert(paths); assert(meta_path); - assert(ret_extensions_used); r = determine_top_lower_dirs(op, meta_path); if (r < 0) return r; - r = determine_middle_lower_dirs(op, paths, ret_extensions_used); + r = determine_middle_lower_dirs(op, paths); if (r < 0) return r; @@ -1137,6 +1186,7 @@ static int mount_overlayfs_with_op( const char *meta_path) { int r; + const char *top_layer = NULL; assert(op); assert(overlay_path); @@ -1153,8 +1203,19 @@ static int mount_overlayfs_with_op( r = mkdir_p(op->work_dir, 0700); if (r < 0) return log_error_errno(r, "Failed to make directory '%s': %m", op->work_dir); + top_layer = op->upper_dir; + } else { + assert(!strv_isempty(op->lower_dirs)); + top_layer = op->lower_dirs[0]; } + /* Overlayfs merged directory has the same mode as the top layer (either first lowerdir in options in + * read-only case, or upperdir for mutable case. Set up top overlayfs layer to the same mode as the + * unmerged hierarchy, otherwise we might end up with merged hierarchy owned by root and with mode + * being 0700. */ + if (chmod(top_layer, op->hierarchy_mode) < 0) + return log_error_errno(errno, "Failed to set permissions of '%s' to %04o: %m", top_layer, op->hierarchy_mode); + r = mount_overlayfs(image_class, noexec, overlay_path, op->lower_dirs, op->upper_dir, op->work_dir); if (r < 0) return r; @@ -1327,6 +1388,7 @@ static int merge_hierarchy( const char *workspace_path) { _cleanup_(overlayfs_paths_freep) OverlayFSPaths *op = NULL; + _cleanup_strv_free_ char **used_paths = NULL; size_t extensions_used = 0; int r; @@ -1337,17 +1399,21 @@ static int merge_hierarchy( assert(overlay_path); assert(workspace_path); - r = overlayfs_paths_new(hierarchy, workspace_path, &op); - if (r < 0) - return r; - - r = determine_lower_dirs(op, paths, meta_path, &extensions_used); + r = determine_used_extensions(hierarchy, paths, &used_paths, &extensions_used); if (r < 0) return r; if (extensions_used == 0) /* No extension with files in this hierarchy? Then don't do anything. */ return 0; + r = overlayfs_paths_new(hierarchy, workspace_path, &op); + if (r < 0) + return r; + + r = determine_lower_dirs(op, used_paths, meta_path); + if (r < 0) + return r; + r = determine_upper_dir(op); if (r < 0) return r; @@ -1740,7 +1806,7 @@ static int merge(ImageClass image_class, 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"); + return log_error_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to merge hierarchies"); r = need_reload(image_class, hierarchies, no_reload); if (r < 0) @@ -2272,7 +2338,7 @@ static int sysext_main(int argc, char *argv[]) { } static int run(int argc, char *argv[]) { - const char* env_var; + const char *env_var; int r; log_setup(); diff --git a/test/units/testsuite-50.sysext.sh b/test/units/testsuite-50.sysext.sh index f3077f580d..8cfa0a485c 100755 --- a/test/units/testsuite-50.sysext.sh +++ b/test/units/testsuite-50.sysext.sh @@ -5,6 +5,8 @@ set -o pipefail FAKE_ROOTS_DIR="$(mktemp -d --tmpdir="" fake-roots-XXX)" +shopt -s nullglob + # shellcheck disable=SC2317 at_exit() { set +ex @@ -25,29 +27,82 @@ at_exit() { trap at_exit EXIT +# Clears the trap command - it needs to be invoked for every test-case subshell +# so prepending commands with prepend_trap inside the subshell won't preserve +# the trap commands from outer shell. +init_trap() { + trap - EXIT +} + +prepend_trap() { + set +x + + local command=${1}; shift + local previous_commands + + previous_commands=$(trap -p EXIT) + if [[ -z $previous_commands ]]; then + previous_commands=':' + else + previous_commands=${previous_commands#'trap -- '} + previous_commands=${previous_commands%' EXIT'} + previous_commands=$(xargs <<<"$previous_commands") + fi + + # shellcheck disable=SC2064 # We use double quotes on purpose here. + trap "${command}; ${previous_commands}" EXIT + + set -x +} + prepare_root() { - local root=${1:?} + local root=${1:-} local hierarchy=${2:?} local dir - if [[ -d $root ]]; then + if [[ -n $root ]] && [[ -d $root ]]; then echo >&2 "Directory $root already exists, possible copy-paste error?" exit 1 fi + local -a leftovers=( "$root/var/lib/extensions/"* "$root/var/lib/extensions.mutable/"* ) + if [[ ${#leftovers[@]} -gt 0 ]]; then + echo >&2 "Leftovers remained, make sure to clean them up in the test case: ${leftovers[*]}" + exit 1 + fi + for dir in "$hierarchy" "/usr/lib" "/var/lib/extensions/" "/var/lib/extensions.mutable"; do mkdir -p "$root$dir" done + if [[ -e $root/usr/lib/os-release ]]; then + mv "$root/usr/lib/os-release" "$root/usr/lib/os-release.orig" + fi + { echo "ID=testtest" echo "VERSION=1.2.3" } >"$root/usr/lib/os-release" + + prepend_trap "cleanup_os_release ${root@Q}" +} + +cleanup_os_release() { + # shellcheck disable=SC2317 # It is not unreachable, used in a trap couple lines above. + local root=${1:-} + + # shellcheck disable=SC2317 # It is not unreachable, used in a trap couple lines above. + rm -f "$root/usr/lib/os-release" + # shellcheck disable=SC2317 # It is not unreachable, used in a trap couple lines above. + if [[ -e $root/usr/lib/os-release.orig ]]; then + # shellcheck disable=SC2317 # It is not unreachable, used in a trap couple lines above. + mv "$root/usr/lib/os-release.orig" "$root/usr/lib/os-release" + fi } prepare_extension_image() { - local root="${1:?}" - local hierarchy="${2:?}" + local root=${1:-} + local hierarchy=${2:?} local ext_dir ext_release name name="test-extension" @@ -57,6 +112,8 @@ prepare_extension_image() { echo "ID=_any" >"$ext_release" mkdir -p "$ext_dir/$hierarchy" touch "$ext_dir$hierarchy/preexisting-file-in-extension-image" + + prepend_trap "rm -rf ${ext_dir@Q}" } prepare_extension_mutable_dir() { @@ -64,38 +121,60 @@ prepare_extension_mutable_dir() { mkdir -p "$dir" touch "$dir/preexisting-file-in-extensions-mutable" + prepend_trap "rm -rf ${dir@Q}" } make_read_only() { - local root="${1:?}" - local hierarchy="${2:?}" + local root=${1:-} + local hierarchy=${2:?} mount -o bind,ro "$root$hierarchy" "$root$hierarchy" + prepend_trap "umount ${root@Q}${hierarchy@Q}" } prepare_hierarchy() { - local root="${1:?}" - local hierarchy="${2:?}" + local root=${1:-} + local hierarchy=${2:?} + local file - touch "$root$hierarchy/preexisting-file-in-hierarchy" + file="$root$hierarchy/preexisting-file-in-hierarchy" + touch "$file" + prepend_trap "rm -f ${file@Q}" } prepare_read_only_hierarchy() { - local root="${1:?}" - local hierarchy="${2:?}" + local root=${1:-} + local hierarchy=${2:?} prepare_hierarchy "$root" "$hierarchy" make_read_only "$root" "$hierarchy" } +move_existing_hierarchy_aside() { + local root=${1:-} + local hierarchy=${2:?} + + if [[ -z $root ]] && [[ $hierarchy = /usr ]]; then + echo >&2 "Hell no, not moving /usr aside" + exit 1 + fi + + local path=$root$hierarchy + + if [[ -e $path ]]; then + mv "$path" "$path.orig" + prepend_trap "mv ${path@Q}.orig ${path@Q}" + fi +} + # Extra arguments: # -e: check for a preexisting file in extension # -h: check for a preexisting file in hierarchy # -u: check for a preexisting file in upperdir extension_verify() { - local root="${1:?}" - local hierarchy="${2:?}" - local message="${3:?}" + local root=${1:-} + local hierarchy=${2:?} + local message=${3:?} shift 3 # Map each option to a pre-defined file name local -A option_files_map=( @@ -108,12 +187,12 @@ extension_verify() { [h]=0 [u]=0 ) - local file full_path option + local file full_path opt option while getopts "ehu" opt; do case "$opt" in e|h|u) - args[$opt]=1 + args["$opt"]=1 ;; *) echo >&2 "Unxexpected option: $opt" @@ -121,20 +200,18 @@ extension_verify() { esac done - echo "${args[@]}" - for option in "${!option_files_map[@]}"; do - file="${option_files_map[$option]}" + file=${option_files_map["$option"]} full_path="$root$hierarchy/$file" - if [[ ${args[$option]} -ne 0 ]]; then - if [[ ! -f "$full_path" ]]; then + if [[ ${args["$option"]} -ne 0 ]]; then + if [[ ! -f $full_path ]]; then ls -la "$root$hierarchy" echo >&2 "Expected file '$file' to exist under $root$hierarchy $message" exit 1 fi else - if [[ -f "$full_path" ]]; then + if [[ -f $full_path ]]; then ls -la "$root$hierarchy" echo >&2 "Expected file '$file' to not exist under $root$hierarchy $message" exit 1 @@ -146,8 +223,8 @@ extension_verify() { extension_verify_after_merge() ( set +x - local root="${1:?}" - local hierarchy="${2:?}" + local root=${1:-} + local hierarchy=${2:?} shift 2 extension_verify "$root" "$hierarchy" "after merge" "$@" @@ -156,87 +233,120 @@ extension_verify_after_merge() ( extension_verify_after_unmerge() ( set +x - local root="${1:?}" - local hierarchy="${2:?}" + local root=${1:-} + local hierarchy=${2:?} shift 2 extension_verify "$root" "$hierarchy" "after unmerge" "$@" ) +run_systemd_sysext() { + local root=${1:-} + shift + + local -a sysext_args + sysext_args=() + + if [[ -n $root ]]; then + sysext_args+=( "--root=$root" ) + fi + sysext_args+=( "$@" ) + systemd-sysext "${sysext_args[@]}" +} + # General systemd-sysext tests +run_sysext_tests() { + # The roots_dir variable may be empty - in such case all the tests will run + # on /, otherwise they will run on $roots_dir/. + local roots_dir=${1}; shift + + # Each test runs in a subshell, so we can use traps for cleanups without + # clobbering toplevel traps, and we can do skips by invoking "exit 0". + +( init_trap : "No extension data in /var/lib/extensions.mutable/…, R/O hierarchy, mutability disabled by default, read-only merged" -fake_root="$FAKE_ROOTS_DIR/simple-read-only-with-read-only-hierarchy" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/simple-read-only-with-read-only-hierarchy"} +hierarchy=/opt prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" merge +run_systemd_sysext "$fake_root" merge (! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h (! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs") +) +( init_trap : "No extension data in /var/lib/extensions.mutable/…, mutable hierarchy, mutability disabled by default, read-only merged" -fake_root="$FAKE_ROOTS_DIR/simple-read-only-with-mutable-hierarchy" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/simple-read-only-with-mutable-hierarchy"} +hierarchy=/opt prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_hierarchy "$fake_root" "$hierarchy" touch "$fake_root$hierarchy/should-succeed-on-mutable-fs" -SYSTEMD_SYSEXT_HIERARCHIEe="$hierarchy" systemd-sysext --root="$fake_root" merge +run_systemd_sysext "$fake_root" merge (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h touch "$fake_root$hierarchy/should-succeed-on-mutable-fs-again" +) +( init_trap : "No extension data in /var/lib/extensions.mutable/…, no hierarchy either, mutability disabled by default, read-only merged" -fake_root="$FAKE_ROOTS_DIR/simple-read-only-with-missing-hierarchy" +fake_root=${roots_dir:+"$roots_dir/simple-read-only-with-missing-hierarchy"} hierarchy=/opt +move_existing_hierarchy_aside "$fake_root" "$hierarchy" prepare_root "$fake_root" "$hierarchy" rmdir "$fake_root/$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" merge +run_systemd_sysext "$fake_root" merge (! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs") extension_verify_after_merge "$fake_root" "$hierarchy" -e -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" +) +( init_trap : "No extension data in /var/lib/extensions.mutable/…, empty hierarchy, mutability disabled by default, read-only merged" -fake_root="$FAKE_ROOTS_DIR/simple-read-only-with-empty-hierarchy" +fake_root=${roots_dir:+"$roots_dir/simple-read-only-with-empty-hierarchy"} hierarchy=/opt +move_existing_hierarchy_aside "$fake_root" "$hierarchy" prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" make_read_only "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" merge +run_systemd_sysext "$fake_root" merge (! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs") extension_verify_after_merge "$fake_root" "$hierarchy" -e -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" +) +( init_trap : "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, mutability disabled by default, read-only merged" -fake_root="$FAKE_ROOTS_DIR/simple-mutable-with-read-only-hierarchy-disabled" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/simple-mutable-with-read-only-hierarchy-disabled"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -245,16 +355,19 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" merge +run_systemd_sysext "$fake_root" merge (! touch "$fake_root$hierarchy/should-be-read-only") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h +) + +( init_trap : "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, auto-mutability, mutable merged" -fake_root="$FAKE_ROOTS_DIR/simple-mutable-with-read-only-hierarchy" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/simple-mutable-with-read-only-hierarchy"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -263,63 +376,71 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge +run_systemd_sysext "$fake_root" --mutable=auto merge touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u test -f "$extension_data_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h test -f "$extension_data_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) +( init_trap : "Extension data in /var/lib/extensions.mutable/…, missing hierarchy, auto-mutability, mutable merged" -fake_root="$FAKE_ROOTS_DIR/simple-mutable-with-missing-hierarchy" +fake_root=${roots_dir:+"$roots_dir/simple-mutable-with-missing-hierarchy"} hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" +move_existing_hierarchy_aside "$fake_root" "$hierarchy" prepare_root "$fake_root" "$hierarchy" rmdir "$fake_root/$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_extension_mutable_dir "$extension_data_dir" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge +run_systemd_sysext "$fake_root" --mutable=auto merge touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -u test -f "$extension_data_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" test -f "$extension_data_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) +( init_trap : "Extension data in /var/lib/extensions.mutable/…, empty hierarchy, auto-mutability, mutable merged" -fake_root="$FAKE_ROOTS_DIR/simple-mutable-with-empty-hierarchy" +fake_root=${roots_dir:+"$roots_dir/simple-mutable-with-empty-hierarchy"} hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" +move_existing_hierarchy_aside "$fake_root" "$hierarchy" prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_extension_mutable_dir "$extension_data_dir" make_read_only "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge +run_systemd_sysext "$fake_root" --mutable=auto merge touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -u test -f "$extension_data_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" test -f "$extension_data_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) +( init_trap : "/var/lib/extensions.mutable/… is a symlink to other dir, R/O hierarchy, auto-mutability, mutable merged" -fake_root="$FAKE_ROOTS_DIR/mutable-symlink-with-read-only-hierarchy" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/mutable-symlink-with-read-only-hierarchy"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" extension_real_dir="$fake_root/upperdir" @@ -327,25 +448,28 @@ prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_extension_mutable_dir "$extension_real_dir" ln -sfTr "$extension_real_dir" "$extension_data_dir" +prepend_trap "rm -f ${extension_data_dir@Q}" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge +run_systemd_sysext "$fake_root" --mutable=auto merge touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u test -f "$extension_data_dir/now-is-mutable" test -f "$extension_real_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h test -f "$extension_data_dir/now-is-mutable" test -f "$extension_real_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) +( init_trap : "/var/lib/extensions.mutable/… is a symlink to the hierarchy itself, mutable hierarchy, auto-mutability, mutable merged" -fake_root="$FAKE_ROOTS_DIR/mutable-self-upper" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/mutable-self-upper"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" extension_real_dir="$fake_root$hierarchy" @@ -353,23 +477,26 @@ prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_extension_mutable_dir "$extension_real_dir" ln -sfTr "$extension_real_dir" "$extension_data_dir" +prepend_trap "rm -f ${extension_data_dir@Q}" touch "$fake_root$hierarchy/preexisting-file-in-hierarchy" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge +run_systemd_sysext "$fake_root" --mutable=auto merge touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u test -f "$extension_data_dir/now-is-mutable" test -f "$extension_real_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h -u test -f "$extension_data_dir/now-is-mutable" test -f "$extension_real_dir/now-is-mutable" +) +( init_trap : "/var/lib/extensions.mutable/… is a symlink to the hierarchy itself, R/O hierarchy, auto-mutability, expected fail" -fake_root="$FAKE_ROOTS_DIR/failure-self-upper-ro" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/failure-self-upper-ro"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" extension_real_dir="$fake_root$hierarchy" @@ -377,34 +504,39 @@ prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_extension_mutable_dir "$extension_real_dir" ln -sfTr "$extension_real_dir" "$extension_data_dir" +prepend_trap "rm -f ${extension_data_dir@Q}" prepare_read_only_hierarchy "$fake_root" "$hierarchy" -(! SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge) +(! run_systemd_sysext "$fake_root" --mutable=auto merge) +) +( init_trap : "/var/lib/extensions.mutable/… is a dangling symlink, auto-mutability, read-only merged" -fake_root="$FAKE_ROOTS_DIR/read-only-mutable-dangling-symlink" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/read-only-mutable-dangling-symlink"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" ln -sfTr "/should/not/exist/" "$extension_data_dir" +prepend_trap "rm -f ${extension_data_dir@Q}" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -# run systemd-sysext -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge +run_systemd_sysext "$fake_root" --mutable=auto merge (! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h +) +( init_trap : "/var/lib/extensions.mutable/… exists but ignored, mutability disabled explicitly, read-only merged" -fake_root="$FAKE_ROOTS_DIR/disabled" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/disabled"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -413,18 +545,19 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -# run systemd-sysext -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=no merge +run_systemd_sysext "$fake_root" --mutable=no merge (! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h +) +( init_trap : "/var/lib/extensions.mutable/… exists but is imported instead, read-only merged" -fake_root="$FAKE_ROOTS_DIR/imported" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/imported"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -433,19 +566,21 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -# run systemd-sysext -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=import merge +run_systemd_sysext "$fake_root" --mutable=import merge (! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h +) -: "/var/lib/extensions.mutable/… doesn't exists, mutability enabled, mutable merged" -fake_root="$FAKE_ROOTS_DIR/enabled" -hierarchy=/usr +( init_trap +: "/var/lib/extensions.mutable/… does not exist, mutability enabled, mutable merged" +fake_root=${roots_dir:+"$roots_dir/enabled"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" +extension_data_dir_usr="$fake_root/var/lib/extensions.mutable/usr" prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" @@ -453,39 +588,52 @@ prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") test ! -d "$extension_data_dir" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=yes merge +run_systemd_sysext "$fake_root" --mutable=yes merge +# systemd-sysext with --mutable=yes creates extensions.mutable directory for +# the hierarchy, so delete it after the test +prepend_trap "rm -rf ${extension_data_dir@Q}" +# systemd-sysext with --mutable=yes creates extensions.mutable directory also +# for the /usr hierarchy, because the image needs to have +# /usr/lib/extension-release.d/extension-release. file - this causes the +# /usr hierarchy to also become mutable +prepend_trap "rm -rf ${extension_data_dir_usr@Q}" test -d "$extension_data_dir" touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -h test -f "$extension_data_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h test -f "$extension_data_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) -: "/var/lib/extensions.mutable/… doesn't exists, auto-mutability, read-only merged" -fake_root="$FAKE_ROOTS_DIR/simple-read-only-explicit" -hierarchy=/usr +( init_trap +: "/var/lib/extensions.mutable/… does not exist, auto-mutability, read-only merged" +fake_root=${roots_dir:+"$roots_dir/simple-read-only-explicit"} +hierarchy=/opt prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=auto merge +run_systemd_sysext "$fake_root" --mutable=auto merge (! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h +) -: "/var/lib/extensions.mutable/… doesn't exists, mutability enabled through env var, mutable merged" -fake_root="$FAKE_ROOTS_DIR/enabled-env-var" -hierarchy=/usr +( init_trap +: "/var/lib/extensions.mutable/… does not exist, mutability enabled through env var, mutable merged" +fake_root=${roots_dir:+"$roots_dir/enabled-env-var"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" +extension_data_dir_usr="$fake_root/var/lib/extensions.mutable/usr" prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" @@ -493,38 +641,50 @@ prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") test ! -d "$extension_data_dir" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=yes systemd-sysext --root="$fake_root" merge +SYSTEMD_SYSEXT_MUTABLE_MODE=yes run_systemd_sysext "$fake_root" merge +# systemd-sysext with --mutable=yes creates extensions.mutable directory for +# the hierarchy, so delete it after the test +prepend_trap "rm -rf ${extension_data_dir@Q}" +# systemd-sysext with --mutable=yes creates extensions.mutable directory also +# for the /usr hierarchy, because the image needs to have +# /usr/lib/extension-release.d/extension-release. file - this causes the +# /usr hierarchy to also become mutable +prepend_trap "rm -rf ${extension_data_dir_usr@Q}" test -d "$extension_data_dir" touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -h test -f "$extension_data_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=yes systemd-sysext --root="$fake_root" unmerge +SYSTEMD_SYSEXT_MUTABLE_MODE=yes run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h test -f "$extension_data_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) -: "/var/lib/extensions.mutable/… doesn't exists, auto-mutability enabled through env var, read-only merged" -fake_root="$FAKE_ROOTS_DIR/read-only-auto-env-var" -hierarchy=/usr +( init_trap +: "/var/lib/extensions.mutable/… does not exist, auto-mutability enabled through env var, read-only merged" +fake_root=${roots_dir:+"$roots_dir/read-only-auto-env-var"} +hierarchy=/opt prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=auto systemd-sysext --root="$fake_root" --mutable=auto merge +SYSTEMD_SYSEXT_MUTABLE_MODE=auto run_systemd_sysext "$fake_root" --mutable=auto merge (! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=auto systemd-sysext --root="$fake_root" unmerge +SYSTEMD_SYSEXT_MUTABLE_MODE=auto run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h +) +( init_trap : "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, auto-mutability enabled through env var, mutable merged" -fake_root="$FAKE_ROOTS_DIR/auto-mutable-env-var" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/auto-mutable-env-var"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -533,20 +693,22 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=auto systemd-sysext --root="$fake_root" merge +SYSTEMD_SYSEXT_MUTABLE_MODE=auto run_systemd_sysext "$fake_root" merge touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u test -f "$extension_data_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=auto systemd-sysext --root="$fake_root" unmerge +SYSTEMD_SYSEXT_MUTABLE_MODE=auto run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h test -f "$extension_data_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) +( init_trap : "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, mutability disabled through env var, read-only merged" -fake_root="$FAKE_ROOTS_DIR/env-var-disabled" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/env-var-disabled"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -555,17 +717,19 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=no systemd-sysext --root="$fake_root" merge +SYSTEMD_SYSEXT_MUTABLE_MODE=no run_systemd_sysext "$fake_root" merge (! touch "$fake_root$hierarchy/should-be-read-only") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=no systemd-sysext --root="$fake_root" unmerge +SYSTEMD_SYSEXT_MUTABLE_MODE=no run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h +) +( init_trap : "/var/lib/extensions.mutable/… exists but is imported through env var, read-only merged" -fake_root="$FAKE_ROOTS_DIR/imported-env-var" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/imported-env-var"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -574,17 +738,19 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=import systemd-sysext --root="$fake_root" merge +SYSTEMD_SYSEXT_MUTABLE_MODE=import run_systemd_sysext "$fake_root" merge (! touch "$fake_root$hierarchy/should-still-fail-on-read-only-fs") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=import systemd-sysext --root="$fake_root" unmerge +SYSTEMD_SYSEXT_MUTABLE_MODE=import run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h +) +( init_trap : "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, mutability enabled through env var but overridden via CLI option, read-only merged" -fake_root="$FAKE_ROOTS_DIR/env-var-overridden" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/env-var-overridden"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -593,17 +759,19 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=yes systemd-sysext --root="$fake_root" --mutable=no merge +SYSTEMD_SYSEXT_MUTABLE_MODE=yes run_systemd_sysext "$fake_root" --mutable=no merge (! touch "$fake_root$hierarchy/should-be-read-only") extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=yes systemd-sysext --root="$fake_root" unmerge +SYSTEMD_SYSEXT_MUTABLE_MODE=yes run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h +) +( init_trap : "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, ephemeral mutability, mutable merged" -fake_root="$FAKE_ROOTS_DIR/ephemeral" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/ephemeral"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -612,20 +780,22 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=ephemeral merge +run_systemd_sysext "$fake_root" --mutable=ephemeral merge touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -h test ! -f "$extension_data_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h test ! -f "$extension_data_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) +( init_trap : "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, ephemeral mutability through env var, mutable merged" -fake_root="$FAKE_ROOTS_DIR/ephemeral-env-var" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/ephemeral-env-var"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -634,20 +804,22 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral systemd-sysext --root="$fake_root" merge +SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral run_systemd_sysext "$fake_root" merge touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -h test ! -f "$extension_data_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral systemd-sysext --root="$fake_root" unmerge +SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h test ! -f "$extension_data_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) +( init_trap : "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, ephemeral import mutability, mutable merged" -fake_root="$FAKE_ROOTS_DIR/ephemeral-import" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/ephemeral-import"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -656,21 +828,22 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -# run systemd-sysext -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=ephemeral-import merge +run_systemd_sysext "$fake_root" --mutable=ephemeral-import merge touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u test ! -f "$extension_data_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" unmerge +run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h test ! -f "$extension_data_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) +( init_trap : "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, ephemeral import mutability through env var, mutable merged" -fake_root="$FAKE_ROOTS_DIR/ephemeral-import-env-var" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/ephemeral-import-env-var"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" prepare_root "$fake_root" "$hierarchy" @@ -679,20 +852,22 @@ prepare_extension_mutable_dir "$extension_data_dir" prepare_read_only_hierarchy "$fake_root" "$hierarchy" (! touch "$fake_root$hierarchy/should-fail-on-read-only-fs") -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral-import systemd-sysext --root="$fake_root" merge +SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral-import run_systemd_sysext "$fake_root" merge touch "$fake_root$hierarchy/now-is-mutable" extension_verify_after_merge "$fake_root" "$hierarchy" -e -h -u test ! -f "$extension_data_dir/now-is-mutable" -SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral-import systemd-sysext --root="$fake_root" unmerge +SYSTEMD_SYSEXT_MUTABLE_MODE=ephemeral-import run_systemd_sysext "$fake_root" unmerge extension_verify_after_unmerge "$fake_root" "$hierarchy" -h test ! -f "$extension_data_dir/now-is-mutable" test ! -f "$fake_root$hierarchy/now-is-mutable" +) +( init_trap : "Extension data pointing to mutable hierarchy, ephemeral import mutability, expected fail" -fake_root="$FAKE_ROOTS_DIR/ephemeral-import-self" -hierarchy=/usr +fake_root=${roots_dir:+"$roots_dir/ephemeral-import-self"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" extension_real_dir="$fake_root$hierarchy" @@ -700,15 +875,18 @@ prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_extension_mutable_dir "$extension_real_dir" ln -sfTr "$extension_real_dir" "$extension_data_dir" +prepend_trap "rm -f ${extension_data_dir@Q}" prepare_hierarchy "$fake_root" "$hierarchy" touch "$fake_root$hierarchy/should-succeed-on-read-only-fs" -(! SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=ephemeral-import merge) +(! run_systemd_sysext "$fake_root" --mutable=ephemeral-import merge) +) -: "Extension data pointing to mutable hierarchy, import mutability, expected fail" -fake_root="$FAKE_ROOTS_DIR/import-self" -hierarchy=/usr +( init_trap +: "Extension data pointing to mutable hierarchy, import mutability, expected fail" +fake_root=${roots_dir:+"$roots_dir/import-self"} +hierarchy=/opt extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" extension_real_dir="$fake_root$hierarchy" @@ -716,9 +894,88 @@ prepare_root "$fake_root" "$hierarchy" prepare_extension_image "$fake_root" "$hierarchy" prepare_extension_mutable_dir "$extension_real_dir" ln -sfTr "$extension_real_dir" "$extension_data_dir" +prepend_trap "rm -f ${extension_data_dir@Q}" prepare_hierarchy "$fake_root" "$hierarchy" touch "$fake_root$hierarchy/should-succeed-on-read-only-fs" -(! SYSTEMD_SYSEXT_HIERARCHIES="$hierarchy" systemd-sysext --root="$fake_root" --mutable=import merge) +(! run_systemd_sysext "$fake_root" --mutable=import merge) +) + + +for mutable_mode in no yes ephemeral; do + ( init_trap + : "Check if merging the hierarchy does not change its permissions, checking with --mutable=${mutable_mode}" + + fake_root=${roots_dir:+"$roots_dir/perm-checks-mutable-$mutable_mode"} + hierarchy=/opt + extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" + + prepare_root "$fake_root" "$hierarchy" + prepare_extension_image "$fake_root" "$hierarchy" + prepare_extension_mutable_dir "$extension_data_dir" + prepare_read_only_hierarchy "${fake_root}" "${hierarchy}" + + full_path="$fake_root$hierarchy" + permissions_before_merge=$(stat --format=%A "$full_path") + + run_systemd_sysext "$fake_root" "--mutable=$mutable_mode" merge + if [[ $mutable_mode = yes ]]; then + # systemd-sysext with --mutable=yes creates extensions.mutable + # directory also for the /usr hierarchy, because the image needs to + # have /usr/lib/extension-release.d/extension-release. file - + # this causes the /usr hierarchy to also become mutable + extension_data_dir_usr="$fake_root/var/lib/extensions.mutable/usr" + prepend_trap "rm -rf ${extension_data_dir_usr@Q}" + fi + + permissions_after_merge=$(stat --format=%A "$full_path") + + run_systemd_sysext "$fake_root" unmerge + + permissions_after_unmerge=$(stat --format=%A "$full_path") + + if [[ "$permissions_before_merge" != "$permissions_after_merge" ]]; then + echo >&2 "Broken hierarchy permissions after merging with mutable mode ${mutable_mode@Q}, expected ${permissions_before_merge@Q}, got ${permissions_after_merge@Q}" + exit 1 + fi + + if [[ "$permissions_before_merge" != "$permissions_after_unmerge" ]]; then + echo >&2 "Broken hierarchy permissions after unmerging with mutable mode ${mutable_mode@Q}, expected ${permissions_before_merge@Q}, got ${permissions_after_unmerge@Q}" + exit 1 + fi + ) +done + + +( init_trap +: "Check if merging fails in case of invalid mutable directory permissions" + +fake_root=${roots_dir:+"$roots_dir/mutable-directory-with-invalid-permissions"} +hierarchy=/opt +extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" + +prepare_root "$fake_root" "$hierarchy" +prepare_extension_image "$fake_root" "$hierarchy" +prepare_extension_mutable_dir "$extension_data_dir" +prepare_hierarchy "$fake_root" "$hierarchy" + +old_mode=$(stat --format '%#a' "$fake_root$hierarchy") +chmod 0755 "$fake_root$hierarchy" +prepend_trap "chmod ${old_mode@Q} ${fake_root@Q}${hierarchy@Q}" +chmod 0700 "$extension_data_dir" + +(! run_systemd_sysext "$fake_root" --mutable=yes merge) +) + +} # End of run_sysext_tests + + +# For preparing /, we need mutable /usr/. If it is read only, skip running the +# sysext tests on /. +if [[ -w /usr ]]; then + run_sysext_tests '' +fi +run_sysext_tests "$FAKE_ROOTS_DIR" + exit 0