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