From 0bd8481aa751d7be7ea90ed47a4c8c208f066c03 Mon Sep 17 00:00:00 2001 From: Charles Hedrick Date: Wed, 4 Dec 2024 17:01:57 -0500 Subject: [PATCH] add get_name implementation for exports. (#16833) This fixes a serious performance problem with NFS handling of large directories, as the new get_name code is much more efficient than the default zfs_readdir. This is actually part of 20232ecfaa34177bef6c08f2f1a55b8c8bd20da4 in 2.3. But I've taken only the minimum code to implement get_name, and not the rest of the long name changes. Signed-off-by: Charles Hedrick Co-authored-by: Charles L. Hedrick Reviewed-by: Alexander Motin Reviewed by: Brian Behlendorf Reviewed by: Brian Behlendorf Reviewed-by: Tony Hutter --- include/os/linux/zfs/sys/zfs_vnops_os.h | 1 + module/os/linux/zfs/zfs_vnops_os.c | 42 +++++++++++++++++++++++++ module/os/linux/zfs/zpl_export.c | 32 +++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/include/os/linux/zfs/sys/zfs_vnops_os.h b/include/os/linux/zfs/sys/zfs_vnops_os.h index f34eb153f546..db33eda57705 100644 --- a/include/os/linux/zfs/sys/zfs_vnops_os.h +++ b/include/os/linux/zfs/sys/zfs_vnops_os.h @@ -44,6 +44,7 @@ extern int zfs_write_simple(znode_t *zp, const void *data, size_t len, loff_t pos, size_t *resid); extern int zfs_lookup(znode_t *dzp, char *nm, znode_t **zpp, int flags, cred_t *cr, int *direntflags, pathname_t *realpnp); +extern int zfs_get_name(znode_t *dzp, char *name, znode_t *zp); extern int zfs_create(znode_t *dzp, char *name, vattr_t *vap, int excl, int mode, znode_t **zpp, cred_t *cr, int flag, vsecattr_t *vsecp, zidmap_t *mnt_ns); diff --git a/module/os/linux/zfs/zfs_vnops_os.c b/module/os/linux/zfs/zfs_vnops_os.c index 97de89767d93..c26d232b0021 100644 --- a/module/os/linux/zfs/zfs_vnops_os.c +++ b/module/os/linux/zfs/zfs_vnops_os.c @@ -526,6 +526,48 @@ zfs_lookup(znode_t *zdp, char *nm, znode_t **zpp, int flags, cred_t *cr, return (error); } +/* + * Perform a linear search in directory for the name of specific inode. + * Note we don't pass in the buffer size of name because it's hardcoded to + * NAME_MAX+1(256) in Linux. + * + * IN: dzp - znode of directory to search. + * zp - znode of the target + * + * OUT: name - dentry name of the target + * + * RETURN: 0 on success, error code on failure. + */ +int +zfs_get_name(znode_t *dzp, char *name, znode_t *zp) +{ + zfsvfs_t *zfsvfs = ZTOZSB(dzp); + int error = 0; + + if ((error = zfs_enter_verify_zp(zfsvfs, dzp, FTAG)) != 0) + return (error); + + if ((error = zfs_verify_zp(zp)) != 0) { + zfs_exit(zfsvfs, FTAG); + return (error); + } + + /* ctldir should have got their name in zfs_vget */ + if (dzp->z_is_ctldir || zp->z_is_ctldir) { + zfs_exit(zfsvfs, FTAG); + return (ENOENT); + } + + /* buffer len is hardcoded to 256 in Linux kernel */ + error = zap_value_search(zfsvfs->z_os, dzp->z_id, zp->z_id, + ZFS_DIRENT_OBJ(-1ULL), name); + + zfs_exit(zfsvfs, FTAG); + return (error); +} + + + /* * Attempt to create a new entry in a directory. If the entry * already exists, truncate the file if permissible, else return diff --git a/module/os/linux/zfs/zpl_export.c b/module/os/linux/zfs/zpl_export.c index b6b9e2754055..2d749d9c9376 100644 --- a/module/os/linux/zfs/zpl_export.c +++ b/module/os/linux/zfs/zpl_export.c @@ -24,6 +24,7 @@ */ +#include #include #include #include @@ -102,6 +103,36 @@ zpl_fh_to_dentry(struct super_block *sb, struct fid *fh, return (d_obtain_alias(ip)); } +/* + * In case the filesystem contains name longer than 255, we need to override + * the default get_name so we don't get buffer overflow. Unfortunately, since + * the buffer size is hardcoded in Linux, we will get ESTALE error in this + * case. + */ +static int +zpl_get_name(struct dentry *parent, char *name, struct dentry *child) +{ + cred_t *cr = CRED(); + fstrans_cookie_t cookie; + struct inode *dir = parent->d_inode; + struct inode *ip = child->d_inode; + int error; + + if (!dir || !S_ISDIR(dir->i_mode)) + return (-ENOTDIR); + + crhold(cr); + cookie = spl_fstrans_mark(); + spl_inode_lock_shared(dir); + error = -zfs_get_name(ITOZ(dir), name, ITOZ(ip)); + spl_inode_unlock_shared(dir); + spl_fstrans_unmark(cookie); + crfree(cr); + + return (error); +} + + static struct dentry * zpl_get_parent(struct dentry *child) { @@ -146,6 +177,7 @@ zpl_commit_metadata(struct inode *inode) const struct export_operations zpl_export_operations = { .encode_fh = zpl_encode_fh, .fh_to_dentry = zpl_fh_to_dentry, + .get_name = zpl_get_name, .get_parent = zpl_get_parent, .commit_metadata = zpl_commit_metadata, };