aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/sys/vfs/Makefile2
-rw-r--r--tests/sys/vfs/abi_root_symlink.sh279
2 files changed, 281 insertions, 0 deletions
diff --git a/tests/sys/vfs/Makefile b/tests/sys/vfs/Makefile
index e25282b2c70a..f34616e22ebf 100644
--- a/tests/sys/vfs/Makefile
+++ b/tests/sys/vfs/Makefile
@@ -5,6 +5,8 @@ TESTSDIR= ${TESTSBASE}/sys/vfs
ATF_TESTS_C+= lookup_cap_dotdot
CFLAGS.lookup_cap_dotdot.c+= -I${SRCTOP}/tests
+ATF_TESTS_SH+= abi_root_symlink
+
#ATF_TESTS_SH+= lookup_test
TAP_TESTS_SH+= trailing_slash
diff --git a/tests/sys/vfs/abi_root_symlink.sh b/tests/sys/vfs/abi_root_symlink.sh
new file mode 100644
index 000000000000..18f7b5915a28
--- /dev/null
+++ b/tests/sys/vfs/abi_root_symlink.sh
@@ -0,0 +1,279 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2026 Devin Teske <dteske@FreeBSD.org>
+#
+# Regression tests for absolute symlink resolution under an ABI root
+# (compat.linux.emul_path). namei(9) resolves paths for Linux ABI
+# processes by trying the ABI root first and falling back to the
+# native root. Absolute symlink targets receive the same treatment:
+# a target is first resolved under the ABI root (so self-contained
+# Linux userlands keep working, PR 289739); when the target itself
+# does not resolve there, the walk restarts once from the native
+# root using the expansion (so symlinks pointing at host paths, and
+# linprocfs' /proc/<pid>/exe, keep working, PR 297426). An ENOENT
+# past a target that did resolve under the ABI root falls back to
+# the original path like any other lookup.
+#
+# These tests require the Linux ABI (linux_enable=YES) and a Linux
+# userland (e.g. emulators/linux_base-rl9) providing ${emul_path}/bin/sh.
+# Without that, they skip, so CI jobs that lack a Linux base do not
+# fail. The two native-only cases still fail on CURRENT namei(9) and
+# use atf_expect_fail until the native-root retry lands (PR 297426).
+# Each case comments the fixture so it can be rebuilt by hand.
+# $wd is any empty directory (the ATF work directory when kyua
+# runs). $emul is compat.linux.emul_path, usually /compat/linux.
+# $emul$wd means the same path names created under that ABI root.
+#
+
+require_linux()
+{
+ emul=$(sysctl -n compat.linux.emul_path 2>/dev/null)
+ [ "$emul" ] || atf_skip "Linux ABI not present (compat.linux.emul_path)"
+ kldstat -q -m linux64 || kldstat -q -m linux ||
+ atf_skip "linux(4) not loaded"
+ lsh="$emul/bin/sh"
+ [ -x "$lsh" ] || atf_skip "Linux userland not installed ($lsh)"
+ # Mirror of the ATF work directory under the ABI root.
+ wd=$(pwd)
+ abiwd="$emul$wd"
+ atf_check mkdir -p "$abiwd"
+}
+
+cleanup_linux()
+{
+ emul=$(sysctl -n compat.linux.emul_path 2>/dev/null)
+ [ "$emul" ] || return 0
+ rm -rf "$emul$(pwd)" 2>/dev/null
+ # Remove now-empty parents mirrored under the ABI root.
+ rmdir -p "$emul$(dirname "$(pwd)")" 2>/dev/null
+ return 0
+}
+
+atf_test_case symlink_target_in_abi_root cleanup
+symlink_target_in_abi_root_head()
+{
+ atf_set "descr" "Absolute symlink target under the ABI root wins" \
+ "over an identically named native file (PR 289739)"
+ atf_set "require.user" "root"
+}
+symlink_target_in_abi_root_body()
+{
+ #
+ # Recreate by hand. $wd is any empty directory. $emul is
+ # compat.linux.emul_path (usually /compat/linux).
+ #
+ # Native:
+ # $wd/f-both
+ # Regular file containing the word "native".
+ #
+ # ABI (the same path names created under $emul):
+ # $emul$wd/f-both
+ # Regular file containing the word "abi".
+ # $emul$wd/link-both
+ # Absolute symlink whose target is $wd/f-both.
+ #
+ # A Linux process opens $wd/link-both. namei tries that path
+ # under the ABI root first ($emul$wd/link-both), follows the
+ # symlink to $wd/f-both, and stays on the ABI pass, so the
+ # file that is read is $emul$wd/f-both.
+ #
+ # Expected result: the read returns "abi".
+ #
+ require_linux
+ printf native > "$wd/f-both"
+ printf abi > "$abiwd/f-both"
+ atf_check ln -s "$wd/f-both" "$abiwd/link-both"
+ atf_check -o inline:"abi" "$lsh" -c "cat $wd/link-both"
+}
+symlink_target_in_abi_root_cleanup()
+{
+ cleanup_linux
+}
+
+atf_test_case symlink_target_native_only cleanup
+symlink_target_native_only_head()
+{
+ atf_set "descr" "Absolute symlink under the ABI root pointing at" \
+ "a native-only file falls back to the native root (PR 297426)"
+ atf_set "require.user" "root"
+}
+symlink_target_native_only_body()
+{
+ #
+ # Recreate by hand. $wd is any empty directory. $emul is
+ # compat.linux.emul_path (usually /compat/linux).
+ #
+ # Native:
+ # $wd/f-native
+ # Regular file containing the word "native".
+ #
+ # ABI (the same path names created under $emul):
+ # $emul$wd/link-native
+ # Absolute symlink whose target is $wd/f-native.
+ # There is no $emul$wd/f-native.
+ #
+ # A Linux process opens $wd/link-native. namei tries that
+ # path under the ABI root first ($emul$wd/link-native) and
+ # follows the symlink to $wd/f-native. That target does not
+ # exist under the ABI root, so the walk should restart once
+ # from the native root using the expansion and read
+ # $wd/f-native.
+ #
+ # Expected result: the read returns "native".
+ # On CURRENT namei this is ENOENT (atf_expect_fail; PR 297426).
+ #
+ require_linux
+ printf native > "$wd/f-native"
+ atf_check ln -s "$wd/f-native" "$abiwd/link-native"
+ atf_expect_fail \
+ "PR 297426: abs symlink to native-only target fails under ABI root"
+ atf_check -o inline:"native" "$lsh" -c "cat $wd/link-native"
+}
+symlink_target_native_only_cleanup()
+{
+ cleanup_linux
+}
+
+atf_test_case symlink_target_native_only_midpath cleanup
+symlink_target_native_only_midpath_head()
+{
+ atf_set "descr" "Absolute directory symlink under the ABI root" \
+ "pointing at a native-only directory falls back to the native" \
+ "root for lookups through it (PR 297426), as with" \
+ "linux-rl9-fontconfig's /etc/fonts -> /usr/local/etc/fonts"
+ atf_set "require.user" "root"
+}
+symlink_target_native_only_midpath_body()
+{
+ #
+ # Recreate by hand. $wd is any empty directory. $emul is
+ # compat.linux.emul_path (usually /compat/linux).
+ #
+ # Native:
+ # $wd/ndir/
+ # Directory.
+ # $wd/ndir/f
+ # Regular file containing the word "native".
+ #
+ # ABI (the same path names created under $emul):
+ # $emul$wd/dirlink
+ # Absolute symlink whose target is $wd/ndir.
+ # There is no $emul$wd/ndir.
+ #
+ # A Linux process opens $wd/dirlink/f. namei tries that path
+ # under the ABI root first ($emul$wd/dirlink/f), follows
+ # dirlink to $wd/ndir, and then wants $wd/ndir/f. The
+ # directory does not exist under the ABI root, so the walk
+ # should restart once from the native root using the
+ # expansion and read $wd/ndir/f. This is the
+ # linux-rl9-fontconfig case (/etc/fonts ->
+ # /usr/local/etc/fonts).
+ #
+ # Expected result: the read returns "native".
+ # On CURRENT namei this is ENOENT (atf_expect_fail; PR 297426).
+ #
+ require_linux
+ atf_check mkdir "$wd/ndir"
+ printf native > "$wd/ndir/f"
+ atf_check ln -s "$wd/ndir" "$abiwd/dirlink"
+ atf_expect_fail \
+ "PR 297426: abs dir symlink to native-only target fails under ABI root"
+ atf_check -o inline:"native" "$lsh" -c "cat $wd/dirlink/f"
+}
+symlink_target_native_only_midpath_cleanup()
+{
+ cleanup_linux
+}
+
+atf_test_case symlink_target_resolved_suffix cleanup
+symlink_target_resolved_suffix_head()
+{
+ atf_set "descr" "ENOENT past an absolute symlink target that" \
+ "resolved under the ABI root is not retried from the native" \
+ "root, even when the missing suffix exists natively"
+ atf_set "require.user" "root"
+}
+symlink_target_resolved_suffix_body()
+{
+ #
+ # Recreate by hand. $wd is any empty directory. $emul is
+ # compat.linux.emul_path (usually /compat/linux).
+ #
+ # Native:
+ # $wd/tdir/
+ # Directory.
+ # $wd/tdir/f
+ # Regular file containing the word "native".
+ #
+ # ABI (the same path names created under $emul):
+ # $emul$wd/tdir/
+ # Empty directory (no file named f).
+ # $emul$wd/reslink
+ # Absolute symlink whose target is $wd/tdir.
+ #
+ # A Linux process opens $wd/reslink/f. namei tries that path
+ # under the ABI root first ($emul$wd/reslink/f), follows
+ # reslink to $wd/tdir, and finds $emul$wd/tdir on the ABI
+ # pass. The leftover component f is missing there. Because
+ # the symlink target already resolved under the ABI root,
+ # the native restart must not walk the expansion (that would
+ # incorrectly find $wd/tdir/f). It restarts the original
+ # path and still gets ENOENT.
+ #
+ # Expected result: ENOENT.
+ #
+ require_linux
+ atf_check mkdir "$abiwd/tdir"
+ atf_check mkdir "$wd/tdir"
+ printf native > "$wd/tdir/f"
+ atf_check ln -s "$wd/tdir" "$abiwd/reslink"
+ atf_check -s not-exit:0 -e ignore "$lsh" -c "cat $wd/reslink/f"
+}
+symlink_target_resolved_suffix_cleanup()
+{
+ cleanup_linux
+}
+
+atf_test_case plain_native_fallback cleanup
+plain_native_fallback_head()
+{
+ atf_set "descr" "Plain path (no symlink) absent under the ABI root" \
+ "still falls back to the native root"
+ atf_set "require.user" "root"
+}
+plain_native_fallback_body()
+{
+ #
+ # Recreate by hand. $wd is any empty directory. $emul is
+ # compat.linux.emul_path (usually /compat/linux).
+ #
+ # Native:
+ # $wd/f-plain
+ # Regular file containing the word "native".
+ #
+ # ABI (the same path names created under $emul):
+ # There is no $emul$wd/f-plain, and no symlink.
+ #
+ # A Linux process opens $wd/f-plain. namei tries that path
+ # under the ABI root first, gets ENOENT, and restarts the
+ # original path from the native root.
+ #
+ # Expected result: the read returns "native".
+ #
+ require_linux
+ printf native > "$wd/f-plain"
+ atf_check -o inline:"native" "$lsh" -c "cat $wd/f-plain"
+}
+plain_native_fallback_cleanup()
+{
+ cleanup_linux
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case symlink_target_in_abi_root
+ atf_add_test_case symlink_target_native_only
+ atf_add_test_case symlink_target_native_only_midpath
+ atf_add_test_case symlink_target_resolved_suffix
+ atf_add_test_case plain_native_fallback
+}