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