| #include	<u.h> | 
 | #include	<libc.h> | 
 | #include	<ctype.h> | 
 | #include	<bio.h> | 
 |  | 
 | /* | 
 |  * tail command, posix plus v10 option -r. | 
 |  * the simple command tail -c, legal in v10, is illegal | 
 |  */ | 
 |  | 
 | vlong	count; | 
 | int	anycount; | 
 | int	follow; | 
 | int	file	= 0; | 
 | char*	umsg	= "usage: tail [-n N] [-c N] [-f] [-r] [+-N[bc][fr]] [file]"; | 
 |  | 
 | Biobuf	bout; | 
 | enum | 
 | { | 
 | 	BEG, | 
 | 	END | 
 | } origin = END; | 
 | enum | 
 | { | 
 | 	CHARS, | 
 | 	LINES | 
 | } units = LINES; | 
 | enum | 
 | { | 
 | 	FWD, | 
 | 	REV | 
 | } dir = FWD; | 
 |  | 
 | extern	void	copy(void); | 
 | extern	void	fatal(char*); | 
 | extern	int	getnumber(char*); | 
 | extern	void	keep(void); | 
 | extern	void	reverse(void); | 
 | extern	void	skip(void); | 
 | extern	void	suffix(char*); | 
 | extern	long	tread(char*, long); | 
 | #define trunc tailtrunc | 
 | extern	void	trunc(Dir*, Dir**); | 
 | extern	vlong	tseek(vlong, int); | 
 | extern	void	twrite(char*, long); | 
 | extern	void	usage(void); | 
 |  | 
 | #define JUMP(o,p) tseek(o,p), copy() | 
 |  | 
 | void | 
 | main(int argc, char **argv) | 
 | { | 
 | 	int seekable, c; | 
 |  | 
 | 	Binit(&bout, 1, OWRITE); | 
 | 	for(; argc > 1 && ((c=*argv[1])=='-'||c=='+'); argc--,argv++ ) { | 
 | 		if(getnumber(argv[1])) { | 
 | 			suffix(argv[1]); | 
 | 			continue; | 
 | 		} else | 
 | 		if(c == '-') | 
 | 			switch(argv[1][1]) { | 
 | 			case 'c': | 
 | 				units = CHARS; | 
 | 			case 'n': | 
 | 				if(getnumber(argv[1]+2)) | 
 | 					continue; | 
 | 				else | 
 | 				if(argc > 2 && getnumber(argv[2])) { | 
 | 					argc--, argv++; | 
 | 					continue; | 
 | 				} else | 
 | 					usage(); | 
 | 			case 'r': | 
 | 				dir = REV; | 
 | 				continue; | 
 | 			case 'f': | 
 | 				follow++; | 
 | 				continue; | 
 | 			case '-': | 
 | 				argc--, argv++; | 
 | 			} | 
 | 		break; | 
 | 	} | 
 | 	if(dir==REV && (units==CHARS || follow || origin==BEG)) | 
 | 		fatal("incompatible options"); | 
 | 	if(!anycount) | 
 | 		count = dir==REV? ~0ULL>>1: 10; | 
 | 	if(origin==BEG && units==LINES && count>0) | 
 | 		count--; | 
 | 	if(argc > 2) | 
 | 		usage(); | 
 | 	if(argc > 1 && (file=open(argv[1],0)) < 0) | 
 | 		fatal(argv[1]); | 
 | 	seekable = seek(file,0L,0) == 0; | 
 |  | 
 | 	if(!seekable && origin==END) | 
 | 		keep(); | 
 | 	else | 
 | 	if(!seekable && origin==BEG) | 
 | 		skip(); | 
 | 	else | 
 | 	if(units==CHARS && origin==END) | 
 | 		JUMP(-count, 2); | 
 | 	else | 
 | 	if(units==CHARS && origin==BEG) | 
 | 		JUMP(count, 0); | 
 | 	else | 
 | 	if(units==LINES && origin==END) | 
 | 		reverse(); | 
 | 	else | 
 | 	if(units==LINES && origin==BEG) | 
 | 		skip(); | 
 | 	if(follow && seekable) | 
 | 		for(;;) { | 
 | 			static Dir *sb0, *sb1; | 
 | 			trunc(sb1, &sb0); | 
 | 			copy(); | 
 | 			trunc(sb0, &sb1); | 
 | 			sleep(5000); | 
 | 		} | 
 | 	exits(0); | 
 | } | 
 |  | 
 | void | 
 | trunc(Dir *old, Dir **new) | 
 | { | 
 | 	Dir *d; | 
 | 	vlong olength; | 
 |  | 
 | 	d = dirfstat(file); | 
 | 	if(d == nil) | 
 | 		return; | 
 | 	olength = 0; | 
 | 	if(old) | 
 | 		olength = old->length; | 
 | 	if(d->length < olength) | 
 | 		d->length = tseek(0L, 0); | 
 | 	free(*new); | 
 | 	*new = d; | 
 | } | 
 |  | 
 | void | 
 | suffix(char *s) | 
 | { | 
 | 	while(*s && strchr("0123456789+-", *s)) | 
 | 		s++; | 
 | 	switch(*s) { | 
 | 	case 'b': | 
 | 		if((count *= 1024) < 0) | 
 | 			fatal("too big"); | 
 | 	case 'c': | 
 | 		units = CHARS; | 
 | 	case 'l': | 
 | 		s++; | 
 | 	} | 
 | 	switch(*s) { | 
 | 	case 'r': | 
 | 		dir = REV; | 
 | 		return; | 
 | 	case 'f': | 
 | 		follow++; | 
 | 		return; | 
 | 	case 0: | 
 | 		return; | 
 | 	} | 
 | 	usage(); | 
 | } | 
 |  | 
 | /* | 
 |  * read past head of the file to find tail | 
 |  */ | 
 | void | 
 | skip(void) | 
 | { | 
 | 	int i; | 
 | 	long n; | 
 | 	char buf[Bsize]; | 
 | 	if(units == CHARS) { | 
 | 		for( ; count>0; count -=n) { | 
 | 			n = count<Bsize? count: Bsize; | 
 | 			if(!(n = tread(buf, n))) | 
 | 				return; | 
 | 		} | 
 | 	} else /*units == LINES*/ { | 
 | 		n = i = 0; | 
 | 		while(count > 0) { | 
 | 			if(!(n = tread(buf, Bsize))) | 
 | 				return; | 
 | 			for(i=0; i<n && count>0; i++) | 
 | 				if(buf[i]=='\n') | 
 | 					count--; | 
 | 		} | 
 | 		twrite(buf+i, n-i); | 
 | 	} | 
 | 	copy(); | 
 | } | 
 |  | 
 | void | 
 | copy(void) | 
 | { | 
 | 	long n; | 
 | 	char buf[Bsize]; | 
 | 	while((n=tread(buf, Bsize)) > 0) { | 
 | 		twrite(buf, n); | 
 | 		Bflush(&bout);	/* for FWD on pipe; else harmless */ | 
 | 	} | 
 | } | 
 |  | 
 | /* | 
 |  * read whole file, keeping the tail | 
 |  *	complexity is length(file)*length(tail). | 
 |  *	could be linear. | 
 |  */ | 
 | void | 
 | keep(void) | 
 | { | 
 | 	int len = 0; | 
 | 	long bufsiz = 0; | 
 | 	char *buf = 0; | 
 | 	int j, k, n; | 
 |  | 
 | 	for(n=1; n;) { | 
 | 		if(len+Bsize > bufsiz) { | 
 | 			bufsiz += 2*Bsize; | 
 | 			if(!(buf = realloc(buf, bufsiz+1))) | 
 | 				fatal("out of space"); | 
 | 		} | 
 | 		for(; n && len<bufsiz; len+=n) | 
 | 			n = tread(buf+len, bufsiz-len); | 
 | 		if(count >= len) | 
 | 			continue; | 
 | 		if(units == CHARS) | 
 | 			j = len - count; | 
 | 		else { | 
 | 			/* units == LINES */ | 
 | 			j = buf[len-1]=='\n'? len-1: len; | 
 | 			for(k=0; j>0; j--) | 
 | 				if(buf[j-1] == '\n') | 
 | 					if(++k >= count) | 
 | 						break; | 
 | 		} | 
 | 		memmove(buf, buf+j, len-=j); | 
 | 	} | 
 | 	if(dir == REV) { | 
 | 		if(len>0 && buf[len-1]!='\n') | 
 | 			buf[len++] = '\n'; | 
 | 		for(j=len-1 ; j>0; j--) | 
 | 			if(buf[j-1] == '\n') { | 
 | 				twrite(buf+j, len-j); | 
 | 				if(--count <= 0) | 
 | 					return; | 
 | 				len = j; | 
 | 			} | 
 | 	} | 
 | 	if(count > 0) | 
 | 		twrite(buf, len); | 
 | } | 
 |  | 
 | /* | 
 |  * count backward and print tail of file | 
 |  */ | 
 | void | 
 | reverse(void) | 
 | { | 
 | 	int first; | 
 | 	long len = 0; | 
 | 	long n = 0; | 
 | 	long bufsiz = 0; | 
 | 	char *buf = 0; | 
 | 	vlong pos = tseek(0L, 2); | 
 |  | 
 | 	for(first=1; pos>0 && count>0; first=0) { | 
 | 		n = pos>Bsize? Bsize: (int)pos; | 
 | 		pos -= n; | 
 | 		if(len+n > bufsiz) { | 
 | 			bufsiz += 2*Bsize; | 
 | 			if(!(buf = realloc(buf, bufsiz+1))) | 
 | 				fatal("out of space"); | 
 | 		} | 
 | 		memmove(buf+n, buf, len); | 
 | 		len += n; | 
 | 		tseek(pos, 0); | 
 | 		if(tread(buf, n) != n) | 
 | 			fatal("length error"); | 
 | 		if(first && buf[len-1]!='\n') | 
 | 			buf[len++] = '\n'; | 
 | 		for(n=len-1 ; n>0 && count>0; n--) | 
 | 			if(buf[n-1] == '\n') { | 
 | 				count--; | 
 | 				if(dir == REV) | 
 | 					twrite(buf+n, len-n); | 
 | 				len = n; | 
 | 			} | 
 | 	} | 
 | 	if(dir == FWD) { | 
 | 		tseek(n==0? 0 : pos+n+1, 0); | 
 | 		copy(); | 
 | 	} else | 
 | 	if(count > 0) | 
 | 		twrite(buf, len); | 
 | } | 
 |  | 
 | vlong | 
 | tseek(vlong o, int p) | 
 | { | 
 | 	o = seek(file, o, p); | 
 | 	if(o == -1) | 
 | 		fatal(""); | 
 | 	return o; | 
 | } | 
 |  | 
 | long | 
 | tread(char *buf, long n) | 
 | { | 
 | 	int r = read(file, buf, n); | 
 | 	if(r == -1) | 
 | 		fatal(""); | 
 | 	return r; | 
 | } | 
 |  | 
 | void | 
 | twrite(char *s, long n) | 
 | { | 
 | 	if(Bwrite(&bout, s, n) != n) | 
 | 		fatal(""); | 
 | } | 
 |  | 
 | int | 
 | getnumber(char *s) | 
 | { | 
 | 	if(*s=='-' || *s=='+') | 
 | 		s++; | 
 | 	if(!isdigit((uchar)*s)) | 
 | 		return 0; | 
 | 	if(s[-1] == '+') | 
 | 		origin = BEG; | 
 | 	if(anycount++) | 
 | 		fatal("excess option"); | 
 | 	count = atol(s); | 
 |  | 
 | 	/* check range of count */ | 
 | 	if(count < 0 || (int)count != count) | 
 | 		fatal("too big"); | 
 | 	return 1; | 
 | }	 | 
 |  | 
 | void		 | 
 | fatal(char *s) | 
 | { | 
 | 	char buf[ERRMAX]; | 
 |  | 
 | 	errstr(buf, sizeof buf); | 
 | 	fprint(2, "tail: %s: %s\n", s, buf); | 
 | 	exits(s); | 
 | } | 
 |  | 
 | void | 
 | usage(void) | 
 | { | 
 | 	fprint(2, "%s\n", umsg); | 
 | 	exits("usage"); | 
 | } |