diff options
Diffstat (limited to 'text-utils/tailf.c')
-rw-r--r-- | text-utils/tailf.c | 292 |
1 files changed, 292 insertions, 0 deletions
diff --git a/text-utils/tailf.c b/text-utils/tailf.c new file mode 100644 index 0000000..9571645 --- /dev/null +++ b/text-utils/tailf.c @@ -0,0 +1,292 @@ +/* tailf.c -- tail a log file and then follow it + * Created: Tue Jan 9 15:49:21 1996 by faith@acm.org + * Copyright 1996, 2003 Rickard E. Faith (faith@acm.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * less -F and tail -f cause a disk access every five seconds. This + * program avoids this problem by waiting for the file size to change. + * Hence, the file is not accessed, and the access time does not need to be + * flushed back to disk. This is sort of a "stealth" tail. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <ctype.h> +#include <errno.h> +#include <getopt.h> +#ifdef HAVE_INOTIFY_INIT +#include <sys/inotify.h> +#endif + +#include "nls.h" +#include "xalloc.h" +#include "usleep.h" +#include "strutils.h" +#include "c.h" +#include "closestream.h" + +#define DEFAULT_LINES 10 + +static void +tailf(const char *filename, int lines) +{ + char *buf, *p; + int head = 0; + int tail = 0; + FILE *str; + int i; + + if (!(str = fopen(filename, "r"))) + err(EXIT_FAILURE, _("cannot open %s"), filename); + + buf = xmalloc((lines ? lines : 1) * BUFSIZ); + p = buf; + while (fgets(p, BUFSIZ, str)) { + if (++tail >= lines) { + tail = 0; + head = 1; + } + p = buf + (tail * BUFSIZ); + } + + if (head) { + for (i = tail; i < lines; i++) + fputs(buf + (i * BUFSIZ), stdout); + for (i = 0; i < tail; i++) + fputs(buf + (i * BUFSIZ), stdout); + } else { + for (i = head; i < tail; i++) + fputs(buf + (i * BUFSIZ), stdout); + } + + fflush(stdout); + free(buf); + fclose(str); +} + +static void +roll_file(const char *filename, off_t *size) +{ + char buf[BUFSIZ]; + int fd; + struct stat st; + off_t pos; + + fd = open(filename, O_RDONLY); + if (fd < 0) + err(EXIT_FAILURE, _("cannot open %s"), filename); + + if (fstat(fd, &st) == -1) + err(EXIT_FAILURE, _("stat failed %s"), filename); + + if (st.st_size == *size) { + close(fd); + return; + } + + if (lseek(fd, *size, SEEK_SET) != (off_t)-1) { + ssize_t rc, wc; + + while ((rc = read(fd, buf, sizeof(buf))) > 0) { + wc = write(STDOUT_FILENO, buf, rc); + if (rc != wc) + warnx(_("incomplete write to \"%s\" (written %zd, expected %zd)\n"), + filename, wc, rc); + } + fflush(stdout); + } + + pos = lseek(fd, 0, SEEK_CUR); + + /* If we've successfully read something, use the file position, this + * avoids data duplication. If we read nothing or hit an error, reset + * to the reported size, this handles truncated files. + */ + *size = (pos != -1 && pos != *size) ? pos : st.st_size; + + close(fd); +} + +static void +watch_file(const char *filename, off_t *size) +{ + do { + roll_file(filename, size); + usleep(250000); + } while(1); +} + + +#ifdef HAVE_INOTIFY_INIT + +#define EVENTS (IN_MODIFY|IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT) +#define NEVENTS 4 + +static int +watch_file_inotify(const char *filename, off_t *size) +{ + char buf[ NEVENTS * sizeof(struct inotify_event) ]; + int fd, ffd, e; + ssize_t len; + + fd = inotify_init(); + if (fd == -1) + return 0; + + ffd = inotify_add_watch(fd, filename, EVENTS); + if (ffd == -1) { + if (errno == ENOSPC) + errx(EXIT_FAILURE, _("%s: cannot add inotify watch " + "(limit of inotify watches was reached)."), + filename); + + err(EXIT_FAILURE, _("%s: cannot add inotify watch."), filename); + } + + while (ffd >= 0) { + len = read(fd, buf, sizeof(buf)); + if (len < 0 && (errno == EINTR || errno == EAGAIN)) + continue; + if (len < 0) + err(EXIT_FAILURE, + _("%s: cannot read inotify events"), filename); + + for (e = 0; e < len; ) { + struct inotify_event *ev = (struct inotify_event *) &buf[e]; + + if (ev->mask & IN_MODIFY) + roll_file(filename, size); + else { + close(ffd); + ffd = -1; + break; + } + e += sizeof(struct inotify_event) + ev->len; + } + } + close(fd); + return 1; +} + +#endif /* HAVE_INOTIFY_INIT */ + +static void __attribute__ ((__noreturn__)) usage(FILE *out) +{ + fprintf(out, + _("\nUsage:\n" + " %s [option] file\n"), + program_invocation_short_name); + + fprintf(out, _( + "\nOptions:\n" + " -n, --lines NUMBER output the last NUMBER lines\n" + " -NUMBER same as `-n NUMBER'\n" + " -V, --version output version information and exit\n" + " -h, --help display this help and exit\n\n")); + + exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); +} + +/* parses -N option */ +static long old_style_option(int *argc, char **argv) +{ + int i = 1, nargs = *argc; + long lines = -1; + + while(i < nargs) { + if (argv[i][0] == '-' && isdigit(argv[i][1])) { + lines = strtol_or_err(argv[i] + 1, + _("failed to parse number of lines")); + nargs--; + if (nargs - i) + memmove(argv + i, argv + i + 1, + sizeof(char *) * (nargs - i)); + } else + i++; + } + *argc = nargs; + return lines; +} + +int main(int argc, char **argv) +{ + const char *filename; + long lines; + int ch; + struct stat st; + off_t size = 0; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + atexit(close_stdout); + + static const struct option longopts[] = { + { "lines", required_argument, 0, 'n' }, + { "version", no_argument, 0, 'V' }, + { "help", no_argument, 0, 'h' }, + { NULL, 0, 0, 0 } + }; + + lines = old_style_option(&argc, argv); + if (lines < 0) + lines = DEFAULT_LINES; + + while ((ch = getopt_long(argc, argv, "n:N:Vh", longopts, NULL)) != -1) + switch((char)ch) { + case 'n': + case 'N': + lines = strtol_or_err(optarg, + _("failed to parse number of lines")); + break; + case 'V': + printf(_("%s from %s\n"), program_invocation_short_name, + PACKAGE_STRING); + exit(EXIT_SUCCESS); + case 'h': + usage(stdout); + default: + usage(stderr); + } + + if (argc == optind) + errx(EXIT_FAILURE, _("no input file specified")); + + filename = argv[optind]; + + if (stat(filename, &st) != 0) + err(EXIT_FAILURE, _("stat failed %s"), filename); + + size = st.st_size;; + tailf(filename, lines); + +#ifdef HAVE_INOTIFY_INIT + if (!watch_file_inotify(filename, &size)) +#endif + watch_file(filename, &size); + + return EXIT_SUCCESS; +} + |