xref: /aosp_15_r20/external/toybox/toys/posix/tail.c (revision cf5a6c84e2b8763fc1a7db14496fd4742913b199)
1 /* tail.c - copy last lines from input to stdout.
2  *
3  * Copyright 2012 Timothy Elliott <[email protected]>
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|TOYFLAG_LINEBUF))
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     struct dev_ino di;
40   } *F;
41 )
42 
43 struct line_list {
44   struct line_list *next, *prev;
45   char *data;
46   int len;
47 };
48 
read_chunk(int fd,int len)49 static struct line_list *read_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 
write_chunk(void * ptr)65 static void write_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 = read_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, write_chunk);
128 
129   // In case of -f
130   lseek(fd, bytes, SEEK_SET);
131   return 1;
132 }
133 
134 // For -f and -F
tail_continue()135 static void tail_continue()
136 {
137   long long pos;
138   char *path;
139   struct stat sb;
140   int i = 0, fd, len;
141 
142   for (i = 0; ; i++) {
143     if (FLAG(f)) fd = xnotify_wait(TT.not, &path);
144     else {
145       if (i == TT.file_no) {
146         i = 0;
147         msleep(TT.ss);
148       }
149       fd = TT.F[i].fd;
150       path = TT.F[i].path;
151 
152       if (stat(TT.F[i].path, &sb)) {
153         if (fd >= 0) {
154           close(fd);
155           TT.F[i].fd = -1;
156           error_msg("file inaccessible: %s\n", TT.F[i].path);
157         }
158         continue;
159       }
160 
161       if (fd<0 || !same_dev_ino(&sb, &TT.F[i].di)) {
162         if (fd>=0) close(fd);
163         if (-1 == (TT.F[i].fd = fd = open(path, O_RDONLY))) continue;
164         error_msg("following new file: %s\n", path);
165         TT.F[i].di.dev = sb.st_dev;
166         TT.F[i].di.ino = sb.st_ino;
167       } else if (sb.st_size <= (pos = lseek(fd, 0, SEEK_CUR))) {
168         if (pos == sb.st_size) continue;
169         error_msg("file truncated: %s\n", path);
170         lseek(fd, 0, SEEK_SET);
171       }
172     }
173 
174     while ((len = read(fd, toybuf, sizeof(toybuf)))>0) {
175       if (TT.file_no>1 && TT.last_fd != fd) {
176         TT.last_fd = fd;
177         xprintf("\n==> %s <==\n", path);
178       }
179       xwrite(1, toybuf, len);
180     }
181   }
182 }
183 
184 // Called for each file listed on command line, and/or stdin
do_tail(int fd,char * name)185 static void do_tail(int fd, char *name)
186 {
187   long bytes = TT.c, lines = TT.n;
188   int linepop = 1;
189 
190   if (FLAG(F)) {
191     if (!fd) perror_exit("no -F with '-'");
192   } else if (fd == -1) return;
193   if (FLAG(f) || FLAG(F)) {
194     char *s = name;
195     struct stat sb;
196 
197     if (!fd) sprintf(s = toybuf, "/proc/self/fd/%d", fd);
198 
199     if (FLAG(f)) xnotify_add(TT.not, fd, s);
200     if (FLAG(F)) {
201       if (fd != -1) {
202         if (fstat(fd, &sb)) perror_exit("%s", name);
203         TT.F[TT.file_no].di.dev = sb.st_dev;
204         TT.F[TT.file_no].di.ino = sb.st_ino;
205       }
206       TT.F[TT.file_no].fd = fd;
207       TT.F[TT.file_no].path = s;
208     }
209   }
210 
211   if (TT.file_no++) xputc('\n');
212   TT.last_fd = fd;
213   if (toys.optc > 1) xprintf("==> %s <==\n", name);
214 
215   // Are we measuring from the end of the file?
216 
217   if (bytes<0 || lines<0) {
218     struct line_list *list = 0, *new;
219 
220     // The slow codepath is always needed, and can handle all input,
221     // so make lseek support optional.
222     if (try_lseek(fd, bytes, lines)) return;
223 
224     // Read data until we run out, keep a trailing buffer
225     for (;;) {
226       // Read next page of data, appending to linked list in order
227       if (!(new = read_chunk(fd, sizeof(toybuf)))) break;
228       dlist_add_nomalloc((void *)&list, (void *)new);
229 
230       // If tracing bytes, add until we have enough, discarding overflow.
231       if (TT.c) {
232         bytes += new->len;
233         if (bytes > 0) {
234           while (list->len <= bytes) {
235             bytes -= list->len;
236             free(dlist_pop(&list));
237           }
238           list->data += bytes;
239           list->len -= bytes;
240           bytes = 0;
241         }
242       } else {
243         int len = new->len, count;
244         char *try = new->data;
245 
246         // First character _after_ a newline starts a new line, which
247         // works even if file doesn't end with a newline
248         for (count=0; count<len; count++) {
249           if (linepop) lines++;
250           linepop = try[count] == '\n';
251 
252           if (lines > 0) {
253             char c;
254 
255             do {
256               c = *list->data;
257               if (!--(list->len)) free(dlist_pop(&list));
258               else list->data++;
259             } while (c != '\n');
260             lines--;
261           }
262         }
263       }
264     }
265 
266     // Output/free the buffer.
267     llist_traverse(list, write_chunk);
268 
269   // Measuring from the beginning of the file.
270   } else for (;;) {
271     int len, offset = 0;
272 
273     // Error while reading does not exit.  Error writing does.
274     len = read(fd, toybuf, sizeof(toybuf));
275     if (len<1) break;
276     while (bytes > 1 || lines > 1) {
277       bytes--;
278       if (toybuf[offset++] == '\n') lines--;
279       if (offset >= len) break;
280     }
281     if (offset<len) xwrite(1, toybuf+offset, len-offset);
282   }
283 }
284 
tail_main(void)285 void tail_main(void)
286 {
287   char **args = toys.optargs;
288 
289   if (!FLAG(n) && !FLAG(c)) {
290     char *arg = *args;
291 
292     // handle old "-42" / "+42" style arguments, else default to last 10 lines
293     if (arg && (*arg == '-' || *arg == '+') && arg[1]) {
294       TT.n = atolx(*(args++));
295       toys.optc--;
296     } else TT.n = -10;
297   }
298 
299   if (FLAG(F)) TT.F = xzalloc(toys.optc*sizeof(*TT.F));
300   else if (FLAG(f)) TT.not = xnotify_init(toys.optc);
301   TT.ss = TT.s ? xparsemillitime(TT.s) : 1000;
302 
303   loopfiles_rw(args,
304     O_RDONLY|WARN_ONLY|LOOPFILES_ANYWAY|O_CLOEXEC*!(FLAG(f) || FLAG(F)),
305     0, do_tail);
306 
307   // Wait for more data when following files
308   if (TT.file_no && (FLAG(F) || FLAG(f))) tail_continue();
309 }
310