Skip to content

Commit f82a567

Browse files
committed
os: implement readdir for darwin
1 parent b4fa658 commit f82a567

18 files changed

+819
-67
lines changed

src/os/dir.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2016 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+
type readdirMode int
8+
9+
const (
10+
readdirName readdirMode = iota
11+
readdirDirEntry
12+
readdirFileInfo
13+
)
14+
15+
// Readdir reads the contents of the directory associated with file and
16+
// returns a slice of up to n FileInfo values, as would be returned
17+
// by Lstat, in directory order. Subsequent calls on the same file will yield
18+
// further FileInfos.
19+
//
20+
// If n > 0, Readdir returns at most n FileInfo structures. In this case, if
21+
// Readdir returns an empty slice, it will return a non-nil error
22+
// explaining why. At the end of a directory, the error is io.EOF.
23+
//
24+
// If n <= 0, Readdir returns all the FileInfo from the directory in
25+
// a single slice. In this case, if Readdir succeeds (reads all
26+
// the way to the end of the directory), it returns the slice and a
27+
// nil error. If it encounters an error before the end of the
28+
// directory, Readdir returns the FileInfo read until that point
29+
// and a non-nil error.
30+
//
31+
// Most clients are better served by the more efficient ReadDir method.
32+
func (f *File) Readdir(n int) ([]FileInfo, error) {
33+
if f == nil {
34+
return nil, ErrInvalid
35+
}
36+
_, _, infos, err := f.readdir(n, readdirFileInfo)
37+
if infos == nil {
38+
// Readdir has historically always returned a non-nil empty slice, never nil,
39+
// even on error (except misuse with nil receiver above).
40+
// Keep it that way to avoid breaking overly sensitive callers.
41+
infos = []FileInfo{}
42+
}
43+
return infos, err
44+
}
45+
46+
// Readdirnames reads the contents of the directory associated with file
47+
// and returns a slice of up to n names of files in the directory,
48+
// in directory order. Subsequent calls on the same file will yield
49+
// further names.
50+
//
51+
// If n > 0, Readdirnames returns at most n names. In this case, if
52+
// Readdirnames returns an empty slice, it will return a non-nil error
53+
// explaining why. At the end of a directory, the error is io.EOF.
54+
//
55+
// If n <= 0, Readdirnames returns all the names from the directory in
56+
// a single slice. In this case, if Readdirnames succeeds (reads all
57+
// the way to the end of the directory), it returns the slice and a
58+
// nil error. If it encounters an error before the end of the
59+
// directory, Readdirnames returns the names read until that point and
60+
// a non-nil error.
61+
func (f *File) Readdirnames(n int) (names []string, err error) {
62+
if f == nil {
63+
return nil, ErrInvalid
64+
}
65+
names, _, _, err = f.readdir(n, readdirName)
66+
if names == nil {
67+
// Readdirnames has historically always returned a non-nil empty slice, never nil,
68+
// even on error (except misuse with nil receiver above).
69+
// Keep it that way to avoid breaking overly sensitive callers.
70+
names = []string{}
71+
}
72+
return names, err
73+
}

src/os/dir_darwin.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
typ := dtToType(dirent.Type)
75+
de, err := newUnixDirent(f.name, string(name), typ)
76+
if IsNotExist(err) {
77+
// File disappeared between readdir and stat.
78+
// Treat as if it didn't exist.
79+
continue
80+
}
81+
if err != nil {
82+
return nil, dirents, nil, err
83+
}
84+
dirents = append(dirents, de)
85+
} else {
86+
info, err := lstat(f.name + "/" + string(name))
87+
if IsNotExist(err) {
88+
// File disappeared between readdir + stat.
89+
// Treat as if it didn't exist.
90+
continue
91+
}
92+
if err != nil {
93+
return nil, nil, infos, err
94+
}
95+
infos = append(infos, info)
96+
}
97+
runtime.KeepAlive(f)
98+
}
99+
100+
if n > 0 && len(names)+len(dirents)+len(infos) == 0 {
101+
return nil, nil, nil, io.EOF
102+
}
103+
return names, dirents, infos, nil
104+
}
105+
106+
func dtToType(typ uint8) FileMode {
107+
switch typ {
108+
case syscall.DT_BLK:
109+
return ModeDevice
110+
case syscall.DT_CHR:
111+
return ModeDevice | ModeCharDevice
112+
case syscall.DT_DIR:
113+
return ModeDir
114+
case syscall.DT_FIFO:
115+
return ModeNamedPipe
116+
case syscall.DT_LNK:
117+
return ModeSymlink
118+
case syscall.DT_REG:
119+
return 0
120+
case syscall.DT_SOCK:
121+
return ModeSocket
122+
}
123+
return ^FileMode(0)
124+
}
125+
126+
// Implemented in syscall/syscall_darwin.go.
127+
128+
//go:linkname closedir syscall.closedir
129+
func closedir(dir uintptr) (err error)
130+
131+
//go:linkname readdir_r syscall.readdir_r
132+
func readdir_r(dir uintptr, entry *syscall.Dirent, result **syscall.Dirent) (res syscall.Errno)

src/os/dir_other.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// +build baremetal wasm,!wasi
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+
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
14+
return nil, nil, nil, &PathError{Op: "readdir unimplemented", Err: syscall.ENOTDIR}
15+
}

src/os/dir_unix.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// +build linux,!baremetal
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+
// Auxiliary information if the File describes a directory
14+
type dirInfo struct {
15+
// TODO
16+
}
17+
18+
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
19+
return nil, nil, nil, &PathError{Op: "readdir unimplemented", Err: syscall.ENOTDIR}
20+
}

src/os/dir_windows.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
"syscall"
9+
)
10+
11+
func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
12+
return nil, nil, nil, &PathError{Op: "readdir unimplemented", Path: file.name, Err: syscall.ENOTDIR}
13+
}

src/os/file.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ const (
2424
SEEK_END int = io.SeekEnd
2525
)
2626

27+
// lstat is overridden in tests.
28+
var lstat = Lstat
29+
2730
// Mkdir creates a directory. If the operation fails, it will return an error of
2831
// type *PathError.
2932
func Mkdir(path string, perm FileMode) error {
@@ -62,12 +65,6 @@ func RemoveAll(path string) error {
6265
return ErrNotImplemented
6366
}
6467

65-
// File represents an open file descriptor.
66-
type File struct {
67-
handle FileHandle
68-
name string
69-
}
70-
7168
// Name returns the name of the file with which it was opened.
7269
func (f *File) Name() string {
7370
return f.name
@@ -84,7 +81,7 @@ func OpenFile(name string, flag int, perm FileMode) (*File, error) {
8481
if err != nil {
8582
return nil, &PathError{"open", name, err}
8683
}
87-
return &File{name: name, handle: handle}, nil
84+
return NewFile(handle, name), nil
8885
}
8986

9087
// Open opens the file named for reading.
@@ -166,16 +163,6 @@ func (f *File) Close() (err error) {
166163
return
167164
}
168165

169-
// Readdir is a stub, not yet implemented
170-
func (f *File) Readdir(n int) ([]FileInfo, error) {
171-
return nil, &PathError{"readdir", f.name, ErrNotImplemented}
172-
}
173-
174-
// Readdirnames is a stub, not yet implemented
175-
func (f *File) Readdirnames(n int) (names []string, err error) {
176-
return nil, &PathError{"readdirnames", f.name, ErrNotImplemented}
177-
}
178-
179166
// Seek is a stub, not yet implemented
180167
func (f *File) Seek(offset int64, whence int) (ret int64, err error) {
181168
return 0, &PathError{"seek", f.name, ErrNotImplemented}

src/os/file_anyos.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ func init() {
2020
// Stdin, Stdout, and Stderr are open Files pointing to the standard input,
2121
// standard output, and standard error file descriptors.
2222
var (
23-
Stdin = &File{unixFileHandle(syscall.Stdin), "/dev/stdin"}
24-
Stdout = &File{unixFileHandle(syscall.Stdout), "/dev/stdout"}
25-
Stderr = &File{unixFileHandle(syscall.Stderr), "/dev/stderr"}
23+
Stdin = NewFile(unixFileHandle(syscall.Stdin), "/dev/stdin")
24+
Stdout = NewFile(unixFileHandle(syscall.Stdout), "/dev/stdout")
25+
Stderr = NewFile(unixFileHandle(syscall.Stderr), "/dev/stderr")
2626
)
2727

2828
const DevNull = "/dev/null"

src/os/file_go_116.go

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,14 @@ package os
55
import (
66
"io"
77
"io/fs"
8+
"sort"
89
)
910

1011
type (
11-
DirEntry = fs.DirEntry
1212
FileMode = fs.FileMode
1313
FileInfo = fs.FileInfo
1414
)
1515

16-
func (f *File) ReadDir(n int) ([]DirEntry, error) {
17-
return nil, &PathError{"ReadDir", f.name, ErrNotImplemented}
18-
}
19-
20-
func ReadDir(name string) ([]DirEntry, error) {
21-
return nil, &PathError{"ReadDir", name, ErrNotImplemented}
22-
}
23-
2416
// The followings are copied from Go 1.16 official implementation:
2517
// https://github.com/golang/go/blob/go1.16/src/os/file.go
2618

@@ -111,3 +103,52 @@ const (
111103

112104
ModePerm = fs.ModePerm // Unix permission bits, 0o777
113105
)
106+
107+
// TODO: move to dir.go once we drop go 1.15
108+
109+
// A DirEntry is an entry read from a directory
110+
// (using the ReadDir function or a File's ReadDir method).
111+
type DirEntry = fs.DirEntry
112+
113+
// ReadDir reads the contents of the directory associated with the file f
114+
// and returns a slice of DirEntry values in directory order.
115+
// Subsequent calls on the same file will yield later DirEntry records in the directory.
116+
//
117+
// If n > 0, ReadDir returns at most n DirEntry records.
118+
// In this case, if ReadDir returns an empty slice, it will return an error explaining why.
119+
// At the end of a directory, the error is io.EOF.
120+
//
121+
// If n <= 0, ReadDir returns all the DirEntry records remaining in the directory.
122+
// When it succeeds, it returns a nil error (not io.EOF).
123+
func (f *File) ReadDir(n int) ([]DirEntry, error) {
124+
if f == nil {
125+
return nil, ErrInvalid
126+
}
127+
_, dirents, _, err := f.readdir(n, readdirDirEntry)
128+
if dirents == nil {
129+
// Match Readdir and Readdirnames: don't return nil slices.
130+
dirents = []DirEntry{}
131+
}
132+
return dirents, err
133+
}
134+
135+
// testingForceReadDirLstat forces ReadDir to call Lstat, for testing that code path.
136+
// This can be difficult to provoke on some Unix systems otherwise.
137+
var testingForceReadDirLstat bool
138+
139+
// ReadDir reads the named directory,
140+
// returning all its directory entries sorted by filename.
141+
// If an error occurs reading the directory,
142+
// ReadDir returns the entries it was able to read before the error,
143+
// along with the error.
144+
func ReadDir(name string) ([]DirEntry, error) {
145+
f, err := Open(name)
146+
if err != nil {
147+
return nil, err
148+
}
149+
defer f.Close()
150+
151+
dirs, err := f.ReadDir(-1)
152+
sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
153+
return dirs, err
154+
}

0 commit comments

Comments
 (0)