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, "?fFs:c(bytes)-n(lines)-[-cn][-fF]", TOYFLAG_USR|TOYFLAG_BIN))
  10
  11config 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
  30GLOBALS(
  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
  43struct line_list {
  44  struct line_list *next, *prev;
  45  char *data;
  46  int len;
  47};
  48
  49static 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
  65static 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.
  76static 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
 135static 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
 185static 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
 285void 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" style arguments, else default to last 10 lines
 293    if (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