new utilities.
the .C files compile but are renamed to avoid building automatically.
diff --git a/src/cmd/tail.c b/src/cmd/tail.c
new file mode 100644
index 0000000..448b3ea
--- /dev/null
+++ b/src/cmd/tail.c
@@ -0,0 +1,362 @@
+#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
+ */
+
+long	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);
+extern	void	trunc(Dir*, Dir**);
+extern	long	tseek(long, 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? ~0UL>>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;
+	ulong 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;
+	long 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);
+}
+
+long
+tseek(long 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(*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");
+}