diff --git a/src/lib/os/dir_amd64_darwin.go b/src/lib/os/dir_amd64_darwin.go
index f1401825c8b5d27f67df07a2bac66ac418559ad8..e66f540c858dd1457b9c4773fedf142f594d9da2 100644
--- a/src/lib/os/dir_amd64_darwin.go
+++ b/src/lib/os/dir_amd64_darwin.go
@@ -10,36 +10,44 @@ import (
 	"unsafe";
 )
 
+const (
+	blockSize = 4096	// TODO(r): use statfs
+)
+
 // Negative count means read until EOF.
 func Readdirnames(fd *FD, count int) (names []string, err *os.Error) {
-	// Getdirentries needs the file offset - it's too hard for the kernel to remember
-	// a number it already has written down.
-	base, err1 := syscall.Seek(fd.fd, 0, 1);
-	if err1 != 0 {
-		return nil, os.ErrnoToError(err1)
+	// If this fd has no dirinfo, create one.
+	if fd.dirinfo == nil {
+		fd.dirinfo = new(DirInfo);
+		// The buffer must be at least a block long.
+		// TODO(r): use fstatfs to find fs block size.
+		fd.dirinfo.buf = make([]byte, blockSize);
 	}
-	// The buffer must be at least a block long.
-	// TODO(r): use fstatfs to find fs block size.
-	var buf = make([]byte, 8192);
-	names = make([]string, 0, 100);	// TODO: could be smarter about size
-	for {
-		if count == 0 {
-			break
-		}
-		ret, err2 := syscall.Getdirentries(fd.fd, &buf[0], int64(len(buf)), &base);
-		if ret < 0 || err2 != 0 {
-			return names, os.ErrnoToError(err2)
-		}
-		if ret == 0 {
-			break
-		}
-		for w, i := uintptr(0),uintptr(0); i < uintptr(ret); i += w {
-			if count == 0 {
-				break
+	d := fd.dirinfo;
+	size := count;
+	if size < 0 {
+		size = 100
+	}
+	names = make([]string, 0, size);	// Empty with room to grow.
+	for count != 0 {
+		// Refill the buffer if necessary
+		if d.bufp == d.nbuf {
+			var errno int64;
+			// Final argument is (basep *int64) and the syscall doesn't take nil.
+			d.nbuf, errno = syscall.Getdirentries(fd.fd, &d.buf[0], int64(len(d.buf)), new(int64));
+			if d.nbuf < 0 {
+				return names, os.ErrnoToError(errno)
 			}
-			dirent := unsafe.Pointer((uintptr(unsafe.Pointer(&buf[0])) + i)).(*syscall.Dirent);
-			w = uintptr(dirent.Reclen);
-			if dirent.Ino == 0 {
+			if d.nbuf == 0 {
+				break	// EOF
+			}
+			d.bufp = 0;
+		}
+		// Drain the buffer
+		for count != 0 && d.bufp < d.nbuf {
+			dirent := unsafe.Pointer(&d.buf[d.bufp]).(*syscall.Dirent);
+			d.bufp += int64(dirent.Reclen);
+			if dirent.Ino == 0 {	// File absent in directory.
 				continue
 			}
 			count--;
@@ -54,7 +62,7 @@ func Readdirnames(fd *FD, count int) (names []string, err *os.Error) {
 			names[len(names)-1] = string(dirent.Name[0:dirent.Namlen]);
 		}
 	}
-	return names, nil;
+	return names, nil
 }
 
 // TODO(r): see comment in dir_amd64_linux.go
@@ -74,7 +82,7 @@ func Readdir(fd *FD, count int) (dirs []Dir, err *os.Error) {
 	}
 	// The buffer must be at least a block long.
 	// TODO(r): use fstatfs to find fs block size.
-	var buf = make([]byte, 8192);
+	var buf = make([]byte, blockSize);
 	dirs = make([]Dir, 0, 100);	// TODO: could be smarter about size
 	for {
 		if count == 0 {
@@ -106,7 +114,7 @@ func Readdir(fd *FD, count int) (dirs []Dir, err *os.Error) {
 			}
 			dirs = dirs[0:len(dirs)+1];
 			filename := string(dirent.Name[0:dirent.Namlen]);
-			dirp, err := Stat(dirname + filename);
+			dirp, err := Lstat(dirname + filename);
 			if dirp == nil || err != nil {
 				dirs[len(dirs)-1].Name = filename;	// rest will be zeroed out
 			} else {
diff --git a/src/lib/os/os_file.go b/src/lib/os/os_file.go
index 25b14d03f2276181b5b2be41fa4bfd96cc52dccf..cd924bd205a36d6841732ed6cf7c37a6c228229f 100644
--- a/src/lib/os/os_file.go
+++ b/src/lib/os/os_file.go
@@ -7,10 +7,18 @@ package os
 import syscall "syscall"
 import os "os"
 
+// Auxiliary information if the FD describes a directory
+type DirInfo struct {	// TODO(r): 6g bug means this can't be private
+	buf	[]byte;	// buffer for directory I/O
+	nbuf	int64;	// length of buf; return value from Getdirentries
+	bufp	int64;	// location of next record in buf.
+}
+
 // FDs are wrappers for file descriptors
 type FD struct {
 	fd int64;
 	name	string;
+	dirinfo	*DirInfo;	// nil unless directory being read
 }
 
 func (fd *FD) Fd() int64 {
@@ -25,7 +33,7 @@ func NewFD(fd int64, name string) *FD {
 	if fd < 0 {
 		return nil
 	}
-	return &FD{fd, name}
+	return &FD{fd, name, nil}
 }
 
 var (
@@ -90,6 +98,17 @@ func (fd *FD) Write(b []byte) (ret int, err *Error) {
 	return int(r), ErrnoToError(e)
 }
 
+func (fd *FD) Seek(offset int64, whence int) (ret int64, err *Error) {
+	r, e := syscall.Seek(fd.fd, offset, int64(whence));
+	if e != 0 {
+		return -1, ErrnoToError(e)
+	}
+	if fd.dirinfo != nil && r != 0 {
+		return -1, ErrnoToError(syscall.EISDIR)
+	}
+	return r, nil
+}
+
 func (fd *FD) WriteString(s string) (ret int, err *Error) {
 	if fd == nil {
 		return 0, EINVAL
diff --git a/src/lib/os/os_test.go b/src/lib/os/os_test.go
index 5e0c2bf4bc706b05bd7ab40d64439b517fb8d3da..5c37a6b13952a567504e399c4d868d68a6e9fb5f 100644
--- a/src/lib/os/os_test.go
+++ b/src/lib/os/os_test.go
@@ -101,11 +101,11 @@ func testReaddirnames(dir string, contents []string, t *testing.T) {
 	fd, err := Open(dir, O_RDONLY, 0);
 	defer fd.Close();
 	if err != nil {
-		t.Fatalf("open %q failed: %s\n", dir, err.String());
+		t.Fatalf("open %q failed: %v\n", dir, err);
 	}
 	s, err2 := Readdirnames(fd, -1);
 	if err2 != nil {
-		t.Fatal("readdirnames . failed:", err);
+		t.Fatalf("readdirnames %q failed: %v", err2);
 	}
 	for i, m := range contents {
 		found := false;
@@ -127,11 +127,11 @@ func testReaddir(dir string, contents []string, t *testing.T) {
 	fd, err := Open(dir, O_RDONLY, 0);
 	defer fd.Close();
 	if err != nil {
-		t.Fatalf("open %q failed: %s\n", dir, err.String());
+		t.Fatalf("open %q failed: %v", dir, err);
 	}
 	s, err2 := Readdir(fd, -1);
 	if err2 != nil {
-		t.Fatal("readdir . failed:", err);
+		t.Fatalf("readdir %q failed: %v", dir, err2);
 	}
 	for i, m := range contents {
 		found := false;
@@ -158,3 +158,46 @@ func TestReaddir(t *testing.T) {
 	testReaddir(".", dot, t);
 	testReaddir("/etc", etc, t);
 }
+
+// Read the directory one entry at a time.
+func smallReaddirnames(fd *FD, length int, t *testing.T) []string {
+	names := make([]string, length);
+	count := 0;
+	for {
+		d, err := Readdirnames(fd, 1);
+		if err != nil {
+			t.Fatalf("readdir %q failed: %v", fd.Name(), err);
+		}
+		if len(d) == 0 {
+			break
+		}
+		names[count] = d[0];
+		count++;
+	}
+	return names[0:count]
+}
+
+// Check that reading a directory one entry at a time gives the same result
+// as reading it all at once.
+func TestReaddirnamesOneAtATime(t *testing.T) {
+	dir := "/usr/bin";	// big directory that doesn't change often.
+	fd, err := Open(dir, O_RDONLY, 0);
+	defer fd.Close();
+	if err != nil {
+		t.Fatalf("open %q failed: %v", dir, err);
+	}
+	all, err1 := Readdirnames(fd, -1);
+	if err1 != nil {
+		t.Fatalf("readdirnames %q failed: %v", dir, err1);
+	}
+	fd1, err2 := Open(dir, O_RDONLY, 0);
+	if err2 != nil {
+		t.Fatalf("open %q failed: %v\n", dir, err2);
+	}
+	small := smallReaddirnames(fd1, len(all)+100, t);	// +100 in case we screw up
+	for i, n := range all {
+		if small[i] != n {
+			t.Errorf("small read %q %q mismatch: %v\n", small[i], n);
+		}
+	}
+}
diff --git a/src/lib/syscall/file_darwin.go b/src/lib/syscall/file_darwin.go
index c1e43c31ffa1e3d1bc8b038d14be0fe0cfd59019..5d128f743ce4d38ea6a04565a9baf09d2b912b7f 100644
--- a/src/lib/syscall/file_darwin.go
+++ b/src/lib/syscall/file_darwin.go
@@ -96,8 +96,5 @@ func Dup2(fd1, fd2 int64) (ret int64, errno int64) {
 
 func Getdirentries(fd int64, buf *byte, nbytes int64, basep *int64) (ret int64, errno int64) {
 	r1, r2, err := Syscall6(SYS_GETDIRENTRIES64, fd, int64(uintptr(unsafe.Pointer(buf))), nbytes, int64(uintptr(unsafe.Pointer(basep))), 0, 0);
-	if r1 != -1 {
-		*basep = r2
-	}
 	return r1, err;
 }