Add support for following more than one file i.e.
tail -f file1 file2
This commit is contained in:
parent
dba13dd1d3
commit
15a55f7926
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=137225
@ -50,8 +50,17 @@ struct mapinfo {
|
||||
int fd;
|
||||
};
|
||||
|
||||
struct file_info {
|
||||
FILE *fp;
|
||||
char *file_name;
|
||||
struct stat st;
|
||||
};
|
||||
|
||||
typedef struct file_info file_info_t;
|
||||
|
||||
enum STYLE { NOTSET = 0, FBYTES, FLINES, RBYTES, RLINES, REVERSE };
|
||||
|
||||
void follow(file_info_t *, enum STYLE, off_t);
|
||||
void forward(FILE *, enum STYLE, off_t, struct stat *);
|
||||
void reverse(FILE *, enum STYLE, off_t, struct stat *);
|
||||
|
||||
@ -63,5 +72,5 @@ void oerr(void);
|
||||
int mapprint(struct mapinfo *, off_t, off_t);
|
||||
int maparound(struct mapinfo *, off_t);
|
||||
|
||||
extern int Fflag, fflag, rflag, rval;
|
||||
extern int Fflag, fflag, rflag, rval, no_files;
|
||||
extern const char *fname;
|
||||
|
@ -66,6 +66,10 @@ static void rlines(FILE *, off_t, struct stat *);
|
||||
#define USE_KQUEUE 1
|
||||
#define ADD_EVENTS 2
|
||||
|
||||
struct kevent *ev;
|
||||
int action = USE_SLEEP;
|
||||
int kq;
|
||||
|
||||
/*
|
||||
* forward -- display the file, from an offset, forward.
|
||||
*
|
||||
@ -172,95 +176,14 @@ forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp)
|
||||
break;
|
||||
}
|
||||
|
||||
if (fflag) {
|
||||
kq = kqueue();
|
||||
if (kq < 0)
|
||||
err(1, "kqueue");
|
||||
action = ADD_EVENTS;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
while ((ch = getc(fp)) != EOF)
|
||||
if (putchar(ch) == EOF)
|
||||
oerr();
|
||||
if (ferror(fp)) {
|
||||
ierr();
|
||||
return;
|
||||
}
|
||||
(void)fflush(stdout);
|
||||
if (! fflag)
|
||||
break;
|
||||
clearerr(fp);
|
||||
|
||||
switch (action) {
|
||||
case ADD_EVENTS:
|
||||
n = 0;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 0;
|
||||
|
||||
if (Fflag && fileno(fp) != STDIN_FILENO) {
|
||||
EV_SET(&ev[n], fileno(fp), EVFILT_VNODE,
|
||||
EV_ADD | EV_ENABLE | EV_CLEAR,
|
||||
NOTE_DELETE | NOTE_RENAME, 0, 0);
|
||||
n++;
|
||||
}
|
||||
EV_SET(&ev[n], fileno(fp), EVFILT_READ,
|
||||
EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
|
||||
n++;
|
||||
|
||||
if (kevent(kq, ev, n, NULL, 0, &ts) < 0) {
|
||||
action = USE_SLEEP;
|
||||
} else {
|
||||
action = USE_KQUEUE;
|
||||
}
|
||||
break;
|
||||
|
||||
case USE_KQUEUE:
|
||||
ts.tv_sec = 1;
|
||||
ts.tv_nsec = 0;
|
||||
/*
|
||||
* In the -F case we set a timeout to ensure that
|
||||
* we re-stat the file at least once every second.
|
||||
*/
|
||||
n = kevent(kq, NULL, 0, ev, 1, Fflag ? &ts : NULL);
|
||||
if (n < 0)
|
||||
err(1, "kevent");
|
||||
if (n == 0) {
|
||||
/* timeout */
|
||||
break;
|
||||
} else if (ev->filter == EVFILT_READ && ev->data < 0) {
|
||||
/* file shrank, reposition to end */
|
||||
if (fseeko(fp, (off_t)0, SEEK_END) == -1) {
|
||||
ierr();
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case USE_SLEEP:
|
||||
(void) usleep(250000);
|
||||
clearerr(fp);
|
||||
break;
|
||||
}
|
||||
|
||||
if (Fflag && fileno(fp) != STDIN_FILENO) {
|
||||
while (stat(fname, &sb2) != 0)
|
||||
/* file was rotated, wait until it reappears */
|
||||
(void)sleep(1);
|
||||
if (sb2.st_ino != sbp->st_ino ||
|
||||
sb2.st_dev != sbp->st_dev ||
|
||||
sb2.st_nlink == 0) {
|
||||
fp = freopen(fname, "r", fp);
|
||||
if (fp == NULL) {
|
||||
ierr();
|
||||
return;
|
||||
} else {
|
||||
*sbp = sb2;
|
||||
action = ADD_EVENTS;
|
||||
}
|
||||
}
|
||||
}
|
||||
while ((ch = getc(fp)) != EOF)
|
||||
if (putchar(ch) == EOF)
|
||||
oerr();
|
||||
if (ferror(fp)) {
|
||||
ierr();
|
||||
return;
|
||||
}
|
||||
(void)fflush(stdout);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -316,3 +239,152 @@ rlines(fp, off, sbp)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* follow -- display the file, from an offset, forward.
|
||||
*
|
||||
*/
|
||||
|
||||
void
|
||||
show(file_info_t *file)
|
||||
{
|
||||
int ch, first;
|
||||
|
||||
first = 1;
|
||||
while ((ch = getc(file->fp)) != EOF) {
|
||||
if (first && no_files > 1) {
|
||||
(void)printf("\n==> %s <==\n", file->file_name);
|
||||
first = 0;
|
||||
}
|
||||
if (putchar(ch) == EOF)
|
||||
oerr();
|
||||
}
|
||||
(void)fflush(stdout);
|
||||
if (ferror(file->fp)) {
|
||||
file->fp = NULL;
|
||||
ierr();
|
||||
} else
|
||||
clearerr(file->fp);
|
||||
}
|
||||
|
||||
void
|
||||
set_events(file_info_t *files)
|
||||
{
|
||||
int i, n = 0;
|
||||
file_info_t *file;
|
||||
struct timespec ts;
|
||||
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 0;
|
||||
|
||||
action = USE_KQUEUE;
|
||||
for (i = 0, file = files; i < no_files; i++, file++) {
|
||||
if (! file->fp)
|
||||
continue;
|
||||
if (Fflag && fileno(file->fp) != STDIN_FILENO) {
|
||||
EV_SET(&ev[n], fileno(file->fp), EVFILT_VNODE,
|
||||
EV_ADD | EV_ENABLE | EV_CLEAR,
|
||||
NOTE_DELETE | NOTE_RENAME, 0, 0);
|
||||
n++;
|
||||
}
|
||||
EV_SET(&ev[n], fileno(file->fp), EVFILT_READ,
|
||||
EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, 0);
|
||||
n++;
|
||||
}
|
||||
|
||||
if (kevent(kq, ev, n, NULL, 0, &ts) < 0) {
|
||||
action = USE_SLEEP;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
follow(file_info_t *files, enum STYLE style, off_t off)
|
||||
{
|
||||
int active, i, n = -1;
|
||||
struct stat sb2;
|
||||
struct stat *sbp;
|
||||
file_info_t *file;
|
||||
long spin=1;
|
||||
struct timespec ts;
|
||||
|
||||
/* Position each of the files */
|
||||
|
||||
file = files;
|
||||
active = 0;
|
||||
n = 0;
|
||||
for (i = 0; i < no_files; i++, file++) {
|
||||
if (file->fp) {
|
||||
active = 1;
|
||||
n++;
|
||||
if (no_files > 1)
|
||||
(void)printf("\n==> %s <==\n", file->file_name);
|
||||
forward(file->fp, style, off, &file->st);
|
||||
if (Fflag && fileno(file->fp) != STDIN_FILENO)
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (! active)
|
||||
return;
|
||||
|
||||
kq = kqueue();
|
||||
if (kq < 0)
|
||||
err(1, "kqueue");
|
||||
ev = malloc(n * sizeof(struct kevent));
|
||||
if (! ev)
|
||||
err(1, "Couldn't allocate memory for kevents.");
|
||||
set_events(files);
|
||||
|
||||
for (;;) {
|
||||
for (i = 0, file = files; i < no_files; i++, file++) {
|
||||
if (! file->fp)
|
||||
continue;
|
||||
if (Fflag && file->fp && fileno(file->fp) != STDIN_FILENO) {
|
||||
if (stat(file->file_name, &sb2) != 0) {
|
||||
/* file was rotated, skip it until it reappears */
|
||||
continue;
|
||||
}
|
||||
if (sb2.st_ino != file->st.st_ino ||
|
||||
sb2.st_dev != file->st.st_dev ||
|
||||
sb2.st_nlink == 0) {
|
||||
file->fp = freopen(file->file_name, "r", file->fp);
|
||||
if (file->fp == NULL) {
|
||||
ierr();
|
||||
continue;
|
||||
} else {
|
||||
memcpy(&file->st, &sb2, sizeof(struct stat));
|
||||
set_events(files);
|
||||
}
|
||||
}
|
||||
}
|
||||
show(file);
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case USE_KQUEUE:
|
||||
ts.tv_sec = 1;
|
||||
ts.tv_nsec = 0;
|
||||
/*
|
||||
* In the -F case we set a timeout to ensure that
|
||||
* we re-stat the file at least once every second.
|
||||
*/
|
||||
n = kevent(kq, NULL, 0, ev, 1, Fflag ? &ts : NULL);
|
||||
if (n < 0)
|
||||
err(1, "kevent");
|
||||
if (n == 0) {
|
||||
/* timeout */
|
||||
break;
|
||||
} else if (ev->filter == EVFILT_READ && ev->data < 0) {
|
||||
/* file shrank, reposition to end */
|
||||
if (lseek(ev->ident, (off_t)0, SEEK_END) == -1) {
|
||||
ierr();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case USE_SLEEP:
|
||||
(void) usleep(250000);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -60,9 +60,11 @@ static const char sccsid[] = "@(#)tail.c 8.1 (Berkeley) 6/6/93";
|
||||
|
||||
#include "extern.h"
|
||||
|
||||
int Fflag, fflag, rflag, rval;
|
||||
int Fflag, fflag, rflag, rval, no_files;
|
||||
const char *fname;
|
||||
|
||||
file_info_t *files;
|
||||
|
||||
static void obsolete(char **);
|
||||
static void usage(void);
|
||||
|
||||
@ -73,7 +75,8 @@ main(int argc, char *argv[])
|
||||
FILE *fp;
|
||||
off_t off;
|
||||
enum STYLE style;
|
||||
int ch, first;
|
||||
int i, ch, first;
|
||||
file_info_t *file;
|
||||
char *p;
|
||||
|
||||
/*
|
||||
@ -138,8 +141,7 @@ main(int argc, char *argv[])
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (fflag && argc > 1)
|
||||
errx(1, "-f option only appropriate for a single file");
|
||||
no_files = argc ? argc : 1;
|
||||
|
||||
/*
|
||||
* If displaying in reverse, don't permit follow option, and convert
|
||||
@ -168,7 +170,29 @@ main(int argc, char *argv[])
|
||||
}
|
||||
}
|
||||
|
||||
if (*argv)
|
||||
if (*argv && fflag) {
|
||||
files = (struct file_info *) malloc(no_files * sizeof(struct file_info));
|
||||
if (! files)
|
||||
err(1, "Couldn't malloc space for file descriptors.");
|
||||
|
||||
for (file = files; (fname = *argv++); file++) {
|
||||
file->file_name = malloc(strlen(fname)+1);
|
||||
if (! file->file_name)
|
||||
errx(1, "Couldn't malloc space for file name.");
|
||||
strncpy(file->file_name, fname, strlen(fname)+1);
|
||||
if ((file->fp = fopen(file->file_name, "r")) == NULL ||
|
||||
fstat(fileno(file->fp), &file->st)) {
|
||||
file->fp = NULL;
|
||||
ierr();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
follow(files, style, off);
|
||||
for (i = 0, file = files; i < no_files; i++, file++) {
|
||||
free(file->file_name);
|
||||
}
|
||||
free(files);
|
||||
} else if (*argv) {
|
||||
for (first = 1; (fname = *argv++);) {
|
||||
if ((fp = fopen(fname, "r")) == NULL ||
|
||||
fstat(fileno(fp), &sb)) {
|
||||
@ -186,9 +210,8 @@ main(int argc, char *argv[])
|
||||
reverse(fp, style, off, &sb);
|
||||
else
|
||||
forward(fp, style, off, &sb);
|
||||
(void)fclose(fp);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
fname = "stdin";
|
||||
|
||||
if (fstat(fileno(stdin), &sb)) {
|
||||
|
Loading…
Reference in New Issue
Block a user