From 738eaf969744503b83fddb340905c1871da1196c Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Tue, 19 Mar 2024 13:20:46 +0100 Subject: [PATCH 01/16] sysext: Move parsing mutable mode to a separate function --- src/sysext/sysext.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 2329081992..496c5596a7 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -119,6 +119,24 @@ 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; + + 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) { @@ -2109,16 +2127,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 '?': From ad43c784bb537eea2d175087f621e5687fc5dee1 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Tue, 19 Mar 2024 14:01:00 +0100 Subject: [PATCH 02/16] sysext: Add support for mutable mode environment variables The environment variable names are SYSTEMD_SYSEXT_MUTABLE_MODE for systemd-sysext and SYSTEMD_CONFEXT_MUTABLE_MODE for systemd-confext. These override the default mutable mode setting, but can be still overridden by a command-line flag. --- src/sysext/sysext.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 496c5596a7..0fd5afe221 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -90,6 +90,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 +103,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,6 +116,7 @@ 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, } @@ -2165,12 +2168,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; From 33cb10f68d27defd394da67b221e2ae7220a8ba9 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Wed, 20 Mar 2024 08:34:41 +0100 Subject: [PATCH 03/16] docs: Document SYSTEMD_SYSEXT_MUTABLE_MODE env var --- docs/ENVIRONMENT.md | 7 +++++++ 1 file changed, 7 insertions(+) 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 From 73cfa16d1695e3ba8b62c4f1db0625c14b1f16a9 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Tue, 19 Mar 2024 14:40:57 +0100 Subject: [PATCH 04/16] test: Fail sysext test if reusing a root directory --- test/units/testsuite-50.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index 370e8f01ed..dc31ce4da3 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -808,6 +808,10 @@ 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" } From 9245b547cc21cbe940731482fc7dcc7dc001999b Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Tue, 27 Feb 2024 14:52:30 +0100 Subject: [PATCH 05/16] test: Extend sysext tests with cases using env var for mutable mode --- test/units/testsuite-50.sh | 229 +++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index dc31ce4da3..fca4de2954 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -815,6 +815,16 @@ prep_root() { 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 @@ -950,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, @@ -982,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 @@ -1480,6 +1492,223 @@ 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 + + # # done # From ca49d4c33dd6ce8e1a1dd8cfa5434c3fc6be2b63 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Tue, 19 Mar 2024 14:47:36 +0100 Subject: [PATCH 06/16] man: Install sysext man pages when ENABLE_SYSEXT is true --- man/rules/meson.build | 2 +- man/systemd-sysext.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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..e086796411 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"> - From 454bb41030bd5eda3fdad525548d79467e19b3f9 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Tue, 27 Feb 2024 16:29:08 +0100 Subject: [PATCH 07/16] sysext: Add missing --mutable mode in help output --- src/sysext/sysext.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 0fd5afe221..0e39e1c1b2 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -2017,6 +2017,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\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" From 6be5b442392c50c33dce4d9673be4906fa1c531b Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Wed, 28 Feb 2024 09:17:27 +0100 Subject: [PATCH 08/16] sysext: Implement ephemeral mode To enable it, use "ephemeral" either for mutable mode environment variable or for value of "--mutable=" flag. Instead of using mutable dir in /var/lib/extensions.mutable/, we create a directory for overlayfs upperdir and workdir in the same tmpfs mount that sysext worker process creates in /run/systemd/sysext. As the path for the workdir will be gone when the worker quits, there is no need to do any additional cleanup. As such, there is also no need to store a path to workdir in the metadata directory. --- src/sysext/sysext.c | 69 +++++++++++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 0e39e1c1b2..51333dc6fd 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -56,6 +56,7 @@ typedef enum MutableMode { MUTABLE_NO, MUTABLE_AUTO, MUTABLE_IMPORT, + MUTABLE_EPHEMERAL, _MUTABLE_MAX, _MUTABLE_INVALID = -EINVAL, } MutableMode; @@ -133,6 +134,9 @@ static int parse_mutable_mode(const char *p) { if (streq(p, "import")) return MUTABLE_IMPORT; + if (streq(p, "ephemeral")) + return MUTABLE_EPHEMERAL; + r = parse_boolean(p); if (r < 0) return r; @@ -657,17 +661,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; @@ -769,8 +762,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 = "/var/lib/extensions.mutable"; int r; assert(hierarchy); @@ -782,14 +780,25 @@ static int resolve_mutable_directory(const char *hierarchy, char **ret_resolved_ return 0; } - path = determine_mutable_directory_path_for_hierarchy(hierarchy); + if (arg_mutable == MUTABLE_EPHEMERAL) { + /* 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)) { _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(); @@ -798,7 +807,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); @@ -806,7 +815,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; @@ -820,7 +829,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; @@ -1137,6 +1146,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 (arg_mutable == MUTABLE_EPHEMERAL) + 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)); @@ -1230,7 +1243,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; @@ -1241,8 +1255,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; @@ -1551,7 +1566,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) @@ -1561,6 +1576,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, @@ -1568,7 +1588,8 @@ static int merge_subprocess( extensions, paths, meta_path, - overlay_path); + overlay_path, + merge_hierarchy_workspace); if (r < 0) return r; } @@ -2017,7 +2038,7 @@ 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\n" + " --mutable=yes|no|auto|import|ephemeral\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" From 504bdf08b52364913d00339316c0efca821354ee Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Wed, 28 Feb 2024 09:42:25 +0100 Subject: [PATCH 09/16] man: Document sysext ephemeral mode --- man/systemd-sysext.xml | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml index e086796411..df5dcafbd1 100644 --- a/man/systemd-sysext.xml +++ b/man/systemd-sysext.xml @@ -221,21 +221,26 @@ 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. 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 +258,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 +409,14 @@ /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. + + From 153e7f7b24d56ff292dd1b79d4b97568e9fe36d3 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Wed, 28 Feb 2024 09:59:18 +0100 Subject: [PATCH 10/16] test: Extend sysext tests with cases using ephemeral mode --- test/units/testsuite-50.sh | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index fca4de2954..9b20b1a29b 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -1709,6 +1709,81 @@ 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 + + # # done # From 3d6a34a591ca0415337a7728525459d4ed068f90 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Wed, 28 Feb 2024 14:23:22 +0100 Subject: [PATCH 11/16] sysext: Implement ephemeral import mode To enable it, use "ephemeral-import" either for mutable mode environment variable or for value of "--mutable=" flag. This is a combination of "ephemeral" and "import" modes. It results in a mutable hierarchy that includes contents of the mutable extension data, but the modifications are thrown away when the hierarchy is unmerged. --- src/sysext/sysext.c | 123 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 104 insertions(+), 19 deletions(-) diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 51333dc6fd..57f06b2678 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -57,6 +57,7 @@ typedef enum MutableMode { MUTABLE_AUTO, MUTABLE_IMPORT, MUTABLE_EPHEMERAL, + MUTABLE_EPHEMERAL_IMPORT, _MUTABLE_MAX, _MUTABLE_INVALID = -EINVAL, } MutableMode; @@ -76,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); @@ -137,6 +140,9 @@ static int parse_mutable_mode(const char *p) { 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; @@ -768,7 +774,7 @@ static int resolve_mutable_directory( char **ret_resolved_mutable_directory) { _cleanup_free_ char *path = NULL, *resolved_path = NULL, *dir_name = NULL; - const char *root = arg_root, *base = "/var/lib/extensions.mutable"; + const char *root = arg_root, *base = mutable_extensions_base_dir; int r; assert(hierarchy); @@ -780,7 +786,7 @@ static int resolve_mutable_directory( return 0; } - if (arg_mutable == MUTABLE_EPHEMERAL) { + 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; @@ -795,7 +801,7 @@ static int resolve_mutable_directory( if (!path) return log_oom(); - if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_EPHEMERAL)) { + if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) { _cleanup_free_ char *path_in_root = NULL; path_in_root = path_join(root, path); @@ -848,6 +854,83 @@ 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. */ + + 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) { + log_debug("Not importing mutable directory for hierarchy %s as a lower dir, because it points to the hierarchy itself", op->hierarchy); + return 0; + } + + 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) { + log_debug("Not importing mutable directory for hierarchy %s as a lower dir, because it points to the hierarchy itself", op->hierarchy); + return 0; + } + + 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; @@ -859,12 +942,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; } @@ -928,7 +1012,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; } @@ -937,13 +1026,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; @@ -963,7 +1048,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; @@ -1147,7 +1232,7 @@ static int write_work_dir_file(ImageClass image_class, const char *meta_path, co return 0; /* Do not store work dir path for ephemeral mode, it will be gone once this process is done. */ - if (arg_mutable == MUTABLE_EPHEMERAL) + 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)); @@ -2038,7 +2123,7 @@ 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\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" From 5ae2f83b3e1b56e4f9826748d1afc4c771d82746 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Wed, 28 Feb 2024 14:50:05 +0100 Subject: [PATCH 12/16] man: Document sysext ephemeral-import mode --- man/systemd-sysext.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/man/systemd-sysext.xml b/man/systemd-sysext.xml index df5dcafbd1..5a6dffd2c5 100644 --- a/man/systemd-sysext.xml +++ b/man/systemd-sysext.xml @@ -236,6 +236,10 @@ 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. @@ -417,6 +421,14 @@ 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. + + From e2bdece2844600827e1b67b25db3ab8e633746e5 Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Thu, 29 Feb 2024 08:18:43 +0100 Subject: [PATCH 13/16] test: Add test cases for sysext ephemeral-import mode --- test/units/testsuite-50.sh | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index 9b20b1a29b..d358b38354 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -1784,6 +1784,81 @@ test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did n 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 + + # # done # From baaa1679268a4ad05aeb037b028f2b5cdbff463f Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Wed, 28 Feb 2024 15:41:46 +0100 Subject: [PATCH 14/16] test, sysext: Actually fail the whole operation if sd-merge worker failed This also fixes a wrong merge failure check. --- src/sysext/sysext.c | 3 ++- test/units/testsuite-50.sh | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 57f06b2678..267c90f976 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -1742,9 +1742,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) diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index d358b38354..e2ab409c26 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -1323,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" # From f94da307f91e4f3346dca62c7c50a7816a2d946a Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Wed, 28 Feb 2024 16:34:05 +0100 Subject: [PATCH 15/16] sysext: Fail when trying to import mutable layer that's a symlink to hierarchy --- src/sysext/sysext.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 267c90f976..b984e9b9a0 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -880,10 +880,8 @@ static int maybe_import_mutable_directory(OverlayFSPaths *op) { 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) { - log_debug("Not importing mutable directory for hierarchy %s as a lower dir, because it points to the hierarchy itself", op->hierarchy); - return 0; - } + 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) @@ -919,10 +917,8 @@ static int maybe_import_ignored_mutable_directory(OverlayFSPaths *op) { 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) { - log_debug("Not importing mutable directory for hierarchy %s as a lower dir, because it points to the hierarchy itself", op->hierarchy); - return 0; - } + 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) From 18c1ea41c1b730e7f7a810ffcb0845a1e36a231f Mon Sep 17 00:00:00 2001 From: Krzesimir Nowak Date: Thu, 29 Feb 2024 08:19:16 +0100 Subject: [PATCH 16/16] test: Add cases for failures to import the hierarchy --- test/units/testsuite-50.sh | 54 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index e2ab409c26..7c4a9db96d 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -1859,6 +1859,60 @@ test ! -f "${fake_root}${hierarchy}/now-is-mutable" || die "now-is-mutable did n 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 #