mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-10-28 11:25:21 +01:00
zfs_ctldir: make .zfs/snapshot/<name> btime the snapshot creation time
Before the automount, '.zfs/snapshot/<name>' is a placeholder inode using simple_dir_inode_operations, which has no getattr, so statx(2) reports no birth time. Once mounted, it is the snapshot's root directory and reports that directory's crtime, i.e. the dataset creation time. A consumer therefore gets either no birth time or the dataset creation time, never the snapshot's, and the answer changes when the snapshot is automounted. Report the snapshot creation time as stx_btime consistently in both states, so it can be read with a plain statx(2) on the path. Give the placeholder its own inode_operations with a getattr that returns the creation time zfsctl_inode_alloc() already stores, and override the birth time of a mounted snapshot's root in zfs_getattr_fast() next to the existing inode number override. The STATX_BTIME copy moves from zpl_getattr() into zfs_getattr_fast() so the override is not overwritten. Add snapdir_stat_btime to ZTS. Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Alexander Motin <[email protected]> Signed-off-by: Ameer Hamza <[email protected]> Closes #19093
This commit is contained in:
1 parent
fa4bc4dec7
commit
f5b2fc8e29
8 files changed
+148
-13
No files matched your search
@@ -86,6 +86,8 @@ extern const struct inode_operations zpl_ops_root;
|
||||
extern const struct file_operations zpl_fops_snapdir;
|
||||
extern const struct inode_operations zpl_ops_snapdir;
|
||||
|
||||
extern const struct inode_operations zpl_ops_snapdirs;
|
||||
|
||||
extern const struct file_operations zpl_fops_shares;
|
||||
extern const struct inode_operations zpl_ops_shares;
|
||||
|
||||
|
||||
@@ -536,6 +536,8 @@ zfsctl_inode_alloc(zfsvfs_t *zfsvfs, uint64_t id,
|
||||
zp->z_pflags = 0;
|
||||
zp->z_mode = 0;
|
||||
zp->z_sync_cnt = 0;
|
||||
zp->z_btime.tv_sec = creation;
|
||||
zp->z_btime.tv_nsec = 0;
|
||||
ip->i_generation = 0;
|
||||
ip->i_ino = id;
|
||||
ip->i_mode = (S_IFDIR | S_IRWXUGO);
|
||||
@@ -865,7 +867,7 @@ zfsctl_snapdir_lookup(struct inode *dip, const char *name, struct inode **ipp,
|
||||
}
|
||||
|
||||
*ipp = zfsctl_inode_lookup(zfsvfs, ZFSCTL_INO_SNAPDIRS - id,
|
||||
&simple_dir_operations, &simple_dir_inode_operations);
|
||||
&simple_dir_operations, &zpl_ops_snapdirs);
|
||||
if (*ipp == NULL)
|
||||
error = SET_ERROR(ENOENT);
|
||||
|
||||
|
||||
@@ -1775,14 +1775,31 @@ zfs_getattr_fast(zidmap_t *idmap, u32 request_mask, struct inode *ip,
|
||||
|
||||
mutex_exit(&zp->z_lock);
|
||||
|
||||
#ifdef STATX_BTIME
|
||||
if (request_mask & STATX_BTIME) {
|
||||
sp->btime = zp->z_btime;
|
||||
sp->result_mask |= STATX_BTIME;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Required to prevent NFS client from detecting different inode
|
||||
* numbers of snapshot root dentry before and after snapshot mount.
|
||||
* Likewise report the snapshot creation time as the birth time,
|
||||
* matching the unmounted '.zfs/snapshot/<name>' directory.
|
||||
*/
|
||||
if (zfsvfs->z_issnap) {
|
||||
if (ip->i_sb->s_root->d_inode == ip)
|
||||
if (ip->i_sb->s_root->d_inode == ip) {
|
||||
sp->ino = ZFSCTL_INO_SNAPDIRS -
|
||||
dmu_objset_id(zfsvfs->z_os);
|
||||
#ifdef STATX_BTIME
|
||||
if (request_mask & STATX_BTIME) {
|
||||
sp->btime.tv_sec = dsl_get_creation(
|
||||
dmu_objset_ds(zfsvfs->z_os));
|
||||
sp->btime.tv_nsec = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
zfs_exit(zfsvfs, FTAG);
|
||||
|
||||
@@ -88,7 +88,7 @@ ZPL_IDMAP_IOP_DEFINE(int, zpl_root_getattr, 4,
|
||||
const struct path *, path, struct kstat *, stat, u32, request_mask,
|
||||
unsigned int, query_flags)
|
||||
{
|
||||
(void) request_mask, (void) query_flags;
|
||||
(void) query_flags;
|
||||
struct inode *ip = path->dentry->d_inode;
|
||||
zpl_generic_fillattr(idmap, request_mask, ip, stat);
|
||||
stat->atime = current_time(ip);
|
||||
@@ -731,7 +731,7 @@ ZPL_IDMAP_IOP_DEFINE(int, zpl_snapdir_getattr, 4,
|
||||
const struct path *, path, struct kstat *, stat, u32, request_mask,
|
||||
unsigned int, query_flags)
|
||||
{
|
||||
(void) request_mask, (void) query_flags;
|
||||
(void) query_flags;
|
||||
struct inode *ip = path->dentry->d_inode;
|
||||
zfsvfs_t *zfsvfs = ITOZSB(ip);
|
||||
int error;
|
||||
@@ -788,6 +788,38 @@ const struct inode_operations zpl_ops_snapdir = {
|
||||
.mkdir = zpl_snapdir_mkdir,
|
||||
};
|
||||
|
||||
/*
|
||||
* Get unmounted '.zfs/snapshot/<name>' directory attributes.
|
||||
*/
|
||||
ZPL_IDMAP_IOP_DEFINE(int, zpl_snapdirs_getattr, 4,
|
||||
const struct path *, path, struct kstat *, stat, u32, request_mask,
|
||||
unsigned int, query_flags)
|
||||
{
|
||||
(void) query_flags;
|
||||
struct inode *ip = path->dentry->d_inode;
|
||||
znode_t *zp __maybe_unused = ITOZ(ip);
|
||||
|
||||
zpl_generic_fillattr(idmap, request_mask, ip, stat);
|
||||
|
||||
#ifdef STATX_BTIME
|
||||
if ((request_mask & STATX_BTIME) && zp->z_btime.tv_sec != 0) {
|
||||
stat->btime = zp->z_btime;
|
||||
stat->result_mask |= STATX_BTIME;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* The '.zfs/snapshot/<name>' directory inode operations, used until the
|
||||
* snapshot is mounted over it.
|
||||
*/
|
||||
const struct inode_operations zpl_ops_snapdirs = {
|
||||
.lookup = simple_lookup,
|
||||
.getattr = zpl_snapdirs_getattr,
|
||||
};
|
||||
|
||||
static struct dentry *
|
||||
zpl_shares_lookup(struct inode *dip, struct dentry *dentry,
|
||||
unsigned int flags)
|
||||
@@ -854,7 +886,7 @@ ZPL_IDMAP_IOP_DEFINE(int, zpl_shares_getattr, 4,
|
||||
const struct path *, path, struct kstat *, stat, u32, request_mask,
|
||||
unsigned int, query_flags)
|
||||
{
|
||||
(void) request_mask, (void) query_flags;
|
||||
(void) query_flags;
|
||||
struct inode *ip = path->dentry->d_inode;
|
||||
zfsvfs_t *zfsvfs = ITOZSB(ip);
|
||||
znode_t *dzp;
|
||||
|
||||
@@ -432,13 +432,6 @@ ZPL_IDMAP_IOP_DEFINE(int, zpl_getattr, 4,
|
||||
|
||||
error = -zfs_getattr_fast(idmap, request_mask, ip, stat);
|
||||
|
||||
#ifdef STATX_BTIME
|
||||
if (request_mask & STATX_BTIME) {
|
||||
stat->btime = zp->z_btime;
|
||||
stat->result_mask |= STATX_BTIME;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef STATX_CHANGE_COOKIE
|
||||
if (request_mask & STATX_CHANGE_COOKIE) {
|
||||
/*
|
||||
|
||||
@@ -238,7 +238,7 @@ tests = ['snapdir_admin_create_destroy', 'snapdir_admin_rename_destroy',
|
||||
'snapdir_stat_no_mount', 'snapdir_mount_bind', 'snapdir_mount_move',
|
||||
'snapdir_mount_expire_bind', 'snapdir_mount_expire_move',
|
||||
'snapdir_mount_multiple', 'snapdir_mount_fd_hold', 'snapdir_mount_foreign',
|
||||
'snapdir_mount_destroy_defer']
|
||||
'snapdir_mount_destroy_defer', 'snapdir_stat_btime']
|
||||
tags = ['functional', 'snapdir']
|
||||
|
||||
[tests/functional/syncfs:Linux]
|
||||
|
||||
@@ -2296,6 +2296,7 @@ nobase_dist_datadir_zfs_tests_tests_SCRIPTS += \
|
||||
functional/snapdir/snapdir_mount_no_setuid.ksh \
|
||||
functional/snapdir/snapdir_mount_parallel.ksh \
|
||||
functional/snapdir/snapdir_mount_unmount.ksh \
|
||||
functional/snapdir/snapdir_stat_btime.ksh \
|
||||
functional/snapdir/snapdir_stat_no_mount.ksh \
|
||||
functional/snapshot/cleanup.ksh \
|
||||
functional/snapshot/clone_001_pos.ksh \
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/bin/ksh -p
|
||||
# SPDX-License-Identifier: CDDL-1.0
|
||||
#
|
||||
# This file and its contents are supplied under the terms of the
|
||||
# Common Development and Distribution License ("CDDL"), version 1.0.
|
||||
# You may only use this file in accordance with the terms of version
|
||||
# 1.0 of the CDDL.
|
||||
#
|
||||
# A full copy of the text of the CDDL should have accompanied this
|
||||
# source. A copy of the CDDL is also available via the Internet at
|
||||
# https://opensource.org/license/CDDL-1.0.
|
||||
#
|
||||
|
||||
#
|
||||
# Copyright (c) 2026 by iXsystems, Inc.
|
||||
#
|
||||
|
||||
. $STF_SUITE/tests/functional/snapdir/snapdir.kshlib
|
||||
. $STF_SUITE/tests/functional/snapdir/snapdir.cfg
|
||||
|
||||
verify_runnable "both"
|
||||
|
||||
log_assert "Verify .zfs/snapshot/<name> reports the snapshot creation time as" \
|
||||
"its birth time whether or not the snapshot is mounted"
|
||||
|
||||
if ! is_linux ; then
|
||||
log_unsupported "This test uses the Linux-only statx helper"
|
||||
fi
|
||||
|
||||
function cleanup
|
||||
{
|
||||
destroy_pool $TESTPOOL
|
||||
}
|
||||
|
||||
log_onexit cleanup
|
||||
|
||||
# The statx helper opens with O_PATH and never automounts, so it can observe
|
||||
# the unmounted placeholder. It exits 2 if STATX_BTIME is not in stx_mask.
|
||||
function check_btime # <path> <expected seconds> <description>
|
||||
{
|
||||
typeset out btime
|
||||
out=$(statx btime $1 2>&1) || log_fail "$3: statx btime failed: $out"
|
||||
btime=${out#btime: }
|
||||
log_note "$3: btime $btime (snapshot creation $2)"
|
||||
[[ ${btime%.*} -eq $2 && ${btime#*.} -eq 0 ]] ||
|
||||
log_fail "$3: btime $btime != snapshot creation $2"
|
||||
}
|
||||
|
||||
# Create a pool and a filesystem
|
||||
create_pool $TESTPOOL $DISKS
|
||||
log_must zfs create -o snapdir=visible -o mountpoint=$TESTDIR/fs \
|
||||
$TESTPOOL/$TESTFS
|
||||
|
||||
# The creation property has one-second granularity; make sure the snapshot's
|
||||
# differs from the filesystem root's crtime, which is what the mounted root
|
||||
# used to report, so that the mounted-state check below is discriminating.
|
||||
sleep 2
|
||||
log_must zfs snapshot $TESTPOOL/$TESTFS@snap
|
||||
|
||||
typeset snap=$TESTPOOL/$TESTFS@snap
|
||||
typeset snapdir=$TESTDIR/fs/$SNAPROOT/snap
|
||||
typeset -i creation=$(get_prop creation $snap)
|
||||
typeset fs_btime
|
||||
fs_btime=$(statx btime $TESTDIR/fs) ||
|
||||
log_fail "statx btime $TESTDIR/fs failed: $fs_btime"
|
||||
fs_btime=${fs_btime#btime: }
|
||||
log_must test ${fs_btime%.*} -lt $creation
|
||||
|
||||
# 1. Unmounted placeholder: reports the snapshot creation time, and
|
||||
# statx itself must not have mounted the snapshot.
|
||||
log_must test -z "$(get_mount_paths $snap)"
|
||||
check_btime $snapdir $creation "unmounted"
|
||||
log_must test -z "$(get_mount_paths $snap)"
|
||||
typeset ino=$(statx ino $snapdir)
|
||||
|
||||
# 2. Mounted root: trigger the automount, same birth time, same inode
|
||||
# number (zfs_getattr_fast() remaps both for the snapshot root).
|
||||
log_must ls -l $snapdir
|
||||
log_must test "$(get_mount_paths $snap)" == "$snapdir"
|
||||
check_btime $snapdir $creation "mounted"
|
||||
log_must test "$(statx ino $snapdir)" == "$ino"
|
||||
|
||||
# 3. After an explicit unmount the placeholder is back.
|
||||
log_must umount $snapdir
|
||||
log_must test -z "$(get_mount_paths $snap)"
|
||||
check_btime $snapdir $creation "unmounted again"
|
||||
|
||||
log_pass "Snapshot dir birth time is the snapshot creation time in both states."
|
||||
Reference in new issue
Block a user