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