xref: /plan9/sys/src/cmd/tail.c (revision 7bd483b0b97911f3ba6c830c06efdc49c6cf0263)
13e12c5d1SDavid du Colombier #include	<u.h>
23e12c5d1SDavid du Colombier #include	<libc.h>
33e12c5d1SDavid du Colombier #include	<ctype.h>
43e12c5d1SDavid du Colombier #include	<bio.h>
53e12c5d1SDavid du Colombier 
67dd7cddfSDavid du Colombier /*
77dd7cddfSDavid du Colombier  * tail command, posix plus v10 option -r.
87dd7cddfSDavid du Colombier  * the simple command tail -c, legal in v10, is illegal
97dd7cddfSDavid du Colombier  */
103e12c5d1SDavid du Colombier 
113e12c5d1SDavid du Colombier long	count;
123e12c5d1SDavid du Colombier int	anycount;
133e12c5d1SDavid du Colombier int	follow;
143e12c5d1SDavid du Colombier int	file	= 0;
157dd7cddfSDavid du Colombier char*	umsg	= "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc][fr]] [file]";
163e12c5d1SDavid du Colombier 
177dd7cddfSDavid du Colombier Biobuf	bout;
187dd7cddfSDavid du Colombier enum
197dd7cddfSDavid du Colombier {
207dd7cddfSDavid du Colombier 	BEG,
217dd7cddfSDavid du Colombier 	END
227dd7cddfSDavid du Colombier } origin = END;
237dd7cddfSDavid du Colombier enum
247dd7cddfSDavid du Colombier {
257dd7cddfSDavid du Colombier 	CHARS,
267dd7cddfSDavid du Colombier 	LINES
277dd7cddfSDavid du Colombier } units = LINES;
287dd7cddfSDavid du Colombier enum
297dd7cddfSDavid du Colombier {
307dd7cddfSDavid du Colombier 	FWD,
317dd7cddfSDavid du Colombier 	REV
327dd7cddfSDavid du Colombier } dir = FWD;
337dd7cddfSDavid du Colombier 
347dd7cddfSDavid du Colombier extern	void	copy(void);
357dd7cddfSDavid du Colombier extern	void	fatal(char*);
363e12c5d1SDavid du Colombier extern	int	getnumber(char*);
377dd7cddfSDavid du Colombier extern	void	keep(void);
387dd7cddfSDavid du Colombier extern	void	reverse(void);
397dd7cddfSDavid du Colombier extern	void	skip(void);
407dd7cddfSDavid du Colombier extern	void	suffix(char*);
417dd7cddfSDavid du Colombier extern	long	tread(char*, long);
429a747e4fSDavid du Colombier extern	void	trunc(Dir*, Dir**);
43*7bd483b0SDavid du Colombier extern	vlong	tseek(vlong, int);
447dd7cddfSDavid du Colombier extern	void	twrite(char*, long);
457dd7cddfSDavid du Colombier extern	void	usage(void);
4625332fe6SDavid du Colombier static	int	isseekable(int fd);
477dd7cddfSDavid du Colombier 
487dd7cddfSDavid du Colombier #define JUMP(o,p) tseek(o,p), copy()
493e12c5d1SDavid du Colombier 
503e12c5d1SDavid du Colombier void
main(int argc,char ** argv)513e12c5d1SDavid du Colombier main(int argc, char **argv)
523e12c5d1SDavid du Colombier {
533e12c5d1SDavid du Colombier 	int seekable, c;
547dd7cddfSDavid du Colombier 
553e12c5d1SDavid du Colombier 	Binit(&bout, 1, OWRITE);
563e12c5d1SDavid du Colombier 	for(; argc > 1 && ((c=*argv[1])=='-'||c=='+'); argc--,argv++ ) {
573e12c5d1SDavid du Colombier 		if(getnumber(argv[1])) {
583e12c5d1SDavid du Colombier 			suffix(argv[1]);
593e12c5d1SDavid du Colombier 			continue;
607dd7cddfSDavid du Colombier 		} else
617dd7cddfSDavid du Colombier 		if(c == '-')
623e12c5d1SDavid du Colombier 			switch(argv[1][1]) {
633e12c5d1SDavid du Colombier 			case 'c':
643e12c5d1SDavid du Colombier 				units = CHARS;
653e12c5d1SDavid du Colombier 			case 'n':
663e12c5d1SDavid du Colombier 				if(getnumber(argv[1]+2))
673e12c5d1SDavid du Colombier 					continue;
687dd7cddfSDavid du Colombier 				else
697dd7cddfSDavid du Colombier 				if(argc > 2 && getnumber(argv[2])) {
703e12c5d1SDavid du Colombier 					argc--, argv++;
713e12c5d1SDavid du Colombier 					continue;
723e12c5d1SDavid du Colombier 				} else
733e12c5d1SDavid du Colombier 					usage();
743e12c5d1SDavid du Colombier 			case 'r':
753e12c5d1SDavid du Colombier 				dir = REV;
763e12c5d1SDavid du Colombier 				continue;
773e12c5d1SDavid du Colombier 			case 'f':
783e12c5d1SDavid du Colombier 				follow++;
793e12c5d1SDavid du Colombier 				continue;
803e12c5d1SDavid du Colombier 			case '-':
813e12c5d1SDavid du Colombier 				argc--, argv++;
823e12c5d1SDavid du Colombier 			}
833e12c5d1SDavid du Colombier 		break;
843e12c5d1SDavid du Colombier 	}
853e12c5d1SDavid du Colombier 	if(dir==REV && (units==CHARS || follow || origin==BEG))
863e12c5d1SDavid du Colombier 		fatal("incompatible options");
873e12c5d1SDavid du Colombier 	if(!anycount)
883e12c5d1SDavid du Colombier 		count = dir==REV? ~0UL>>1: 10;
893e12c5d1SDavid du Colombier 	if(origin==BEG && units==LINES && count>0)
903e12c5d1SDavid du Colombier 		count--;
913e12c5d1SDavid du Colombier 	if(argc > 2)
923e12c5d1SDavid du Colombier 		usage();
933e12c5d1SDavid du Colombier 	if(argc > 1 && (file=open(argv[1],0)) < 0)
943e12c5d1SDavid du Colombier 		fatal(argv[1]);
9525332fe6SDavid du Colombier 	seekable = isseekable(file);
963e12c5d1SDavid du Colombier 
973e12c5d1SDavid du Colombier 	if(!seekable && origin==END)
983e12c5d1SDavid du Colombier 		keep();
997dd7cddfSDavid du Colombier 	else
1007dd7cddfSDavid du Colombier 	if(!seekable && origin==BEG)
1013e12c5d1SDavid du Colombier 		skip();
1027dd7cddfSDavid du Colombier 	else
1037dd7cddfSDavid du Colombier 	if(units==CHARS && origin==END)
1047dd7cddfSDavid du Colombier 		JUMP(-count, 2);
1057dd7cddfSDavid du Colombier 	else
1067dd7cddfSDavid du Colombier 	if(units==CHARS && origin==BEG)
1077dd7cddfSDavid du Colombier 		JUMP(count, 0);
1087dd7cddfSDavid du Colombier 	else
1097dd7cddfSDavid du Colombier 	if(units==LINES && origin==END)
1103e12c5d1SDavid du Colombier 		reverse();
1117dd7cddfSDavid du Colombier 	else
1127dd7cddfSDavid du Colombier 	if(units==LINES && origin==BEG)
1133e12c5d1SDavid du Colombier 		skip();
1143e12c5d1SDavid du Colombier 	if(follow && seekable)
1153e12c5d1SDavid du Colombier 		for(;;) {
1169a747e4fSDavid du Colombier 			static Dir *sb0, *sb1;
1179a747e4fSDavid du Colombier 			trunc(sb1, &sb0);
1183e12c5d1SDavid du Colombier 			copy();
1199a747e4fSDavid du Colombier 			trunc(sb0, &sb1);
1203e12c5d1SDavid du Colombier 			sleep(5000);
1213e12c5d1SDavid du Colombier 		}
1223e12c5d1SDavid du Colombier 	exits(0);
1233e12c5d1SDavid du Colombier }
1243e12c5d1SDavid du Colombier 
1253e12c5d1SDavid du Colombier void
trunc(Dir * old,Dir ** new)1269a747e4fSDavid du Colombier trunc(Dir *old, Dir **new)
1273e12c5d1SDavid du Colombier {
1289a747e4fSDavid du Colombier 	Dir *d;
129*7bd483b0SDavid du Colombier 	vlong olength;
1309a747e4fSDavid du Colombier 
1319a747e4fSDavid du Colombier 	d = dirfstat(file);
1329a747e4fSDavid du Colombier 	if(d == nil)
1339a747e4fSDavid du Colombier 		return;
1349a747e4fSDavid du Colombier 	olength = 0;
1359a747e4fSDavid du Colombier 	if(old)
1369a747e4fSDavid du Colombier 		olength = old->length;
1379a747e4fSDavid du Colombier 	if(d->length < olength)
138*7bd483b0SDavid du Colombier 		d->length = tseek(0LL, 0);
1399a747e4fSDavid du Colombier 	free(*new);
1409a747e4fSDavid du Colombier 	*new = d;
1413e12c5d1SDavid du Colombier }
1423e12c5d1SDavid du Colombier 
1433e12c5d1SDavid du Colombier void
suffix(char * s)1443e12c5d1SDavid du Colombier suffix(char *s)
1453e12c5d1SDavid du Colombier {
1463e12c5d1SDavid du Colombier 	while(*s && strchr("0123456789+-", *s))
1473e12c5d1SDavid du Colombier 		s++;
1483e12c5d1SDavid du Colombier 	switch(*s) {
1493e12c5d1SDavid du Colombier 	case 'b':
1503e12c5d1SDavid du Colombier 		if((count *= 1024) < 0)
1513e12c5d1SDavid du Colombier 			fatal("too big");
1523e12c5d1SDavid du Colombier 	case 'c':
1533e12c5d1SDavid du Colombier 		units = CHARS;
1543e12c5d1SDavid du Colombier 	case 'l':
1553e12c5d1SDavid du Colombier 		s++;
1563e12c5d1SDavid du Colombier 	}
1573e12c5d1SDavid du Colombier 	switch(*s) {
1583e12c5d1SDavid du Colombier 	case 'r':
1593e12c5d1SDavid du Colombier 		dir = REV;
1603e12c5d1SDavid du Colombier 		return;
1613e12c5d1SDavid du Colombier 	case 'f':
1623e12c5d1SDavid du Colombier 		follow++;
1633e12c5d1SDavid du Colombier 		return;
1643e12c5d1SDavid du Colombier 	case 0:
1653e12c5d1SDavid du Colombier 		return;
1663e12c5d1SDavid du Colombier 	}
1673e12c5d1SDavid du Colombier 	usage();
1683e12c5d1SDavid du Colombier }
1693e12c5d1SDavid du Colombier 
1707dd7cddfSDavid du Colombier /*
1717dd7cddfSDavid du Colombier  * read past head of the file to find tail
1727dd7cddfSDavid du Colombier  */
1733e12c5d1SDavid du Colombier void
skip(void)1747dd7cddfSDavid du Colombier skip(void)
1753e12c5d1SDavid du Colombier {
1763e12c5d1SDavid du Colombier 	int i;
1773e12c5d1SDavid du Colombier 	long n;
1783e12c5d1SDavid du Colombier 	char buf[Bsize];
1793e12c5d1SDavid du Colombier 	if(units == CHARS) {
1803e12c5d1SDavid du Colombier 		for( ; count>0; count -=n) {
1813e12c5d1SDavid du Colombier 			n = count<Bsize? count: Bsize;
1823e12c5d1SDavid du Colombier 			if(!(n = tread(buf, n)))
1833e12c5d1SDavid du Colombier 				return;
1843e12c5d1SDavid du Colombier 		}
1853e12c5d1SDavid du Colombier 	} else /*units == LINES*/ {
1863e12c5d1SDavid du Colombier 		n = i = 0;
1873e12c5d1SDavid du Colombier 		while(count > 0) {
1883e12c5d1SDavid du Colombier 			if(!(n = tread(buf, Bsize)))
1893e12c5d1SDavid du Colombier 				return;
1903e12c5d1SDavid du Colombier 			for(i=0; i<n && count>0; i++)
1913e12c5d1SDavid du Colombier 				if(buf[i]=='\n')
1923e12c5d1SDavid du Colombier 					count--;
1933e12c5d1SDavid du Colombier 		}
1943e12c5d1SDavid du Colombier 		twrite(buf+i, n-i);
1953e12c5d1SDavid du Colombier 	}
1963e12c5d1SDavid du Colombier 	copy();
1973e12c5d1SDavid du Colombier }
1983e12c5d1SDavid du Colombier 
1993e12c5d1SDavid du Colombier void
copy(void)2003e12c5d1SDavid du Colombier copy(void)
2013e12c5d1SDavid du Colombier {
2023e12c5d1SDavid du Colombier 	long n;
2033e12c5d1SDavid du Colombier 	char buf[Bsize];
2043e12c5d1SDavid du Colombier 	while((n=tread(buf, Bsize)) > 0) {
2053e12c5d1SDavid du Colombier 		twrite(buf, n);
2063e12c5d1SDavid du Colombier 		Bflush(&bout);	/* for FWD on pipe; else harmless */
2073e12c5d1SDavid du Colombier 	}
2083e12c5d1SDavid du Colombier }
2093e12c5d1SDavid du Colombier 
2107dd7cddfSDavid du Colombier /*
2117dd7cddfSDavid du Colombier  * read whole file, keeping the tail
2127dd7cddfSDavid du Colombier  *	complexity is length(file)*length(tail).
2137dd7cddfSDavid du Colombier  *	could be linear.
2147dd7cddfSDavid du Colombier  */
2153e12c5d1SDavid du Colombier void
keep(void)2167dd7cddfSDavid du Colombier keep(void)
2177dd7cddfSDavid du Colombier {
2183e12c5d1SDavid du Colombier 	int len = 0;
2193e12c5d1SDavid du Colombier 	long bufsiz = 0;
2203e12c5d1SDavid du Colombier 	char *buf = 0;
2213e12c5d1SDavid du Colombier 	int j, k, n;
2227dd7cddfSDavid du Colombier 
2233e12c5d1SDavid du Colombier 	for(n=1; n;) {
2243e12c5d1SDavid du Colombier 		if(len+Bsize > bufsiz) {
2253e12c5d1SDavid du Colombier 			bufsiz += 2*Bsize;
2263e12c5d1SDavid du Colombier 			if(!(buf = realloc(buf, bufsiz+1)))
2273e12c5d1SDavid du Colombier 				fatal("out of space");
2283e12c5d1SDavid du Colombier 		}
2293e12c5d1SDavid du Colombier 		for(; n && len<bufsiz; len+=n)
2303e12c5d1SDavid du Colombier 			n = tread(buf+len, bufsiz-len);
2313e12c5d1SDavid du Colombier 		if(count >= len)
2323e12c5d1SDavid du Colombier 			continue;
2333e12c5d1SDavid du Colombier 		if(units == CHARS)
2343e12c5d1SDavid du Colombier 			j = len - count;
2357dd7cddfSDavid du Colombier 		else {
2367dd7cddfSDavid du Colombier 			/* units == LINES */
2373e12c5d1SDavid du Colombier 			j = buf[len-1]=='\n'? len-1: len;
2383e12c5d1SDavid du Colombier 			for(k=0; j>0; j--)
2393e12c5d1SDavid du Colombier 				if(buf[j-1] == '\n')
2403e12c5d1SDavid du Colombier 					if(++k >= count)
2413e12c5d1SDavid du Colombier 						break;
2423e12c5d1SDavid du Colombier 		}
2433e12c5d1SDavid du Colombier 		memmove(buf, buf+j, len-=j);
2443e12c5d1SDavid du Colombier 	}
2453e12c5d1SDavid du Colombier 	if(dir == REV) {
2463e12c5d1SDavid du Colombier 		if(len>0 && buf[len-1]!='\n')
2473e12c5d1SDavid du Colombier 			buf[len++] = '\n';
2483e12c5d1SDavid du Colombier 		for(j=len-1 ; j>0; j--)
2493e12c5d1SDavid du Colombier 			if(buf[j-1] == '\n') {
2503e12c5d1SDavid du Colombier 				twrite(buf+j, len-j);
2513e12c5d1SDavid du Colombier 				if(--count <= 0)
2523e12c5d1SDavid du Colombier 					return;
2533e12c5d1SDavid du Colombier 				len = j;
2543e12c5d1SDavid du Colombier 			}
2553e12c5d1SDavid du Colombier 	}
2563e12c5d1SDavid du Colombier 	if(count > 0)
2573e12c5d1SDavid du Colombier 		twrite(buf, len);
2583e12c5d1SDavid du Colombier }
2593e12c5d1SDavid du Colombier 
2607dd7cddfSDavid du Colombier /*
2617dd7cddfSDavid du Colombier  * count backward and print tail of file
2627dd7cddfSDavid du Colombier  */
2633e12c5d1SDavid du Colombier void
reverse(void)2647dd7cddfSDavid du Colombier reverse(void)
2653e12c5d1SDavid du Colombier {
2663e12c5d1SDavid du Colombier 	int first;
2673e12c5d1SDavid du Colombier 	long len = 0;
2683e12c5d1SDavid du Colombier 	long n = 0;
2693e12c5d1SDavid du Colombier 	long bufsiz = 0;
2703e12c5d1SDavid du Colombier 	char *buf = 0;
271*7bd483b0SDavid du Colombier 	vlong pos = tseek(0LL, 2);
2727dd7cddfSDavid du Colombier 
2733e12c5d1SDavid du Colombier 	for(first=1; pos>0 && count>0; first=0) {
274*7bd483b0SDavid du Colombier 		n = pos>Bsize? Bsize: (long)pos;
2753e12c5d1SDavid du Colombier 		pos -= n;
2763e12c5d1SDavid du Colombier 		if(len+n > bufsiz) {
2773e12c5d1SDavid du Colombier 			bufsiz += 2*Bsize;
2783e12c5d1SDavid du Colombier 			if(!(buf = realloc(buf, bufsiz+1)))
2793e12c5d1SDavid du Colombier 				fatal("out of space");
2803e12c5d1SDavid du Colombier 		}
2813e12c5d1SDavid du Colombier 		memmove(buf+n, buf, len);
2823e12c5d1SDavid du Colombier 		len += n;
2833e12c5d1SDavid du Colombier 		tseek(pos, 0);
2843e12c5d1SDavid du Colombier 		if(tread(buf, n) != n)
2853e12c5d1SDavid du Colombier 			fatal("length error");
2863e12c5d1SDavid du Colombier 		if(first && buf[len-1]!='\n')
2873e12c5d1SDavid du Colombier 			buf[len++] = '\n';
2883e12c5d1SDavid du Colombier 		for(n=len-1 ; n>0 && count>0; n--)
2893e12c5d1SDavid du Colombier 			if(buf[n-1] == '\n') {
2903e12c5d1SDavid du Colombier 				count--;
2913e12c5d1SDavid du Colombier 				if(dir == REV)
2923e12c5d1SDavid du Colombier 					twrite(buf+n, len-n);
2933e12c5d1SDavid du Colombier 				len = n;
2943e12c5d1SDavid du Colombier 			}
2953e12c5d1SDavid du Colombier 	}
2963e12c5d1SDavid du Colombier 	if(dir == FWD) {
297*7bd483b0SDavid du Colombier 		if(n)
298*7bd483b0SDavid du Colombier 			tseek(pos+n+1, 0);
299*7bd483b0SDavid du Colombier 		else
300*7bd483b0SDavid du Colombier 			tseek(0, 0);
3013e12c5d1SDavid du Colombier 		copy();
3027dd7cddfSDavid du Colombier 	} else
3037dd7cddfSDavid du Colombier 	if(count > 0)
3043e12c5d1SDavid du Colombier 		twrite(buf, len);
3053e12c5d1SDavid du Colombier }
3063e12c5d1SDavid du Colombier 
307*7bd483b0SDavid du Colombier vlong
tseek(vlong o,int p)308*7bd483b0SDavid du Colombier tseek(vlong o, int p)
3093e12c5d1SDavid du Colombier {
3103e12c5d1SDavid du Colombier 	o = seek(file, o, p);
3113e12c5d1SDavid du Colombier 	if(o == -1)
3123e12c5d1SDavid du Colombier 		fatal("");
3133e12c5d1SDavid du Colombier 	return o;
3143e12c5d1SDavid du Colombier }
3153e12c5d1SDavid du Colombier 
3163e12c5d1SDavid du Colombier long
tread(char * buf,long n)3173e12c5d1SDavid du Colombier tread(char *buf, long n)
3183e12c5d1SDavid du Colombier {
3193e12c5d1SDavid du Colombier 	int r = read(file, buf, n);
3203e12c5d1SDavid du Colombier 	if(r == -1)
3213e12c5d1SDavid du Colombier 		fatal("");
3223e12c5d1SDavid du Colombier 	return r;
3233e12c5d1SDavid du Colombier }
3243e12c5d1SDavid du Colombier 
3253e12c5d1SDavid du Colombier void
twrite(char * s,long n)3263e12c5d1SDavid du Colombier twrite(char *s, long n)
3273e12c5d1SDavid du Colombier {
3283e12c5d1SDavid du Colombier 	if(Bwrite(&bout, s, n) != n)
3293e12c5d1SDavid du Colombier 		fatal("");
3303e12c5d1SDavid du Colombier }
3313e12c5d1SDavid du Colombier 
3323e12c5d1SDavid du Colombier int
getnumber(char * s)3333e12c5d1SDavid du Colombier getnumber(char *s)
3343e12c5d1SDavid du Colombier {
3353e12c5d1SDavid du Colombier 	if(*s=='-' || *s=='+')
3363e12c5d1SDavid du Colombier 		s++;
3373e12c5d1SDavid du Colombier 	if(!isdigit(*s))
3383e12c5d1SDavid du Colombier 		return 0;
3393e12c5d1SDavid du Colombier 	if(s[-1] == '+')
3403e12c5d1SDavid du Colombier 		origin = BEG;
3413e12c5d1SDavid du Colombier 	if(anycount++)
3423e12c5d1SDavid du Colombier 		fatal("excess option");
3433e12c5d1SDavid du Colombier 	count = atol(s);
3447dd7cddfSDavid du Colombier 
3457dd7cddfSDavid du Colombier 	/* check range of count */
3467dd7cddfSDavid du Colombier 	if(count < 0 ||	(int)count != count)
3473e12c5d1SDavid du Colombier 		fatal("too big");
3483e12c5d1SDavid du Colombier 	return 1;
3493e12c5d1SDavid du Colombier }
3503e12c5d1SDavid du Colombier 
3513e12c5d1SDavid du Colombier void
fatal(char * s)3523e12c5d1SDavid du Colombier fatal(char *s)
3533e12c5d1SDavid du Colombier {
3549a747e4fSDavid du Colombier 	char buf[ERRMAX];
3553e12c5d1SDavid du Colombier 
3569a747e4fSDavid du Colombier 	errstr(buf, sizeof buf);
3573e12c5d1SDavid du Colombier 	fprint(2, "tail: %s: %s\n", s, buf);
3583e12c5d1SDavid du Colombier 	exits(s);
3593e12c5d1SDavid du Colombier }
3603e12c5d1SDavid du Colombier 
3613e12c5d1SDavid du Colombier void
usage(void)3623e12c5d1SDavid du Colombier usage(void)
3633e12c5d1SDavid du Colombier {
3647dd7cddfSDavid du Colombier 	fprint(2, "%s\n", umsg);
3653e12c5d1SDavid du Colombier 	exits("usage");
3663e12c5d1SDavid du Colombier }
36725332fe6SDavid du Colombier 
36836d3592fSDavid du Colombier /* return true if seeks work and if the file is > 0 length.
36936d3592fSDavid du Colombier  * this will eventually bite me in the ass if seeking a file
37036d3592fSDavid du Colombier  * is not conservative. - presotto
37136d3592fSDavid du Colombier  */
37225332fe6SDavid du Colombier static int
isseekable(int fd)37325332fe6SDavid du Colombier isseekable(int fd)
37425332fe6SDavid du Colombier {
37536d3592fSDavid du Colombier 	vlong m;
37625332fe6SDavid du Colombier 
37725332fe6SDavid du Colombier 	m = seek(fd, 0, 1);
37825332fe6SDavid du Colombier 	if(m < 0)
37925332fe6SDavid du Colombier 		return 0;
38025332fe6SDavid du Colombier 	return 1;
38125332fe6SDavid du Colombier }
382