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
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 #include <sys/inotify.h>
33 
34 GLOBALS(
35   long lines;
36   long bytes;
37 
38   int file_no, ffd, *files;
39 )
40 
41 struct line_list {
42   struct line_list *next, *prev;
43   char *data;
44   int len;
45 };
46 
get_chunk(int fd,int len)47 static struct line_list *get_chunk(int fd, int len)
48 {
49   struct line_list *line = xmalloc(sizeof(struct line_list)+len);
50 
51   memset(line, 0, sizeof(struct line_list));
52   line->data = ((char *)line) + sizeof(struct line_list);
53   line->len = readall(fd, line->data, len);
54 
55   if (line->len < 1) {
56     free(line);
57     return 0;
58   }
59 
60   return line;
61 }
62 
dump_chunk(void * ptr)63 static void dump_chunk(void *ptr)
64 {
65   struct line_list *list = ptr;
66 
67   xwrite(1, list->data, list->len);
68   free(list);
69 }
70 
71 // Reading through very large files is slow.  Using lseek can speed things
72 // up a lot, but isn't applicable to all input (cat | tail).
73 // Note: bytes and lines are negative here.
try_lseek(int fd,long bytes,long lines)74 static int try_lseek(int fd, long bytes, long lines)
75 {
76   struct line_list *list = 0, *temp;
77   int flag = 0, chunk = sizeof(toybuf);
78   off_t pos = lseek(fd, 0, SEEK_END);
79 
80   // If lseek() doesn't work on this stream, return now.
81   if (pos<0) return 0;
82 
83   // Seek to the right spot, output data from there.
84   if (bytes) {
85     if (lseek(fd, bytes, SEEK_END)<0) lseek(fd, 0, SEEK_SET);
86     xsendfile(fd, 1);
87     return 1;
88   }
89 
90   // Read from end to find enough lines, then output them.
91 
92   bytes = pos;
93   while (lines && pos) {
94     int offset;
95 
96     // Read in next chunk from end of file
97     if (chunk>pos) chunk = pos;
98     pos -= chunk;
99     if (pos != lseek(fd, pos, SEEK_SET)) {
100       perror_msg("seek failed");
101       break;
102     }
103     if (!(temp = get_chunk(fd, chunk))) break;
104     temp->next = list;
105     list = temp;
106 
107     // Count newlines in this chunk.
108     offset = list->len;
109     while (offset--) {
110       // If the last line ends with a newline, that one doesn't count.
111       if (!flag) flag++;
112 
113       // Start outputting data right after newline
114       else if (list->data[offset] == '\n' && !++lines) {
115         offset++;
116         list->data += offset;
117         list->len -= offset;
118 
119         break;
120       }
121     }
122   }
123 
124   // Output stored data
125   llist_traverse(list, dump_chunk);
126 
127   // In case of -f
128   lseek(fd, bytes, SEEK_SET);
129   return 1;
130 }
131 
132 // Called for each file listed on command line, and/or stdin
do_tail(int fd,char * name)133 static void do_tail(int fd, char *name)
134 {
135   long bytes = TT.bytes, lines = TT.lines;
136   int linepop = 1;
137 
138   if (toys.optflags & FLAG_f) {
139     int f = TT.file_no*2;
140     char *s = name;
141 
142     if (!fd) sprintf(s = toybuf, "/proc/self/fd/%d", fd);
143     TT.files[f++] = fd;
144     if (0 > (TT.files[f] = inotify_add_watch(TT.ffd, s, IN_MODIFY)))
145       perror_msg("bad -f on '%s'", name);
146   }
147 
148   if (toys.optc > 1) {
149     if (TT.file_no++) xputc('\n');
150     xprintf("==> %s <==\n", name);
151   }
152 
153   // Are we measuring from the end of the file?
154 
155   if (bytes<0 || lines<0) {
156     struct line_list *list = 0, *new;
157 
158     // The slow codepath is always needed, and can handle all input,
159     // so make lseek support optional.
160     if (CFG_TAIL_SEEK && try_lseek(fd, bytes, lines)) return;
161 
162     // Read data until we run out, keep a trailing buffer
163     for (;;) {
164       // Read next page of data, appending to linked list in order
165       if (!(new = get_chunk(fd, sizeof(toybuf)))) break;
166       dlist_add_nomalloc((void *)&list, (void *)new);
167 
168       // If tracing bytes, add until we have enough, discarding overflow.
169       if (TT.bytes) {
170         bytes += new->len;
171         if (bytes > 0) {
172           while (list->len <= bytes) {
173             bytes -= list->len;
174             free(dlist_pop(&list));
175           }
176           list->data += bytes;
177           list->len -= bytes;
178           bytes = 0;
179         }
180       } else {
181         int len = new->len, count;
182         char *try = new->data;
183 
184         // First character _after_ a newline starts a new line, which
185         // works even if file doesn't end with a newline
186         for (count=0; count<len; count++) {
187           if (linepop) lines++;
188           linepop = try[count] == '\n';
189 
190           if (lines > 0) {
191             char c;
192 
193             do {
194               c = *list->data;
195               if (!--(list->len)) free(dlist_pop(&list));
196               else list->data++;
197             } while (c != '\n');
198             lines--;
199           }
200         }
201       }
202     }
203 
204     // Output/free the buffer.
205     llist_traverse(list, dump_chunk);
206 
207   // Measuring from the beginning of the file.
208   } else for (;;) {
209     int len, offset = 0;
210 
211     // Error while reading does not exit.  Error writing does.
212     len = read(fd, toybuf, sizeof(toybuf));
213     if (len<1) break;
214     while (bytes > 1 || lines > 1) {
215       bytes--;
216       if (toybuf[offset++] == '\n') lines--;
217       if (offset >= len) break;
218     }
219     if (offset<len) xwrite(1, toybuf+offset, len-offset);
220   }
221 }
222 
tail_main(void)223 void tail_main(void)
224 {
225   char **args = toys.optargs;
226 
227   if (!(toys.optflags&(FLAG_n|FLAG_c))) {
228     char *arg = *args;
229 
230     // handle old "-42" style arguments
231     if (arg && *arg == '-' && arg[1]) {
232       TT.lines = atolx(*(args++));
233       toys.optc--;
234     }
235 
236     // if nothing specified, default -n to -10
237     TT.lines = -10;
238   }
239 
240   // Allocate 2 ints per optarg for -f
241   if (toys.optflags&FLAG_f) {
242     if ((TT.ffd = inotify_init()) < 0) perror_exit("inotify_init");
243     TT.files = xmalloc(toys.optc*8);
244   }
245   loopfiles_rw(args, O_RDONLY|(O_CLOEXEC*!(toys.optflags&FLAG_f)),
246     0, 0, do_tail);
247 
248   if ((toys.optflags & FLAG_f) && TT.file_no) {
249     int len, last_fd = TT.files[(TT.file_no-1)*2], i, fd;
250     struct inotify_event ev;
251 
252     for (;;) {
253       if (sizeof(ev)!=read(TT.ffd, &ev, sizeof(ev))) perror_exit("inotify");
254 
255       for (i = 0; i<TT.file_no && ev.wd!=TT.files[(i*2)+1]; i++);
256       if (i==TT.file_no) continue;
257       fd = TT.files[i*2];
258 
259       // Read new data.
260       while ((len = read(fd, toybuf, sizeof(toybuf)))>0) {
261         if (last_fd != fd) {
262           last_fd = fd;
263           xprintf("\n==> %s <==\n", args[i]);
264         }
265 
266         xwrite(1, toybuf, len);
267       }
268     }
269   }
270 }
271