1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1992-2008 AT&T Intellectual Property * 5 * and is licensed under the * 6 * Common Public License, Version 1.0 * 7 * by AT&T Intellectual Property * 8 * * 9 * A copy of the License is available at * 10 * http://www.opensource.org/licenses/cpl1.0.txt * 11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 12 * * 13 * Information and Software Systems Research * 14 * AT&T Research * 15 * Florham Park NJ * 16 * * 17 * Glenn Fowler <gsf@research.att.com> * 18 * David Korn <dgk@research.att.com> * 19 * * 20 ***********************************************************************/ 21 #pragma prototyped 22 23 /* 24 * print the tail of one or more files 25 * 26 * David Korn 27 * Glenn Fowler 28 */ 29 30 static const char usage[] = 31 "+[-?\n@(#)$Id: tail (AT&T Research) 2006-10-18 $\n]" 32 USAGE_LICENSE 33 "[+NAME?tail - output trailing portion of one or more files ]" 34 "[+DESCRIPTION?\btail\b copies one or more input files to standard output " 35 "starting at a designated point for each file. Copying starts " 36 "at the point indicated by the options and is unlimited in size.]" 37 "[+?By default a header of the form \b==> \b\afilename\a\b <==\b " 38 "is output before all but the first file but this can be changed " 39 "with the \b-q\b and \b-v\b options.]" 40 "[+?If no \afile\a is given, or if the \afile\a is \b-\b, \btail\b " 41 "copies from standard input. The start of the file is defined " 42 "as the current offset.]" 43 "[+?The option argument for \b-c\b can optionally be " 44 "followed by one of the following characters to specify a different " 45 "unit other than a single byte:]{" 46 "[+b?512 bytes.]" 47 "[+k?1-kilobyte.]" 48 "[+m?1-megabyte.]" 49 "}" 50 "[+?For backwards compatibility, \b-\b\anumber\a is equivalent to " 51 "\b-n\b \anumber\a and \b+\b\anumber\a is equivalent to " 52 "\b-n -\b\anumber\a.]" 53 54 "[n:lines]:[lines:=10?Copy \alines\a lines from each file. A negative value " 55 "for \alines\a indicates an offset from the start of the file.]" 56 "[c:bytes]:?[chars?Copy \achars\a bytes from each file. A negative value " 57 "for \achars\a indicates an offset from the start of the file.]" 58 "[f:forever|follow?Loop forever trying to read more characters as the " 59 "end of each file to copy new data. Ignored if reading from a pipe " 60 "or fifo.]" 61 "[h!:headers?Output filename headers.]" 62 "[L:log?When a \b--forever\b file times out via \b--timeout\b, verify that " 63 "the curent file has not been renamed and replaced by another file " 64 "of the same name (a common log file practice) before giving up on " 65 "the file.]" 66 "[q:quiet?Don't output filename headers. For GNU compatibility.]" 67 "[r:reverse?Output lines in reverse order.]" 68 "[s:silent?Don't warn about timeout expiration and log file changes.]" 69 "[t:timeout?Stop checking after \atimeout\a elapses with no additional " 70 "\b--forever\b output. A separate elapsed time is maintained for " 71 "each file operand. There is no timeout by default. The default " 72 "\atimeout\a unit is seconds. \atimeout\a may be a catenation of 1 " 73 "or more integers, each followed by a 1 character suffix. The suffix " 74 "may be omitted from the last integer, in which case it is " 75 "interpreted as seconds. The supported suffixes are:]:[timeout]{" 76 "[+s?seconds]" 77 "[+m?minutes]" 78 "[+h?hours]" 79 "[+d?days]" 80 "[+w?weeks]" 81 "[+M?months]" 82 "[+y?years]" 83 "[+S?scores]" 84 "}" 85 "[v:verbose?Always ouput filename headers.]" 86 "\n" 87 "\n[file ...]\n" 88 "\n" 89 "[+EXIT STATUS?]{" 90 "[+0?All files copied successfully.]" 91 "[+>0?One or more files did not copy.]" 92 "}" 93 "[+SEE ALSO?\bcat\b(1), \bhead\b(1), \brev\b(1)]" 94 ; 95 96 #include <cmd.h> 97 #include <ctype.h> 98 #include <ls.h> 99 #include <tm.h> 100 #include <rev.h> 101 102 #define COUNT (1<<0) 103 #define ERROR (1<<1) 104 #define FOLLOW (1<<2) 105 #define HEADERS (1<<3) 106 #define LOG (1<<4) 107 #define NEGATIVE (1<<5) 108 #define POSITIVE (1<<6) 109 #define REVERSE (1<<7) 110 #define SILENT (1<<8) 111 #define TIMEOUT (1<<9) 112 #define VERBOSE (1<<10) 113 114 #define NOW (unsigned long)time(NiL) 115 116 #define LINES 10 117 118 #ifdef S_ISSOCK 119 #define FIFO(m) (S_ISFIFO(m)||S_ISSOCK(m)) 120 #else 121 #define FIFO(m) S_ISFIFO(m) 122 #endif 123 124 struct Tail_s; typedef struct Tail_s Tail_t; 125 126 struct Tail_s 127 { 128 Tail_t* next; 129 char* name; 130 Sfio_t* sp; 131 Sfoff_t last; 132 unsigned long expire; 133 long dev; 134 long ino; 135 }; 136 137 /* 138 * if file is seekable, position file to tail location and return offset 139 * otherwise, return -1 140 */ 141 142 static Sfoff_t 143 tailpos(register Sfio_t* fp, register Sfoff_t number, int delim) 144 { 145 register size_t n; 146 register Sfoff_t offset; 147 register Sfoff_t first; 148 register Sfoff_t last; 149 register char* s; 150 register char* t; 151 struct stat st; 152 153 last = sfsize(fp); 154 if ((first = sfseek(fp, (Sfoff_t)0, SEEK_CUR)) < 0) 155 return last || fstat(sffileno(fp), &st) || st.st_size || FIFO(st.st_mode) ? -1 : 0; 156 if (delim < 0) 157 { 158 if ((offset = last - number) < first) 159 return first; 160 return offset; 161 } 162 if ((offset = last - SF_BUFSIZE) < first) 163 offset = first; 164 for (;;) 165 { 166 sfseek(fp, offset, SEEK_SET); 167 n = last - offset; 168 if (!(s = sfreserve(fp, n, SF_LOCKR))) 169 return -1; 170 t = s + n; 171 while (t > s) 172 if (*--t == delim && number-- <= 0) 173 { 174 sfread(fp, s, 0); 175 return offset + (t - s) + 1; 176 } 177 sfread(fp, s, 0); 178 if (offset == first) 179 break; 180 last = offset; 181 if ((offset = last - SF_BUFSIZE) < first) 182 offset = first; 183 } 184 return first; 185 } 186 187 /* 188 * this code handles tail from a pipe without any size limits 189 */ 190 191 static void 192 pipetail(Sfio_t* infile, Sfio_t* outfile, Sfoff_t number, int delim) 193 { 194 register Sfio_t* out; 195 register Sfoff_t n; 196 register Sfoff_t nleft = number; 197 register size_t a = 2 * SF_BUFSIZE; 198 register int fno = 0; 199 Sfoff_t offset[2]; 200 Sfio_t* tmp[2]; 201 202 if (delim < 0 && a > number) 203 a = number; 204 out = tmp[0] = sftmp(a); 205 tmp[1] = sftmp(a); 206 offset[0] = offset[1] = 0; 207 while ((n = sfmove(infile, out, number, delim)) > 0) 208 { 209 offset[fno] = sftell(out); 210 if ((nleft -= n) <= 0) 211 { 212 out = tmp[fno= !fno]; 213 sfseek(out, (Sfoff_t)0, SEEK_SET); 214 nleft = number; 215 } 216 } 217 if (nleft == number) 218 { 219 offset[fno] = 0; 220 fno= !fno; 221 } 222 sfseek(tmp[0], (Sfoff_t)0, SEEK_SET); 223 224 /* 225 * see whether both files are needed 226 */ 227 228 if (offset[fno]) 229 { 230 sfseek(tmp[1], (Sfoff_t)0, SEEK_SET); 231 if ((n = number - nleft) > 0) 232 sfmove(tmp[!fno], NiL, n, delim); 233 if ((n = offset[!fno] - sftell(tmp[!fno])) > 0) 234 sfmove(tmp[!fno], outfile, n, -1); 235 } 236 else 237 fno = !fno; 238 sfmove(tmp[fno], outfile, offset[fno], -1); 239 sfclose(tmp[0]); 240 sfclose(tmp[1]); 241 } 242 243 /* 244 * (re)initialize a tail stream 245 */ 246 247 static int 248 init(Tail_t* tp, Sfoff_t number, int delim, int flags) 249 { 250 Sfoff_t offset; 251 struct stat st; 252 253 if (tp->sp) 254 { 255 offset = 0; 256 if (tp->sp == sfstdin) 257 tp->sp = 0; 258 } 259 else if (!number) 260 offset = 0; 261 else 262 offset = 1; 263 if (!tp->name || streq(tp->name, "-")) 264 { 265 tp->name = "/dev/stdin"; 266 tp->sp = sfstdin; 267 } 268 else if (!(tp->sp = sfopen(tp->sp, tp->name, "r"))) 269 { 270 error(ERROR_system(0), "%s: cannot open", tp->name); 271 return -1; 272 } 273 sfset(tp->sp, SF_SHARE, 0); 274 if (offset) 275 { 276 if ((offset = tailpos(tp->sp, number, delim)) < 0) 277 { 278 error(ERROR_SYSTEM|2, "%s: cannot position file to tail", tp->name); 279 goto bad; 280 } 281 sfseek(tp->sp, offset, SEEK_SET); 282 } 283 tp->last = offset; 284 if (flags & LOG) 285 { 286 if (fstat(sffileno(tp->sp), &st)) 287 { 288 error(ERROR_system(0), "%s: cannot stat", tp->name); 289 goto bad; 290 } 291 tp->dev = st.st_dev; 292 tp->ino = st.st_ino; 293 } 294 return 0; 295 bad: 296 if (tp->sp != sfstdin) 297 sfclose(tp->sp); 298 tp->sp = 0; 299 return -1; 300 } 301 302 /* 303 * convert number with validity diagnostics 304 */ 305 306 static intmax_t 307 num(register const char* s, char** e, int* f, int o) 308 { 309 intmax_t number; 310 char* t; 311 int c; 312 313 *f &= ~(ERROR|NEGATIVE|POSITIVE); 314 if ((c = *s) == '-') 315 { 316 *f |= NEGATIVE; 317 s++; 318 } 319 else if (c == '+') 320 { 321 *f |= POSITIVE; 322 s++; 323 } 324 while (*s == '0' && isdigit(*(s + 1))) 325 s++; 326 errno = 0; 327 number = strtonll(s, &t, NiL, 0); 328 if (!o && t > s && *(t - 1) == 'l') 329 t--; 330 if (t == s) 331 number = LINES; 332 if (o && *t) 333 { 334 number = 0; 335 *f |= ERROR; 336 error(2, "-%c: %s: invalid numeric argument -- unknown suffix", o, s); 337 } 338 else if (errno) 339 { 340 *f |= ERROR; 341 if (o) 342 error(2, "-%c: %s: invalid numeric argument -- out of range", o, s); 343 else 344 error(2, "%s: invalid numeric argument -- out of range", s); 345 } 346 else 347 { 348 *f |= COUNT; 349 if (c == '-') 350 number = -number; 351 } 352 if (e) 353 *e = t; 354 return number; 355 } 356 357 int 358 b_tail(int argc, char** argv, void* context) 359 { 360 static const char header_fmt[] = "\n==> %s <==\n"; 361 362 register Sfio_t* ip; 363 register int n; 364 register int i; 365 register int delim = '\n'; 366 int flags = HEADERS; 367 char* s; 368 char* t; 369 char* r; 370 char* e; 371 char* file; 372 Sfoff_t offset; 373 Sfoff_t number = LINES; 374 unsigned long timeout = 0; 375 struct stat st; 376 const char* format = header_fmt+1; 377 size_t z; 378 Sfio_t* op; 379 register Tail_t* fp; 380 register Tail_t* pp; 381 register Tail_t* hp; 382 Tail_t* files; 383 384 cmdinit(argc, argv, context, ERROR_CATALOG, 0); 385 for (;;) 386 { 387 switch (n = optget(argv, usage)) 388 { 389 case 'c': 390 delim = -1; 391 if (opt_info.arg && *opt_info.arg=='f' && !*(opt_info.arg+1)) 392 { 393 flags |= FOLLOW; 394 continue; 395 } 396 /*FALLTHROUGH*/ 397 case 'n': 398 flags |= COUNT; 399 if (s = opt_info.arg) 400 number = num(s, &s, &flags, n); 401 else 402 { 403 number = LINES; 404 flags &= ~(ERROR|NEGATIVE|POSITIVE); 405 s = ""; 406 } 407 if (n=='c' && *s=='f') 408 { 409 s++; 410 flags |= FOLLOW; 411 } 412 if (flags & ERROR) 413 continue; 414 if (flags & (NEGATIVE|POSITIVE)) 415 number = -number; 416 if (opt_info.option[0]=='+') 417 number = -number; 418 continue; 419 case 'f': 420 flags |= FOLLOW; 421 continue; 422 case 'h': 423 if (opt_info.num) 424 flags |= HEADERS; 425 else 426 flags &= ~HEADERS; 427 continue; 428 case 'L': 429 flags |= LOG; 430 continue; 431 case 'q': 432 flags &= ~HEADERS; 433 continue; 434 case 'r': 435 flags |= REVERSE; 436 continue; 437 case 's': 438 flags |= SILENT; 439 continue; 440 case 't': 441 flags |= TIMEOUT; 442 timeout = strelapsed(opt_info.arg, &s, 1); 443 if (*s) 444 error(ERROR_exit(1), "%s: invalid elapsed time", opt_info.arg); 445 continue; 446 case 'v': 447 flags |= VERBOSE; 448 continue; 449 case ':': 450 /* handle old style arguments */ 451 r = s = argv[opt_info.index]; 452 number = num(s, &t, &flags, 0); 453 for (;;) 454 { 455 switch (*t++) 456 { 457 case 0: 458 opt_info.offset = t - r - 1; 459 if (number) 460 number = -number; 461 break; 462 case 'c': 463 delim = -1; 464 continue; 465 case 'f': 466 flags |= FOLLOW; 467 continue; 468 case 'l': 469 delim = '\n'; 470 continue; 471 case 'r': 472 flags |= REVERSE; 473 continue; 474 default: 475 error(2, "%s: invalid suffix", t - 1); 476 opt_info.offset = strlen(r); 477 break; 478 } 479 break; 480 } 481 continue; 482 case '?': 483 error(ERROR_usage(2), "%s", opt_info.arg); 484 break; 485 } 486 break; 487 } 488 argv += opt_info.index; 489 if (!*argv) 490 { 491 flags &= ~HEADERS; 492 if (fstat(0, &st)) 493 error(ERROR_system(0), "/dev/stdin: cannot stat"); 494 else if (FIFO(st.st_mode)) 495 flags &= ~FOLLOW; 496 } 497 else if (!*(argv + 1)) 498 flags &= ~HEADERS; 499 if (flags & REVERSE) 500 { 501 if (delim < 0) 502 error(2, "--reverse requires line mode"); 503 else if (!(flags & COUNT)) 504 number = 0; 505 flags &= ~FOLLOW; 506 } 507 if ((flags & (FOLLOW|TIMEOUT)) == TIMEOUT) 508 { 509 flags &= ~TIMEOUT; 510 timeout = 0; 511 error(ERROR_warn(0), "--timeout ignored for --noforever"); 512 } 513 if ((flags & (LOG|TIMEOUT)) == LOG) 514 { 515 flags &= ~LOG; 516 error(ERROR_warn(0), "--log ignored for --notimeout"); 517 } 518 if (error_info.errors) 519 error(ERROR_usage(2), "%s", optusage(NiL)); 520 if (flags & FOLLOW) 521 { 522 if (!(fp = (Tail_t*)stakalloc(argc * sizeof(Tail_t)))) 523 error(ERROR_system(1), "out of space"); 524 files = 0; 525 s = *argv; 526 do 527 { 528 fp->name = s; 529 fp->sp = 0; 530 if (!init(fp, number, delim, flags)) 531 { 532 fp->expire = timeout ? (NOW + timeout + 1) : 0; 533 if (files) 534 pp->next = fp; 535 else 536 files = fp; 537 pp = fp; 538 fp++; 539 } 540 } while (s && (s = *++argv)); 541 if (!files) 542 return error_info.errors != 0; 543 pp->next = 0; 544 hp = 0; 545 for (;;) 546 { 547 if (sfsync(sfstdout)) 548 error(ERROR_system(1), "write error"); 549 sleep(1); 550 n = 0; 551 pp = 0; 552 fp = files; 553 while (fp) 554 { 555 if (fstat(sffileno(fp->sp), &st)) 556 error(ERROR_system(0), "%s: cannot stat", fp->name); 557 else if (st.st_size > fp->last) 558 { 559 n = 1; 560 if (timeout) 561 fp->expire = NOW + timeout; 562 z = st.st_size - fp->last; 563 i = 0; 564 if ((s = sfreserve(fp->sp, z, SF_LOCKR)) || (z = sfvalue(fp->sp)) && (s = sfreserve(fp->sp, z, SF_LOCKR)) && (i = 1)) 565 { 566 r = 0; 567 for (e = (t = s) + z; t < e; t++) 568 if (*t == '\n') 569 r = t; 570 if (r || i && (r = e)) 571 { 572 if ((flags & (HEADERS|VERBOSE)) && hp != fp) 573 { 574 hp = fp; 575 sfprintf(sfstdout, format, fp->name); 576 format = header_fmt; 577 } 578 z = r - s + 1; 579 fp->last += z; 580 sfwrite(sfstdout, s, z); 581 } 582 else 583 z = 0; 584 sfread(fp->sp, s, z); 585 } 586 goto next; 587 } 588 else if (!timeout || fp->expire > NOW) 589 goto next; 590 else 591 { 592 if (flags & LOG) 593 { 594 i = 3; 595 while (--i && stat(fp->name, &st)) 596 sleep(1); 597 if (i && (fp->dev != st.st_dev || fp->ino != st.st_ino) && !init(fp, 0, 0, flags)) 598 { 599 if (!(flags & SILENT)) 600 error(ERROR_warn(0), "%s: log file change", fp->name); 601 fp->expire = NOW + timeout; 602 goto next; 603 } 604 } 605 if (!(flags & SILENT)) 606 error(ERROR_warn(0), "%s: %s timeout", fp->name, fmtelapsed(timeout, 1)); 607 } 608 if (fp->sp && fp->sp != sfstdin) 609 sfclose(fp->sp); 610 if (pp) 611 pp = pp->next = fp->next; 612 else if (!(files = files->next)) 613 return error_info.errors != 0; 614 fp = fp->next; 615 continue; 616 next: 617 pp = fp; 618 fp = fp->next; 619 } 620 } 621 } 622 else 623 { 624 if (file = *argv) 625 argv++; 626 do 627 { 628 if (!file || streq(file, "-")) 629 { 630 file = "/dev/stdin"; 631 ip = sfstdin; 632 } 633 else if (!(ip = sfopen(NiL, file, "r"))) 634 { 635 error(ERROR_system(0), "%s: cannot open", file); 636 continue; 637 } 638 if (flags & (HEADERS|VERBOSE)) 639 sfprintf(sfstdout, format, file); 640 format = header_fmt; 641 if (number < 0 || !number && (flags & POSITIVE)) 642 { 643 sfset(ip, SF_SHARE, !(flags & FOLLOW)); 644 if (number < -1) 645 sfmove(ip, NiL, -number - 1, delim); 646 if (flags & REVERSE) 647 rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR)); 648 else 649 sfmove(ip, sfstdout, SF_UNBOUND, -1); 650 } 651 else 652 { 653 sfset(ip, SF_SHARE, 0); 654 if ((offset = tailpos(ip, number, delim)) >= 0) 655 { 656 if (flags & REVERSE) 657 rev_line(ip, sfstdout, offset); 658 else 659 { 660 sfseek(ip, offset, SEEK_SET); 661 sfmove(ip, sfstdout, SF_UNBOUND, -1); 662 } 663 } 664 else 665 { 666 op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout; 667 pipetail(ip, op, number, delim); 668 if (flags & REVERSE) 669 { 670 sfseek(op, (Sfoff_t)0, SEEK_SET); 671 rev_line(op, sfstdout, (Sfoff_t)0); 672 sfclose(op); 673 } 674 flags = 0; 675 } 676 } 677 if (ip != sfstdin) 678 sfclose(ip); 679 } while (file = *argv++); 680 } 681 return error_info.errors != 0; 682 } 683