1// Copyright 2009 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 || dragonfly || freebsd || (js && wasm) || wasip1 || linux || netbsd || openbsd || solaris
6
7package os
8
9import (
10	"internal/byteorder"
11	"internal/goarch"
12	"io"
13	"runtime"
14	"sync"
15	"syscall"
16	"unsafe"
17)
18
19// Auxiliary information if the File describes a directory
20type dirInfo struct {
21	mu   sync.Mutex
22	buf  *[]byte // buffer for directory I/O
23	nbuf int     // length of buf; return value from Getdirentries
24	bufp int     // location of next record in buf.
25}
26
27const (
28	// More than 5760 to work around https://golang.org/issue/24015.
29	blockSize = 8192
30)
31
32var dirBufPool = sync.Pool{
33	New: func() any {
34		// The buffer must be at least a block long.
35		buf := make([]byte, blockSize)
36		return &buf
37	},
38}
39
40func (d *dirInfo) close() {
41	if d.buf != nil {
42		dirBufPool.Put(d.buf)
43		d.buf = nil
44	}
45}
46
47func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
48	// If this file has no dirInfo, create one.
49	d := f.dirinfo.Load()
50	if d == nil {
51		d = new(dirInfo)
52		f.dirinfo.Store(d)
53	}
54	d.mu.Lock()
55	defer d.mu.Unlock()
56	if d.buf == nil {
57		d.buf = dirBufPool.Get().(*[]byte)
58	}
59
60	// Change the meaning of n for the implementation below.
61	//
62	// The n above was for the public interface of "if n <= 0,
63	// Readdir returns all the FileInfo from the directory in a
64	// single slice".
65	//
66	// But below, we use only negative to mean looping until the
67	// end and positive to mean bounded, with positive
68	// terminating at 0.
69	if n == 0 {
70		n = -1
71	}
72
73	for n != 0 {
74		// Refill the buffer if necessary
75		if d.bufp >= d.nbuf {
76			d.bufp = 0
77			var errno error
78			d.nbuf, errno = f.pfd.ReadDirent(*d.buf)
79			runtime.KeepAlive(f)
80			if errno != nil {
81				return names, dirents, infos, &PathError{Op: "readdirent", Path: f.name, Err: errno}
82			}
83			if d.nbuf <= 0 {
84				// Optimization: we can return the buffer to the pool, there is nothing else to read.
85				dirBufPool.Put(d.buf)
86				d.buf = nil
87				break // EOF
88			}
89		}
90
91		// Drain the buffer
92		buf := (*d.buf)[d.bufp:d.nbuf]
93		reclen, ok := direntReclen(buf)
94		if !ok || reclen > uint64(len(buf)) {
95			break
96		}
97		rec := buf[:reclen]
98		d.bufp += int(reclen)
99		ino, ok := direntIno(rec)
100		if !ok {
101			break
102		}
103		// When building to wasip1, the host runtime might be running on Windows
104		// or might expose a remote file system which does not have the concept
105		// of inodes. Therefore, we cannot make the assumption that it is safe
106		// to skip entries with zero inodes.
107		if ino == 0 && runtime.GOOS != "wasip1" {
108			continue
109		}
110		const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name))
111		namlen, ok := direntNamlen(rec)
112		if !ok || namoff+namlen > uint64(len(rec)) {
113			break
114		}
115		name := rec[namoff : namoff+namlen]
116		for i, c := range name {
117			if c == 0 {
118				name = name[:i]
119				break
120			}
121		}
122		// Check for useless names before allocating a string.
123		if string(name) == "." || string(name) == ".." {
124			continue
125		}
126		if n > 0 { // see 'n == 0' comment above
127			n--
128		}
129		if mode == readdirName {
130			names = append(names, string(name))
131		} else if mode == readdirDirEntry {
132			de, err := newUnixDirent(f.name, string(name), direntType(rec))
133			if IsNotExist(err) {
134				// File disappeared between readdir and stat.
135				// Treat as if it didn't exist.
136				continue
137			}
138			if err != nil {
139				return nil, dirents, nil, err
140			}
141			dirents = append(dirents, de)
142		} else {
143			info, err := lstat(f.name + "/" + string(name))
144			if IsNotExist(err) {
145				// File disappeared between readdir + stat.
146				// Treat as if it didn't exist.
147				continue
148			}
149			if err != nil {
150				return nil, nil, infos, err
151			}
152			infos = append(infos, info)
153		}
154	}
155
156	if n > 0 && len(names)+len(dirents)+len(infos) == 0 {
157		return nil, nil, nil, io.EOF
158	}
159	return names, dirents, infos, nil
160}
161
162// readInt returns the size-bytes unsigned integer in native byte order at offset off.
163func readInt(b []byte, off, size uintptr) (u uint64, ok bool) {
164	if len(b) < int(off+size) {
165		return 0, false
166	}
167	if goarch.BigEndian {
168		return readIntBE(b[off:], size), true
169	}
170	return readIntLE(b[off:], size), true
171}
172
173func readIntBE(b []byte, size uintptr) uint64 {
174	switch size {
175	case 1:
176		return uint64(b[0])
177	case 2:
178		return uint64(byteorder.BeUint16(b))
179	case 4:
180		return uint64(byteorder.BeUint32(b))
181	case 8:
182		return uint64(byteorder.BeUint64(b))
183	default:
184		panic("syscall: readInt with unsupported size")
185	}
186}
187
188func readIntLE(b []byte, size uintptr) uint64 {
189	switch size {
190	case 1:
191		return uint64(b[0])
192	case 2:
193		return uint64(byteorder.LeUint16(b))
194	case 4:
195		return uint64(byteorder.LeUint32(b))
196	case 8:
197		return uint64(byteorder.LeUint64(b))
198	default:
199		panic("syscall: readInt with unsupported size")
200	}
201}
202