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
tailpos(register Sfio_t * fp,register Sfoff_t number,int delim)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
pipetail(Sfio_t * infile,Sfio_t * outfile,Sfoff_t number,int delim)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
init(Tail_t * tp,Sfoff_t number,int delim,int flags,const char ** format)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
num(register const char * s,char ** e,int * f,int o)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
b_tail(int argc,char ** argv,Shbltin_t * context)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