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