FreeBSD: avoid lookup overhead for nonexistent xattr directories

Port the z_xattr_dir_absent cache to FreeBSD.  As on Linux, a
getextattr that misses in the SA otherwise falls through to
zfs_get_xattrdir(), which takes the "" ZXATTR dirlock and reads
SA_ZPL_XATTR only to find the file has no xattr directory.  The
in-core znode now caches that: after an SA miss zfs_getextattr_impl()
skips the directory lookup when the flag is set, zfs_get_xattrdir()
sets it when no directory is found and clears it when one is found,
and zfs_make_xattrdir() clears it on creation, which also covers the
TX_MKXATTR ZIL replay path.

The flag is serialized by the base file's vnode lock.
zfs_make_xattrdir(), the only path that creates the directory and
clears the flag, runs with the vnode held exclusive, while every
reader that sets the flag holds it shared, so a set can never race
the clear.  ASSERT_VOP_ELOCKED() in zfs_make_xattrdir() and
ASSERT_VOP_LOCKED() in zfs_get_xattrdir() enforce this, both skipped
during ZIL replay since it is single threaded with no locked vnode.
There is no on-disk format change.

Reviewed-by: Alexander Motin <[email protected]>
Reviewed-by: Brian Behlendorf <[email protected]>
Reviewed-by: Richard Yao <[email protected]>
Reviewed-by: Rob Norris <[email protected]>
Signed-off-by: Ameer Hamza <[email protected]>
Closes #18687
This commit is contained in:
Ameer Hamza authored and Brian Behlendorf committed 2026-06-24 10:21:46 -07:00
1 parent 04092e815e
commit 2ea519c2a5
3 files changed
+32 -2

No files matched your search

+27 -1
View File
@@ -828,6 +828,9 @@ zfs_make_xattrdir(znode_t *zp, vattr_t *vap, znode_t **xvpp, cred_t *cr)
boolean_t fuid_dirtied;
uint64_t parent __maybe_unused;
if (zfsvfs->z_replay == B_FALSE)
ASSERT_VOP_ELOCKED(ZTOV(zp), __func__);
*xvpp = NULL;
if ((error = zfs_acl_ids_create(zp, IS_XATTR, vap, cr, NULL,
@@ -875,6 +878,9 @@ zfs_make_xattrdir(znode_t *zp, vattr_t *vap, znode_t **xvpp, cred_t *cr)
getnewvnode_drop_reserve();
/* Record that the file now has an xattr directory. */
zp->z_xattr_dir_absent = B_FALSE;
*xvpp = xzp;
return (0);
@@ -900,6 +906,19 @@ zfs_get_xattrdir(znode_t *zp, znode_t **xzpp, cred_t *cr, int flags)
znode_t *xzp;
vattr_t va;
int error;
if (zfsvfs->z_replay == B_FALSE)
ASSERT_VOP_LOCKED(ZTOV(zp), __func__);
/*
* Fast path: a file already known to have no xattr directory, when not
* creating one, returns without taking the "" ZXATTR dirlock or doing
* the SA_ZPL_XATTR lookup below. z_xattr_dir_absent tracks this: it is
* set when the lookup finds no directory and cleared when one is found
* or created.
*/
if (!(flags & CREATE_XATTR_DIR) && zp->z_xattr_dir_absent)
return (SET_ERROR(ENOATTR));
top:
error = zfs_dirent_lookup(zp, "", &xzp, ZXATTR);
if (error)
@@ -907,12 +926,19 @@ top:
if (xzp != NULL) {
*xzpp = xzp;
zp->z_xattr_dir_absent = B_FALSE;
return (0);
}
if (!(flags & CREATE_XATTR_DIR))
if (!(flags & CREATE_XATTR_DIR)) {
/*
* z_xattr_dir_absent is serialized by the base vnode lock,
* which the creator holds exclusive and readers hold shared.
*/
zp->z_xattr_dir_absent = B_TRUE;
return (SET_ERROR(ENOATTR));
}
if (zfsvfs->z_vfs->vfs_flag & VFS_RDONLY) {
return (SET_ERROR(EROFS));
+1 -1
View File
@@ -6021,7 +6021,7 @@ zfs_getextattr_impl(struct vop_getextattr_args *ap, boolean_t compat)
error = ENOENT;
if (zfsvfs->z_use_sa && zp->z_is_sa)
error = zfs_getextattr_sa(ap, attrname);
if (error == ENOENT)
if (error == ENOENT && !zp->z_xattr_dir_absent)
error = zfs_getextattr_dir(ap, attrname);
return (error);
}
+4
View File
@@ -287,6 +287,7 @@ zfs_create_share_dir(zfsvfs_t *zfsvfs, dmu_tx_t *tx)
ASSERT(!POINTER_IS_VALID(sharezp->z_zfsvfs));
sharezp->z_unlinked = 0;
sharezp->z_atime_dirty = 0;
sharezp->z_xattr_dir_absent = B_FALSE;
sharezp->z_zfsvfs = zfsvfs;
sharezp->z_is_sa = zfsvfs->z_use_sa;
sharezp->z_pflags = 0;
@@ -447,6 +448,7 @@ zfs_znode_alloc(zfsvfs_t *zfsvfs, dmu_buf_t *db, int blksz,
zp->z_sa_hdl = NULL;
zp->z_unlinked = 0;
zp->z_atime_dirty = 0;
zp->z_xattr_dir_absent = B_FALSE;
zp->z_mapcnt = 0;
zp->z_id = db->db_object;
zp->z_blksz = blksz;
@@ -1130,6 +1132,7 @@ zfs_rezget(znode_t *zp)
nvlist_free(zp->z_xattr_cached);
zp->z_xattr_cached = NULL;
}
zp->z_xattr_dir_absent = B_FALSE;
rw_exit(&zp->z_xattr_lock);
ASSERT0P(zp->z_sa_hdl);
@@ -1810,6 +1813,7 @@ zfs_create_fs(objset_t *os, cred_t *cr, nvlist_t *zplprops, dmu_tx_t *tx)
ASSERT(!POINTER_IS_VALID(rootzp->z_zfsvfs));
rootzp->z_unlinked = 0;
rootzp->z_atime_dirty = 0;
rootzp->z_xattr_dir_absent = B_FALSE;
rootzp->z_is_sa = USE_SA(version, os);
rootzp->z_pflags = 0;