tail(1)

Plan9にはtail(1)はあるけど、head(1)がない。頭隠して尻隠さず?

戯言はおいておいて、最近古本屋で見つけた「デーモン君のソースコード探検」にtailの実装にmmapを使っているという話が書かれていた。確かにお尻からfseek(fseeko)でちまちまさかのぼるより効率が良さそうだ。Plan9はmmapを持たないので、どんな実装になっているのか調べてみた。

オプションによって挙動は変わるが、お尻のcount行を表示はtail.c:keep()関数で処理している。何のことはない、count行分をバッファに残しながら、頭からreadするという、すごく単純な実装だった。

/*
 * 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);
}

話は戻って、「デーモン君のソースコード探検」はNetBSD 1.6を素材に使っているが、OpenBSDのtailはNetBSDがmmapを使う以前にブランチしたものらしく、fseekoでちまちまファイルポインタを動かしている。mmapを使わない理由はあるのだろうか?
CVSリポジトリへのリンクを示すが、この処理を行っている箇所は、forward.c:rlines()。

また、同書では-fオプションの実装を調べていて、selectでタイムアウトするテクニック*1を紹介しているのだが、今の実装はどちらのBSDでも、kqueue&keventを使っている。

単純なコマンドでも、読んでみると勉強になるなぁ。

*1:sleep(3)を使うと内部で複数のシステムコールが呼ばれるが、select(2)だと一回で済む。