tests/sys/vfs: add ABI-root absolute symlink tests

Pin the overlay namei(9) uses for Linux ABI processes: a target under
the ABI root wins (PR 289739), a native-only target should retry from
the native root (PR 297426), an ENOENT past a resolved ABI target is
not retried, and the plain no-symlink native fallback is unchanged.
Skip without a Linux userland so CI stays green.  The two native-only
cases use atf_expect_fail until the retry lands.

PR:		297426
Reviewed by:	kib
Differential Revision:	https://reviews.freebsd.org/D59990
This commit is contained in:
Devin Teske committed 2026-09-26 11:12:33 -07:00
1 parent e8da57256d
commit b98d169d1f
2 files changed
+281

No files matched your search

+2
View File
@@ -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
+279
View File
@@ -0,0 +1,279 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2026 Devin Teske <[email protected]>
#
# 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
}