1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 1992-2007 AT&T Knowledge Ventures * 5 * and is licensed under the * 6 * Common Public License, Version 1.0 * 7 * by AT&T Knowledge Ventures * 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 case 'N': 399 flags |= COUNT; 400 if (s = opt_info.arg) 401 number = num(s, &s, &flags, n); 402 else 403 { 404 number = LINES; 405 flags &= ~(ERROR|NEGATIVE|POSITIVE); 406 s = ""; 407 } 408 if (n=='c' && *s=='f') 409 { 410 s++; 411 flags |= FOLLOW; 412 } 413 if (flags & ERROR) 414 continue; 415 if (flags & (NEGATIVE|POSITIVE)) 416 number = -number; 417 if (opt_info.option[0]=='+') 418 number = -number; 419 continue; 420 case 'f': 421 flags |= FOLLOW; 422 continue; 423 case 'h': 424 if (opt_info.num) 425 flags |= HEADERS; 426 else 427 flags &= ~HEADERS; 428 continue; 429 case 'L': 430 flags |= LOG; 431 continue; 432 case 'q': 433 flags &= ~HEADERS; 434 continue; 435 case 'r': 436 flags |= REVERSE; 437 continue; 438 case 's': 439 flags |= SILENT; 440 continue; 441 case 't': 442 flags |= TIMEOUT; 443 timeout = strelapsed(opt_info.arg, &s, 1); 444 if (*s) 445 error(ERROR_exit(1), "%s: invalid elapsed time", opt_info.arg); 446 continue; 447 case 'v': 448 flags |= VERBOSE; 449 continue; 450 case ':': 451 /* handle old style arguments */ 452 r = s = argv[opt_info.index]; 453 number = num(s, &t, &flags, 0); 454 for (;;) 455 { 456 switch (*t++) 457 { 458 case 0: 459 opt_info.offset = t - r - 1; 460 if (number) 461 number = -number; 462 break; 463 case 'c': 464 delim = -1; 465 continue; 466 case 'f': 467 flags |= FOLLOW; 468 continue; 469 case 'l': 470 delim = '\n'; 471 continue; 472 case 'r': 473 flags |= REVERSE; 474 continue; 475 default: 476 error(2, "%s: invalid suffix", t - 1); 477 opt_info.offset = strlen(r); 478 break; 479 } 480 break; 481 } 482 continue; 483 case '?': 484 error(ERROR_usage(2), "%s", opt_info.arg); 485 break; 486 } 487 break; 488 } 489 argv += opt_info.index; 490 if (!*argv) 491 { 492 flags &= ~HEADERS; 493 if (fstat(0, &st)) 494 error(ERROR_system(0), "/dev/stdin: cannot stat"); 495 else if (FIFO(st.st_mode)) 496 flags &= ~FOLLOW; 497 } 498 else if (!*(argv + 1)) 499 flags &= ~HEADERS; 500 if (flags & REVERSE) 501 { 502 if (delim < 0) 503 error(2, "--reverse requires line mode"); 504 else if (!(flags & COUNT)) 505 number = 0; 506 flags &= ~FOLLOW; 507 } 508 if ((flags & (FOLLOW|TIMEOUT)) == TIMEOUT) 509 { 510 flags &= ~TIMEOUT; 511 timeout = 0; 512 error(ERROR_warn(0), "--timeout ignored for --noforever"); 513 } 514 if ((flags & (LOG|TIMEOUT)) == LOG) 515 { 516 flags &= ~LOG; 517 error(ERROR_warn(0), "--log ignored for --notimeout"); 518 } 519 if (error_info.errors) 520 error(ERROR_usage(2), "%s", optusage(NiL)); 521 if (flags & FOLLOW) 522 { 523 if (!(fp = (Tail_t*)stakalloc(argc * sizeof(Tail_t)))) 524 error(ERROR_system(1), "out of space"); 525 files = 0; 526 s = *argv; 527 do 528 { 529 fp->name = s; 530 fp->sp = 0; 531 if (!init(fp, number, delim, flags)) 532 { 533 fp->expire = timeout ? (NOW + timeout + 1) : 0; 534 if (files) 535 pp->next = fp; 536 else 537 files = fp; 538 pp = fp; 539 fp++; 540 } 541 } while (s && (s = *++argv)); 542 if (!files) 543 return error_info.errors != 0; 544 pp->next = 0; 545 hp = 0; 546 for (;;) 547 { 548 if (sfsync(sfstdout)) 549 error(ERROR_system(1), "write error"); 550 sleep(1); 551 n = 0; 552 pp = 0; 553 fp = files; 554 while (fp) 555 { 556 if (fstat(sffileno(fp->sp), &st)) 557 error(ERROR_system(0), "%s: cannot stat", fp->name); 558 else if (st.st_size > fp->last) 559 { 560 n = 1; 561 if (timeout) 562 fp->expire = NOW + timeout; 563 z = st.st_size - fp->last; 564 i = 0; 565 if ((s = sfreserve(fp->sp, z, SF_LOCKR)) || (z = sfvalue(fp->sp)) && (s = sfreserve(fp->sp, z, SF_LOCKR)) && (i = 1)) 566 { 567 r = 0; 568 for (e = (t = s) + z; t < e; t++) 569 if (*t == '\n') 570 r = t; 571 if (r || i && (r = e)) 572 { 573 if ((flags & (HEADERS|VERBOSE)) && hp != fp) 574 { 575 hp = fp; 576 sfprintf(sfstdout, format, fp->name); 577 format = header_fmt; 578 } 579 z = r - s + 1; 580 fp->last += z; 581 sfwrite(sfstdout, s, z); 582 } 583 else 584 z = 0; 585 sfread(fp->sp, s, z); 586 } 587 goto next; 588 } 589 else if (!timeout || fp->expire > NOW) 590 goto next; 591 else 592 { 593 if (flags & LOG) 594 { 595 i = 3; 596 while (--i && stat(fp->name, &st)) 597 sleep(1); 598 if (i && (fp->dev != st.st_dev || fp->ino != st.st_ino) && !init(fp, 0, 0, flags)) 599 { 600 if (!(flags & SILENT)) 601 error(ERROR_warn(0), "%s: log file change", fp->name); 602 fp->expire = NOW + timeout; 603 goto next; 604 } 605 } 606 if (!(flags & SILENT)) 607 error(ERROR_warn(0), "%s: %s timeout", fp->name, fmtelapsed(timeout, 1)); 608 } 609 if (fp->sp && fp->sp != sfstdin) 610 sfclose(fp->sp); 611 if (pp) 612 pp = pp->next = fp->next; 613 else if (!(files = files->next)) 614 return error_info.errors != 0; 615 fp = fp->next; 616 continue; 617 next: 618 pp = fp; 619 fp = fp->next; 620 } 621 } 622 } 623 else 624 { 625 if (file = *argv) 626 argv++; 627 do 628 { 629 if (!file || streq(file, "-")) 630 { 631 file = "/dev/stdin"; 632 ip = sfstdin; 633 } 634 else if (!(ip = sfopen(NiL, file, "r"))) 635 { 636 error(ERROR_system(0), "%s: cannot open", file); 637 continue; 638 } 639 if (flags & (HEADERS|VERBOSE)) 640 sfprintf(sfstdout, format, file); 641 format = header_fmt; 642 if (number < 0 || !number && (flags & POSITIVE)) 643 { 644 sfset(ip, SF_SHARE, !(flags & FOLLOW)); 645 if (number < -1) 646 sfmove(ip, NiL, -number - 1, delim); 647 if (flags & REVERSE) 648 rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR)); 649 else 650 sfmove(ip, sfstdout, SF_UNBOUND, -1); 651 } 652 else 653 { 654 sfset(ip, SF_SHARE, 0); 655 if ((offset = tailpos(ip, number, delim)) >= 0) 656 { 657 if (flags & REVERSE) 658 rev_line(ip, sfstdout, offset); 659 else 660 { 661 sfseek(ip, offset, SEEK_SET); 662 sfmove(ip, sfstdout, SF_UNBOUND, -1); 663 } 664 } 665 else 666 { 667 op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout; 668 pipetail(ip, op, number, delim); 669 if (flags & REVERSE) 670 { 671 sfseek(op, (Sfoff_t)0, SEEK_SET); 672 rev_line(op, sfstdout, (Sfoff_t)0); 673 sfclose(op); 674 } 675 flags = 0; 676 } 677 } 678 if (ip != sfstdin) 679 sfclose(ip); 680 } while (file = *argv++); 681 } 682 return error_info.errors != 0; 683 } 684