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