diff mbox series

io: statx, fstatat64: allow using NULL as path

Message ID 20240925-statx-null-path-user-v1-1-73e21e14c79e@gmail.com
State New
Headers show
Series io: statx, fstatat64: allow using NULL as path | expand

Commit Message

Miao Wang via B4 Relay Sept. 24, 2024, 4:37 p.m. UTC
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

Xi Ruoyao Sept. 25, 2024, 2:23 p.m. UTC | #1
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.
Florian Weimer Sept. 30, 2024, 1:29 p.m. UTC | #2
* 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
diff mbox series

Patch

diff --git a/io/bits/statx-generic.h b/io/bits/statx-generic.h
index 19c3565edc..c0e00c9d8b 100644
--- a/io/bits/statx-generic.h
+++ b/io/bits/statx-generic.h
@@ -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
diff --git a/io/sys/stat.h b/io/sys/stat.h
index 3b4ba80132..7ea39b8755 100644
--- a/io/sys/stat.h
+++ b/io/sys/stat.h
@@ -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
diff --git a/io/tst-statx.c b/io/tst-statx.c
index 685924ae76..4b70999e96 100644
--- a/io/tst-statx.c
+++ b/io/tst-statx.c
@@ -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);
diff --git a/sysdeps/mach/hurd/fstatat64.c b/sysdeps/mach/hurd/fstatat64.c
index 61b6593f3a..18dc9d2f08 100644
--- a/sysdeps/mach/hurd/fstatat64.c
+++ b/sysdeps/mach/hurd/fstatat64.c
@@ -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)
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;
+    }
+#endif
+
   return INTERNAL_SYSCALL_ERROR_P (r)
 	 ? INLINE_SYSCALL_ERROR_RETURN_VALUE (-r)
 	 : 0;
diff --git a/sysdeps/unix/sysv/linux/statx.c b/sysdeps/unix/sysv/linux/statx.c
index a6295a155d..9549614a6b 100644
--- a/sysdeps/unix/sysv/linux/statx.c
+++ b/sysdeps/unix/sysv/linux/statx.c
@@ -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