Do not return ESTALE for open-unlinked files

When the Linux fh_to_dentry() export operation is called for an 
open-unlinked file on a ZFS filesystem, an ESTALE error is returned. 
This behavior differs from other filesystem implementations on Linux 
(like eg. ext4, XFS), which grant access to inodes that are unlinked 
but still referenced. Update both the Linux and FreeBSD code to 
conform with the established Linux behavior.

Reviewed-by: Brian Behlendorf <[email protected]>
Signed-off-by: tiehexue <[email protected]>
Closes #18699 
Closes #18718
This commit is contained in:
tiehexue authored and GitHub committed 2026-07-08 12:02:10 -07:00
1 parent 83bf407840
commit 6acb99cb1b
12 files changed
+293 -18

No files matched your search

+1 -5
View File
@@ -1861,10 +1861,6 @@ zfs_vget(vfs_t *vfsp, ino_t ino, int flags, vnode_t **vpp)
if ((err = zfs_enter(zfsvfs, FTAG)) != 0)
return (err);
err = zfs_zget(zfsvfs, ino, &zp);
if (err == 0 && zp->z_unlinked) {
vrele(ZTOV(zp));
err = EINVAL;
}
if (err == 0)
*vpp = ZTOV(zp);
zfs_exit(zfsvfs, FTAG);
@@ -2021,7 +2017,7 @@ zfs_fhtovp(vfs_t *vfsp, fid_t *fidp, int flags, vnode_t **vpp)
zp_gen = zp_gen & gen_mask;
if (zp_gen == 0)
zp_gen = 1;
if (zp->z_unlinked || zp_gen != fid_gen) {
if (zp_gen != fid_gen) {
dprintf("znode gen (%llu) != fid gen (%llu)\n",
(u_longlong_t)zp_gen, (u_longlong_t)fid_gen);
vrele(ZTOV(zp));
+8 -12
View File
@@ -1005,18 +1005,14 @@ again:
*/
ASSERT3P(zp, !=, NULL);
ASSERT3U(zp->z_id, ==, obj_num);
if (zp->z_unlinked) {
err = SET_ERROR(ENOENT);
} else {
vp = ZTOV(zp);
/*
* Don't let the vnode disappear after
* ZFS_OBJ_HOLD_EXIT.
*/
VN_HOLD(vp);
*zpp = zp;
err = 0;
}
vp = ZTOV(zp);
/*
* Don't let the vnode disappear after
* ZFS_OBJ_HOLD_EXIT.
*/
VN_HOLD(vp);
*zpp = zp;
err = 0;
sa_buf_rele(db, NULL);
ZFS_OBJ_HOLD_EXIT(zfsvfs, obj_num);
+1 -1
View File
@@ -1683,7 +1683,7 @@ zfs_vget(struct super_block *sb, struct inode **ipp, fid_t *fidp)
zp_gen = 1;
if ((fid_gen == 0) && (zfsvfs->z_root == object))
fid_gen = zp_gen;
if (zp->z_unlinked || zp_gen != fid_gen) {
if (zp_gen != fid_gen) {
dprintf("znode gen (%llu) != fid gen (%llu)\n", zp_gen,
fid_gen);
zrele(zp);
+4
View File
@@ -1081,6 +1081,10 @@ tags = ['functional', 'trim']
tests = ['truncate_001_pos', 'truncate_002_pos', 'truncate_timestamps']
tags = ['functional', 'truncate']
[tests/functional/unlink]
tests = ['unlinked_fh_accessible']
tags = ['functional', 'unlink']
[tests/functional/upgrade]
tests = ['upgrade_userobj_001_pos', 'upgrade_readonly_pool']
tags = ['functional', 'upgrade']
+1
View File
@@ -61,3 +61,4 @@
/idmap_util
/statx
/setlease
/file_unlink_fh
+3
View File
@@ -145,3 +145,6 @@ endif
scripts_zfs_tests_bin_PROGRAMS += %D%/file_fadvise
%C%_file_fadvise_SOURCES = %D%/file/file_fadvise.c
scripts_zfs_tests_bin_PROGRAMS += %D%/file_unlink_fh
%C%_file_unlink_fh_SOURCES = %D%/file/file_unlink_fh.c
+145
View File
@@ -0,0 +1,145 @@
// 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 2026, tiehexue <tiehexue@hotmail.com>. All rights reserved.
*/
/*
* Verify that a filehandle-based reopen of an open-unlinked file
* succeeds. On Linux this exercises zfs_vget() via fh_to_dentry;
* on FreeBSD it exercises zfs_fhtovp() via VFS_FHTOVP.
*
* Exit 0 on success, non-zero on failure.
*
* Most code copied from https://reviews.freebsd.org/D57982
* and https://github.com/openzfs/zfs/issues/18699
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#ifdef __FreeBSD__
#include <sys/param.h>
#include <sys/mount.h>
#endif
int
main(int argc, char *argv[])
{
const char *filename;
int file_fd = -1, handle_fd = -1;
int ret = 2;
if (argc != 2) {
(void) fprintf(stderr, "usage: %s <filename>\n", argv[0]);
return (2);
}
filename = argv[1];
file_fd = open(filename, O_CREAT | O_RDWR | O_TRUNC, 0644);
if (file_fd < 0) {
perror("open");
goto out;
}
#ifdef __linux__
struct file_handle *fhp;
int mount_fd, mnt_id;
mount_fd = open(".", O_RDONLY | O_DIRECTORY);
if (mount_fd < 0) {
perror("open mount_fd");
goto out;
}
fhp = malloc(sizeof (struct file_handle) + 128);
if (fhp == NULL) {
perror("malloc");
(void) close(mount_fd);
goto out;
}
fhp->handle_bytes = 128;
/* Obtain filehandle while the file still has a name */
if (name_to_handle_at(AT_FDCWD, filename, fhp,
&mnt_id, 0) < 0) {
perror("name_to_handle_at");
free(fhp);
(void) close(mount_fd);
goto out;
}
/* Unlink — file_fd keeps the inode alive */
if (unlink(filename) < 0) {
perror("unlink");
free(fhp);
(void) close(mount_fd);
goto out;
}
/* Reopen via filehandle — exercises fh_to_dentry/zfs_vget */
handle_fd = open_by_handle_at(mount_fd, fhp, O_RDONLY);
free(fhp);
(void) close(mount_fd);
#elif defined(__FreeBSD__)
fhandle_t fh;
/* Obtain ZFS filehandle while the file still has a name */
if (getfh(filename, &fh) < 0) {
perror("getfh");
goto out;
}
/* Unlink — file_fd keeps the vnode alive */
if (unlink(filename) < 0) {
perror("unlink");
goto out;
}
/* Reopen via filehandle — exercises VFS_FHTOVP/zfs_fhtovp */
handle_fd = fhopen(&fh, O_RDONLY);
#endif
if (handle_fd < 0) {
(void) fprintf(stderr, "FAIL: filehandle reopen: %s\n",
strerror(errno));
ret = 1;
} else {
(void) printf("filehandle reopen succeeded, fd=%d\n",
handle_fd);
ret = 0;
}
out:
if (handle_fd >= 0)
(void) close(handle_fd);
if (file_fd >= 0)
(void) close(file_fd);
return (ret);
}
+1
View File
@@ -198,6 +198,7 @@ export ZFSTEST_FILES_COMMON='badsend
file_append
file_check
file_trunc
file_unlink_fh
file_write
get_diff
getversion
+3
View File
@@ -2283,6 +2283,9 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
functional/truncate/truncate_001_pos.ksh \
functional/truncate/truncate_002_pos.ksh \
functional/truncate/truncate_timestamps.ksh \
functional/unlink/cleanup.ksh \
functional/unlink/setup.ksh \
functional/unlink/unlinked_fh_accessible.ksh \
functional/upgrade/cleanup.ksh \
functional/upgrade/setup.ksh \
functional/upgrade/upgrade_projectquota_001_pos.ksh \
+30
View File
@@ -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 2026, tiehexue <[email protected]>. All rights reserved.
#
. $STF_SUITE/include/libtest.shlib
default_cleanup
+32
View File
@@ -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 2026, tiehexue <[email protected]>. All rights reserved.
#
. $STF_SUITE/include/libtest.shlib
DISK=${DISKS%% *}
default_setup_noexit $DISK
log_pass
@@ -0,0 +1,64 @@
#!/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 2026, tiehexue <[email protected]>. All rights reserved.
#
. $STF_SUITE/include/libtest.shlib
#
# DESCRIPTION:
# Verify that a filehandle-based reopen of an open-unlinked file
# succeeds. This tests that zfs_vget() / zfs_fhtovp() does not
# incorrectly reject znodes with z_unlinked when the inode/vnode
# is still referenced.
#
# STRATEGY:
# 1. Use the file_unlink_fh helper to create a file, obtain a
# filehandle (name_to_handle_at on Linux, getfh on FreeBSD),
# unlink the file, and then attempt to re-open it via
# open_by_handle_at (Linux) / fhopen (FreeBSD).
# 2. Verify that the reopen succeeds.
#
verify_runnable "global"
function cleanup
{
cd "$cwd" || true
[[ -e $TESTDIR ]] && log_must rm -Rf $TESTDIR/*
}
log_assert "filehandle reopen of open-unlinked file should succeed"
log_onexit cleanup
cwd=$PWD
log_must cd $TESTDIR
# file_unlink_fh: creates file, gets filehandle, unlinks, reopens via
# open_by_handle_at (Linux) or fhopen (FreeBSD). Exits 0 on success.
log_must file_unlink_fh unlinked_fh_testfile
log_pass "filehandle reopen of open-unlinked file succeeded"