1 /* tail.c - copy last lines from input to stdout.
2 *
3 * Copyright 2012 Timothy Elliott <[email protected]>
4 *
5 * See http://opengroup.org/onlinepubs/9699919799/utilities/tail.html
6 *
7 * Deviations from posix: -f waits for pipe/fifo on stdin (nonblock?).
8
9 USE_TAIL(NEWTOY(tail, "?fFs:c(bytes)-n(lines)-[-cn][-fF]", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_LINEBUF))
10
11 config TAIL
12 bool "tail"
13 default y
14 help
15 usage: tail [-n|c NUMBER] [-f|F] [-s SECONDS] [FILE...]
16
17 Copy last lines from files to stdout. If no files listed, copy from
18 stdin. Filename "-" is a synonym for stdin.
19
20 -n Output the last NUMBER lines (default 10), +X counts from start
21 -c Output the last NUMBER bytes, +NUMBER counts from start
22 -f Follow FILE(s) by descriptor, waiting for more data to be appended
23 -F Follow FILE(s) by filename, waiting for more data, and retrying
24 -s Used with -F, sleep SECONDS between retries (default 1)
25 */
26
27 #define FOR_tail
28 #include "toys.h"
29
30 GLOBALS(
31 long n, c;
32 char *s;
33
34 int file_no, last_fd, ss;
35 struct xnotify *not;
36 struct {
37 char *path;
38 int fd;
39 struct dev_ino di;
40 } *F;
41 )
42
43 struct line_list {
44 struct line_list *next, *prev;
45 char *data;
46 int len;
47 };
48
read_chunk(int fd,int len)49 static struct line_list *read_chunk(int fd, int len)
50 {
51 struct line_list *line = xmalloc(sizeof(struct line_list)+len);
52
53 memset(line, 0, sizeof(struct line_list));
54 line->data = ((char *)line) + sizeof(struct line_list);
55 line->len = readall(fd, line->data, len);
56
57 if (line->len < 1) {
58 free(line);
59 return 0;
60 }
61
62 return line;
63 }
64
write_chunk(void * ptr)65 static void write_chunk(void *ptr)
66 {
67 struct line_list *list = ptr;
68
69 xwrite(1, list->data, list->len);
70 free(list);
71 }
72
73 // Reading through very large files is slow. Using lseek can speed things
74 // up a lot, but isn't applicable to all input (cat | tail).
75 // Note: bytes and lines are negative here.
try_lseek(int fd,long bytes,long lines)76 static int try_lseek(int fd, long bytes, long lines)
77 {
78 struct line_list *list = 0, *temp;
79 int flag = 0, chunk = sizeof(toybuf);
80 off_t pos = lseek(fd, 0, SEEK_END);
81
82 // If lseek() doesn't work on this stream, return now.
83 if (pos<0) return 0;
84
85 // Seek to the right spot, output data from there.
86 if (bytes) {
87 if (lseek(fd, bytes, SEEK_END)<0) lseek(fd, 0, SEEK_SET);
88 xsendfile(fd, 1);
89 return 1;
90 }
91
92 // Read from end to find enough lines, then output them.
93
94 bytes = pos;
95 while (lines && pos) {
96 int offset;
97
98 // Read in next chunk from end of file
99 if (chunk>pos) chunk = pos;
100 pos -= chunk;
101 if (pos != lseek(fd, pos, SEEK_SET)) {
102 perror_msg("seek failed");
103 break;
104 }
105 if (!(temp = read_chunk(fd, chunk))) break;
106 temp->next = list;
107 list = temp;
108
109 // Count newlines in this chunk.
110 offset = list->len;
111 while (offset--) {
112 // If the last line ends with a newline, that one doesn't count.
113 if (!flag) flag++;
114
115 // Start outputting data right after newline
116 else if (list->data[offset] == '\n' && !++lines) {
117 offset++;
118 list->data += offset;
119 list->len -= offset;
120
121 break;
122 }
123 }
124 }
125
126 // Output stored data
127 llist_traverse(list, write_chunk);
128
129 // In case of -f
130 lseek(fd, bytes, SEEK_SET);
131 return 1;
132 }
133
134 // For -f and -F
tail_continue()135 static void tail_continue()
136 {
137 long long pos;
138 char *path;
139 struct stat sb;
140 int i = 0, fd, len;
141
142 for (i = 0; ; i++) {
143 if (FLAG(f)) fd = xnotify_wait(TT.not, &path);
144 else {
145 if (i == TT.file_no) {
146 i = 0;
147 msleep(TT.ss);
148 }
149 fd = TT.F[i].fd;
150 path = TT.F[i].path;
151
152 if (stat(TT.F[i].path, &sb)) {
153 if (fd >= 0) {
154 close(fd);
155 TT.F[i].fd = -1;
156 error_msg("file inaccessible: %s\n", TT.F[i].path);
157 }
158 continue;
159 }
160
161 if (fd<0 || !same_dev_ino(&sb, &TT.F[i].di)) {
162 if (fd>=0) close(fd);
163 if (-1 == (TT.F[i].fd = fd = open(path, O_RDONLY))) continue;
164 error_msg("following new file: %s\n", path);
165 TT.F[i].di.dev = sb.st_dev;
166 TT.F[i].di.ino = sb.st_ino;
167 } else if (sb.st_size <= (pos = lseek(fd, 0, SEEK_CUR))) {
168 if (pos == sb.st_size) continue;
169 error_msg("file truncated: %s\n", path);
170 lseek(fd, 0, SEEK_SET);
171 }
172 }
173
174 while ((len = read(fd, toybuf, sizeof(toybuf)))>0) {
175 if (TT.file_no>1 && TT.last_fd != fd) {
176 TT.last_fd = fd;
177 xprintf("\n==> %s <==\n", path);
178 }
179 xwrite(1, toybuf, len);
180 }
181 }
182 }
183
184 // Called for each file listed on command line, and/or stdin
do_tail(int fd,char * name)185 static void do_tail(int fd, char *name)
186 {
187 long bytes = TT.c, lines = TT.n;
188 int linepop = 1;
189
190 if (FLAG(F)) {
191 if (!fd) perror_exit("no -F with '-'");
192 } else if (fd == -1) return;
193 if (FLAG(f) || FLAG(F)) {
194 char *s = name;
195 struct stat sb;
196
197 if (!fd) sprintf(s = toybuf, "/proc/self/fd/%d", fd);
198
199 if (FLAG(f)) xnotify_add(TT.not, fd, s);
200 if (FLAG(F)) {
201 if (fd != -1) {
202 if (fstat(fd, &sb)) perror_exit("%s", name);
203 TT.F[TT.file_no].di.dev = sb.st_dev;
204 TT.F[TT.file_no].di.ino = sb.st_ino;
205 }
206 TT.F[TT.file_no].fd = fd;
207 TT.F[TT.file_no].path = s;
208 }
209 }
210
211 if (TT.file_no++) xputc('\n');
212 TT.last_fd = fd;
213 if (toys.optc > 1) xprintf("==> %s <==\n", name);
214
215 // Are we measuring from the end of the file?
216
217 if (bytes<0 || lines<0) {
218 struct line_list *list = 0, *new;
219
220 // The slow codepath is always needed, and can handle all input,
221 // so make lseek support optional.
222 if (try_lseek(fd, bytes, lines)) return;
223
224 // Read data until we run out, keep a trailing buffer
225 for (;;) {
226 // Read next page of data, appending to linked list in order
227 if (!(new = read_chunk(fd, sizeof(toybuf)))) break;
228 dlist_add_nomalloc((void *)&list, (void *)new);
229
230 // If tracing bytes, add until we have enough, discarding overflow.
231 if (TT.c) {
232 bytes += new->len;
233 if (bytes > 0) {
234 while (list->len <= bytes) {
235 bytes -= list->len;
236 free(dlist_pop(&list));
237 }
238 list->data += bytes;
239 list->len -= bytes;
240 bytes = 0;
241 }
242 } else {
243 int len = new->len, count;
244 char *try = new->data;
245
246 // First character _after_ a newline starts a new line, which
247 // works even if file doesn't end with a newline
248 for (count=0; count<len; count++) {
249 if (linepop) lines++;
250 linepop = try[count] == '\n';
251
252 if (lines > 0) {
253 char c;
254
255 do {
256 c = *list->data;
257 if (!--(list->len)) free(dlist_pop(&list));
258 else list->data++;
259 } while (c != '\n');
260 lines--;
261 }
262 }
263 }
264 }
265
266 // Output/free the buffer.
267 llist_traverse(list, write_chunk);
268
269 // Measuring from the beginning of the file.
270 } else for (;;) {
271 int len, offset = 0;
272
273 // Error while reading does not exit. Error writing does.
274 len = read(fd, toybuf, sizeof(toybuf));
275 if (len<1) break;
276 while (bytes > 1 || lines > 1) {
277 bytes--;
278 if (toybuf[offset++] == '\n') lines--;
279 if (offset >= len) break;
280 }
281 if (offset<len) xwrite(1, toybuf+offset, len-offset);
282 }
283 }
284
tail_main(void)285 void tail_main(void)
286 {
287 char **args = toys.optargs;
288
289 if (!FLAG(n) && !FLAG(c)) {
290 char *arg = *args;
291
292 // handle old "-42" / "+42" style arguments, else default to last 10 lines
293 if (arg && (*arg == '-' || *arg == '+') && arg[1]) {
294 TT.n = atolx(*(args++));
295 toys.optc--;
296 } else TT.n = -10;
297 }
298
299 if (FLAG(F)) TT.F = xzalloc(toys.optc*sizeof(*TT.F));
300 else if (FLAG(f)) TT.not = xnotify_init(toys.optc);
301 TT.ss = TT.s ? xparsemillitime(TT.s) : 1000;
302
303 loopfiles_rw(args,
304 O_RDONLY|WARN_ONLY|LOOPFILES_ANYWAY|O_CLOEXEC*!(FLAG(f) || FLAG(F)),
305 0, do_tail);
306
307 // Wait for more data when following files
308 if (TT.file_no && (FLAG(F) || FLAG(f))) tail_continue();
309 }
310