From dedd939fe6c47431b136b13110ea0b5384ead8dd Mon Sep 17 00:00:00 2001 From: Andrew Walker Date: Wed, 31 Dec 2025 13:14:41 -0600 Subject: [PATCH] Add fh_to_parent export definition This commit adds support for converting a file handle to its parent dentry. This is called in exportfs_decode_fh_raw() when subtree checking is enabled in NFS. Defining this and handling the expanded filehandles allows the knfsd to succeed in handling the file handle where it might otherwise fail with ESTALE when trying to open by filehandle. A side effect of this change is that name_to_handle_at(2) and open_by_handle_at(2) now support AT_HANDLE_CONNECTABLE. Signed-off-by: Andrew Walker --- module/os/linux/zfs/zpl_export.c | 87 +++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 6 deletions(-) diff --git a/module/os/linux/zfs/zpl_export.c b/module/os/linux/zfs/zpl_export.c index 711da151f65e..0568bb63c75e 100644 --- a/module/os/linux/zfs/zpl_export.c +++ b/module/os/linux/zfs/zpl_export.c @@ -37,8 +37,8 @@ zpl_encode_fh(struct inode *ip, __u32 *fh, int *max_len, struct inode *parent) { fstrans_cookie_t cookie; ushort_t empty_fid = 0; - fid_t *fid; - int len_bytes, rc; + fid_t *fid, *pfid; + int len_bytes, required_len, parent_len, rc, prc, fh_type; len_bytes = *max_len * sizeof (__u32); @@ -56,11 +56,44 @@ zpl_encode_fh(struct inode *ip, __u32 *fh, int *max_len, struct inode *parent) else rc = zfs_fid(ip, fid); + required_len = offsetof(fid_t, fid_data) + fid->fid_len; + + /* + * Kernel has requested that the resulting file handle contain + * a reference to the provided parent. This typically would happen + * if the NFS export has subtree checking enabled. + */ + if (parent != NULL) { + if ((rc == 0) && (len_bytes > + required_len + offsetof(fid_t, fid_data))) { + parent_len = len_bytes - required_len; + pfid = (fid_t *)((char *)fh + required_len); + pfid->fid_len = parent_len - offsetof(fid_t, fid_data); + } else { + empty_fid = 0; + pfid = (fid_t *)&empty_fid; + } + + if (zfsctl_is_node(parent)) + prc = zfsctl_fid(parent, pfid); + else + prc = zfs_fid(parent, pfid); + + if (rc == 0 && prc != 0) + rc = prc; + + required_len += offsetof(fid_t, fid_data) + + pfid->fid_len; + fh_type = FILEID_INO32_GEN_PARENT; + } else { + fh_type = FILEID_INO32_GEN; + } + spl_fstrans_unmark(cookie); - len_bytes = offsetof(fid_t, fid_data) + fid->fid_len; - *max_len = roundup(len_bytes, sizeof (__u32)) / sizeof (__u32); - return (rc == 0 ? FILEID_INO32_GEN : 255); + *max_len = roundup(required_len, sizeof (__u32)) / sizeof (__u32); + + return (rc == 0 ? fh_type : FILEID_INVALID); } static struct dentry * @@ -74,7 +107,8 @@ zpl_fh_to_dentry(struct super_block *sb, struct fid *fh, len_bytes = fh_len * sizeof (__u32); - if (fh_type != FILEID_INO32_GEN || + if ((fh_type != FILEID_INO32_GEN && + fh_type != FILEID_INO32_GEN_PARENT) || len_bytes < offsetof(fid_t, fid_data) || len_bytes < offsetof(fid_t, fid_data) + fid->fid_len) return (ERR_PTR(-EINVAL)); @@ -104,6 +138,46 @@ zpl_fh_to_dentry(struct super_block *sb, struct fid *fh, return (d_obtain_alias(ip)); } +static struct dentry * +zpl_fh_to_parent(struct super_block *sb, struct fid *fh, + int fh_len, int fh_type) +{ + /* + * Convert the provided struct fid to a dentry for the parent + * This is possible only if it was created with the parent, + * e.g. type is FILEID_INO32_GEN_PARENT. When this type of + * filehandle is created we simply pack the parent fid_t + * after the entry's fid_t. So this function will adjust + * offset in the provided buffer to the begining of the + * parent fid_t and call zpl_fh_to_dentry() on it. + */ + fid_t *fid = (fid_t *)fh; + fid_t *pfid; + int len_bytes, parent_len_bytes, child_fid_bytes, parent_fh_len; + + len_bytes = fh_len * sizeof (__u32); + + if ((fh_type != FILEID_INO32_GEN_PARENT) || + len_bytes < offsetof(fid_t, fid_data) || + len_bytes < offsetof(fid_t, fid_data) + fid->fid_len) + return (ERR_PTR(-EINVAL)); + + child_fid_bytes = offsetof(fid_t, fid_data) + fid->fid_len; + parent_len_bytes = len_bytes - child_fid_bytes; + + if (parent_len_bytes < offsetof(fid_t, fid_data)) + return (ERR_PTR(-EINVAL)); + + pfid = (fid_t *)((char *)fh + child_fid_bytes); + + if (parent_len_bytes < offsetof(fid_t, fid_data) + pfid->fid_len) + return (ERR_PTR(-EINVAL)); + + parent_fh_len = parent_len_bytes / sizeof (__u32); + return (zpl_fh_to_dentry(sb, (struct fid *)pfid, parent_fh_len, + FILEID_INO32_GEN)); +} + /* * 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 @@ -177,6 +251,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, + .fh_to_parent = zpl_fh_to_parent, .get_name = zpl_get_name, .get_parent = zpl_get_parent, .commit_metadata = zpl_commit_metadata,