| 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 } |