coreutils/coreutils-8.31-statx.patch

1295 lines
38 KiB
Diff

From 061032235577e58980f76f6340e8b0e7f0350dd0 Mon Sep 17 00:00:00 2001
From: Jeff Layton <jlayton@kernel.org>
Date: Tue, 28 May 2019 08:21:42 -0400
Subject: [PATCH 1/4] 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 c123860..957ee92 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -12360,6 +12360,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 bb1ef1a..3bb84f3 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).
@@ -676,7 +704,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);
@@ -980,57 +1007,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 && defined STATX_BTIME
- 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)
@@ -1067,139 +1043,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
@@ -1241,6 +1084,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. */
@@ -1270,33 +1124,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;
}
@@ -1384,6 +1233,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,
@@ -1391,6 +1438,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)
{
@@ -1414,9 +1464,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. */
@@ -1525,6 +1718,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\
@@ -1537,6 +1734,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\
@@ -1670,6 +1874,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 c19f77360d564e0c0d5ab0159299ebd8d6c34a2f Mon Sep 17 00:00:00 2001
From: Jeff Layton <jlayton@kernel.org>
Date: Fri, 14 Jun 2019 14:37:43 -0400
Subject: [PATCH 2/4] 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 3bb84f3..ec0bb7d 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 0081747eb0fd1eb604e1f17c8c5bfaf5119310a9 Mon Sep 17 00:00:00 2001
From: Andreas Dilger <adilger@whamcloud.com>
Date: Thu, 27 Jun 2019 02:25:55 -0600
Subject: [PATCH 3/4] 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 ec0bb7d..ee68f16 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -1282,7 +1282,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;
@@ -1354,7 +1354,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 e18c739a523f760d8372f8dd33f047a88a1b48fd Mon Sep 17 00:00:00 2001
From: Jeff Layton <jlayton@kernel.org>
Date: Thu, 19 Sep 2019 11:59:45 -0400
Subject: [PATCH 4/4] 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 a69d405..6075391 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 120ce15..034087f 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. */
@@ -2737,10 +2868,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);
@@ -3202,7 +3333,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;
@@ -3211,7 +3342,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)
@@ -3231,7 +3362,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;
}
@@ -3320,7 +3451,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_mode)
- && stat (linkname, &linkstats) == 0)
+ && stat_for_mode (linkname, &linkstats) == 0)
{
f->linkok = true;
f->linkmode = linkstats.st_mode;
diff --git a/src/stat.c b/src/stat.c
index ee68f16..f2bf0dc 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
@@ -1245,37 +1246,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