1da2e3ebdSchin /***********************************************************************
2da2e3ebdSchin * *
3da2e3ebdSchin * This software is part of the ast package *
4*d6f391efSAndy Fiddaman * Copyright (c) 1992-2013 AT&T Intellectual Property *
5da2e3ebdSchin * and is licensed under the *
6b30d1939SAndy Fiddaman * Eclipse Public License, Version 1.0 *
77c2fbfb3SApril Chin * by AT&T Intellectual Property *
8da2e3ebdSchin * *
9da2e3ebdSchin * A copy of the License is available at *
10b30d1939SAndy Fiddaman * http://www.eclipse.org/org/documents/epl-v10.html *
11b30d1939SAndy Fiddaman * (with md5 checksum b35adb5213ca9657e911e9befb180842) *
12da2e3ebdSchin * *
13da2e3ebdSchin * Information and Software Systems Research *
14da2e3ebdSchin * AT&T Research *
15da2e3ebdSchin * Florham Park NJ *
16da2e3ebdSchin * *
17*d6f391efSAndy Fiddaman * Glenn Fowler <glenn.s.fowler@gmail.com> *
18*d6f391efSAndy Fiddaman * David Korn <dgkorn@gmail.com> *
19da2e3ebdSchin * *
20da2e3ebdSchin ***********************************************************************/
21da2e3ebdSchin #pragma prototyped
22da2e3ebdSchin
23da2e3ebdSchin /*
24da2e3ebdSchin * print the tail of one or more files
25da2e3ebdSchin *
26da2e3ebdSchin * David Korn
27da2e3ebdSchin * Glenn Fowler
28da2e3ebdSchin */
29da2e3ebdSchin
30da2e3ebdSchin static const char usage[] =
31*d6f391efSAndy Fiddaman "+[-?\n@(#)$Id: tail (AT&T Research) 2013-09-19 $\n]"
32da2e3ebdSchin USAGE_LICENSE
33da2e3ebdSchin "[+NAME?tail - output trailing portion of one or more files ]"
34da2e3ebdSchin "[+DESCRIPTION?\btail\b copies one or more input files to standard output "
35da2e3ebdSchin "starting at a designated point for each file. Copying starts "
36da2e3ebdSchin "at the point indicated by the options and is unlimited in size.]"
37da2e3ebdSchin "[+?By default a header of the form \b==> \b\afilename\a\b <==\b "
38da2e3ebdSchin "is output before all but the first file but this can be changed "
39da2e3ebdSchin "with the \b-q\b and \b-v\b options.]"
40da2e3ebdSchin "[+?If no \afile\a is given, or if the \afile\a is \b-\b, \btail\b "
41da2e3ebdSchin "copies from standard input. The start of the file is defined "
42da2e3ebdSchin "as the current offset.]"
43da2e3ebdSchin "[+?The option argument for \b-c\b can optionally be "
44da2e3ebdSchin "followed by one of the following characters to specify a different "
45da2e3ebdSchin "unit other than a single byte:]{"
46da2e3ebdSchin "[+b?512 bytes.]"
4734f9b3eeSRoland Mainz "[+k?1 KiB.]"
4834f9b3eeSRoland Mainz "[+m?1 MiB.]"
4934f9b3eeSRoland Mainz "[+g?1 GiB.]"
50da2e3ebdSchin "}"
51da2e3ebdSchin "[+?For backwards compatibility, \b-\b\anumber\a is equivalent to "
52da2e3ebdSchin "\b-n\b \anumber\a and \b+\b\anumber\a is equivalent to "
5334f9b3eeSRoland Mainz "\b-n -\b\anumber\a. \anumber\a may also have these option "
5434f9b3eeSRoland Mainz "suffixes: \bb c f g k l m r\b.]"
55da2e3ebdSchin
56da2e3ebdSchin "[n:lines]:[lines:=10?Copy \alines\a lines from each file. A negative value "
5734f9b3eeSRoland Mainz "for \alines\a indicates an offset from the end of the file.]"
5834f9b3eeSRoland Mainz "[b:blocks?Copy units of 512 bytes.]"
59da2e3ebdSchin "[c:bytes]:?[chars?Copy \achars\a bytes from each file. A negative value "
6034f9b3eeSRoland Mainz "for \achars\a indicates an offset from the end of the file.]"
61da2e3ebdSchin "[f:forever|follow?Loop forever trying to read more characters as the "
62da2e3ebdSchin "end of each file to copy new data. Ignored if reading from a pipe "
63da2e3ebdSchin "or fifo.]"
64da2e3ebdSchin "[h!:headers?Output filename headers.]"
6534f9b3eeSRoland Mainz "[l:lines?Copy units of lines. This is the default.]"
66da2e3ebdSchin "[L:log?When a \b--forever\b file times out via \b--timeout\b, verify that "
67da2e3ebdSchin "the curent file has not been renamed and replaced by another file "
68da2e3ebdSchin "of the same name (a common log file practice) before giving up on "
69da2e3ebdSchin "the file.]"
70da2e3ebdSchin "[q:quiet?Don't output filename headers. For GNU compatibility.]"
71da2e3ebdSchin "[r:reverse?Output lines in reverse order.]"
72da2e3ebdSchin "[s:silent?Don't warn about timeout expiration and log file changes.]"
73da2e3ebdSchin "[t:timeout?Stop checking after \atimeout\a elapses with no additional "
74da2e3ebdSchin "\b--forever\b output. A separate elapsed time is maintained for "
75da2e3ebdSchin "each file operand. There is no timeout by default. The default "
76da2e3ebdSchin "\atimeout\a unit is seconds. \atimeout\a may be a catenation of 1 "
77da2e3ebdSchin "or more integers, each followed by a 1 character suffix. The suffix "
78da2e3ebdSchin "may be omitted from the last integer, in which case it is "
79da2e3ebdSchin "interpreted as seconds. The supported suffixes are:]:[timeout]{"
80da2e3ebdSchin "[+s?seconds]"
81da2e3ebdSchin "[+m?minutes]"
82da2e3ebdSchin "[+h?hours]"
83da2e3ebdSchin "[+d?days]"
84da2e3ebdSchin "[+w?weeks]"
85da2e3ebdSchin "[+M?months]"
86da2e3ebdSchin "[+y?years]"
87da2e3ebdSchin "[+S?scores]"
88da2e3ebdSchin "}"
89da2e3ebdSchin "[v:verbose?Always ouput filename headers.]"
9034f9b3eeSRoland Mainz
91da2e3ebdSchin "\n"
92da2e3ebdSchin "\n[file ...]\n"
93da2e3ebdSchin "\n"
9434f9b3eeSRoland Mainz
95da2e3ebdSchin "[+EXIT STATUS?]{"
96da2e3ebdSchin "[+0?All files copied successfully.]"
97da2e3ebdSchin "[+>0?One or more files did not copy.]"
98da2e3ebdSchin "}"
99da2e3ebdSchin "[+SEE ALSO?\bcat\b(1), \bhead\b(1), \brev\b(1)]"
100da2e3ebdSchin ;
101da2e3ebdSchin
102da2e3ebdSchin #include <cmd.h>
103da2e3ebdSchin #include <ctype.h>
104da2e3ebdSchin #include <ls.h>
105b30d1939SAndy Fiddaman #include <tv.h>
106da2e3ebdSchin #include <rev.h>
107da2e3ebdSchin
108da2e3ebdSchin #define COUNT (1<<0)
109da2e3ebdSchin #define ERROR (1<<1)
110da2e3ebdSchin #define FOLLOW (1<<2)
111da2e3ebdSchin #define HEADERS (1<<3)
11234f9b3eeSRoland Mainz #define LINES (1<<4)
11334f9b3eeSRoland Mainz #define LOG (1<<5)
11434f9b3eeSRoland Mainz #define NEGATIVE (1<<6)
11534f9b3eeSRoland Mainz #define POSITIVE (1<<7)
11634f9b3eeSRoland Mainz #define REVERSE (1<<8)
11734f9b3eeSRoland Mainz #define SILENT (1<<9)
11834f9b3eeSRoland Mainz #define TIMEOUT (1<<10)
11934f9b3eeSRoland Mainz #define VERBOSE (1<<11)
120da2e3ebdSchin
121da2e3ebdSchin #define NOW (unsigned long)time(NiL)
122da2e3ebdSchin
12334f9b3eeSRoland Mainz #define DEFAULT 10
124da2e3ebdSchin
125da2e3ebdSchin #ifdef S_ISSOCK
126da2e3ebdSchin #define FIFO(m) (S_ISFIFO(m)||S_ISSOCK(m))
127da2e3ebdSchin #else
128da2e3ebdSchin #define FIFO(m) S_ISFIFO(m)
129da2e3ebdSchin #endif
130da2e3ebdSchin
131da2e3ebdSchin struct Tail_s; typedef struct Tail_s Tail_t;
132da2e3ebdSchin
133da2e3ebdSchin struct Tail_s
134da2e3ebdSchin {
135da2e3ebdSchin Tail_t* next;
136da2e3ebdSchin char* name;
137da2e3ebdSchin Sfio_t* sp;
1383e14f97fSRoger A. Faulkner Sfoff_t cur;
1393e14f97fSRoger A. Faulkner Sfoff_t end;
140da2e3ebdSchin unsigned long expire;
141da2e3ebdSchin long dev;
142da2e3ebdSchin long ino;
14334f9b3eeSRoland Mainz int fifo;
144da2e3ebdSchin };
145da2e3ebdSchin
14634f9b3eeSRoland Mainz static const char header_fmt[] = "\n==> %s <==\n";
14734f9b3eeSRoland Mainz
148da2e3ebdSchin /*
149da2e3ebdSchin * if file is seekable, position file to tail location and return offset
150da2e3ebdSchin * otherwise, return -1
151da2e3ebdSchin */
152da2e3ebdSchin
153da2e3ebdSchin static Sfoff_t
tailpos(register Sfio_t * fp,register Sfoff_t number,int delim)154da2e3ebdSchin tailpos(register Sfio_t* fp, register Sfoff_t number, int delim)
155da2e3ebdSchin {
156da2e3ebdSchin register size_t n;
157da2e3ebdSchin register Sfoff_t offset;
158da2e3ebdSchin register Sfoff_t first;
159da2e3ebdSchin register Sfoff_t last;
160da2e3ebdSchin register char* s;
161da2e3ebdSchin register char* t;
162*d6f391efSAndy Fiddaman int incomplete;
163da2e3ebdSchin struct stat st;
164da2e3ebdSchin
165*d6f391efSAndy Fiddaman error(-1, "AHA#%d tail number=%I*d", __LINE__, sizeof(number), number);
166da2e3ebdSchin last = sfsize(fp);
167da2e3ebdSchin if ((first = sfseek(fp, (Sfoff_t)0, SEEK_CUR)) < 0)
168da2e3ebdSchin return last || fstat(sffileno(fp), &st) || st.st_size || FIFO(st.st_mode) ? -1 : 0;
169da2e3ebdSchin if (delim < 0)
170da2e3ebdSchin {
171da2e3ebdSchin if ((offset = last - number) < first)
172da2e3ebdSchin return first;
173da2e3ebdSchin return offset;
174da2e3ebdSchin }
175*d6f391efSAndy Fiddaman incomplete = 1;
176da2e3ebdSchin for (;;)
177da2e3ebdSchin {
17834f9b3eeSRoland Mainz if ((offset = last - SF_BUFSIZE) < first)
17934f9b3eeSRoland Mainz offset = first;
180da2e3ebdSchin sfseek(fp, offset, SEEK_SET);
181da2e3ebdSchin n = last - offset;
182da2e3ebdSchin if (!(s = sfreserve(fp, n, SF_LOCKR)))
183da2e3ebdSchin return -1;
184da2e3ebdSchin t = s + n;
185*d6f391efSAndy Fiddaman if (incomplete)
186*d6f391efSAndy Fiddaman {
187*d6f391efSAndy Fiddaman if (t > s && *(t - 1) != delim && number-- <= 0)
188*d6f391efSAndy Fiddaman {
189*d6f391efSAndy Fiddaman sfread(fp, s, 0);
190*d6f391efSAndy Fiddaman return offset + (t - s);
191*d6f391efSAndy Fiddaman }
192*d6f391efSAndy Fiddaman incomplete = 0;
193*d6f391efSAndy Fiddaman }
194da2e3ebdSchin while (t > s)
195da2e3ebdSchin if (*--t == delim && number-- <= 0)
196da2e3ebdSchin {
197da2e3ebdSchin sfread(fp, s, 0);
198da2e3ebdSchin return offset + (t - s) + 1;
199da2e3ebdSchin }
200da2e3ebdSchin sfread(fp, s, 0);
201da2e3ebdSchin if (offset == first)
202da2e3ebdSchin break;
203da2e3ebdSchin last = offset;
204da2e3ebdSchin }
205da2e3ebdSchin return first;
206da2e3ebdSchin }
207da2e3ebdSchin
208da2e3ebdSchin /*
209da2e3ebdSchin * this code handles tail from a pipe without any size limits
210da2e3ebdSchin */
211da2e3ebdSchin
212da2e3ebdSchin static void
pipetail(Sfio_t * infile,Sfio_t * outfile,Sfoff_t number,int delim)213da2e3ebdSchin pipetail(Sfio_t* infile, Sfio_t* outfile, Sfoff_t number, int delim)
214da2e3ebdSchin {
215da2e3ebdSchin register Sfio_t* out;
216da2e3ebdSchin register Sfoff_t n;
217da2e3ebdSchin register Sfoff_t nleft = number;
218da2e3ebdSchin register size_t a = 2 * SF_BUFSIZE;
219da2e3ebdSchin register int fno = 0;
220da2e3ebdSchin Sfoff_t offset[2];
221da2e3ebdSchin Sfio_t* tmp[2];
222da2e3ebdSchin
223da2e3ebdSchin if (delim < 0 && a > number)
224da2e3ebdSchin a = number;
225da2e3ebdSchin out = tmp[0] = sftmp(a);
226da2e3ebdSchin tmp[1] = sftmp(a);
227da2e3ebdSchin offset[0] = offset[1] = 0;
228da2e3ebdSchin while ((n = sfmove(infile, out, number, delim)) > 0)
229da2e3ebdSchin {
230da2e3ebdSchin offset[fno] = sftell(out);
231da2e3ebdSchin if ((nleft -= n) <= 0)
232da2e3ebdSchin {
233da2e3ebdSchin out = tmp[fno= !fno];
234da2e3ebdSchin sfseek(out, (Sfoff_t)0, SEEK_SET);
235da2e3ebdSchin nleft = number;
236da2e3ebdSchin }
237da2e3ebdSchin }
238da2e3ebdSchin if (nleft == number)
239da2e3ebdSchin {
240da2e3ebdSchin offset[fno] = 0;
241da2e3ebdSchin fno= !fno;
242da2e3ebdSchin }
243da2e3ebdSchin sfseek(tmp[0], (Sfoff_t)0, SEEK_SET);
244da2e3ebdSchin
245da2e3ebdSchin /*
246da2e3ebdSchin * see whether both files are needed
247da2e3ebdSchin */
248da2e3ebdSchin
249da2e3ebdSchin if (offset[fno])
250da2e3ebdSchin {
251da2e3ebdSchin sfseek(tmp[1], (Sfoff_t)0, SEEK_SET);
252da2e3ebdSchin if ((n = number - nleft) > 0)
253da2e3ebdSchin sfmove(tmp[!fno], NiL, n, delim);
254da2e3ebdSchin if ((n = offset[!fno] - sftell(tmp[!fno])) > 0)
255da2e3ebdSchin sfmove(tmp[!fno], outfile, n, -1);
256da2e3ebdSchin }
257da2e3ebdSchin else
258da2e3ebdSchin fno = !fno;
259da2e3ebdSchin sfmove(tmp[fno], outfile, offset[fno], -1);
260da2e3ebdSchin sfclose(tmp[0]);
261da2e3ebdSchin sfclose(tmp[1]);
262da2e3ebdSchin }
263da2e3ebdSchin
264da2e3ebdSchin /*
265da2e3ebdSchin * (re)initialize a tail stream
266da2e3ebdSchin */
267da2e3ebdSchin
268da2e3ebdSchin static int
init(Tail_t * tp,Sfoff_t number,int delim,int flags,const char ** format)26934f9b3eeSRoland Mainz init(Tail_t* tp, Sfoff_t number, int delim, int flags, const char** format)
270da2e3ebdSchin {
271da2e3ebdSchin Sfoff_t offset;
27234f9b3eeSRoland Mainz Sfio_t* op;
273da2e3ebdSchin struct stat st;
274da2e3ebdSchin
27534f9b3eeSRoland Mainz tp->fifo = 0;
276da2e3ebdSchin if (tp->sp)
277da2e3ebdSchin {
278da2e3ebdSchin offset = 0;
279da2e3ebdSchin if (tp->sp == sfstdin)
280da2e3ebdSchin tp->sp = 0;
281da2e3ebdSchin }
282da2e3ebdSchin else
283da2e3ebdSchin offset = 1;
284da2e3ebdSchin if (!tp->name || streq(tp->name, "-"))
285da2e3ebdSchin {
286da2e3ebdSchin tp->name = "/dev/stdin";
287da2e3ebdSchin tp->sp = sfstdin;
288da2e3ebdSchin }
289da2e3ebdSchin else if (!(tp->sp = sfopen(tp->sp, tp->name, "r")))
290da2e3ebdSchin {
291da2e3ebdSchin error(ERROR_system(0), "%s: cannot open", tp->name);
292da2e3ebdSchin return -1;
293da2e3ebdSchin }
294da2e3ebdSchin sfset(tp->sp, SF_SHARE, 0);
295*d6f391efSAndy Fiddaman error(-1, "AHA#%d offset=%I*d number=%I*d", __LINE__, sizeof(offset), offset, sizeof(number), number);
296da2e3ebdSchin if (offset)
297da2e3ebdSchin {
29834f9b3eeSRoland Mainz if (number < 0 || !number && (flags & POSITIVE))
29934f9b3eeSRoland Mainz {
30034f9b3eeSRoland Mainz sfset(tp->sp, SF_SHARE, !(flags & FOLLOW));
30134f9b3eeSRoland Mainz if (number < -1)
30234f9b3eeSRoland Mainz {
30334f9b3eeSRoland Mainz sfmove(tp->sp, NiL, -number - 1, delim);
30434f9b3eeSRoland Mainz offset = sfseek(tp->sp, (Sfoff_t)0, SEEK_CUR);
30534f9b3eeSRoland Mainz }
30634f9b3eeSRoland Mainz else
30734f9b3eeSRoland Mainz offset = 0;
30834f9b3eeSRoland Mainz }
30934f9b3eeSRoland Mainz else if ((offset = tailpos(tp->sp, number, delim)) >= 0)
31034f9b3eeSRoland Mainz sfseek(tp->sp, offset, SEEK_SET);
31134f9b3eeSRoland Mainz else if (fstat(sffileno(tp->sp), &st))
31234f9b3eeSRoland Mainz {
31334f9b3eeSRoland Mainz error(ERROR_system(0), "%s: cannot stat", tp->name);
31434f9b3eeSRoland Mainz goto bad;
31534f9b3eeSRoland Mainz }
31634f9b3eeSRoland Mainz else if (!FIFO(st.st_mode))
317da2e3ebdSchin {
318da2e3ebdSchin error(ERROR_SYSTEM|2, "%s: cannot position file to tail", tp->name);
319da2e3ebdSchin goto bad;
320da2e3ebdSchin }
32134f9b3eeSRoland Mainz else
32234f9b3eeSRoland Mainz {
32334f9b3eeSRoland Mainz tp->fifo = 1;
32434f9b3eeSRoland Mainz if (flags & (HEADERS|VERBOSE))
32534f9b3eeSRoland Mainz {
32634f9b3eeSRoland Mainz sfprintf(sfstdout, *format, tp->name);
32734f9b3eeSRoland Mainz *format = header_fmt;
32834f9b3eeSRoland Mainz }
32934f9b3eeSRoland Mainz op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout;
33034f9b3eeSRoland Mainz pipetail(tp->sp ? tp->sp : sfstdin, op, number, delim);
33134f9b3eeSRoland Mainz if (flags & REVERSE)
33234f9b3eeSRoland Mainz {
33334f9b3eeSRoland Mainz sfseek(op, (Sfoff_t)0, SEEK_SET);
33434f9b3eeSRoland Mainz rev_line(op, sfstdout, (Sfoff_t)0);
33534f9b3eeSRoland Mainz sfclose(op);
33634f9b3eeSRoland Mainz }
33734f9b3eeSRoland Mainz }
338da2e3ebdSchin }
3393e14f97fSRoger A. Faulkner tp->cur = tp->end = offset;
340da2e3ebdSchin if (flags & LOG)
341da2e3ebdSchin {
342da2e3ebdSchin if (fstat(sffileno(tp->sp), &st))
343da2e3ebdSchin {
344da2e3ebdSchin error(ERROR_system(0), "%s: cannot stat", tp->name);
345da2e3ebdSchin goto bad;
346da2e3ebdSchin }
347da2e3ebdSchin tp->dev = st.st_dev;
348da2e3ebdSchin tp->ino = st.st_ino;
349da2e3ebdSchin }
350da2e3ebdSchin return 0;
351da2e3ebdSchin bad:
352da2e3ebdSchin if (tp->sp != sfstdin)
353da2e3ebdSchin sfclose(tp->sp);
354da2e3ebdSchin tp->sp = 0;
355da2e3ebdSchin return -1;
356da2e3ebdSchin }
357da2e3ebdSchin
358da2e3ebdSchin /*
359da2e3ebdSchin * convert number with validity diagnostics
360da2e3ebdSchin */
361da2e3ebdSchin
362da2e3ebdSchin static intmax_t
num(register const char * s,char ** e,int * f,int o)363da2e3ebdSchin num(register const char* s, char** e, int* f, int o)
364da2e3ebdSchin {
365da2e3ebdSchin intmax_t number;
366da2e3ebdSchin char* t;
367da2e3ebdSchin int c;
368da2e3ebdSchin
369da2e3ebdSchin *f &= ~(ERROR|NEGATIVE|POSITIVE);
370da2e3ebdSchin if ((c = *s) == '-')
371da2e3ebdSchin {
372da2e3ebdSchin *f |= NEGATIVE;
373da2e3ebdSchin s++;
374da2e3ebdSchin }
375da2e3ebdSchin else if (c == '+')
376da2e3ebdSchin {
377da2e3ebdSchin *f |= POSITIVE;
378da2e3ebdSchin s++;
379da2e3ebdSchin }
380da2e3ebdSchin while (*s == '0' && isdigit(*(s + 1)))
381da2e3ebdSchin s++;
382da2e3ebdSchin errno = 0;
383da2e3ebdSchin number = strtonll(s, &t, NiL, 0);
384da2e3ebdSchin if (t == s)
38534f9b3eeSRoland Mainz number = DEFAULT;
386da2e3ebdSchin if (o && *t)
387da2e3ebdSchin {
388da2e3ebdSchin number = 0;
389da2e3ebdSchin *f |= ERROR;
390da2e3ebdSchin error(2, "-%c: %s: invalid numeric argument -- unknown suffix", o, s);
391da2e3ebdSchin }
392da2e3ebdSchin else if (errno)
393da2e3ebdSchin {
394da2e3ebdSchin *f |= ERROR;
395da2e3ebdSchin if (o)
396da2e3ebdSchin error(2, "-%c: %s: invalid numeric argument -- out of range", o, s);
397da2e3ebdSchin else
398da2e3ebdSchin error(2, "%s: invalid numeric argument -- out of range", s);
399da2e3ebdSchin }
400da2e3ebdSchin else
401da2e3ebdSchin {
402da2e3ebdSchin *f |= COUNT;
40334f9b3eeSRoland Mainz if (t > s && isalpha(*(t - 1)))
40434f9b3eeSRoland Mainz *f &= ~LINES;
405da2e3ebdSchin if (c == '-')
406da2e3ebdSchin number = -number;
407da2e3ebdSchin }
408da2e3ebdSchin if (e)
409da2e3ebdSchin *e = t;
410da2e3ebdSchin return number;
411da2e3ebdSchin }
412da2e3ebdSchin
413da2e3ebdSchin int
b_tail(int argc,char ** argv,Shbltin_t * context)414b30d1939SAndy Fiddaman b_tail(int argc, char** argv, Shbltin_t* context)
415da2e3ebdSchin {
416da2e3ebdSchin register Sfio_t* ip;
417da2e3ebdSchin register int n;
418da2e3ebdSchin register int i;
41934f9b3eeSRoland Mainz int delim;
42034f9b3eeSRoland Mainz int flags = HEADERS|LINES;
42134f9b3eeSRoland Mainz int blocks = 0;
422da2e3ebdSchin char* s;
423da2e3ebdSchin char* t;
424da2e3ebdSchin char* r;
425da2e3ebdSchin char* file;
426*d6f391efSAndy Fiddaman Sfoff_t moved;
427da2e3ebdSchin Sfoff_t offset;
42834f9b3eeSRoland Mainz Sfoff_t number = DEFAULT;
429da2e3ebdSchin unsigned long timeout = 0;
430da2e3ebdSchin struct stat st;
431da2e3ebdSchin const char* format = header_fmt+1;
43234f9b3eeSRoland Mainz ssize_t z;
4333e14f97fSRoger A. Faulkner ssize_t w;
434da2e3ebdSchin Sfio_t* op;
435da2e3ebdSchin register Tail_t* fp;
436da2e3ebdSchin register Tail_t* pp;
437da2e3ebdSchin register Tail_t* hp;
438da2e3ebdSchin Tail_t* files;
439b30d1939SAndy Fiddaman Tv_t tv;
440da2e3ebdSchin
441*d6f391efSAndy Fiddaman cmdinit(argc, argv, context, ERROR_CATALOG, ERROR_NOTIFY);
442da2e3ebdSchin for (;;)
443da2e3ebdSchin {
444da2e3ebdSchin switch (n = optget(argv, usage))
445da2e3ebdSchin {
44634f9b3eeSRoland Mainz case 0:
44734f9b3eeSRoland Mainz if (!(flags & FOLLOW) && argv[opt_info.index] && (argv[opt_info.index][0] == '-' || argv[opt_info.index][0] == '+') && !argv[opt_info.index][1])
448da2e3ebdSchin {
44934f9b3eeSRoland Mainz number = argv[opt_info.index][0] == '-' ? 10 : -10;
45034f9b3eeSRoland Mainz flags |= LINES;
45134f9b3eeSRoland Mainz opt_info.index++;
452da2e3ebdSchin continue;
453da2e3ebdSchin }
45434f9b3eeSRoland Mainz break;
45534f9b3eeSRoland Mainz case 'b':
45634f9b3eeSRoland Mainz blocks = 512;
45734f9b3eeSRoland Mainz flags &= ~LINES;
45834f9b3eeSRoland Mainz if (opt_info.option[0] == '+')
45934f9b3eeSRoland Mainz number = -number;
46034f9b3eeSRoland Mainz continue;
46134f9b3eeSRoland Mainz case 'c':
46234f9b3eeSRoland Mainz flags &= ~LINES;
46334f9b3eeSRoland Mainz if (opt_info.arg == argv[opt_info.index - 1])
46434f9b3eeSRoland Mainz {
46534f9b3eeSRoland Mainz strtol(opt_info.arg, &s, 10);
46634f9b3eeSRoland Mainz if (*s)
46734f9b3eeSRoland Mainz {
46834f9b3eeSRoland Mainz opt_info.index--;
46934f9b3eeSRoland Mainz t = "";
47034f9b3eeSRoland Mainz goto suffix;
47134f9b3eeSRoland Mainz }
47234f9b3eeSRoland Mainz }
47334f9b3eeSRoland Mainz else if (opt_info.arg && isalpha(*opt_info.arg))
47434f9b3eeSRoland Mainz {
47534f9b3eeSRoland Mainz t = opt_info.arg;
47634f9b3eeSRoland Mainz goto suffix;
47734f9b3eeSRoland Mainz }
478da2e3ebdSchin /*FALLTHROUGH*/
479da2e3ebdSchin case 'n':
480da2e3ebdSchin flags |= COUNT;
481da2e3ebdSchin if (s = opt_info.arg)
482da2e3ebdSchin number = num(s, &s, &flags, n);
483da2e3ebdSchin else
484da2e3ebdSchin {
48534f9b3eeSRoland Mainz number = DEFAULT;
486da2e3ebdSchin flags &= ~(ERROR|NEGATIVE|POSITIVE);
487da2e3ebdSchin s = "";
488da2e3ebdSchin }
48934f9b3eeSRoland Mainz if (n != 'n' && s && isalpha(*s))
490da2e3ebdSchin {
49134f9b3eeSRoland Mainz t = s;
49234f9b3eeSRoland Mainz goto suffix;
493da2e3ebdSchin }
494da2e3ebdSchin if (flags & ERROR)
495da2e3ebdSchin continue;
496da2e3ebdSchin if (flags & (NEGATIVE|POSITIVE))
497da2e3ebdSchin number = -number;
498da2e3ebdSchin if (opt_info.option[0]=='+')
499da2e3ebdSchin number = -number;
500da2e3ebdSchin continue;
501da2e3ebdSchin case 'f':
502da2e3ebdSchin flags |= FOLLOW;
503da2e3ebdSchin continue;
504da2e3ebdSchin case 'h':
505da2e3ebdSchin if (opt_info.num)
506da2e3ebdSchin flags |= HEADERS;
507da2e3ebdSchin else
508da2e3ebdSchin flags &= ~HEADERS;
509da2e3ebdSchin continue;
51034f9b3eeSRoland Mainz case 'l':
51134f9b3eeSRoland Mainz flags |= LINES;
51234f9b3eeSRoland Mainz if (opt_info.option[0] == '+')
51334f9b3eeSRoland Mainz number = -number;
51434f9b3eeSRoland Mainz continue;
515da2e3ebdSchin case 'L':
516da2e3ebdSchin flags |= LOG;
517da2e3ebdSchin continue;
518da2e3ebdSchin case 'q':
519da2e3ebdSchin flags &= ~HEADERS;
520da2e3ebdSchin continue;
521da2e3ebdSchin case 'r':
522da2e3ebdSchin flags |= REVERSE;
523da2e3ebdSchin continue;
524da2e3ebdSchin case 's':
525da2e3ebdSchin flags |= SILENT;
526da2e3ebdSchin continue;
527da2e3ebdSchin case 't':
528da2e3ebdSchin flags |= TIMEOUT;
529da2e3ebdSchin timeout = strelapsed(opt_info.arg, &s, 1);
530da2e3ebdSchin if (*s)
5313e14f97fSRoger A. Faulkner error(ERROR_exit(1), "%s: invalid elapsed time [%s]", opt_info.arg, s);
532da2e3ebdSchin continue;
533da2e3ebdSchin case 'v':
534da2e3ebdSchin flags |= VERBOSE;
535da2e3ebdSchin continue;
536da2e3ebdSchin case ':':
537da2e3ebdSchin /* handle old style arguments */
53834f9b3eeSRoland Mainz if (!(r = argv[opt_info.index]) || !opt_info.offset)
53934f9b3eeSRoland Mainz {
54034f9b3eeSRoland Mainz error(2, "%s", opt_info.arg);
54134f9b3eeSRoland Mainz break;
54234f9b3eeSRoland Mainz }
54334f9b3eeSRoland Mainz s = r + opt_info.offset - 1;
54434f9b3eeSRoland Mainz if (i = *(s - 1) == '-' || *(s - 1) == '+')
54534f9b3eeSRoland Mainz s--;
54634f9b3eeSRoland Mainz if ((number = num(s, &t, &flags, 0)) && i)
54734f9b3eeSRoland Mainz number = -number;
54834f9b3eeSRoland Mainz goto compatibility;
54934f9b3eeSRoland Mainz suffix:
55034f9b3eeSRoland Mainz r = 0;
55134f9b3eeSRoland Mainz if (opt_info.option[0] == '+')
55234f9b3eeSRoland Mainz number = -number;
55334f9b3eeSRoland Mainz compatibility:
554da2e3ebdSchin for (;;)
555da2e3ebdSchin {
556da2e3ebdSchin switch (*t++)
557da2e3ebdSchin {
558da2e3ebdSchin case 0:
55934f9b3eeSRoland Mainz if (r)
56034f9b3eeSRoland Mainz opt_info.offset = t - r - 1;
561da2e3ebdSchin break;
562da2e3ebdSchin case 'c':
56334f9b3eeSRoland Mainz flags &= ~LINES;
564da2e3ebdSchin continue;
565da2e3ebdSchin case 'f':
566da2e3ebdSchin flags |= FOLLOW;
567da2e3ebdSchin continue;
568da2e3ebdSchin case 'l':
56934f9b3eeSRoland Mainz flags |= LINES;
570da2e3ebdSchin continue;
571da2e3ebdSchin case 'r':
572da2e3ebdSchin flags |= REVERSE;
573da2e3ebdSchin continue;
574da2e3ebdSchin default:
575da2e3ebdSchin error(2, "%s: invalid suffix", t - 1);
57634f9b3eeSRoland Mainz if (r)
57734f9b3eeSRoland Mainz opt_info.offset = strlen(r);
578da2e3ebdSchin break;
579da2e3ebdSchin }
580da2e3ebdSchin break;
581da2e3ebdSchin }
582da2e3ebdSchin continue;
583da2e3ebdSchin case '?':
584da2e3ebdSchin error(ERROR_usage(2), "%s", opt_info.arg);
585da2e3ebdSchin break;
586da2e3ebdSchin }
587da2e3ebdSchin break;
588da2e3ebdSchin }
589da2e3ebdSchin argv += opt_info.index;
590da2e3ebdSchin if (!*argv)
591da2e3ebdSchin {
592da2e3ebdSchin flags &= ~HEADERS;
593da2e3ebdSchin if (fstat(0, &st))
594da2e3ebdSchin error(ERROR_system(0), "/dev/stdin: cannot stat");
595da2e3ebdSchin else if (FIFO(st.st_mode))
596da2e3ebdSchin flags &= ~FOLLOW;
597da2e3ebdSchin }
598da2e3ebdSchin else if (!*(argv + 1))
599da2e3ebdSchin flags &= ~HEADERS;
60034f9b3eeSRoland Mainz delim = (flags & LINES) ? '\n' : -1;
60134f9b3eeSRoland Mainz if (blocks)
60234f9b3eeSRoland Mainz number *= blocks;
603da2e3ebdSchin if (flags & REVERSE)
604da2e3ebdSchin {
605da2e3ebdSchin if (delim < 0)
606da2e3ebdSchin error(2, "--reverse requires line mode");
60734f9b3eeSRoland Mainz if (!(flags & COUNT))
60834f9b3eeSRoland Mainz number = -1;
609da2e3ebdSchin flags &= ~FOLLOW;
610da2e3ebdSchin }
611da2e3ebdSchin if ((flags & (FOLLOW|TIMEOUT)) == TIMEOUT)
612da2e3ebdSchin {
613da2e3ebdSchin flags &= ~TIMEOUT;
614da2e3ebdSchin timeout = 0;
615da2e3ebdSchin error(ERROR_warn(0), "--timeout ignored for --noforever");
616da2e3ebdSchin }
617da2e3ebdSchin if ((flags & (LOG|TIMEOUT)) == LOG)
618da2e3ebdSchin {
619da2e3ebdSchin flags &= ~LOG;
620da2e3ebdSchin error(ERROR_warn(0), "--log ignored for --notimeout");
621da2e3ebdSchin }
622da2e3ebdSchin if (error_info.errors)
623da2e3ebdSchin error(ERROR_usage(2), "%s", optusage(NiL));
624da2e3ebdSchin if (flags & FOLLOW)
625da2e3ebdSchin {
626da2e3ebdSchin if (!(fp = (Tail_t*)stakalloc(argc * sizeof(Tail_t))))
627da2e3ebdSchin error(ERROR_system(1), "out of space");
628da2e3ebdSchin files = 0;
629da2e3ebdSchin s = *argv;
630da2e3ebdSchin do
631da2e3ebdSchin {
632da2e3ebdSchin fp->name = s;
633da2e3ebdSchin fp->sp = 0;
63434f9b3eeSRoland Mainz if (!init(fp, number, delim, flags, &format))
635da2e3ebdSchin {
636da2e3ebdSchin fp->expire = timeout ? (NOW + timeout + 1) : 0;
637da2e3ebdSchin if (files)
638da2e3ebdSchin pp->next = fp;
639da2e3ebdSchin else
640da2e3ebdSchin files = fp;
641da2e3ebdSchin pp = fp;
642da2e3ebdSchin fp++;
643da2e3ebdSchin }
644da2e3ebdSchin } while (s && (s = *++argv));
645da2e3ebdSchin if (!files)
646da2e3ebdSchin return error_info.errors != 0;
647da2e3ebdSchin pp->next = 0;
648da2e3ebdSchin hp = 0;
6493e14f97fSRoger A. Faulkner n = 1;
650b30d1939SAndy Fiddaman tv.tv_sec = 1;
651b30d1939SAndy Fiddaman tv.tv_nsec = 0;
65234f9b3eeSRoland Mainz while (fp = files)
653da2e3ebdSchin {
6543e14f97fSRoger A. Faulkner if (n)
6553e14f97fSRoger A. Faulkner n = 0;
656*d6f391efSAndy Fiddaman else if (sh_checksig(context) || tvsleep(&tv, NiL) && sh_checksig(context))
657b30d1939SAndy Fiddaman {
658b30d1939SAndy Fiddaman error_info.errors++;
659b30d1939SAndy Fiddaman break;
660b30d1939SAndy Fiddaman }
661da2e3ebdSchin pp = 0;
662da2e3ebdSchin while (fp)
663da2e3ebdSchin {
664da2e3ebdSchin if (fstat(sffileno(fp->sp), &st))
665da2e3ebdSchin error(ERROR_system(0), "%s: cannot stat", fp->name);
6663e14f97fSRoger A. Faulkner else if (fp->fifo || fp->end < st.st_size)
667da2e3ebdSchin {
668da2e3ebdSchin n = 1;
669da2e3ebdSchin if (timeout)
670da2e3ebdSchin fp->expire = NOW + timeout;
6713e14f97fSRoger A. Faulkner z = fp->fifo ? SF_UNBOUND : st.st_size - fp->cur;
672da2e3ebdSchin i = 0;
673da2e3ebdSchin if ((s = sfreserve(fp->sp, z, SF_LOCKR)) || (z = sfvalue(fp->sp)) && (s = sfreserve(fp->sp, z, SF_LOCKR)) && (i = 1))
674da2e3ebdSchin {
6753e14f97fSRoger A. Faulkner z = sfvalue(fp->sp);
6763e14f97fSRoger A. Faulkner for (r = s + z; r > s && *(r - 1) != '\n'; r--);
6773e14f97fSRoger A. Faulkner if ((w = r - s) || i && (w = z))
678da2e3ebdSchin {
679da2e3ebdSchin if ((flags & (HEADERS|VERBOSE)) && hp != fp)
680da2e3ebdSchin {
681da2e3ebdSchin hp = fp;
682da2e3ebdSchin sfprintf(sfstdout, format, fp->name);
683da2e3ebdSchin format = header_fmt;
684da2e3ebdSchin }
6853e14f97fSRoger A. Faulkner fp->cur += w;
6863e14f97fSRoger A. Faulkner sfwrite(sfstdout, s, w);
687da2e3ebdSchin }
688da2e3ebdSchin else
6893e14f97fSRoger A. Faulkner w = 0;
6903e14f97fSRoger A. Faulkner sfread(fp->sp, s, w);
6913e14f97fSRoger A. Faulkner fp->end += w;
692da2e3ebdSchin }
693da2e3ebdSchin goto next;
694da2e3ebdSchin }
695da2e3ebdSchin else if (!timeout || fp->expire > NOW)
696da2e3ebdSchin goto next;
697da2e3ebdSchin else
698da2e3ebdSchin {
699da2e3ebdSchin if (flags & LOG)
700da2e3ebdSchin {
701da2e3ebdSchin i = 3;
702da2e3ebdSchin while (--i && stat(fp->name, &st))
703*d6f391efSAndy Fiddaman if (sh_checksig(context))
704b30d1939SAndy Fiddaman {
705b30d1939SAndy Fiddaman error_info.errors++;
706b30d1939SAndy Fiddaman goto done;
707b30d1939SAndy Fiddaman }
708*d6f391efSAndy Fiddaman else
709*d6f391efSAndy Fiddaman tvsleep(&tv, NiL);
71034f9b3eeSRoland Mainz if (i && (fp->dev != st.st_dev || fp->ino != st.st_ino) && !init(fp, 0, 0, flags, &format))
711da2e3ebdSchin {
712da2e3ebdSchin if (!(flags & SILENT))
713da2e3ebdSchin error(ERROR_warn(0), "%s: log file change", fp->name);
714da2e3ebdSchin fp->expire = NOW + timeout;
715da2e3ebdSchin goto next;
716da2e3ebdSchin }
717da2e3ebdSchin }
718da2e3ebdSchin if (!(flags & SILENT))
719da2e3ebdSchin error(ERROR_warn(0), "%s: %s timeout", fp->name, fmtelapsed(timeout, 1));
720da2e3ebdSchin }
721da2e3ebdSchin if (fp->sp && fp->sp != sfstdin)
722da2e3ebdSchin sfclose(fp->sp);
723da2e3ebdSchin if (pp)
724da2e3ebdSchin pp = pp->next = fp->next;
7253e14f97fSRoger A. Faulkner else
7263e14f97fSRoger A. Faulkner files = files->next;
727da2e3ebdSchin fp = fp->next;
728da2e3ebdSchin continue;
729da2e3ebdSchin next:
730da2e3ebdSchin pp = fp;
731da2e3ebdSchin fp = fp->next;
732da2e3ebdSchin }
7333e14f97fSRoger A. Faulkner if (sfsync(sfstdout))
7343e14f97fSRoger A. Faulkner error(ERROR_system(1), "write error");
735da2e3ebdSchin }
736b30d1939SAndy Fiddaman done:
737b30d1939SAndy Fiddaman for (fp = files; fp; fp = fp->next)
738b30d1939SAndy Fiddaman if (fp->sp && fp->sp != sfstdin)
739b30d1939SAndy Fiddaman sfclose(fp->sp);
740da2e3ebdSchin }
741da2e3ebdSchin else
742da2e3ebdSchin {
743da2e3ebdSchin if (file = *argv)
744da2e3ebdSchin argv++;
745da2e3ebdSchin do
746da2e3ebdSchin {
747da2e3ebdSchin if (!file || streq(file, "-"))
748da2e3ebdSchin {
749da2e3ebdSchin file = "/dev/stdin";
750da2e3ebdSchin ip = sfstdin;
751da2e3ebdSchin }
752da2e3ebdSchin else if (!(ip = sfopen(NiL, file, "r")))
753da2e3ebdSchin {
754da2e3ebdSchin error(ERROR_system(0), "%s: cannot open", file);
755da2e3ebdSchin continue;
756da2e3ebdSchin }
757da2e3ebdSchin if (flags & (HEADERS|VERBOSE))
75834f9b3eeSRoland Mainz {
759da2e3ebdSchin sfprintf(sfstdout, format, file);
76034f9b3eeSRoland Mainz format = header_fmt;
76134f9b3eeSRoland Mainz }
762da2e3ebdSchin if (number < 0 || !number && (flags & POSITIVE))
763da2e3ebdSchin {
76434f9b3eeSRoland Mainz sfset(ip, SF_SHARE, 1);
765*d6f391efSAndy Fiddaman if (number < -1 && (moved = sfmove(ip, NiL, -(number + 1), delim)) >= 0 && delim >= 0 && moved < -(number + 1))
766*d6f391efSAndy Fiddaman (void)sfgetr(ip, delim, SF_LASTR);
767da2e3ebdSchin if (flags & REVERSE)
768da2e3ebdSchin rev_line(ip, sfstdout, sfseek(ip, (Sfoff_t)0, SEEK_CUR));
769da2e3ebdSchin else
770da2e3ebdSchin sfmove(ip, sfstdout, SF_UNBOUND, -1);
771da2e3ebdSchin }
772da2e3ebdSchin else
773da2e3ebdSchin {
774da2e3ebdSchin sfset(ip, SF_SHARE, 0);
775da2e3ebdSchin if ((offset = tailpos(ip, number, delim)) >= 0)
776da2e3ebdSchin {
777da2e3ebdSchin if (flags & REVERSE)
778da2e3ebdSchin rev_line(ip, sfstdout, offset);
779da2e3ebdSchin else
780da2e3ebdSchin {
781da2e3ebdSchin sfseek(ip, offset, SEEK_SET);
782da2e3ebdSchin sfmove(ip, sfstdout, SF_UNBOUND, -1);
783da2e3ebdSchin }
784da2e3ebdSchin }
785da2e3ebdSchin else
786da2e3ebdSchin {
787da2e3ebdSchin op = (flags & REVERSE) ? sftmp(4*SF_BUFSIZE) : sfstdout;
788da2e3ebdSchin pipetail(ip, op, number, delim);
789da2e3ebdSchin if (flags & REVERSE)
790da2e3ebdSchin {
791da2e3ebdSchin sfseek(op, (Sfoff_t)0, SEEK_SET);
792da2e3ebdSchin rev_line(op, sfstdout, (Sfoff_t)0);
793da2e3ebdSchin sfclose(op);
794da2e3ebdSchin }
795da2e3ebdSchin flags = 0;
796da2e3ebdSchin }
797da2e3ebdSchin }
798da2e3ebdSchin if (ip != sfstdin)
799da2e3ebdSchin sfclose(ip);
800b30d1939SAndy Fiddaman } while ((file = *argv++) && !sh_checksig(context));
801da2e3ebdSchin }
802da2e3ebdSchin return error_info.errors != 0;
803da2e3ebdSchin }
804