Skip to content

Commit 3ea7a36

Browse files
committed
os: implement readdir for darwin and linux
readdir is disabled on linux for 386 and arm until syscall.seek is implemented there. windows is hard, so leaving that for later. File src/os/dir_other_go115.go can be deleted when we drop support for go 1.15.
1 parent 4681d14 commit 3ea7a36

21 files changed

+971
-67
lines changed

src/os/dir.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// +build go1.16
2+
3+
// Copyright 2016 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package os
8+
9+
import (
10+
"io/fs"
11+
"sort"
12+
)
13+
14+
type readdirMode int
15+
16+
const (
17+
readdirName readdirMode = iota
18+
readdirDirEntry
19+
readdirFileInfo
20+
)
21+
22+
// Readdir reads the contents of the directory associated with file and
23+
// returns a slice of up to n FileInfo values, as would be returned
24+
// by Lstat, in directory order. Subsequent calls on the same file will yield
25+
// further FileInfos.
26+
//
27+
// If n > 0, Readdir returns at most n FileInfo structures. In this case, if
28+
// Readdir returns an empty slice, it will return a non-nil error
29+
// explaining why. At the end of a directory, the error is io.EOF.
30+
//
31+
// If n <= 0, Readdir returns all the FileInfo from the directory in
32+
// a single slice. In this case, if Readdir succeeds (reads all
33+
// the way to the end of the directory), it returns the slice and a
34+
// nil error. If it encounters an error before the end of the
35+
// directory, Readdir returns the FileInfo read until that point
36+
// and a non-nil error.
37+
//
38+
// Most clients are better served by the more efficient ReadDir method.
39+
func (f *File) Readdir(n int) ([]FileInfo, error) {
40+
if f == nil {
41+
return nil, ErrInvalid
42+
}
43+
_, _, infos, err := f.readdir(n, readdirFileInfo)
44+
if infos == nil {
45+
// Readdir has historically always returned a non-nil empty slice, never nil,
46+
// even on error (except misuse with nil receiver above).
47+
// Keep it that way to avoid breaking overly sensitive callers.
48+
infos = []FileInfo{}
49+
}
50+
return infos, err
51+
}
52+
53+
// Readdirnames reads the contents of the directory associated with file
54+
// and returns a slice of up to n names of files in the directory,
55+
// in directory order. Subsequent calls on the same file will yield
56+
// further names.
57+
//
58+
// If n > 0, Readdirnames returns at most n names. In this case, if
59+
// Readdirnames returns an empty slice, it will return a non-nil error
60+
// explaining why. At the end of a directory, the error is io.EOF.
61+
//
62+
// If n <= 0, Readdirnames returns all the names from the directory in
63+
// a single slice. In this case, if Readdirnames succeeds (reads all
64+
// the way to the end of the directory), it returns the slice and a
65+
// nil error. If it encounters an error before the end of the
66+
// directory, Readdirnames returns the names read until that point and
67+
// a non-nil error.
68+
func (f *File) Readdirnames(n int) (names []string, err error) {
69+
if f == nil {
70+
return nil, ErrInvalid
71+
}
72+
names, _, _, err = f.readdir(n, readdirName)
73+
if names == nil {
74+
// Readdirnames has historically always returned a non-nil empty slice, never nil,
75+
// even on error (except misuse with nil receiver above).
76+
// Keep it that way to avoid breaking overly sensitive callers.
77+
names = []string{}
78+
}
79+
return names, err
80+
}
81+
82+
// A DirEntry is an entry read from a directory
83+
// (using the ReadDir function or a File's ReadDir method).
84+
type DirEntry = fs.DirEntry
85+
86+
// ReadDir reads the contents of the directory associated with the file f
87+
// and returns a slice of DirEntry values in directory order.
88+
// Subsequent calls on the same file will yield later DirEntry records in the directory.
89+
//
90+
// If n > 0, ReadDir returns at most n DirEntry records.
91+
// In this case, if ReadDir returns an empty slice, it will return an error explaining why.
92+
// At the end of a directory, the error is io.EOF.
93+
//
94+
// If n <= 0, ReadDir returns all the DirEntry records remaining in the directory.
95+
// When it succeeds, it returns a nil error (not io.EOF).
96+
func (f *File) ReadDir(n int) ([]DirEntry, error) {
97+
if f == nil {
98+
return nil, ErrInvalid
99+
}
100+
_, dirents, _, err := f.readdir(n, readdirDirEntry)
101+
if dirents == nil {
102+
// Match Readdir and Readdirnames: don't return nil slices.
103+
dirents = []DirEntry{}
104+
}
105+
return dirents, err
106+
}
107+
108+
// testingForceReadDirLstat forces ReadDir to call Lstat, for testing that code path.
109+
// This can be difficult to provoke on some Unix systems otherwise.
110+
var testingForceReadDirLstat bool
111+
112+
// ReadDir reads the named directory,
113+
// returning all its directory entries sorted by filename.
114+
// If an error occurs reading the directory,
115+
// ReadDir returns the entries it was able to read before the error,
116+
// along with the error.
117+
func ReadDir(name string) ([]DirEntry, error) {
118+
f, err := Open(name)
119+
if err != nil {
120+
return nil, err
121+
}
122+
defer f.Close()
123+
124+
dirs, err := f.ReadDir(-1)
125+
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
126+
return dirs, err
127+
}

src/os/dir_darwin.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
package os
6+
7+
import (
8+
"io"
9+
"runtime"
10+
"syscall"
11+
"unsafe"
12+
)
13+
14+
// Auxiliary information if the File describes a directory
15+
type dirInfo struct {
16+
dir uintptr // Pointer to DIR structure from dirent.h
17+
}
18+
19+
func (d *dirInfo) close() {
20+
if d.dir == 0 {
21+
return
22+
}
23+
closedir(d.dir)
24+
d.dir = 0
25+
}
26+
27+
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
28+
if f.dirinfo == nil {
29+
dir, call, errno := darwinOpenDir(syscallFd(f.handle.(unixFileHandle)))
30+
if errno != nil {
31+
return nil, nil, nil, &PathError{Op: call, Path: f.name, Err: errno}
32+
}
33+
f.dirinfo = &dirInfo{
34+
dir: dir,
35+
}
36+
}
37+
d := f.dirinfo
38+
39+
size := n
40+
if size <= 0 {
41+
size = 100
42+
n = -1
43+
}
44+
45+
var dirent syscall.Dirent
46+
var entptr *syscall.Dirent
47+
for len(names)+len(dirents)+len(infos) < size || n == -1 {
48+
if errno := readdir_r(d.dir, &dirent, &entptr); errno != 0 {
49+
if errno == syscall.EINTR {
50+
continue
51+
}
52+
return names, dirents, infos, &PathError{Op: "readdir", Path: f.name, Err: errno}
53+
}
54+
if entptr == nil { // EOF
55+
break
56+
}
57+
if dirent.Ino == 0 {
58+
continue
59+
}
60+
name := (*[len(syscall.Dirent{}.Name)]byte)(unsafe.Pointer(&dirent.Name))[:]
61+
for i, c := range name {
62+
if c == 0 {
63+
name = name[:i]
64+
break
65+
}
66+
}
67+
// Check for useless names before allocating a string.
68+
if string(name) == "." || string(name) == ".." {
69+
continue
70+
}
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+
}
96+
runtime.KeepAlive(f)
97+
}
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
121+
}
122+
return ^FileMode(0)
123+
}
124+
125+
// darwinOpenDir returns a pointer to a DIR structure suitable for
126+
// ReadDir. In case of an error, the name of the failed
127+
// syscall is returned along with a syscall.Errno.
128+
// Borrowed from upstream's internal/poll/fd_opendir_darwin.go
129+
func darwinOpenDir(fd syscallFd) (uintptr, string, error) {
130+
// fdopendir(3) takes control of the file descriptor,
131+
// so use a dup.
132+
fd2, err := syscall.Dup(fd)
133+
if err != nil {
134+
return 0, "dup", err
135+
}
136+
var dir uintptr
137+
for {
138+
dir, err = syscall.Fdopendir(fd2)
139+
if err != syscall.EINTR {
140+
break
141+
}
142+
}
143+
if err != nil {
144+
syscall.Close(fd2)
145+
return 0, "fdopendir", err
146+
}
147+
return dir, "", nil
148+
}
149+
150+
// Implemented in syscall/syscall_darwin.go.
151+
152+
//go:linkname closedir syscall.closedir
153+
func closedir(dir uintptr) (err error)
154+
155+
//go:linkname readdir_r syscall.readdir_r
156+
func readdir_r(dir uintptr, entry *syscall.Dirent, result **syscall.Dirent) (res syscall.Errno)

src/os/dir_other.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// +build go1.16,baremetal go1.16,js go1.16,wasi go1.16,windows
2+
3+
// Copyright 2009 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package os
8+
9+
import (
10+
"syscall"
11+
)
12+
13+
type dirInfo struct {
14+
}
15+
16+
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
17+
return nil, nil, nil, &PathError{Op: "readdir unimplemented", Err: syscall.ENOTDIR}
18+
}

src/os/dir_other_go115.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// +build !go1.16
2+
3+
// Copyright 2009 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package os
8+
9+
func (f *File) Readdirnames(n int) (names []string, err error) {
10+
return nil, ErrInvalid
11+
}

0 commit comments

Comments
 (0)