blob: f6d7813e1230ea0d1b7faf20b170fe0fcd52e2bf [file] [log] [blame]
Colin Crossae7fd6b2017-12-21 16:44:26 -08001// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package fs
16
17// This is based on the readdir implementation from Go 1.9:
18// Copyright 2009 The Go Authors. All rights reserved.
19// Use of this source code is governed by a BSD-style
20// license that can be found in the LICENSE file.
21
22import (
23 "os"
24 "syscall"
25 "unsafe"
26)
27
28const (
29 blockSize = 4096
30)
31
32func readdir(path string) ([]DirEntryInfo, error) {
33 f, err := os.Open(path)
34 defer f.Close()
35
36 if err != nil {
37 return nil, err
38 }
39 // This implicitly switches the fd to non-blocking mode, which is less efficient than what
40 // file.ReadDir does since it will keep a thread blocked and not just a goroutine.
41 fd := int(f.Fd())
42
43 buf := make([]byte, blockSize)
44 entries := make([]*dirEntryInfo, 0, 100)
45
46 for {
47 n, errno := syscall.ReadDirent(fd, buf)
48 if errno != nil {
49 err = os.NewSyscallError("readdirent", errno)
50 break
51 }
52 if n <= 0 {
53 break // EOF
54 }
55
56 entries = parseDirent(buf[:n], entries)
57 }
58
59 ret := make([]DirEntryInfo, 0, len(entries))
60
61 for _, entry := range entries {
62 if !entry.modeExists {
63 mode, lerr := lstatFileMode(path + "/" + entry.name)
64 if os.IsNotExist(lerr) {
65 // File disappeared between readdir + stat.
66 // Just treat it as if it didn't exist.
67 continue
68 }
69 if lerr != nil {
70 return ret, lerr
71 }
72 entry.mode = mode
73 entry.modeExists = true
74 }
75 ret = append(ret, entry)
76 }
77
78 return ret, err
79}
80
81func parseDirent(buf []byte, entries []*dirEntryInfo) []*dirEntryInfo {
82 for len(buf) > 0 {
83 reclen, ok := direntReclen(buf)
84 if !ok || reclen > uint64(len(buf)) {
85 return entries
86 }
87 rec := buf[:reclen]
88 buf = buf[reclen:]
89 ino, ok := direntIno(rec)
90 if !ok {
91 break
92 }
93 if ino == 0 { // File absent in directory.
94 continue
95 }
96 typ, ok := direntType(rec)
97 if !ok {
98 break
99 }
100 const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name))
101 namlen, ok := direntNamlen(rec)
102 if !ok || namoff+namlen > uint64(len(rec)) {
103 break
104 }
105 name := rec[namoff : namoff+namlen]
106
107 for i, c := range name {
108 if c == 0 {
109 name = name[:i]
110 break
111 }
112 }
113 // Check for useless names before allocating a string.
114 if string(name) == "." || string(name) == ".." {
115 continue
116 }
117
118 mode, modeExists := direntTypeToFileMode(typ)
119
120 entries = append(entries, &dirEntryInfo{string(name), mode, modeExists})
121 }
122 return entries
123}
124
125func direntIno(buf []byte) (uint64, bool) {
126 return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino))
127}
128
129func direntType(buf []byte) (uint64, bool) {
130 return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Type), unsafe.Sizeof(syscall.Dirent{}.Type))
131}
132
133func direntReclen(buf []byte) (uint64, bool) {
134 return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen))
135}
136
137func direntNamlen(buf []byte) (uint64, bool) {
138 reclen, ok := direntReclen(buf)
139 if !ok {
140 return 0, false
141 }
142 return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true
143}
144
145// readInt returns the size-bytes unsigned integer in native byte order at offset off.
146func readInt(b []byte, off, size uintptr) (u uint64, ok bool) {
147 if len(b) < int(off+size) {
148 return 0, false
149 }
150 return readIntLE(b[off:], size), true
151}
152
153func readIntLE(b []byte, size uintptr) uint64 {
154 switch size {
155 case 1:
156 return uint64(b[0])
157 case 2:
158 _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
159 return uint64(b[0]) | uint64(b[1])<<8
160 case 4:
161 _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
162 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24
163 case 8:
164 _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
165 return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
166 uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
167 default:
168 panic("syscall: readInt with unsupported size")
169 }
170}
171
172// If the directory entry doesn't specify the type, fall back to using lstat to get the type.
173func lstatFileMode(name string) (os.FileMode, error) {
174 stat, err := os.Lstat(name)
175 if err != nil {
176 return 0, err
177 }
178
179 return stat.Mode() & (os.ModeType | os.ModeCharDevice), nil
180}
181
182// from Linux and Darwin dirent.h
183const (
184 DT_UNKNOWN = 0
185 DT_FIFO = 1
186 DT_CHR = 2
187 DT_DIR = 4
188 DT_BLK = 6
189 DT_REG = 8
190 DT_LNK = 10
191 DT_SOCK = 12
192)
193
194func direntTypeToFileMode(typ uint64) (os.FileMode, bool) {
195 exists := true
196 var mode os.FileMode
197 switch typ {
198 case DT_UNKNOWN:
199 exists = false
200 case DT_FIFO:
201 mode = os.ModeNamedPipe
202 case DT_CHR:
203 mode = os.ModeDevice | os.ModeCharDevice
204 case DT_DIR:
205 mode = os.ModeDir
206 case DT_BLK:
207 mode = os.ModeDevice
208 case DT_REG:
209 mode = 0
210 case DT_LNK:
211 mode = os.ModeSymlink
212 case DT_SOCK:
213 mode = os.ModeSocket
214 default:
215 exists = false
216 }
217
218 return mode, exists
219}