From 933f238bd1dfd8931fa3cc60f61aea19802daefd Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Wed, 30 Jun 2021 16:38:42 -0700 Subject: tail: implement -F (and its companion -s). (Based on someone else's patch.) Implementing -F with inotify is a lot more work (including more portability shims for macOS), so this is a simpler polling implementation. Also fix my earlier mistake where xnotify_add() wasn't actually an 'x' function that exits on failure. --- lib/portability.c | 4 +- toys/posix/tail.c | 117 +++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 96 insertions(+), 25 deletions(-) diff --git a/lib/portability.c b/lib/portability.c index 6118d0f2..f97af60e 100644 --- a/lib/portability.c +++ b/lib/portability.c @@ -211,7 +211,7 @@ int xnotify_add(struct xnotify *not, int fd, char *path) if (not->count == not->max) error_exit("xnotify_add overflow"); EV_SET(&event, fd, EVFILT_VNODE, EV_ADD|EV_CLEAR, NOTE_WRITE, 0, NULL); if (kevent(not->kq, &event, 1, NULL, 0, NULL) == -1 || event.flags & EV_ERROR) - return -1; + error_exit("xnotify_add failed on %s", path); not->paths[not->count] = path; not->fds[not->count++] = fd; @@ -257,7 +257,7 @@ int xnotify_add(struct xnotify *not, int fd, char *path) if (not->max == not->count) error_exit("xnotify_add overflow"); if ((not->fds[i] = inotify_add_watch(not->kq, path, IN_MODIFY))==-1) - return -1; + perror_exit("xnotify_add failed on %s", path); not->fds[i+1] = fd; not->paths[not->count++] = path; diff --git a/toys/posix/tail.c b/toys/posix/tail.c index 89ec70e8..842814c0 100644 --- a/toys/posix/tail.c +++ b/toys/posix/tail.c @@ -6,30 +6,39 @@ * * Deviations from posix: -f waits for pipe/fifo on stdin (nonblock?). -USE_TAIL(NEWTOY(tail, "?fc-n-[-cn]", TOYFLAG_USR|TOYFLAG_BIN)) +USE_TAIL(NEWTOY(tail, "?fFs#=1c-n-[-cn][-fF]", TOYFLAG_USR|TOYFLAG_BIN)) config TAIL bool "tail" default y help - usage: tail [-n|c NUMBER] [-f] [FILE...] + usage: tail [-n|c NUMBER] [-f|F] [-s SECONDS] [FILE...] Copy last lines from files to stdout. If no files listed, copy from stdin. Filename "-" is a synonym for stdin. -n Output the last NUMBER lines (default 10), +X counts from start -c Output the last NUMBER bytes, +NUMBER counts from start - -f Follow FILE(s), waiting for more data to be appended + -f Follow FILE(s) by descriptor, waiting for more data to be appended + -F Follow FILE(s) by filename, waiting for more data, and retrying + -s Used with -F, sleep SECONDS between retries (default 1) */ #define FOR_tail #include "toys.h" -GLOBALS( - long n, c; +struct follow_file { + char *path; + int fd; + dev_t dev; + ino_t ino; +}; +GLOBALS( + long n, c, s; int file_no, last_fd; struct xnotify *not; + struct follow_file *F; ) struct line_list { @@ -123,17 +132,89 @@ static int try_lseek(int fd, long bytes, long lines) return 1; } +static void show_new(int fd, char *path) +{ + int len; + + while ((len = read(fd, toybuf, sizeof(toybuf)))>0) { + if (TT.last_fd != fd) { + TT.last_fd = fd; + xprintf("\n==> %s <==\n", path); + } + xwrite(1, toybuf, len); + } +} + +static void tail_F() +{ + struct stat sb; + int i, old_fd; + + for (;;) { + for (i = 0; i= 0) { + xprintf("tail: file inaccessible: %s\n", TT.F[i].path); + close(old_fd); + TT.F[i].fd = -1; + } + continue; + } + + if (old_fd < 0 || sb.st_dev != TT.F[i].dev || sb.st_ino != TT.F[i].ino) { + if (old_fd >= 0) close(old_fd); + TT.F[i].fd = open(TT.F[i].path, O_RDONLY); + if (TT.F[i].fd == -1) continue; + else { + xprintf("tail: following new file: %s\n", TT.F[i].path); + TT.F[i].dev = sb.st_dev; + TT.F[i].ino = sb.st_ino; + } + } else if (sb.st_size && sb.st_size < lseek(TT.F[i].fd, 0, SEEK_CUR)) { + // If file was truncated, move to start. + xprintf("tail: file truncated: %s\n", TT.F[i].path); + xlseek(TT.F[i].fd, 0, SEEK_SET); + } + + show_new(TT.F[i].fd, TT.F[i].path); + } + + sleep(TT.s); + } +} + +static void tail_f() +{ + char *path; + int fd; + + for (;;) { + fd = xnotify_wait(TT.not, &path); + show_new(fd, path); + } +} + // Called for each file listed on command line, and/or stdin static void do_tail(int fd, char *name) { long bytes = TT.c, lines = TT.n; int linepop = 1; - if (FLAG(f)) { + if (FLAG(f) || FLAG(F)) { char *s = name; + struct stat sb; if (!fd) sprintf(s = toybuf, "/proc/self/fd/%d", fd); - if (xnotify_add(TT.not, fd, s)) perror_exit("-f on '%s' failed", s); + + if (FLAG(f)) xnotify_add(TT.not, fd, s); + if (FLAG(F)) { + if (fstat(fd, &sb) != 0) perror_exit("%s", name); + TT.F[TT.file_no].fd = fd; + TT.F[TT.file_no].path = s; + TT.F[TT.file_no].dev = sb.st_dev; + TT.F[TT.file_no].ino = sb.st_ino; + } } if (TT.file_no++) xputc('\n'); @@ -227,23 +308,13 @@ void tail_main(void) } } - if (FLAG(f)) TT.not = xnotify_init(toys.optc); - loopfiles_rw(args, O_RDONLY|WARN_ONLY|(O_CLOEXEC*!FLAG(f)), 0, do_tail); + if (FLAG(F)) TT.F = xmalloc(toys.optc * sizeof(struct follow_file)); + else if (FLAG(f)) TT.not = xnotify_init(toys.optc); - if (FLAG(f) && TT.file_no) { - for (;;) { - char *path; - int fd = xnotify_wait(TT.not, &path), len; - - // Read new data. - while ((len = read(fd, toybuf, sizeof(toybuf)))>0) { - if (TT.last_fd != fd) { - TT.last_fd = fd; - xprintf("\n==> %s <==\n", path); - } + loopfiles_rw(args, O_RDONLY|WARN_ONLY|(O_CLOEXEC*!(FLAG(f) || FLAG(F))), 0, do_tail); - xwrite(1, toybuf, len); - } - } + if (TT.file_no) { + if (FLAG(F)) tail_F(); + else if (FLAG(f)) tail_f(); } } -- cgit v1.2.3