version 1.24, 2008/10/17 11:38:20 |
version 1.25, 2008/11/13 18:33:03 |
|
|
#include <stdio.h> |
#include <stdio.h> |
#include <string.h> |
#include <string.h> |
#include <unistd.h> |
#include <unistd.h> |
#include <stdlib.h> |
|
|
|
#include "extern.h" |
#include "extern.h" |
|
|
|
|
forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp) |
forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp) |
{ |
{ |
int ch; |
int ch; |
|
struct stat nsb; |
|
int kq, queue; |
|
struct kevent ke; |
|
|
switch(style) { |
switch(style) { |
case FBYTES: |
case FBYTES: |
|
|
break; |
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 = -1; |
if ((kq = kqueue()) < 0) |
kq_retry: |
err(2, "kqueue() failed"); |
if (fflag && ((kq = kqueue()) >= 0)) { |
|
|
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_SET(&ke, fileno(fp), EVFILT_READ, |
EV_ENABLE | EV_ADD | EV_CLEAR, |
EV_ENABLE | EV_ADD | EV_CLEAR, |
NULL, 0, NULL); |
0, |
if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) |
0, NULL); |
goto cleanup; |
if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) { |
else if (S_ISREG(files[i].fst.st_mode)) { |
close(kq); |
/* one event to detect if inode changed */ |
kq = -1; |
|
} else if (S_ISREG(sbp->st_mode)) { |
EV_SET(&ke, fileno(fp), EVFILT_VNODE, |
EV_SET(&ke, fileno(fp), EVFILT_VNODE, |
EV_ENABLE | EV_ADD | EV_CLEAR, |
EV_ENABLE | EV_ADD | EV_CLEAR, |
NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE, |
NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE, |
0, NULL); |
0, NULL); |
if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) |
if (kevent(kq, &ke, 1, NULL, 0, NULL) < 0) { |
goto cleanup; |
close(kq); |
|
kq = -1; |
|
} |
} |
} |
} |
} |
|
|
/* no files to read */ |
|
if (nbfiles == 0) |
|
goto cleanup; |
|
|
|
if (fp == NULL) |
|
fp = files[nbfiles - 1].fp; |
|
|
|
for (;;) { |
for (;;) { |
while (!feof(fp) && (ch = getc(fp)) != EOF) |
while (!feof(fp) && (ch = getc(fp)) != EOF) |
if (putchar(ch) == EOF) |
if (putchar(ch) == EOF) |
oerr(); |
oerr(); |
if (ferror(fp)) |
if (ferror(fp)) { |
goto cleanup; |
ierr(); |
|
if (kq != -1) |
|
close(kq); |
|
return; |
|
} |
(void)fflush(stdout); |
(void)fflush(stdout); |
|
if (!fflag) |
|
break; |
clearerr(fp); |
clearerr(fp); |
/* give it a chance to fail.. */ |
queue = 1; |
if (kevent(kq, NULL, 0, &ke, 1, NULL) <= 0) { |
if (kq < 0 || kevent(kq, NULL, 0, &ke, 1, NULL) <= 0) { |
|
queue = 0; |
sleep(1); |
sleep(1); |
|
} else if (ke.filter == EVFILT_READ) { |
continue; |
continue; |
} else { |
} else if ((ke.fflags & NOTE_TRUNCATE) == 0) { |
/* an event occured on file #i */ |
/* |
for (i = 0 ; i < nbfiles ; i++) |
* File was renamed or deleted. |
if (fileno(files[i].fp) == ke.ident) |
* |
break; |
* 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; |
|
} |
|
|
/* EVFILT_READ event, check that it's on the current fp */ |
if (is_stdin || stat(fname, &nsb) != 0) |
if (ke.filter == EVFILT_READ) { |
continue; |
if (fp != files[i].fp) { |
/* Reopen file if the inode changes or file was truncated */ |
(void)printf("\n==> %s <==\n",files[i].fname); |
if (nsb.st_ino != sbp->st_ino) { |
fp = files[i].fp; |
warnx("%s has been replaced, reopening.", fname); |
clearerr(fp); |
if ((fp = freopen(fname, "r", fp)) == NULL) { |
} |
ierr(); |
/* EVFILT_VNODE event and File was renamed or deleted */ |
if (kq >= 0) |
} else if (ke.fflags & (NOTE_DELETE | NOTE_RENAME)) { |
close(kq); |
/* file didn't reappear */ |
return; |
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) |
if (kq >= 0) |
close(kq); |
close(kq); |
free(files); |
|
return; |
|
} |
} |
|
|
/* |
/* |