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 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 
     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 
     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.
     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
    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 
    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