summaryrefslogtreecommitdiff
path: root/tail.c
diff options
context:
space:
mode:
Diffstat (limited to 'tail.c')
-rw-r--r--tail.c446
1 files changed, 423 insertions, 23 deletions
diff --git a/tail.c b/tail.c
index 697177d..5198892 100644
--- a/tail.c
+++ b/tail.c
@@ -1,3 +1,402 @@
+#include "internal.h"
+/* This file contains _two_ implementations of tail. One is
+ * a bit more full featured, but costs 6k. The other (i.e. the
+ * SIMPLE_TAIL one) is less capable, but is good enough for about
+ * 99% of the things folks want to use tail for, and only costs 2k.
+ */
+
+
+#ifdef BB_FEATURE_SIMPLE_TAIL
+
+/* tail -- output the last part of file(s)
+ Copyright (C) 89, 90, 91, 95, 1996 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ Original version by Paul Rubin <phr@ocf.berkeley.edu>.
+ Extensions by David MacKenzie <djm@gnu.ai.mit.edu>.
+ tail -f for multiple files by Ian Lance Taylor <ian@airs.com>.
+
+ Rewrote the option parser, removed locales support,
+ and generally busyboxed, Erik Andersen <andersen@lineo.com>
+
+ Removed superfluous options and associated code ("-c", "-n", "-q").
+ Removed "tail -f" suport for multiple files.
+ Both changes by Friedrich Vedder <fwv@myrtle.lahn.de>.
+
+ */
+
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+
+#define XWRITE(fd, buffer, n_bytes) \
+ do { \
+ if (n_bytes > 0 && fwrite ((buffer), 1, (n_bytes), stdout) == 0) \
+ error("write error"); \
+ } while (0)
+
+/* Number of items to tail. */
+#define DEFAULT_N_LINES 10
+
+/* Size of atomic reads. */
+#ifndef BUFSIZ
+#define BUFSIZ (512 * 8)
+#endif
+
+/* If nonzero, read from the end of one file until killed. */
+static int forever;
+
+/* If nonzero, print filename headers. */
+static int print_headers;
+
+const char tail_usage[] =
+ "tail [OPTION] [FILE]...\n\n"
+ "Print last 10 lines of each FILE to standard output.\n"
+ "With more than one FILE, precede each with a header giving the\n"
+ "file name. With no FILE, or when FILE is -, read standard input.\n\n"
+ "Options:\n"
+ "\t-n NUM\t\tPrint last NUM lines instead of first 10\n"
+ "\t-f\t\tOutput data as the file grows. This version\n"
+ "\t\t\tof 'tail -f' supports only one file at a time.\n";
+
+
+static void write_header(const char *filename)
+{
+ static int first_file = 1;
+
+ printf("%s==> %s <==\n", (first_file ? "" : "\n"), filename);
+ first_file = 0;
+}
+
+/* Print the last N_LINES lines from the end of file FD.
+ Go backward through the file, reading `BUFSIZ' bytes at a time (except
+ probably the first), until we hit the start of the file or have
+ read NUMBER newlines.
+ POS starts out as the length of the file (the offset of the last
+ byte of the file + 1).
+ Return 0 if successful, 1 if an error occurred. */
+
+static int
+file_lines(const char *filename, int fd, long int n_lines, off_t pos)
+{
+ char buffer[BUFSIZ];
+ int bytes_read;
+ int i; /* Index into `buffer' for scanning. */
+
+ if (n_lines == 0)
+ return 0;
+
+ /* Set `bytes_read' to the size of the last, probably partial, buffer;
+ 0 < `bytes_read' <= `BUFSIZ'. */
+ bytes_read = pos % BUFSIZ;
+ if (bytes_read == 0)
+ bytes_read = BUFSIZ;
+ /* Make `pos' a multiple of `BUFSIZ' (0 if the file is short), so that all
+ reads will be on block boundaries, which might increase efficiency. */
+ pos -= bytes_read;
+ lseek(fd, pos, SEEK_SET);
+ bytes_read = fullRead(fd, buffer, bytes_read);
+ if (bytes_read == -1)
+ error("read error");
+
+ /* Count the incomplete line on files that don't end with a newline. */
+ if (bytes_read && buffer[bytes_read - 1] != '\n')
+ --n_lines;
+
+ do {
+ /* Scan backward, counting the newlines in this bufferfull. */
+ for (i = bytes_read - 1; i >= 0; i--) {
+ /* Have we counted the requested number of newlines yet? */
+ if (buffer[i] == '\n' && n_lines-- == 0) {
+ /* If this newline wasn't the last character in the buffer,
+ print the text after it. */
+ if (i != bytes_read - 1)
+ XWRITE(STDOUT_FILENO, &buffer[i + 1],
+ bytes_read - (i + 1));
+ return 0;
+ }
+ }
+ /* Not enough newlines in that bufferfull. */
+ if (pos == 0) {
+ /* Not enough lines in the file; print the entire file. */
+ lseek(fd, (off_t) 0, SEEK_SET);
+ return 0;
+ }
+ pos -= BUFSIZ;
+ lseek(fd, pos, SEEK_SET);
+ }
+ while ((bytes_read = fullRead(fd, buffer, BUFSIZ)) > 0);
+ if (bytes_read == -1)
+ error("read error");
+
+ return 0;
+}
+
+/* Print the last N_LINES lines from the end of the standard input,
+ open for reading as pipe FD.
+ Buffer the text as a linked list of LBUFFERs, adding them as needed.
+ Return 0 if successful, 1 if an error occured. */
+
+static int pipe_lines(const char *filename, int fd, long int n_lines)
+{
+ struct linebuffer {
+ int nbytes, nlines;
+ char buffer[BUFSIZ];
+ struct linebuffer *next;
+ };
+ typedef struct linebuffer LBUFFER;
+ LBUFFER *first, *last, *tmp;
+ int i; /* Index into buffers. */
+ int total_lines = 0; /* Total number of newlines in all buffers. */
+ int errors = 0;
+
+ first = last = (LBUFFER *) xmalloc(sizeof(LBUFFER));
+ first->nbytes = first->nlines = 0;
+ first->next = NULL;
+ tmp = (LBUFFER *) xmalloc(sizeof(LBUFFER));
+
+ /* Input is always read into a fresh buffer. */
+ while ((tmp->nbytes = fullRead(fd, tmp->buffer, BUFSIZ)) > 0) {
+ tmp->nlines = 0;
+ tmp->next = NULL;
+
+ /* Count the number of newlines just read. */
+ for (i = 0; i < tmp->nbytes; i++)
+ if (tmp->buffer[i] == '\n')
+ ++tmp->nlines;
+ total_lines += tmp->nlines;
+
+ /* If there is enough room in the last buffer read, just append the new
+ one to it. This is because when reading from a pipe, `nbytes' can
+ often be very small. */
+ if (tmp->nbytes + last->nbytes < BUFSIZ) {
+ memcpy(&last->buffer[last->nbytes], tmp->buffer, tmp->nbytes);
+ last->nbytes += tmp->nbytes;
+ last->nlines += tmp->nlines;
+ } else {
+ /* If there's not enough room, link the new buffer onto the end of
+ the list, then either free up the oldest buffer for the next
+ read if that would leave enough lines, or else malloc a new one.
+ Some compaction mechanism is possible but probably not
+ worthwhile. */
+ last = last->next = tmp;
+ if (total_lines - first->nlines > n_lines) {
+ tmp = first;
+ total_lines -= first->nlines;
+ first = first->next;
+ } else
+ tmp = (LBUFFER *) xmalloc(sizeof(LBUFFER));
+ }
+ }
+ if (tmp->nbytes == -1)
+ error("read error");
+
+ free((char *) tmp);
+
+ /* This prevents a core dump when the pipe contains no newlines. */
+ if (n_lines == 0)
+ goto free_lbuffers;
+
+ /* Count the incomplete line on files that don't end with a newline. */
+ if (last->buffer[last->nbytes - 1] != '\n') {
+ ++last->nlines;
+ ++total_lines;
+ }
+
+ /* Run through the list, printing lines. First, skip over unneeded
+ buffers. */
+ for (tmp = first; total_lines - tmp->nlines > n_lines; tmp = tmp->next)
+ total_lines -= tmp->nlines;
+
+ /* Find the correct beginning, then print the rest of the file. */
+ if (total_lines > n_lines) {
+ char *cp;
+
+ /* Skip `total_lines' - `n_lines' newlines. We made sure that
+ `total_lines' - `n_lines' <= `tmp->nlines'. */
+ cp = tmp->buffer;
+ for (i = total_lines - n_lines; i; --i)
+ while (*cp++ != '\n')
+ /* Do nothing. */ ;
+ i = cp - tmp->buffer;
+ } else
+ i = 0;
+ XWRITE(STDOUT_FILENO, &tmp->buffer[i], tmp->nbytes - i);
+
+ for (tmp = tmp->next; tmp; tmp = tmp->next)
+ XWRITE(STDOUT_FILENO, tmp->buffer, tmp->nbytes);
+
+ free_lbuffers:
+ while (first) {
+ tmp = first->next;
+ free((char *) first);
+ first = tmp;
+ }
+ return errors;
+}
+
+/* Display file FILENAME from the current position in FD to the end.
+ If `forever' is nonzero, keep reading from the end of the file
+ until killed. Return the number of bytes read from the file. */
+
+static long dump_remainder(const char *filename, int fd)
+{
+ char buffer[BUFSIZ];
+ int bytes_read;
+ long total;
+
+ total = 0;
+ output:
+ while ((bytes_read = fullRead(fd, buffer, BUFSIZ)) > 0) {
+ XWRITE(STDOUT_FILENO, buffer, bytes_read);
+ total += bytes_read;
+ }
+ if (bytes_read == -1)
+ error("read error");
+ if (forever) {
+ fflush(stdout);
+ sleep(1);
+ goto output;
+ }
+
+ return total;
+}
+
+/* Output the last N_LINES lines of file FILENAME open for reading in FD.
+ Return 0 if successful, 1 if an error occurred. */
+
+static int tail_lines(const char *filename, int fd, long int n_lines)
+{
+ struct stat stats;
+ off_t length;
+
+ if (print_headers)
+ write_header(filename);
+
+ if (fstat(fd, &stats))
+ error("fstat error");
+
+ /* Use file_lines only if FD refers to a regular file with
+ its file pointer positioned at beginning of file. */
+ /* FIXME: adding the lseek conjunct is a kludge.
+ Once there's a reasonable test suite, fix the true culprit:
+ file_lines. file_lines shouldn't presume that the input
+ file pointer is initially positioned to beginning of file. */
+ if (S_ISREG(stats.st_mode)
+ && lseek(fd, (off_t) 0, SEEK_CUR) == (off_t) 0) {
+ length = lseek(fd, (off_t) 0, SEEK_END);
+ if (length != 0 && file_lines(filename, fd, n_lines, length))
+ return 1;
+ dump_remainder(filename, fd);
+ } else
+ return pipe_lines(filename, fd, n_lines);
+
+ return 0;
+}
+
+/* Display the last N_UNITS lines of file FILENAME.
+ "-" for FILENAME means the standard input.
+ Return 0 if successful, 1 if an error occurred. */
+
+static int tail_file(const char *filename, off_t n_units)
+{
+ int fd, errors;
+
+ if (!strcmp(filename, "-")) {
+ filename = "standard input";
+ errors = tail_lines(filename, 0, (long) n_units);
+ } else {
+ /* Not standard input. */
+ fd = open(filename, O_RDONLY);
+ if (fd == -1)
+ error("open error");
+
+ errors = tail_lines(filename, fd, (long) n_units);
+ close(fd);
+ }
+
+ return errors;
+}
+
+extern int tail_main(int argc, char **argv)
+{
+ int exit_status = 0;
+ int n_units = DEFAULT_N_LINES;
+ int n_tmp, i;
+ char opt;
+
+ forever = print_headers = 0;
+
+ /* parse argv[] */
+ for (i = 1; i < argc; i++) {
+ if (argv[i][0] == '-') {
+ opt = argv[i][1];
+ switch (opt) {
+ case 'f':
+ forever = 1;
+ break;
+ case 'n':
+ n_tmp = 0;
+ if (++i < argc)
+ n_tmp = atoi(argv[i]);
+ if (n_tmp < 1)
+ usage(tail_usage);
+ n_units = n_tmp;
+ break;
+ case '-':
+ case 'h':
+ usage(tail_usage);
+ default:
+ fprintf(stderr, "tail: invalid option -- %c\n", opt);
+ usage(tail_usage);
+ }
+ } else {
+ break;
+ }
+ }
+
+ if (i + 1 < argc) {
+ if (forever) {
+ fprintf(stderr,
+ "tail: option -f is invalid with multiple files\n");
+ usage(tail_usage);
+ }
+ print_headers = 1;
+ }
+
+ if (i >= argc) {
+ exit_status |= tail_file("-", n_units);
+ } else {
+ for (; i < argc; i++)
+ exit_status |= tail_file(argv[i], n_units);
+ }
+
+ exit(exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+
+#else
+// Here follows the code for the full featured tail code
+
+
/* tail -- output the last part of file(s)
Copyright (C) 89, 90, 91, 95, 1996 Free Software Foundation, Inc.
@@ -42,7 +441,7 @@
#define NDEBUG 1
-static void error(int i, int errnum, char* fmt, ...)
+static void detailed_error(int i, int errnum, char* fmt, ...)
{
va_list arguments;
@@ -60,7 +459,7 @@ static void error(int i, int errnum, char* fmt, ...)
assert ((fd) == 1); \
assert ((n_bytes) >= 0); \
if (n_bytes > 0 && fwrite ((buffer), 1, (n_bytes), stdout) == 0) \
- error (EXIT_FAILURE, errno, "write error"); \
+ detailed_error (EXIT_FAILURE, errno, "write error"); \
} \
while (0)
@@ -100,8 +499,6 @@ enum header_mode
multiple_files, always, never
};
-char *xmalloc ();
-
/* The name this program was run with. */
char *program_name;
@@ -168,7 +565,7 @@ file_lines (const char *filename, int fd, long int n_lines, off_t pos)
bytes_read = fullRead (fd, buffer, bytes_read);
if (bytes_read == -1)
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
return 1;
}
@@ -204,7 +601,7 @@ file_lines (const char *filename, int fd, long int n_lines, off_t pos)
while ((bytes_read = fullRead (fd, buffer, BUFSIZ)) > 0);
if (bytes_read == -1)
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
return 1;
}
return 0;
@@ -276,7 +673,7 @@ pipe_lines (const char *filename, int fd, long int n_lines)
}
if (tmp->nbytes == -1)
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
errors = 1;
free ((char *) tmp);
goto free_lbuffers;
@@ -390,7 +787,7 @@ pipe_bytes (const char *filename, int fd, off_t n_bytes)
}
if (tmp->nbytes == -1)
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
errors = 1;
free ((char *) tmp);
goto free_cbuffers;
@@ -438,7 +835,7 @@ start_bytes (const char *filename, int fd, off_t n_bytes)
n_bytes -= bytes_read;
if (bytes_read == -1)
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
return 1;
}
else if (n_bytes < 0)
@@ -466,7 +863,7 @@ start_lines (const char *filename, int fd, long int n_lines)
}
if (bytes_read == -1)
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
return 1;
}
else if (bytes_to_skip < bytes_read)
@@ -496,7 +893,7 @@ output:
total += bytes_read;
}
if (bytes_read == -1)
- error (EXIT_FAILURE, errno, "%s", filename);
+ detailed_error (EXIT_FAILURE, errno, "%s", filename);
if (forever)
{
fflush (stdout);
@@ -540,7 +937,7 @@ tail_forever (char **names, int nfiles)
continue;
if (fstat (file_descs[i], &stats) < 0)
{
- error (0, errno, "%s", names[i]);
+ detailed_error (0, errno, "%s", names[i]);
file_descs[i] = -1;
continue;
}
@@ -590,7 +987,7 @@ tail_bytes (const char *filename, int fd, off_t n_bytes)
error, either. */
if (fstat (fd, &stats))
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
return 1;
}
@@ -619,7 +1016,7 @@ tail_bytes (const char *filename, int fd, off_t n_bytes)
}
else
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
return 1;
}
@@ -656,7 +1053,7 @@ tail_lines (const char *filename, int fd, long int n_lines)
if (fstat (fd, &stats))
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
return 1;
}
@@ -723,12 +1120,12 @@ tail_file (const char *filename, off_t n_units, int filenum)
{
if (fstat (0, &stats) < 0)
{
- error (0, errno, "standard input");
+ detailed_error (0, errno, "standard input");
errors = 1;
}
else if (!S_ISREG (stats.st_mode))
{
- error (0, 0,
+ detailed_error (0, 0,
"standard input: cannot follow end of non-regular file");
errors = 1;
}
@@ -749,7 +1146,7 @@ tail_file (const char *filename, off_t n_units, int filenum)
{
if (forever_multiple)
file_descs[filenum] = -1;
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
errors = 1;
}
else
@@ -761,12 +1158,12 @@ tail_file (const char *filename, off_t n_units, int filenum)
{
if (fstat (fd, &stats) < 0)
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
errors = 1;
}
else if (!S_ISREG (stats.st_mode))
{
- error (0, 0, "%s: cannot follow end of non-regular file",
+ detailed_error (0, 0, "%s: cannot follow end of non-regular file",
filename);
errors = 1;
}
@@ -785,7 +1182,7 @@ tail_file (const char *filename, off_t n_units, int filenum)
{
if (close (fd))
{
- error (0, errno, "%s", filename);
+ detailed_error (0, errno, "%s", filename);
errors = 1;
}
}
@@ -903,8 +1300,11 @@ tail_main (int argc, char **argv)
}
if (have_read_stdin && close (0) < 0)
- error (EXIT_FAILURE, errno, "-");
+ detailed_error (EXIT_FAILURE, errno, "-");
if (fclose (stdout) == EOF)
- error (EXIT_FAILURE, errno, "write error");
+ detailed_error (EXIT_FAILURE, errno, "write error");
exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
+
+
+#endif