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