diff --git a/unix/dirent_test.go b/unix/dirent_test.go new file mode 100644 index 00000000..48eb257f --- /dev/null +++ b/unix/dirent_test.go @@ -0,0 +1,150 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +package unix_test + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "sort" + "strconv" + "strings" + "testing" + "unsafe" + + "golang.org/x/sys/unix" +) + +func TestDirent(t *testing.T) { + const ( + direntBufSize = 2048 + filenameMinSize = 11 + ) + + d, err := ioutil.TempDir("", "dirent-test") + if err != nil { + t.Fatalf("tempdir: %v", err) + } + defer os.RemoveAll(d) + t.Logf("tmpdir: %s", d) + + for i, c := range []byte("0123456789") { + name := string(bytes.Repeat([]byte{c}, filenameMinSize+i)) + err = ioutil.WriteFile(filepath.Join(d, name), nil, 0644) + if err != nil { + t.Fatalf("writefile: %v", err) + } + } + + buf := bytes.Repeat([]byte("DEADBEAF"), direntBufSize/8) + fd, err := unix.Open(d, unix.O_RDONLY, 0) + if err != nil { + t.Fatalf("Open: %v", err) + } + defer unix.Close(fd) + n, err := unix.ReadDirent(fd, buf) + if err != nil { + t.Fatalf("ReadDirent: %v", err) + } + buf = buf[:n] + + names := make([]string, 0, 10) + for len(buf) > 0 { + var bc int + bc, _, names = unix.ParseDirent(buf, -1, names) + if bc == 0 && len(buf) > 0 { + t.Fatal("no progress") + } + buf = buf[bc:] + } + + sort.Strings(names) + t.Logf("names: %q", names) + + if len(names) != 10 { + t.Errorf("got %d names; expected 10", len(names)) + } + for i, name := range names { + ord, err := strconv.Atoi(name[:1]) + if err != nil { + t.Fatalf("names[%d] is non-integer %q: %v", i, names[i], err) + } + if expected := string(strings.Repeat(name[:1], filenameMinSize+ord)); name != expected { + t.Errorf("names[%d] is %q (len %d); expected %q (len %d)", i, name, len(name), expected, len(expected)) + } + } +} + +func TestDirentRepeat(t *testing.T) { + const N = 100 + // Note: the size of the buffer is small enough that the loop + // below will need to execute multiple times. See issue #31368. + size := N * unsafe.Offsetof(unix.Dirent{}.Name) / 4 + if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" { + if size < 1024 { + size = 1024 // DIRBLKSIZ, see issue 31403. + } + if runtime.GOOS == "freebsd" { + t.Skip("need to fix issue 31416 first") + } + } + + // Make a directory containing N files + d, err := ioutil.TempDir("", "direntRepeat-test") + if err != nil { + t.Fatalf("tempdir: %v", err) + } + defer os.RemoveAll(d) + + var files []string + for i := 0; i < N; i++ { + files = append(files, fmt.Sprintf("file%d", i)) + } + for _, file := range files { + err = ioutil.WriteFile(filepath.Join(d, file), []byte("contents"), 0644) + if err != nil { + t.Fatalf("writefile: %v", err) + } + } + + // Read the directory entries using ReadDirent. + fd, err := unix.Open(d, unix.O_RDONLY, 0) + if err != nil { + t.Fatalf("Open: %v", err) + } + defer unix.Close(fd) + var files2 []string + for { + buf := make([]byte, size) + n, err := unix.ReadDirent(fd, buf) + if err != nil { + t.Fatalf("ReadDirent: %v", err) + } + if n == 0 { + break + } + buf = buf[:n] + for len(buf) > 0 { + var consumed int + consumed, _, files2 = unix.ParseDirent(buf, -1, files2) + if consumed == 0 && len(buf) > 0 { + t.Fatal("no progress") + } + buf = buf[consumed:] + } + } + + // Check results + sort.Strings(files) + sort.Strings(files2) + if strings.Join(files, "|") != strings.Join(files2, "|") { + t.Errorf("bad file list: want\n%q\ngot\n%q", files, files2) + } +} diff --git a/unix/readdirent_getdents.go b/unix/readdirent_getdents.go new file mode 100644 index 00000000..2c51af8c --- /dev/null +++ b/unix/readdirent_getdents.go @@ -0,0 +1,12 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build aix freebsd linux netbsd openbsd + +package unix + +// ReadDirent reads directory entries from fd and writes them into buf. +func ReadDirent(fd int, buf []byte) (n int, err error) { + return Getdents(fd, buf) +} diff --git a/unix/readdirent_getdirentries.go b/unix/readdirent_getdirentries.go new file mode 100644 index 00000000..e5bf23d3 --- /dev/null +++ b/unix/readdirent_getdirentries.go @@ -0,0 +1,17 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly + +package unix + +// ReadDirent reads directory entries from fd and writes them into buf. +func ReadDirent(fd int, buf []byte) (n int, err error) { + // Final argument is (basep *uintptr) and the syscall doesn't take nil. + // 64 bits should be enough. (32 bits isn't even on 386). Since the + // actual system call is getdirentries64, 64 is a good guess. + // TODO(rsc): Can we use a single global basep for all calls? + var base = (*uintptr)(unsafe.Pointer(new(uint64))) + return Getdirentries(fd, buf, base) +} diff --git a/unix/syscall_aix.go b/unix/syscall_aix.go index 45e12fb8..a079243d 100644 --- a/unix/syscall_aix.go +++ b/unix/syscall_aix.go @@ -281,7 +281,7 @@ func sendfile(outfd int, infd int, offset *int64, count int) (written int, err e } //sys getdirent(fd int, buf []byte) (n int, err error) -func ReadDirent(fd int, buf []byte) (n int, err error) { +func Getdents(fd int, buf []byte) (n int, err error) { return getdirent(fd, buf) } diff --git a/unix/syscall_bsd.go b/unix/syscall_bsd.go index 41c33e96..97a8eef6 100644 --- a/unix/syscall_bsd.go +++ b/unix/syscall_bsd.go @@ -63,15 +63,6 @@ func Setgroups(gids []int) (err error) { return setgroups(len(a), &a[0]) } -func ReadDirent(fd int, buf []byte) (n int, err error) { - // Final argument is (basep *uintptr) and the syscall doesn't take nil. - // 64 bits should be enough. (32 bits isn't even on 386). Since the - // actual system call is getdirentries64, 64 is a good guess. - // TODO(rsc): Can we use a single global basep for all calls? - var base = (*uintptr)(unsafe.Pointer(new(uint64))) - return Getdirentries(fd, buf, base) -} - // Wait status is 7 bits at bottom, either 0 (exited), // 0x7F (stopped), or a signal number that caused an exit. // The 0x80 bit is whether there was a core dump. diff --git a/unix/syscall_linux.go b/unix/syscall_linux.go index 87174189..11d07ace 100644 --- a/unix/syscall_linux.go +++ b/unix/syscall_linux.go @@ -1413,10 +1413,6 @@ func Reboot(cmd int) (err error) { return reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd, "") } -func ReadDirent(fd int, buf []byte) (n int, err error) { - return Getdents(fd, buf) -} - //sys mount(source string, target string, fstype string, flags uintptr, data *byte) (err error) func Mount(source string, target string, fstype string, flags uintptr, data string) (err error) { diff --git a/unix/syscall_solaris.go b/unix/syscall_solaris.go index e4780127..9147ba15 100644 --- a/unix/syscall_solaris.go +++ b/unix/syscall_solaris.go @@ -189,6 +189,7 @@ func Setgroups(gids []int) (err error) { return setgroups(len(a), &a[0]) } +// ReadDirent reads directory entries from fd and writes them into buf. func ReadDirent(fd int, buf []byte) (n int, err error) { // Final argument is (basep *uintptr) and the syscall doesn't take nil. // TODO(rsc): Can we use a single global basep for all calls?