1// 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}
220