• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  * Deviations from posix: -f waits for pipe/fifo on stdin (nonblock?).
8 
9 USE_TAIL(NEWTOY(tail, "?fc-n-[-cn]", TOYFLAG_USR|TOYFLAG_BIN))
10 
11 config TAIL
12   bool "tail"
13   default y
14   help
15     usage: tail [-n|c NUMBER] [-f] [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), waiting for more data to be appended
23 
24 config TAIL_SEEK
25   bool "tail seek support"
26   default y
27   depends on TAIL
28   help
29     This version uses lseek, which is faster on large files.
30 */
31 
32 #define FOR_tail
33 #include "toys.h"
34 #include <sys/inotify.h>
35 
36 GLOBALS(
37   long lines;
38   long bytes;
39 
40   int file_no, ffd, *files;
41 )
42 
43 struct line_list {
44   struct line_list *next, *prev;
45   char *data;
46   int len;
47 };
48 
get_chunk(int fd,int len)49 static struct line_list *get_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 
dump_chunk(void * ptr)65 static void dump_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 = get_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, dump_chunk);
128 
129   // In case of -f
130   lseek(fd, bytes, SEEK_SET);
131   return 1;
132 }
133 
134 // Called for each file listed on command line, and/or stdin
do_tail(int fd,char * name)135 static void do_tail(int fd, char *name)
136 {
137   long bytes = TT.bytes, lines = TT.lines;
138   int linepop = 1;
139 
140   if (toys.optflags & FLAG_f) {
141     int f = TT.file_no*2;
142     char *s = name;
143 
144     if (!fd) sprintf(s = toybuf, "/proc/self/fd/%d", fd);
145     TT.files[f++] = fd;
146     if (0 > (TT.files[f] = inotify_add_watch(TT.ffd, s, IN_MODIFY)))
147       perror_msg("bad -f on '%s'", name);
148   }
149 
150   if (TT.file_no++) xputc('\n');
151   if (toys.optc > 1) xprintf("==> %s <==\n", name);
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     } else {
235       // if nothing specified, default -n to -10
236       TT.lines = -10;
237     }
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|WARN_ONLY|(O_CLOEXEC*!(toys.optflags&FLAG_f)),
246     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