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