diff --git a/lib/libspl/os/linux/getmntany.c b/lib/libspl/os/linux/getmntany.c index ee1cdf59b..0e9591c08 100644 --- a/lib/libspl/os/linux/getmntany.c +++ b/lib/libspl/os/linux/getmntany.c @@ -143,7 +143,14 @@ getextmntent(const char *path, struct extmnttab *entry, struct stat64 *statbuf) } #ifdef HAVE_STATX_MNT_ID - if (statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT | AT_SYMLINK_NOFOLLOW, + /* + * Use AT_STATX_SYNC_AS_STAT without AT_SYMLINK_NOFOLLOW so that + * symlinks are followed, matching the behavior of stat64() above. + * Without this, if path is a symlink crossing a mount boundary, + * statx() returns the mnt_id of the symlink's location rather + * than the symlink target's mount. + */ + if (statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT, STATX_MNT_ID, &stx) == 0 && (stx.stx_mask & STATX_MNT_ID)) { have_mnt_id = B_TRUE; target_mnt_id = stx.stx_mnt_id; diff --git a/tests/runfiles/common.run b/tests/runfiles/common.run index 7d86d2873..69752e07a 100644 --- a/tests/runfiles/common.run +++ b/tests/runfiles/common.run @@ -252,6 +252,10 @@ tests = ['zfs_inherit_001_neg', 'zfs_inherit_002_neg', 'zfs_inherit_003_pos', 'zfs_inherit_mountpoint'] tags = ['functional', 'cli_root', 'zfs_inherit'] +[tests/functional/cli_root/zfs_list] +tests = ['zfs_list_009_pos'] +tags = ['functional', 'cli_root', 'zfs_list'] + [tests/functional/cli_root/zfs_load-key] tests = ['zfs_load-key', 'zfs_load-key_all', 'zfs_load-key_file', 'zfs_load-key_https', 'zfs_load-key_location', 'zfs_load-key_noop', diff --git a/tests/zfs-tests/tests/Makefile.am b/tests/zfs-tests/tests/Makefile.am index 89d945a76..97b644c31 100644 --- a/tests/zfs-tests/tests/Makefile.am +++ b/tests/zfs-tests/tests/Makefile.am @@ -767,6 +767,9 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \ functional/cli_root/zfs_jail/cleanup.ksh \ functional/cli_root/zfs_jail/setup.ksh \ functional/cli_root/zfs_jail/zfs_jail_001_pos.ksh \ + functional/cli_root/zfs_list/cleanup.ksh \ + functional/cli_root/zfs_list/setup.ksh \ + functional/cli_root/zfs_list/zfs_list_009_pos.ksh \ functional/cli_root/zfs_load-key/cleanup.ksh \ functional/cli_root/zfs_load-key/setup.ksh \ functional/cli_root/zfs_load-key/zfs_load-key_all.ksh \ diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_list/cleanup.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_list/cleanup.ksh new file mode 100755 index 000000000..138dfe047 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_list/cleanup.ksh @@ -0,0 +1,30 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +default_cleanup diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_list/setup.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_list/setup.ksh new file mode 100755 index 000000000..912fcfc40 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_list/setup.ksh @@ -0,0 +1,32 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +verify_runnable "global" + +default_setup $DISKS diff --git a/tests/zfs-tests/tests/functional/cli_root/zfs_list/zfs_list_009_pos.ksh b/tests/zfs-tests/tests/functional/cli_root/zfs_list/zfs_list_009_pos.ksh new file mode 100755 index 000000000..758aa7608 --- /dev/null +++ b/tests/zfs-tests/tests/functional/cli_root/zfs_list/zfs_list_009_pos.ksh @@ -0,0 +1,69 @@ +#!/bin/ksh -p +# SPDX-License-Identifier: CDDL-1.0 +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or https://opensource.org/licenses/CDDL-1.0. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright (c) 2026 by Delphix. All rights reserved. +# + +. $STF_SUITE/include/libtest.shlib + +# +# DESCRIPTION: +# 'zfs list -Ho name ' follows symlinks when resolving the path to +# a dataset name. A symlink that crosses a mount boundary must resolve to +# the dataset owning the symlink's target, not the dataset containing the +# symlink itself. +# +# STRATEGY: +# 1. Create two child datasets: ds1 and ds2. +# 2. Place a symlink inside ds1 that points into ds2. +# 3. Verify that 'zfs list -Ho name ' returns ds2. +# + +verify_runnable "global" + +DS1="$TESTPOOL/$TESTFS/ds1" +DS2="$TESTPOOL/$TESTFS/ds2" +LINK="$TESTDIR/ds1/link_to_ds2" + +function cleanup +{ + rm -f "$LINK" + datasetexists "$DS1" && log_must zfs destroy "$DS1" + datasetexists "$DS2" && log_must zfs destroy "$DS2" +} + +log_onexit cleanup + +log_assert "'zfs list -Ho name' follows symlinks when resolving a path." + +log_must zfs create "$DS1" +log_must zfs create "$DS2" +log_must ln -s "$TESTDIR/ds2" "$LINK" + +result=$(zfs list -Ho name "$LINK") +if [[ "$result" != "$DS2" ]]; then + log_fail "'zfs list -Ho name $LINK' returned '$result', expected '$DS2'" +fi + +log_pass "'zfs list -Ho name' correctly follows a symlink crossing a mount boundary."