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