diff --git a/ORIGtail.c b/tail_p2g1.c index 2244509dd..c992d22fd 100644 --- a/ORIGtail.c +++ b/tail_p2g1.c @@ -1,3 +1,40 @@ +//=========================================================================== +// Modified version of tail.c, based against git version 9.1.139-b5904, +// to address the issue described in +// +// https://lists.gnu.org/archive/html/coreutils/2023-01/msg00030.html +// +// It incorporates Padraig's informal mods and patches from that thread, +// along with some additional debug code and a further patch added by me, +// as follows: +// +// 1. Padraig's inline diff from his coreutils email dated +// 30 Jan 2023 21:27:50 +0000. +// +// 2. The untested patch named tail-F-dev.patch attached to Padraig's email +// dated 1 Feb 2023 21:25:40 +0000. (This patch almost, but not quite, +// fixes the problem; with only this patch and the one above applied, +// dump_remainder() dies with EBADF.) +// +// 3. Various GDG debug printfs, noted in this file by "GDG: debug +// print only", added to understand why the above two patches did not +// quite fix the problem. These printfs can be enabled/disabled via +// static global "show_gdg_printfs", which is defined just before usage(). +// +// 4. An additional mod to Padraig's patches above, which seems to fully +// address the problem, without breaking any "make check" tests. This +// mod is noted in the code by "GDG: Functional change", and is +// commented in detail at that point. +// +// NOTE: This file does _not_ include the inline diff from Padraig's email +// dated 1 Feb 2023 20:37:47 +0000 (which he subsequently noted shortly +// thereafter would not work). +// +// - gdg, 2023.02.05 +// +//=========================================================================== + + /* tail -- output the last part of file(s) Copyright (C) 1989-2023 Free Software Foundation, Inc. @@ -256,6 +293,11 @@ static struct option const long_options[] = {NULL, 0, NULL, 0} }; +// +// GDG: If set, GDG printfs are emitted. +// +static int show_gdg_printfs = 0; + void usage (int status) { @@ -462,6 +504,13 @@ static uintmax_t dump_remainder (bool want_header, char const *pretty_filename, int fd, uintmax_t n_bytes) { + // GDG: debug print only + if (show_gdg_printfs) + { + error(0, 0, _("dump_remainder(fn=%s, fd=%d)"), + quoteaf (pretty_filename), fd); + } + uintmax_t n_written; uintmax_t n_remaining = n_bytes; @@ -1001,6 +1050,12 @@ recheck (struct File_spec *f, bool blocking) assert (valid_file_spec (f)); + // GDG: debug print only + if (show_gdg_printfs) + { + error (0, 0, _("recheck(): open(%s,%d)"), quoteaf (pretty_name (f)), fd); + } + /* If the open fails because the file doesn't exist, then mark the file as not tailable. */ f->tailable = !(reopen_inaccessible_files && fd == -1); @@ -1104,12 +1159,24 @@ recheck (struct File_spec *f, bool blocking) /* Close the old one. */ close_fd (f->fd, pretty_name (f)); + // GDG: debug print only + if (show_gdg_printfs) + { + error (0, 0, _("recheck(): close_fd(%d, %s) in Clause 1"), + f->fd, quoteaf (pretty_name (f))); + } } else { /* No changes detected, so close new fd. */ close_fd (fd, pretty_name (f)); + // GDG: debug print only + if (show_gdg_printfs) + { + error (0, 0, _("recheck(): close_fd(%d, %s) in Clause 2"), + fd, quoteaf (pretty_name (f))); + } } /* FIXME: When a log is rotated, daemons tend to log to the @@ -1122,7 +1189,8 @@ recheck (struct File_spec *f, bool blocking) { /* Start at the beginning of the file. */ record_open_fd (f, fd, 0, &new_stats, (is_stdin ? -1 : blocking)); - xlseek (fd, 0, SEEK_SET, pretty_name (f)); + if (S_ISREG (new_stats.st_mode)) + xlseek (fd, 0, SEEK_SET, pretty_name (f)); } } @@ -1221,6 +1289,9 @@ tail_forever (struct File_spec *f, size_t n_files, double sleep_interval) f[i].blocking = blocking; } + // This flag added by Padraig's tail-F-dev.patch + bool read_unchanged = false; + if (!f[i].blocking) { if (fstat (fd, &stats) != 0) @@ -1240,10 +1311,28 @@ tail_forever (struct File_spec *f, size_t n_files, double sleep_interval) <= f[i].n_unchanged_stats++) && follow_mode == Follow_name) { + // GDG: debug print only + if (show_gdg_printfs) + { + error(0, 0, + _("tail_forever(fd=%d): Call recheck()"), fd); + } recheck (&f[i], f[i].blocking); f[i].n_unchanged_stats = 0; } - continue; + if (S_ISREG (stats.st_mode) || 1 < n_files) + continue; + else + { + // GDG: debug print only + if (show_gdg_printfs) + { + error(0, 0, + _("tail_forever(fd=%d): Set read_unchanged=true"), + fd); + } + read_unchanged = true; + } } /* This file has changed. Print out what we can, and @@ -1252,8 +1341,40 @@ tail_forever (struct File_spec *f, size_t n_files, double sleep_interval) f[i].mtime = get_stat_mtime (&stats); f[i].mode = stats.st_mode; + // + // GDG: Functional change. + // + // At this point in the non-blocking clause, the assumption is + // that the recheck() call above has found that the file has + // changed, so it's necessary to re-point local variable fd to + // the new file descriptor obtained by recheck()'s call to + // record_open_fd(); that new file descriptor is f[i]->fd. + // + // If this re-point of fd is not done, them the fd corresponding + // to the old (now-closed) file winds up being used in the call + // to dump_remainder() further below, leading to EBADF. + // + // I am not 100% confident that this change is entirely correct + // as-is; only that it (a) seems to make sense superficially, + // (b) does seem to fix the particular problem being studied, + // and (c) does not cause any of the "make check" tests to fail. + // However, due to my unfamiliarity with the code and the less + // than transparent logic flow in this clause, it is certainly + // possible that this putative fix is still not quite right and + // winds up breaking something else in some subtle way. + // + // However: If this change _is_ deemed to be correct, then it + // would seem to imply that the use of 'fd' in the regular-file + // case in the 'XXX heuristic' clause below (where fd is used as + // an arg to xlseek()) may also be historically wrong. As to how + // it could ever haved worked when seeking on a closed fd... + // I have no clue. Perhaps that clause was never being invoked. + // + fd = f[i].fd; + /* reset counter */ - f[i].n_unchanged_stats = 0; + if (! read_unchanged) + f[i].n_unchanged_stats = 0; /* XXX: This is only a heuristic, as the file may have also been truncated and written to if st_size >= size @@ -1286,8 +1407,18 @@ tail_forever (struct File_spec *f, size_t n_files, double sleep_interval) else bytes_to_read = COPY_TO_EOF; + // GDG: debug print only + if (show_gdg_printfs) + { + error(0, 0, + _("tail_forever(): call dump_remainder(name=%s, fd=%d)"), + quoteaf (name), fd); + } bytes_read = dump_remainder (false, name, fd, bytes_to_read); + if (read_unchanged && bytes_read) + f[i].n_unchanged_stats = 0; + any_input |= (bytes_read != 0); f[i].size += bytes_read; }