blob: c111d02c0ebd0f72efe7a9a230dcc78fb686f17e [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.
*/
/*
* This command is deprecated. The utility is in maintenance mode,
* meaning we keep them in source tree for backward compatibility
* only. Do not waste time making this command better, unless the
* fix is about security or other very critical issue.
*
* See Documentation/deprecated.txt for more information.
*/
#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>
#include <sys/mman.h>
#include <limits.h>
#ifdef HAVE_INOTIFY_INIT
#include <sys/inotify.h>
#endif
#include "nls.h"
#include "xalloc.h"
#include "strutils.h"
#include "c.h"
#include "closestream.h"
#define DEFAULT_LINES 10
/* st->st_size has to be greater than zero and smaller or equal to SIZE_MAX! */
static void tailf(const char *filename, size_t lines, struct stat *st)
{
int fd;
size_t i;
char *data;
fd = open(filename, O_RDONLY);
if (fd < 0)
err(EXIT_FAILURE, _("cannot open %s"), filename);
data = mmap(0, st->st_size, PROT_READ, MAP_SHARED, fd, 0);
i = (size_t) st->st_size - 1;
/* humans do not think last new line in a file should be counted,
* in that case do off by one from counter point of view */
if (data[i] == '\n')
lines++;
while (i) {
if (data[i] == '\n' && --lines == 0) {
i++;
break;
}
i--;
}
fwrite(data + i, st->st_size - i, 1, stdout);
munmap(data, st->st_size);
close(fd);
fflush(stdout);
}
static void roll_file(const char *filename, struct stat *old)
{
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 of %s failed"), filename);
if (st.st_size == old->st_size) {
close(fd);
return;
}
if (lseek(fd, old->st_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.
*/
old->st_size = (pos != -1 && pos != old->st_size) ? pos : st.st_size;
close(fd);
}
static void watch_file(const char *filename, struct stat *old)
{
do {
roll_file(filename, old);
xusleep(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, struct stat *old)
{
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, old);
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)
{
fputs(USAGE_HEADER, out);
fprintf(out, _(" %s [option] <file>\n"), program_invocation_short_name);
fputs(USAGE_SEPARATOR, out);
fputs(_("Follow the growth of a log file.\n"), out);
fputs(USAGE_OPTIONS, out);
fputs(_(" -n, --lines <number> output the last <number> lines\n"), out);
fputs(_(" -<number> same as '-n <number>'\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(USAGE_HELP, out);
fputs(USAGE_VERSION, out);
fprintf(out, USAGE_MAN_TAIL("tailf(1)"));
fputs(_("Warning: use of 'tailf' is deprecated, use 'tail -f' instead.\n"), out);
exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
}
/* parses -N option */
static long old_style_option(int *argc, char **argv, size_t *lines)
{
int i = 1, nargs = *argc, ret = 0;
while(i < nargs) {
if (argv[i][0] == '-' && isdigit(argv[i][1])) {
*lines = strtoul_or_err(argv[i] + 1,
_("failed to parse number of lines"));
nargs--;
ret = 1;
if (nargs - i)
memmove(argv + i, argv + i + 1,
sizeof(char *) * (nargs - i));
} else
i++;
}
*argc = nargs;
return ret;
}
int main(int argc, char **argv)
{
const char *filename;
size_t lines;
int ch;
struct stat st;
static const struct option longopts[] = {
{ "lines", required_argument, 0, 'n' },
{ "version", no_argument, 0, 'V' },
{ "help", no_argument, 0, 'h' },
{ NULL, 0, 0, 0 }
};
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
atexit(close_stdout);
if (!old_style_option(&argc, argv, &lines))
lines = DEFAULT_LINES;
while ((ch = getopt_long(argc, argv, "n:N:Vh", longopts, NULL)) != -1)
switch ((char)ch) {
case 'n':
case 'N':
lines = strtoul_or_err(optarg,
_("failed to parse number of lines"));
break;
case 'V':
printf(UTIL_LINUX_VERSION);
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 of %s failed"), filename);
if (!S_ISREG(st.st_mode))
errx(EXIT_FAILURE, _("%s: is not a file"), filename);
/* mmap is based on size_t */
if (st.st_size > 0 && (uintmax_t) st.st_size <= (uintmax_t) SIZE_MAX)
tailf(filename, lines, &st);
#ifdef HAVE_INOTIFY_INIT
if (!watch_file_inotify(filename, &st))
#endif
watch_file(filename, &st);
return EXIT_SUCCESS;
}