github.com/tristanisham/sys@v0.0.0-20240326010300-a16cbabb7555/unix/dirent_test.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos 6 7 package unix_test 8 9 import ( 10 "bytes" 11 "fmt" 12 "os" 13 "path/filepath" 14 "runtime" 15 "sort" 16 "strconv" 17 "strings" 18 "testing" 19 "unsafe" 20 21 "golang.org/x/sys/unix" 22 ) 23 24 func TestDirent(t *testing.T) { 25 const ( 26 direntBufSize = 2048 // arbitrary? See https://go.dev/issue/37323. 27 filenameMinSize = 11 28 ) 29 30 d := t.TempDir() 31 t.Logf("tmpdir: %s", d) 32 33 for i, c := range []byte("0123456789") { 34 name := string(bytes.Repeat([]byte{c}, filenameMinSize+i)) 35 err := os.WriteFile(filepath.Join(d, name), nil, 0644) 36 if err != nil { 37 t.Fatal(err) 38 } 39 } 40 41 names := make([]string, 0, 10) 42 43 fd, err := unix.Open(d, unix.O_RDONLY, 0) 44 if err != nil { 45 t.Fatalf("Open: %v", err) 46 } 47 defer unix.Close(fd) 48 49 buf := bytes.Repeat([]byte{0xCD}, direntBufSize) 50 for { 51 n, err := unix.ReadDirent(fd, buf) 52 if err == unix.EINVAL { 53 // On linux, 'man getdents64' says that EINVAL indicates result buffer is too small. 54 // Try a bigger buffer. 55 t.Logf("ReadDirent: %v; retrying with larger buffer", err) 56 buf = bytes.Repeat([]byte{0xCD}, len(buf)*2) 57 continue 58 } 59 if err != nil { 60 t.Fatalf("ReadDirent: %v", err) 61 } 62 t.Logf("ReadDirent: read %d bytes", n) 63 if n == 0 { 64 break 65 } 66 67 var consumed, count int 68 consumed, count, names = unix.ParseDirent(buf[:n], -1, names) 69 t.Logf("ParseDirent: %d new name(s)", count) 70 if consumed != n { 71 t.Fatalf("ParseDirent: consumed %d bytes; expected %d", consumed, n) 72 } 73 } 74 75 sort.Strings(names) 76 t.Logf("names: %q", names) 77 78 if len(names) != 10 { 79 t.Errorf("got %d names; expected 10", len(names)) 80 } 81 for i, name := range names { 82 ord, err := strconv.Atoi(name[:1]) 83 if err != nil { 84 t.Fatalf("names[%d] is non-integer %q: %v", i, names[i], err) 85 } 86 if expected := string(strings.Repeat(name[:1], filenameMinSize+ord)); name != expected { 87 t.Errorf("names[%d] is %q (len %d); expected %q (len %d)", i, name, len(name), expected, len(expected)) 88 } 89 } 90 } 91 92 func TestDirentRepeat(t *testing.T) { 93 const N = 100 94 // Note: the size of the buffer is small enough that the loop 95 // below will need to execute multiple times. See issue #31368. 96 size := N * unsafe.Offsetof(unix.Dirent{}.Name) / 4 97 if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" { 98 if size < 1024 { 99 size = 1024 // DIRBLKSIZ, see issue 31403. 100 } 101 if runtime.GOOS == "freebsd" { 102 t.Skip("need to fix issue 31416 first") 103 } 104 } 105 106 // Make a directory containing N files 107 d := t.TempDir() 108 109 var files []string 110 for i := 0; i < N; i++ { 111 files = append(files, fmt.Sprintf("file%d", i)) 112 } 113 for _, file := range files { 114 err := os.WriteFile(filepath.Join(d, file), []byte("contents"), 0644) 115 if err != nil { 116 t.Fatal(err) 117 } 118 } 119 120 // Read the directory entries using ReadDirent. 121 fd, err := unix.Open(d, unix.O_RDONLY, 0) 122 if err != nil { 123 t.Fatalf("Open: %v", err) 124 } 125 defer unix.Close(fd) 126 var files2 []string 127 for { 128 buf := make([]byte, size) 129 n, err := unix.ReadDirent(fd, buf) 130 if err != nil { 131 t.Fatalf("ReadDirent: %v", err) 132 } 133 if n == 0 { 134 break 135 } 136 buf = buf[:n] 137 for len(buf) > 0 { 138 var consumed int 139 consumed, _, files2 = unix.ParseDirent(buf, -1, files2) 140 if consumed == 0 && len(buf) > 0 { 141 t.Fatal("no progress") 142 } 143 buf = buf[consumed:] 144 } 145 } 146 147 // Check results 148 sort.Strings(files) 149 sort.Strings(files2) 150 if strings.Join(files, "|") != strings.Join(files2, "|") { 151 t.Errorf("bad file list: want\n%q\ngot\n%q", files, files2) 152 } 153 }