toybox/toys/posix/tail.c
<<
>>
Prefs
   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
   9USE_TAIL(NEWTOY(tail, "?fc-n-[-cn]", TOYFLAG_USR|TOYFLAG_BIN))
  10
  11config 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
  28GLOBALS(
  29  long n, c;
  30
  31  int file_no, last_fd;
  32  struct xnotify *not;
  33)
  34
  35struct line_list {
  36  struct line_list *next, *prev;
  37  char *data;
  38  int len;
  39};
  40
  41static struct line_list *get_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
  57static void dump_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.
  68static 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 = get_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, dump_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
 127static 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 = get_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, dump_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
 213void 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