xref: /plan9/sys/src/cmd/tail.c (revision 7bd483b0b97911f3ba6c830c06efdc49c6cf0263)
1 #include	<u.h>
2 #include	<libc.h>
3 #include	<ctype.h>
4 #include	<bio.h>
5 
6 /*
7  * tail command, posix plus v10 option -r.
8  * the simple command tail -c, legal in v10, is illegal
9  */
10 
11 long	count;
12 int	anycount;
13 int	follow;
14 int	file	= 0;
15 char*	umsg	= "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc][fr]] [file]";
16 
17 Biobuf	bout;
18 enum
19 {
20 	BEG,
21 	END
22 } origin = END;
23 enum
24 {
25 	CHARS,
26 	LINES
27 } units = LINES;
28 enum
29 {
30 	FWD,
31 	REV
32 } dir = FWD;
33 
34 extern	void	copy(void);
35 extern	void	fatal(char*);
36 extern	int	getnumber(char*);
37 extern	void	keep(void);
38 extern	void	reverse(void);
39 extern	void	skip(void);
40 extern	void	suffix(char*);
41 extern	long	tread(char*, long);
42 extern	void	trunc(Dir*, Dir**);
43 extern	vlong	tseek(vlong, int);
44 extern	void	twrite(char*, long);
45 extern	void	usage(void);
46 static	int	isseekable(int fd);
47 
48 #define JUMP(o,p) tseek(o,p), copy()
49 
50 void
main(int argc,char ** argv)51 main(int argc, char **argv)
52 {
53 	int seekable, c;
54 
55 	Binit(&bout, 1, OWRITE);
56 	for(; argc > 1 && ((c=*argv[1])=='-'||c=='+'); argc--,argv++ ) {
57 		if(getnumber(argv[1])) {
58 			suffix(argv[1]);
59 			continue;
60 		} else
61 		if(c == '-')
62 			switch(argv[1][1]) {
63 			case 'c':
64 				units = CHARS;
65 			case 'n':
66 				if(getnumber(argv[1]+2))
67 					continue;
68 				else
69 				if(argc > 2 && getnumber(argv[2])) {
70 					argc--, argv++;
71 					continue;
72 				} else
73 					usage();
74 			case 'r':
75 				dir = REV;
76 				continue;
77 			case 'f':
78 				follow++;
79 				continue;
80 			case '-':
81 				argc--, argv++;
82 			}
83 		break;
84 	}
85 	if(dir==REV && (units==CHARS || follow || origin==BEG))
86 		fatal("incompatible options");
87 	if(!anycount)
88 		count = dir==REV? ~0UL>>1: 10;
89 	if(origin==BEG && units==LINES && count>0)
90 		count--;
91 	if(argc > 2)
92 		usage();
93 	if(argc > 1 && (file=open(argv[1],0)) < 0)
94 		fatal(argv[1]);
95 	seekable = isseekable(file);
96 
97 	if(!seekable && origin==END)
98 		keep();
99 	else
100 	if(!seekable && origin==BEG)
101 		skip();
102 	else
103 	if(units==CHARS && origin==END)
104 		JUMP(-count, 2);
105 	else
106 	if(units==CHARS && origin==BEG)
107 		JUMP(count, 0);
108 	else
109 	if(units==LINES && origin==END)
110 		reverse();
111 	else
112 	if(units==LINES && origin==BEG)
113 		skip();
114 	if(follow && seekable)
115 		for(;;) {
116 			static Dir *sb0, *sb1;
117 			trunc(sb1, &sb0);
118 			copy();
119 			trunc(sb0, &sb1);
120 			sleep(5000);
121 		}
122 	exits(0);
123 }
124 
125 void
trunc(Dir * old,Dir ** new)126 trunc(Dir *old, Dir **new)
127 {
128 	Dir *d;
129 	vlong olength;
130 
131 	d = dirfstat(file);
132 	if(d == nil)
133 		return;
134 	olength = 0;
135 	if(old)
136 		olength = old->length;
137 	if(d->length < olength)
138 		d->length = tseek(0LL, 0);
139 	free(*new);
140 	*new = d;
141 }
142 
143 void
suffix(char * s)144 suffix(char *s)
145 {
146 	while(*s && strchr("0123456789+-", *s))
147 		s++;
148 	switch(*s) {
149 	case 'b':
150 		if((count *= 1024) < 0)
151 			fatal("too big");
152 	case 'c':
153 		units = CHARS;
154 	case 'l':
155 		s++;
156 	}
157 	switch(*s) {
158 	case 'r':
159 		dir = REV;
160 		return;
161 	case 'f':
162 		follow++;
163 		return;
164 	case 0:
165 		return;
166 	}
167 	usage();
168 }
169 
170 /*
171  * read past head of the file to find tail
172  */
173 void
skip(void)174 skip(void)
175 {
176 	int i;
177 	long n;
178 	char buf[Bsize];
179 	if(units == CHARS) {
180 		for( ; count>0; count -=n) {
181 			n = count<Bsize? count: Bsize;
182 			if(!(n = tread(buf, n)))
183 				return;
184 		}
185 	} else /*units == LINES*/ {
186 		n = i = 0;
187 		while(count > 0) {
188 			if(!(n = tread(buf, Bsize)))
189 				return;
190 			for(i=0; i<n && count>0; i++)
191 				if(buf[i]=='\n')
192 					count--;
193 		}
194 		twrite(buf+i, n-i);
195 	}
196 	copy();
197 }
198 
199 void
copy(void)200 copy(void)
201 {
202 	long n;
203 	char buf[Bsize];
204 	while((n=tread(buf, Bsize)) > 0) {
205 		twrite(buf, n);
206 		Bflush(&bout);	/* for FWD on pipe; else harmless */
207 	}
208 }
209 
210 /*
211  * read whole file, keeping the tail
212  *	complexity is length(file)*length(tail).
213  *	could be linear.
214  */
215 void
keep(void)216 keep(void)
217 {
218 	int len = 0;
219 	long bufsiz = 0;
220 	char *buf = 0;
221 	int j, k, n;
222 
223 	for(n=1; n;) {
224 		if(len+Bsize > bufsiz) {
225 			bufsiz += 2*Bsize;
226 			if(!(buf = realloc(buf, bufsiz+1)))
227 				fatal("out of space");
228 		}
229 		for(; n && len<bufsiz; len+=n)
230 			n = tread(buf+len, bufsiz-len);
231 		if(count >= len)
232 			continue;
233 		if(units == CHARS)
234 			j = len - count;
235 		else {
236 			/* units == LINES */
237 			j = buf[len-1]=='\n'? len-1: len;
238 			for(k=0; j>0; j--)
239 				if(buf[j-1] == '\n')
240 					if(++k >= count)
241 						break;
242 		}
243 		memmove(buf, buf+j, len-=j);
244 	}
245 	if(dir == REV) {
246 		if(len>0 && buf[len-1]!='\n')
247 			buf[len++] = '\n';
248 		for(j=len-1 ; j>0; j--)
249 			if(buf[j-1] == '\n') {
250 				twrite(buf+j, len-j);
251 				if(--count <= 0)
252 					return;
253 				len = j;
254 			}
255 	}
256 	if(count > 0)
257 		twrite(buf, len);
258 }
259 
260 /*
261  * count backward and print tail of file
262  */
263 void
reverse(void)264 reverse(void)
265 {
266 	int first;
267 	long len = 0;
268 	long n = 0;
269 	long bufsiz = 0;
270 	char *buf = 0;
271 	vlong pos = tseek(0LL, 2);
272 
273 	for(first=1; pos>0 && count>0; first=0) {
274 		n = pos>Bsize? Bsize: (long)pos;
275 		pos -= n;
276 		if(len+n > bufsiz) {
277 			bufsiz += 2*Bsize;
278 			if(!(buf = realloc(buf, bufsiz+1)))
279 				fatal("out of space");
280 		}
281 		memmove(buf+n, buf, len);
282 		len += n;
283 		tseek(pos, 0);
284 		if(tread(buf, n) != n)
285 			fatal("length error");
286 		if(first && buf[len-1]!='\n')
287 			buf[len++] = '\n';
288 		for(n=len-1 ; n>0 && count>0; n--)
289 			if(buf[n-1] == '\n') {
290 				count--;
291 				if(dir == REV)
292 					twrite(buf+n, len-n);
293 				len = n;
294 			}
295 	}
296 	if(dir == FWD) {
297 		if(n)
298 			tseek(pos+n+1, 0);
299 		else
300 			tseek(0, 0);
301 		copy();
302 	} else
303 	if(count > 0)
304 		twrite(buf, len);
305 }
306 
307 vlong
tseek(vlong o,int p)308 tseek(vlong o, int p)
309 {
310 	o = seek(file, o, p);
311 	if(o == -1)
312 		fatal("");
313 	return o;
314 }
315 
316 long
tread(char * buf,long n)317 tread(char *buf, long n)
318 {
319 	int r = read(file, buf, n);
320 	if(r == -1)
321 		fatal("");
322 	return r;
323 }
324 
325 void
twrite(char * s,long n)326 twrite(char *s, long n)
327 {
328 	if(Bwrite(&bout, s, n) != n)
329 		fatal("");
330 }
331 
332 int
getnumber(char * s)333 getnumber(char *s)
334 {
335 	if(*s=='-' || *s=='+')
336 		s++;
337 	if(!isdigit(*s))
338 		return 0;
339 	if(s[-1] == '+')
340 		origin = BEG;
341 	if(anycount++)
342 		fatal("excess option");
343 	count = atol(s);
344 
345 	/* check range of count */
346 	if(count < 0 ||	(int)count != count)
347 		fatal("too big");
348 	return 1;
349 }
350 
351 void
fatal(char * s)352 fatal(char *s)
353 {
354 	char buf[ERRMAX];
355 
356 	errstr(buf, sizeof buf);
357 	fprint(2, "tail: %s: %s\n", s, buf);
358 	exits(s);
359 }
360 
361 void
usage(void)362 usage(void)
363 {
364 	fprint(2, "%s\n", umsg);
365 	exits("usage");
366 }
367 
368 /* return true if seeks work and if the file is > 0 length.
369  * this will eventually bite me in the ass if seeking a file
370  * is not conservative. - presotto
371  */
372 static int
isseekable(int fd)373 isseekable(int fd)
374 {
375 	vlong m;
376 
377 	m = seek(fd, 0, 1);
378 	if(m < 0)
379 		return 0;
380 	return 1;
381 }
382