1 /* tail.c - copy last lines from input to stdout.
2 *
3 * Copyright 2012 Timothy Elliott <tle@holymonkey.com>
4 *
5 * See http://opengroup.org/onlinepubs/9699919799/utilities/tail.html
6
7 USE_TAIL(NEWTOY(tail, "?fc-n-[-cn]", TOYFLAG_USR|TOYFLAG_BIN))
8
9 config TAIL
10 bool "tail"
11 default y
12 help
13 usage: tail [-n|c NUMBER] [-f] [FILE...]
14
15 Copy last lines from files to stdout. If no files listed, copy from
16 stdin. Filename "-" is a synonym for stdin.
17
18 -n output the last NUMBER lines (default 10), +X counts from start.
19 -c output the last NUMBER bytes, +NUMBER counts from start
20 #-f follow FILE(s), waiting for more data to be appended [TODO]
21
22 config TAIL_SEEK
23 bool "tail seek support"
24 default y
25 depends on TAIL
26 help
27 This version uses lseek, which is faster on large files.
28 */
29
30 #define FOR_tail
31 #include "toys.h"
32
33 GLOBALS(
34 long lines;
35 long bytes;
36
37 int file_no;
38 )
39
40 struct line_list {
41 struct line_list *next, *prev;
42 char *data;
43 int len;
44 };
45
get_chunk(int fd,int len)46 static struct line_list *get_chunk(int fd, int len)
47 {
48 struct line_list *line = xmalloc(sizeof(struct line_list)+len);
49
50 memset(line, 0, sizeof(struct line_list));
51 line->data = ((char *)line) + sizeof(struct line_list);
52 line->len = readall(fd, line->data, len);
53
54 if (line->len < 1) {
55 free(line);
56 return 0;
57 }
58
59 return line;
60 }
61
dump_chunk(void * ptr)62 static void dump_chunk(void *ptr)
63 {
64 struct line_list *list = ptr;
65
66 xwrite(1, list->data, list->len);
67 free(list);
68 }
69
70 // Reading through very large files is slow. Using lseek can speed things
71 // up a lot, but isn't applicable to all input (cat | tail).
72 // Note: bytes and lines are negative here.
try_lseek(int fd,long bytes,long lines)73 static int try_lseek(int fd, long bytes, long lines)
74 {
75 struct line_list *list = 0, *temp;
76 int flag = 0, chunk = sizeof(toybuf);
77 ssize_t pos = lseek(fd, 0, SEEK_END);
78
79 // If lseek() doesn't work on this stream, return now.
80 if (pos<0) return 0;
81
82 // Seek to the right spot, output data from there.
83 if (bytes) {
84 if (lseek(fd, bytes, SEEK_END)<0) lseek(fd, 0, SEEK_SET);
85 xsendfile(fd, 1);
86 return 1;
87 }
88
89 // Read from end to find enough lines, then output them.
90
91 bytes = pos;
92 while (lines && pos) {
93 int offset;
94
95 // Read in next chunk from end of file
96 if (chunk>pos) chunk = pos;
97 pos -= chunk;
98 if (pos != lseek(fd, pos, SEEK_SET)) {
99 perror_msg("seek failed");
100 break;
101 }
102 if (!(temp = get_chunk(fd, chunk))) break;
103 temp->next = list;
104 list = temp;
105
106 // Count newlines in this chunk.
107 offset = list->len;
108 while (offset--) {
109 // If the last line ends with a newline, that one doesn't count.
110 if (!flag) flag++;
111
112 // Start outputting data right after newline
113 else if (list->data[offset] == '\n' && !++lines) {
114 offset++;
115 list->data += offset;
116 list->len -= offset;
117
118 break;
119 }
120 }
121 }
122
123 // Output stored data
124 llist_traverse(list, dump_chunk);
125
126 // In case of -f
127 lseek(fd, bytes, SEEK_SET);
128 return 1;
129 }
130
131 // Called for each file listed on command line, and/or stdin
do_tail(int fd,char * name)132 static void do_tail(int fd, char *name)
133 {
134 long bytes = TT.bytes, lines = TT.lines;
135 int linepop = 1;
136
137 if (toys.optc > 1) {
138 if (TT.file_no++) xputc('\n');
139 xprintf("==> %s <==\n", name);
140 }
141
142 // Are we measuring from the end of the file?
143
144 if (bytes<0 || lines<0) {
145 struct line_list *list = 0, *new;
146
147 // The slow codepath is always needed, and can handle all input,
148 // so make lseek support optional.
149 if (CFG_TAIL_SEEK && try_lseek(fd, bytes, lines)) return;
150
151 // Read data until we run out, keep a trailing buffer
152 for (;;) {
153 // Read next page of data, appending to linked list in order
154 if (!(new = get_chunk(fd, sizeof(toybuf)))) break;
155 dlist_add_nomalloc((void *)&list, (void *)new);
156
157 // If tracing bytes, add until we have enough, discarding overflow.
158 if (TT.bytes) {
159 bytes += new->len;
160 if (bytes > 0) {
161 while (list->len <= bytes) {
162 bytes -= list->len;
163 free(dlist_pop(&list));
164 }
165 list->data += bytes;
166 list->len -= bytes;
167 bytes = 0;
168 }
169 } else {
170 int len = new->len, count;
171 char *try = new->data;
172
173 // First character _after_ a newline starts a new line, which
174 // works even if file doesn't end with a newline
175 for (count=0; count<len; count++) {
176 if (linepop) lines++;
177 linepop = try[count] == '\n';
178
179 if (lines > 0) {
180 char c;
181
182 do {
183 c = *list->data;
184 if (!--(list->len)) free(dlist_pop(&list));
185 else list->data++;
186 } while (c != '\n');
187 lines--;
188 }
189 }
190 }
191 }
192
193 // Output/free the buffer.
194 llist_traverse(list, dump_chunk);
195
196 // Measuring from the beginning of the file.
197 } else for (;;) {
198 int len, offset = 0;
199
200 // Error while reading does not exit. Error writing does.
201 len = read(fd, toybuf, sizeof(toybuf));
202 if (len<1) break;
203 while (bytes > 1 || lines > 1) {
204 bytes--;
205 if (toybuf[offset++] == '\n') lines--;
206 if (offset >= len) break;
207 }
208 if (offset<len) xwrite(1, toybuf+offset, len-offset);
209 }
210
211 // -f support: cache name/descriptor
212 }
213
tail_main(void)214 void tail_main(void)
215 {
216 char **args = toys.optargs;
217
218 if (!(toys.optflags&(FLAG_n|FLAG_c))) {
219 char *arg = *args;
220
221 // handle old "-42" style arguments
222 if (arg && *arg == '-' && arg[1]) {
223 TT.lines = atolx(*(args++));
224 toys.optc--;
225 }
226
227 // if nothing specified, default -n to -10
228 TT.lines = -10;
229 }
230
231 loopfiles(args, do_tail);
232
233 // do -f stuff
234 }
235