summaryrefslogtreecommitdiff
path: root/usr.bin
diff options
context:
space:
mode:
authorLandry Breuil <landry@cvs.openbsd.org>2008-10-17 11:38:21 +0000
committerLandry Breuil <landry@cvs.openbsd.org>2008-10-17 11:38:21 +0000
commit9b4733da35992ec07dab56f95becc8bdcf1bc474 (patch)
tree8d89f401bf46f39ab7cdf5b2ce75586b9b6e738a /usr.bin
parent4fb9aef39732f61018ea5bc215cc514328861680 (diff)
Permit tail -f to follow multiple files, useful when you want to monitor
several logfiles in a single terminal. split forward() and move the corresponding -rewritten- code to follow(). Closes PR 5092. ok millert@ gilles@ sobrado@
Diffstat (limited to 'usr.bin')
-rw-r--r--usr.bin/tail/extern.h3
-rw-r--r--usr.bin/tail/forward.c219
-rw-r--r--usr.bin/tail/tail.c50
3 files changed, 189 insertions, 83 deletions
diff --git a/usr.bin/tail/extern.h b/usr.bin/tail/extern.h
index 5ab91243f51..1cda5b85cfd 100644
--- a/usr.bin/tail/extern.h
+++ b/usr.bin/tail/extern.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: extern.h,v 1.9 2004/02/16 19:48:21 otto Exp $ */
+/* $OpenBSD: extern.h,v 1.10 2008/10/17 11:38:20 landry Exp $ */
/* $NetBSD: extern.h,v 1.3 1994/11/23 07:42:00 jtc Exp $ */
/*-
@@ -40,6 +40,7 @@ enum STYLE { NOTSET = 0, FBYTES, FLINES, RBYTES, RLINES, REVERSE };
void forward(FILE *, enum STYLE, off_t, struct stat *);
void reverse(FILE *, enum STYLE, off_t, struct stat *);
+void follow(char **, int, enum STYLE, off_t);
int bytes(FILE *, off_t);
int lines(FILE *, off_t);
diff --git a/usr.bin/tail/forward.c b/usr.bin/tail/forward.c
index 40e5f324058..e36498d9285 100644
--- a/usr.bin/tail/forward.c
+++ b/usr.bin/tail/forward.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: forward.c,v 1.23 2007/10/22 01:14:26 deraadt Exp $ */
+/* $OpenBSD: forward.c,v 1.24 2008/10/17 11:38:20 landry Exp $ */
/* $NetBSD: forward.c,v 1.7 1996/02/13 16:49:10 ghudson Exp $ */
/*-
@@ -37,7 +37,7 @@
#if 0
static char sccsid[] = "@(#)forward.c 8.1 (Berkeley) 6/6/93";
#endif
-static char rcsid[] = "$OpenBSD: forward.c,v 1.23 2007/10/22 01:14:26 deraadt Exp $";
+static char rcsid[] = "$OpenBSD: forward.c,v 1.24 2008/10/17 11:38:20 landry Exp $";
#endif /* not lint */
#include <sys/types.h>
@@ -48,6 +48,7 @@ static char rcsid[] = "$OpenBSD: forward.c,v 1.23 2007/10/22 01:14:26 deraadt Ex
#include <stdio.h>
#include <string.h>
#include <unistd.h>
+#include <stdlib.h>
#include "extern.h"
@@ -79,9 +80,6 @@ void
forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
{
int ch;
- struct stat nsb;
- int kq, queue;
- struct kevent ke;
switch(style) {
case FBYTES:
@@ -160,83 +158,188 @@ forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
break;
}
+ while (!feof(fp) && (ch = getc(fp)) != EOF)
+ if (putchar(ch) == EOF)
+ oerr();
+ if (ferror(fp)) {
+ ierr();
+ return;
+ }
+ (void)fflush(stdout);
+}
+
+struct file_info {
+ FILE *fp;
+ char *fname;
+ struct stat fst;
+};
+
+/*
+ * follow one or multiple files, i.e don't stop when end-of-file is reached,
+ * but rather wait for additional data to be appended to the input.
+ * this implements -f switch.
+ */
+void
+follow(char **fnames, int nbfiles, enum STYLE style, off_t off)
+{
+ int ch, first, i;
+ int kq;
+ struct kevent ke;
+ struct stat cst;
+ FILE *fp;
+ struct file_info *files;
+
kq = -1;
-kq_retry:
- if (fflag && ((kq = kqueue()) >= 0)) {
+ if ((kq = kqueue()) < 0)
+ err(2, "kqueue() failed");
+
+ if ((files = calloc(nbfiles, sizeof(struct file_info))) == NULL)
+ err(1, "calloc() failed");
+
+ for (first = 1, i = 0; (files[i].fname = *fnames++); i++) {
+ if ((fp = fopen(files[i].fname, "r")) == NULL ||
+ fstat(fileno(fp), &(files[i].fst))) {
+ warn("%s",files[i].fname);
+ nbfiles--;
+ i--;
+ continue;
+ }
+ if (S_ISDIR(files[i].fst.st_mode)) {
+ warnx("%s is a directory, skipping.",files[i].fname);
+ nbfiles--;
+ i--;
+ continue;
+ }
+ files[i].fp = fp;
+ if (nbfiles > 1) {
+ (void)printf("%s==> %s <==\n",
+ first ? "" : "\n", files[i].fname);
+ first = 0;
+ }
+
+ /* print from the given offset to the end */
+ if (off != 0) {
+ if (style == RBYTES) {
+ if (S_ISREG(files[i].fst.st_mode)) {
+ if (files[i].fst.st_size >= off &&
+ fseeko(fp, -off, SEEK_END) == -1) {
+ ierr();
+ goto cleanup;
+ }
+ }
+ if (bytes(fp, off))
+ goto cleanup;
+ } else if (rlines(fp, off, &(files[i].fst)) != 0)
+ lines(fp, off);
+ }
+ (void)fflush(stdout);
+
+ /* one event to see if there is data to read */
+ ke.ident = fileno(fp);
EV_SET(&ke, fileno(fp), EVFILT_READ,
EV_ENABLE | EV_ADD | EV_CLEAR,
- 0,
- 0, NULL);
- if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) {
- close(kq);
- kq = -1;
- } else if (S_ISREG(sbp->st_mode)) {
+ NULL, 0, NULL);
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0)
+ goto cleanup;
+ else if (S_ISREG(files[i].fst.st_mode)) {
+ /* one event to detect if inode changed */
EV_SET(&ke, fileno(fp), EVFILT_VNODE,
EV_ENABLE | EV_ADD | EV_CLEAR,
NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE,
0, NULL);
- if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) {
- close(kq);
- kq = -1;
- }
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0)
+ goto cleanup;
}
}
+ /* no files to read */
+ if (nbfiles == 0)
+ goto cleanup;
+
+ if (fp == NULL)
+ fp = files[nbfiles - 1].fp;
+
for (;;) {
while (!feof(fp) && (ch = getc(fp)) != EOF)
if (putchar(ch) == EOF)
oerr();
- if (ferror(fp)) {
- ierr();
- if (kq != -1)
- close(kq);
- return;
- }
+ if (ferror(fp))
+ goto cleanup;
+
(void)fflush(stdout);
- if (!fflag)
- break;
clearerr(fp);
- queue = 1;
- if (kq < 0 || kevent(kq, NULL, 0, &ke, 1, NULL) <= 0) {
- queue = 0;
+ /* give it a chance to fail.. */
+ if (kevent(kq, NULL, 0, &ke, 1, NULL) <= 0) {
sleep(1);
- } else if (ke.filter == EVFILT_READ) {
continue;
- } else if ((ke.fflags & NOTE_TRUNCATE) == 0) {
- /*
- * File was renamed or deleted.
- *
- * Continue to look at it until a new file reappears
- * with the same name.
- * Fall back to the old algorithm for that.
- */
- close(kq);
- kq = -1;
- }
+ } else {
+ /* an event occured on file #i */
+ for (i = 0 ; i < nbfiles ; i++)
+ if (fileno(files[i].fp) == ke.ident)
+ break;
- if (is_stdin || stat(fname, &nsb) != 0)
- continue;
- /* Reopen file if the inode changes or file was truncated */
- if (nsb.st_ino != sbp->st_ino) {
- warnx("%s has been replaced, reopening.", fname);
- if ((fp = freopen(fname, "r", fp)) == NULL) {
- ierr();
- if (kq >= 0)
- close(kq);
- return;
+ /* EVFILT_READ event, check that it's on the current fp */
+ if (ke.filter == EVFILT_READ) {
+ if (fp != files[i].fp) {
+ (void)printf("\n==> %s <==\n",files[i].fname);
+ fp = files[i].fp;
+ clearerr(fp);
+ }
+ /* EVFILT_VNODE event and File was renamed or deleted */
+ } else if (ke.fflags & (NOTE_DELETE | NOTE_RENAME)) {
+ /* file didn't reappear */
+ if (stat(files[i].fname, &cst) != 0) {
+ warnx("%s has been renamed or deleted.", files[i].fname);
+ if (--nbfiles == 0)
+ goto cleanup;
+ /* overwrite with the latest file_info */
+ fp = files[nbfiles].fp;
+ (void)memcpy(&files[i], &files[nbfiles], sizeof(struct file_info));
+ } else {
+ /* Reopen file if the inode changed */
+ if (cst.st_ino != files[i].fst.st_ino) {
+ warnx("%s has been replaced, reopening.", files[i].fname);
+ if ((fp = freopen(files[i].fname, "r", files[i].fp)) == NULL) {
+ ierr();
+ goto cleanup;
+ }
+ /*
+ * on freopen(), events corresponding to the fp
+ * were deleted from kqueue, we readd them
+ */
+ ke.ident = fileno(fp);
+ EV_SET(&ke, fileno(fp), EVFILT_READ,
+ EV_ENABLE | EV_ADD | EV_CLEAR,
+ NULL, 0, NULL);
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0)
+ goto cleanup;
+ else if (S_ISREG(files[i].fst.st_mode)) {
+ EV_SET(&ke, fileno(fp), EVFILT_VNODE,
+ EV_ENABLE | EV_ADD | EV_CLEAR,
+ NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE,
+ 0, NULL);
+ if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0)
+ goto cleanup;
+ }
+ files[i].fp = fp;
+ }
+ (void)memcpy(&(files[i].fst), &cst, sizeof(cst));
+ }
+ } else if (ke.fflags & NOTE_TRUNCATE) {
+ /* reset file if it was truncated */
+ warnx("%s has been truncated, resetting.", files[i].fname);
+ fpurge(files[i].fp);
+ rewind(files[i].fp);
+ continue;
}
- (void)memcpy(sbp, &nsb, sizeof(nsb));
- goto kq_retry;
- } else if ((queue && (ke.fflags & NOTE_TRUNCATE)) ||
- (!queue && nsb.st_size < sbp->st_size)) {
- warnx("%s has been truncated, resetting.", fname);
- fpurge(fp);
- rewind(fp);
}
- (void)memcpy(sbp, &nsb, sizeof(nsb));
}
+
+cleanup:
if (kq >= 0)
close(kq);
+ free(files);
+ return;
}
/*
diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c
index 2246e7b77db..10d164290cd 100644
--- a/usr.bin/tail/tail.c
+++ b/usr.bin/tail/tail.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: tail.c,v 1.14 2007/10/31 16:29:50 jmc Exp $ */
+/* $OpenBSD: tail.c,v 1.15 2008/10/17 11:38:20 landry Exp $ */
/*-
* Copyright (c) 1991, 1993
@@ -42,7 +42,7 @@ static char copyright[] =
#if 0
static char sccsid[] = "@(#)tail.c 8.1 (Berkeley) 6/6/93";
#endif
-static char rcsid[] = "$OpenBSD: tail.c,v 1.14 2007/10/31 16:29:50 jmc Exp $";
+static char rcsid[] = "$OpenBSD: tail.c,v 1.15 2008/10/17 11:38:20 landry Exp $";
#endif /* not lint */
#include <sys/types.h>
@@ -132,10 +132,6 @@ main(int argc, char *argv[])
}
argc -= optind;
argv += optind;
-
- if (fflag && argc > 1)
- errx(1, "-f option only appropriate for a single file");
-
/*
* If displaying in reverse, don't permit follow option, and convert
* style values.
@@ -162,27 +158,33 @@ main(int argc, char *argv[])
style = RLINES;
}
}
+
+ if (*argv) {
+ if (fflag) {
+ follow(argv, argc, style, off);
+ }
+ else {
+ for (first = 1; (fname = *argv++);) {
+ if ((fp = fopen(fname, "r")) == NULL ||
+ fstat(fileno(fp), &sb)) {
+ ierr();
+ continue;
+ }
+ if (argc > 1) {
+ (void)printf("%s==> %s <==\n",
+ first ? "" : "\n", fname);
+ first = 0;
+ (void)fflush(stdout);
+ }
- if (*argv)
- for (first = 1; (fname = *argv++);) {
- if ((fp = fopen(fname, "r")) == NULL ||
- fstat(fileno(fp), &sb)) {
- ierr();
- continue;
+ if (rflag)
+ reverse(fp, style, off, &sb);
+ else
+ forward(fp, style, off, &sb);
+ (void)fclose(fp);
}
- if (argc > 1) {
- (void)printf("%s==> %s <==\n",
- first ? "" : "\n", fname);
- first = 0;
- (void)fflush(stdout);
- }
-
- if (rflag)
- reverse(fp, style, off, &sb);
- else
- forward(fp, style, off, &sb);
- (void)fclose(fp);
}
+ }
else {
fname = "stdin";
is_stdin = 1;