Blob


1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 #include <bio.h>
6 /*
7 * tail command, posix plus v10 option -r.
8 * the simple command tail -c, legal in v10, is illegal
9 */
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][fr]] [file]";
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;
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);
48 #define JUMP(o,p) tseek(o,p), copy()
50 void
51 main(int argc, char **argv)
52 {
53 int seekable, c;
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;
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);
122 exits(0);
125 void
126 trunc(Dir *old, Dir **new)
128 Dir *d;
129 vlong olength;
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;
143 void
144 suffix(char *s)
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++;
157 switch(*s) {
158 case 'r':
159 dir = REV;
160 return;
161 case 'f':
162 follow++;
163 return;
164 case 0:
165 return;
167 usage();
170 /*
171 * read past head of the file to find tail
172 */
173 void
174 skip(void)
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;
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--;
194 twrite(buf+i, n-i);
196 copy();
199 void
200 copy(void)
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 */
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)
218 int len = 0;
219 long bufsiz = 0;
220 char *buf = 0;
221 int j, k, n;
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");
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;
243 memmove(buf, buf+j, len-=j);
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;
256 if(count > 0)
257 twrite(buf, len);
260 /*
261 * count backward and print tail of file
262 */
263 void
264 reverse(void)
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);
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");
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;
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);
304 vlong
305 tseek(vlong o, int p)
307 o = seek(file, o, p);
308 if(o == -1)
309 fatal("");
310 return o;
313 long
314 tread(char *buf, long n)
316 int r = read(file, buf, n);
317 if(r == -1)
318 fatal("");
319 return r;
322 void
323 twrite(char *s, long n)
325 if(Bwrite(&bout, s, n) != n)
326 fatal("");
329 int
330 getnumber(char *s)
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);
342 /* check range of count */
343 if(count < 0 || (int)count != count)
344 fatal("too big");
345 return 1;
348 void
349 fatal(char *s)
351 char buf[ERRMAX];
353 errstr(buf, sizeof buf);
354 fprint(2, "tail: %s: %s\n", s, buf);
355 exits(s);
358 void
359 usage(void)
361 fprint(2, "%s\n", umsg);
362 exits("usage");