1 /***********************************************************************
3 * This software is part of the ast package *
4 * Copyright (c) 1992-2010 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Common Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
13 * Information and Software Systems Research *
17 * Glenn Fowler <gsf@research.att.com> *
18 * David Korn <dgk@research.att.com> *
20 ***********************************************************************/
24 * print the tail of one or more files
30 static const char usage
[] =
31 "+[-?\n@(#)$Id: tail (AT&T Research) 2010-03-23 $\n]"
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:]{"
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.]"
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 "
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 "
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]{"
89 "[v:verbose?Always ouput filename headers.]"
96 "[+0?All files copied successfully.]"
97 "[+>0?One or more files did not copy.]"
99 "[+SEE ALSO?\bcat\b(1), \bhead\b(1), \brev\b(1)]"
110 #define FOLLOW (1<<2)
111 #define HEADERS (1<<3)
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)
121 #define NOW (unsigned long)time(NiL)
126 #define FIFO(m) (S_ISFIFO(m)||S_ISSOCK(m))
128 #define FIFO(m) S_ISFIFO(m)
131 struct Tail_s
; typedef struct Tail_s Tail_t
;
140 unsigned long expire
;
146 static const char header_fmt
[] = "\n==> %s <==\n";
149 * if file is seekable, position file to tail location and return offset
150 * otherwise, return -1
154 tailpos(register Sfio_t
* fp
, register Sfoff_t number
, int delim
)
157 register Sfoff_t offset
;
158 register Sfoff_t first
;
159 register Sfoff_t last
;
165 if ((first
= sfseek(fp
, (Sfoff_t
)0, SEEK_CUR
)) < 0)
166 return last
|| fstat(sffileno(fp
), &st
) || st
.st_size
|| FIFO(st
.st_mode
) ? -1 : 0;
169 if ((offset
= last
- number
) < first
)
175 if ((offset
= last
- SF_BUFSIZE
) < first
)
177 sfseek(fp
, offset
, SEEK_SET
);
179 if (!(s
= sfreserve(fp
, n
, SF_LOCKR
)))
183 if (*--t
== delim
&& number
-- <= 0)
186 return offset
+ (t
- s
) + 1;
197 * this code handles tail from a pipe without any size limits
201 pipetail(Sfio_t
* infile
, Sfio_t
* outfile
, Sfoff_t number
, int delim
)
203 register Sfio_t
* out
;
205 register Sfoff_t nleft
= number
;
206 register size_t a
= 2 * SF_BUFSIZE
;
207 register int fno
= 0;
211 if (delim
< 0 && a
> number
)
213 out
= tmp
[0] = sftmp(a
);
215 offset
[0] = offset
[1] = 0;
216 while ((n
= sfmove(infile
, out
, number
, delim
)) > 0)
218 offset
[fno
] = sftell(out
);
219 if ((nleft
-= n
) <= 0)
221 out
= tmp
[fno
= !fno
];
222 sfseek(out
, (Sfoff_t
)0, SEEK_SET
);
231 sfseek(tmp
[0], (Sfoff_t
)0, SEEK_SET
);
234 * see whether both files are needed
239 sfseek(tmp
[1], (Sfoff_t
)0, SEEK_SET
);
240 if ((n
= number
- nleft
) > 0)
241 sfmove(tmp
[!fno
], NiL
, n
, delim
);
242 if ((n
= offset
[!fno
] - sftell(tmp
[!fno
])) > 0)
243 sfmove(tmp
[!fno
], outfile
, n
, -1);
247 sfmove(tmp
[fno
], outfile
, offset
[fno
], -1);
253 * (re)initialize a tail stream
257 init(Tail_t
* tp
, Sfoff_t number
, int delim
, int flags
, const char** format
)
267 if (tp
->sp
== sfstdin
)
274 if (!tp
->name
|| streq(tp
->name
, "-"))
276 tp
->name
= "/dev/stdin";
279 else if (!(tp
->sp
= sfopen(tp
->sp
, tp
->name
, "r")))
281 error(ERROR_system(0), "%s: cannot open", tp
->name
);
284 sfset(tp
->sp
, SF_SHARE
, 0);
287 if (number
< 0 || !number
&& (flags
& POSITIVE
))
289 sfset(tp
->sp
, SF_SHARE
, !(flags
& FOLLOW
));
292 sfmove(tp
->sp
, NiL
, -number
- 1, delim
);
293 offset
= sfseek(tp
->sp
, (Sfoff_t
)0, SEEK_CUR
);
298 else if ((offset
= tailpos(tp
->sp
, number
, delim
)) >= 0)
299 sfseek(tp
->sp
, offset
, SEEK_SET
);
300 else if (fstat(sffileno(tp
->sp
), &st
))
302 error(ERROR_system(0), "%s: cannot stat", tp
->name
);
305 else if (!FIFO(st
.st_mode
))
307 error(ERROR_SYSTEM
|2, "%s: cannot position file to tail", tp
->name
);
313 if (flags
& (HEADERS
|VERBOSE
))
315 sfprintf(sfstdout
, *format
, tp
->name
);
316 *format
= header_fmt
;
318 op
= (flags
& REVERSE
) ? sftmp(4*SF_BUFSIZE
) : sfstdout
;
319 pipetail(tp
->sp
? tp
->sp
: sfstdin
, op
, number
, delim
);
322 sfseek(op
, (Sfoff_t
)0, SEEK_SET
);
323 rev_line(op
, sfstdout
, (Sfoff_t
)0);
328 tp
->cur
= tp
->end
= offset
;
331 if (fstat(sffileno(tp
->sp
), &st
))
333 error(ERROR_system(0), "%s: cannot stat", tp
->name
);
341 if (tp
->sp
!= sfstdin
)
348 * convert number with validity diagnostics
352 num(register const char* s
, char** e
, int* f
, int o
)
358 *f
&= ~(ERROR
|NEGATIVE
|POSITIVE
);
369 while (*s
== '0' && isdigit(*(s
+ 1)))
372 number
= strtonll(s
, &t
, NiL
, 0);
379 error(2, "-%c: %s: invalid numeric argument -- unknown suffix", o
, s
);
385 error(2, "-%c: %s: invalid numeric argument -- out of range", o
, s
);
387 error(2, "%s: invalid numeric argument -- out of range", s
);
392 if (t
> s
&& isalpha(*(t
- 1)))
403 b_tail(int argc
, char** argv
, void* context
)
409 int flags
= HEADERS
|LINES
;
416 Sfoff_t number
= DEFAULT
;
417 unsigned long timeout
= 0;
419 const char* format
= header_fmt
+1;
428 cmdinit(argc
, argv
, context
, ERROR_CATALOG
, 0);
431 switch (n
= optget(argv
, usage
))
434 if (!(flags
& FOLLOW
) && argv
[opt_info
.index
] && (argv
[opt_info
.index
][0] == '-' || argv
[opt_info
.index
][0] == '+') && !argv
[opt_info
.index
][1])
436 number
= argv
[opt_info
.index
][0] == '-' ? 10 : -10;
445 if (opt_info
.option
[0] == '+')
450 if (opt_info
.arg
== argv
[opt_info
.index
- 1])
452 strtol(opt_info
.arg
, &s
, 10);
460 else if (opt_info
.arg
&& isalpha(*opt_info
.arg
))
468 if (s
= opt_info
.arg
)
469 number
= num(s
, &s
, &flags
, n
);
473 flags
&= ~(ERROR
|NEGATIVE
|POSITIVE
);
476 if (n
!= 'n' && s
&& isalpha(*s
))
483 if (flags
& (NEGATIVE
|POSITIVE
))
485 if (opt_info
.option
[0]=='+')
499 if (opt_info
.option
[0] == '+')
516 timeout
= strelapsed(opt_info
.arg
, &s
, 1);
518 error(ERROR_exit(1), "%s: invalid elapsed time [%s]", opt_info
.arg
, s
);
524 /* handle old style arguments */
525 if (!(r
= argv
[opt_info
.index
]) || !opt_info
.offset
)
527 error(2, "%s", opt_info
.arg
);
530 s
= r
+ opt_info
.offset
- 1;
531 if (i
= *(s
- 1) == '-' || *(s
- 1) == '+')
533 if ((number
= num(s
, &t
, &flags
, 0)) && i
)
538 if (opt_info
.option
[0] == '+')
547 opt_info
.offset
= t
- r
- 1;
562 error(2, "%s: invalid suffix", t
- 1);
564 opt_info
.offset
= strlen(r
);
571 error(ERROR_usage(2), "%s", opt_info
.arg
);
576 argv
+= opt_info
.index
;
581 error(ERROR_system(0), "/dev/stdin: cannot stat");
582 else if (FIFO(st
.st_mode
))
585 else if (!*(argv
+ 1))
587 delim
= (flags
& LINES
) ? '\n' : -1;
593 error(2, "--reverse requires line mode");
594 if (!(flags
& COUNT
))
598 if ((flags
& (FOLLOW
|TIMEOUT
)) == TIMEOUT
)
602 error(ERROR_warn(0), "--timeout ignored for --noforever");
604 if ((flags
& (LOG
|TIMEOUT
)) == LOG
)
607 error(ERROR_warn(0), "--log ignored for --notimeout");
609 if (error_info
.errors
)
610 error(ERROR_usage(2), "%s", optusage(NiL
));
613 if (!(fp
= (Tail_t
*)stakalloc(argc
* sizeof(Tail_t
))))
614 error(ERROR_system(1), "out of space");
621 if (!init(fp
, number
, delim
, flags
, &format
))
623 fp
->expire
= timeout
? (NOW
+ timeout
+ 1) : 0;
631 } while (s
&& (s
= *++argv
));
633 return error_info
.errors
!= 0;
646 if (fstat(sffileno(fp
->sp
), &st
))
647 error(ERROR_system(0), "%s: cannot stat", fp
->name
);
648 else if (fp
->fifo
|| fp
->end
< st
.st_size
)
652 fp
->expire
= NOW
+ timeout
;
653 z
= fp
->fifo
? SF_UNBOUND
: st
.st_size
- fp
->cur
;
655 if ((s
= sfreserve(fp
->sp
, z
, SF_LOCKR
)) || (z
= sfvalue(fp
->sp
)) && (s
= sfreserve(fp
->sp
, z
, SF_LOCKR
)) && (i
= 1))
658 for (r
= s
+ z
; r
> s
&& *(r
- 1) != '\n'; r
--);
659 if ((w
= r
- s
) || i
&& (w
= z
))
661 if ((flags
& (HEADERS
|VERBOSE
)) && hp
!= fp
)
664 sfprintf(sfstdout
, format
, fp
->name
);
668 sfwrite(sfstdout
, s
, w
);
672 sfread(fp
->sp
, s
, w
);
677 else if (!timeout
|| fp
->expire
> NOW
)
684 while (--i
&& stat(fp
->name
, &st
))
686 if (i
&& (fp
->dev
!= st
.st_dev
|| fp
->ino
!= st
.st_ino
) && !init(fp
, 0, 0, flags
, &format
))
688 if (!(flags
& SILENT
))
689 error(ERROR_warn(0), "%s: log file change", fp
->name
);
690 fp
->expire
= NOW
+ timeout
;
694 if (!(flags
& SILENT
))
695 error(ERROR_warn(0), "%s: %s timeout", fp
->name
, fmtelapsed(timeout
, 1));
697 if (fp
->sp
&& fp
->sp
!= sfstdin
)
700 pp
= pp
->next
= fp
->next
;
709 if (sfsync(sfstdout
))
710 error(ERROR_system(1), "write error");
719 if (!file
|| streq(file
, "-"))
724 else if (!(ip
= sfopen(NiL
, file
, "r")))
726 error(ERROR_system(0), "%s: cannot open", file
);
729 if (flags
& (HEADERS
|VERBOSE
))
731 sfprintf(sfstdout
, format
, file
);
734 if (number
< 0 || !number
&& (flags
& POSITIVE
))
736 sfset(ip
, SF_SHARE
, 1);
738 sfmove(ip
, NiL
, -number
- 1, delim
);
740 rev_line(ip
, sfstdout
, sfseek(ip
, (Sfoff_t
)0, SEEK_CUR
));
742 sfmove(ip
, sfstdout
, SF_UNBOUND
, -1);
746 sfset(ip
, SF_SHARE
, 0);
747 if ((offset
= tailpos(ip
, number
, delim
)) >= 0)
750 rev_line(ip
, sfstdout
, offset
);
753 sfseek(ip
, offset
, SEEK_SET
);
754 sfmove(ip
, sfstdout
, SF_UNBOUND
, -1);
759 op
= (flags
& REVERSE
) ? sftmp(4*SF_BUFSIZE
) : sfstdout
;
760 pipetail(ip
, op
, number
, delim
);
763 sfseek(op
, (Sfoff_t
)0, SEEK_SET
);
764 rev_line(op
, sfstdout
, (Sfoff_t
)0);
772 } while (file
= *argv
++);
774 return error_info
.errors
!= 0;