Blob Blame History Raw
From b6a4efe4347a061161054698857b6dde0f3ed67c Mon Sep 17 00:00:00 2001
From: Martin Bukatovic <martin.bukatovic@gmail.com>
Date: Sat, 2 Mar 2019 19:57:17 -0800
Subject: [PATCH 1/5] stat: print birth time on systems supporting statx

* configure.ac: Check for statx(), available on glibc >= 2.28.
* src/stat.c (get_birthtime): Call statx() when available.
* NEWS: Mention the improvement.

Upstream-commit: 186896d65f6182dff15cad6c1045d22ad2004962
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 configure.ac |  3 +++
 src/stat.c   | 19 +++++++++++++++++++
 2 files changed, 22 insertions(+)

diff --git a/configure.ac b/configure.ac
index 9f7a8a5..c24ce2a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -318,6 +318,9 @@ if test $ac_cv_func_getattrat = yes; then
   AC_SUBST([LIB_NVPAIR])
 fi
 
+# glibc >= 2.28 and linux kernel >= 4.11
+AC_CHECK_FUNCS([statx])
+
 # SCO-ODT-3.0 is reported to need -los to link programs using initgroups
 AC_CHECK_FUNCS([initgroups])
 if test $ac_cv_func_initgroups = no; then
diff --git a/src/stat.c b/src/stat.c
index 0a5ef3c..9e71cbe 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -1007,6 +1007,25 @@ get_birthtime (int fd, char const *filename, struct stat const *st)
     }
 #endif
 
+#if HAVE_STATX
+  if (ts.tv_nsec < 0)
+    {
+      struct statx stx;
+      if ((fd < 0
+           ? statx (AT_FDCWD, filename,
+                    follow_links ? 0 : AT_SYMLINK_NOFOLLOW,
+                    STATX_BTIME, &stx)
+           : statx (fd, "", AT_EMPTY_PATH, STATX_BTIME, &stx)) == 0)
+        {
+          if ((stx.stx_mask & STATX_BTIME) && stx.stx_btime.tv_sec != 0)
+            {
+              ts.tv_sec = stx.stx_btime.tv_sec;
+              ts.tv_nsec = stx.stx_btime.tv_nsec;
+            }
+        }
+    }
+#endif
+
   return ts;
 }
 
-- 
2.20.1


From 21ff41c1e5ef4668669f92d04c69a258708ba20e Mon Sep 17 00:00:00 2001
From: Jeff Layton <jlayton@kernel.org>
Date: Tue, 28 May 2019 08:21:42 -0400
Subject: [PATCH 2/5] stat: Use statx where available and support --cached

* src/stat.c: Drop statbuf argument from out_epoch_sec().
Use statx() rather than [lf]stat() where available,
so a separate call is not required to get birth time.
Set STATX_* mask bits only for things we want to print,
which can be more efficient on some file systems.
Add a new --cache= command-line option that sets the appropriate hint
flags in the statx call.  These are primarily used with network
file systems to indicate what level of cache coherency is desired.
The new option is available unconditionally for better portability,
and ignored where not implemented.
* doc/coreutils.texi: Add documention for --cached.
* man/stat.x (SEE ALSO): Mention statx().

Upstream-commit: 6cc35de16fdc52d417602b66d5e90694d7e02994
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 doc/coreutils.texi |  21 ++
 man/stat.x         |   2 +-
 src/stat.c         | 623 ++++++++++++++++++++++++++++++---------------
 3 files changed, 444 insertions(+), 202 deletions(-)

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 6ac99be..547d17b 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -12201,6 +12201,27 @@ Report information about the file systems where the given files are located
 instead of information about the files themselves.
 This option implies the @option{-L} option.
 
+@item --cached=@var{mode}
+@opindex --cached=@var{mode}
+@cindex attribute caching
+Control how attributes are read from the file system;
+if supported by the system.  This allows one to
+control the trade-off between freshness and efficiency
+of attribute access, especially useful with remote file systems.
+@var{mode} can be:
+
+@table @samp
+@item always
+Always read the already cached attributes if available.
+
+@item never
+Always sychronize with the latest file system attributes.
+
+@item default
+Leave the caching behavior to the underlying file system.
+
+@end table
+
 @item -c
 @itemx --format=@var{format}
 @opindex -c
diff --git a/man/stat.x b/man/stat.x
index dc3781e..b9f8c68 100644
--- a/man/stat.x
+++ b/man/stat.x
@@ -3,4 +3,4 @@ stat \- display file or file system status
 [DESCRIPTION]
 .\" Add any additional description here
 [SEE ALSO]
-stat(2), statfs(2)
+stat(2), statfs(2), statx(2)
diff --git a/src/stat.c b/src/stat.c
index 9e71cbe..32ffb6d 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -28,6 +28,12 @@
 # define USE_STATVFS 0
 #endif
 
+#if HAVE_STATX && defined STATX_INO
+# define USE_STATX 1
+#else
+# define USE_STATX 0
+#endif
+
 #include <stddef.h>
 #include <stdio.h>
 #include <stdalign.h>
@@ -194,6 +200,23 @@ enum
   PRINTF_OPTION = CHAR_MAX + 1
 };
 
+enum cached_mode
+{
+  cached_default,
+  cached_never,
+  cached_always
+};
+
+static char const *const cached_args[] =
+{
+  "default", "never", "always", NULL
+};
+
+static enum cached_mode const cached_modes[] =
+{
+  cached_default, cached_never, cached_always
+};
+
 static struct option const long_options[] =
 {
   {"dereference", no_argument, NULL, 'L'},
@@ -201,6 +224,7 @@ static struct option const long_options[] =
   {"format", required_argument, NULL, 'c'},
   {"printf", required_argument, NULL, PRINTF_OPTION},
   {"terse", no_argument, NULL, 't'},
+  {"cached", required_argument, NULL, 0},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -221,6 +245,10 @@ static char const *trailing_delim = "";
 static char const *decimal_point;
 static size_t decimal_point_len;
 
+static bool
+print_stat (char *pformat, size_t prefix_len, unsigned int m,
+            int fd, char const *filename, void const *data);
+
 /* Return the type of the specified file system.
    Some systems have statfvs.f_basetype[FSTYPSZ] (AIX, HP-UX, and Solaris).
    Others have statvfs.f_fstypename[_VFS_NAMELEN] (NetBSD 3.0).
@@ -674,7 +702,6 @@ out_minus_zero (char *pformat, size_t prefix_len)
    acts like printf's %f format.  */
 static void
 out_epoch_sec (char *pformat, size_t prefix_len,
-               struct stat const *statbuf _GL_UNUSED,
                struct timespec arg)
 {
   char *dot = memchr (pformat, '.', prefix_len);
@@ -978,57 +1005,6 @@ print_mount_point:
   return fail;
 }
 
-static struct timespec
-get_birthtime (int fd, char const *filename, struct stat const *st)
-{
-  struct timespec ts = get_stat_birthtime (st);
-
-#if HAVE_GETATTRAT
-  if (ts.tv_nsec < 0)
-    {
-      nvlist_t *response;
-      if ((fd < 0
-           ? getattrat (AT_FDCWD, XATTR_VIEW_READWRITE, filename, &response)
-           : fgetattr (fd, XATTR_VIEW_READWRITE, &response))
-          == 0)
-        {
-          uint64_t *val;
-          uint_t n;
-          if (nvlist_lookup_uint64_array (response, A_CRTIME, &val, &n) == 0
-              && 2 <= n
-              && val[0] <= TYPE_MAXIMUM (time_t)
-              && val[1] < 1000000000 * 2 /* for leap seconds */)
-            {
-              ts.tv_sec = val[0];
-              ts.tv_nsec = val[1];
-            }
-          nvlist_free (response);
-        }
-    }
-#endif
-
-#if HAVE_STATX
-  if (ts.tv_nsec < 0)
-    {
-      struct statx stx;
-      if ((fd < 0
-           ? statx (AT_FDCWD, filename,
-                    follow_links ? 0 : AT_SYMLINK_NOFOLLOW,
-                    STATX_BTIME, &stx)
-           : statx (fd, "", AT_EMPTY_PATH, STATX_BTIME, &stx)) == 0)
-        {
-          if ((stx.stx_mask & STATX_BTIME) && stx.stx_btime.tv_sec != 0)
-            {
-              ts.tv_sec = stx.stx_btime.tv_sec;
-              ts.tv_nsec = stx.stx_btime.tv_nsec;
-            }
-        }
-    }
-#endif
-
-  return ts;
-}
-
 /* Map a TS with negative TS.tv_nsec to {0,0}.  */
 static inline struct timespec
 neg_to_zero (struct timespec ts)
@@ -1065,139 +1041,6 @@ getenv_quoting_style (void)
 /* Equivalent to quotearg(), but explicit to avoid syntax checks.  */
 #define quoteN(x) quotearg_style (get_quoting_style (NULL), x)
 
-/* Print stat info.  Return zero upon success, nonzero upon failure.  */
-static bool
-print_stat (char *pformat, size_t prefix_len, unsigned int m,
-            int fd, char const *filename, void const *data)
-{
-  struct stat *statbuf = (struct stat *) data;
-  struct passwd *pw_ent;
-  struct group *gw_ent;
-  bool fail = false;
-
-  switch (m)
-    {
-    case 'n':
-      out_string (pformat, prefix_len, filename);
-      break;
-    case 'N':
-      out_string (pformat, prefix_len, quoteN (filename));
-      if (S_ISLNK (statbuf->st_mode))
-        {
-          char *linkname = areadlink_with_size (filename, statbuf->st_size);
-          if (linkname == NULL)
-            {
-              error (0, errno, _("cannot read symbolic link %s"),
-                     quoteaf (filename));
-              return true;
-            }
-          printf (" -> ");
-          out_string (pformat, prefix_len, quoteN (linkname));
-          free (linkname);
-        }
-      break;
-    case 'd':
-      out_uint (pformat, prefix_len, statbuf->st_dev);
-      break;
-    case 'D':
-      out_uint_x (pformat, prefix_len, statbuf->st_dev);
-      break;
-    case 'i':
-      out_uint (pformat, prefix_len, statbuf->st_ino);
-      break;
-    case 'a':
-      out_uint_o (pformat, prefix_len, statbuf->st_mode & CHMOD_MODE_BITS);
-      break;
-    case 'A':
-      out_string (pformat, prefix_len, human_access (statbuf));
-      break;
-    case 'f':
-      out_uint_x (pformat, prefix_len, statbuf->st_mode);
-      break;
-    case 'F':
-      out_string (pformat, prefix_len, file_type (statbuf));
-      break;
-    case 'h':
-      out_uint (pformat, prefix_len, statbuf->st_nlink);
-      break;
-    case 'u':
-      out_uint (pformat, prefix_len, statbuf->st_uid);
-      break;
-    case 'U':
-      pw_ent = getpwuid (statbuf->st_uid);
-      out_string (pformat, prefix_len,
-                  pw_ent ? pw_ent->pw_name : "UNKNOWN");
-      break;
-    case 'g':
-      out_uint (pformat, prefix_len, statbuf->st_gid);
-      break;
-    case 'G':
-      gw_ent = getgrgid (statbuf->st_gid);
-      out_string (pformat, prefix_len,
-                  gw_ent ? gw_ent->gr_name : "UNKNOWN");
-      break;
-    case 't':
-      out_uint_x (pformat, prefix_len, major (statbuf->st_rdev));
-      break;
-    case 'm':
-      fail |= out_mount_point (filename, pformat, prefix_len, statbuf);
-      break;
-    case 'T':
-      out_uint_x (pformat, prefix_len, minor (statbuf->st_rdev));
-      break;
-    case 's':
-      out_int (pformat, prefix_len, statbuf->st_size);
-      break;
-    case 'B':
-      out_uint (pformat, prefix_len, ST_NBLOCKSIZE);
-      break;
-    case 'b':
-      out_uint (pformat, prefix_len, ST_NBLOCKS (*statbuf));
-      break;
-    case 'o':
-      out_uint (pformat, prefix_len, ST_BLKSIZE (*statbuf));
-      break;
-    case 'w':
-      {
-        struct timespec t = get_birthtime (fd, filename, statbuf);
-        if (t.tv_nsec < 0)
-          out_string (pformat, prefix_len, "-");
-        else
-          out_string (pformat, prefix_len, human_time (t));
-      }
-      break;
-    case 'W':
-      out_epoch_sec (pformat, prefix_len, statbuf,
-                     neg_to_zero (get_birthtime (fd, filename, statbuf)));
-      break;
-    case 'x':
-      out_string (pformat, prefix_len, human_time (get_stat_atime (statbuf)));
-      break;
-    case 'X':
-      out_epoch_sec (pformat, prefix_len, statbuf, get_stat_atime (statbuf));
-      break;
-    case 'y':
-      out_string (pformat, prefix_len, human_time (get_stat_mtime (statbuf)));
-      break;
-    case 'Y':
-      out_epoch_sec (pformat, prefix_len, statbuf, get_stat_mtime (statbuf));
-      break;
-    case 'z':
-      out_string (pformat, prefix_len, human_time (get_stat_ctime (statbuf)));
-      break;
-    case 'Z':
-      out_epoch_sec (pformat, prefix_len, statbuf, get_stat_ctime (statbuf));
-      break;
-    case 'C':
-      fail |= out_file_context (pformat, prefix_len, filename);
-      break;
-    default:
-      fputc ('?', stdout);
-      break;
-    }
-  return fail;
-}
-
 /* Output a single-character \ escape.  */
 
 static void
@@ -1239,6 +1082,17 @@ print_esc_char (char c)
   putchar (c);
 }
 
+static size_t _GL_ATTRIBUTE_PURE
+format_code_offset (char const* directive)
+{
+  size_t len = strspn (directive + 1, printf_flags);
+  char const *fmt_char = directive + len + 1;
+  fmt_char += strspn (fmt_char, digits);
+  if (*fmt_char == '.')
+    fmt_char += 1 + strspn (fmt_char + 1, digits);
+  return fmt_char - directive;
+}
+
 /* Print the information specified by the format string, FORMAT,
    calling PRINT_FUNC for each %-directive encountered.
    Return zero upon success, nonzero upon failure.  */
@@ -1268,33 +1122,28 @@ print_it (char const *format, int fd, char const *filename,
         {
         case '%':
           {
-            size_t len = strspn (b + 1, printf_flags);
-            char const *fmt_char = b + len + 1;
-            fmt_char += strspn (fmt_char, digits);
-            if (*fmt_char == '.')
-              fmt_char += 1 + strspn (fmt_char + 1, digits);
-            len = fmt_char - (b + 1);
-            unsigned int fmt_code = *fmt_char;
-            memcpy (dest, b, len + 1);
-
-            b = fmt_char;
-            switch (fmt_code)
+            size_t len = format_code_offset (b);
+            char const *fmt_char = b + len;
+            memcpy (dest, b, len);
+            b += len;
+
+            switch (*fmt_char)
               {
               case '\0':
                 --b;
                 FALLTHROUGH;
               case '%':
-                if (0 < len)
+                if (1 < len)
                   {
-                    dest[len + 1] = *fmt_char;
-                    dest[len + 2] = '\0';
+                    dest[len] = *fmt_char;
+                    dest[len + 1] = '\0';
                     die (EXIT_FAILURE, 0, _("%s: invalid directive"),
                          quote (dest));
                   }
                 putchar ('%');
                 break;
               default:
-                fail |= print_func (dest, len + 1, fmt_code,
+                fail |= print_func (dest, len, to_uchar (*fmt_char),
                                     fd, filename, data);
                 break;
               }
@@ -1382,6 +1231,204 @@ do_statfs (char const *filename, char const *format)
   return ! fail;
 }
 
+struct print_args {
+  struct stat *st;
+  struct timespec btime;
+};
+
+/* Ask statx to avoid syncing? */
+static bool dont_sync;
+
+/* Ask statx to force sync? */
+static bool force_sync;
+
+#if USE_STATX
+/* Much of the format printing requires a struct stat or timespec */
+static struct timespec
+statx_timestamp_to_timespec (struct statx_timestamp tsx)
+{
+  struct timespec ts;
+
+  ts.tv_sec = tsx.tv_sec;
+  ts.tv_nsec = tsx.tv_nsec;
+  return ts;
+}
+
+static void
+statx_to_stat (struct statx *stx, struct stat *stat)
+{
+  stat->st_dev = makedev (stx->stx_dev_major, stx->stx_dev_minor);
+  stat->st_ino = stx->stx_ino;
+  stat->st_mode = stx->stx_mode;
+  stat->st_nlink = stx->stx_nlink;
+  stat->st_uid = stx->stx_uid;
+  stat->st_gid = stx->stx_gid;
+  stat->st_rdev = makedev (stx->stx_rdev_major, stx->stx_rdev_minor);
+  stat->st_size = stx->stx_size;
+  stat->st_blksize = stx->stx_blksize;
+/* define to avoid sc_prohibit_stat_st_blocks.  */
+# define SC_ST_BLOCKS st_blocks
+  stat->SC_ST_BLOCKS = stx->stx_blocks;
+  stat->st_atim = statx_timestamp_to_timespec (stx->stx_atime);
+  stat->st_mtim = statx_timestamp_to_timespec (stx->stx_mtime);
+  stat->st_ctim = statx_timestamp_to_timespec (stx->stx_ctime);
+}
+
+static unsigned int
+fmt_to_mask (char fmt)
+{
+  switch (fmt)
+    {
+    case 'N':
+      return STATX_MODE|STATX_SIZE;
+    case 'd':
+    case 'D':
+      return STATX_MODE;
+    case 'i':
+      return STATX_INO;
+    case 'a':
+    case 'A':
+      return STATX_MODE;
+    case 'f':
+      return STATX_MODE|STATX_TYPE;
+    case 'F':
+      return STATX_TYPE;
+    case 'h':
+      return STATX_NLINK;
+    case 'u':
+    case 'U':
+      return STATX_UID;
+    case 'g':
+    case 'G':
+      return STATX_GID;
+    case 'm':
+      return STATX_MODE|STATX_INO;
+    case 's':
+      return STATX_SIZE;
+    case 't':
+    case 'T':
+      return STATX_MODE;
+    case 'b':
+      return STATX_BLOCKS;
+    case 'w':
+    case 'W':
+      return STATX_BTIME;
+    case 'x':
+    case 'X':
+      return STATX_ATIME;
+    case 'y':
+    case 'Y':
+      return STATX_MTIME;
+    case 'z':
+    case 'Z':
+      return STATX_CTIME;
+    }
+  return 0;
+}
+
+static unsigned int _GL_ATTRIBUTE_PURE
+format_to_mask (char const *format)
+{
+  unsigned int mask = 0;
+  char const *b;
+
+  for (b = format; *b; b++)
+    {
+      if (*b != '%')
+        continue;
+
+      b += format_code_offset (b);
+      if (*b == '\0')
+        break;
+      mask |= fmt_to_mask (*b);
+    }
+  return mask;
+}
+
+/* statx the file and print what we find */
+static bool ATTRIBUTE_WARN_UNUSED_RESULT
+do_stat (char const *filename, char const *format, char const *format2)
+{
+  int fd = STREQ (filename, "-") ? 0 : AT_FDCWD;
+  int flags = 0;
+  struct stat st;
+  struct statx stx;
+  const char *pathname = filename;
+  struct print_args pa;
+  pa.st = &st;
+  pa.btime = (struct timespec) {-1, -1};
+
+  if (AT_FDCWD != fd)
+    {
+      pathname = "";
+      flags = AT_EMPTY_PATH;
+    }
+  else if (!follow_links)
+    {
+      flags = AT_SYMLINK_NOFOLLOW;
+    }
+
+  if (dont_sync)
+    flags |= AT_STATX_DONT_SYNC;
+  else if (force_sync)
+    flags |= AT_STATX_FORCE_SYNC;
+
+  fd = statx (fd, pathname, flags, format_to_mask (format), &stx);
+  if (fd < 0)
+    {
+      if (flags & AT_EMPTY_PATH)
+        error (0, errno, _("cannot stat standard input"));
+      else
+        error (0, errno, _("cannot statx %s"), quoteaf (filename));
+      return false;
+    }
+
+  if (S_ISBLK (stx.stx_mode) || S_ISCHR (stx.stx_mode))
+    format = format2;
+
+  statx_to_stat (&stx, &st);
+  if (stx.stx_mask & STATX_BTIME)
+    pa.btime = statx_timestamp_to_timespec (stx.stx_btime);
+
+  bool fail = print_it (format, fd, filename, print_stat, &pa);
+  return ! fail;
+}
+
+#else /* USE_STATX */
+
+static struct timespec
+get_birthtime (int fd, char const *filename, struct stat const *st)
+{
+  struct timespec ts = get_stat_birthtime (st);
+
+# if HAVE_GETATTRAT
+  if (ts.tv_nsec < 0)
+    {
+      nvlist_t *response;
+      if ((fd < 0
+           ? getattrat (AT_FDCWD, XATTR_VIEW_READWRITE, filename, &response)
+           : fgetattr (fd, XATTR_VIEW_READWRITE, &response))
+          == 0)
+        {
+          uint64_t *val;
+          uint_t n;
+          if (nvlist_lookup_uint64_array (response, A_CRTIME, &val, &n) == 0
+              && 2 <= n
+              && val[0] <= TYPE_MAXIMUM (time_t)
+              && val[1] < 1000000000 * 2 /* for leap seconds */)
+            {
+              ts.tv_sec = val[0];
+              ts.tv_nsec = val[1];
+            }
+          nvlist_free (response);
+        }
+    }
+# endif
+
+  return ts;
+}
+
+
 /* stat the file and print what we find */
 static bool ATTRIBUTE_WARN_UNUSED_RESULT
 do_stat (char const *filename, char const *format,
@@ -1389,6 +1436,9 @@ do_stat (char const *filename, char const *format,
 {
   int fd = STREQ (filename, "-") ? 0 : -1;
   struct stat statbuf;
+  struct print_args pa;
+  pa.st = &statbuf;
+  pa.btime = (struct timespec) {-1, -1};
 
   if (0 <= fd)
     {
@@ -1412,9 +1462,152 @@ do_stat (char const *filename, char const *format,
   if (S_ISBLK (statbuf.st_mode) || S_ISCHR (statbuf.st_mode))
     format = format2;
 
-  bool fail = print_it (format, fd, filename, print_stat, &statbuf);
+  bool fail = print_it (format, fd, filename, print_stat, &pa);
   return ! fail;
 }
+#endif /* USE_STATX */
+
+
+/* Print stat info.  Return zero upon success, nonzero upon failure.  */
+static bool
+print_stat (char *pformat, size_t prefix_len, unsigned int m,
+            int fd, char const *filename, void const *data)
+{
+  struct print_args *parg = (struct print_args *) data;
+  struct stat *statbuf = parg->st;
+  struct timespec btime = parg->btime;
+  struct passwd *pw_ent;
+  struct group *gw_ent;
+  bool fail = false;
+
+  switch (m)
+    {
+    case 'n':
+      out_string (pformat, prefix_len, filename);
+      break;
+    case 'N':
+      out_string (pformat, prefix_len, quoteN (filename));
+      if (S_ISLNK (statbuf->st_mode))
+        {
+          char *linkname = areadlink_with_size (filename, statbuf->st_size);
+          if (linkname == NULL)
+            {
+              error (0, errno, _("cannot read symbolic link %s"),
+                     quoteaf (filename));
+              return true;
+            }
+          printf (" -> ");
+          out_string (pformat, prefix_len, quoteN (linkname));
+          free (linkname);
+        }
+      break;
+    case 'd':
+      out_uint (pformat, prefix_len, statbuf->st_dev);
+      break;
+    case 'D':
+      out_uint_x (pformat, prefix_len, statbuf->st_dev);
+      break;
+    case 'i':
+      out_uint (pformat, prefix_len, statbuf->st_ino);
+      break;
+    case 'a':
+      out_uint_o (pformat, prefix_len, statbuf->st_mode & CHMOD_MODE_BITS);
+      break;
+    case 'A':
+      out_string (pformat, prefix_len, human_access (statbuf));
+      break;
+    case 'f':
+      out_uint_x (pformat, prefix_len, statbuf->st_mode);
+      break;
+    case 'F':
+      out_string (pformat, prefix_len, file_type (statbuf));
+      break;
+    case 'h':
+      out_uint (pformat, prefix_len, statbuf->st_nlink);
+      break;
+    case 'u':
+      out_uint (pformat, prefix_len, statbuf->st_uid);
+      break;
+    case 'U':
+      pw_ent = getpwuid (statbuf->st_uid);
+      out_string (pformat, prefix_len,
+                  pw_ent ? pw_ent->pw_name : "UNKNOWN");
+      break;
+    case 'g':
+      out_uint (pformat, prefix_len, statbuf->st_gid);
+      break;
+    case 'G':
+      gw_ent = getgrgid (statbuf->st_gid);
+      out_string (pformat, prefix_len,
+                  gw_ent ? gw_ent->gr_name : "UNKNOWN");
+      break;
+    case 'm':
+      fail |= out_mount_point (filename, pformat, prefix_len, statbuf);
+      break;
+    case 's':
+      out_int (pformat, prefix_len, statbuf->st_size);
+      break;
+    case 't':
+      out_uint_x (pformat, prefix_len, major (statbuf->st_rdev));
+      break;
+    case 'T':
+      out_uint_x (pformat, prefix_len, minor (statbuf->st_rdev));
+      break;
+    case 'B':
+      out_uint (pformat, prefix_len, ST_NBLOCKSIZE);
+      break;
+    case 'b':
+      out_uint (pformat, prefix_len, ST_NBLOCKS (*statbuf));
+      break;
+    case 'o':
+      out_uint (pformat, prefix_len, ST_BLKSIZE (*statbuf));
+      break;
+    case 'w':
+      {
+#if ! USE_STATX
+        btime = get_birthtime (fd, filename, statbuf);
+#endif
+        if (btime.tv_nsec < 0)
+          out_string (pformat, prefix_len, "-");
+        else
+          out_string (pformat, prefix_len, human_time (btime));
+      }
+      break;
+    case 'W':
+      {
+#if ! USE_STATX
+        btime = get_birthtime (fd, filename, statbuf);
+#endif
+        out_epoch_sec (pformat, prefix_len, neg_to_zero (btime));
+      }
+      break;
+    case 'x':
+      out_string (pformat, prefix_len, human_time (get_stat_atime (statbuf)));
+      break;
+    case 'X':
+      out_epoch_sec (pformat, prefix_len, get_stat_atime (statbuf));
+      break;
+    case 'y':
+      out_string (pformat, prefix_len, human_time (get_stat_mtime (statbuf)));
+      break;
+    case 'Y':
+      out_epoch_sec (pformat, prefix_len, get_stat_mtime (statbuf));
+      break;
+    case 'z':
+      out_string (pformat, prefix_len, human_time (get_stat_ctime (statbuf)));
+      break;
+    case 'Z':
+      out_epoch_sec (pformat, prefix_len, get_stat_ctime (statbuf));
+      break;
+    case 'C':
+      fail |= out_file_context (pformat, prefix_len, filename);
+      break;
+    default:
+      fputc ('?', stdout);
+      break;
+    }
+  return fail;
+}
 
 /* Return an allocated format string in static storage that
    corresponds to whether FS and TERSE options were declared.  */
@@ -1523,6 +1716,10 @@ Display file or file system status.\n\
       fputs (_("\
   -L, --dereference     follow links\n\
   -f, --file-system     display file system status instead of file status\n\
+"), stdout);
+      fputs (_("\
+      --cached=MODE     specify how to use cached attributes;\n\
+                          useful on remote file systems. See MODE below\n\
 "), stdout);
       fputs (_("\
   -c  --format=FORMAT   use the specified FORMAT instead of the default;\n\
@@ -1535,6 +1732,13 @@ Display file or file system status.\n\
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
 
+      fputs (_("\n\
+The --cached MODE argument can be; always, never, or default.\n\
+`always` will use cached attributes if available, while\n\
+`never` will try to synchronize with the latest attributes, and\n\
+`default` will leave it up to the underlying file system.\n\
+"), stdout);
+
       fputs (_("\n\
 The valid format sequences for files (without --file-system):\n\
 \n\
@@ -1668,6 +1872,23 @@ main (int argc, char *argv[])
           terse = true;
           break;
 
+        case 0:
+          switch (XARGMATCH ("--cached", optarg, cached_args, cached_modes))
+            {
+              case cached_never:
+                force_sync = true;
+                dont_sync = false;
+                break;
+              case cached_always:
+                force_sync = false;
+                dont_sync = true;
+                break;
+              case cached_default:
+                force_sync = false;
+                dont_sync = false;
+            }
+          break;
+
         case_GETOPT_HELP_CHAR;
 
         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
-- 
2.20.1


From b9d6d0b4902cfa5ff3edf5ac7a082138f3237847 Mon Sep 17 00:00:00 2001
From: Jeff Layton <jlayton@kernel.org>
Date: Fri, 14 Jun 2019 14:37:43 -0400
Subject: [PATCH 3/5] stat: fix enabling of statx logic

* src/stat.c: STATX_INO isn't defined until stat.h is included.
Move the test down so it works properly.

Upstream-commit: 0b9bac90d8283c1262e74f0dbda87583508de9a3
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/stat.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/stat.c b/src/stat.c
index 32ffb6d..1d9d83a 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -28,12 +28,6 @@
 # define USE_STATVFS 0
 #endif
 
-#if HAVE_STATX && defined STATX_INO
-# define USE_STATX 1
-#else
-# define USE_STATX 0
-#endif
-
 #include <stddef.h>
 #include <stdio.h>
 #include <stdalign.h>
@@ -80,6 +74,12 @@
 #include "find-mount-point.h"
 #include "xvasprintf.h"
 
+#if HAVE_STATX && defined STATX_INO
+# define USE_STATX 1
+#else
+# define USE_STATX 0
+#endif
+
 #if USE_STATVFS
 # define STRUCT_STATXFS_F_FSID_IS_INTEGER STRUCT_STATVFS_F_FSID_IS_INTEGER
 # define HAVE_STRUCT_STATXFS_F_TYPE HAVE_STRUCT_STATVFS_F_TYPE
-- 
2.20.1


From 987cb69ae212a257b5f8d1582dac03c2aa1aa399 Mon Sep 17 00:00:00 2001
From: Andreas Dilger <adilger@whamcloud.com>
Date: Thu, 27 Jun 2019 02:25:55 -0600
Subject: [PATCH 4/5] stat: don't explicitly request file size for filenames

When calling 'stat -c %N' to print the filename, don't explicitly
request the size of the file via statx(), as it may add overhead on
some filesystems.  The size is only needed to optimize an allocation
for the relatively rare case of reading a symlink name, and the worst
effect is a somewhat-too-large temporary buffer may be allocated for
areadlink_with_size(), or internal retries if buffer is too small.

The file size will be returned by statx() on most filesystems, even
if not requested, unless the filesystem considers this to be too
expensive for that file, in which case the tradeoff is worthwhile.

* src/stat.c: Don't explicitly request STATX_SIZE for filenames.

Upstream-commit: a1a5e9a32eb9525680edd02fd127240c27ba0999
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/stat.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/stat.c b/src/stat.c
index 1d9d83a..32bb5f0 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -1280,7 +1280,7 @@ fmt_to_mask (char fmt)
   switch (fmt)
     {
     case 'N':
-      return STATX_MODE|STATX_SIZE;
+      return STATX_MODE;
     case 'd':
     case 'D':
       return STATX_MODE;
@@ -1352,7 +1352,7 @@ do_stat (char const *filename, char const *format, char const *format2)
   int fd = STREQ (filename, "-") ? 0 : AT_FDCWD;
   int flags = 0;
   struct stat st;
-  struct statx stx;
+  struct statx stx = { 0, };
   const char *pathname = filename;
   struct print_args pa;
   pa.st = &st;
-- 
2.20.1


From 5da6c36dacac4fd610ddad1bc0b2547a9cdfdf2f Mon Sep 17 00:00:00 2001
From: Jeff Layton <jlayton@kernel.org>
Date: Thu, 19 Sep 2019 11:59:45 -0400
Subject: [PATCH 5/5] ls: use statx instead of stat when available

statx allows ls to indicate interest in only certain inode metadata.
This is potentially a win on networked/clustered/distributed
file systems. In cases where we'd have to do a full, heavyweight stat()
call we can now do a much lighter statx() call.

As a real-world example, consider a file system like CephFS where one
client is actively writing to a file and another client does an
ls --color in the same directory. --color means that we need to fetch
the mode of the file.

Doing that with a stat() call means that we have to fetch the size and
mtime in addition to the mode. The MDS in that situation will have to
revoke caps in order to ensure that it has up-to-date values to report,
which disrupts the writer.

This has a measurable affect on performance. I ran a fio sequential
write test on one cephfs client and had a second client do "ls --color"
in a tight loop on the directory that held the file:

Baseline -- no activity on the second client:

WRITE: bw=76.7MiB/s (80.4MB/s), 76.7MiB/s-76.7MiB/s (80.4MB/s-80.4MB/s),
       io=4600MiB (4824MB), run=60016-60016msec

Without this patch series, we see a noticable performance hit:

WRITE: bw=70.4MiB/s (73.9MB/s), 70.4MiB/s-70.4MiB/s (73.9MB/s-73.9MB/s),
       io=4228MiB (4433MB), run=60012-60012msec

With this patch series, we gain most of that ground back:

WRITE: bw=75.9MiB/s (79.6MB/s), 75.9MiB/s-75.9MiB/s (79.6MB/s-79.6MB/s),
       io=4555MiB (4776MB), run=60019-60019msec

* src/stat.c: move statx to stat struct conversion to new header...
* src/statx.h: ...here.
* src/ls.c: Add wrapper functions for stat/lstat/fstat calls,
and add variants for when we are only interested in specific info.
Add statx-enabled functions and set the request mask based on the
output format and what values are needed.

Upstream-commit: a99ab266110795ed94a9cb4d2765ddad9c4310da
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/local.mk |   1 +
 src/ls.c     | 145 ++++++++++++++++++++++++++++++++++++++++++++++++---
 src/stat.c   |  32 +-----------
 src/statx.h  |  52 ++++++++++++++++++
 4 files changed, 192 insertions(+), 38 deletions(-)
 create mode 100644 src/statx.h

diff --git a/src/local.mk b/src/local.mk
index 7a587bb..c013590 100644
--- a/src/local.mk
+++ b/src/local.mk
@@ -58,6 +58,7 @@ noinst_HEADERS =		\
   src/prog-fprintf.h		\
   src/remove.h			\
   src/set-fields.h		\
+  src/statx.h			\
   src/system.h			\
   src/uname.h
 
diff --git a/src/ls.c b/src/ls.c
index bf0c594..7f68e3c 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -114,6 +114,7 @@
 #include "xgethostname.h"
 #include "c-ctype.h"
 #include "canonicalize.h"
+#include "statx.h"
 
 /* Include <sys/capability.h> last to avoid a clash of <sys/types.h>
    include guards with some premature versions of libcap.
@@ -1063,6 +1064,136 @@ dired_dump_obstack (const char *prefix, struct obstack *os)
     }
 }
 
+#if HAVE_STATX && defined STATX_INO
+static unsigned int _GL_ATTRIBUTE_PURE
+time_type_to_statx (void)
+{
+  switch (time_type)
+    {
+    case time_ctime:
+      return STATX_CTIME;
+    case time_mtime:
+      return STATX_MTIME;
+    case time_atime:
+      return STATX_ATIME;
+    default:
+      abort ();
+    }
+    return 0;
+}
+
+static unsigned int _GL_ATTRIBUTE_PURE
+calc_req_mask (void)
+{
+  unsigned int mask = STATX_MODE;
+
+  if (print_inode)
+    mask |= STATX_INO;
+
+  if (print_block_size)
+    mask |= STATX_BLOCKS;
+
+  if (format == long_format) {
+    mask |= STATX_NLINK | STATX_SIZE | time_type_to_statx ();
+    if (print_owner || print_author)
+      mask |= STATX_UID;
+    if (print_group)
+      mask |= STATX_GID;
+  }
+
+  switch (sort_type)
+    {
+    case sort_none:
+    case sort_name:
+    case sort_version:
+    case sort_extension:
+      break;
+    case sort_time:
+      mask |= time_type_to_statx ();
+      break;
+    case sort_size:
+      mask |= STATX_SIZE;
+      break;
+    default:
+      abort ();
+    }
+
+  return mask;
+}
+
+static int
+do_statx (int fd, const char *name, struct stat *st, int flags,
+          unsigned int mask)
+{
+  struct statx stx;
+  int ret = statx (fd, name, flags, mask, &stx);
+  if (ret >= 0)
+    statx_to_stat (&stx, st);
+  return ret;
+}
+
+static inline int
+do_stat (const char *name, struct stat *st)
+{
+  return do_statx (AT_FDCWD, name, st, 0, calc_req_mask ());
+}
+
+static inline int
+do_lstat (const char *name, struct stat *st)
+{
+  return do_statx (AT_FDCWD, name, st, AT_SYMLINK_NOFOLLOW, calc_req_mask ());
+}
+
+static inline int
+stat_for_mode (const char *name, struct stat *st)
+{
+  return do_statx (AT_FDCWD, name, st, 0, STATX_MODE);
+}
+
+/* dev+ino should be static, so no need to sync with backing store */
+static inline int
+stat_for_ino (const char *name, struct stat *st)
+{
+  return do_statx (AT_FDCWD, name, st, 0, STATX_INO);
+}
+
+static inline int
+fstat_for_ino (int fd, struct stat *st)
+{
+  return do_statx (fd, "", st, AT_EMPTY_PATH, STATX_INO);
+}
+#else
+static inline int
+do_stat (const char *name, struct stat *st)
+{
+  return stat (name, st);
+}
+
+static inline int
+do_lstat (const char *name, struct stat *st)
+{
+  return lstat (name, st);
+}
+
+static inline int
+stat_for_mode (const char *name, struct stat *st)
+{
+  return stat (name, st);
+}
+
+static inline int
+stat_for_ino (const char *name, struct stat *st)
+{
+  return stat (name, st);
+}
+
+static inline int
+fstat_for_ino (int fd, struct stat *st)
+{
+  return fstat (fd, st);
+}
+#endif
+
 /* Return the address of the first plain %b spec in FMT, or NULL if
    there is no such spec.  %5b etc. do not match, so that user
    widths/flags are honored.  */
@@ -2733,10 +2864,10 @@ print_dir (char const *name, char const *realname, bool command_line_arg)
       struct stat dir_stat;
       int fd = dirfd (dirp);
 
-      /* If dirfd failed, endure the overhead of using stat.  */
+      /* If dirfd failed, endure the overhead of stat'ing by path  */
       if ((0 <= fd
-           ? fstat (fd, &dir_stat)
-           : stat (name, &dir_stat)) < 0)
+           ? fstat_for_ino (fd, &dir_stat)
+           : stat_for_ino (name, &dir_stat)) < 0)
         {
           file_failure (command_line_arg,
                         _("cannot determine device and inode of %s"), name);
@@ -3198,7 +3329,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
       switch (dereference)
         {
         case DEREF_ALWAYS:
-          err = stat (full_name, &f->stat);
+          err = do_stat (full_name, &f->stat);
           do_deref = true;
           break;
 
@@ -3207,7 +3338,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
           if (command_line_arg)
             {
               bool need_lstat;
-              err = stat (full_name, &f->stat);
+              err = do_stat (full_name, &f->stat);
               do_deref = true;
 
               if (dereference == DEREF_COMMAND_LINE_ARGUMENTS)
@@ -3227,7 +3358,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
           FALLTHROUGH;
 
         default: /* DEREF_NEVER */
-          err = lstat (full_name, &f->stat);
+          err = do_lstat (full_name, &f->stat);
           do_deref = false;
           break;
         }
@@ -3316,7 +3447,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
              they won't be traced and when no indicator is needed.  */
           if (linkname
               && (file_type <= indicator_style || check_symlink_color)
-              && stat (linkname, &linkstats) == 0)
+              && stat_for_mode (linkname, &linkstats) == 0)
             {
               f->linkok = true;
 
diff --git a/src/stat.c b/src/stat.c
index 32bb5f0..03fecc3 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -73,6 +73,7 @@
 #include "strftime.h"
 #include "find-mount-point.h"
 #include "xvasprintf.h"
+#include "statx.h"
 
 #if HAVE_STATX && defined STATX_INO
 # define USE_STATX 1
@@ -1243,37 +1244,6 @@ static bool dont_sync;
 static bool force_sync;
 
 #if USE_STATX
-/* Much of the format printing requires a struct stat or timespec */
-static struct timespec
-statx_timestamp_to_timespec (struct statx_timestamp tsx)
-{
-  struct timespec ts;
-
-  ts.tv_sec = tsx.tv_sec;
-  ts.tv_nsec = tsx.tv_nsec;
-  return ts;
-}
-
-static void
-statx_to_stat (struct statx *stx, struct stat *stat)
-{
-  stat->st_dev = makedev (stx->stx_dev_major, stx->stx_dev_minor);
-  stat->st_ino = stx->stx_ino;
-  stat->st_mode = stx->stx_mode;
-  stat->st_nlink = stx->stx_nlink;
-  stat->st_uid = stx->stx_uid;
-  stat->st_gid = stx->stx_gid;
-  stat->st_rdev = makedev (stx->stx_rdev_major, stx->stx_rdev_minor);
-  stat->st_size = stx->stx_size;
-  stat->st_blksize = stx->stx_blksize;
-/* define to avoid sc_prohibit_stat_st_blocks.  */
-# define SC_ST_BLOCKS st_blocks
-  stat->SC_ST_BLOCKS = stx->stx_blocks;
-  stat->st_atim = statx_timestamp_to_timespec (stx->stx_atime);
-  stat->st_mtim = statx_timestamp_to_timespec (stx->stx_mtime);
-  stat->st_ctim = statx_timestamp_to_timespec (stx->stx_ctime);
-}
-
 static unsigned int
 fmt_to_mask (char fmt)
 {
diff --git a/src/statx.h b/src/statx.h
new file mode 100644
index 0000000..19f3e18
--- /dev/null
+++ b/src/statx.h
@@ -0,0 +1,52 @@
+/* statx -> stat conversion functions for coreutils
+   Copyright (C) 2019 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifndef COREUTILS_STATX_H
+# define COREUTILS_STATX_H
+
+# if HAVE_STATX && defined STATX_INO
+/* Much of the format printing requires a struct stat or timespec */
+static inline struct timespec
+statx_timestamp_to_timespec (struct statx_timestamp tsx)
+{
+  struct timespec ts;
+
+  ts.tv_sec = tsx.tv_sec;
+  ts.tv_nsec = tsx.tv_nsec;
+  return ts;
+}
+
+static inline void
+statx_to_stat (struct statx *stx, struct stat *stat)
+{
+  stat->st_dev = makedev (stx->stx_dev_major, stx->stx_dev_minor);
+  stat->st_ino = stx->stx_ino;
+  stat->st_mode = stx->stx_mode;
+  stat->st_nlink = stx->stx_nlink;
+  stat->st_uid = stx->stx_uid;
+  stat->st_gid = stx->stx_gid;
+  stat->st_rdev = makedev (stx->stx_rdev_major, stx->stx_rdev_minor);
+  stat->st_size = stx->stx_size;
+  stat->st_blksize = stx->stx_blksize;
+/* define to avoid sc_prohibit_stat_st_blocks.  */
+#  define SC_ST_BLOCKS st_blocks
+  stat->SC_ST_BLOCKS = stx->stx_blocks;
+  stat->st_atim = statx_timestamp_to_timespec (stx->stx_atime);
+  stat->st_mtim = statx_timestamp_to_timespec (stx->stx_mtime);
+  stat->st_ctim = statx_timestamp_to_timespec (stx->stx_ctime);
+}
+# endif /* HAVE_STATX && defined STATX_INO */
+#endif /* COREUTILS_STATX_H */
-- 
2.20.1