blob: b992cd16d50572d96383de19bd8d83b57f574692 [file] [log] [blame]
/* vi: set sw=4 ts=4:
*
* tail.c - copy last lines from input to stdout.
*
* Copyright 2012 Timothy Elliott <tle@holymonkey.com>
*
* See http://www.opengroup.org/onlinepubs/009695399/utilities/tail.html
USE_TAIL(NEWTOY(tail, "c-|fn-|", TOYFLAG_BIN))
config TAIL
bool "tail"
default n
help
usage: tail [-n|c number] [-f] [file...]
Copy last lines from files to stdout. If no files listed, copy from
stdin. Filename "-" is a synonym for stdin.
-n output the last X lines (default 10), +X counts from start.
-c output the last X bytes, +X counts from start
-f follow file, waiting for more data to be appended
*/
#include "toys.h"
DEFINE_GLOBALS(
long lines;
long bytes;
int file_no;
)
#define TT this.tail
#define FLAG_n 1
#define FLAG_f 2
#define FLAG_c 4
struct line_list {
struct line_list *next;
char *data;
ssize_t len;
long lines;
};
static void print_after_offset(int fd, long bytes, long lines)
{
ssize_t read_len;
long size=sizeof(toybuf);
char c;
while (bytes > 0 || lines > 0) {
if (1>read(fd, &c, 1)) break;
bytes--;
if (c == '\n') lines--;
}
for (;;) {
read_len = xread(fd, toybuf, size);
if (read_len<1) break;
xwrite(1, toybuf, read_len);
}
}
static void print_last_bytes(int fd, long bytes)
{
char *buf1, *buf2, *temp;
ssize_t read_len;
buf1 = xmalloc(bytes);
buf2 = xmalloc(bytes);
for(;;) {
// swap buf1 and buf2
temp = buf1;
buf1 = buf2;
buf2 = temp;
read_len = readall(fd, buf2, bytes);
if (read_len<bytes) break;
}
// output part of buf1 and all of buf2
xwrite(1, buf1 + read_len, bytes - read_len);
xwrite(1, buf2, read_len);
if (CFG_TOYBOX_FREE) {
free(buf1);
free(buf2);
}
}
static void free_line(void *data)
{
struct line_list *list = (struct line_list *)data;
free(list->data);
free(list);
}
static void llist_add(struct line_list **head, struct line_list *new)
{
struct line_list *cur = *head;
new->next = NULL;
if (cur) {
while (cur->next)
cur = cur->next;
cur->next = new;
} else *head = new;
}
static void print_last_lines(int fd, long lines)
{
ssize_t i, total=0;
long size=sizeof(toybuf);
struct line_list *buf=NULL, *cur;
for (;;) {
// read from input and append to buffer list
cur = xzalloc(sizeof(struct line_list));
cur->data = xmalloc(size);
cur->len = readall(fd, cur->data, size);
llist_add(&buf, cur);
// count newlines in latest input
for (i=0; i<cur->len; i++)
if (cur->data[i] == '\n') cur->lines++;
total += cur->lines;
// release first buffers if it leaves us enough newlines
while (total - buf->lines > lines) {
total -= buf->lines;
free_line(llist_pop(&buf));
}
if (cur->len < size) break;
}
// if last buffer doesn't end in a newline, pretend like it did
if (cur->data[cur->len - 1]!='\n') total++;
// print out part of the first buffer
i = 0;
while (total>lines)
if (buf->data[i++]=='\n') total--;
xwrite(1, buf->data + i, buf->len - i);
// print remaining buffers in their entirety
for (cur=buf->next; cur; cur=cur->next)
xwrite(1, cur->data, cur->len);
if (CFG_TOYBOX_FREE) llist_free(buf, free_line);
}
static void do_tail(int fd, char *name)
{
long lines=TT.lines, bytes=TT.bytes;
if (toys.optc > 1) {
// print an extra newline for all but the first file
if (TT.file_no++) xputc('\n');
xprintf("==> %s <==\n", name);
}
if (lines > 0 || bytes > 0) print_after_offset(fd, bytes, lines);
else if (bytes < 0) print_last_bytes(fd, bytes * -1);
else print_last_lines(fd, lines * -1);
}
void tail_main(void)
{
// if nothing specified, default -n to -10
if (!(toys.optflags&(FLAG_n|FLAG_c))) TT.lines = -10;
loopfiles(toys.optargs, do_tail);
}