Home | History | Annotate | Download | only in posix
      1 /* tail.c - copy last lines from input to stdout.
      2  *
      3  * Copyright 2012 Timothy Elliott <tle (at) 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 
     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 
     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.
     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
    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 
    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