]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysext: Configure overlayfs mount options via envvar
authorNick Labich <nick@labich.org>
Fri, 17 Oct 2025 02:42:58 +0000 (22:42 -0400)
committerLennart Poettering <lennart@poettering.net>
Mon, 20 Oct 2025 07:59:02 +0000 (09:59 +0200)
Implements #39314

docs/ENVIRONMENT.md
src/sysext/sysext.c
test/units/TEST-50-DISSECT.sysext.sh

index 6eda26c7d684e168177bdab4224e43ee3b2de80a..caf0e4210374bd3f112fbf6ff003810024ceda2b 100644 (file)
@@ -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
index ef29148926135f906062e2d122942956e968a6d6..99dcee44cfe02754bd6b18b5f75773f95b656316 100644 (file)
@@ -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;
 
index 3c8c7de8c1ce634ae0336f857aec877d63d78a20..097e62df80fe7664380f09e69cb642f75336cec1 100755 (executable)
@@ -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"}