xref: /onnv-gate/usr/src/lib/libcmd/common/tail.c (revision 10898)
14887Schin /***********************************************************************
24887Schin *                                                                      *
34887Schin *               This software is part of the ast package               *
4*10898Sroland.mainz@nrubsig.org *          Copyright (c) 1992-2009 AT&T Intellectual Property          *
54887Schin *                      and is licensed under the                       *
64887Schin *                  Common Public License, Version 1.0                  *
78462SApril.Chin@Sun.COM *                    by AT&T Intellectual Property                     *
84887Schin *                                                                      *
94887Schin *                A copy of the License is available at                 *
104887Schin *            http://www.opensource.org/licenses/cpl1.0.txt             *
114887Schin *         (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9)         *
124887Schin *                                                                      *
134887Schin *              Information and Software Systems Research               *
144887Schin *                            AT&T Research                             *
154887Schin *                           Florham Park NJ                            *
164887Schin *                                                                      *
174887Schin *                 Glenn Fowler <gsf@research.att.com>                  *
184887Schin *                  David Korn <dgk@research.att.com>                   *
194887Schin *                                                                      *
204887Schin ***********************************************************************/
214887Schin #pragma prototyped
224887Schin 
234887Schin /*
244887Schin  * print the tail of one or more files
254887Schin  *
264887Schin  *   David Korn
274887Schin  *   Glenn Fowler
284887Schin  */
294887Schin 
304887Schin static const char usage[] =
31*10898Sroland.mainz@nrubsig.org "+[-?\n@(#)$Id: tail (AT&T Research) 2009-08-25 $\n]"
324887Schin USAGE_LICENSE
334887Schin "[+NAME?tail - output trailing portion of one or more files ]"
344887Schin "[+DESCRIPTION?\btail\b copies one or more input files to standard output "
354887Schin 	"starting at a designated point for each file.  Copying starts "
364887Schin 	"at the point indicated by the options and is unlimited in size.]"
374887Schin "[+?By default a header of the form \b==> \b\afilename\a\b <==\b "
384887Schin 	"is output before all but the first file but this can be changed "
394887Schin 	"with the \b-q\b and \b-v\b options.]"
404887Schin "[+?If no \afile\a is given, or if the \afile\a is \b-\b, \btail\b "
414887Schin 	"copies from standard input. The start of the file is defined "
424887Schin 	"as the current offset.]"
434887Schin "[+?The option argument for \b-c\b can optionally be "
444887Schin 	"followed by one of the following characters to specify a different "
454887Schin 	"unit other than a single byte:]{"
464887Schin 		"[+b?512 bytes.]"
47*10898Sroland.mainz@nrubsig.org 		"[+k?1 KiB.]"
48*10898Sroland.mainz@nrubsig.org 		"[+m?1 MiB.]"
49*10898Sroland.mainz@nrubsig.org 		"[+g?1 GiB.]"
504887Schin 	"}"
514887Schin "[+?For backwards compatibility, \b-\b\anumber\a  is equivalent to "
524887Schin 	"\b-n\b \anumber\a and \b+\b\anumber\a is equivalent to "
53*10898Sroland.mainz@nrubsig.org 	"\b-n -\b\anumber\a. \anumber\a may also have these option "
54*10898Sroland.mainz@nrubsig.org 	"suffixes: \bb c f g k l m r\b.]"
554887Schin 
564887Schin "[n:lines]:[lines:=10?Copy \alines\a lines from each file.  A negative value "
57*10898Sroland.mainz@nrubsig.org 	"for \alines\a indicates an offset from the end of the file.]"
58*10898Sroland.mainz@nrubsig.org "[b:blocks?Copy units of 512 bytes.]"
594887Schin "[c:bytes]:?[chars?Copy \achars\a bytes from each file.  A negative value "
60*10898Sroland.mainz@nrubsig.org 	"for \achars\a indicates an offset from the end of the file.]"
614887Schin "[f:forever|follow?Loop forever trying to read more characters as the "
624887Schin 	"end of each file to copy new data. Ignored if reading from a pipe "
634887Schin 	"or fifo.]"
644887Schin "[h!:headers?Output filename headers.]"
65*10898Sroland.mainz@nrubsig.org "[l:lines?Copy units of lines. This is the default.]"
664887Schin "[L:log?When a \b--forever\b file times out via \b--timeout\b, verify that "
674887Schin 	"the curent file has not been renamed and replaced by another file "
684887Schin 	"of the same name (a common log file practice) before giving up on "
694887Schin 	"the file.]"
704887Schin "[q:quiet?Don't output filename headers. For GNU compatibility.]"
714887Schin "[r:reverse?Output lines in reverse order.]"
724887Schin "[s:silent?Don't warn about timeout expiration and log file changes.]"
734887Schin "[t:timeout?Stop checking after \atimeout\a elapses with no additional "
744887Schin 	"\b--forever\b output. A separate elapsed time is maintained for "
754887Schin 	"each file operand. There is no timeout by default. The default "
764887Schin 	"\atimeout\a unit is seconds. \atimeout\a may be a catenation of 1 "
774887Schin 	"or more integers, each followed by a 1 character suffix. The suffix "
784887Schin 	"may be omitted from the last integer, in which case it is "
794887Schin 	"interpreted as seconds. The supported suffixes are:]:[timeout]{"
804887Schin 		"[+s?seconds]"
814887Schin 		"[+m?minutes]"
824887Schin 		"[+h?hours]"
834887Schin 		"[+d?days]"
844887Schin 		"[+w?weeks]"
854887Schin 		"[+M?months]"
864887Schin 		"[+y?years]"
874887Schin 		"[+S?scores]"
884887Schin 	"}"
894887Schin "[v:verbose?Always ouput filename headers.]"
90*10898Sroland.mainz@nrubsig.org 
914887Schin "\n"
924887Schin "\n[file ...]\n"
934887Schin "\n"
94*10898Sroland.mainz@nrubsig.org 
954887Schin "[+EXIT STATUS?]{"
964887Schin 	"[+0?All files copied successfully.]"
974887Schin 	"[+>0?One or more files did not copy.]"
984887Schin "}"
994887Schin "[+SEE ALSO?\bcat\b(1), \bhead\b(1), \brev\b(1)]"
1004887Schin ;
1014887Schin 
1024887Schin #include <cmd.h>
1034887Schin #include <ctype.h>
1044887Schin #include <ls.h>
1054887Schin #include <tm.h>
1064887Schin #include <rev.h>
1074887Schin 
1084887Schin #define COUNT		(1<<0)
1094887Schin #define ERROR		(1<<1)
1104887Schin #define FOLLOW		(1<<2)
1114887Schin #define HEADERS		(1<<3)
112*10898Sroland.mainz@nrubsig.org #define LINES		(1<<4)
113*10898Sroland.mainz@nrubsig.org #define LOG		(1<<5)
114*10898Sroland.mainz@nrubsig.org #define NEGATIVE	(1<<6)
115*10898Sroland.mainz@nrubsig.org #define POSITIVE	(1<<7)
116*10898Sroland.mainz@nrubsig.org #define REVERSE		(1<<8)
117*10898Sroland.mainz@nrubsig.org #define SILENT		(1<<9)
118*10898Sroland.mainz@nrubsig.org #define TIMEOUT		(1<<10)
119*10898Sroland.mainz@nrubsig.org #define VERBOSE		(1<<11)
1204887Schin 
1214887Schin #define NOW		(unsigned long)time(NiL)
1224887Schin 
123*10898Sroland.mainz@nrubsig.org #define DEFAULT		10
1244887Schin 
1254887Schin #ifdef S_ISSOCK
1264887Schin #define FIFO(m)		(S_ISFIFO(m)||S_ISSOCK(m))
1274887Schin #else
1284887Schin #define FIFO(m)		S_ISFIFO(m)
1294887Schin #endif
1304887Schin 
1314887Schin struct Tail_s; typedef struct Tail_s Tail_t;
1324887Schin 
1334887Schin struct Tail_s
1344887Schin {
1354887Schin 	Tail_t*		next;
1364887Schin 	char*		name;
1374887Schin 	Sfio_t*		sp;
1384887Schin 	Sfoff_t		last;
1394887Schin 	unsigned long	expire;
1404887Schin 	long		dev;
1414887Schin 	long		ino;
142*10898Sroland.mainz@nrubsig.org 	int		fifo;
1434887Schin };
1444887Schin 
145*10898Sroland.mainz@nrubsig.org static const char	header_fmt[] = "\n==> %s <==\n";
146*10898Sroland.mainz@nrubsig.org 
1474887Schin /*
1484887Schin  * if file is seekable, position file to tail location and return offset
1494887Schin  * otherwise, return -1
1504887Schin  */
1514887Schin 
1524887Schin static Sfoff_t
1534887Schin tailpos(register Sfio_t* fp, register Sfoff_t number, int delim)
1544887Schin {
1554887Schin 	register size_t		n;
1564887Schin 	register Sfoff_t	offset;
1574887Schin 	register Sfoff_t	first;
1584887Schin 	register Sfoff_t	last;
1594887Schin 	register char*		s;
1604887Schin 	register char*		t;
1614887Schin 	struct stat		st;
1624887Schin 
1634887Schin 	last = sfsize(fp);
1644887Schin 	if ((first = sfseek(fp, (Sfoff_t)0, SEEK_CUR)) < 0)
1654887Schin 		return last || fstat(sffileno(fp), &st) || st.st_size || FIFO(st.st_mode) ? -1 : 0;
1664887Schin 	if (delim < 0)
1674887Schin 	{
1684887Schin 		if ((offset = last - number) < first)
1694887Schin 			return first;
1704887Schin 		return offset;
1714887Schin 	}
1724887Schin 	for (;;)
1734887Schin 	{
174*10898Sroland.mainz@nrubsig.org 		if ((offset = last - SF_BUFSIZE) < first)
175*10898Sroland.mainz@nrubsig.org 			offset = first;
1764887Schin 		sfseek(fp, offset, SEEK_SET);
1774887Schin 		n = last - offset;
1784887Schin 		if (!(s = sfreserve(fp, n, SF_LOCKR)))
1794887Schin 			return -1;
1804887Schin 		t = s + n;
1814887Schin 		while (t > s)
1824887Schin 			if (*--t == delim && number-- <= 0)
1834887Schin 			{
1844887Schin 				sfread(fp, s, 0);
1854887Schin 				return offset + (t - s) + 1;
1864887Schin 			}
1874887Schin 		sfread(fp, s, 0);
1884887Schin 		if (offset == first)
1894887Schin 			break;
1904887Schin 		last = offset;
1914887Schin 	}
1924887Schin 	return first;
1934887Schin }
1944887Schin 
1954887Schin /*
1964887Schin  * this code handles tail from a pipe without any size limits
1974887Schin  */
1984887Schin 
1994887Schin static void
2004887Schin pipetail(Sfio_t* infile, Sfio_t* outfile, Sfoff_t number, int delim)
2014887Schin {
2024887Schin 	register Sfio_t*	out;
2034887Schin 	register Sfoff_t	n;
2044887Schin 	register Sfoff_t	nleft = number;
2054887Schin 	register size_t		a = 2 * SF_BUFSIZE;
2064887Schin 	register int		fno = 0;
2074887Schin 	Sfoff_t			offset[2];
2084887Schin 	Sfio_t*			tmp[2];
2094887Schin 
2104887Schin 	if (delim < 0 && a > number)
2114887Schin 		a = number;
2124887Schin 	out = tmp[0] = sftmp(a);
2134887Schin 	tmp[1] = sftmp(a);
2144887Schin 	offset[0] = offset[1] = 0;
2154887Schin 	while ((n = sfmove(infile, out, number, delim)) > 0)
2164887Schin 	{
2174887Schin 		offset[fno] = sftell(out);
2184887Schin 		if ((nleft -= n) <= 0)
2194887Schin 		{
2204887Schin 			out = tmp[fno= !fno];
2214887Schin 			sfseek(out, (Sfoff_t)0, SEEK_SET);
2224887Schin 			nleft = number;
2234887Schin 		}
2244887Schin 	}
2254887Schin 	if (nleft == number)
2264887Schin 	{
2274887Schin 		offset[fno] = 0;
2284887Schin 		fno= !fno;
2294887Schin 	}
2304887Schin 	sfseek(tmp[0], (Sfoff_t)0, SEEK_SET);
2314887Schin 
2324887Schin 	/*
2334887Schin 	 * see whether both files are needed
2344887Schin 	 */
2354887Schin 
2364887Schin 	if (offset[fno])
2374887Schin 	{
2384887Schin 		sfseek(tmp[1], (Sfoff_t)0, SEEK_SET);
2394887Schin 		if ((n = number - nleft) > 0)
2404887Schin 			sfmove(tmp[!fno], NiL, n, delim);
2414887Schin 		if ((n = offset[!fno] - sftell(tmp[!fno])) > 0)
2424887Schin 			sfmove(tmp[!fno], outfile, n, -1);
2434887Schin 	}
2444887Schin 	else
2454887Schin 		fno = !fno;
2464887Schin 	sfmove(tmp[fno], outfile, offset[fno], -1);
2474887Schin 	sfclose(tmp[0]);
2484887Schin 	sfclose(tmp[1]);
2494887Schin }
2504887Schin 
2514887Schin /*
2524887Schin  * (re)initialize a tail stream
2534887Schin  */
2544887Schin 
2554887Schin static int
256*10898Sroland.mainz@nrubsig.org init(Tail_t* tp, Sfoff_t number, int delim, int flags, const char** format)
2574887Schin {
2584887Schin 	Sfoff_t		offset;
259*10898Sroland.mainz@nrubsig.org 	Sfio_t*		op;
2604887Schin 	struct stat	st;
2614887Schin 
262*10898Sroland.mainz@nrubsig.org 	tp->fifo = 0;
2634887Schin 	if (tp->sp)
2644887Schin 	{
2654887Schin 		offset = 0;
2664887Schin 		if (tp->sp == sfstdin)
2674887Schin 			tp->sp = 0;
2684887Schin 	}
2694887Schin 	else if (!number)
2704887Schin 		offset = 0;
2714887Schin 	else
2724887Schin 		offset = 1;
2734887Schin 	if (!tp->name || streq(tp->name, "-"))
2744887Schin 	{
2754887Schin 		tp->name = "/dev/stdin";
2764887Schin 		tp->sp = sfstdin;
2774887Schin 	}
2784887Schin 	else if (!(tp->sp = sfopen(tp->sp, tp->name, "r")))
2794887Schin 	{
2804887Schin 		error(ERROR_system(0), "%s: cannot open", tp->name);
2814887Schin 		return -1;
2824887Schin 	}
2834887Schin 	sfset(tp->sp, SF_SHARE, 0);
2844887Schin 	if (offset)
2854887Schin 	{
286*10898Sroland.mainz@nrubsig.org 		if (number < 0 || !number && (flags & POSITIVE))
287*10898Sroland.mainz@nrubsig.org 		{
288*10898Sroland.mainz@nrubsig.org 			sfset(tp->sp, SF_SHARE, !(flags & FOLLOW));
289*10898Sroland.mainz@nrubsig.org 			if (number < -1)
290*10898Sroland.mainz@nrubsig.org 			{
291*10898Sroland.mainz@nrubsig.org 				sfmove(tp->sp, NiL, -number - 1, delim);
292*10898Sroland.mainz@nrubsig.org 				offset = sfseek(tp->sp, (Sfoff_t)0, SEEK_CUR);
293*10898Sroland.mainz@nrubsig.org 			}
294*10898Sroland.mainz@nrubsig.org 			else
295*10898Sroland.mainz@nrubsig.org 				offset = 0;
296*10898Sroland.mainz@nrubsig.org 		}
297*10898Sroland.mainz@nrubsig.org 		else if ((offset = tailpos(tp->sp, number, delim)) >= 0)
298*10898Sroland.mainz@nrubsig.org 			sfseek(tp->sp, offset, SEEK_SET);
299*10898Sroland.mainz@nrubsig.org 		else if (fstat(sffileno(tp->sp), &st))
300*10898Sroland.mainz@nrubsig.org 		{
301*10898Sroland.mainz@nrubsig.org 			error(ERROR_system(0), "%s: cannot stat", tp->name);
302*10898Sroland.mainz@nrubsig.org 			goto bad;
303*10898Sroland.mainz@nrubsig.org 		}
304*10898Sroland.mainz@nrubsig.org 		else if (!FIFO(st.st_mode))
3054887Schin 		{
3064887Schin 			error(ERROR_SYSTEM|2, "%s: cannot position file to tail", tp->name);
3074887Schin 			goto bad;
3084887Schin 		}
309*10898Sroland.mainz@nrubsig.org 		else
310*10898Sroland.mainz@nrubsig.org 		{
311*10898Sroland.mainz@nrubsig.org 			tp->fifo = 1;
312*10898Sroland.mainz@nrubsig.org 			if (flags & (HEADERS|VERBOSE))
313*10898Sroland.mainz@nrubsig.org 			{
314*10898Sroland.mainz@nrubsig.org 				sfprintf(sfstdout, *format, tp->name);
315*10898Sroland.mainz@nrubsig.org 				*format = header_fmt;
316*10898Sroland.mainz@nrubsig.org 			}
317*10898Sroland.mainz@nrubsig.org 			op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout;
318*10898Sroland.mainz@nrubsig.org 			pipetail(tp->sp ? tp->sp : sfstdin, op, number, delim);
319*10898Sroland.mainz@nrubsig.org 			if (flags & REVERSE)
320*10898Sroland.mainz@nrubsig.org 			{
321*10898Sroland.mainz@nrubsig.org 				sfseek(op, (Sfoff_t)0, SEEK_SET);
322*10898Sroland.mainz@nrubsig.org 				rev_line(op, sfstdout, (Sfoff_t)0);
323*10898Sroland.mainz@nrubsig.org 				sfclose(op);
324*10898Sroland.mainz@nrubsig.org 			}
325*10898Sroland.mainz@nrubsig.org 		}
3264887Schin 	}
3274887Schin 	tp->last = offset;
3284887Schin 	if (flags & LOG)
3294887Schin 	{
3304887Schin 		if (fstat(sffileno(tp->sp), &st))
3314887Schin 		{
3324887Schin 			error(ERROR_system(0), "%s: cannot stat", tp->name);
3334887Schin 			goto bad;
3344887Schin 		}
3354887Schin 		tp->dev = st.st_dev;
3364887Schin 		tp->ino = st.st_ino;
3374887Schin 	}
3384887Schin 	return 0;
3394887Schin  bad:
3404887Schin 	if (tp->sp != sfstdin)
3414887Schin 		sfclose(tp->sp);
3424887Schin 	tp->sp = 0;
3434887Schin 	return -1;
3444887Schin }
3454887Schin 
3464887Schin /*
3474887Schin  * convert number with validity diagnostics
3484887Schin  */
3494887Schin 
3504887Schin static intmax_t
3514887Schin num(register const char* s, char** e, int* f, int o)
3524887Schin {
3534887Schin 	intmax_t	number;
3544887Schin 	char*		t;
3554887Schin 	int		c;
3564887Schin 
3574887Schin 	*f &= ~(ERROR|NEGATIVE|POSITIVE);
3584887Schin 	if ((c = *s) == '-')
3594887Schin 	{
3604887Schin 		*f |= NEGATIVE;
3614887Schin 		s++;
3624887Schin 	}
3634887Schin 	else if (c == '+')
3644887Schin 	{
3654887Schin 		*f |= POSITIVE;
3664887Schin 		s++;
3674887Schin 	}
3684887Schin 	while (*s == '0' && isdigit(*(s + 1)))
3694887Schin 		s++;
3704887Schin 	errno = 0;
3714887Schin 	number = strtonll(s, &t, NiL, 0);
3724887Schin 	if (t == s)
373*10898Sroland.mainz@nrubsig.org 		number = DEFAULT;
3744887Schin 	if (o && *t)
3754887Schin 	{
3764887Schin 		number = 0;
3774887Schin 		*f |= ERROR;
3784887Schin 		error(2, "-%c: %s: invalid numeric argument -- unknown suffix", o, s);
3794887Schin 	}
3804887Schin 	else if (errno)
3814887Schin 	{
3824887Schin 		*f |= ERROR;
3834887Schin 		if (o)
3844887Schin 			error(2, "-%c: %s: invalid numeric argument -- out of range", o, s);
3854887Schin 		else
3864887Schin 			error(2, "%s: invalid numeric argument -- out of range", s);
3874887Schin 	}
3884887Schin 	else
3894887Schin 	{
3904887Schin 		*f |= COUNT;
391*10898Sroland.mainz@nrubsig.org 		if (t > s && isalpha(*(t - 1)))
392*10898Sroland.mainz@nrubsig.org 			*f &= ~LINES;
3934887Schin 		if (c == '-')
3944887Schin 			number = -number;
3954887Schin 	}
3964887Schin 	if (e)
3974887Schin 		*e = t;
3984887Schin 	return number;
3994887Schin }
4004887Schin 
4014887Schin int
4024887Schin b_tail(int argc, char** argv, void* context)
4034887Schin {
4044887Schin 	register Sfio_t*	ip;
4054887Schin 	register int		n;
4064887Schin 	register int		i;
407*10898Sroland.mainz@nrubsig.org 	int			delim;
408*10898Sroland.mainz@nrubsig.org 	int			flags = HEADERS|LINES;
409*10898Sroland.mainz@nrubsig.org 	int			blocks = 0;
4104887Schin 	char*			s;
4114887Schin 	char*			t;
4124887Schin 	char*			r;
4134887Schin 	char*			e;
4144887Schin 	char*			file;
4154887Schin 	Sfoff_t			offset;
416*10898Sroland.mainz@nrubsig.org 	Sfoff_t			number = DEFAULT;
4174887Schin 	unsigned long		timeout = 0;
4184887Schin 	struct stat		st;
4194887Schin 	const char*		format = header_fmt+1;
420*10898Sroland.mainz@nrubsig.org 	ssize_t			z;
4214887Schin 	Sfio_t*			op;
4224887Schin 	register Tail_t*	fp;
4234887Schin 	register Tail_t*	pp;
4244887Schin 	register Tail_t*	hp;
4254887Schin 	Tail_t*			files;
4264887Schin 
4274887Schin 	cmdinit(argc, argv, context, ERROR_CATALOG, 0);
4284887Schin 	for (;;)
4294887Schin 	{
4304887Schin 		switch (n = optget(argv, usage))
4314887Schin 		{
432*10898Sroland.mainz@nrubsig.org 		case 0:
433*10898Sroland.mainz@nrubsig.org 			if (!(flags & FOLLOW) && argv[opt_info.index] && (argv[opt_info.index][0] == '-' || argv[opt_info.index][0] == '+') && !argv[opt_info.index][1])
434*10898Sroland.mainz@nrubsig.org 			{
435*10898Sroland.mainz@nrubsig.org 				number = argv[opt_info.index][0] == '-' ? 10 : -10;
436*10898Sroland.mainz@nrubsig.org 				flags |= LINES;
437*10898Sroland.mainz@nrubsig.org 				opt_info.index++;
438*10898Sroland.mainz@nrubsig.org 				continue;
439*10898Sroland.mainz@nrubsig.org 			}
440*10898Sroland.mainz@nrubsig.org 			break;
441*10898Sroland.mainz@nrubsig.org 		case 'b':
442*10898Sroland.mainz@nrubsig.org 			blocks = 512;
443*10898Sroland.mainz@nrubsig.org 			flags &= ~LINES;
444*10898Sroland.mainz@nrubsig.org 			if (opt_info.option[0] == '+')
445*10898Sroland.mainz@nrubsig.org 				number = -number;
446*10898Sroland.mainz@nrubsig.org 			continue;
4474887Schin 		case 'c':
448*10898Sroland.mainz@nrubsig.org 			flags &= ~LINES;
449*10898Sroland.mainz@nrubsig.org 			if (opt_info.arg == argv[opt_info.index - 1])
4504887Schin 			{
451*10898Sroland.mainz@nrubsig.org 				strtol(opt_info.arg, &s, 10);
452*10898Sroland.mainz@nrubsig.org 				if (*s)
453*10898Sroland.mainz@nrubsig.org 				{
454*10898Sroland.mainz@nrubsig.org 					opt_info.index--;
455*10898Sroland.mainz@nrubsig.org 					t = "";
456*10898Sroland.mainz@nrubsig.org 					goto suffix;
457*10898Sroland.mainz@nrubsig.org 				}
458*10898Sroland.mainz@nrubsig.org 			}
459*10898Sroland.mainz@nrubsig.org 			else if (opt_info.arg && isalpha(*opt_info.arg))
460*10898Sroland.mainz@nrubsig.org 			{
461*10898Sroland.mainz@nrubsig.org 				t = opt_info.arg;
462*10898Sroland.mainz@nrubsig.org 				goto suffix;
4634887Schin 			}
4644887Schin 			/*FALLTHROUGH*/
4654887Schin 		case 'n':
4664887Schin 			flags |= COUNT;
4674887Schin 			if (s = opt_info.arg)
4684887Schin 				number = num(s, &s, &flags, n);
4694887Schin 			else
4704887Schin 			{
471*10898Sroland.mainz@nrubsig.org 				number = DEFAULT;
4724887Schin 				flags &= ~(ERROR|NEGATIVE|POSITIVE);
4734887Schin 				s = "";
4744887Schin 			}
475*10898Sroland.mainz@nrubsig.org 			if (n != 'n' && s && isalpha(*s))
4764887Schin 			{
477*10898Sroland.mainz@nrubsig.org 				t = s;
478*10898Sroland.mainz@nrubsig.org 				goto suffix;
4794887Schin 			}
4804887Schin 			if (flags & ERROR)
4814887Schin 				continue;
4824887Schin 			if (flags & (NEGATIVE|POSITIVE))
4834887Schin 				number = -number;
4844887Schin 			if (opt_info.option[0]=='+')
4854887Schin 				number = -number;
4864887Schin 			continue;
4874887Schin 		case 'f':
4884887Schin 			flags |= FOLLOW;
4894887Schin 			continue;
4904887Schin 		case 'h':
4914887Schin 			if (opt_info.num)
4924887Schin 				flags |= HEADERS;
4934887Schin 			else
4944887Schin 				flags &= ~HEADERS;
4954887Schin 			continue;
496*10898Sroland.mainz@nrubsig.org 		case 'l':
497*10898Sroland.mainz@nrubsig.org 			flags |= LINES;
498*10898Sroland.mainz@nrubsig.org 			if (opt_info.option[0] == '+')
499*10898Sroland.mainz@nrubsig.org 				number = -number;
500*10898Sroland.mainz@nrubsig.org 			continue;
5014887Schin 		case 'L':
5024887Schin 			flags |= LOG;
5034887Schin 			continue;
5044887Schin 		case 'q':
5054887Schin 			flags &= ~HEADERS;
5064887Schin 			continue;
5074887Schin 		case 'r':
5084887Schin 			flags |= REVERSE;
5094887Schin 			continue;
5104887Schin 		case 's':
5114887Schin 			flags |= SILENT;
5124887Schin 			continue;
5134887Schin 		case 't':
5144887Schin 			flags |= TIMEOUT;
5154887Schin 			timeout = strelapsed(opt_info.arg, &s, 1);
5164887Schin 			if (*s)
5174887Schin 				error(ERROR_exit(1), "%s: invalid elapsed time", opt_info.arg);
5184887Schin 			continue;
5194887Schin 		case 'v':
5204887Schin 			flags |= VERBOSE;
5214887Schin 			continue;
5224887Schin 		case ':':
5234887Schin 			/* handle old style arguments */
524*10898Sroland.mainz@nrubsig.org 			if (!(r = argv[opt_info.index]) || !opt_info.offset)
525*10898Sroland.mainz@nrubsig.org 			{
526*10898Sroland.mainz@nrubsig.org 				error(2, "%s", opt_info.arg);
527*10898Sroland.mainz@nrubsig.org 				break;
528*10898Sroland.mainz@nrubsig.org 			}
529*10898Sroland.mainz@nrubsig.org 			s = r + opt_info.offset - 1;
530*10898Sroland.mainz@nrubsig.org 			if (i = *(s - 1) == '-' || *(s - 1) == '+')
531*10898Sroland.mainz@nrubsig.org 				s--;
532*10898Sroland.mainz@nrubsig.org 			if ((number = num(s, &t, &flags, 0)) && i)
533*10898Sroland.mainz@nrubsig.org 				number = -number;
534*10898Sroland.mainz@nrubsig.org 			goto compatibility;
535*10898Sroland.mainz@nrubsig.org 		suffix:
536*10898Sroland.mainz@nrubsig.org 			r = 0;
537*10898Sroland.mainz@nrubsig.org 			if (opt_info.option[0] == '+')
538*10898Sroland.mainz@nrubsig.org 				number = -number;
539*10898Sroland.mainz@nrubsig.org 		compatibility:
5404887Schin 			for (;;)
5414887Schin 			{
5424887Schin 				switch (*t++)
5434887Schin 				{
5444887Schin 				case 0:
545*10898Sroland.mainz@nrubsig.org 					if (r)
546*10898Sroland.mainz@nrubsig.org 						opt_info.offset = t - r - 1;
5474887Schin 					break;
5484887Schin 				case 'c':
549*10898Sroland.mainz@nrubsig.org 					flags &= ~LINES;
5504887Schin 					continue;
5514887Schin 				case 'f':
5524887Schin 					flags |= FOLLOW;
5534887Schin 					continue;
5544887Schin 				case 'l':
555*10898Sroland.mainz@nrubsig.org 					flags |= LINES;
5564887Schin 					continue;
5574887Schin 				case 'r':
5584887Schin 					flags |= REVERSE;
5594887Schin 					continue;
5604887Schin 				default:
5614887Schin 					error(2, "%s: invalid suffix", t - 1);
562*10898Sroland.mainz@nrubsig.org 					if (r)
563*10898Sroland.mainz@nrubsig.org 						opt_info.offset = strlen(r);
5644887Schin 					break;
5654887Schin 				}
5664887Schin 				break;
5674887Schin 			}
5684887Schin 			continue;
5694887Schin 		case '?':
5704887Schin 			error(ERROR_usage(2), "%s", opt_info.arg);
5714887Schin 			break;
5724887Schin 		}
5734887Schin 		break;
5744887Schin 	}
5754887Schin 	argv += opt_info.index;
5764887Schin 	if (!*argv)
5774887Schin 	{
5784887Schin 		flags &= ~HEADERS;
5794887Schin 		if (fstat(0, &st))
5804887Schin 			error(ERROR_system(0), "/dev/stdin: cannot stat");
5814887Schin 		else if (FIFO(st.st_mode))
5824887Schin 			flags &= ~FOLLOW;
5834887Schin 	}
5844887Schin 	else if (!*(argv + 1))
5854887Schin 		flags &= ~HEADERS;
586*10898Sroland.mainz@nrubsig.org 	delim = (flags & LINES) ? '\n' : -1;
587*10898Sroland.mainz@nrubsig.org 	if (blocks)
588*10898Sroland.mainz@nrubsig.org 		number *= blocks;
5894887Schin 	if (flags & REVERSE)
5904887Schin 	{
5914887Schin 		if (delim < 0)
5924887Schin 			error(2, "--reverse requires line mode");
593*10898Sroland.mainz@nrubsig.org 		if (!(flags & COUNT))
594*10898Sroland.mainz@nrubsig.org 			number = -1;
5954887Schin 		flags &= ~FOLLOW;
5964887Schin 	}
5974887Schin 	if ((flags & (FOLLOW|TIMEOUT)) == TIMEOUT)
5984887Schin 	{
5994887Schin 		flags &= ~TIMEOUT;
6004887Schin 		timeout = 0;
6014887Schin 		error(ERROR_warn(0), "--timeout ignored for --noforever");
6024887Schin 	}
6034887Schin 	if ((flags & (LOG|TIMEOUT)) == LOG)
6044887Schin 	{
6054887Schin 		flags &= ~LOG;
6064887Schin 		error(ERROR_warn(0), "--log ignored for --notimeout");
6074887Schin 	}
6084887Schin 	if (error_info.errors)
6094887Schin 		error(ERROR_usage(2), "%s", optusage(NiL));
6104887Schin 	if (flags & FOLLOW)
6114887Schin 	{
6124887Schin 		if (!(fp = (Tail_t*)stakalloc(argc * sizeof(Tail_t))))
6134887Schin 			error(ERROR_system(1), "out of space");
6144887Schin 		files = 0;
6154887Schin 		s = *argv;
6164887Schin 		do
6174887Schin 		{
6184887Schin 			fp->name = s;
6194887Schin 			fp->sp = 0;
620*10898Sroland.mainz@nrubsig.org 			if (!init(fp, number, delim, flags, &format))
6214887Schin 			{
6224887Schin 				fp->expire = timeout ? (NOW + timeout + 1) : 0;
6234887Schin 				if (files)
6244887Schin 					pp->next = fp;
6254887Schin 				else
6264887Schin 					files = fp;
6274887Schin 				pp = fp;
6284887Schin 				fp++;
6294887Schin 			}
6304887Schin 		} while (s && (s = *++argv));
6314887Schin 		if (!files)
6324887Schin 			return error_info.errors != 0;
6334887Schin 		pp->next = 0;
6344887Schin 		hp = 0;
635*10898Sroland.mainz@nrubsig.org 		while (fp = files)
6364887Schin 		{
6374887Schin 			if (sfsync(sfstdout))
6384887Schin 				error(ERROR_system(1), "write error");
639*10898Sroland.mainz@nrubsig.org #if 0
6404887Schin 			sleep(1);
641*10898Sroland.mainz@nrubsig.org #else
642*10898Sroland.mainz@nrubsig.org 			{
643*10898Sroland.mainz@nrubsig.org 				struct timespec rqt = { 0L, 1000000000L/4L };
644*10898Sroland.mainz@nrubsig.org 				(void)nanosleep(&rqt, NULL);
645*10898Sroland.mainz@nrubsig.org 			}
646*10898Sroland.mainz@nrubsig.org #endif
6474887Schin 			n = 0;
6484887Schin 			pp = 0;
6494887Schin 			while (fp)
6504887Schin 			{
6514887Schin 				if (fstat(sffileno(fp->sp), &st))
6524887Schin 					error(ERROR_system(0), "%s: cannot stat", fp->name);
653*10898Sroland.mainz@nrubsig.org 				else if (st.st_size > fp->last || fp->fifo)
6544887Schin 				{
6554887Schin 					n = 1;
6564887Schin 					if (timeout)
6574887Schin 						fp->expire = NOW + timeout;
658*10898Sroland.mainz@nrubsig.org 					z = fp->fifo ? SF_UNBOUND : st.st_size - fp->last;
6594887Schin 					i = 0;
6604887Schin 					if ((s = sfreserve(fp->sp, z, SF_LOCKR)) || (z = sfvalue(fp->sp)) && (s = sfreserve(fp->sp, z, SF_LOCKR)) && (i = 1))
6614887Schin 					{
662*10898Sroland.mainz@nrubsig.org 						if (fp->fifo)
663*10898Sroland.mainz@nrubsig.org 							z = sfvalue(fp->sp);
6644887Schin 						r = 0;
6654887Schin 						for (e = (t = s) + z; t < e; t++)
6664887Schin 							if (*t == '\n')
6674887Schin 								r = t;
6684887Schin 						if (r || i && (r = e))
6694887Schin 						{
6704887Schin 							if ((flags & (HEADERS|VERBOSE)) && hp != fp)
6714887Schin 							{
6724887Schin 								hp = fp;
6734887Schin 								sfprintf(sfstdout, format, fp->name);
6744887Schin 								format = header_fmt;
6754887Schin 							}
6764887Schin 							z = r - s + 1;
6774887Schin 							fp->last += z;
6784887Schin 							sfwrite(sfstdout, s, z);
6794887Schin 						}
6804887Schin 						else
6814887Schin 							z = 0;
6824887Schin 						sfread(fp->sp, s, z);
6834887Schin 					}
6844887Schin 					goto next;
6854887Schin 				}
6864887Schin 				else if (!timeout || fp->expire > NOW)
6874887Schin 					goto next;
6884887Schin 				else
6894887Schin 				{
6904887Schin 					if (flags & LOG)
6914887Schin 					{
6924887Schin 						i = 3;
6934887Schin 						while (--i && stat(fp->name, &st))
6944887Schin 							sleep(1);
695*10898Sroland.mainz@nrubsig.org 						if (i && (fp->dev != st.st_dev || fp->ino != st.st_ino) && !init(fp, 0, 0, flags, &format))
6964887Schin 						{
6974887Schin 							if (!(flags & SILENT))
6984887Schin 								error(ERROR_warn(0), "%s: log file change", fp->name);
6994887Schin 							fp->expire = NOW + timeout;
7004887Schin 							goto next;
7014887Schin 						}
7024887Schin 					}
7034887Schin 					if (!(flags & SILENT))
7044887Schin 						error(ERROR_warn(0), "%s: %s timeout", fp->name, fmtelapsed(timeout, 1));
7054887Schin 				}
7064887Schin 				if (fp->sp && fp->sp != sfstdin)
7074887Schin 					sfclose(fp->sp);
7084887Schin 				if (pp)
7094887Schin 					pp = pp->next = fp->next;
7104887Schin 				else if (!(files = files->next))
7114887Schin 					return error_info.errors != 0;
7124887Schin 				fp = fp->next;
7134887Schin 				continue;
7144887Schin 			next:
7154887Schin 				pp = fp;
7164887Schin 				fp = fp->next;
7174887Schin 			}
7184887Schin 		}
7194887Schin 	}
7204887Schin 	else
7214887Schin 	{
7224887Schin 		if (file = *argv)
7234887Schin 			argv++;
7244887Schin 		do
7254887Schin 		{
7264887Schin 			if (!file || streq(file, "-"))
7274887Schin 			{
7284887Schin 				file = "/dev/stdin";
7294887Schin 				ip = sfstdin;
7304887Schin 			}
7314887Schin 			else if (!(ip = sfopen(NiL, file, "r")))
7324887Schin 			{
7334887Schin 				error(ERROR_system(0), "%s: cannot open", file);
7344887Schin 				continue;
7354887Schin 			}
7364887Schin 			if (flags & (HEADERS|VERBOSE))
737*10898Sroland.mainz@nrubsig.org 			{
7384887Schin 				sfprintf(sfstdout, format, file);
739*10898Sroland.mainz@nrubsig.org 				format = header_fmt;
740*10898Sroland.mainz@nrubsig.org 			}
7414887Schin 			if (number < 0 || !number && (flags & POSITIVE))
7424887Schin 			{
743*10898Sroland.mainz@nrubsig.org 				sfset(ip, SF_SHARE, 1);
7444887Schin 				if (number < -1)
7454887Schin 					sfmove(ip, NiL, -number - 1, delim);
7464887Schin 				if (flags & REVERSE)
7474887Schin 					rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR));
7484887Schin 				else
7494887Schin 					sfmove(ip, sfstdout, SF_UNBOUND, -1);
7504887Schin 			}
7514887Schin 			else
7524887Schin 			{
7534887Schin 				sfset(ip, SF_SHARE, 0);
7544887Schin 				if ((offset = tailpos(ip, number, delim)) >= 0)
7554887Schin 				{
7564887Schin 					if (flags & REVERSE)
7574887Schin 						rev_line(ip, sfstdout, offset);
7584887Schin 					else
7594887Schin 					{
7604887Schin 						sfseek(ip, offset, SEEK_SET);
7614887Schin 						sfmove(ip, sfstdout, SF_UNBOUND, -1);
7624887Schin 					}
7634887Schin 				}
7644887Schin 				else
7654887Schin 				{
7664887Schin 					op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout;
7674887Schin 					pipetail(ip, op, number, delim);
7684887Schin 					if (flags & REVERSE)
7694887Schin 					{
7704887Schin 						sfseek(op, (Sfoff_t)0, SEEK_SET);
7714887Schin 						rev_line(op, sfstdout, (Sfoff_t)0);
7724887Schin 						sfclose(op);
7734887Schin 					}
7744887Schin 					flags = 0;
7754887Schin 				}
7764887Schin 			}
7774887Schin 			if (ip != sfstdin)
7784887Schin 				sfclose(ip);
7794887Schin 		} while (file = *argv++);
7804887Schin 	}
7814887Schin 	return error_info.errors != 0;
7824887Schin }
783