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