Message ID | 20241007130825.10326-3-xry111@xry111.site (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | vfs: fstatat, statx: Consistently accept AT_EMPTY_PATH and NULL path | expand |
On Mon, Oct 7, 2024 at 3:08 PM Xi Ruoyao <xry111@xry111.site> wrote: > > We've supported {statx,fstatat}(real_fd, NULL, AT_EMPTY_PATH, ...) since > Linux 6.11 for better performance. However there are other cases, for > example using AT_FDCWD as the fd or having AT_SYMLINK_NOFOLLOW in flags, > not covered by the fast path. While it may be impossible, too > difficult, or not very beneficial to optimize these cases, we should > still turn NULL into "" for them in the slow path to make the API easier > to be documented and used. > > Fixes: 0ef625bba6fb ("vfs: support statx(..., NULL, AT_EMPTY_PATH, ...)") > Cc: stable@vger.kernel.org > Signed-off-by: Xi Ruoyao <xry111@xry111.site> > --- > fs/stat.c | 10 ++++++++-- > 1 file changed, 8 insertions(+), 2 deletions(-) > > diff --git a/fs/stat.c b/fs/stat.c > index ed9d4fd8ba2c..5d1b51c23c62 100644 > --- a/fs/stat.c > +++ b/fs/stat.c > @@ -337,8 +337,11 @@ int vfs_fstatat(int dfd, const char __user *filename, > flags &= ~AT_NO_AUTOMOUNT; > if (flags == AT_EMPTY_PATH && vfs_empty_path(dfd, filename)) > return vfs_fstat(dfd, stat); > + else if ((flags & AT_EMPTY_PATH) && !filename) > + name = getname_kernel(""); > + else > + name = getname_flags(filename, getname_statx_lookup_flags(statx_flags)); > > - name = getname_flags(filename, getname_statx_lookup_flags(statx_flags)); > ret = vfs_statx(dfd, name, statx_flags, stat, STATX_BASIC_STATS); > putname(name); > > @@ -791,8 +794,11 @@ SYSCALL_DEFINE5(statx, > 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); > + else if ((lflags & AT_EMPTY_PATH) && !filename) > + name = getname_kernel(""); > + else > + name = getname_flags(filename, getname_statx_lookup_flags(flags)); > > - name = getname_flags(filename, getname_statx_lookup_flags(flags)); > ret = do_statx(dfd, name, flags, mask, buffer); > putname(name); > I thought you are going to patch up the 2 callsites of vfs_empty_path() or add the flags argument to said routine so that it can do the branching internally. Either way I don't think implementing AT_FDCWD + NULL + AT_EMPTY_PATH with getname_kernel("") is necessary.
On Tue, Oct 08, 2024 at 05:57:00AM +0200, Mateusz Guzik wrote: > On Mon, Oct 7, 2024 at 3:08 PM Xi Ruoyao <xry111@xry111.site> wrote: > > > > We've supported {statx,fstatat}(real_fd, NULL, AT_EMPTY_PATH, ...) since > > Linux 6.11 for better performance. However there are other cases, for > > example using AT_FDCWD as the fd or having AT_SYMLINK_NOFOLLOW in flags, > > not covered by the fast path. While it may be impossible, too > > difficult, or not very beneficial to optimize these cases, we should > > still turn NULL into "" for them in the slow path to make the API easier > > to be documented and used. > > > > Fixes: 0ef625bba6fb ("vfs: support statx(..., NULL, AT_EMPTY_PATH, ...)") > > Cc: stable@vger.kernel.org > > Signed-off-by: Xi Ruoyao <xry111@xry111.site> > > --- > > fs/stat.c | 10 ++++++++-- > > 1 file changed, 8 insertions(+), 2 deletions(-) > > > > diff --git a/fs/stat.c b/fs/stat.c > > index ed9d4fd8ba2c..5d1b51c23c62 100644 > > --- a/fs/stat.c > > +++ b/fs/stat.c > > @@ -337,8 +337,11 @@ int vfs_fstatat(int dfd, const char __user *filename, > > flags &= ~AT_NO_AUTOMOUNT; > > if (flags == AT_EMPTY_PATH && vfs_empty_path(dfd, filename)) > > return vfs_fstat(dfd, stat); > > + else if ((flags & AT_EMPTY_PATH) && !filename) > > + name = getname_kernel(""); > > + else > > + name = getname_flags(filename, getname_statx_lookup_flags(statx_flags)); > > > > - name = getname_flags(filename, getname_statx_lookup_flags(statx_flags)); > > ret = vfs_statx(dfd, name, statx_flags, stat, STATX_BASIC_STATS); > > putname(name); > > > > @@ -791,8 +794,11 @@ SYSCALL_DEFINE5(statx, > > 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); > > + else if ((lflags & AT_EMPTY_PATH) && !filename) > > + name = getname_kernel(""); > > + else > > + name = getname_flags(filename, getname_statx_lookup_flags(flags)); > > > > - name = getname_flags(filename, getname_statx_lookup_flags(flags)); > > ret = do_statx(dfd, name, flags, mask, buffer); > > putname(name); > > > > I thought you are going to patch up the 2 callsites of > vfs_empty_path() or add the flags argument to said routine so that it > can do the branching internally. > > Either way I don't think implementing AT_FDCWD + NULL + AT_EMPTY_PATH > with getname_kernel("") is necessary. Folks, please don't go there. Really. IMO vfs_empty_path() is a wrong API in the first place. Too low-level and racy as well. See the approach in #work.xattr; I'm going to lift that into fs/namei.c (well, the slow path - everything after "if path is NULL, we are done") and yes, fs/stat.c users get handled better that way.
On Tue, Oct 08, 2024 at 05:16:21AM +0100, Al Viro wrote: > Folks, please don't go there. Really. IMO vfs_empty_path() is a wrong API > in the first place. Too low-level and racy as well. > > See the approach in #work.xattr; I'm going to lift that into fs/namei.c > (well, the slow path - everything after "if path is NULL, we are done") and > yes, fs/stat.c users get handled better that way. FWIW, the intermediate (just after that commit) state of those functions is int vfs_fstatat(int dfd, const char __user *filename, struct kstat *stat, int flags) { int ret; int statx_flags = flags | AT_NO_AUTOMOUNT; struct filename *name = getname_maybe_null(filename, flags); if (!name) return vfs_fstat(dfd, stat); ret = vfs_statx(dfd, name, statx_flags, stat, STATX_BASIC_STATS); putname(name); return ret; } and SYSCALL_DEFINE5(statx, int, dfd, const char __user *, filename, unsigned, flags, unsigned int, mask, struct statx __user *, buffer) { int ret; unsigned lflags; struct filename *name = getname_maybe_null(filename, flags); /* * 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 (!name) return do_statx_fd(dfd, flags & ~AT_NO_AUTOMOUNT, mask, buffer); ret = do_statx(dfd, name, flags, mask, buffer); putname(name); return ret; } static inline struct filename *getname_maybe_null(const char __user *name, int flags) { if (!(flags & AT_EMPTY_PATH)) return getname(name); if (!name) return NULL; return __getname_maybe_null(name); } struct filename *__getname_maybe_null(const char __user *pathname) { struct filename *name; char c; /* try to save on allocations; loss on um, though */ if (get_user(c, pathname)) return ERR_PTR(-EFAULT); if (!c) return NULL; name = getname_flags(pathname, LOOKUP_EMPTY); if (!IS_ERR(name) && !(name->name[0])) { putname(name); name = NULL; } return name; }
On Tue, Oct 08, 2024 at 05:27:51AM +0100, Al Viro wrote: > On Tue, Oct 08, 2024 at 05:16:21AM +0100, Al Viro wrote: > > > Folks, please don't go there. Really. IMO vfs_empty_path() is a wrong API > > in the first place. Too low-level and racy as well. > > > > See the approach in #work.xattr; I'm going to lift that into fs/namei.c > > (well, the slow path - everything after "if path is NULL, we are done") and > > yes, fs/stat.c users get handled better that way. > > FWIW, the intermediate (just after that commit) state of those functions is > > int vfs_fstatat(int dfd, const char __user *filename, > struct kstat *stat, int flags) > { > int ret; > int statx_flags = flags | AT_NO_AUTOMOUNT; > struct filename *name = getname_maybe_null(filename, flags); > > if (!name) > return vfs_fstat(dfd, stat); > > ret = vfs_statx(dfd, name, statx_flags, stat, STATX_BASIC_STATS); > putname(name); > > return ret; > } > > and > > SYSCALL_DEFINE5(statx, > int, dfd, const char __user *, filename, unsigned, flags, > unsigned int, mask, > struct statx __user *, buffer) > { > int ret; > unsigned lflags; > struct filename *name = getname_maybe_null(filename, flags); > > /* > * 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 (!name) > return do_statx_fd(dfd, flags & ~AT_NO_AUTOMOUNT, mask, buffer); > > ret = do_statx(dfd, name, flags, mask, buffer); > putname(name); > > return ret; > } > > static inline struct filename *getname_maybe_null(const char __user *name, int flags) > { > if (!(flags & AT_EMPTY_PATH)) > return getname(name); > > if (!name) > return NULL; > return __getname_maybe_null(name); > } > > struct filename *__getname_maybe_null(const char __user *pathname) > { > struct filename *name; > char c; > > /* try to save on allocations; loss on um, though */ > if (get_user(c, pathname)) > return ERR_PTR(-EFAULT); > if (!c) > return NULL; > > name = getname_flags(pathname, LOOKUP_EMPTY); > if (!IS_ERR(name) && !(name->name[0])) { > putname(name); > name = NULL; > } > return name; > } Incidentally, the name 'getname_statx_lookup_flags' is an atrocity: * getname and its variants do not give a fuck for the state of any flags besides AT_EMPTY_PATH * lookups, OTOH, ignore LOOKUP_EMPTY (which shouldn't have been in the LOOKUP_... namespace to start with). Another fun question: why do we play with setting ->mnt_id, etc. in vfs_statx_path() if vfs_getattr() returns non-zero? Or when we hit it via vfs_statx() from vfs_fstatat()...
On Tue, 2024-10-08 at 05:27 +0100, Al Viro wrote: > On Tue, Oct 08, 2024 at 05:16:21AM +0100, Al Viro wrote: > > > Folks, please don't go there. Really. IMO vfs_empty_path() is a wrong API > > in the first place. Too low-level and racy as well. > > > > See the approach in #work.xattr; I'm going to lift that into fs/namei.c > > (well, the slow path - everything after "if path is NULL, we are done") and > > yes, fs/stat.c users get handled better that way. So IIUC I just need to sit down here and wait for your branch to be merged? > FWIW, the intermediate (just after that commit) state of those functions is > > int vfs_fstatat(int dfd, const char __user *filename, > struct kstat *stat, int flags) > { > int ret; > int statx_flags = flags | AT_NO_AUTOMOUNT; > struct filename *name = getname_maybe_null(filename, flags); > > if (!name) > return vfs_fstat(dfd, stat); > > ret = vfs_statx(dfd, name, statx_flags, stat, STATX_BASIC_STATS); > putname(name); > > return ret; > } > > and > > SYSCALL_DEFINE5(statx, > int, dfd, const char __user *, filename, unsigned, flags, > unsigned int, mask, > struct statx __user *, buffer) > { > int ret; > unsigned lflags; > struct filename *name = getname_maybe_null(filename, flags); > > /* > * 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 (!name) > return do_statx_fd(dfd, flags & ~AT_NO_AUTOMOUNT, mask, buffer); > > ret = do_statx(dfd, name, flags, mask, buffer); > putname(name); > > return ret; > } > > static inline struct filename *getname_maybe_null(const char __user *name, int flags) > { > if (!(flags & AT_EMPTY_PATH)) > return getname(name); > > if (!name) > return NULL; > return __getname_maybe_null(name); > } > > struct filename *__getname_maybe_null(const char __user *pathname) > { > struct filename *name; > char c; > > /* try to save on allocations; loss on um, though */ > if (get_user(c, pathname)) > return ERR_PTR(-EFAULT); > if (!c) > return NULL; > > name = getname_flags(pathname, LOOKUP_EMPTY); > if (!IS_ERR(name) && !(name->name[0])) { > putname(name); > name = NULL; > } > return name; > }
diff --git a/fs/stat.c b/fs/stat.c index ed9d4fd8ba2c..5d1b51c23c62 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -337,8 +337,11 @@ int vfs_fstatat(int dfd, const char __user *filename, flags &= ~AT_NO_AUTOMOUNT; if (flags == AT_EMPTY_PATH && vfs_empty_path(dfd, filename)) return vfs_fstat(dfd, stat); + else if ((flags & AT_EMPTY_PATH) && !filename) + name = getname_kernel(""); + else + name = getname_flags(filename, getname_statx_lookup_flags(statx_flags)); - name = getname_flags(filename, getname_statx_lookup_flags(statx_flags)); ret = vfs_statx(dfd, name, statx_flags, stat, STATX_BASIC_STATS); putname(name); @@ -791,8 +794,11 @@ SYSCALL_DEFINE5(statx, 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); + else if ((lflags & AT_EMPTY_PATH) && !filename) + name = getname_kernel(""); + else + name = getname_flags(filename, getname_statx_lookup_flags(flags)); - name = getname_flags(filename, getname_statx_lookup_flags(flags)); ret = do_statx(dfd, name, flags, mask, buffer); putname(name);
We've supported {statx,fstatat}(real_fd, NULL, AT_EMPTY_PATH, ...) since Linux 6.11 for better performance. However there are other cases, for example using AT_FDCWD as the fd or having AT_SYMLINK_NOFOLLOW in flags, not covered by the fast path. While it may be impossible, too difficult, or not very beneficial to optimize these cases, we should still turn NULL into "" for them in the slow path to make the API easier to be documented and used. Fixes: 0ef625bba6fb ("vfs: support statx(..., NULL, AT_EMPTY_PATH, ...)") Cc: stable@vger.kernel.org Signed-off-by: Xi Ruoyao <xry111@xry111.site> --- fs/stat.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)