mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
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:
1 parent
83bf407840
commit
6acb99cb1b
12 files changed
+293
-18
No files matched your search
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -61,3 +61,4 @@
|
||||
/idmap_util
|
||||
/statx
|
||||
/setlease
|
||||
/file_unlink_fh
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -198,6 +198,7 @@ export ZFSTEST_FILES_COMMON='badsend
|
||||
file_append
|
||||
file_check
|
||||
file_trunc
|
||||
file_unlink_fh
|
||||
file_write
|
||||
get_diff
|
||||
getversion
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
Reference in new issue
Block a user