Skip to content

Commit a4ede9f

Browse files
committed
os: add File.ReadDir method and DirEntry type
ReadDir provides a portable, efficient way to read a directory and discover the type of directory entries. This enables a more efficient file system walk, yet to be added. See #41467 for the proposal review for the API. Fixes #41467. Change-Id: I461a526793ae46df48821aa448b04f1705546739 Reviewed-on: https://go-review.googlesource.com/c/go/+/261540 Trust: Russ Cox <[email protected]> Run-TryBot: Russ Cox <[email protected]> Reviewed-by: Rob Pike <[email protected]>
1 parent 8fe372c commit a4ede9f

20 files changed

+784
-110
lines changed

src/os/dir.go

+60-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
package os
66

7+
type readdirMode int
8+
9+
const (
10+
readdirName readdirMode = iota
11+
readdirDirEntry
12+
readdirFileInfo
13+
)
14+
715
// Readdir reads the contents of the directory associated with file and
816
// returns a slice of up to n FileInfo values, as would be returned
917
// by Lstat, in directory order. Subsequent calls on the same file will yield
@@ -19,11 +27,14 @@ package os
1927
// nil error. If it encounters an error before the end of the
2028
// directory, Readdir returns the FileInfo read until that point
2129
// and a non-nil error.
30+
//
31+
// Most clients are better served by the more efficient ReadDir method.
2232
func (f *File) Readdir(n int) ([]FileInfo, error) {
2333
if f == nil {
2434
return nil, ErrInvalid
2535
}
26-
return f.readdir(n)
36+
_, _, infos, err := f.readdir(n, readdirFileInfo)
37+
return infos, err
2738
}
2839

2940
// Readdirnames reads the contents of the directory associated with file
@@ -45,5 +56,52 @@ func (f *File) Readdirnames(n int) (names []string, err error) {
4556
if f == nil {
4657
return nil, ErrInvalid
4758
}
48-
return f.readdirnames(n)
59+
names, _, _, err = f.readdir(n, readdirName)
60+
return names, err
61+
}
62+
63+
// A DirEntry is an entry read from a directory
64+
// (using the ReadDir function or a File's ReadDir method).
65+
type DirEntry interface {
66+
// Name returns the name of the file (or subdirectory) described by the entry.
67+
// This name is only the final element of the path, not the entire path.
68+
// For example, Name would return "hello.go" not "/home/gopher/hello.go".
69+
Name() string
70+
71+
// IsDir reports whether the entry describes a subdirectory.
72+
IsDir() bool
73+
74+
// Type returns the type bits for the entry.
75+
// The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method.
76+
Type() FileMode
77+
78+
// Info returns the FileInfo for the file or subdirectory described by the entry.
79+
// The returned FileInfo may be from the time of the original directory read
80+
// or from the time of the call to Info. If the file has been removed or renamed
81+
// since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist).
82+
// If the entry denotes a symbolic link, Info reports the information about the link itself,
83+
// not the link's target.
84+
Info() (FileInfo, error)
85+
}
86+
87+
// ReadDir reads the contents of the directory associated with the file f
88+
// and returns a slice of DirEntry values in directory order.
89+
// Subsequent calls on the same file will yield later DirEntry records in the directory.
90+
//
91+
// If n > 0, ReadDir returns at most n DirEntry records.
92+
// In this case, if ReadDir returns an empty slice, it will return an error explaining why.
93+
// At the end of a directory, the error is io.EOF.
94+
//
95+
// If n <= 0, ReadDir returns all the DirEntry records remaining in the directory.
96+
// When it succeeds, it returns a nil error (not io.EOF).
97+
func (f *File) ReadDir(n int) ([]DirEntry, error) {
98+
if f == nil {
99+
return nil, ErrInvalid
100+
}
101+
_, dirents, _, err := f.readdir(n, readdirDirEntry)
102+
return dirents, err
49103
}
104+
105+
// testingForceReadDirLstat forces ReadDir to call Lstat, for testing that code path.
106+
// This can be difficult to provoke on some Unix systems otherwise.
107+
var testingForceReadDirLstat bool

src/os/dir_darwin.go

+53-9
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ func (d *dirInfo) close() {
2424
d.dir = 0
2525
}
2626

27-
func (f *File) readdirnames(n int) (names []string, err error) {
27+
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
2828
if f.dirinfo == nil {
2929
dir, call, errno := f.pfd.OpenDir()
3030
if errno != nil {
31-
return nil, &PathError{call, f.name, errno}
31+
return nil, nil, nil, &PathError{Op: call, Path: f.name, Err: errno}
3232
}
3333
f.dirinfo = &dirInfo{
3434
dir: dir,
@@ -42,15 +42,14 @@ func (f *File) readdirnames(n int) (names []string, err error) {
4242
n = -1
4343
}
4444

45-
names = make([]string, 0, size)
4645
var dirent syscall.Dirent
4746
var entptr *syscall.Dirent
48-
for len(names) < size || n == -1 {
47+
for len(names)+len(dirents)+len(infos) < size || n == -1 {
4948
if errno := readdir_r(d.dir, &dirent, &entptr); errno != 0 {
5049
if errno == syscall.EINTR {
5150
continue
5251
}
53-
return names, &PathError{"readdir", f.name, errno}
52+
return names, dirents, infos, &PathError{Op: "readdir", Path: f.name, Err: errno}
5453
}
5554
if entptr == nil { // EOF
5655
break
@@ -69,13 +68,58 @@ func (f *File) readdirnames(n int) (names []string, err error) {
6968
if string(name) == "." || string(name) == ".." {
7069
continue
7170
}
72-
names = append(names, string(name))
71+
if mode == readdirName {
72+
names = append(names, string(name))
73+
} else if mode == readdirDirEntry {
74+
de, err := newUnixDirent(f.name, string(name), dtToType(dirent.Type))
75+
if IsNotExist(err) {
76+
// File disappeared between readdir and stat.
77+
// Treat as if it didn't exist.
78+
continue
79+
}
80+
if err != nil {
81+
return nil, dirents, nil, err
82+
}
83+
dirents = append(dirents, de)
84+
} else {
85+
info, err := lstat(f.name + "/" + string(name))
86+
if IsNotExist(err) {
87+
// File disappeared between readdir + stat.
88+
// Treat as if it didn't exist.
89+
continue
90+
}
91+
if err != nil {
92+
return nil, nil, infos, err
93+
}
94+
infos = append(infos, info)
95+
}
7396
runtime.KeepAlive(f)
7497
}
75-
if n >= 0 && len(names) == 0 {
76-
return names, io.EOF
98+
99+
if n > 0 && len(names)+len(dirents)+len(infos) == 0 {
100+
return nil, nil, nil, io.EOF
101+
}
102+
return names, dirents, infos, nil
103+
}
104+
105+
func dtToType(typ uint8) FileMode {
106+
switch typ {
107+
case syscall.DT_BLK:
108+
return ModeDevice
109+
case syscall.DT_CHR:
110+
return ModeDevice | ModeCharDevice
111+
case syscall.DT_DIR:
112+
return ModeDir
113+
case syscall.DT_FIFO:
114+
return ModeNamedPipe
115+
case syscall.DT_LNK:
116+
return ModeSymlink
117+
case syscall.DT_REG:
118+
return 0
119+
case syscall.DT_SOCK:
120+
return ModeSocket
77121
}
78-
return names, nil
122+
return ^FileMode(0)
79123
}
80124

81125
// Implemented in syscall/syscall_darwin.go.

src/os/dir_plan9.go

+25-17
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"syscall"
1010
)
1111

12-
func (file *File) readdir(n int) ([]FileInfo, error) {
12+
func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
1313
// If this file has no dirinfo, create one.
1414
if file.dirinfo == nil {
1515
file.dirinfo = new(dirInfo)
@@ -20,7 +20,6 @@ func (file *File) readdir(n int) ([]FileInfo, error) {
2020
size = 100
2121
n = -1
2222
}
23-
fi := make([]FileInfo, 0, size) // Empty with room to grow.
2423
for n != 0 {
2524
// Refill the buffer if necessary.
2625
if d.bufp >= d.nbuf {
@@ -33,41 +32,50 @@ func (file *File) readdir(n int) ([]FileInfo, error) {
3332
if err == io.EOF {
3433
break
3534
}
36-
return fi, &PathError{"readdir", file.name, err}
35+
return names, dirents, infos, &PathError{"readdir", file.name, err}
3736
}
3837
if nb < syscall.STATFIXLEN {
39-
return fi, &PathError{"readdir", file.name, syscall.ErrShortStat}
38+
return names, dirents, infos, &PathError{"readdir", file.name, syscall.ErrShortStat}
4039
}
4140
}
4241

4342
// Get a record from the buffer.
4443
b := d.buf[d.bufp:]
4544
m := int(uint16(b[0])|uint16(b[1])<<8) + 2
4645
if m < syscall.STATFIXLEN {
47-
return fi, &PathError{"readdir", file.name, syscall.ErrShortStat}
46+
return names, dirents, infos, &PathError{"readdir", file.name, syscall.ErrShortStat}
4847
}
4948

5049
dir, err := syscall.UnmarshalDir(b[:m])
5150
if err != nil {
52-
return fi, &PathError{"readdir", file.name, err}
51+
return names, dirents, infos, &PathError{"readdir", file.name, err}
5352
}
54-
fi = append(fi, fileInfoFromStat(dir))
5553

54+
if mode == readdirName {
55+
names = append(names, dir.Name)
56+
} else {
57+
f := fileInfoFromStat(dir)
58+
if mode == readdirDirEntry {
59+
dirents = append(dirents, dirEntry{f})
60+
} else {
61+
infos = append(infos, f)
62+
}
63+
}
5664
d.bufp += m
5765
n--
5866
}
5967

60-
if n >= 0 && len(fi) == 0 {
61-
return fi, io.EOF
68+
if n > 0 && len(names)+len(dirents)+len(infos) == 0 {
69+
return nil, nil, nil, io.EOF
6270
}
63-
return fi, nil
71+
return names, dirents, infos, nil
6472
}
6573

66-
func (file *File) readdirnames(n int) (names []string, err error) {
67-
fi, err := file.Readdir(n)
68-
names = make([]string, len(fi))
69-
for i := range fi {
70-
names[i] = fi[i].Name()
71-
}
72-
return
74+
type dirEntry struct {
75+
fs *fileStat
7376
}
77+
78+
func (de dirEntry) Name() string { return de.fs.Name() }
79+
func (de dirEntry) IsDir() bool { return de.fs.IsDir() }
80+
func (de dirEntry) Type() FileMode { return de.fs.Mode().Type() }
81+
func (de dirEntry) Info() (FileInfo, error) { return de.fs, nil }

0 commit comments

Comments
 (0)