1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1992-2010 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) 2010-03-23 $\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 cur; 139 Sfoff_t end; 140 unsigned long expire; 141 long dev; 142 long ino; 143 int fifo; 144 }; 145 146 static const char header_fmt[] = "\n==> %s <==\n"; 147 148 /* 149 * if file is seekable, position file to tail location and return offset 150 * otherwise, return -1 151 */ 152 153 static Sfoff_t 154 tailpos(register Sfio_t* fp, register Sfoff_t number, int delim) 155 { 156 register size_t n; 157 register Sfoff_t offset; 158 register Sfoff_t first; 159 register Sfoff_t last; 160 register char* s; 161 register char* t; 162 struct stat st; 163 164 last = sfsize(fp); 165 if ((first = sfseek(fp, (Sfoff_t)0, SEEK_CUR)) < 0) 166 return last || fstat(sffileno(fp), &st) || st.st_size || FIFO(st.st_mode) ? -1 : 0; 167 if (delim < 0) 168 { 169 if ((offset = last - number) < first) 170 return first; 171 return offset; 172 } 173 for (;;) 174 { 175 if ((offset = last - SF_BUFSIZE) < first) 176 offset = first; 177 sfseek(fp, offset, SEEK_SET); 178 n = last - offset; 179 if (!(s = sfreserve(fp, n, SF_LOCKR))) 180 return -1; 181 t = s + n; 182 while (t > s) 183 if (*--t == delim && number-- <= 0) 184 { 185 sfread(fp, s, 0); 186 return offset + (t - s) + 1; 187 } 188 sfread(fp, s, 0); 189 if (offset == first) 190 break; 191 last = offset; 192 } 193 return first; 194 } 195 196 /* 197 * this code handles tail from a pipe without any size limits 198 */ 199 200 static void 201 pipetail(Sfio_t* infile, Sfio_t* outfile, Sfoff_t number, int delim) 202 { 203 register Sfio_t* out; 204 register Sfoff_t n; 205 register Sfoff_t nleft = number; 206 register size_t a = 2 * SF_BUFSIZE; 207 register int fno = 0; 208 Sfoff_t offset[2]; 209 Sfio_t* tmp[2]; 210 211 if (delim < 0 && a > number) 212 a = number; 213 out = tmp[0] = sftmp(a); 214 tmp[1] = sftmp(a); 215 offset[0] = offset[1] = 0; 216 while ((n = sfmove(infile, out, number, delim)) > 0) 217 { 218 offset[fno] = sftell(out); 219 if ((nleft -= n) <= 0) 220 { 221 out = tmp[fno= !fno]; 222 sfseek(out, (Sfoff_t)0, SEEK_SET); 223 nleft = number; 224 } 225 } 226 if (nleft == number) 227 { 228 offset[fno] = 0; 229 fno= !fno; 230 } 231 sfseek(tmp[0], (Sfoff_t)0, SEEK_SET); 232 233 /* 234 * see whether both files are needed 235 */ 236 237 if (offset[fno]) 238 { 239 sfseek(tmp[1], (Sfoff_t)0, SEEK_SET); 240 if ((n = number - nleft) > 0) 241 sfmove(tmp[!fno], NiL, n, delim); 242 if ((n = offset[!fno] - sftell(tmp[!fno])) > 0) 243 sfmove(tmp[!fno], outfile, n, -1); 244 } 245 else 246 fno = !fno; 247 sfmove(tmp[fno], outfile, offset[fno], -1); 248 sfclose(tmp[0]); 249 sfclose(tmp[1]); 250 } 251 252 /* 253 * (re)initialize a tail stream 254 */ 255 256 static int 257 init(Tail_t* tp, Sfoff_t number, int delim, int flags, const char** format) 258 { 259 Sfoff_t offset; 260 Sfio_t* op; 261 struct stat st; 262 263 tp->fifo = 0; 264 if (tp->sp) 265 { 266 offset = 0; 267 if (tp->sp == sfstdin) 268 tp->sp = 0; 269 } 270 else if (!number) 271 offset = 0; 272 else 273 offset = 1; 274 if (!tp->name || streq(tp->name, "-")) 275 { 276 tp->name = "/dev/stdin"; 277 tp->sp = sfstdin; 278 } 279 else if (!(tp->sp = sfopen(tp->sp, tp->name, "r"))) 280 { 281 error(ERROR_system(0), "%s: cannot open", tp->name); 282 return -1; 283 } 284 sfset(tp->sp, SF_SHARE, 0); 285 if (offset) 286 { 287 if (number < 0 || !number && (flags & POSITIVE)) 288 { 289 sfset(tp->sp, SF_SHARE, !(flags & FOLLOW)); 290 if (number < -1) 291 { 292 sfmove(tp->sp, NiL, -number - 1, delim); 293 offset = sfseek(tp->sp, (Sfoff_t)0, SEEK_CUR); 294 } 295 else 296 offset = 0; 297 } 298 else if ((offset = tailpos(tp->sp, number, delim)) >= 0) 299 sfseek(tp->sp, offset, SEEK_SET); 300 else if (fstat(sffileno(tp->sp), &st)) 301 { 302 error(ERROR_system(0), "%s: cannot stat", tp->name); 303 goto bad; 304 } 305 else if (!FIFO(st.st_mode)) 306 { 307 error(ERROR_SYSTEM|2, "%s: cannot position file to tail", tp->name); 308 goto bad; 309 } 310 else 311 { 312 tp->fifo = 1; 313 if (flags & (HEADERS|VERBOSE)) 314 { 315 sfprintf(sfstdout, *format, tp->name); 316 *format = header_fmt; 317 } 318 op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout; 319 pipetail(tp->sp ? tp->sp : sfstdin, op, number, delim); 320 if (flags & REVERSE) 321 { 322 sfseek(op, (Sfoff_t)0, SEEK_SET); 323 rev_line(op, sfstdout, (Sfoff_t)0); 324 sfclose(op); 325 } 326 } 327 } 328 tp->cur = tp->end = offset; 329 if (flags & LOG) 330 { 331 if (fstat(sffileno(tp->sp), &st)) 332 { 333 error(ERROR_system(0), "%s: cannot stat", tp->name); 334 goto bad; 335 } 336 tp->dev = st.st_dev; 337 tp->ino = st.st_ino; 338 } 339 return 0; 340 bad: 341 if (tp->sp != sfstdin) 342 sfclose(tp->sp); 343 tp->sp = 0; 344 return -1; 345 } 346 347 /* 348 * convert number with validity diagnostics 349 */ 350 351 static intmax_t 352 num(register const char* s, char** e, int* f, int o) 353 { 354 intmax_t number; 355 char* t; 356 int c; 357 358 *f &= ~(ERROR|NEGATIVE|POSITIVE); 359 if ((c = *s) == '-') 360 { 361 *f |= NEGATIVE; 362 s++; 363 } 364 else if (c == '+') 365 { 366 *f |= POSITIVE; 367 s++; 368 } 369 while (*s == '0' && isdigit(*(s + 1))) 370 s++; 371 errno = 0; 372 number = strtonll(s, &t, NiL, 0); 373 if (t == s) 374 number = DEFAULT; 375 if (o && *t) 376 { 377 number = 0; 378 *f |= ERROR; 379 error(2, "-%c: %s: invalid numeric argument -- unknown suffix", o, s); 380 } 381 else if (errno) 382 { 383 *f |= ERROR; 384 if (o) 385 error(2, "-%c: %s: invalid numeric argument -- out of range", o, s); 386 else 387 error(2, "%s: invalid numeric argument -- out of range", s); 388 } 389 else 390 { 391 *f |= COUNT; 392 if (t > s && isalpha(*(t - 1))) 393 *f &= ~LINES; 394 if (c == '-') 395 number = -number; 396 } 397 if (e) 398 *e = t; 399 return number; 400 } 401 402 int 403 b_tail(int argc, char** argv, void* context) 404 { 405 register Sfio_t* ip; 406 register int n; 407 register int i; 408 int delim; 409 int flags = HEADERS|LINES; 410 int blocks = 0; 411 char* s; 412 char* t; 413 char* r; 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 ssize_t w; 422 Sfio_t* op; 423 register Tail_t* fp; 424 register Tail_t* pp; 425 register Tail_t* hp; 426 Tail_t* files; 427 428 cmdinit(argc, argv, context, ERROR_CATALOG, 0); 429 for (;;) 430 { 431 switch (n = optget(argv, usage)) 432 { 433 case 0: 434 if (!(flags & FOLLOW) && argv[opt_info.index] && (argv[opt_info.index][0] == '-' || argv[opt_info.index][0] == '+') && !argv[opt_info.index][1]) 435 { 436 number = argv[opt_info.index][0] == '-' ? 10 : -10; 437 flags |= LINES; 438 opt_info.index++; 439 continue; 440 } 441 break; 442 case 'b': 443 blocks = 512; 444 flags &= ~LINES; 445 if (opt_info.option[0] == '+') 446 number = -number; 447 continue; 448 case 'c': 449 flags &= ~LINES; 450 if (opt_info.arg == argv[opt_info.index - 1]) 451 { 452 strtol(opt_info.arg, &s, 10); 453 if (*s) 454 { 455 opt_info.index--; 456 t = ""; 457 goto suffix; 458 } 459 } 460 else if (opt_info.arg && isalpha(*opt_info.arg)) 461 { 462 t = opt_info.arg; 463 goto suffix; 464 } 465 /*FALLTHROUGH*/ 466 case 'n': 467 flags |= COUNT; 468 if (s = opt_info.arg) 469 number = num(s, &s, &flags, n); 470 else 471 { 472 number = DEFAULT; 473 flags &= ~(ERROR|NEGATIVE|POSITIVE); 474 s = ""; 475 } 476 if (n != 'n' && s && isalpha(*s)) 477 { 478 t = s; 479 goto suffix; 480 } 481 if (flags & ERROR) 482 continue; 483 if (flags & (NEGATIVE|POSITIVE)) 484 number = -number; 485 if (opt_info.option[0]=='+') 486 number = -number; 487 continue; 488 case 'f': 489 flags |= FOLLOW; 490 continue; 491 case 'h': 492 if (opt_info.num) 493 flags |= HEADERS; 494 else 495 flags &= ~HEADERS; 496 continue; 497 case 'l': 498 flags |= LINES; 499 if (opt_info.option[0] == '+') 500 number = -number; 501 continue; 502 case 'L': 503 flags |= LOG; 504 continue; 505 case 'q': 506 flags &= ~HEADERS; 507 continue; 508 case 'r': 509 flags |= REVERSE; 510 continue; 511 case 's': 512 flags |= SILENT; 513 continue; 514 case 't': 515 flags |= TIMEOUT; 516 timeout = strelapsed(opt_info.arg, &s, 1); 517 if (*s) 518 error(ERROR_exit(1), "%s: invalid elapsed time [%s]", opt_info.arg, s); 519 continue; 520 case 'v': 521 flags |= VERBOSE; 522 continue; 523 case ':': 524 /* handle old style arguments */ 525 if (!(r = argv[opt_info.index]) || !opt_info.offset) 526 { 527 error(2, "%s", opt_info.arg); 528 break; 529 } 530 s = r + opt_info.offset - 1; 531 if (i = *(s - 1) == '-' || *(s - 1) == '+') 532 s--; 533 if ((number = num(s, &t, &flags, 0)) && i) 534 number = -number; 535 goto compatibility; 536 suffix: 537 r = 0; 538 if (opt_info.option[0] == '+') 539 number = -number; 540 compatibility: 541 for (;;) 542 { 543 switch (*t++) 544 { 545 case 0: 546 if (r) 547 opt_info.offset = t - r - 1; 548 break; 549 case 'c': 550 flags &= ~LINES; 551 continue; 552 case 'f': 553 flags |= FOLLOW; 554 continue; 555 case 'l': 556 flags |= LINES; 557 continue; 558 case 'r': 559 flags |= REVERSE; 560 continue; 561 default: 562 error(2, "%s: invalid suffix", t - 1); 563 if (r) 564 opt_info.offset = strlen(r); 565 break; 566 } 567 break; 568 } 569 continue; 570 case '?': 571 error(ERROR_usage(2), "%s", opt_info.arg); 572 break; 573 } 574 break; 575 } 576 argv += opt_info.index; 577 if (!*argv) 578 { 579 flags &= ~HEADERS; 580 if (fstat(0, &st)) 581 error(ERROR_system(0), "/dev/stdin: cannot stat"); 582 else if (FIFO(st.st_mode)) 583 flags &= ~FOLLOW; 584 } 585 else if (!*(argv + 1)) 586 flags &= ~HEADERS; 587 delim = (flags & LINES) ? '\n' : -1; 588 if (blocks) 589 number *= blocks; 590 if (flags & REVERSE) 591 { 592 if (delim < 0) 593 error(2, "--reverse requires line mode"); 594 if (!(flags & COUNT)) 595 number = -1; 596 flags &= ~FOLLOW; 597 } 598 if ((flags & (FOLLOW|TIMEOUT)) == TIMEOUT) 599 { 600 flags &= ~TIMEOUT; 601 timeout = 0; 602 error(ERROR_warn(0), "--timeout ignored for --noforever"); 603 } 604 if ((flags & (LOG|TIMEOUT)) == LOG) 605 { 606 flags &= ~LOG; 607 error(ERROR_warn(0), "--log ignored for --notimeout"); 608 } 609 if (error_info.errors) 610 error(ERROR_usage(2), "%s", optusage(NiL)); 611 if (flags & FOLLOW) 612 { 613 if (!(fp = (Tail_t*)stakalloc(argc * sizeof(Tail_t)))) 614 error(ERROR_system(1), "out of space"); 615 files = 0; 616 s = *argv; 617 do 618 { 619 fp->name = s; 620 fp->sp = 0; 621 if (!init(fp, number, delim, flags, &format)) 622 { 623 fp->expire = timeout ? (NOW + timeout + 1) : 0; 624 if (files) 625 pp->next = fp; 626 else 627 files = fp; 628 pp = fp; 629 fp++; 630 } 631 } while (s && (s = *++argv)); 632 if (!files) 633 return error_info.errors != 0; 634 pp->next = 0; 635 hp = 0; 636 n = 1; 637 while (fp = files) 638 { 639 if (n) 640 n = 0; 641 else 642 sleep(1); 643 pp = 0; 644 while (fp) 645 { 646 if (fstat(sffileno(fp->sp), &st)) 647 error(ERROR_system(0), "%s: cannot stat", fp->name); 648 else if (fp->fifo || fp->end < st.st_size) 649 { 650 n = 1; 651 if (timeout) 652 fp->expire = NOW + timeout; 653 z = fp->fifo ? SF_UNBOUND : st.st_size - fp->cur; 654 i = 0; 655 if ((s = sfreserve(fp->sp, z, SF_LOCKR)) || (z = sfvalue(fp->sp)) && (s = sfreserve(fp->sp, z, SF_LOCKR)) && (i = 1)) 656 { 657 z = sfvalue(fp->sp); 658 for (r = s + z; r > s && *(r - 1) != '\n'; r--); 659 if ((w = r - s) || i && (w = z)) 660 { 661 if ((flags & (HEADERS|VERBOSE)) && hp != fp) 662 { 663 hp = fp; 664 sfprintf(sfstdout, format, fp->name); 665 format = header_fmt; 666 } 667 fp->cur += w; 668 sfwrite(sfstdout, s, w); 669 } 670 else 671 w = 0; 672 sfread(fp->sp, s, w); 673 fp->end += w; 674 } 675 goto next; 676 } 677 else if (!timeout || fp->expire > NOW) 678 goto next; 679 else 680 { 681 if (flags & LOG) 682 { 683 i = 3; 684 while (--i && stat(fp->name, &st)) 685 sleep(1); 686 if (i && (fp->dev != st.st_dev || fp->ino != st.st_ino) && !init(fp, 0, 0, flags, &format)) 687 { 688 if (!(flags & SILENT)) 689 error(ERROR_warn(0), "%s: log file change", fp->name); 690 fp->expire = NOW + timeout; 691 goto next; 692 } 693 } 694 if (!(flags & SILENT)) 695 error(ERROR_warn(0), "%s: %s timeout", fp->name, fmtelapsed(timeout, 1)); 696 } 697 if (fp->sp && fp->sp != sfstdin) 698 sfclose(fp->sp); 699 if (pp) 700 pp = pp->next = fp->next; 701 else 702 files = files->next; 703 fp = fp->next; 704 continue; 705 next: 706 pp = fp; 707 fp = fp->next; 708 } 709 if (sfsync(sfstdout)) 710 error(ERROR_system(1), "write error"); 711 } 712 } 713 else 714 { 715 if (file = *argv) 716 argv++; 717 do 718 { 719 if (!file || streq(file, "-")) 720 { 721 file = "/dev/stdin"; 722 ip = sfstdin; 723 } 724 else if (!(ip = sfopen(NiL, file, "r"))) 725 { 726 error(ERROR_system(0), "%s: cannot open", file); 727 continue; 728 } 729 if (flags & (HEADERS|VERBOSE)) 730 { 731 sfprintf(sfstdout, format, file); 732 format = header_fmt; 733 } 734 if (number < 0 || !number && (flags & POSITIVE)) 735 { 736 sfset(ip, SF_SHARE, 1); 737 if (number < -1) 738 sfmove(ip, NiL, -number - 1, delim); 739 if (flags & REVERSE) 740 rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR)); 741 else 742 sfmove(ip, sfstdout, SF_UNBOUND, -1); 743 } 744 else 745 { 746 sfset(ip, SF_SHARE, 0); 747 if ((offset = tailpos(ip, number, delim)) >= 0) 748 { 749 if (flags & REVERSE) 750 rev_line(ip, sfstdout, offset); 751 else 752 { 753 sfseek(ip, offset, SEEK_SET); 754 sfmove(ip, sfstdout, SF_UNBOUND, -1); 755 } 756 } 757 else 758 { 759 op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout; 760 pipetail(ip, op, number, delim); 761 if (flags & REVERSE) 762 { 763 sfseek(op, (Sfoff_t)0, SEEK_SET); 764 rev_line(op, sfstdout, (Sfoff_t)0); 765 sfclose(op); 766 } 767 flags = 0; 768 } 769 } 770 if (ip != sfstdin) 771 sfclose(ip); 772 } while (file = *argv++); 773 } 774 return error_info.errors != 0; 775 } 776