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][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 #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 }