xref: /titanic_50/usr/src/lib/libcmd/common/tail.c (revision 75d94465dbafa487b716482dc36d5150a4ec9853)
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