aboutsummaryrefslogtreecommitdiff
path: root/toys/tail.c
blob: 08bae7b6d966c47fd901df62d64567f5212433bc (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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/* 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_HEAD(NEWTOY(tail, "c:fn:", TOYFLAG_BIN))

config TAIL
	bool "tail"
	default n
	help
	  usage: tail [-n number] [-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	Line offset to start copying from. The number may be signed.
	        When negative, the line offset is counted from the end of the 
	        file. When positive, the offset is counted from the beginning.
	        When the sign is omitted, the offset is counted from the end of
		the file. Defaults to -10.

	  -c    Byte offset to start copying from. As above, may be a positive
	        or negative signed number.

	  -f   	Continue reading input after reaching the last line of input.
	        This option is ignored if the input file is not a regular file
	        or the given file is a FIFO.
*/

#include "toys.h"

DEFINE_GLOBALS(
	char *lines_str;
	char *bytes_str;
	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 = xmalloc(sizeof(struct line_list));
		memset(cur, 0, 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++) xprintf("\n");
		xprintf("==> %s <==\n", name);
		xflush();
	}

	if (lines > 0 || bytes > 0) print_after_offset(fd, bytes, lines);
	else if (bytes < 0) print_last_bytes(fd, bytes * -1);
	else if (lines < 0) print_last_lines(fd, lines * -1);
}

long atolx_default_negative(char *str)
{
	long val = atolx(str);
	return str[0] != '+' && str[0] != '-' ? val * -1 : val;
}

void tail_main(void)
{
	// if option -c or -n has no sign then we make it negative
	if (toys.optflags & FLAG_c)
		TT.bytes = atolx_default_negative(TT.bytes_str);

	if (toys.optflags & FLAG_n)
		TT.lines = atolx_default_negative(TT.lines_str);
	else
		TT.lines = -10;

	loopfiles(toys.optargs, do_tail);
}