From: Nick Labich Date: Fri, 17 Oct 2025 02:42:58 +0000 (-0400) Subject: sysext: Configure overlayfs mount options via envvar X-Git-Tag: v259-rc1~287 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=942ad8503a2025a8e6d3597fe6a00da652b93354;p=thirdparty%2Fsystemd.git sysext: Configure overlayfs mount options via envvar Implements #39314 --- diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 6eda26c7d68..caf0e421037 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -413,6 +413,14 @@ All tools: variable. Similarly, `$SYSTEMD_CONFEXT_MUTABLE_MODE` works for confext images and supports the systemd-confext multi-call functionality of sysext. +* `$SYSTEMD_SYSEXT_OVERLAYFS_MOUNT_OPTIONS` — this variable may be used to + override the overlayfs mount options applied for hierarchies managed by + `systemd-sysext`. Similarly, `$SYSTEMD_CONFEXT_OVERLAYFS_MOUNT_OPTIONS` works + for confext images and supports the systemd-confext multi-call functionality + of sysext. Read-only hierarchies have no mount options added by + default. Mutable hierarchies have the following mount options added by + default: `redirect_dir=on,noatime,metacopy=off,index=off`. + `systemd-tmpfiles`: * `$SYSTEMD_TMPFILES_FORCE_SUBVOL` — if unset, `v`/`q`/`Q` lines will create diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index ef291489261..99dcee44cfe 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -92,12 +92,17 @@ static int arg_noexec = -1; static ImagePolicy *arg_image_policy = NULL; static bool arg_varlink = false; static MutableMode arg_mutable = MUTABLE_NO; +static const char *arg_overlayfs_mount_options = NULL; /* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */ static ImageClass arg_image_class = IMAGE_SYSEXT; #define MUTABLE_EXTENSIONS_BASE_DIR "/var/lib/extensions.mutable" +/* redirect_dir=on and noatime prevent unnecessary upcopies, metacopy=off prevents broken + * files from partial upcopies after umount, index=off allows reuse of the upper/work dirs */ +#define MUTABLE_EXTENSIONS_MOUNT_OPTIONS "redirect_dir=on,noatime,metacopy=off,index=off" + STATIC_DESTRUCTOR_REGISTER(arg_hierarchies, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); @@ -114,6 +119,7 @@ static const struct { const char *scope_env; const char *name_env; const char *mode_env; + const char *opts_env; const ImagePolicy *default_image_policy; unsigned long default_mount_flags; } image_class_info[_IMAGE_CLASS_MAX] = { @@ -127,6 +133,7 @@ static const struct { .scope_env = "SYSEXT_SCOPE", .name_env = "SYSTEMD_SYSEXT_HIERARCHIES", .mode_env = "SYSTEMD_SYSEXT_MUTABLE_MODE", + .opts_env = "SYSTEMD_SYSEXT_OVERLAYFS_MOUNT_OPTIONS", .default_image_policy = &image_policy_sysext, .default_mount_flags = MS_RDONLY|MS_NODEV, }, @@ -140,6 +147,7 @@ static const struct { .scope_env = "CONFEXT_SCOPE", .name_env = "SYSTEMD_CONFEXT_HIERARCHIES", .mode_env = "SYSTEMD_CONFEXT_MUTABLE_MODE", + .opts_env = "SYSTEMD_CONFEXT_OVERLAYFS_MOUNT_OPTIONS", .default_image_policy = &image_policy_confext, .default_mount_flags = MS_RDONLY|MS_NODEV|MS_NOSUID|MS_NOEXEC, } @@ -733,7 +741,8 @@ static int mount_overlayfs( const char *where, char **layers, const char *upper_dir, - const char *work_dir) { + const char *work_dir, + const char *mount_options) { _cleanup_free_ char *options = NULL; bool separator = false; @@ -769,12 +778,15 @@ static int mount_overlayfs( r = append_overlayfs_path_option(&options, ",", "workdir", work_dir); if (r < 0) return r; - /* redirect_dir=on and noatime prevent unnecessary upcopies, metacopy=off prevents broken - * files from partial upcopies after umount, index=off allows reuse of the upper/work dirs */ - if (!strextend(&options, ",redirect_dir=on,noatime,metacopy=off,index=off")) - return log_oom(); + + if (!mount_options) + mount_options = MUTABLE_EXTENSIONS_MOUNT_OPTIONS; } + + if (!isempty(mount_options) && !strextend(&options, ",", mount_options)) + return log_oom(); + /* Now mount the actual overlayfs */ r = mount_nofollow_verbose(LOG_ERR, image_class_info[image_class].short_identifier, where, "overlay", flags, options); if (r < 0) @@ -1339,7 +1351,8 @@ static int mount_overlayfs_with_op( ImageClass image_class, int noexec, const char *overlay_path, - const char *meta_path) { + const char *meta_path, + const char *mount_options) { int r; const char *top_layer = NULL; @@ -1389,7 +1402,7 @@ static int mount_overlayfs_with_op( 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); + r = mount_overlayfs(image_class, noexec, overlay_path, op->lower_dirs, op->upper_dir, op->work_dir, mount_options); if (r < 0) return r; @@ -1664,7 +1677,7 @@ static int merge_hierarchy( if (r < 0) return r; - r = mount_overlayfs_with_op(op, image_class, noexec, overlay_path, meta_path); + r = mount_overlayfs_with_op(op, image_class, noexec, overlay_path, meta_path, arg_overlayfs_mount_options); if (r < 0) return r; @@ -2606,6 +2619,34 @@ static int parse_argv(int argc, char *argv[]) { return 1; } +static int parse_env(void) { + const char *env_var; + int r; + + env_var = secure_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; + } + + env_var = secure_getenv(image_class_info[arg_image_class].opts_env); + if (env_var) + arg_overlayfs_mount_options = env_var; + + /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and + * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline + * switch. */ + r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env); + if (r < 0) + return log_error_errno(r, "Failed to parse %s environment variable: %m", image_class_info[arg_image_class].name_env); + + return 0; +} + static int sysext_main(int argc, char *argv[]) { static const Verb verbs[] = { @@ -2622,23 +2663,16 @@ 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; - /* Parse environment variable first */ - 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; - } + /* Parse environment variables first */ + r = parse_env(); + if (r < 0) + return r; /* Parse configuration file */ r = parse_config_file(arg_image_class); @@ -2650,13 +2684,6 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - /* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and - * /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline - * switch. */ - r = parse_env_extension_hierarchies(&arg_hierarchies, image_class_info[arg_image_class].name_env); - if (r < 0) - return log_error_errno(r, "Failed to parse environment variable: %m"); - if (arg_varlink) { _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; diff --git a/test/units/TEST-50-DISSECT.sysext.sh b/test/units/TEST-50-DISSECT.sysext.sh index 3c8c7de8c1c..097e62df80f 100755 --- a/test/units/TEST-50-DISSECT.sysext.sh +++ b/test/units/TEST-50-DISSECT.sysext.sh @@ -287,6 +287,18 @@ extension_verify_after_unmerge() ( extension_verify "$root" "$hierarchy" "after unmerge" "$@" ) +extension_verify_mount_option() ( + local target=${1:?} + local option=${2:?} + + grep "^sysext" /proc/mounts | while read -r _ tgt _ opts _ _; do + if [[ "$target" == "$tgt" && ! "$opts" =~ .*"$option".* ]]; then + echo >&2 "Mount options ($opts) do not include expected option ($option)" + exit 1 + fi + done +) + run_systemd_sysext() { local root=${1:-} shift @@ -331,6 +343,26 @@ extension_verify_after_unmerge "$fake_root" "$hierarchy" -h ) +( init_trap +: "No extension data in /var/lib/extensions.mutable/…, R/O hierarchy, mutability disabled by default, read-only merged, default, mount options" +fake_root=${roots_dir:+"$roots_dir/simple-read-only-with-read-only-hierarchy-options"} +hierarchy=/opt + +prepare_root "$fake_root" "$hierarchy" +prepare_extension_image "$fake_root" "$hierarchy" +prepare_read_only_hierarchy "$fake_root" "$hierarchy" + +SYSTEMD_SYSEXT_OVERLAYFS_MOUNT_OPTIONS="metacopy=off,noatime"\ + run_systemd_sysext "$fake_root" merge + +extension_verify_mount_option "$hierarchy" metacopy=off \ +|| (! extension_verify_mount_option "$hierarchy" metacopy=on) +extension_verify_mount_option "$hierarchy" noatime + +run_systemd_sysext "$fake_root" unmerge +) + + ( init_trap : "No extension data in /var/lib/extensions.mutable/…, mutable hierarchy, mutability disabled by default, read-only merged" fake_root=${roots_dir:+"$roots_dir/simple-read-only-with-mutable-hierarchy"} @@ -437,6 +469,40 @@ test ! -f "$fake_root$hierarchy/now-is-mutable" ) +( init_trap +: "Extension data in /var/lib/extensions.mutable/…, R/O hierarchy, auto-mutability, mutable merged, mount options" +fake_root=${roots_dir:+"$roots_dir/simple-mutable-with-read-only-hierarchy-options"} +hierarchy=/opt +extension_data_dir="$fake_root/var/lib/extensions.mutable$hierarchy" + +[[ "$FSTYPE" == "fuseblk" ]] && exit 0 + +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" + +run_systemd_sysext "$fake_root" --mutable=auto merge + +extension_verify_mount_option "$fake_root$hierarchy" index=off \ +|| (! extension_verify_mount_option "$fake_root$hierarchy" index=on) +extension_verify_mount_option "$fake_root$hierarchy" metacopy=off \ +|| (! extension_verify_mount_option "$fake_root$hierarchy" metacopy=on) +extension_verify_mount_option "$fake_root$hierarchy" noatime +(! extension_verify_mount_option "$fake_root$hierarchy" redirect_dir=off) + +SYSTEMD_SYSEXT_OVERLAYFS_MOUNT_OPTIONS="relatime,metacopy=on"\ + run_systemd_sysext "$fake_root" --mutable=auto refresh + +(! extension_verify_mount_option "$fake_root$hierarchy" metacopy=off) \ +|| extension_verify_mount_option "$fake_root$hierarchy" metacopy=on +(! extension_verify_mount_option "$fake_root$hierarchy" noatime) +extension_verify_mount_option "$fake_root$hierarchy" relatime + +run_systemd_sysext "$fake_root" unmerge +) + + ( init_trap : "Extension data in /var/lib/extensions.mutable/…, missing hierarchy, auto-mutability, mutable merged" fake_root=${roots_dir:+"$roots_dir/simple-mutable-with-missing-hierarchy"}