Skip to content

Commit 86de1fb

Browse files
committed
os: implement readdir for linux
1 parent c4c9d74 commit 86de1fb

File tree

9 files changed

+460
-181
lines changed

9 files changed

+460
-181
lines changed

src/os/dir_other.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// +build baremetal wasm,!wasi
1+
// +build baremetal wasi
22

33
// Copyright 2009 The Go Authors. All rights reserved.
44
// Use of this source code is governed by a BSD-style
@@ -10,6 +10,9 @@ import (
1010
"syscall"
1111
)
1212

13+
type dirInfo struct {
14+
}
15+
1316
func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) {
1417
return nil, nil, nil, &PathError{Op: "readdir unimplemented", Err: syscall.ENOTDIR}
1518
}

src/os/dir_unix.go

Lines changed: 178 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// +build linux,!baremetal
1+
// +build linux,!baremetal,!wasi
22

33
// Copyright 2009 The Go Authors. All rights reserved.
44
// Use of this source code is governed by a BSD-style
@@ -7,14 +7,189 @@
77
package os
88

99
import (
10+
"io"
11+
"sync"
1012
"syscall"
13+
"unsafe"
1114
)
1215

1316
// Auxiliary information if the File describes a directory
1417
type dirInfo struct {
15-
// TODO
18+
buf *[]byte // buffer for directory I/O
19+
nbuf int // length of buf; return value from Getdirentries
20+
bufp int // location of next record in buf.
21+
}
22+
23+
const (
24+
// More than 5760 to work around https://golang.org/issue/24015.
25+
blockSize = 8192
26+
)
27+
28+
var dirBufPool = sync.Pool{
29+
New: func() interface{} {
30+
// The buffer must be at least a block long.
31+
buf := make([]byte, blockSize)
32+
return &buf
33+
},
34+
}
35+
36+
func (d *dirInfo) close() {
37+
if d.buf != nil {
38+
dirBufPool.Put(d.buf)
39+
d.buf = nil
40+
}
1641
}
1742

1843
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}
44+
// If this file has no dirinfo, create one.
45+
if f.dirinfo == nil {
46+
f.dirinfo = new(dirInfo)
47+
f.dirinfo.buf = dirBufPool.Get().(*[]byte)
48+
}
49+
d := f.dirinfo
50+
51+
// Change the meaning of n for the implementation below.
52+
//
53+
// The n above was for the public interface of "if n <= 0,
54+
// Readdir returns all the FileInfo from the directory in a
55+
// single slice".
56+
//
57+
// But below, we use only negative to mean looping until the
58+
// end and positive to mean bounded, with positive
59+
// terminating at 0.
60+
if n == 0 {
61+
n = -1
62+
}
63+
64+
for n != 0 {
65+
// Refill the buffer if necessary
66+
if d.bufp >= d.nbuf {
67+
d.bufp = 0
68+
var errno error
69+
d.nbuf, errno = syscall.ReadDirent(syscallFd(f.handle.(unixFileHandle)), *d.buf)
70+
if d.nbuf < 0 {
71+
errno = handleSyscallError(errno)
72+
}
73+
if errno != nil {
74+
return names, dirents, infos, &PathError{Op: "readdirent", Path: f.name, Err: errno}
75+
}
76+
if d.nbuf <= 0 {
77+
break // EOF
78+
}
79+
}
80+
81+
// Drain the buffer
82+
buf := (*d.buf)[d.bufp:d.nbuf]
83+
reclen, ok := direntReclen(buf)
84+
if !ok || reclen > uint64(len(buf)) {
85+
break
86+
}
87+
rec := buf[:reclen]
88+
d.bufp += int(reclen)
89+
ino, ok := direntIno(rec)
90+
if !ok {
91+
break
92+
}
93+
if ino == 0 {
94+
continue
95+
}
96+
const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name))
97+
namlen, ok := direntNamlen(rec)
98+
if !ok || namoff+namlen > uint64(len(rec)) {
99+
break
100+
}
101+
name := rec[namoff : namoff+namlen]
102+
for i, c := range name {
103+
if c == 0 {
104+
name = name[:i]
105+
break
106+
}
107+
}
108+
// Check for useless names before allocating a string.
109+
if string(name) == "." || string(name) == ".." {
110+
continue
111+
}
112+
if n > 0 { // see 'n == 0' comment above
113+
n--
114+
}
115+
if mode == readdirName {
116+
names = append(names, string(name))
117+
} else if mode == readdirDirEntry {
118+
de, err := newUnixDirent(f.name, string(name), direntType(rec))
119+
if IsNotExist(err) {
120+
// File disappeared between readdir and stat.
121+
// Treat as if it didn't exist.
122+
continue
123+
}
124+
if err != nil {
125+
return nil, dirents, nil, err
126+
}
127+
dirents = append(dirents, de)
128+
} else {
129+
info, err := lstat(f.name + "/" + string(name))
130+
if IsNotExist(err) {
131+
// File disappeared between readdir + stat.
132+
// Treat as if it didn't exist.
133+
continue
134+
}
135+
if err != nil {
136+
return nil, nil, infos, err
137+
}
138+
infos = append(infos, info)
139+
}
140+
}
141+
142+
if n > 0 && len(names)+len(dirents)+len(infos) == 0 {
143+
return nil, nil, nil, io.EOF
144+
}
145+
return names, dirents, infos, nil
146+
}
147+
148+
// readInt returns the size-bytes unsigned integer in native byte order at offset off.
149+
func readInt(b []byte, off, size uintptr) (u uint64, ok bool) {
150+
if len(b) < int(off+size) {
151+
return 0, false
152+
}
153+
if isBigEndian {
154+
return readIntBE(b[off:], size), true
155+
}
156+
return readIntLE(b[off:], size), true
157+
}
158+
159+
func readIntBE(b []byte, size uintptr) uint64 {
160+
switch size {
161+
case 1:
162+
return uint64(b[0])
163+
case 2:
164+
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
165+
return uint64(b[1]) | uint64(b[0])<<8
166+
case 4:
167+
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
168+
return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24
169+
case 8:
170+
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
171+
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
172+
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
173+
default:
174+
panic("syscall: readInt with unsupported size")
175+
}
176+
}
177+
178+
func readIntLE(b []byte, size uintptr) uint64 {
179+
switch size {
180+
case 1:
181+
return uint64(b[0])
182+
case 2:
183+
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
184+
return uint64(b[0]) | uint64(b[1])<<8
185+
case 4:
186+
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
187+
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24
188+
case 8:
189+
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
190+
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
191+
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
192+
default:
193+
panic("syscall: readInt with unsupported size")
194+
}
20195
}

src/os/dirent_linux.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// +build !baremetal,!js,!wasi
2+
3+
// Copyright 2020 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+
"unsafe"
12+
)
13+
14+
func direntIno(buf []byte) (uint64, bool) {
15+
return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino))
16+
}
17+
18+
func direntReclen(buf []byte) (uint64, bool) {
19+
return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen))
20+
}
21+
22+
func direntNamlen(buf []byte) (uint64, bool) {
23+
reclen, ok := direntReclen(buf)
24+
if !ok {
25+
return 0, false
26+
}
27+
return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true
28+
}
29+
30+
func direntType(buf []byte) FileMode {
31+
off := unsafe.Offsetof(syscall.Dirent{}.Type)
32+
if off >= uintptr(len(buf)) {
33+
return ^FileMode(0) // unknown
34+
}
35+
typ := buf[off]
36+
switch typ {
37+
case syscall.DT_BLK:
38+
return ModeDevice
39+
case syscall.DT_CHR:
40+
return ModeDevice | ModeCharDevice
41+
case syscall.DT_DIR:
42+
return ModeDir
43+
case syscall.DT_FIFO:
44+
return ModeNamedPipe
45+
case syscall.DT_LNK:
46+
return ModeSymlink
47+
case syscall.DT_REG:
48+
return 0
49+
case syscall.DT_SOCK:
50+
return ModeSocket
51+
}
52+
return ^FileMode(0) // unknown
53+
}

src/os/endian_big.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
// +build ppc64 mips mips64
6+
7+
package os
8+
9+
const isBigEndian = true

src/os/endian_little.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
// +build 386 amd64 arm arm64 ppc64le mips64le mipsle riscv64 wasm
6+
7+
package os
8+
9+
const isBigEndian = false

0 commit comments

Comments
 (0)