continue;
}
- mount_fd = RET_NERRNO(open_tree(AT_FDCWD, path, OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_RECURSIVE));
- if (mount_fd == -ENOENT) /* The path may be hidden by another over-mount or already unmounted. */
- continue;
- if (mount_fd < 0)
- return log_debug_errno(mount_fd, "Failed to open subtree of mounted filesystem '%s': %m", path);
+ /* If possible on a newer kernel, use MS_PRIVATE to decouple it from the original mount.
+ * Otherwise MNT_DETACH of the source path could propagate through and unmount the
+ * just-moved nested children at the destination (relevant for preserving nested mounts
+ * under sysext hierarchies). */
+ static bool mount_attr_unsupported = false;
+
+ if (!mount_attr_unsupported) {
+ mount_fd = open_tree_attr_with_fallback(
+ AT_FDCWD, path,
+ OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_RECURSIVE,
+ &(struct mount_attr) { .propagation = MS_PRIVATE });
+ if (mount_fd == -ENOENT) /* The path may be hidden by another over-mount or already unmounted. */
+ continue;
+ if (mount_fd < 0 && ERRNO_IS_NEG_NOT_SUPPORTED(mount_fd)) {
+ /* On a kernel older than 5.12 without mount_setattr() we do the regular
+ * clone. Nested mounts under sysext and similar cases may get lost. */
+ log_debug_errno(mount_fd, "mount_setattr() not supported, falling back to plain open_tree() without MS_PRIVATE: %m");
+ mount_attr_unsupported = true;
+ } else if (mount_fd < 0)
+ return log_debug_errno(mount_fd, "Failed to open subtree of mounted filesystem '%s': %m", path);
+ }
+
+ if (mount_attr_unsupported) {
+ mount_fd = RET_NERRNO(open_tree(AT_FDCWD, path, OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_RECURSIVE));
+ if (mount_fd == -ENOENT)
+ continue;
+ if (mount_fd < 0)
+ return log_debug_errno(mount_fd, "Failed to open subtree of mounted filesystem '%s': %m", path);
+ }
p = strdup(path);
if (!p)
if (child_fd < 0)
return log_error_errno(errno, "Failed to pin mountpoint %s: %m", t);
- r = mount_follow_verbose(LOG_ERR, m->path, FORMAT_PROC_FD_PATH(child_fd), /* fstype= */ NULL, MS_BIND|MS_REC, /* options= */ NULL);
+ /* Instead of a bind mount we attach the detached clone produced by
+ * open_tree_attr_with_fallback() from get_sub_mounts() because that has no propagation
+ * relationship with the original anymore and the MNT_DETACH below won't propagate for
+ * nested mounts. */
+ r = RET_NERRNO(move_mount(m->mount_fd, "", child_fd, "", MOVE_MOUNT_F_EMPTY_PATH|MOVE_MOUNT_T_EMPTY_PATH));
if (r < 0)
- return r;
+ return log_error_errno(r, "Failed to move mount '%s' to '%s': %m", m->path, t);
(void) umount_verbose(LOG_WARNING, m->path, MNT_DETACH);
}
fi
)
+( init_trap
+: "Nested tmpfs submounts under the hierarchy survive merge/refresh/unmerge round-trip"
+fake_root=${roots_dir:+"$roots_dir/nested-submounts"}
+hierarchy=/opt
+
+# Don't run the test if the inner mount won't be preserved due to an old kernel
+if ! systemd-analyze compare-versions "$(uname -r)" ge 5.12; then
+ echo >&2 "Kernel too old for mount_setattr (need >= 5.12), skipping nested submount test"
+ exit 0
+fi
+
+prepare_root "$fake_root" "$hierarchy"
+prepare_extension_image "$fake_root" "$hierarchy"
+prepare_hierarchy "$fake_root" "$hierarchy"
+
+# Two tmpfs mounts, one nested in the hierarchy under the other. Reproduces the nested mount layout from
+# https://github.com/flatcar/Flatcar/issues/2111 and verifies that we preserve nested mounts across merge,
+# refresh, and unmerge.
+outer_mp="$fake_root$hierarchy/outer"
+inner_mp="$outer_mp/inner"
+mkdir -p "$outer_mp"
+mount -t tmpfs tmpfs "$outer_mp"
+prepend_trap "umount -l ${outer_mp@Q} 2>/dev/null || true"
+mkdir -p "$inner_mp"
+mount -t tmpfs tmpfs "$inner_mp"
+prepend_trap "umount -l ${inner_mp@Q} 2>/dev/null || true"
+touch "$outer_mp/outer-marker"
+touch "$inner_mp/inner-marker"
+
+run_systemd_sysext "$fake_root" merge
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+mountpoint "$outer_mp"
+mountpoint "$inner_mp"
+test -f "$outer_mp/outer-marker"
+test -f "$inner_mp/inner-marker"
+
+run_systemd_sysext "$fake_root" refresh --always-refresh=yes
+extension_verify_after_merge "$fake_root" "$hierarchy" -e -h
+mountpoint "$outer_mp"
+mountpoint "$inner_mp"
+test -f "$outer_mp/outer-marker"
+test -f "$inner_mp/inner-marker"
+
+run_systemd_sysext "$fake_root" unmerge
+extension_verify_after_unmerge "$fake_root" "$hierarchy" -h
+mountpoint "$outer_mp"
+mountpoint "$inner_mp"
+test -f "$outer_mp/outer-marker"
+test -f "$inner_mp/inner-marker"
+)
+
} # End of run_sysext_tests