xref: /titanic_44/usr/src/lib/libcmd/common/tail.c (revision b92a49ee0e4c83e644a2e7f8cd7753cd479a412a)
1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 1992-2009 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) 2009-08-25 $\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		last;
139 	unsigned long	expire;
140 	long		dev;
141 	long		ino;
142 	int		fifo;
143 };
144 
145 static const char	header_fmt[] = "\n==> %s <==\n";
146 
147 /*
148  * if file is seekable, position file to tail location and return offset
149  * otherwise, return -1
150  */
151 
152 static Sfoff_t
153 tailpos(register Sfio_t* fp, register Sfoff_t number, int delim)
154 {
155 	register size_t		n;
156 	register Sfoff_t	offset;
157 	register Sfoff_t	first;
158 	register Sfoff_t	last;
159 	register char*		s;
160 	register char*		t;
161 	struct stat		st;
162 
163 	last = sfsize(fp);
164 	if ((first = sfseek(fp, (Sfoff_t)0, SEEK_CUR)) < 0)
165 		return last || fstat(sffileno(fp), &st) || st.st_size || FIFO(st.st_mode) ? -1 : 0;
166 	if (delim < 0)
167 	{
168 		if ((offset = last - number) < first)
169 			return first;
170 		return offset;
171 	}
172 	for (;;)
173 	{
174 		if ((offset = last - SF_BUFSIZE) < first)
175 			offset = first;
176 		sfseek(fp, offset, SEEK_SET);
177 		n = last - offset;
178 		if (!(s = sfreserve(fp, n, SF_LOCKR)))
179 			return -1;
180 		t = s + n;
181 		while (t > s)
182 			if (*--t == delim && number-- <= 0)
183 			{
184 				sfread(fp, s, 0);
185 				return offset + (t - s) + 1;
186 			}
187 		sfread(fp, s, 0);
188 		if (offset == first)
189 			break;
190 		last = offset;
191 	}
192 	return first;
193 }
194 
195 /*
196  * this code handles tail from a pipe without any size limits
197  */
198 
199 static void
200 pipetail(Sfio_t* infile, Sfio_t* outfile, Sfoff_t number, int delim)
201 {
202 	register Sfio_t*	out;
203 	register Sfoff_t	n;
204 	register Sfoff_t	nleft = number;
205 	register size_t		a = 2 * SF_BUFSIZE;
206 	register int		fno = 0;
207 	Sfoff_t			offset[2];
208 	Sfio_t*			tmp[2];
209 
210 	if (delim < 0 && a > number)
211 		a = number;
212 	out = tmp[0] = sftmp(a);
213 	tmp[1] = sftmp(a);
214 	offset[0] = offset[1] = 0;
215 	while ((n = sfmove(infile, out, number, delim)) > 0)
216 	{
217 		offset[fno] = sftell(out);
218 		if ((nleft -= n) <= 0)
219 		{
220 			out = tmp[fno= !fno];
221 			sfseek(out, (Sfoff_t)0, SEEK_SET);
222 			nleft = number;
223 		}
224 	}
225 	if (nleft == number)
226 	{
227 		offset[fno] = 0;
228 		fno= !fno;
229 	}
230 	sfseek(tmp[0], (Sfoff_t)0, SEEK_SET);
231 
232 	/*
233 	 * see whether both files are needed
234 	 */
235 
236 	if (offset[fno])
237 	{
238 		sfseek(tmp[1], (Sfoff_t)0, SEEK_SET);
239 		if ((n = number - nleft) > 0)
240 			sfmove(tmp[!fno], NiL, n, delim);
241 		if ((n = offset[!fno] - sftell(tmp[!fno])) > 0)
242 			sfmove(tmp[!fno], outfile, n, -1);
243 	}
244 	else
245 		fno = !fno;
246 	sfmove(tmp[fno], outfile, offset[fno], -1);
247 	sfclose(tmp[0]);
248 	sfclose(tmp[1]);
249 }
250 
251 /*
252  * (re)initialize a tail stream
253  */
254 
255 static int
256 init(Tail_t* tp, Sfoff_t number, int delim, int flags, const char** format)
257 {
258 	Sfoff_t		offset;
259 	Sfio_t*		op;
260 	struct stat	st;
261 
262 	tp->fifo = 0;
263 	if (tp->sp)
264 	{
265 		offset = 0;
266 		if (tp->sp == sfstdin)
267 			tp->sp = 0;
268 	}
269 	else if (!number)
270 		offset = 0;
271 	else
272 		offset = 1;
273 	if (!tp->name || streq(tp->name, "-"))
274 	{
275 		tp->name = "/dev/stdin";
276 		tp->sp = sfstdin;
277 	}
278 	else if (!(tp->sp = sfopen(tp->sp, tp->name, "r")))
279 	{
280 		error(ERROR_system(0), "%s: cannot open", tp->name);
281 		return -1;
282 	}
283 	sfset(tp->sp, SF_SHARE, 0);
284 	if (offset)
285 	{
286 		if (number < 0 || !number && (flags & POSITIVE))
287 		{
288 			sfset(tp->sp, SF_SHARE, !(flags & FOLLOW));
289 			if (number < -1)
290 			{
291 				sfmove(tp->sp, NiL, -number - 1, delim);
292 				offset = sfseek(tp->sp, (Sfoff_t)0, SEEK_CUR);
293 			}
294 			else
295 				offset = 0;
296 		}
297 		else if ((offset = tailpos(tp->sp, number, delim)) >= 0)
298 			sfseek(tp->sp, offset, SEEK_SET);
299 		else if (fstat(sffileno(tp->sp), &st))
300 		{
301 			error(ERROR_system(0), "%s: cannot stat", tp->name);
302 			goto bad;
303 		}
304 		else if (!FIFO(st.st_mode))
305 		{
306 			error(ERROR_SYSTEM|2, "%s: cannot position file to tail", tp->name);
307 			goto bad;
308 		}
309 		else
310 		{
311 			tp->fifo = 1;
312 			if (flags & (HEADERS|VERBOSE))
313 			{
314 				sfprintf(sfstdout, *format, tp->name);
315 				*format = header_fmt;
316 			}
317 			op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout;
318 			pipetail(tp->sp ? tp->sp : sfstdin, op, number, delim);
319 			if (flags & REVERSE)
320 			{
321 				sfseek(op, (Sfoff_t)0, SEEK_SET);
322 				rev_line(op, sfstdout, (Sfoff_t)0);
323 				sfclose(op);
324 			}
325 		}
326 	}
327 	tp->last = offset;
328 	if (flags & LOG)
329 	{
330 		if (fstat(sffileno(tp->sp), &st))
331 		{
332 			error(ERROR_system(0), "%s: cannot stat", tp->name);
333 			goto bad;
334 		}
335 		tp->dev = st.st_dev;
336 		tp->ino = st.st_ino;
337 	}
338 	return 0;
339  bad:
340 	if (tp->sp != sfstdin)
341 		sfclose(tp->sp);
342 	tp->sp = 0;
343 	return -1;
344 }
345 
346 /*
347  * convert number with validity diagnostics
348  */
349 
350 static intmax_t
351 num(register const char* s, char** e, int* f, int o)
352 {
353 	intmax_t	number;
354 	char*		t;
355 	int		c;
356 
357 	*f &= ~(ERROR|NEGATIVE|POSITIVE);
358 	if ((c = *s) == '-')
359 	{
360 		*f |= NEGATIVE;
361 		s++;
362 	}
363 	else if (c == '+')
364 	{
365 		*f |= POSITIVE;
366 		s++;
367 	}
368 	while (*s == '0' && isdigit(*(s + 1)))
369 		s++;
370 	errno = 0;
371 	number = strtonll(s, &t, NiL, 0);
372 	if (t == s)
373 		number = DEFAULT;
374 	if (o && *t)
375 	{
376 		number = 0;
377 		*f |= ERROR;
378 		error(2, "-%c: %s: invalid numeric argument -- unknown suffix", o, s);
379 	}
380 	else if (errno)
381 	{
382 		*f |= ERROR;
383 		if (o)
384 			error(2, "-%c: %s: invalid numeric argument -- out of range", o, s);
385 		else
386 			error(2, "%s: invalid numeric argument -- out of range", s);
387 	}
388 	else
389 	{
390 		*f |= COUNT;
391 		if (t > s && isalpha(*(t - 1)))
392 			*f &= ~LINES;
393 		if (c == '-')
394 			number = -number;
395 	}
396 	if (e)
397 		*e = t;
398 	return number;
399 }
400 
401 int
402 b_tail(int argc, char** argv, void* context)
403 {
404 	register Sfio_t*	ip;
405 	register int		n;
406 	register int		i;
407 	int			delim;
408 	int			flags = HEADERS|LINES;
409 	int			blocks = 0;
410 	char*			s;
411 	char*			t;
412 	char*			r;
413 	char*			e;
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 	Sfio_t*			op;
422 	register Tail_t*	fp;
423 	register Tail_t*	pp;
424 	register Tail_t*	hp;
425 	Tail_t*			files;
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", opt_info.arg);
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 		while (fp = files)
636 		{
637 			if (sfsync(sfstdout))
638 				error(ERROR_system(1), "write error");
639 #if 0
640 			sleep(1);
641 #else
642 			{
643 				struct timespec rqt = { 0L, 1000000000L/4L };
644 				(void)nanosleep(&rqt, NULL);
645 			}
646 #endif
647 			n = 0;
648 			pp = 0;
649 			while (fp)
650 			{
651 				if (fstat(sffileno(fp->sp), &st))
652 					error(ERROR_system(0), "%s: cannot stat", fp->name);
653 				else if (st.st_size > fp->last || fp->fifo)
654 				{
655 					n = 1;
656 					if (timeout)
657 						fp->expire = NOW + timeout;
658 					z = fp->fifo ? SF_UNBOUND : st.st_size - fp->last;
659 					i = 0;
660 					if ((s = sfreserve(fp->sp, z, SF_LOCKR)) || (z = sfvalue(fp->sp)) && (s = sfreserve(fp->sp, z, SF_LOCKR)) && (i = 1))
661 					{
662 						if (fp->fifo)
663 							z = sfvalue(fp->sp);
664 						r = 0;
665 						for (e = (t = s) + z; t < e; t++)
666 							if (*t == '\n')
667 								r = t;
668 						if (r || i && (r = e))
669 						{
670 							if ((flags & (HEADERS|VERBOSE)) && hp != fp)
671 							{
672 								hp = fp;
673 								sfprintf(sfstdout, format, fp->name);
674 								format = header_fmt;
675 							}
676 							z = r - s + 1;
677 							fp->last += z;
678 							sfwrite(sfstdout, s, z);
679 						}
680 						else
681 							z = 0;
682 						sfread(fp->sp, s, z);
683 					}
684 					goto next;
685 				}
686 				else if (!timeout || fp->expire > NOW)
687 					goto next;
688 				else
689 				{
690 					if (flags & LOG)
691 					{
692 						i = 3;
693 						while (--i && stat(fp->name, &st))
694 							sleep(1);
695 						if (i && (fp->dev != st.st_dev || fp->ino != st.st_ino) && !init(fp, 0, 0, flags, &format))
696 						{
697 							if (!(flags & SILENT))
698 								error(ERROR_warn(0), "%s: log file change", fp->name);
699 							fp->expire = NOW + timeout;
700 							goto next;
701 						}
702 					}
703 					if (!(flags & SILENT))
704 						error(ERROR_warn(0), "%s: %s timeout", fp->name, fmtelapsed(timeout, 1));
705 				}
706 				if (fp->sp && fp->sp != sfstdin)
707 					sfclose(fp->sp);
708 				if (pp)
709 					pp = pp->next = fp->next;
710 				else if (!(files = files->next))
711 					return error_info.errors != 0;
712 				fp = fp->next;
713 				continue;
714 			next:
715 				pp = fp;
716 				fp = fp->next;
717 			}
718 		}
719 	}
720 	else
721 	{
722 		if (file = *argv)
723 			argv++;
724 		do
725 		{
726 			if (!file || streq(file, "-"))
727 			{
728 				file = "/dev/stdin";
729 				ip = sfstdin;
730 			}
731 			else if (!(ip = sfopen(NiL, file, "r")))
732 			{
733 				error(ERROR_system(0), "%s: cannot open", file);
734 				continue;
735 			}
736 			if (flags & (HEADERS|VERBOSE))
737 			{
738 				sfprintf(sfstdout, format, file);
739 				format = header_fmt;
740 			}
741 			if (number < 0 || !number && (flags & POSITIVE))
742 			{
743 				sfset(ip, SF_SHARE, 1);
744 				if (number < -1)
745 					sfmove(ip, NiL, -number - 1, delim);
746 				if (flags & REVERSE)
747 					rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR));
748 				else
749 					sfmove(ip, sfstdout, SF_UNBOUND, -1);
750 			}
751 			else
752 			{
753 				sfset(ip, SF_SHARE, 0);
754 				if ((offset = tailpos(ip, number, delim)) >= 0)
755 				{
756 					if (flags & REVERSE)
757 						rev_line(ip, sfstdout, offset);
758 					else
759 					{
760 						sfseek(ip, offset, SEEK_SET);
761 						sfmove(ip, sfstdout, SF_UNBOUND, -1);
762 					}
763 				}
764 				else
765 				{
766 					op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout;
767 					pipetail(ip, op, number, delim);
768 					if (flags & REVERSE)
769 					{
770 						sfseek(op, (Sfoff_t)0, SEEK_SET);
771 						rev_line(op, sfstdout, (Sfoff_t)0);
772 						sfclose(op);
773 					}
774 					flags = 0;
775 				}
776 			}
777 			if (ip != sfstdin)
778 				sfclose(ip);
779 		} while (file = *argv++);
780 	}
781 	return error_info.errors != 0;
782 }
783