110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
// +build darwin freebsd linux netbsd openbsd
|
|
|
|
package godirwalk
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func readdirents(osDirname string, scratchBuffer []byte) (Dirents, error) {
|
|
dh, err := os.Open(osDirname)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "cannot Open")
|
|
}
|
|
|
|
var entries Dirents
|
|
|
|
fd := int(dh.Fd())
|
|
|
|
if len(scratchBuffer) < MinimumScratchBufferSize {
|
|
scratchBuffer = make([]byte, DefaultScratchBufferSize)
|
|
}
|
|
|
|
var de *syscall.Dirent
|
|
|
|
for {
|
|
n, err := syscall.ReadDirent(fd, scratchBuffer)
|
|
if err != nil {
|
|
_ = dh.Close() // ignore potential error returned by Close
|
|
return nil, errors.Wrap(err, "cannot ReadDirent")
|
|
}
|
|
if n <= 0 {
|
|
break // end of directory reached
|
|
}
|
|
// Loop over the bytes returned by reading the directory entries.
|
|
buf := scratchBuffer[:n]
|
|
for len(buf) > 0 {
|
|
de = (*syscall.Dirent)(unsafe.Pointer(&buf[0])) // point entry to first syscall.Dirent in buffer
|
|
buf = buf[de.Reclen:] // advance buffer
|
|
|
|
if inoFromDirent(de) == 0 {
|
|
continue // this item has been deleted, but not yet removed from directory
|
|
}
|
|
|
|
nameSlice := nameFromDirent(de)
|
|
namlen := len(nameSlice)
|
|
if (namlen == 0) || (namlen == 1 && nameSlice[0] == '.') || (namlen == 2 && nameSlice[0] == '.' && nameSlice[1] == '.') {
|
|
continue // skip unimportant entries
|
|
}
|
|
osChildname := string(nameSlice)
|
|
|
|
// Convert syscall constant, which is in purview of OS, to a
|
|
// constant defined by Go, assumed by this project to be stable.
|
|
var mode os.FileMode
|
|
switch de.Type {
|
|
case syscall.DT_REG:
|
|
// regular file
|
|
case syscall.DT_DIR:
|
|
mode = os.ModeDir
|
|
case syscall.DT_LNK:
|
|
mode = os.ModeSymlink
|
|
case syscall.DT_CHR:
|
|
mode = os.ModeDevice | os.ModeCharDevice
|
|
case syscall.DT_BLK:
|
|
mode = os.ModeDevice
|
|
case syscall.DT_FIFO:
|
|
mode = os.ModeNamedPipe
|
|
case syscall.DT_SOCK:
|
|
mode = os.ModeSocket
|
|
default:
|
|
// If syscall returned unknown type (e.g., DT_UNKNOWN, DT_WHT),
|
|
// then resolve actual mode by getting stat.
|
|
fi, err := os.Lstat(filepath.Join(osDirname, osChildname))
|
|
if err != nil {
|
|
_ = dh.Close() // ignore potential error returned by Close
|
|
return nil, errors.Wrap(err, "cannot Stat")
|
|
}
|
|
// We only care about the bits that identify the type of a file
|
|
// system node, and can ignore append, exclusive, temporary,
|
|
// setuid, setgid, permission bits, and sticky bits, which are
|
|
// coincident to the bits that declare type of the file system
|
|
// node.
|
|
mode = fi.Mode() & os.ModeType
|
|
}
|
|
|
|
entries = append(entries, &Dirent{name: osChildname, modeType: mode})
|
|
}
|
|
}
|
|
if err = dh.Close(); err != nil {
|
|
return nil, err
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
func readdirnames(osDirname string, scratchBuffer []byte) ([]string, error) {
|
|
des, err := readdirents(osDirname, scratchBuffer)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
names := make([]string, len(des))
|
|
for i, v := range des {
|
|
names[i] = v.name
|
|
}
|
|
return names, nil
|
|
}
|