qrz/vendor/github.com/karrick/godirwalk/scandir_unix.go

162 lines
4.8 KiB
Go
Raw Normal View History

// +build !windows
package godirwalk
import (
"os"
"syscall"
"unsafe"
)
// 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 that lazily enumerates the
// contents of a single directory.
//
// scanner, err := godirwalk.NewScanner(dirname)
// if err != nil {
// fatal("cannot scan directory: %s", err)
// }
//
// for scanner.Scan() {
// dirent, err := scanner.Dirent()
// if err != nil {
// warning("cannot get dirent: %s", err)
// continue
// }
// name := dirent.Name()
// if name == "break" {
// break
// }
// if name == "continue" {
// continue
// }
// fmt.Printf("%v %v\n", dirent.ModeType(), dirent.Name())
// }
// if err := scanner.Err(); err != nil {
// fatal("cannot scan directory: %s", err)
// }
func NewScanner(osDirname string) (*Scanner, error) {
return NewScannerWithScratchBuffer(osDirname, nil)
}
// NewScannerWithScratchBuffer returns a new directory Scanner that lazily
// enumerates the contents of a single directory. On platforms other than
// Windows it uses the provided scratch buffer to read from the file system. On
// Windows the scratch buffer is ignored.
func NewScannerWithScratchBuffer(osDirname string, scratchBuffer []byte) (*Scanner, error) {
dh, err := os.Open(osDirname)
if err != nil {
return nil, err
}
if len(scratchBuffer) < MinimumScratchBufferSize {
scratchBuffer = newScratchBuffer()
}
scanner := &Scanner{
scratchBuffer: scratchBuffer,
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, path: s.osDirname}
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
}
if cerr := s.dh.Close(); err == nil {
s.err = cerr
}
s.osDirname, s.childName = "", ""
s.scratchBuffer, s.workBuffer = nil, nil
s.dh, s.de, s.sde, s.statErr = nil, nil, nil, nil
s.fd = 0
}
// Err returns any error associated with scanning a directory. It is normal to
// call Err after Scan returns false, even though they both ensure Scanner
// resources are released. Do not call until done scanning a directory.
func (s *Scanner) Err() error {
s.done(nil)
return s.err
}
// Name returns the base 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.dh == nil {
return false
}
s.de = nil
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)
// n, err := unix.ReadDirent(s.fd, s.scratchBuffer)
if err != nil {
s.done(err)
return false
}
if n <= 0 { // end of directory: normal exit
s.done(nil)
return false
}
s.workBuffer = s.scratchBuffer[:n] // trim work buffer to number of bytes read
}
s.sde = (*syscall.Dirent)(unsafe.Pointer(&s.workBuffer[0])) // point entry to first syscall.Dirent in buffer
s.workBuffer = s.workBuffer[reclen(s.sde):] // 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)
nameLength := len(nameSlice)
if nameLength == 0 || (nameSlice[0] == '.' && (nameLength == 1 || (nameLength == 2 && nameSlice[1] == '.'))) {
continue
}
s.childName = string(nameSlice)
return true
}
}