From: Paul Eggert Date: Tue, 29 Jul 2025 15:24:58 +0000 (-0700) Subject: tail: refactor to skip stat call on failure X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=09fc945b374d256869615591171d744462e20c6e;p=thirdparty%2Fcoreutils.git tail: refactor to skip stat call on failure * src/tail.c (tail_bytes): New function. (tail_bytes, tail_lines, tail): Accept struct stat pointer from caller instead of calling fstat ourselves. All callers changed. (tail_file): Skip a call to fstat if fstat already failed. * tests/tail/follow-stdin.sh: Adjust to match new behavior on failure, which omits a redundant diagnostic. --- diff --git a/src/tail.c b/src/tail.c index 3956afdd41..6ba3e8379b 100644 --- a/src/tail.c +++ b/src/tail.c @@ -1840,21 +1840,30 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files, } #endif -/* Output the last N_BYTES bytes of file FILENAME open for reading in FD. - Return true if successful. */ +/* Get the status for file F, which has descriptor FD, into *ST. + Return true on success, false (diagnosing the failure) otherwise. */ static bool -tail_bytes (char const *pretty_filename, int fd, uintmax_t n_bytes, - uintmax_t *read_pos) +get_file_status (struct File_spec *f, int fd, struct stat *st) { - struct stat stats; - - if (fstat (fd, &stats)) + if (fstat (fd, st) < 0) { - error (0, errno, _("cannot fstat %s"), quoteaf (pretty_filename)); + f->errnum = errno; + error (0, f->errnum, _("cannot fstat %s"), quoteaf (pretty_name (f))); return false; } + return true; +} + +/* Output the last bytes of the file PRETTY_FILENAME open for reading + in FD and with status ST. Output the last N_BYTES bytes, and set *READ_POS + to the resulting read position if the file is a regular file, and + to an unspecified value otherwise. Return true if and only if successful. */ +static bool +tail_bytes (char const *pretty_filename, int fd, struct stat const *st, + uintmax_t n_bytes, uintmax_t *read_pos) +{ if (from_start) { off_t pos = (presume_input_pipe @@ -1878,14 +1887,14 @@ tail_bytes (char const *pretty_filename, int fd, uintmax_t n_bytes, if (0 <= current_pos) { - if (usable_st_size (&stats)) + if (usable_st_size (st)) { /* Use st_size only if it's so large that this is probably not a /proc or similar file, where st_size is notional. */ - off_t smallish_size = STP_BLKSIZE (&stats); - if (smallish_size < stats.st_size) - end_pos = stats.st_size; + off_t smallish_size = STP_BLKSIZE (st); + if (smallish_size < st->st_size) + end_pos = st->st_size; } else { @@ -1933,21 +1942,15 @@ tail_bytes (char const *pretty_filename, int fd, uintmax_t n_bytes, return true; } -/* Output the last N_LINES lines of file FILENAME open for reading in FD. - Return true if successful. */ +/* Output the last lines of the file PRETTY_FILENAME open for reading + in FD and with status ST. Output the last N_LINES lines, and set *READ_POS + to the resulting read position if the file is a regular file, and + to an unspecified value otherwise. Return true if and only if successful. */ static bool -tail_lines (char const *pretty_filename, int fd, uintmax_t n_lines, - uintmax_t *read_pos) +tail_lines (char const *pretty_filename, int fd, struct stat const *st, + uintmax_t n_lines, uintmax_t *read_pos) { - struct stat stats; - - if (fstat (fd, &stats)) - { - error (0, errno, _("cannot fstat %s"), quoteaf (pretty_filename)); - return false; - } - if (from_start) { /* If skipping all input use lseek if possible, for speed. */ @@ -1970,13 +1973,13 @@ tail_lines (char const *pretty_filename, int fd, uintmax_t n_lines, /* Use file_lines only if FD refers to a regular file for which lseek (... SEEK_END) works. */ if ( ! presume_input_pipe - && S_ISREG (stats.st_mode) + && S_ISREG (st->st_mode) && (start_pos = lseek (fd, 0, SEEK_CUR)) != -1 && start_pos < (end_pos = lseek (fd, 0, SEEK_END))) { *read_pos = end_pos; if (end_pos != 0 - && ! file_lines (pretty_filename, fd, &stats, n_lines, + && ! file_lines (pretty_filename, fd, st, n_lines, start_pos, end_pos, read_pos)) return false; } @@ -1995,8 +1998,9 @@ tail_lines (char const *pretty_filename, int fd, uintmax_t n_lines, return true; } -/* Display the last N_UNITS units of file FILENAME, open for reading - via FD. Set *READ_POS to the position of the input stream pointer. +/* Display the last N_UNITS units of file FILENAME, + open for reading via FD and with status *ST. + Set *READ_POS to the position of the input stream pointer. *READ_POS is usually the number of bytes read and corresponds to an offset from the beginning of a file. However, it may be larger than OFF_T_MAX (as for an input pipe), and may also be larger than the @@ -2006,14 +2010,12 @@ tail_lines (char const *pretty_filename, int fd, uintmax_t n_lines, Return true if successful. */ static bool -tail (char const *filename, int fd, uintmax_t n_units, - uintmax_t *read_pos) +tail (char const *filename, int fd, struct stat const *st, + uintmax_t n_units, uintmax_t *read_pos) { *read_pos = 0; - if (count_lines) - return tail_lines (filename, fd, n_units, read_pos); - else - return tail_bytes (filename, fd, n_units, read_pos); + return ((count_lines ? tail_lines : tail_bytes) + (filename, fd, st, n_units, read_pos)); } /* Display the last N_UNITS units of the file described by F. @@ -2061,29 +2063,22 @@ tail_file (struct File_spec *f, uintmax_t n_files, uintmax_t n_units) if (print_headers) write_header (pretty_name (f)); - ok = tail (pretty_name (f), fd, n_units, &read_pos); + + struct stat stats; + bool stat_ok = get_file_status (f, fd, &stats); + ok = stat_ok && tail (pretty_name (f), fd, &stats, n_units, &read_pos); + if (forever) { - struct stat stats; - #if TAIL_TEST_SLEEP /* Before the tail function provided 'read_pos', there was a race condition described in the URL below. This sleep call made the window big enough to exercise the problem. */ xnanosleep (1); #endif - f->errnum = ok - 1; - if (fstat (fd, &stats) < 0) + if (stat_ok && !IS_TAILABLE_FILE_TYPE (stats.st_mode)) { ok = false; - f->errnum = errno; - error (0, errno, _("error reading %s"), - quoteaf (pretty_name (f))); - } - else if (!IS_TAILABLE_FILE_TYPE (stats.st_mode)) - { - ok = false; - f->errnum = -1; f->tailable = false; f->ignore = ! reopen_inaccessible_files; error (0, 0, _("%s: cannot follow end of this type of file%s"), @@ -2091,6 +2086,11 @@ tail_file (struct File_spec *f, uintmax_t n_files, uintmax_t n_units) f->ignore ? _("; giving up on this name") : ""); } + if (ok && !get_file_status (f, fd, &stats)) + ok = false; + else if (stat_ok) + f->errnum = ok - 1; + if (!ok) { f->ignore = ! reopen_inaccessible_files; diff --git a/tests/tail/follow-stdin.sh b/tests/tail/follow-stdin.sh index acec835185..8aa8b78acd 100755 --- a/tests/tail/follow-stdin.sh +++ b/tests/tail/follow-stdin.sh @@ -59,7 +59,6 @@ done returns_ 1 timeout 10 tail -f - <&- 2>errt || fail=1 cat <<\EOF >exp || framework_failure_ tail: cannot fstat 'standard input' -tail: error reading 'standard input' tail: no files remaining EOF sed 's/\(tail:.*\):.*/\1/' errt > err || framework_failure_