| /* 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. |
| */ |
| |
| /* |
| * This command is deprecated. The utility is in maintenance mode, |
| * meaning we keep them in source tree for backward compatibility |
| * only. Do not waste time making this command better, unless the |
| * fix is about security or other very critical issue. |
| * |
| * See Documentation/deprecated.txt for more information. |
| */ |
| |
| |
| #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> |
| #include <sys/mman.h> |
| #include <limits.h> |
| |
| #ifdef HAVE_INOTIFY_INIT |
| #include <sys/inotify.h> |
| #endif |
| |
| #include "nls.h" |
| #include "xalloc.h" |
| #include "strutils.h" |
| #include "c.h" |
| #include "closestream.h" |
| |
| #define DEFAULT_LINES 10 |
| |
| /* st->st_size has to be greater than zero and smaller or equal to SIZE_MAX! */ |
| static void tailf(const char *filename, size_t lines, struct stat *st) |
| { |
| int fd; |
| size_t i; |
| char *data; |
| |
| fd = open(filename, O_RDONLY); |
| if (fd < 0) |
| err(EXIT_FAILURE, _("cannot open %s"), filename); |
| data = mmap(0, st->st_size, PROT_READ, MAP_SHARED, fd, 0); |
| i = (size_t) st->st_size - 1; |
| |
| /* humans do not think last new line in a file should be counted, |
| * in that case do off by one from counter point of view */ |
| if (data[i] == '\n') |
| lines++; |
| while (i) { |
| if (data[i] == '\n' && --lines == 0) { |
| i++; |
| break; |
| } |
| i--; |
| } |
| |
| fwrite(data + i, st->st_size - i, 1, stdout); |
| |
| munmap(data, st->st_size); |
| close(fd); |
| fflush(stdout); |
| } |
| |
| static void roll_file(const char *filename, struct stat *old) |
| { |
| 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 of %s failed"), filename); |
| |
| if (st.st_size == old->st_size) { |
| close(fd); |
| return; |
| } |
| |
| if (lseek(fd, old->st_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. |
| */ |
| old->st_size = (pos != -1 && pos != old->st_size) ? pos : st.st_size; |
| |
| close(fd); |
| } |
| |
| static void watch_file(const char *filename, struct stat *old) |
| { |
| do { |
| roll_file(filename, old); |
| xusleep(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, struct stat *old) |
| { |
| 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, old); |
| 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) |
| { |
| fputs(USAGE_HEADER, out); |
| fprintf(out, _(" %s [option] <file>\n"), program_invocation_short_name); |
| |
| fputs(USAGE_SEPARATOR, out); |
| fputs(_("Follow the growth of a log file.\n"), out); |
| |
| fputs(USAGE_OPTIONS, out); |
| fputs(_(" -n, --lines <number> output the last <number> lines\n"), out); |
| fputs(_(" -<number> same as '-n <number>'\n"), out); |
| |
| fputs(USAGE_SEPARATOR, out); |
| fputs(USAGE_HELP, out); |
| fputs(USAGE_VERSION, out); |
| fprintf(out, USAGE_MAN_TAIL("tailf(1)")); |
| fputs(_("Warning: use of 'tailf' is deprecated, use 'tail -f' instead.\n"), out); |
| |
| exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); |
| } |
| |
| /* parses -N option */ |
| static long old_style_option(int *argc, char **argv, size_t *lines) |
| { |
| int i = 1, nargs = *argc, ret = 0; |
| |
| while(i < nargs) { |
| if (argv[i][0] == '-' && isdigit(argv[i][1])) { |
| *lines = strtoul_or_err(argv[i] + 1, |
| _("failed to parse number of lines")); |
| nargs--; |
| ret = 1; |
| if (nargs - i) |
| memmove(argv + i, argv + i + 1, |
| sizeof(char *) * (nargs - i)); |
| } else |
| i++; |
| } |
| *argc = nargs; |
| return ret; |
| } |
| |
| int main(int argc, char **argv) |
| { |
| const char *filename; |
| size_t lines; |
| int ch; |
| struct stat st; |
| |
| static const struct option longopts[] = { |
| { "lines", required_argument, 0, 'n' }, |
| { "version", no_argument, 0, 'V' }, |
| { "help", no_argument, 0, 'h' }, |
| { NULL, 0, 0, 0 } |
| }; |
| |
| setlocale(LC_ALL, ""); |
| bindtextdomain(PACKAGE, LOCALEDIR); |
| textdomain(PACKAGE); |
| atexit(close_stdout); |
| |
| if (!old_style_option(&argc, argv, &lines)) |
| lines = DEFAULT_LINES; |
| |
| while ((ch = getopt_long(argc, argv, "n:N:Vh", longopts, NULL)) != -1) |
| switch ((char)ch) { |
| case 'n': |
| case 'N': |
| lines = strtoul_or_err(optarg, |
| _("failed to parse number of lines")); |
| break; |
| case 'V': |
| printf(UTIL_LINUX_VERSION); |
| 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 of %s failed"), filename); |
| if (!S_ISREG(st.st_mode)) |
| errx(EXIT_FAILURE, _("%s: is not a file"), filename); |
| |
| /* mmap is based on size_t */ |
| if (st.st_size > 0 && (uintmax_t) st.st_size <= (uintmax_t) SIZE_MAX) |
| tailf(filename, lines, &st); |
| |
| #ifdef HAVE_INOTIFY_INIT |
| if (!watch_file_inotify(filename, &st)) |
| #endif |
| watch_file(filename, &st); |
| |
| return EXIT_SUCCESS; |
| } |
| |