io: statx, fstatat64: allow using NULL as path
Checks
Context |
Check |
Description |
redhat-pt-bot/TryBot-apply_patch |
success
|
Patch applied to master at the time it was sent
|
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 |
success
|
Build passed
|
redhat-pt-bot/TryBot-32bit |
success
|
Build for i686
|
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 |
success
|
Test passed
|
linaro-tcwg-bot/tcwg_glibc_build--master-arm |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_glibc_check--master-arm |
success
|
Test passed
|
Commit Message
From: Miao Wang <shankerwangmiao@gmail.com>
Since linux 6.11, fstatat and statx syscalls allow using NULL as path
when AT_EMPTY_PATH is given. We also expose this to glibc users, and
change path to an empty string when this is not supported by the kernel.
Signed-off-by: Miao Wang <shankerwangmiao@gmail.com>
---
Since kernel 6.11, statx/fstatat(..., NULL, AT_EMPTH_PATH...) are
supported for better performance by avoiding copying buffer data from
userland. Since currently the declaration of statx and fstatat flags
the path argument as nonull, it prevents users from utilizing this
kernel feature. I can come up with the following possible solutions:
1. Directly remove the nonull annotation on the path argument, and
pass the parameter given by the user as-is to the kernel, exposing
the kernel behavior. This would be confusing since on older kernels,
the user would receive EFAULT and need to handle this.
2. Based on 1, but if the user provides NULL, the handling of EFAULT is
integrated in glibc, who implicitly change the parameter to an empty
string if EFAULT received.
In either case, I think there is no need to introduce a new ABI version
of statx() and fstatat(), since we are loosening the restriction on the
given parameters, and executables linked with previous versions of statx
and fstatat can still work after this change.
This RFC patch contains changes for the solution 2 above.
---
io/bits/statx-generic.h | 2 +-
io/sys/stat.h | 10 +++++-----
io/tst-statx.c | 6 ++++++
sysdeps/mach/hurd/fstatat64.c | 2 ++
sysdeps/unix/sysv/linux/fstatat64.c | 12 ++++++++++++
sysdeps/unix/sysv/linux/statx.c | 7 ++++++-
6 files changed, 32 insertions(+), 7 deletions(-)
---
base-commit: 2eee835eca960c9d4119279804214b7a1ed5d156
change-id: 20240925-statx-null-path-user-c0c4ebbdf21c
prerequisite-change-id: 20240821-statx-null-path-531c0775bba4:v5
prerequisite-patch-id: cf313d4d0660fa972ce419167e3847722c3481d3
Best regards,
Comments
On Wed, 2024-09-25 at 00:37 +0800, Miao Wang via B4 Relay wrote:
> From: Miao Wang <shankerwangmiao@gmail.com>
>
> Since linux 6.11, fstatat and statx syscalls allow using NULL as path
> when AT_EMPTY_PATH is given. We also expose this to glibc users, and
> change path to an empty string when this is not supported by the kernel.
>
> Signed-off-by: Miao Wang <shankerwangmiao@gmail.com>
> ---
> Since kernel 6.11, statx/fstatat(..., NULL, AT_EMPTH_PATH...) are
> supported for better performance by avoiding copying buffer data from
> userland. Since currently the declaration of statx and fstatat flags
> the path argument as nonull, it prevents users from utilizing this
> kernel feature. I can come up with the following possible solutions:
>
> 1. Directly remove the nonull annotation on the path argument, and
> pass the parameter given by the user as-is to the kernel, exposing
> the kernel behavior. This would be confusing since on older kernels,
> the user would receive EFAULT and need to handle this.
>
> 2. Based on 1, but if the user provides NULL, the handling of EFAULT is
> integrated in glibc, who implicitly change the parameter to an empty
> string if EFAULT received.
>
> In either case, I think there is no need to introduce a new ABI version
> of statx() and fstatat(), since we are loosening the restriction on the
> given parameters, and executables linked with previous versions of statx
> and fstatat can still work after this change.
>
> This RFC patch contains changes for the solution 2 above.
I'd suggest to separate this into two patches. The first one drops the
__nonnull(...) on the path, the second one implements the fallback. The
first patch would be so obvious to be applied then.
And I'm unsure if we should attempt to fix it up when the user is doing
something unsupported by the running kernel. Are there some similar
cases in the past?
Also there's a difference between what this patch does and the actual
syscall interface of Linux >= 6.11: in Linux 6.11 fstatat(AT_FDCWD,
NULL, &stat, AT_EMPTY_PATH) still returns EFAULT (i.e. NULL can only be
used if AT_EMPTY_PATH **and** fd isn't AT_FDCWD), but this patch will
turn NULL into an empty string, making the case "supported." I'll ask
the kernel dev to see if AT_FDCWD + NULL + AT_EMPTY_PATH case should be
supported.
> diff --git a/sysdeps/mach/hurd/fstatat64.c b/sysdeps/mach/hurd/fstatat64.c
I'm even more unsure if we should attempt to support it on a *completely
different* kernel.
/* snip */
> diff --git a/sysdeps/unix/sysv/linux/fstatat64.c b/sysdeps/unix/sysv/linux/fstatat64.c
> index 59b461cbcf..a9e3b85179 100644
> --- a/sysdeps/unix/sysv/linux/fstatat64.c
> +++ b/sysdeps/unix/sysv/linux/fstatat64.c
> @@ -156,6 +156,10 @@ __fstatat64_time64 (int fd, const char *file, struct __stat64_t64 *buf,
> {
> int r;
>
> +#ifndef __ASSUME_STATX_NULL_PATH
> +retry:
> +#endif
> +
> #if FSTATAT_USE_STATX
> r = fstatat64_time64_statx (fd, file, buf, flag);
> # ifndef __ASSUME_STATX
> @@ -166,6 +170,14 @@ __fstatat64_time64 (int fd, const char *file, struct __stat64_t64 *buf,
> r = fstatat64_time64_stat (fd, file, buf, flag);
> #endif
>
> +#ifndef __ASSUME_STATX_NULL_PATH
> + if (file == NULL && (flag & AT_EMPTY_PATH) && r == -EFAULT)
> + {
> + file = "";
> + goto retry;
I'd just do
return __fstatat64_time64 (fd, "", buf, flag);
Because it's more readable, and any reasonable compiler should produce
the same result as the goto.
* Miao Wang via:
> From: Miao Wang <shankerwangmiao@gmail.com>
>
> Since linux 6.11, fstatat and statx syscalls allow using NULL as path
> when AT_EMPTY_PATH is given. We also expose this to glibc users, and
> change path to an empty string when this is not supported by the kernel.
>
> Signed-off-by: Miao Wang <shankerwangmiao@gmail.com>
> ---
> Since kernel 6.11, statx/fstatat(..., NULL, AT_EMPTH_PATH...) are
> supported for better performance by avoiding copying buffer data from
> userland. Since currently the declaration of statx and fstatat flags
> the path argument as nonull, it prevents users from utilizing this
> kernel feature. I can come up with the following possible solutions:
>
> 1. Directly remove the nonull annotation on the path argument, and
> pass the parameter given by the user as-is to the kernel, exposing
> the kernel behavior. This would be confusing since on older kernels,
> the user would receive EFAULT and need to handle this.
>
> 2. Based on 1, but if the user provides NULL, the handling of EFAULT is
> integrated in glibc, who implicitly change the parameter to an empty
> string if EFAULT received.
>
> In either case, I think there is no need to introduce a new ABI version
> of statx() and fstatat(), since we are loosening the restriction on the
> given parameters, and executables linked with previous versions of statx
> and fstatat can still work after this change.
I'm not sure if we should change glibc to add this emulation. It
prevents probing in the application to figure out if the kernel supports
the shortcut or not. So it would keep hitting the double-system-call
case.
Thanks,
Florian
@@ -62,6 +62,6 @@ __BEGIN_DECLS
/* Fill *BUF with information about PATH in DIRFD. */
int statx (int __dirfd, const char *__restrict __path, int __flags,
unsigned int __mask, struct statx *__restrict __buf)
- __THROW __nonnull ((2, 5));
+ __THROW __nonnull ((5));
__END_DECLS
@@ -263,14 +263,14 @@ extern int __REDIRECT_NTH (fstat64, (int __fd, struct stat64 *__buf),
# ifndef __USE_FILE_OFFSET64
extern int fstatat (int __fd, const char *__restrict __file,
struct stat *__restrict __buf, int __flag)
- __THROW __nonnull ((2, 3));
+ __THROW __nonnull ((3));
# else
# ifdef __USE_TIME64_REDIRECTS
# ifdef __REDIRECT_NTH
extern int __REDIRECT_NTH (fstatat, (int __fd, const char *__restrict __file,
struct stat *__restrict __buf,
int __flag),
- __fstatat64_time64) __nonnull ((2, 3));
+ __fstatat64_time64) __nonnull ((3));
# else
# define fstatat __fstatat64_time64
# endif
@@ -279,7 +279,7 @@ extern int __REDIRECT_NTH (fstatat, (int __fd, const char *__restrict __file,
extern int __REDIRECT_NTH (fstatat, (int __fd, const char *__restrict __file,
struct stat *__restrict __buf,
int __flag),
- fstatat64) __nonnull ((2, 3));
+ fstatat64) __nonnull ((3));
# else
# define fstatat fstatat64
# endif
@@ -290,7 +290,7 @@ extern int __REDIRECT_NTH (fstatat, (int __fd, const char *__restrict __file,
# ifndef __USE_TIME64_REDIRECTS
extern int fstatat64 (int __fd, const char *__restrict __file,
struct stat64 *__restrict __buf, int __flag)
- __THROW __nonnull ((2, 3));
+ __THROW __nonnull ((3));
# else
# ifdef __REDIRECT_NTH
extern int __REDIRECT_NTH (fstatat64, (int __fd,
@@ -298,7 +298,7 @@ extern int __REDIRECT_NTH (fstatat64, (int __fd,
struct stat64 *__restrict __buf,
int __flag),
__fstatat64_time64)
- __nonnull ((2, 3));
+ __nonnull ((3));
# else
# define fstatat64 __fstatat64_time64
# endif
@@ -74,6 +74,12 @@ both_implementations_tests (statx_function impl, const char *path, int fd)
TEST_COMPARE (buf.stx_size, 3);
TEST_COMPARE (buf.stx_ino, ino);
}
+ {
+ struct statx buf = { 0, };
+ TEST_COMPARE (statx (fd, NULL, AT_EMPTY_PATH, STATX_BASIC_STATS, &buf), 0);
+ TEST_COMPARE (buf.stx_size, 3);
+ TEST_COMPARE (buf.stx_ino, ino);
+ }
{
struct statx stx = { 0, };
TEST_COMPARE (statx (fd, "", AT_EMPTY_PATH, STATX_BASIC_STATS, &stx), 0);
@@ -45,6 +45,8 @@ __fstatat64_common (int fd, const char *filename, struct stat64 *buf, int at_fla
int
__fstatat64 (int fd, const char *filename, struct stat64 *buf, int at_flags)
{
+ if ((at_flags & AT_EMPTY_PATH) && filename == NULL)
+ filename = "";
return __fstatat64_common (fd, filename, buf, at_flags, 0);
}
libc_hidden_def (__fstatat64)
@@ -156,6 +156,10 @@ __fstatat64_time64 (int fd, const char *file, struct __stat64_t64 *buf,
{
int r;
+#ifndef __ASSUME_STATX_NULL_PATH
+retry:
+#endif
+
#if FSTATAT_USE_STATX
r = fstatat64_time64_statx (fd, file, buf, flag);
# ifndef __ASSUME_STATX
@@ -166,6 +170,14 @@ __fstatat64_time64 (int fd, const char *file, struct __stat64_t64 *buf,
r = fstatat64_time64_stat (fd, file, buf, flag);
#endif
+#ifndef __ASSUME_STATX_NULL_PATH
+ if (file == NULL && (flag & AT_EMPTY_PATH) && r == -EFAULT)
+ {
+ file = "";
+ goto retry;
+ }
+#endif
+
return INTERNAL_SYSCALL_ERROR_P (r)
? INLINE_SYSCALL_ERROR_RETURN_VALUE (-r)
: 0;
@@ -25,7 +25,12 @@ int
statx (int fd, const char *path, int flags,
unsigned int mask, struct statx *buf)
{
- int ret = INLINE_SYSCALL_CALL (statx, fd, path, flags, mask, buf);
+ int ret;
+ ret = INLINE_SYSCALL_CALL (statx, fd, path, flags, mask, buf);
+#ifndef __ASSUME_STATX_NULL_PATH
+ if (path == NULL && (flags & AT_EMPTY_PATH) && errno == EFAULT)
+ ret = INLINE_SYSCALL_CALL (statx, fd, "", flags, mask, buf);
+#endif
#ifdef __ASSUME_STATX
return ret;
#else