1/* vi: set sw=4 ts=4: */ 2/* 3 * Mini tail implementation for busybox 4 * 5 * Copyright (C) 2001 by Matt Kraai <kraai@alumni.carnegiemellon.edu> 6 * 7 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. 8 */ 9 10/* BB_AUDIT SUSv3 compliant (need fancy for -c) */ 11/* BB_AUDIT GNU compatible -c, -q, and -v options in 'fancy' configuration. */ 12/* http://www.opengroup.org/onlinepubs/007904975/utilities/tail.html */ 13 14/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) 15 * 16 * Pretty much rewritten to fix numerous bugs and reduce realloc() calls. 17 * Bugs fixed (although I may have forgotten one or two... it was pretty bad) 18 * 1) mixing printf/write without fflush()ing stdout 19 * 2) no check that any open files are present 20 * 3) optstring had -q taking an arg 21 * 4) no error checking on write in some cases, and a warning even then 22 * 5) q and s interaction bug 23 * 6) no check for lseek error 24 * 7) lseek attempted when count==0 even if arg was +0 (from top) 25 */ 26 27#include "libbb.h" 28 29static const struct suffix_mult tail_suffixes[] = { 30 { "b", 512 }, 31 { "k", 1024 }, 32 { "m", 1024*1024 }, 33 { "", 0 } 34}; 35 36struct globals { 37 bool status; 38} FIX_ALIASING; 39#define G (*(struct globals*)&bb_common_bufsiz1) 40 41static void tail_xprint_header(const char *fmt, const char *filename) 42{ 43 if (fdprintf(STDOUT_FILENO, fmt, filename) < 0) 44 bb_perror_nomsg_and_die(); 45} 46 47static ssize_t tail_read(int fd, char *buf, size_t count) 48{ 49 ssize_t r; 50 off_t current; 51 struct stat sbuf; 52 53 /* /proc files report zero st_size, don't lseek them. */ 54 if (fstat(fd, &sbuf) == 0 && sbuf.st_size > 0) { 55 current = lseek(fd, 0, SEEK_CUR); 56 if (sbuf.st_size < current) 57 xlseek(fd, 0, SEEK_SET); 58 } 59 60 r = full_read(fd, buf, count); 61 if (r < 0) { 62 bb_perror_msg(bb_msg_read_error); 63 G.status = EXIT_FAILURE; 64 } 65 66 return r; 67} 68 69#define header_fmt_str "\n==> %s <==\n" 70 71static unsigned eat_num(const char *p) 72{ 73 if (*p == '-') 74 p++; 75 else if (*p == '+') { 76 p++; 77 G.status = 1; /* mark that we saw "+" */ 78 } 79 return xatou_sfx(p, tail_suffixes); 80} 81 82int tail_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 83int tail_main(int argc, char **argv) 84{ 85 unsigned count = 10; 86 unsigned sleep_period = 1; 87 bool from_top; 88 const char *str_c, *str_n; 89 90 char *tailbuf; 91 size_t tailbufsize; 92 unsigned header_threshhold = 1; 93 unsigned nfiles; 94 int i, opt; 95 96 int *fds; 97 const char *fmt; 98 99#if ENABLE_INCLUDE_SUSv2 || ENABLE_FEATURE_FANCY_TAIL 100 /* Allow legacy syntax of an initial numeric option without -n. */ 101 if (argv[1] && (argv[1][0] == '+' || argv[1][0] == '-') 102 && isdigit(argv[1][1]) 103 ) { 104 count = eat_num(argv[1]); 105 argv++; 106 argc--; 107 } 108#endif 109 110 /* -s NUM, -F imlies -f */ 111 IF_FEATURE_FANCY_TAIL(opt_complementary = "s+:Ff";) 112 opt = getopt32(argv, "fc:n:" IF_FEATURE_FANCY_TAIL("qs:vF"), 113 &str_c, &str_n IF_FEATURE_FANCY_TAIL(,&sleep_period)); 114#define FOLLOW (opt & 0x1) 115#define COUNT_BYTES (opt & 0x2) 116 //if (opt & 0x1) // -f 117 if (opt & 0x2) count = eat_num(str_c); // -c 118 if (opt & 0x4) count = eat_num(str_n); // -n 119#if ENABLE_FEATURE_FANCY_TAIL 120 /* q: make it impossible for nfiles to be > header_threshhold */ 121 if (opt & 0x8) header_threshhold = UINT_MAX; // -q 122 //if (opt & 0x10) // -s 123 if (opt & 0x20) header_threshhold = 0; // -v 124# define FOLLOW_RETRY (opt & 0x40) 125#else 126# define FOLLOW_RETRY 0 127#endif 128 argc -= optind; 129 argv += optind; 130 from_top = G.status; /* 1 if there was "-c +N" or "-n +N" */ 131 G.status = EXIT_SUCCESS; 132 133 /* open all the files */ 134 fds = xmalloc(sizeof(fds[0]) * (argc + 1)); 135 if (!argv[0]) { 136 struct stat statbuf; 137 138 if (fstat(STDIN_FILENO, &statbuf) == 0 139 && S_ISFIFO(statbuf.st_mode) 140 ) { 141 opt &= ~1; /* clear FOLLOW */ 142 } 143 argv[0] = (char *) bb_msg_standard_input; 144 } 145 nfiles = i = 0; 146 do { 147 int fd = open_or_warn_stdin(argv[i]); 148 if (fd < 0 && !FOLLOW_RETRY) { 149 G.status = EXIT_FAILURE; 150 continue; 151 } 152 fds[nfiles] = fd; 153 argv[nfiles++] = argv[i]; 154 } while (++i < argc); 155 156 if (!nfiles) 157 bb_error_msg_and_die("no files"); 158 159 /* prepare the buffer */ 160 tailbufsize = BUFSIZ; 161 if (!from_top && COUNT_BYTES) { 162 if (tailbufsize < count + BUFSIZ) { 163 tailbufsize = count + BUFSIZ; 164 } 165 } 166 tailbuf = xmalloc(tailbufsize); 167 168 /* tail the files */ 169 fmt = header_fmt_str + 1; /* skip header leading newline on first output */ 170 i = 0; 171 do { 172 char *buf; 173 int taillen; 174 int newlines_seen; 175 unsigned seen; 176 int nread; 177 int fd = fds[i]; 178 179 if (ENABLE_FEATURE_FANCY_TAIL && fd < 0) 180 continue; /* may happen with -E */ 181 182 if (nfiles > header_threshhold) { 183 tail_xprint_header(fmt, argv[i]); 184 fmt = header_fmt_str; 185 } 186 187 if (!from_top) { 188 off_t current = lseek(fd, 0, SEEK_END); 189 if (current > 0) { 190 unsigned off; 191 if (COUNT_BYTES) { 192 /* Optimizing count-bytes case if the file is seekable. 193 * Beware of backing up too far. 194 * Also we exclude files with size 0 (because of /proc/xxx) */ 195 if (count == 0) 196 continue; /* showing zero bytes is easy :) */ 197 current -= count; 198 if (current < 0) 199 current = 0; 200 xlseek(fd, current, SEEK_SET); 201 bb_copyfd_size(fd, STDOUT_FILENO, count); 202 continue; 203 } 204#if 1 /* This is technically incorrect for *LONG* strings, but very useful */ 205 /* Optimizing count-lines case if the file is seekable. 206 * We assume the lines are <64k. 207 * (Users complain that tail takes too long 208 * on multi-gigabyte files) */ 209 off = (count | 0xf); /* for small counts, be more paranoid */ 210 if (off > (INT_MAX / (64*1024))) 211 off = (INT_MAX / (64*1024)); 212 current -= off * (64*1024); 213 if (current < 0) 214 current = 0; 215 xlseek(fd, current, SEEK_SET); 216#endif 217 } 218 } 219 220 buf = tailbuf; 221 taillen = 0; 222 /* "We saw 1st line/byte". 223 * Used only by +N code ("start from Nth", 1-based): */ 224 seen = 1; 225 newlines_seen = 0; 226 while ((nread = tail_read(fd, buf, tailbufsize-taillen)) > 0) { 227 if (from_top) { 228 int nwrite = nread; 229 if (seen < count) { 230 /* We need to skip a few more bytes/lines */ 231 if (COUNT_BYTES) { 232 nwrite -= (count - seen); 233 seen = count; 234 } else { 235 char *s = buf; 236 do { 237 --nwrite; 238 if (*s++ == '\n' && ++seen == count) { 239 break; 240 } 241 } while (nwrite); 242 } 243 } 244 if (nwrite > 0) 245 xwrite(STDOUT_FILENO, buf + nread - nwrite, nwrite); 246 } else if (count) { 247 if (COUNT_BYTES) { 248 taillen += nread; 249 if (taillen > (int)count) { 250 memmove(tailbuf, tailbuf + taillen - count, count); 251 taillen = count; 252 } 253 } else { 254 int k = nread; 255 int newlines_in_buf = 0; 256 257 do { /* count '\n' in last read */ 258 k--; 259 if (buf[k] == '\n') { 260 newlines_in_buf++; 261 } 262 } while (k); 263 264 if (newlines_seen + newlines_in_buf < (int)count) { 265 newlines_seen += newlines_in_buf; 266 taillen += nread; 267 } else { 268 int extra = (buf[nread-1] != '\n'); 269 char *s; 270 271 k = newlines_seen + newlines_in_buf + extra - count; 272 s = tailbuf; 273 while (k) { 274 if (*s == '\n') { 275 k--; 276 } 277 s++; 278 } 279 taillen += nread - (s - tailbuf); 280 memmove(tailbuf, s, taillen); 281 newlines_seen = count - extra; 282 } 283 if (tailbufsize < (size_t)taillen + BUFSIZ) { 284 tailbufsize = taillen + BUFSIZ; 285 tailbuf = xrealloc(tailbuf, tailbufsize); 286 } 287 } 288 buf = tailbuf + taillen; 289 } 290 } /* while (tail_read() > 0) */ 291 if (!from_top) { 292 xwrite(STDOUT_FILENO, tailbuf, taillen); 293 } 294 } while (++i < nfiles); 295 296 tailbuf = xrealloc(tailbuf, BUFSIZ); 297 298 fmt = NULL; 299 300 if (FOLLOW) while (1) { 301 sleep(sleep_period); 302 303 i = 0; 304 do { 305 int nread; 306 const char *filename = argv[i]; 307 int fd = fds[i]; 308 309 if (FOLLOW_RETRY) { 310 struct stat sbuf, fsbuf; 311 312 if (fd < 0 313 || fstat(fd, &fsbuf) < 0 314 || stat(filename, &sbuf) < 0 315 || fsbuf.st_dev != sbuf.st_dev 316 || fsbuf.st_ino != sbuf.st_ino 317 ) { 318 int new_fd; 319 320 if (fd >= 0) 321 close(fd); 322 new_fd = open(filename, O_RDONLY); 323 if (new_fd >= 0) { 324 bb_error_msg("%s has %s; following end of new file", 325 filename, (fd < 0) ? "appeared" : "been replaced" 326 ); 327 } else if (fd >= 0) { 328 bb_perror_msg("%s has become inaccessible", filename); 329 } 330 fds[i] = fd = new_fd; 331 } 332 } 333 if (ENABLE_FEATURE_FANCY_TAIL && fd < 0) 334 continue; 335 if (nfiles > header_threshhold) { 336 fmt = header_fmt_str; 337 } 338 while ((nread = tail_read(fd, tailbuf, BUFSIZ)) > 0) { 339 if (fmt) { 340 tail_xprint_header(fmt, filename); 341 fmt = NULL; 342 } 343 xwrite(STDOUT_FILENO, tailbuf, nread); 344 } 345 } while (++i < nfiles); 346 } 347 if (ENABLE_FEATURE_CLEAN_UP) { 348 free(fds); 349 } 350 return G.status; 351} 352