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