readdirとdirread

UNIXディレクトリエントリを読み出すとき、普通はopendir/readdir/closedirを使う。readdirはディレクトリエントリを一つ読み出すライブラリ関数で、内部的にはgetdents (BSDの場合はgetdirentries)システムコールが呼ばれる。今はobsoleteだと思うけど、Linuxにはreaddirというシステムコールもある。

太古のUNIXファイルシステムは最大ファイル名長が14文字ぐらいで、ディレクトリエントリ(基本的にinode番号とファイル名のタプル)は固定長だった。これならsizeof(dirent)づつreadするだけで十分だ。削除され、すでに無効になったディレクトリエントリも読めてしまうだろうけど。しかし、BSDでFast File System (FFS)が実装され、ディレクトリエントリが可変長になったときに、利便性を考えてreaddirなどが導入された。ディレクトリエントリの実装はファイルシステム(ディスク上への格納形式)依存なので、それを実装非依存にするためにも必要となる。

Plan9には、dirread、dirreadallというreaddirに相当するライブラリ関数があるが、内部ではreadシステムコールを使っている。この違いは9Pプロトコルというか、ディレクトリエントリの抽象度の違いから来ている。ディレクトリをreadする場合、カーネルはディスク上のデータをそのまま返すのではなく、ファイルシステム非依存のディレクトリエントリを返す。

参考までに、dirreadでlsもどきを書くとこんな感じ。dirreadは一回のreadで読み込んだ分のディレクトリエントリを返すので、すべてのディレクトリエントリを得るには、戻り値が0になるまでループする必要がある。まぁ、readと同じセマンティクスだと思えばよい。一方、dirreadallはすべてのディレクトリエントリを返す。

#include <u.h>
#include <libc.h>

void
main(int argc, char **argv)
{
	Dir *de;
	int n, fd, i;

	fd = open(".", OREAD);
	if (fd < 0)
		sysfatal("open");
	for (;;) {
		n = dirread(fd, &de);
		if (n == 0)
			break;
		for (i = 0; i < n; i++)
			print("%s\n", de[i].name);
		free(de);
	}
	exits(nil);
}