This repository has been archived by the owner on Jun 25, 2022. It is now read-only.
/
scandir_unix.go
140 lines (123 loc) · 4.02 KB
/
scandir_unix.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// +build !windows
package godirwalk
import (
"io"
"os"
"syscall"
"unsafe"
)
// MinimumScratchBufferSize specifies the minimum size of the scratch buffer
// that Walk, ReadDirents, ReadDirnames, and Scandir will use when reading file
// entries from the operating system. It is initialized to the result from
// calling `os.Getpagesize()` during program startup.
var MinimumScratchBufferSize = os.Getpagesize()
// Scanner is an iterator to enumerate the contents of a directory.
type Scanner struct {
scratchBuffer []byte // read directory bytes from file system into this buffer
workBuffer []byte // points into scratchBuffer, from which we chunk out directory entries
osDirname string
childName string
err error // err is the error associated with scanning directory
statErr error // statErr is any error return while attempting to stat an entry
dh *os.File // used to close directory after done reading
de *Dirent // most recently decoded directory entry
sde *syscall.Dirent
fd int // file descriptor used to read entries from directory
}
// NewScanner returns a new directory Scanner.
func NewScanner(osDirname string) (*Scanner, error) {
dh, err := os.Open(osDirname)
if err != nil {
return nil, err
}
scanner := &Scanner{
scratchBuffer: make([]byte, MinimumScratchBufferSize),
osDirname: osDirname,
dh: dh,
fd: int(dh.Fd()),
}
return scanner, nil
}
// Dirent returns the current directory entry while scanning a directory.
func (s *Scanner) Dirent() (*Dirent, error) {
if s.de == nil {
s.de = &Dirent{name: s.childName}
s.de.modeType, s.statErr = modeTypeFromDirent(s.sde, s.osDirname, s.childName)
}
return s.de, s.statErr
}
// done is called when directory scanner unable to continue, with either the
// triggering error, or nil when there are simply no more entries to read from
// the directory.
func (s *Scanner) done(err error) {
if s.dh == nil {
return
}
cerr := s.dh.Close()
s.dh = nil
if err == nil {
s.err = cerr
} else {
s.err = err
}
s.osDirname, s.childName = "", ""
s.scratchBuffer, s.workBuffer = nil, nil
s.statErr, s.de, s.sde = nil, nil, nil
s.fd = 0
}
// Err returns the error associated with scanning a directory.
func (s *Scanner) Err() error {
s.done(s.err)
if s.err == io.EOF {
return nil
}
return s.err
}
// Name returns the name of the current directory entry while scanning a
// directory.
func (s *Scanner) Name() string { return s.childName }
// Scan potentially reads and then decodes the next directory entry from the
// file system.
//
// When it returns false, this releases resources used by the Scanner then
// returns any error associated with closing the file system directory resource.
func (s *Scanner) Scan() bool {
if s.err != nil {
return false
}
for {
// When the work buffer has nothing remaining to decode, we need to load
// more data from disk.
if len(s.workBuffer) == 0 {
n, err := syscall.ReadDirent(s.fd, s.scratchBuffer)
if err != nil {
s.done(err)
return false
}
if n <= 0 { // end of directory
s.done(io.EOF)
return false
}
s.workBuffer = s.scratchBuffer[:n] // trim work buffer to number of bytes read
}
// Loop until we have a usable file system entry, or we run out of data
// in the work buffer.
for len(s.workBuffer) > 0 {
s.sde = (*syscall.Dirent)(unsafe.Pointer(&s.workBuffer[0])) // point entry to first syscall.Dirent in buffer
s.workBuffer = s.workBuffer[s.sde.Reclen:] // advance buffer for next iteration through loop
if inoFromDirent(s.sde) == 0 {
continue // inode set to 0 indicates an entry that was marked as deleted
}
nameSlice := nameFromDirent(s.sde)
namlen := len(nameSlice)
if namlen == 0 || (nameSlice[0] == '.' && (namlen == 1 || (namlen == 2 && nameSlice[1] == '.'))) {
continue
}
s.de = nil
s.childName = string(nameSlice)
return true
}
// No more data in the work buffer, so loop around in the outside loop
// to fetch more data.
}
}