diff --git a/coreutils-8.31-statx.patch b/coreutils-8.31-statx.patch new file mode 100644 index 0000000..cc09c2e --- /dev/null +++ b/coreutils-8.31-statx.patch @@ -0,0 +1,1294 @@ +From 061032235577e58980f76f6340e8b0e7f0350dd0 Mon Sep 17 00:00:00 2001 +From: Jeff Layton +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 +--- + 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 + #include + #include +@@ -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 +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 +--- + 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 + #include + #include +@@ -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 +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 +--- + 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 +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 +--- + 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 last to avoid a clash of + 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 . */ ++ ++#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 + diff --git a/coreutils.spec b/coreutils.spec index 85d5eff..ee030fa 100644 --- a/coreutils.spec +++ b/coreutils.spec @@ -1,7 +1,7 @@ Summary: A set of basic GNU tools commonly used in shell scripts Name: coreutils Version: 8.31 -Release: 6%{?dist} +Release: 6.1%{?dist} License: GPLv3+ Url: https://www.gnu.org/software/coreutils/ Source0: https://ftp.gnu.org/gnu/%{name}/%{name}-%{version}.tar.xz @@ -16,6 +16,9 @@ Source106: coreutils-colorls.csh # md5sum,b2sum,sha*sum: --help: add note about binary/text mode Patch1: coreutils-8.31-sums-man-pages.patch +# use statx instead of stat when available (#1760300) +Patch2: coreutils-8.31-statx.patch + # disable the test-lock gnulib test prone to deadlock Patch100: coreutils-8.26-test-lock.patch @@ -235,6 +238,9 @@ rm -f $RPM_BUILD_ROOT%{_infodir}/dir %license COPYING %changelog +* Mon Nov 18 2019 Kamil Dudka - 8.31-6.1 +- use statx instead of stat when available (#1760300) + * Thu Oct 17 2019 Kamil Dudka - 8.31-6 - temporarily disable the use of statx (#1760300)