tail.c - 9base - revived minimalist port of Plan 9 userland to Unix | |
git clone git://git.suckless.org/9base | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
tail.c (5753B) | |
--- | |
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 vlong count; | |
12 int anycount; | |
13 int follow; | |
14 int file = 0; | |
15 char* umsg = "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc]… | |
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 #define trunc tailtrunc | |
43 extern void trunc(Dir*, Dir**); | |
44 extern vlong tseek(vlong, int); | |
45 extern void twrite(char*, long); | |
46 extern void usage(void); | |
47 | |
48 #define JUMP(o,p) tseek(o,p), copy() | |
49 | |
50 void | |
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? ~0ULL>>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 = seek(file,0L,0) == 0; | |
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 | |
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(0L, 0); | |
139 free(*new); | |
140 *new = d; | |
141 } | |
142 | |
143 void | |
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 | |
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 | |
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 | |
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 | |
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(0L, 2); | |
272 | |
273 for(first=1; pos>0 && count>0; first=0) { | |
274 n = pos>Bsize? Bsize: (int)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 tseek(n==0? 0 : pos+n+1, 0); | |
298 copy(); | |
299 } else | |
300 if(count > 0) | |
301 twrite(buf, len); | |
302 } | |
303 | |
304 vlong | |
305 tseek(vlong o, int p) | |
306 { | |
307 o = seek(file, o, p); | |
308 if(o == -1) | |
309 fatal(""); | |
310 return o; | |
311 } | |
312 | |
313 long | |
314 tread(char *buf, long n) | |
315 { | |
316 int r = read(file, buf, n); | |
317 if(r == -1) | |
318 fatal(""); | |
319 return r; | |
320 } | |
321 | |
322 void | |
323 twrite(char *s, long n) | |
324 { | |
325 if(Bwrite(&bout, s, n) != n) | |
326 fatal(""); | |
327 } | |
328 | |
329 int | |
330 getnumber(char *s) | |
331 { | |
332 if(*s=='-' || *s=='+') | |
333 s++; | |
334 if(!isdigit((uchar)*s)) | |
335 return 0; | |
336 if(s[-1] == '+') | |
337 origin = BEG; | |
338 if(anycount++) | |
339 fatal("excess option"); | |
340 count = atol(s); | |
341 | |
342 /* check range of count */ | |
343 if(count < 0 || (int)count != count) | |
344 fatal("too big"); | |
345 return 1; | |
346 } | |
347 | |
348 void | |
349 fatal(char *s) | |
350 { | |
351 char buf[ERRMAX]; | |
352 | |
353 errstr(buf, sizeof buf); | |
354 fprint(2, "tail: %s: %s\n", s, buf); | |
355 exits(s); | |
356 } | |
357 | |
358 void | |
359 usage(void) | |
360 { | |
361 fprint(2, "%s\n", umsg); | |
362 exits("usage"); | |
363 } |