xref: /onnv-gate/usr/src/lib/libcmd/common/tail.c (revision 4887)
1*4887Schin /***********************************************************************
2*4887Schin *                                                                      *
3*4887Schin *               This software is part of the ast package               *
4*4887Schin *           Copyright (c) 1992-2007 AT&T Knowledge Ventures            *
5*4887Schin *                      and is licensed under the                       *
6*4887Schin *                  Common Public License, Version 1.0                  *
7*4887Schin *                      by AT&T Knowledge Ventures                      *
8*4887Schin *                                                                      *
9*4887Schin *                A copy of the License is available at                 *
10*4887Schin *            http://www.opensource.org/licenses/cpl1.0.txt             *
11*4887Schin *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
12*4887Schin *                                                                      *
13*4887Schin *              Information and Software Systems Research               *
14*4887Schin *                            AT&T Research                             *
15*4887Schin *                           Florham Park NJ                            *
16*4887Schin *                                                                      *
17*4887Schin *                 Glenn Fowler <gsf@research.att.com>                  *
18*4887Schin *                  David Korn <dgk@research.att.com>                   *
19*4887Schin *                                                                      *
20*4887Schin ***********************************************************************/
21*4887Schin #pragma prototyped
22*4887Schin 
23*4887Schin /*
24*4887Schin  * print the tail of one or more files
25*4887Schin  *
26*4887Schin  *   David Korn
27*4887Schin  *   Glenn Fowler
28*4887Schin  */
29*4887Schin 
30*4887Schin static const char usage[] =
31*4887Schin "+[-?\n@(#)$Id: tail (AT&T Research) 2006-10-18 $\n]"
32*4887Schin USAGE_LICENSE
33*4887Schin "[+NAME?tail - output trailing portion of one or more files ]"
34*4887Schin "[+DESCRIPTION?\btail\b copies one or more input files to standard output "
35*4887Schin 	"starting at a designated point for each file.  Copying starts "
36*4887Schin 	"at the point indicated by the options and is unlimited in size.]"
37*4887Schin "[+?By default a header of the form \b==> \b\afilename\a\b <==\b "
38*4887Schin 	"is output before all but the first file but this can be changed "
39*4887Schin 	"with the \b-q\b and \b-v\b options.]"
40*4887Schin "[+?If no \afile\a is given, or if the \afile\a is \b-\b, \btail\b "
41*4887Schin 	"copies from standard input. The start of the file is defined "
42*4887Schin 	"as the current offset.]"
43*4887Schin "[+?The option argument for \b-c\b can optionally be "
44*4887Schin 	"followed by one of the following characters to specify a different "
45*4887Schin 	"unit other than a single byte:]{"
46*4887Schin 		"[+b?512 bytes.]"
47*4887Schin 		"[+k?1-kilobyte.]"
48*4887Schin 		"[+m?1-megabyte.]"
49*4887Schin 	"}"
50*4887Schin "[+?For backwards compatibility, \b-\b\anumber\a  is equivalent to "
51*4887Schin 	"\b-n\b \anumber\a and \b+\b\anumber\a is equivalent to "
52*4887Schin 	"\b-n -\b\anumber\a.]"
53*4887Schin 
54*4887Schin "[n:lines]:[lines:=10?Copy \alines\a lines from each file.  A negative value "
55*4887Schin 	"for \alines\a indicates an offset from the start of the file.]"
56*4887Schin "[c:bytes]:?[chars?Copy \achars\a bytes from each file.  A negative value "
57*4887Schin 	"for \achars\a indicates an offset from the start of the file.]"
58*4887Schin "[f:forever|follow?Loop forever trying to read more characters as the "
59*4887Schin 	"end of each file to copy new data. Ignored if reading from a pipe "
60*4887Schin 	"or fifo.]"
61*4887Schin "[h!:headers?Output filename headers.]"
62*4887Schin "[L:log?When a \b--forever\b file times out via \b--timeout\b, verify that "
63*4887Schin 	"the curent file has not been renamed and replaced by another file "
64*4887Schin 	"of the same name (a common log file practice) before giving up on "
65*4887Schin 	"the file.]"
66*4887Schin "[q:quiet?Don't output filename headers. For GNU compatibility.]"
67*4887Schin "[r:reverse?Output lines in reverse order.]"
68*4887Schin "[s:silent?Don't warn about timeout expiration and log file changes.]"
69*4887Schin "[t:timeout?Stop checking after \atimeout\a elapses with no additional "
70*4887Schin 	"\b--forever\b output. A separate elapsed time is maintained for "
71*4887Schin 	"each file operand. There is no timeout by default. The default "
72*4887Schin 	"\atimeout\a unit is seconds. \atimeout\a may be a catenation of 1 "
73*4887Schin 	"or more integers, each followed by a 1 character suffix. The suffix "
74*4887Schin 	"may be omitted from the last integer, in which case it is "
75*4887Schin 	"interpreted as seconds. The supported suffixes are:]:[timeout]{"
76*4887Schin 		"[+s?seconds]"
77*4887Schin 		"[+m?minutes]"
78*4887Schin 		"[+h?hours]"
79*4887Schin 		"[+d?days]"
80*4887Schin 		"[+w?weeks]"
81*4887Schin 		"[+M?months]"
82*4887Schin 		"[+y?years]"
83*4887Schin 		"[+S?scores]"
84*4887Schin 	"}"
85*4887Schin "[v:verbose?Always ouput filename headers.]"
86*4887Schin "\n"
87*4887Schin "\n[file ...]\n"
88*4887Schin "\n"
89*4887Schin "[+EXIT STATUS?]{"
90*4887Schin 	"[+0?All files copied successfully.]"
91*4887Schin 	"[+>0?One or more files did not copy.]"
92*4887Schin "}"
93*4887Schin "[+SEE ALSO?\bcat\b(1), \bhead\b(1), \brev\b(1)]"
94*4887Schin ;
95*4887Schin 
96*4887Schin #include <cmd.h>
97*4887Schin #include <ctype.h>
98*4887Schin #include <ls.h>
99*4887Schin #include <tm.h>
100*4887Schin #include <rev.h>
101*4887Schin 
102*4887Schin #define COUNT		(1<<0)
103*4887Schin #define ERROR		(1<<1)
104*4887Schin #define FOLLOW		(1<<2)
105*4887Schin #define HEADERS		(1<<3)
106*4887Schin #define LOG		(1<<4)
107*4887Schin #define NEGATIVE	(1<<5)
108*4887Schin #define POSITIVE	(1<<6)
109*4887Schin #define REVERSE		(1<<7)
110*4887Schin #define SILENT		(1<<8)
111*4887Schin #define TIMEOUT		(1<<9)
112*4887Schin #define VERBOSE		(1<<10)
113*4887Schin 
114*4887Schin #define NOW		(unsigned long)time(NiL)
115*4887Schin 
116*4887Schin #define LINES		10
117*4887Schin 
118*4887Schin #ifdef S_ISSOCK
119*4887Schin #define FIFO(m)		(S_ISFIFO(m)||S_ISSOCK(m))
120*4887Schin #else
121*4887Schin #define FIFO(m)		S_ISFIFO(m)
122*4887Schin #endif
123*4887Schin 
124*4887Schin struct Tail_s; typedef struct Tail_s Tail_t;
125*4887Schin 
126*4887Schin struct Tail_s
127*4887Schin {
128*4887Schin 	Tail_t*		next;
129*4887Schin 	char*		name;
130*4887Schin 	Sfio_t*		sp;
131*4887Schin 	Sfoff_t		last;
132*4887Schin 	unsigned long	expire;
133*4887Schin 	long		dev;
134*4887Schin 	long		ino;
135*4887Schin };
136*4887Schin 
137*4887Schin /*
138*4887Schin  * if file is seekable, position file to tail location and return offset
139*4887Schin  * otherwise, return -1
140*4887Schin  */
141*4887Schin 
142*4887Schin static Sfoff_t
143*4887Schin tailpos(register Sfio_t* fp, register Sfoff_t number, int delim)
144*4887Schin {
145*4887Schin 	register size_t		n;
146*4887Schin 	register Sfoff_t	offset;
147*4887Schin 	register Sfoff_t	first;
148*4887Schin 	register Sfoff_t	last;
149*4887Schin 	register char*		s;
150*4887Schin 	register char*		t;
151*4887Schin 	struct stat		st;
152*4887Schin 
153*4887Schin 	last = sfsize(fp);
154*4887Schin 	if ((first = sfseek(fp, (Sfoff_t)0, SEEK_CUR)) < 0)
155*4887Schin 		return last || fstat(sffileno(fp), &st) || st.st_size || FIFO(st.st_mode) ? -1 : 0;
156*4887Schin 	if (delim < 0)
157*4887Schin 	{
158*4887Schin 		if ((offset = last - number) < first)
159*4887Schin 			return first;
160*4887Schin 		return offset;
161*4887Schin 	}
162*4887Schin 	if ((offset = last - SF_BUFSIZE) < first)
163*4887Schin 		offset = first;
164*4887Schin 	for (;;)
165*4887Schin 	{
166*4887Schin 		sfseek(fp, offset, SEEK_SET);
167*4887Schin 		n = last - offset;
168*4887Schin 		if (!(s = sfreserve(fp, n, SF_LOCKR)))
169*4887Schin 			return -1;
170*4887Schin 		t = s + n;
171*4887Schin 		while (t > s)
172*4887Schin 			if (*--t == delim && number-- <= 0)
173*4887Schin 			{
174*4887Schin 				sfread(fp, s, 0);
175*4887Schin 				return offset + (t - s) + 1;
176*4887Schin 			}
177*4887Schin 		sfread(fp, s, 0);
178*4887Schin 		if (offset == first)
179*4887Schin 			break;
180*4887Schin 		last = offset;
181*4887Schin 		if ((offset = last - SF_BUFSIZE) < first)
182*4887Schin 			offset = first;
183*4887Schin 	}
184*4887Schin 	return first;
185*4887Schin }
186*4887Schin 
187*4887Schin /*
188*4887Schin  * this code handles tail from a pipe without any size limits
189*4887Schin  */
190*4887Schin 
191*4887Schin static void
192*4887Schin pipetail(Sfio_t* infile, Sfio_t* outfile, Sfoff_t number, int delim)
193*4887Schin {
194*4887Schin 	register Sfio_t*	out;
195*4887Schin 	register Sfoff_t	n;
196*4887Schin 	register Sfoff_t	nleft = number;
197*4887Schin 	register size_t		a = 2 * SF_BUFSIZE;
198*4887Schin 	register int		fno = 0;
199*4887Schin 	Sfoff_t			offset[2];
200*4887Schin 	Sfio_t*			tmp[2];
201*4887Schin 
202*4887Schin 	if (delim < 0 && a > number)
203*4887Schin 		a = number;
204*4887Schin 	out = tmp[0] = sftmp(a);
205*4887Schin 	tmp[1] = sftmp(a);
206*4887Schin 	offset[0] = offset[1] = 0;
207*4887Schin 	while ((n = sfmove(infile, out, number, delim)) > 0)
208*4887Schin 	{
209*4887Schin 		offset[fno] = sftell(out);
210*4887Schin 		if ((nleft -= n) <= 0)
211*4887Schin 		{
212*4887Schin 			out = tmp[fno= !fno];
213*4887Schin 			sfseek(out, (Sfoff_t)0, SEEK_SET);
214*4887Schin 			nleft = number;
215*4887Schin 		}
216*4887Schin 	}
217*4887Schin 	if (nleft == number)
218*4887Schin 	{
219*4887Schin 		offset[fno] = 0;
220*4887Schin 		fno= !fno;
221*4887Schin 	}
222*4887Schin 	sfseek(tmp[0], (Sfoff_t)0, SEEK_SET);
223*4887Schin 
224*4887Schin 	/*
225*4887Schin 	 * see whether both files are needed
226*4887Schin 	 */
227*4887Schin 
228*4887Schin 	if (offset[fno])
229*4887Schin 	{
230*4887Schin 		sfseek(tmp[1], (Sfoff_t)0, SEEK_SET);
231*4887Schin 		if ((n = number - nleft) > 0)
232*4887Schin 			sfmove(tmp[!fno], NiL, n, delim);
233*4887Schin 		if ((n = offset[!fno] - sftell(tmp[!fno])) > 0)
234*4887Schin 			sfmove(tmp[!fno], outfile, n, -1);
235*4887Schin 	}
236*4887Schin 	else
237*4887Schin 		fno = !fno;
238*4887Schin 	sfmove(tmp[fno], outfile, offset[fno], -1);
239*4887Schin 	sfclose(tmp[0]);
240*4887Schin 	sfclose(tmp[1]);
241*4887Schin }
242*4887Schin 
243*4887Schin /*
244*4887Schin  * (re)initialize a tail stream
245*4887Schin  */
246*4887Schin 
247*4887Schin static int
248*4887Schin init(Tail_t* tp, Sfoff_t number, int delim, int flags)
249*4887Schin {
250*4887Schin 	Sfoff_t		offset;
251*4887Schin 	struct stat	st;
252*4887Schin 
253*4887Schin 	if (tp->sp)
254*4887Schin 	{
255*4887Schin 		offset = 0;
256*4887Schin 		if (tp->sp == sfstdin)
257*4887Schin 			tp->sp = 0;
258*4887Schin 	}
259*4887Schin 	else if (!number)
260*4887Schin 		offset = 0;
261*4887Schin 	else
262*4887Schin 		offset = 1;
263*4887Schin 	if (!tp->name || streq(tp->name, "-"))
264*4887Schin 	{
265*4887Schin 		tp->name = "/dev/stdin";
266*4887Schin 		tp->sp = sfstdin;
267*4887Schin 	}
268*4887Schin 	else if (!(tp->sp = sfopen(tp->sp, tp->name, "r")))
269*4887Schin 	{
270*4887Schin 		error(ERROR_system(0), "%s: cannot open", tp->name);
271*4887Schin 		return -1;
272*4887Schin 	}
273*4887Schin 	sfset(tp->sp, SF_SHARE, 0);
274*4887Schin 	if (offset)
275*4887Schin 	{
276*4887Schin 		if ((offset = tailpos(tp->sp, number, delim)) < 0)
277*4887Schin 		{
278*4887Schin 			error(ERROR_SYSTEM|2, "%s: cannot position file to tail", tp->name);
279*4887Schin 			goto bad;
280*4887Schin 		}
281*4887Schin 		sfseek(tp->sp, offset, SEEK_SET);
282*4887Schin 	}
283*4887Schin 	tp->last = offset;
284*4887Schin 	if (flags & LOG)
285*4887Schin 	{
286*4887Schin 		if (fstat(sffileno(tp->sp), &st))
287*4887Schin 		{
288*4887Schin 			error(ERROR_system(0), "%s: cannot stat", tp->name);
289*4887Schin 			goto bad;
290*4887Schin 		}
291*4887Schin 		tp->dev = st.st_dev;
292*4887Schin 		tp->ino = st.st_ino;
293*4887Schin 	}
294*4887Schin 	return 0;
295*4887Schin  bad:
296*4887Schin 	if (tp->sp != sfstdin)
297*4887Schin 		sfclose(tp->sp);
298*4887Schin 	tp->sp = 0;
299*4887Schin 	return -1;
300*4887Schin }
301*4887Schin 
302*4887Schin /*
303*4887Schin  * convert number with validity diagnostics
304*4887Schin  */
305*4887Schin 
306*4887Schin static intmax_t
307*4887Schin num(register const char* s, char** e, int* f, int o)
308*4887Schin {
309*4887Schin 	intmax_t	number;
310*4887Schin 	char*		t;
311*4887Schin 	int		c;
312*4887Schin 
313*4887Schin 	*f &= ~(ERROR|NEGATIVE|POSITIVE);
314*4887Schin 	if ((c = *s) == '-')
315*4887Schin 	{
316*4887Schin 		*f |= NEGATIVE;
317*4887Schin 		s++;
318*4887Schin 	}
319*4887Schin 	else if (c == '+')
320*4887Schin 	{
321*4887Schin 		*f |= POSITIVE;
322*4887Schin 		s++;
323*4887Schin 	}
324*4887Schin 	while (*s == '0' && isdigit(*(s + 1)))
325*4887Schin 		s++;
326*4887Schin 	errno = 0;
327*4887Schin 	number = strtonll(s, &t, NiL, 0);
328*4887Schin 	if (!o && t > s && *(t - 1) == 'l')
329*4887Schin 		t--;
330*4887Schin 	if (t == s)
331*4887Schin 		number = LINES;
332*4887Schin 	if (o && *t)
333*4887Schin 	{
334*4887Schin 		number = 0;
335*4887Schin 		*f |= ERROR;
336*4887Schin 		error(2, "-%c: %s: invalid numeric argument -- unknown suffix", o, s);
337*4887Schin 	}
338*4887Schin 	else if (errno)
339*4887Schin 	{
340*4887Schin 		*f |= ERROR;
341*4887Schin 		if (o)
342*4887Schin 			error(2, "-%c: %s: invalid numeric argument -- out of range", o, s);
343*4887Schin 		else
344*4887Schin 			error(2, "%s: invalid numeric argument -- out of range", s);
345*4887Schin 	}
346*4887Schin 	else
347*4887Schin 	{
348*4887Schin 		*f |= COUNT;
349*4887Schin 		if (c == '-')
350*4887Schin 			number = -number;
351*4887Schin 	}
352*4887Schin 	if (e)
353*4887Schin 		*e = t;
354*4887Schin 	return number;
355*4887Schin }
356*4887Schin 
357*4887Schin int
358*4887Schin b_tail(int argc, char** argv, void* context)
359*4887Schin {
360*4887Schin 	static const char	header_fmt[] = "\n==> %s <==\n";
361*4887Schin 
362*4887Schin 	register Sfio_t*	ip;
363*4887Schin 	register int		n;
364*4887Schin 	register int		i;
365*4887Schin 	register int		delim = '\n';
366*4887Schin 	int			flags = HEADERS;
367*4887Schin 	char*			s;
368*4887Schin 	char*			t;
369*4887Schin 	char*			r;
370*4887Schin 	char*			e;
371*4887Schin 	char*			file;
372*4887Schin 	Sfoff_t			offset;
373*4887Schin 	Sfoff_t			number = LINES;
374*4887Schin 	unsigned long		timeout = 0;
375*4887Schin 	struct stat		st;
376*4887Schin 	const char*		format = header_fmt+1;
377*4887Schin 	size_t			z;
378*4887Schin 	Sfio_t*			op;
379*4887Schin 	register Tail_t*	fp;
380*4887Schin 	register Tail_t*	pp;
381*4887Schin 	register Tail_t*	hp;
382*4887Schin 	Tail_t*			files;
383*4887Schin 
384*4887Schin 	cmdinit(argc, argv, context, ERROR_CATALOG, 0);
385*4887Schin 	for (;;)
386*4887Schin 	{
387*4887Schin 		switch (n = optget(argv, usage))
388*4887Schin 		{
389*4887Schin 		case 'c':
390*4887Schin 			delim = -1;
391*4887Schin 			if (opt_info.arg && *opt_info.arg=='f' && !*(opt_info.arg+1))
392*4887Schin 			{
393*4887Schin 				flags |= FOLLOW;
394*4887Schin 				continue;
395*4887Schin 			}
396*4887Schin 			/*FALLTHROUGH*/
397*4887Schin 		case 'n':
398*4887Schin 		case 'N':
399*4887Schin 			flags |= COUNT;
400*4887Schin 			if (s = opt_info.arg)
401*4887Schin 				number = num(s, &s, &flags, n);
402*4887Schin 			else
403*4887Schin 			{
404*4887Schin 				number = LINES;
405*4887Schin 				flags &= ~(ERROR|NEGATIVE|POSITIVE);
406*4887Schin 				s = "";
407*4887Schin 			}
408*4887Schin 			if (n=='c' && *s=='f')
409*4887Schin 			{
410*4887Schin 				s++;
411*4887Schin 				flags |= FOLLOW;
412*4887Schin 			}
413*4887Schin 			if (flags & ERROR)
414*4887Schin 				continue;
415*4887Schin 			if (flags & (NEGATIVE|POSITIVE))
416*4887Schin 				number = -number;
417*4887Schin 			if (opt_info.option[0]=='+')
418*4887Schin 				number = -number;
419*4887Schin 			continue;
420*4887Schin 		case 'f':
421*4887Schin 			flags |= FOLLOW;
422*4887Schin 			continue;
423*4887Schin 		case 'h':
424*4887Schin 			if (opt_info.num)
425*4887Schin 				flags |= HEADERS;
426*4887Schin 			else
427*4887Schin 				flags &= ~HEADERS;
428*4887Schin 			continue;
429*4887Schin 		case 'L':
430*4887Schin 			flags |= LOG;
431*4887Schin 			continue;
432*4887Schin 		case 'q':
433*4887Schin 			flags &= ~HEADERS;
434*4887Schin 			continue;
435*4887Schin 		case 'r':
436*4887Schin 			flags |= REVERSE;
437*4887Schin 			continue;
438*4887Schin 		case 's':
439*4887Schin 			flags |= SILENT;
440*4887Schin 			continue;
441*4887Schin 		case 't':
442*4887Schin 			flags |= TIMEOUT;
443*4887Schin 			timeout = strelapsed(opt_info.arg, &s, 1);
444*4887Schin 			if (*s)
445*4887Schin 				error(ERROR_exit(1), "%s: invalid elapsed time", opt_info.arg);
446*4887Schin 			continue;
447*4887Schin 		case 'v':
448*4887Schin 			flags |= VERBOSE;
449*4887Schin 			continue;
450*4887Schin 		case ':':
451*4887Schin 			/* handle old style arguments */
452*4887Schin 			r = s = argv[opt_info.index];
453*4887Schin 			number = num(s, &t, &flags, 0);
454*4887Schin 			for (;;)
455*4887Schin 			{
456*4887Schin 				switch (*t++)
457*4887Schin 				{
458*4887Schin 				case 0:
459*4887Schin 					opt_info.offset = t - r - 1;
460*4887Schin 					if (number)
461*4887Schin 						number = -number;
462*4887Schin 					break;
463*4887Schin 				case 'c':
464*4887Schin 					delim = -1;
465*4887Schin 					continue;
466*4887Schin 				case 'f':
467*4887Schin 					flags |= FOLLOW;
468*4887Schin 					continue;
469*4887Schin 				case 'l':
470*4887Schin 					delim = '\n';
471*4887Schin 					continue;
472*4887Schin 				case 'r':
473*4887Schin 					flags |= REVERSE;
474*4887Schin 					continue;
475*4887Schin 				default:
476*4887Schin 					error(2, "%s: invalid suffix", t - 1);
477*4887Schin 					opt_info.offset = strlen(r);
478*4887Schin 					break;
479*4887Schin 				}
480*4887Schin 				break;
481*4887Schin 			}
482*4887Schin 			continue;
483*4887Schin 		case '?':
484*4887Schin 			error(ERROR_usage(2), "%s", opt_info.arg);
485*4887Schin 			break;
486*4887Schin 		}
487*4887Schin 		break;
488*4887Schin 	}
489*4887Schin 	argv += opt_info.index;
490*4887Schin 	if (!*argv)
491*4887Schin 	{
492*4887Schin 		flags &= ~HEADERS;
493*4887Schin 		if (fstat(0, &st))
494*4887Schin 			error(ERROR_system(0), "/dev/stdin: cannot stat");
495*4887Schin 		else if (FIFO(st.st_mode))
496*4887Schin 			flags &= ~FOLLOW;
497*4887Schin 	}
498*4887Schin 	else if (!*(argv + 1))
499*4887Schin 		flags &= ~HEADERS;
500*4887Schin 	if (flags & REVERSE)
501*4887Schin 	{
502*4887Schin 		if (delim < 0)
503*4887Schin 			error(2, "--reverse requires line mode");
504*4887Schin 		else if (!(flags & COUNT))
505*4887Schin 			number = 0;
506*4887Schin 		flags &= ~FOLLOW;
507*4887Schin 	}
508*4887Schin 	if ((flags & (FOLLOW|TIMEOUT)) == TIMEOUT)
509*4887Schin 	{
510*4887Schin 		flags &= ~TIMEOUT;
511*4887Schin 		timeout = 0;
512*4887Schin 		error(ERROR_warn(0), "--timeout ignored for --noforever");
513*4887Schin 	}
514*4887Schin 	if ((flags & (LOG|TIMEOUT)) == LOG)
515*4887Schin 	{
516*4887Schin 		flags &= ~LOG;
517*4887Schin 		error(ERROR_warn(0), "--log ignored for --notimeout");
518*4887Schin 	}
519*4887Schin 	if (error_info.errors)
520*4887Schin 		error(ERROR_usage(2), "%s", optusage(NiL));
521*4887Schin 	if (flags & FOLLOW)
522*4887Schin 	{
523*4887Schin 		if (!(fp = (Tail_t*)stakalloc(argc * sizeof(Tail_t))))
524*4887Schin 			error(ERROR_system(1), "out of space");
525*4887Schin 		files = 0;
526*4887Schin 		s = *argv;
527*4887Schin 		do
528*4887Schin 		{
529*4887Schin 			fp->name = s;
530*4887Schin 			fp->sp = 0;
531*4887Schin 			if (!init(fp, number, delim, flags))
532*4887Schin 			{
533*4887Schin 				fp->expire = timeout ? (NOW + timeout + 1) : 0;
534*4887Schin 				if (files)
535*4887Schin 					pp->next = fp;
536*4887Schin 				else
537*4887Schin 					files = fp;
538*4887Schin 				pp = fp;
539*4887Schin 				fp++;
540*4887Schin 			}
541*4887Schin 		} while (s && (s = *++argv));
542*4887Schin 		if (!files)
543*4887Schin 			return error_info.errors != 0;
544*4887Schin 		pp->next = 0;
545*4887Schin 		hp = 0;
546*4887Schin 		for (;;)
547*4887Schin 		{
548*4887Schin 			if (sfsync(sfstdout))
549*4887Schin 				error(ERROR_system(1), "write error");
550*4887Schin 			sleep(1);
551*4887Schin 			n = 0;
552*4887Schin 			pp = 0;
553*4887Schin 			fp = files;
554*4887Schin 			while (fp)
555*4887Schin 			{
556*4887Schin 				if (fstat(sffileno(fp->sp), &st))
557*4887Schin 					error(ERROR_system(0), "%s: cannot stat", fp->name);
558*4887Schin 				else if (st.st_size > fp->last)
559*4887Schin 				{
560*4887Schin 					n = 1;
561*4887Schin 					if (timeout)
562*4887Schin 						fp->expire = NOW + timeout;
563*4887Schin 					z = st.st_size - fp->last;
564*4887Schin 					i = 0;
565*4887Schin 					if ((s = sfreserve(fp->sp, z, SF_LOCKR)) || (z = sfvalue(fp->sp)) && (s = sfreserve(fp->sp, z, SF_LOCKR)) && (i = 1))
566*4887Schin 					{
567*4887Schin 						r = 0;
568*4887Schin 						for (e = (t = s) + z; t < e; t++)
569*4887Schin 							if (*t == '\n')
570*4887Schin 								r = t;
571*4887Schin 						if (r || i && (r = e))
572*4887Schin 						{
573*4887Schin 							if ((flags & (HEADERS|VERBOSE)) && hp != fp)
574*4887Schin 							{
575*4887Schin 								hp = fp;
576*4887Schin 								sfprintf(sfstdout, format, fp->name);
577*4887Schin 								format = header_fmt;
578*4887Schin 							}
579*4887Schin 							z = r - s + 1;
580*4887Schin 							fp->last += z;
581*4887Schin 							sfwrite(sfstdout, s, z);
582*4887Schin 						}
583*4887Schin 						else
584*4887Schin 							z = 0;
585*4887Schin 						sfread(fp->sp, s, z);
586*4887Schin 					}
587*4887Schin 					goto next;
588*4887Schin 				}
589*4887Schin 				else if (!timeout || fp->expire > NOW)
590*4887Schin 					goto next;
591*4887Schin 				else
592*4887Schin 				{
593*4887Schin 					if (flags & LOG)
594*4887Schin 					{
595*4887Schin 						i = 3;
596*4887Schin 						while (--i && stat(fp->name, &st))
597*4887Schin 							sleep(1);
598*4887Schin 						if (i && (fp->dev != st.st_dev || fp->ino != st.st_ino) && !init(fp, 0, 0, flags))
599*4887Schin 						{
600*4887Schin 							if (!(flags & SILENT))
601*4887Schin 								error(ERROR_warn(0), "%s: log file change", fp->name);
602*4887Schin 							fp->expire = NOW + timeout;
603*4887Schin 							goto next;
604*4887Schin 						}
605*4887Schin 					}
606*4887Schin 					if (!(flags & SILENT))
607*4887Schin 						error(ERROR_warn(0), "%s: %s timeout", fp->name, fmtelapsed(timeout, 1));
608*4887Schin 				}
609*4887Schin 				if (fp->sp && fp->sp != sfstdin)
610*4887Schin 					sfclose(fp->sp);
611*4887Schin 				if (pp)
612*4887Schin 					pp = pp->next = fp->next;
613*4887Schin 				else if (!(files = files->next))
614*4887Schin 					return error_info.errors != 0;
615*4887Schin 				fp = fp->next;
616*4887Schin 				continue;
617*4887Schin 			next:
618*4887Schin 				pp = fp;
619*4887Schin 				fp = fp->next;
620*4887Schin 			}
621*4887Schin 		}
622*4887Schin 	}
623*4887Schin 	else
624*4887Schin 	{
625*4887Schin 		if (file = *argv)
626*4887Schin 			argv++;
627*4887Schin 		do
628*4887Schin 		{
629*4887Schin 			if (!file || streq(file, "-"))
630*4887Schin 			{
631*4887Schin 				file = "/dev/stdin";
632*4887Schin 				ip = sfstdin;
633*4887Schin 			}
634*4887Schin 			else if (!(ip = sfopen(NiL, file, "r")))
635*4887Schin 			{
636*4887Schin 				error(ERROR_system(0), "%s: cannot open", file);
637*4887Schin 				continue;
638*4887Schin 			}
639*4887Schin 			if (flags & (HEADERS|VERBOSE))
640*4887Schin 				sfprintf(sfstdout, format, file);
641*4887Schin 			format = header_fmt;
642*4887Schin 			if (number < 0 || !number && (flags & POSITIVE))
643*4887Schin 			{
644*4887Schin 				sfset(ip, SF_SHARE, !(flags & FOLLOW));
645*4887Schin 				if (number < -1)
646*4887Schin 					sfmove(ip, NiL, -number - 1, delim);
647*4887Schin 				if (flags & REVERSE)
648*4887Schin 					rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR));
649*4887Schin 				else
650*4887Schin 					sfmove(ip, sfstdout, SF_UNBOUND, -1);
651*4887Schin 			}
652*4887Schin 			else
653*4887Schin 			{
654*4887Schin 				sfset(ip, SF_SHARE, 0);
655*4887Schin 				if ((offset = tailpos(ip, number, delim)) >= 0)
656*4887Schin 				{
657*4887Schin 					if (flags & REVERSE)
658*4887Schin 						rev_line(ip, sfstdout, offset);
659*4887Schin 					else
660*4887Schin 					{
661*4887Schin 						sfseek(ip, offset, SEEK_SET);
662*4887Schin 						sfmove(ip, sfstdout, SF_UNBOUND, -1);
663*4887Schin 					}
664*4887Schin 				}
665*4887Schin 				else
666*4887Schin 				{
667*4887Schin 					op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout;
668*4887Schin 					pipetail(ip, op, number, delim);
669*4887Schin 					if (flags & REVERSE)
670*4887Schin 					{
671*4887Schin 						sfseek(op, (Sfoff_t)0, SEEK_SET);
672*4887Schin 						rev_line(op, sfstdout, (Sfoff_t)0);
673*4887Schin 						sfclose(op);
674*4887Schin 					}
675*4887Schin 					flags = 0;
676*4887Schin 				}
677*4887Schin 			}
678*4887Schin 			if (ip != sfstdin)
679*4887Schin 				sfclose(ip);
680*4887Schin 		} while (file = *argv++);
681*4887Schin 	}
682*4887Schin 	return error_info.errors != 0;
683*4887Schin }
684