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