summaryrefslogtreecommitdiff
path: root/text-utils/tailf.c
diff options
context:
space:
mode:
Diffstat (limited to 'text-utils/tailf.c')
-rw-r--r--text-utils/tailf.c292
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;
+}
+