1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1992-2012 AT&T Intellectual Property * 5 * and is licensed under the * 6 * Eclipse Public License, Version 1.0 * 7 * by AT&T Intellectual Property * 8 * * 9 * A copy of the License is available at * 10 * http://www.eclipse.org/org/documents/epl-v10.html * 11 * (with md5 checksum b35adb5213ca9657e911e9befb180842) * 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) 2012-06-19 $\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 <tv.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 271 offset = 1; 272 if (!tp->name || streq(tp->name, "-")) 273 { 274 tp->name = "/dev/stdin"; 275 tp->sp = sfstdin; 276 } 277 else if (!(tp->sp = sfopen(tp->sp, tp->name, "r"))) 278 { 279 error(ERROR_system(0), "%s: cannot open", tp->name); 280 return -1; 281 } 282 sfset(tp->sp, SF_SHARE, 0); 283 if (offset) 284 { 285 if (number < 0 || !number && (flags & POSITIVE)) 286 { 287 sfset(tp->sp, SF_SHARE, !(flags & FOLLOW)); 288 if (number < -1) 289 { 290 sfmove(tp->sp, NiL, -number - 1, delim); 291 offset = sfseek(tp->sp, (Sfoff_t)0, SEEK_CUR); 292 } 293 else 294 offset = 0; 295 } 296 else if ((offset = tailpos(tp->sp, number, delim)) >= 0) 297 sfseek(tp->sp, offset, SEEK_SET); 298 else if (fstat(sffileno(tp->sp), &st)) 299 { 300 error(ERROR_system(0), "%s: cannot stat", tp->name); 301 goto bad; 302 } 303 else if (!FIFO(st.st_mode)) 304 { 305 error(ERROR_SYSTEM|2, "%s: cannot position file to tail", tp->name); 306 goto bad; 307 } 308 else 309 { 310 tp->fifo = 1; 311 if (flags & (HEADERS|VERBOSE)) 312 { 313 sfprintf(sfstdout, *format, tp->name); 314 *format = header_fmt; 315 } 316 op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout; 317 pipetail(tp->sp ? tp->sp : sfstdin, op, number, delim); 318 if (flags & REVERSE) 319 { 320 sfseek(op, (Sfoff_t)0, SEEK_SET); 321 rev_line(op, sfstdout, (Sfoff_t)0); 322 sfclose(op); 323 } 324 } 325 } 326 tp->cur = tp->end = offset; 327 if (flags & LOG) 328 { 329 if (fstat(sffileno(tp->sp), &st)) 330 { 331 error(ERROR_system(0), "%s: cannot stat", tp->name); 332 goto bad; 333 } 334 tp->dev = st.st_dev; 335 tp->ino = st.st_ino; 336 } 337 return 0; 338 bad: 339 if (tp->sp != sfstdin) 340 sfclose(tp->sp); 341 tp->sp = 0; 342 return -1; 343 } 344 345 /* 346 * convert number with validity diagnostics 347 */ 348 349 static intmax_t 350 num(register const char* s, char** e, int* f, int o) 351 { 352 intmax_t number; 353 char* t; 354 int c; 355 356 *f &= ~(ERROR|NEGATIVE|POSITIVE); 357 if ((c = *s) == '-') 358 { 359 *f |= NEGATIVE; 360 s++; 361 } 362 else if (c == '+') 363 { 364 *f |= POSITIVE; 365 s++; 366 } 367 while (*s == '0' && isdigit(*(s + 1))) 368 s++; 369 errno = 0; 370 number = strtonll(s, &t, NiL, 0); 371 if (t == s) 372 number = DEFAULT; 373 if (o && *t) 374 { 375 number = 0; 376 *f |= ERROR; 377 error(2, "-%c: %s: invalid numeric argument -- unknown suffix", o, s); 378 } 379 else if (errno) 380 { 381 *f |= ERROR; 382 if (o) 383 error(2, "-%c: %s: invalid numeric argument -- out of range", o, s); 384 else 385 error(2, "%s: invalid numeric argument -- out of range", s); 386 } 387 else 388 { 389 *f |= COUNT; 390 if (t > s && isalpha(*(t - 1))) 391 *f &= ~LINES; 392 if (c == '-') 393 number = -number; 394 } 395 if (e) 396 *e = t; 397 return number; 398 } 399 400 int 401 b_tail(int argc, char** argv, Shbltin_t* context) 402 { 403 register Sfio_t* ip; 404 register int n; 405 register int i; 406 int delim; 407 int flags = HEADERS|LINES; 408 int blocks = 0; 409 char* s; 410 char* t; 411 char* r; 412 char* file; 413 Sfoff_t offset; 414 Sfoff_t number = DEFAULT; 415 unsigned long timeout = 0; 416 struct stat st; 417 const char* format = header_fmt+1; 418 ssize_t z; 419 ssize_t w; 420 Sfio_t* op; 421 register Tail_t* fp; 422 register Tail_t* pp; 423 register Tail_t* hp; 424 Tail_t* files; 425 Tv_t tv; 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 [%s]", opt_info.arg, s); 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 n = 1; 636 tv.tv_sec = 1; 637 tv.tv_nsec = 0; 638 while (fp = files) 639 { 640 if (n) 641 n = 0; 642 else if (sh_checksig(context) || tvsleep(&tv, NiL)) 643 { 644 error_info.errors++; 645 break; 646 } 647 pp = 0; 648 while (fp) 649 { 650 if (fstat(sffileno(fp->sp), &st)) 651 error(ERROR_system(0), "%s: cannot stat", fp->name); 652 else if (fp->fifo || fp->end < st.st_size) 653 { 654 n = 1; 655 if (timeout) 656 fp->expire = NOW + timeout; 657 z = fp->fifo ? SF_UNBOUND : st.st_size - fp->cur; 658 i = 0; 659 if ((s = sfreserve(fp->sp, z, SF_LOCKR)) || (z = sfvalue(fp->sp)) && (s = sfreserve(fp->sp, z, SF_LOCKR)) && (i = 1)) 660 { 661 z = sfvalue(fp->sp); 662 for (r = s + z; r > s && *(r - 1) != '\n'; r--); 663 if ((w = r - s) || i && (w = z)) 664 { 665 if ((flags & (HEADERS|VERBOSE)) && hp != fp) 666 { 667 hp = fp; 668 sfprintf(sfstdout, format, fp->name); 669 format = header_fmt; 670 } 671 fp->cur += w; 672 sfwrite(sfstdout, s, w); 673 } 674 else 675 w = 0; 676 sfread(fp->sp, s, w); 677 fp->end += w; 678 } 679 goto next; 680 } 681 else if (!timeout || fp->expire > NOW) 682 goto next; 683 else 684 { 685 if (flags & LOG) 686 { 687 i = 3; 688 while (--i && stat(fp->name, &st)) 689 if (sh_checksig(context) || tvsleep(&tv, NiL)) 690 { 691 error_info.errors++; 692 goto done; 693 } 694 if (i && (fp->dev != st.st_dev || fp->ino != st.st_ino) && !init(fp, 0, 0, flags, &format)) 695 { 696 if (!(flags & SILENT)) 697 error(ERROR_warn(0), "%s: log file change", fp->name); 698 fp->expire = NOW + timeout; 699 goto next; 700 } 701 } 702 if (!(flags & SILENT)) 703 error(ERROR_warn(0), "%s: %s timeout", fp->name, fmtelapsed(timeout, 1)); 704 } 705 if (fp->sp && fp->sp != sfstdin) 706 sfclose(fp->sp); 707 if (pp) 708 pp = pp->next = fp->next; 709 else 710 files = files->next; 711 fp = fp->next; 712 continue; 713 next: 714 pp = fp; 715 fp = fp->next; 716 } 717 if (sfsync(sfstdout)) 718 error(ERROR_system(1), "write error"); 719 } 720 done: 721 for (fp = files; fp; fp = fp->next) 722 if (fp->sp && fp->sp != sfstdin) 723 sfclose(fp->sp); 724 } 725 else 726 { 727 if (file = *argv) 728 argv++; 729 do 730 { 731 if (!file || streq(file, "-")) 732 { 733 file = "/dev/stdin"; 734 ip = sfstdin; 735 } 736 else if (!(ip = sfopen(NiL, file, "r"))) 737 { 738 error(ERROR_system(0), "%s: cannot open", file); 739 continue; 740 } 741 if (flags & (HEADERS|VERBOSE)) 742 { 743 sfprintf(sfstdout, format, file); 744 format = header_fmt; 745 } 746 if (number < 0 || !number && (flags & POSITIVE)) 747 { 748 sfset(ip, SF_SHARE, 1); 749 if (number < -1) 750 sfmove(ip, NiL, -number - 1, delim); 751 if (flags & REVERSE) 752 rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR)); 753 else 754 sfmove(ip, sfstdout, SF_UNBOUND, -1); 755 } 756 else 757 { 758 sfset(ip, SF_SHARE, 0); 759 if ((offset = tailpos(ip, number, delim)) >= 0) 760 { 761 if (flags & REVERSE) 762 rev_line(ip, sfstdout, offset); 763 else 764 { 765 sfseek(ip, offset, SEEK_SET); 766 sfmove(ip, sfstdout, SF_UNBOUND, -1); 767 } 768 } 769 else 770 { 771 op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout; 772 pipetail(ip, op, number, delim); 773 if (flags & REVERSE) 774 { 775 sfseek(op, (Sfoff_t)0, SEEK_SET); 776 rev_line(op, sfstdout, (Sfoff_t)0); 777 sfclose(op); 778 } 779 flags = 0; 780 } 781 } 782 if (ip != sfstdin) 783 sfclose(ip); 784 } while ((file = *argv++) && !sh_checksig(context)); 785 } 786 return error_info.errors != 0; 787 } 788