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