1 /* tail.c - copy last lines from input to stdout. 2 * 3 * Copyright 2012 Timothy Elliott <tle (at) holymonkey.com> 4 * 5 * See http://opengroup.org/onlinepubs/9699919799/utilities/tail.html 6 7 USE_TAIL(NEWTOY(tail, "?fc-n-[-cn]", TOYFLAG_USR|TOYFLAG_BIN)) 8 9 config TAIL 10 bool "tail" 11 default y 12 help 13 usage: tail [-n|c NUMBER] [-f] [FILE...] 14 15 Copy last lines from files to stdout. If no files listed, copy from 16 stdin. Filename "-" is a synonym for stdin. 17 18 -n output the last NUMBER lines (default 10), +X counts from start. 19 -c output the last NUMBER bytes, +NUMBER counts from start 20 #-f follow FILE(s), waiting for more data to be appended [TODO] 21 22 config TAIL_SEEK 23 bool "tail seek support" 24 default y 25 depends on TAIL 26 help 27 This version uses lseek, which is faster on large files. 28 */ 29 30 #define FOR_tail 31 #include "toys.h" 32 33 GLOBALS( 34 long lines; 35 long bytes; 36 37 int file_no; 38 ) 39 40 struct line_list { 41 struct line_list *next, *prev; 42 char *data; 43 int len; 44 }; 45 46 static struct line_list *get_chunk(int fd, int len) 47 { 48 struct line_list *line = xmalloc(sizeof(struct line_list)+len); 49 50 memset(line, 0, sizeof(struct line_list)); 51 line->data = ((char *)line) + sizeof(struct line_list); 52 line->len = readall(fd, line->data, len); 53 54 if (line->len < 1) { 55 free(line); 56 return 0; 57 } 58 59 return line; 60 } 61 62 static void dump_chunk(void *ptr) 63 { 64 struct line_list *list = ptr; 65 66 xwrite(1, list->data, list->len); 67 free(list); 68 } 69 70 // Reading through very large files is slow. Using lseek can speed things 71 // up a lot, but isn't applicable to all input (cat | tail). 72 // Note: bytes and lines are negative here. 73 static int try_lseek(int fd, long bytes, long lines) 74 { 75 struct line_list *list = 0, *temp; 76 int flag = 0, chunk = sizeof(toybuf); 77 ssize_t pos = lseek(fd, 0, SEEK_END); 78 79 // If lseek() doesn't work on this stream, return now. 80 if (pos<0) return 0; 81 82 // Seek to the right spot, output data from there. 83 if (bytes) { 84 if (lseek(fd, bytes, SEEK_END)<0) lseek(fd, 0, SEEK_SET); 85 xsendfile(fd, 1); 86 return 1; 87 } 88 89 // Read from end to find enough lines, then output them. 90 91 bytes = pos; 92 while (lines && pos) { 93 int offset; 94 95 // Read in next chunk from end of file 96 if (chunk>pos) chunk = pos; 97 pos -= chunk; 98 if (pos != lseek(fd, pos, SEEK_SET)) { 99 perror_msg("seek failed"); 100 break; 101 } 102 if (!(temp = get_chunk(fd, chunk))) break; 103 temp->next = list; 104 list = temp; 105 106 // Count newlines in this chunk. 107 offset = list->len; 108 while (offset--) { 109 // If the last line ends with a newline, that one doesn't count. 110 if (!flag) flag++; 111 112 // Start outputting data right after newline 113 else if (list->data[offset] == '\n' && !++lines) { 114 offset++; 115 list->data += offset; 116 list->len -= offset; 117 118 break; 119 } 120 } 121 } 122 123 // Output stored data 124 llist_traverse(list, dump_chunk); 125 126 // In case of -f 127 lseek(fd, bytes, SEEK_SET); 128 return 1; 129 } 130 131 // Called for each file listed on command line, and/or stdin 132 static void do_tail(int fd, char *name) 133 { 134 long bytes = TT.bytes, lines = TT.lines; 135 int linepop = 1; 136 137 if (toys.optc > 1) { 138 if (TT.file_no++) xputc('\n'); 139 xprintf("==> %s <==\n", name); 140 } 141 142 // Are we measuring from the end of the file? 143 144 if (bytes<0 || lines<0) { 145 struct line_list *list = 0, *new; 146 147 // The slow codepath is always needed, and can handle all input, 148 // so make lseek support optional. 149 if (CFG_TAIL_SEEK && try_lseek(fd, bytes, lines)) return; 150 151 // Read data until we run out, keep a trailing buffer 152 for (;;) { 153 // Read next page of data, appending to linked list in order 154 if (!(new = get_chunk(fd, sizeof(toybuf)))) break; 155 dlist_add_nomalloc((void *)&list, (void *)new); 156 157 // If tracing bytes, add until we have enough, discarding overflow. 158 if (TT.bytes) { 159 bytes += new->len; 160 if (bytes > 0) { 161 while (list->len <= bytes) { 162 bytes -= list->len; 163 free(dlist_pop(&list)); 164 } 165 list->data += bytes; 166 list->len -= bytes; 167 bytes = 0; 168 } 169 } else { 170 int len = new->len, count; 171 char *try = new->data; 172 173 // First character _after_ a newline starts a new line, which 174 // works even if file doesn't end with a newline 175 for (count=0; count<len; count++) { 176 if (linepop) lines++; 177 linepop = try[count] == '\n'; 178 179 if (lines > 0) { 180 char c; 181 182 do { 183 c = *list->data; 184 if (!--(list->len)) free(dlist_pop(&list)); 185 else list->data++; 186 } while (c != '\n'); 187 lines--; 188 } 189 } 190 } 191 } 192 193 // Output/free the buffer. 194 llist_traverse(list, dump_chunk); 195 196 // Measuring from the beginning of the file. 197 } else for (;;) { 198 int len, offset = 0; 199 200 // Error while reading does not exit. Error writing does. 201 len = read(fd, toybuf, sizeof(toybuf)); 202 if (len<1) break; 203 while (bytes > 1 || lines > 1) { 204 bytes--; 205 if (toybuf[offset++] == '\n') lines--; 206 if (offset >= len) break; 207 } 208 if (offset<len) xwrite(1, toybuf+offset, len-offset); 209 } 210 211 // -f support: cache name/descriptor 212 } 213 214 void tail_main(void) 215 { 216 char **args = toys.optargs; 217 218 if (!(toys.optflags&(FLAG_n|FLAG_c))) { 219 char *arg = *args; 220 221 // handle old "-42" style arguments 222 if (arg && *arg == '-' && arg[1]) { 223 TT.lines = atolx(*(args++)); 224 toys.optc--; 225 } 226 227 // if nothing specified, default -n to -10 228 TT.lines = -10; 229 } 230 231 loopfiles(args, do_tail); 232 233 // do -f stuff 234 } 235