blob: 75998ce7353b3aeca4c34360873a8612920583b2 [file] [log] [blame]
/* 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 <malloc.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <err.h>
#ifdef HAVE_INOTIFY_INIT
#include <sys/inotify.h>
#endif
#include "nls.h"
#include "usleep.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\" for read"), filename);
buf = malloc(lines * 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;
if (!(fd = open(filename, O_RDONLY)))
err(EXIT_FAILURE, _("cannot open \"%s\" for read"), filename);
if (fstat(fd, &st) == -1)
err(EXIT_FAILURE, _("cannot stat \"%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);
}
close(fd);
*size = st.st_size;
}
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 */
int main(int argc, char **argv)
{
const char *filename;
int lines = DEFAULT_LINES;
struct stat st;
off_t size = 0;
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
argc--;
argv++;
for (; argc > 0 && argv[0][0] == '-'; argc--, argv++) {
if (!strcmp(*argv, "-n") || !strcmp(*argv, "--lines")) {
argc--; argv++;
if (argc > 0 && (lines = atoi(argv[0])) <= 0)
errx(EXIT_FAILURE, _("invalid number of lines"));
}
else if (isdigit(argv[0][1])) {
if ((lines = atoi(*argv + 1)) <= 0)
errx(EXIT_FAILURE, _("invalid number of lines"));
}
else
errx(EXIT_FAILURE, _("invalid option"));
}
if (argc != 1)
errx(EXIT_FAILURE, _("usage: tailf [-n N | -N] logfile"));
filename = argv[0];
if (stat(filename, &st) != 0)
err(EXIT_FAILURE, _("cannot stat \"%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;
}