diff options
| -rw-r--r-- | tests/sys/vfs/Makefile | 2 | ||||
| -rw-r--r-- | tests/sys/vfs/abi_root_symlink.sh | 279 |
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 +} |
