aboutsummaryrefslogtreecommitdiff
path: root/toys/tail.c
blob: b992cd16d50572d96383de19bd8d83b57f574692 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/* 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);
}