Add a libc wrapper for statx(2).

Bug: http://b/127675384
Bug: http://b/146676114
Test: treehugger
Change-Id: I844edc12f62717e579870a040cf03dfe60dc280b
(cherry picked from commit 733cedd1c4696ea74dab34d629ef7ac28ecc2200)
diff --git a/docs/status.md b/docs/status.md
index 6f358ea..9d3ad88 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -52,8 +52,8 @@
 
 New libc functions in R (API level 30):
   * Full C11 `<threads.h>` (available as inlines for older API levels).
-  * `memfd_create` and `mlock2` (GNU extensions).
-  * `renameat2` (GNU extension).
+  * `memfd_create` and `mlock2` (Linux-specific GNU extensions).
+  * `renameat2` and `statx` (Linux-specific GNU extensions).
   * `pthread_cond_clockwait`/`pthread_mutex_clocklock`/`pthread_rwlock_clockrdlock`/`pthread_rwlock_clockwrlock`/`sem_clockwait`
 
 New libc behavior in R (API level 30):
diff --git a/libc/SECCOMP_WHITELIST_COMMON.TXT b/libc/SECCOMP_WHITELIST_COMMON.TXT
index 72ced4f..56f9d1d 100644
--- a/libc/SECCOMP_WHITELIST_COMMON.TXT
+++ b/libc/SECCOMP_WHITELIST_COMMON.TXT
@@ -54,8 +54,6 @@
 # Since Linux 4.6, glibc 2.26.
 ssize_t preadv2(int fd, const struct iovec* iov, int iovcnt, off_t offset, int flags) all
 ssize_t pwritev2(int fd, const struct iovec* iov, int iovcnt, off_t offset, int flags) all
-# Since Linux 4.11, glibc 2.30.
-int statx(int, const char*, int, unsigned int, statx*) all
 # Since Linux 5.1, not in glibc.
 int clock_gettime64(clockid_t, timespec64*) lp32
 int clock_settime64(clockid_t, const timespec64*) lp32
diff --git a/libc/SYSCALLS.TXT b/libc/SYSCALLS.TXT
index 571df22..ab309e6 100644
--- a/libc/SYSCALLS.TXT
+++ b/libc/SYSCALLS.TXT
@@ -208,6 +208,7 @@
 ssize_t llistxattr(const char*, char*, size_t) all
 int     removexattr(const char*, const char*) all
 int     lremovexattr(const char*, const char*) all
+int statx(int, const char*, int, unsigned, struct statx*) all
 int     swapon(const char*, int) all
 int     swapoff(const char*) all
 
diff --git a/libc/include/bits/fortify/stat.h b/libc/include/bits/fortify/stat.h
index e92e6ac..2d42a51 100644
--- a/libc/include/bits/fortify/stat.h
+++ b/libc/include/bits/fortify/stat.h
@@ -26,9 +26,7 @@
  * SUCH DAMAGE.
  */
 
-#ifndef _SYS_STAT_H_
-#error "Never include this file directly; instead, include <sys/stat.h>"
-#endif
+#pragma once
 
 mode_t __umask_chk(mode_t) __INTRODUCED_IN(18);
 mode_t __umask_real(mode_t mode) __RENAME(umask);
diff --git a/libc/include/sys/stat.h b/libc/include/sys/stat.h
index a6c73b7..4184f6c 100644
--- a/libc/include/sys/stat.h
+++ b/libc/include/sys/stat.h
@@ -26,8 +26,12 @@
  * SUCH DAMAGE.
  */
 
-#ifndef _SYS_STAT_H_
-#define _SYS_STAT_H_
+#pragma once
+
+/**
+ * @file sys/stat.h
+ * @brief File status.
+ */
 
 #include <bits/timespec.h>
 #include <linux/stat.h>
@@ -169,8 +173,16 @@
 int utimensat(int __dir_fd, const char* __path, const struct timespec __times[2], int __flags);
 int futimens(int __dir_fd, const struct timespec __times[2]) __INTRODUCED_IN(19);
 
+#if defined(__USE_GNU)
+/**
+ * [statx(2)](http://man7.org/linux/man-pages/man2/statx.2.html) returns
+ * extended file status information.
+ *
+ * Returns 0 on success and returns -1 and sets `errno` on failure.
+ */
+int statx(int __dir_fd, const char* __path, int __flags, unsigned __mask, struct statx* __buf) __INTRODUCED_IN(30);
+#endif
+
 __END_DECLS
 
 #include <android/legacy_sys_stat_inlines.h>
-
-#endif
diff --git a/libc/libc.map.txt b/libc/libc.map.txt
index 37807be..4bfb8a2 100644
--- a/libc/libc.map.txt
+++ b/libc/libc.map.txt
@@ -1505,6 +1505,7 @@
     pthread_rwlock_clockwrlock;
     renameat2;
     sem_clockwait;
+    statx;
     thrd_create;
     thrd_current;
     thrd_detach;
diff --git a/tests/sys_stat_test.cpp b/tests/sys_stat_test.cpp
index 97bf580..71591c0 100644
--- a/tests/sys_stat_test.cpp
+++ b/tests/sys_stat_test.cpp
@@ -22,6 +22,14 @@
 #include <android-base/file.h>
 #include <gtest/gtest.h>
 
+#if defined(__BIONIC__)
+#define HAVE_STATX
+#elif defined(__GLIBC_PREREQ)
+#if __GLIBC_PREREQ(2, 28)
+#define HAVE_STATX
+#endif
+#endif
+
 TEST(sys_stat, futimens) {
   FILE* fp = tmpfile();
   ASSERT_TRUE(fp != nullptr);
@@ -95,6 +103,24 @@
   close(fd);
 }
 
+TEST(sys_stat, statx) {
+#if defined(HAVE_STATX)
+  struct statx sx;
+  int rc = statx(AT_FDCWD, "/proc/version", AT_STATX_SYNC_AS_STAT, STATX_ALL, &sx);
+  if (rc == -1 && errno == ENOSYS) {
+    GTEST_SKIP() << "statx returned ENOSYS";
+    return;
+  }
+  ASSERT_EQ(0, rc);
+  struct stat64 sb;
+  ASSERT_EQ(0, stat64("/proc/version", &sb));
+  EXPECT_EQ(sb.st_ino, sx.stx_ino);
+  EXPECT_EQ(sb.st_mode, sx.stx_mode);
+#else
+  GTEST_SKIP() << "statx not available";
+#endif
+}
+
 TEST(sys_stat, fchmodat_EFAULT_file) {
   ASSERT_EQ(-1, fchmodat(AT_FDCWD, (char *) 0x1, 0751, 0));
   ASSERT_EQ(EFAULT, errno);