tail.c revision 4b9c20351f5facff4b84c37757c335709af51133
17aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley/* tail.c - copy last lines from input to stdout.
250591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott *
350591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott * Copyright 2012 Timothy Elliott <tle@holymonkey.com>
450591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott *
5f91b7c89bc852868692b9518185421ebb52d67b3Rob Landley * See http://opengroup.org/onlinepubs/9699919799/utilities/tail.html
650591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
72c48247a01a19c709f693d649d8158bccb5fbf70Rob LandleyUSE_TAIL(NEWTOY(tail, "fc-n-", TOYFLAG_BIN))
850591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
950591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliottconfig TAIL
107aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  bool "tail"
117aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  default y
127aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  help
137aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    usage: tail [-n|c number] [-f] [file...]
1450591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
157aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    Copy last lines from files to stdout. If no files listed, copy from
167aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    stdin. Filename "-" is a synonym for stdin.
1750591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
187aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    -n	output the last X lines (default 10), +X counts from start.
197aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    -c	output the last X bytes, +X counts from start
207aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    -f	follow file, waiting for more data to be appended
212c48247a01a19c709f693d649d8158bccb5fbf70Rob Landley
222c48247a01a19c709f693d649d8158bccb5fbf70Rob Landleyconfig TAIL_SEEK
237aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  bool "tail seek support"
247aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  default y
257aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  depends on TAIL
267aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  help
277aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    This version uses lseek, which is faster on large files.
2850591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott*/
2950591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
30c0e56edaf256adb6c60c5a052525a1ffbb927901Rob Landley#define FOR_tail
3150591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott#include "toys.h"
3250591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
33c0e56edaf256adb6c60c5a052525a1ffbb927901Rob LandleyGLOBALS(
347aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  long lines;
357aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  long bytes;
36b73d8e3c2d3b606295453940e602d173924cd380Rob Landley
377aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  int file_no;
3850591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott)
3950591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
4050591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliottstruct line_list {
414b9c20351f5facff4b84c37757c335709af51133Felix Janda  struct line_list *next;
427aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  char *data;
434b9c20351f5facff4b84c37757c335709af51133Felix Janda  size_t len;
4450591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott};
4550591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
464b9c20351f5facff4b84c37757c335709af51133Felix Jandastatic struct line_list *get_chunk(int fd, size_t len)
4750591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott{
487aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  struct line_list *line = xmalloc(sizeof(struct line_list)+len);
4950591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
504b9c20351f5facff4b84c37757c335709af51133Felix Janda  line->data = (char*)line + sizeof(struct line_list);
517aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  line->len = readall(fd, line->data, len);
524b9c20351f5facff4b84c37757c335709af51133Felix Janda  line->next = 0;
5350591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
544b9c20351f5facff4b84c37757c335709af51133Felix Janda  if (line->len + 1 < 2) {
557aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    free(line);
567aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    return 0;
577aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  }
5850591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
597aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  return line;
6050591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott}
6150591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
622c48247a01a19c709f693d649d8158bccb5fbf70Rob Landleystatic void dump_chunk(void *ptr)
6350591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott{
647aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  struct line_list *list = ptr;
654b9c20351f5facff4b84c37757c335709af51133Felix Janda  size_t len = list->len - (list->data - (char*)list - sizeof(struct line_list));
664b9c20351f5facff4b84c37757c335709af51133Felix Janda
674b9c20351f5facff4b84c37757c335709af51133Felix Janda  xwrite(1, list->data, len);
687aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  free(list);
6950591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott}
7050591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
712c48247a01a19c709f693d649d8158bccb5fbf70Rob Landley// Reading through very large files is slow.  Using lseek can speed things
722c48247a01a19c709f693d649d8158bccb5fbf70Rob Landley// up a lot, but isn't applicable to all input (cat | tail).
732c48247a01a19c709f693d649d8158bccb5fbf70Rob Landley// Note: bytes and lines are negative here.
742c48247a01a19c709f693d649d8158bccb5fbf70Rob Landleystatic int try_lseek(int fd, long bytes, long lines)
7550591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott{
767aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  struct line_list *list = 0, *temp;
774b9c20351f5facff4b84c37757c335709af51133Felix Janda  int flag = 0;
784b9c20351f5facff4b84c37757c335709af51133Felix Janda  size_t chunk = sizeof(toybuf), pos = lseek(fd, 0, SEEK_END);
797aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
807aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  // If lseek() doesn't work on this stream, return now.
814b9c20351f5facff4b84c37757c335709af51133Felix Janda  if (pos == -1) return 0;
827aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
837aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  // Seek to the right spot, output data from there.
847aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  if (bytes) {
857aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    if (lseek(fd, bytes, SEEK_END)<0) lseek(fd, 0, SEEK_SET);
867aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    xsendfile(fd, 1);
877aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    return 1;
887aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  }
897aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
907aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  // Read from end to find enough lines, then output them.
917aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
927aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  bytes = pos;
937aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  while (lines && pos) {
944b9c20351f5facff4b84c37757c335709af51133Felix Janda    size_t offset;
957aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
967aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    // Read in next chunk from end of file
974b9c20351f5facff4b84c37757c335709af51133Felix Janda    if (chunk > pos) chunk = pos;
987aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    pos -= chunk;
997aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    if (pos != lseek(fd, pos, SEEK_SET)) {
1007aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      perror_msg("seek failed");
1017aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      break;
1027aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    }
1037aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    if (!(temp = get_chunk(fd, chunk))) break;
1044b9c20351f5facff4b84c37757c335709af51133Felix Janda    if (list) temp->next = list;
1057aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    list = temp;
1067aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1077aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    // Count newlines in this chunk.
1087aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    offset = list->len;
1097aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    while (offset--) {
1107aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      // If the last line ends with a newline, that one doesn't count.
1114b9c20351f5facff4b84c37757c335709af51133Felix Janda      if (!flag) flag++;
1127aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1137aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      // Start outputting data right after newline
1144b9c20351f5facff4b84c37757c335709af51133Felix Janda      else if (list->data[offset] == '\n' && !++lines) {
1157aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley        offset++;
1167aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley        list->data += offset;
1177aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1184b9c20351f5facff4b84c37757c335709af51133Felix Janda        goto done;
1197aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      }
1207aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    }
1217aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  }
1227aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1234b9c20351f5facff4b84c37757c335709af51133Felix Jandadone:
1247aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  // Output stored data
1257aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  llist_traverse(list, dump_chunk);
1267aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1277aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  // In case of -f
1287aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  lseek(fd, bytes, SEEK_SET);
1297aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  return 1;
13050591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott}
13150591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
1322c48247a01a19c709f693d649d8158bccb5fbf70Rob Landley// Called for each file listed on command line, and/or stdin
13350591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliottstatic void do_tail(int fd, char *name)
13450591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott{
1357aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  long bytes = TT.bytes, lines = TT.lines;
1367aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1377aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  if (toys.optc > 1) {
1387aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    if (TT.file_no++) xputc('\n');
1397aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    xprintf("==> %s <==\n", name);
1407aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  }
1417aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1427aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  // Are we measuring from the end of the file?
1437aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1447aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  if (bytes<0 || lines<0) {
1454b9c20351f5facff4b84c37757c335709af51133Felix Janda    struct line_list *head = 0, *tail, *new;
1464b9c20351f5facff4b84c37757c335709af51133Felix Janda    // circular buffer of lines
1474b9c20351f5facff4b84c37757c335709af51133Felix Janda    struct {
1484b9c20351f5facff4b84c37757c335709af51133Felix Janda      char *start;
1494b9c20351f5facff4b84c37757c335709af51133Felix Janda      struct line_list *inchunk;
1504b9c20351f5facff4b84c37757c335709af51133Felix Janda    } *l = xzalloc(2*-lines*sizeof(void*));
1514b9c20351f5facff4b84c37757c335709af51133Felix Janda    int i = 0, flag = 0;
1524b9c20351f5facff4b84c37757c335709af51133Felix Janda    size_t count, len = bytes;
1537aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1547aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    // The slow codepath is always needed, and can handle all input,
1557aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    // so make lseek support optional.
1564b9c20351f5facff4b84c37757c335709af51133Felix Janda    if (CFG_TAIL_SEEK && try_lseek(fd, bytes, lines)) return;
1577aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1587aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    // Read data until we run out, keep a trailing buffer
1594b9c20351f5facff4b84c37757c335709af51133Felix Janda    for (;;) {
1607aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      char *try;
1617aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1627aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      if (!(new = get_chunk(fd, sizeof(toybuf)))) break;
1637aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      // append in order
1644b9c20351f5facff4b84c37757c335709af51133Felix Janda      if (head) tail->next = new;
1654b9c20351f5facff4b84c37757c335709af51133Felix Janda      else head = new;
1664b9c20351f5facff4b84c37757c335709af51133Felix Janda      tail = new;
1677aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1687aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      try = new->data;
1694b9c20351f5facff4b84c37757c335709af51133Felix Janda      if (lines) for (count = 0; count < new->len; count++, try++) {
1704b9c20351f5facff4b84c37757c335709af51133Felix Janda        if (flag) { // last char was a newline
1714b9c20351f5facff4b84c37757c335709af51133Felix Janda            while (l[i].inchunk && (l[i].inchunk!=head)) free(llist_pop(&head));
1724b9c20351f5facff4b84c37757c335709af51133Felix Janda            l[i].inchunk = tail;
1734b9c20351f5facff4b84c37757c335709af51133Felix Janda            l[i].start = try;
1744b9c20351f5facff4b84c37757c335709af51133Felix Janda            i = (i + 1) % -lines;
1754b9c20351f5facff4b84c37757c335709af51133Felix Janda            flag = 0;
1767aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley        }
1774b9c20351f5facff4b84c37757c335709af51133Felix Janda        if (*try == '\n') flag = 1;
1784b9c20351f5facff4b84c37757c335709af51133Felix Janda      } else { // bytes
1794b9c20351f5facff4b84c37757c335709af51133Felix Janda        if (len + new->len < len) flag = 1; // overflow -> have now read enough
1804b9c20351f5facff4b84c37757c335709af51133Felix Janda        for (len += new->len; flag && (len - head->len < len);) {
1814b9c20351f5facff4b84c37757c335709af51133Felix Janda          len -= head->len;
1824b9c20351f5facff4b84c37757c335709af51133Felix Janda          free(llist_pop(&head));
1837aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley        }
1847aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      }
1857aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    }
1864b9c20351f5facff4b84c37757c335709af51133Felix Janda    if (lines) head->data = l[i].start;
1874b9c20351f5facff4b84c37757c335709af51133Felix Janda    else head->data += len;
1887aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1897aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    // Output/free the buffer.
1904b9c20351f5facff4b84c37757c335709af51133Felix Janda    llist_traverse(head, dump_chunk);
1917aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1924b9c20351f5facff4b84c37757c335709af51133Felix Janda    free(l);
1937aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  // Measuring from the beginning of the file.
1947aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  } else for (;;) {
1957aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    int len, offset = 0;
1967aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
1977aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    // Error while reading does not exit.  Error writing does.
1987aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    len = read(fd, toybuf, sizeof(toybuf));
1997aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    if (len<1) break;
2007aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    while (bytes > 1 || lines > 1) {
2017aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      bytes--;
2027aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      if (toybuf[offset++] == '\n') lines--;
2037aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley      if (offset >= len) break;
2047aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    }
2057aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley    if (offset<len) xwrite(1, toybuf+offset, len-offset);
2067aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  }
2077aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley
2087aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  // -f support: cache name/descriptor
20950591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott}
21050591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
21150591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliottvoid tail_main(void)
21250591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott{
2137aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  // if nothing specified, default -n to -10
2147aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  if (!(toys.optflags&(FLAG_n|FLAG_c))) TT.lines = -10;
21550591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott
2167aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  loopfiles(toys.optargs, do_tail);
2172c48247a01a19c709f693d649d8158bccb5fbf70Rob Landley
2187aa651a6a4496d848f86de9b1e6b3a003256a01fRob Landley  // do -f stuff
21950591238eb4e70bf2bca59f536fd4f2253cb57f9Timothy Elliott}
220