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