Commit 0ef625bba6fb ("vfs: support statx(..., NULL, AT_EMPTY_PATH, ...)") added support for passing in NULL when AT_EMPTY_PATH is given, improving performance when statx is used for fetching stat informantion from a given fd, which is especially important for 32-bit platforms. This commit also improved the performance when an empty string is given by short-circuiting the handling of such paths.
This series is based on the commits in the Linus’ tree. Modifications are applied to vfs_statx_path(). In the original patch, vfs_statx_path() was created to warp around the call to vfs_getattr() after filename_lookup() in vfs_statx(). Since the coresponding code is different in 6.6, the content of vfs_statx_path() is modified to match this.
Tested-by: Xi Ruoyao xry111@xry111.site Signed-off-by: Miao Wang shankerwangmiao@gmail.com --- Christian Brauner (3): file: add fd_raw cleanup class fs: new helper vfs_empty_path() stat: use vfs_empty_path() helper
Mateusz Guzik (1): vfs: support statx(..., NULL, AT_EMPTY_PATH, ...)
fs/internal.h | 14 +++++++ fs/namespace.c | 13 ------ fs/stat.c | 113 ++++++++++++++++++++++++++++++++++++--------------- include/linux/file.h | 1 + include/linux/fs.h | 17 ++++++++ 5 files changed, 112 insertions(+), 46 deletions(-) --- base-commit: 6d1dc55b5bab93ef868d223b740d527ee7501063 change-id: 20240918-statx-stable-linux-6-6-y-02566b94440d
Best regards,
From: Christian Brauner brauner@kernel.org
commit a0fde7e upstream.
So we can also use CLASS(fd_raw, f)(fd) for codepaths where we allow FMODE_PATH aka O_PATH file descriptors to be used.
Signed-off-by: Christian Brauner brauner@kernel.org
Cc: stable@vger.kernel.org # 6.6.x Signed-off-by: Miao Wang shankerwangmiao@gmail.com Tested-by: Xi Ruoyao xry111@xry111.site --- include/linux/file.h | 1 + 1 file changed, 1 insertion(+)
diff --git a/include/linux/file.h b/include/linux/file.h index 6e9099d29343..963df2dc4f61 100644 --- a/include/linux/file.h +++ b/include/linux/file.h @@ -82,6 +82,7 @@ static inline void fdput_pos(struct fd f) }
DEFINE_CLASS(fd, struct fd, fdput(_T), fdget(fd), int fd) +DEFINE_CLASS(fd_raw, struct fd, fdput(_T), fdget_raw(fd), int fd)
extern int f_dupfd(unsigned int from, struct file *file, unsigned flags); extern int replace_fd(unsigned fd, struct file *file, unsigned flags);
From: Christian Brauner brauner@kernel.org
commit 1bc6d44 upstream.
Make it possible to quickly check whether AT_EMPTY_PATH is valid. Note, after some discussion we decided to also allow NULL to be passed instead of requiring the empty string.
Signed-off-by: Christian Brauner brauner@kernel.org
Cc: stable@vger.kernel.org # 6.6.x Signed-off-by: Miao Wang shankerwangmiao@gmail.com Tested-by: Xi Ruoyao xry111@xry111.site --- include/linux/fs.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
diff --git a/include/linux/fs.h b/include/linux/fs.h index 2a554e4045a9..809b354dbddf 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -3454,4 +3454,21 @@ extern int vfs_fadvise(struct file *file, loff_t offset, loff_t len, extern int generic_fadvise(struct file *file, loff_t offset, loff_t len, int advice);
+static inline bool vfs_empty_path(int dfd, const char __user *path) +{ + char c; + + if (dfd < 0) + return false; + + /* We now allow NULL to be used for empty path. */ + if (!path) + return true; + + if (unlikely(get_user(c, path))) + return false; + + return !c; +} + #endif /* _LINUX_FS_H */
From: Christian Brauner brauner@kernel.org
commit 27a2d0c upstream.
Use the newly added helper for this.
Signed-off-by: Christian Brauner brauner@kernel.org
Cc: stable@vger.kernel.org # 6.6.x Signed-off-by: Miao Wang shankerwangmiao@gmail.com Tested-by: Xi Ruoyao xry111@xry111.site --- fs/stat.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/fs/stat.c b/fs/stat.c index 5375be5f97cc..045b2a02de50 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -284,16 +284,8 @@ int vfs_fstatat(int dfd, const char __user *filename, * If AT_EMPTY_PATH is set, we expect the common case to be that * empty path, and avoid doing all the extra pathname work. */ - if (dfd >= 0 && flags == AT_EMPTY_PATH) { - char c; - - ret = get_user(c, filename); - if (unlikely(ret)) - return ret; - - if (likely(!c)) - return vfs_fstat(dfd, stat); - } + if (flags == AT_EMPTY_PATH && vfs_empty_path(dfd, filename)) + return vfs_fstat(dfd, stat);
name = getname_flags(filename, getname_statx_lookup_flags(statx_flags), NULL); ret = vfs_statx(dfd, name, statx_flags, stat, STATX_BASIC_STATS);
From: Mateusz Guzik mjguzik@gmail.com
commit 0ef625b upstream.
The newly used helper also checks for empty ("") paths.
NULL paths with any flag value other than AT_EMPTY_PATH go the usual route and end up with -EFAULT to retain compatibility (Rust is abusing calls of the sort to detect availability of statx).
This avoids path lookup code, lockref management, memory allocation and in case of NULL path userspace memory access (which can be quite expensive with SMAP on x86_64).
Benchmarked with statx(..., AT_EMPTY_PATH, ...) running on Sapphire Rapids, with the "" path for the first two cases and NULL for the last one.
Results in ops/s: stock: 4231237 pre-check: 5944063 (+40%) NULL path: 6601619 (+11%/+56%)
Signed-off-by: Mateusz Guzik mjguzik@gmail.com Link: https://lore.kernel.org/r/20240625151807.620812-1-mjguzik@gmail.com Tested-by: Xi Ruoyao xry111@xry111.site [brauner: use path_mounted() and other tweaks] Signed-off-by: Christian Brauner brauner@kernel.org
Cc: stable@vger.kernel.org # 6.6.x Signed-off-by: Miao Wang shankerwangmiao@gmail.com Tested-by: Xi Ruoyao xry111@xry111.site --- fs/internal.h | 14 ++++++++ fs/namespace.c | 13 -------- fs/stat.c | 101 ++++++++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 92 insertions(+), 36 deletions(-)
diff --git a/fs/internal.h b/fs/internal.h index d64ae03998cc..ac775b2316d5 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -231,6 +231,8 @@ extern const struct dentry_operations ns_dentry_operations; int getname_statx_lookup_flags(int flags); int do_statx(int dfd, struct filename *filename, unsigned int flags, unsigned int mask, struct statx __user *buffer); +int do_statx_fd(int fd, unsigned int flags, unsigned int mask, + struct statx __user *buffer);
/* * fs/splice.c: @@ -298,3 +300,15 @@ ssize_t __kernel_write_iter(struct file *file, struct iov_iter *from, loff_t *po struct mnt_idmap *alloc_mnt_idmap(struct user_namespace *mnt_userns); struct mnt_idmap *mnt_idmap_get(struct mnt_idmap *idmap); void mnt_idmap_put(struct mnt_idmap *idmap); +/** + * path_mounted - check whether path is mounted + * @path: path to check + * + * Determine whether @path refers to the root of a mount. + * + * Return: true if @path is the root of a mount, false if not. + */ +static inline bool path_mounted(const struct path *path) +{ + return path->mnt->mnt_root == path->dentry; +} diff --git a/fs/namespace.c b/fs/namespace.c index e6c61d4997cc..3c79fb8f94e8 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -1844,19 +1844,6 @@ bool may_mount(void) return ns_capable(current->nsproxy->mnt_ns->user_ns, CAP_SYS_ADMIN); }
-/** - * path_mounted - check whether path is mounted - * @path: path to check - * - * Determine whether @path refers to the root of a mount. - * - * Return: true if @path is the root of a mount, false if not. - */ -static inline bool path_mounted(const struct path *path) -{ - return path->mnt->mnt_root == path->dentry; -} - static void warn_mandlock(void) { pr_warn_once("=======================================================\n" diff --git a/fs/stat.c b/fs/stat.c index 045b2a02de50..aa91363342be 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -214,6 +214,38 @@ int getname_statx_lookup_flags(int flags) return lookup_flags; }
+static int vfs_statx_path(struct path *path, int flags, struct kstat *stat, + u32 request_mask) +{ + int error = vfs_getattr(path, stat, request_mask, flags); + + stat->mnt_id = real_mount(path->mnt)->mnt_id; + stat->result_mask |= STATX_MNT_ID; + + if (path_mounted(path)) + stat->attributes |= STATX_ATTR_MOUNT_ROOT; + stat->attributes_mask |= STATX_ATTR_MOUNT_ROOT; + + /* Handle STATX_DIOALIGN for block devices. */ + if (request_mask & STATX_DIOALIGN) { + struct inode *inode = d_backing_inode(path->dentry); + + if (S_ISBLK(inode->i_mode)) + bdev_statx_dioalign(inode, stat); + } + + return error; +} + +static int vfs_statx_fd(int fd, int flags, struct kstat *stat, + u32 request_mask) +{ + CLASS(fd_raw, f)(fd); + if (!f.file) + return -EBADF; + return vfs_statx_path(&f.file->f_path, flags, stat, request_mask); +} + /** * vfs_statx - Get basic and extra attributes by filename * @dfd: A file descriptor representing the base dir for a relative filename @@ -243,31 +275,13 @@ static int vfs_statx(int dfd, struct filename *filename, int flags, retry: error = filename_lookup(dfd, filename, lookup_flags, &path, NULL); if (error) - goto out; - - error = vfs_getattr(&path, stat, request_mask, flags); - - stat->mnt_id = real_mount(path.mnt)->mnt_id; - stat->result_mask |= STATX_MNT_ID; - - if (path.mnt->mnt_root == path.dentry) - stat->attributes |= STATX_ATTR_MOUNT_ROOT; - stat->attributes_mask |= STATX_ATTR_MOUNT_ROOT; - - /* Handle STATX_DIOALIGN for block devices. */ - if (request_mask & STATX_DIOALIGN) { - struct inode *inode = d_backing_inode(path.dentry); - - if (S_ISBLK(inode->i_mode)) - bdev_statx_dioalign(inode, stat); - } - + return error; + error = vfs_statx_path(&path, flags, stat, request_mask); path_put(&path); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; goto retry; } -out: return error; }
@@ -660,7 +674,8 @@ int do_statx(int dfd, struct filename *filename, unsigned int flags, if ((flags & AT_STATX_SYNC_TYPE) == AT_STATX_SYNC_TYPE) return -EINVAL;
- /* STATX_CHANGE_COOKIE is kernel-only for now. Ignore requests + /* + * STATX_CHANGE_COOKIE is kernel-only for now. Ignore requests * from userland. */ mask &= ~STATX_CHANGE_COOKIE; @@ -672,16 +687,41 @@ int do_statx(int dfd, struct filename *filename, unsigned int flags, return cp_statx(&stat, buffer); }
+int do_statx_fd(int fd, unsigned int flags, unsigned int mask, + struct statx __user *buffer) +{ + struct kstat stat; + int error; + + if (mask & STATX__RESERVED) + return -EINVAL; + if ((flags & AT_STATX_SYNC_TYPE) == AT_STATX_SYNC_TYPE) + return -EINVAL; + + /* + * STATX_CHANGE_COOKIE is kernel-only for now. Ignore requests + * from userland. + */ + mask &= ~STATX_CHANGE_COOKIE; + + error = vfs_statx_fd(fd, flags, &stat, mask); + if (error) + return error; + + return cp_statx(&stat, buffer); +} + /** * sys_statx - System call to get enhanced stats * @dfd: Base directory to pathwalk from *or* fd to stat. - * @filename: File to stat or "" with AT_EMPTY_PATH + * @filename: File to stat or either NULL or "" with AT_EMPTY_PATH * @flags: AT_* flags to control pathwalk. * @mask: Parts of statx struct actually required. * @buffer: Result buffer. * * Note that fstat() can be emulated by setting dfd to the fd of interest, - * supplying "" as the filename and setting AT_EMPTY_PATH in the flags. + * supplying "" (or preferably NULL) as the filename and setting AT_EMPTY_PATH + * in the flags. */ SYSCALL_DEFINE5(statx, int, dfd, const char __user *, filename, unsigned, flags, @@ -689,8 +729,23 @@ SYSCALL_DEFINE5(statx, struct statx __user *, buffer) { int ret; + unsigned lflags; struct filename *name;
+ /* + * Short-circuit handling of NULL and "" paths. + * + * For a NULL path we require and accept only the AT_EMPTY_PATH flag + * (possibly |'d with AT_STATX flags). + * + * However, glibc on 32-bit architectures implements fstatat as statx + * with the "" pathname and AT_NO_AUTOMOUNT | AT_EMPTY_PATH flags. + * Supporting this results in the uglification below. + */ + lflags = flags & ~(AT_NO_AUTOMOUNT | AT_STATX_SYNC_TYPE); + if (lflags == AT_EMPTY_PATH && vfs_empty_path(dfd, filename)) + return do_statx_fd(dfd, flags & ~AT_NO_AUTOMOUNT, mask, buffer); + name = getname_flags(filename, getname_statx_lookup_flags(flags), NULL); ret = do_statx(dfd, name, flags, mask, buffer); putname(name);
linux-stable-mirror@lists.linaro.org