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