From: Pádraig Brady Date: Thu, 2 Jan 2020 16:20:13 +0000 (+0000) Subject: ls: support --time=creation to show/sort birth time X-Git-Tag: v8.32~30 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2cecc3cc994b570efac9c377fa7fd0ef7e1ad122;p=thirdparty%2Fcoreutils.git ls: support --time=creation to show/sort birth time * src/ls.c (usage): Reorganize help for --time, and add description for --time=birth. (do_statx): Store btime in mtime if available. (get_stat_btime): A new function to read the creation time from the appropriate stat structure member. (cmp_btime): A new function to compare birth time. (print_long_format): Output '?' when birth time unavailable. * doc/coreutils.texi: Document --time={birth,creation}. * tests/local.mk: Reference the new test. * tests/ls/birthtime.sh: Add a new test. * NEWS: Mention the new feature. --- diff --git a/NEWS b/NEWS index 14ec876b5b..5231b84ac3 100644 --- a/NEWS +++ b/NEWS @@ -62,6 +62,9 @@ GNU coreutils NEWS -*- outline -*- ** New Features + ls now supports the --time=birth option to display and sort by + file creation time, where available. + od --skip-bytes now can use lseek even if the input is not a regular file, greatly improving performance in some cases. diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 96a9cee21b..cb238f0873 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -7877,7 +7877,8 @@ Sort by file size, largest first. @opindex -t @opindex --sort @opindex modification timestamp@r{, sorting files by} -Sort by modification timestamp (mtime), newest first. +Sort by modification timestamp (mtime) by default, newest first. +The timestamp to order by can be changed with the @option{--time} option. @xref{File timestamps}. @item -u @@ -7895,6 +7896,17 @@ When explicitly sorting by time (@option{--sort=time} or @option{-t}) or when not using a long listing format, sort according to the atime. @xref{File timestamps}. +@item --time=birth +@itemx --time=creation +@opindex --time +@opindex birth time@r{, printing or sorting files by} +@opindex creation timestamp@r{, printing or sorting files by} +If the long listing format (e.g., @option{--format=long}) is being used, +print the file creation timestamp if available. +When explicitly sorting by time (@option{--sort=time} or @option{-t}) +or when not using a long listing format, sort according to the birth time. +@xref{File timestamps}. + @item -U @itemx --sort=none @opindex -U diff --git a/src/ls.c b/src/ls.c index c3555f45ce..4acf5f44d9 100644 --- a/src/ls.c +++ b/src/ls.c @@ -460,6 +460,7 @@ enum time_type time_mtime, /* default */ time_ctime, /* -c */ time_atime, /* -u */ + time_btime, /* birth time */ time_numtypes /* the number of elements of this enum */ }; @@ -912,11 +913,16 @@ ARGMATCH_VERIFY (sort_args, sort_types); static char const *const time_args[] = { - "atime", "access", "use", "ctime", "status", NULL + "atime", "access", "use", + "ctime", "status", + "birth", "creation", + NULL }; static enum time_type const time_types[] = { - time_atime, time_atime, time_atime, time_ctime, time_ctime + time_atime, time_atime, time_atime, + time_ctime, time_ctime, + time_btime, time_btime, }; ARGMATCH_VERIFY (time_args, time_types); @@ -1065,6 +1071,24 @@ dired_dump_obstack (const char *prefix, struct obstack *os) } } +/* Return the platform birthtime member of the stat structure, + or fallback to the mtime member, which we have populated + from the statx structure or reset to an invalid timestamp + where birth time is not supported. */ +static struct timespec +get_stat_btime (struct stat const *st) +{ + struct timespec btimespec; + +#if HAVE_STATX && defined STATX_INO + btimespec = get_stat_mtime (st); +#else + btimespec = get_stat_birthtime (st); +#endif + + return btimespec; +} + #if HAVE_STATX && defined STATX_INO static unsigned int _GL_ATTRIBUTE_PURE time_type_to_statx (void) @@ -1077,6 +1101,8 @@ time_type_to_statx (void) return STATX_MTIME; case time_atime: return STATX_ATIME; + case time_btime: + return STATX_BTIME; default: abort (); } @@ -1127,9 +1153,22 @@ do_statx (int fd, const char *name, struct stat *st, int flags, unsigned int mask) { struct statx stx; + bool want_btime = mask & STATX_BTIME; int ret = statx (fd, name, flags, mask, &stx); if (ret >= 0) - statx_to_stat (&stx, st); + { + statx_to_stat (&stx, st); + /* Since we only need one timestamp type, + store birth time in st_mtim. */ + if (want_btime) + { + if (stx.stx_mask & STATX_BTIME) + st->st_mtim = statx_timestamp_to_timespec (stx.stx_btime); + else + st->st_mtim.tv_sec = st->st_mtim.tv_nsec = -1; + } + } + return ret; } @@ -2298,7 +2337,8 @@ decode_switches (int argc, char **argv) -lu means show atime and sort by name, -lut means show atime and sort by atime. */ - if ((time_type == time_ctime || time_type == time_atime) + if ((time_type == time_ctime || time_type == time_atime + || time_type == time_btime) && !sort_type_specified && format != long_format) { sort_type = sort_time; @@ -3785,6 +3825,15 @@ cmp_atime (struct fileinfo const *a, struct fileinfo const *b, return diff ? diff : cmp (a->name, b->name); } +static inline int +cmp_btime (struct fileinfo const *a, struct fileinfo const *b, + int (*cmp) (char const *, char const *)) +{ + int diff = timespec_cmp (get_stat_btime (&b->stat), + get_stat_btime (&a->stat)); + return diff ? diff : cmp (a->name, b->name); +} + static inline int cmp_size (struct fileinfo const *a, struct fileinfo const *b, int (*cmp) (char const *, char const *)) @@ -3816,6 +3865,7 @@ cmp_extension (struct fileinfo const *a, struct fileinfo const *b, DEFINE_SORT_FUNCTIONS (ctime, cmp_ctime) DEFINE_SORT_FUNCTIONS (mtime, cmp_mtime) DEFINE_SORT_FUNCTIONS (atime, cmp_atime) +DEFINE_SORT_FUNCTIONS (btime, cmp_btime) DEFINE_SORT_FUNCTIONS (size, cmp_size) DEFINE_SORT_FUNCTIONS (name, cmp_name) DEFINE_SORT_FUNCTIONS (extension, cmp_extension) @@ -3892,7 +3942,8 @@ static qsortFunc const sort_functions[][2][2][2] = /* last are time sort functions */ LIST_SORTFUNCTION_VARIANTS (mtime), LIST_SORTFUNCTION_VARIANTS (ctime), - LIST_SORTFUNCTION_VARIANTS (atime) + LIST_SORTFUNCTION_VARIANTS (atime), + LIST_SORTFUNCTION_VARIANTS (btime) }; /* The number of sort keys is calculated as the sum of @@ -4162,6 +4213,7 @@ print_long_format (const struct fileinfo *f) char *p; struct timespec when_timespec; struct tm when_local; + bool btime_ok = true; /* Compute the mode string, except remove the trailing space if no file in this directory has an ACL or security context. */ @@ -4191,6 +4243,11 @@ print_long_format (const struct fileinfo *f) case time_atime: when_timespec = get_stat_atime (&f->stat); break; + case time_btime: + when_timespec = get_stat_btime (&f->stat); + if (when_timespec.tv_sec == -1 && when_timespec.tv_nsec == -1) + btime_ok = false; + break; default: abort (); } @@ -4291,7 +4348,8 @@ print_long_format (const struct fileinfo *f) s = 0; *p = '\1'; - if (f->stat_ok && localtime_rz (localtz, &when_timespec.tv_sec, &when_local)) + if (f->stat_ok && btime_ok + && localtime_rz (localtz, &when_timespec.tv_sec, &when_local)) { struct timespec six_months_ago; bool recent; @@ -4332,7 +4390,7 @@ print_long_format (const struct fileinfo *f) print it as a huge integer number of seconds. */ char hbuf[INT_BUFSIZE_BOUND (intmax_t)]; sprintf (p, "%*s ", long_time_expected_width (), - (! f->stat_ok + (! f->stat_ok || ! btime_ok ? "?" : timetostr (when_timespec.tv_sec, hbuf))); /* FIXME: (maybe) We discarded when_timespec.tv_nsec. */ @@ -5380,17 +5438,18 @@ Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.\n\ --sort=WORD sort by WORD instead of name: none (-U), size (-S)\ ,\n\ time (-t), version (-v), extension (-X)\n\ - --time=WORD with -l, show time as WORD instead of default\n\ - modification time: atime or access or use (-u);\ -\n\ - ctime or status (-c); also use specified time\n\ - as sort key if --sort=time (newest first)\n\ + --time=WORD change the default of using modification times;\n\ + access time (-u): atime, access, use;\n\ + change time (-c): ctime, status;\n\ + birth time: birth, creation;\n\ + with -l, WORD determines which time to show;\n\ + with --sort=time, sort by WORD (newest first)\n\ "), stdout); fputs (_("\ --time-style=TIME_STYLE time/date format with -l; see TIME_STYLE below\n\ "), stdout); fputs (_("\ - -t sort by modification time, newest first\n\ + -t sort by time, newest first; see --time\n\ -T, --tabsize=COLS assume tab stops at each COLS instead of 8\n\ "), stdout); fputs (_("\ diff --git a/tests/local.mk b/tests/local.mk index 5285f3a528..bbcb9d4133 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -589,6 +589,7 @@ all_tests = \ tests/ln/target-1.sh \ tests/ls/a-option.sh \ tests/ls/abmon-align.sh \ + tests/ls/birthtime.sh \ tests/ls/block-size.sh \ tests/ls/color-clear-to-eol.sh \ tests/ls/color-dtype-dir.sh \ diff --git a/tests/ls/birthtime.sh b/tests/ls/birthtime.sh new file mode 100755 index 0000000000..1bda03314b --- /dev/null +++ b/tests/ls/birthtime.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# ensure that ls attempts birthtime access + +# Copyright (C) 2020 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 . + +. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src +print_ver_ ls + +# ls should not fail, even if birth time not available +touch a || framework_failure_ +ls --time=birth -l a || fail=1 +ls --time=creation -t a || fail=1 + +Exit $fail