| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package dirwalk
- import (
- "fmt"
- "io/fs"
- "os"
- "path/filepath"
- "sync"
- "syscall"
- "unsafe"
- "go4.org/mem"
- "golang.org/x/sys/unix"
- )
- func init() {
- osWalkShallow = linuxWalkShallow
- }
- var dirEntPool = &sync.Pool{New: func() any { return new(linuxDirEnt) }}
- func linuxWalkShallow(dirName mem.RO, fn WalkFunc) error {
- const blockSize = 8 << 10
- buf := make([]byte, blockSize) // stack-allocated; doesn't escape
- nameb := mem.Append(buf[:0], dirName)
- nameb = append(nameb, 0)
- fd, err := sysOpen(nameb)
- if err != nil {
- return err
- }
- defer syscall.Close(fd)
- bufp := 0 // starting read position in buf
- nbuf := 0 // end valid data in buf
- de := dirEntPool.Get().(*linuxDirEnt)
- defer de.cleanAndPutInPool()
- de.root = dirName
- for {
- if bufp >= nbuf {
- bufp = 0
- nbuf, err = readDirent(fd, buf)
- if err != nil {
- return err
- }
- if nbuf <= 0 {
- return nil
- }
- }
- consumed, name := parseDirEnt(&de.d, buf[bufp:nbuf])
- bufp += consumed
- if len(name) == 0 || string(name) == "." || string(name) == ".." {
- continue
- }
- de.name = mem.B(name)
- if err := fn(de.name, de); err != nil {
- return err
- }
- }
- }
- type linuxDirEnt struct {
- root mem.RO
- d syscall.Dirent
- name mem.RO
- }
- func (de *linuxDirEnt) cleanAndPutInPool() {
- de.root = mem.RO{}
- de.name = mem.RO{}
- dirEntPool.Put(de)
- }
- func (de *linuxDirEnt) Name() string { return de.name.StringCopy() }
- func (de *linuxDirEnt) Info() (fs.FileInfo, error) {
- return os.Lstat(filepath.Join(de.root.StringCopy(), de.name.StringCopy()))
- }
- func (de *linuxDirEnt) IsDir() bool {
- return de.d.Type == syscall.DT_DIR
- }
- func (de *linuxDirEnt) Type() fs.FileMode {
- switch de.d.Type {
- case syscall.DT_BLK:
- return fs.ModeDevice // shrug
- case syscall.DT_CHR:
- return fs.ModeCharDevice
- case syscall.DT_DIR:
- return fs.ModeDir
- case syscall.DT_FIFO:
- return fs.ModeNamedPipe
- case syscall.DT_LNK:
- return fs.ModeSymlink
- case syscall.DT_REG:
- return 0
- case syscall.DT_SOCK:
- return fs.ModeSocket
- default:
- return fs.ModeIrregular // shrug
- }
- }
- func direntNamlen(dirent *syscall.Dirent) int {
- const fixedHdr = uint16(unsafe.Offsetof(syscall.Dirent{}.Name))
- limit := dirent.Reclen - fixedHdr
- const dirNameLen = 256 // sizeof syscall.Dirent.Name
- if limit > dirNameLen {
- limit = dirNameLen
- }
- for i := uint16(0); i < limit; i++ {
- if dirent.Name[i] == 0 {
- return int(i)
- }
- }
- panic("failed to find terminating 0 byte in dirent")
- }
- func parseDirEnt(dirent *syscall.Dirent, buf []byte) (consumed int, name []byte) {
- // golang.org/issue/37269
- copy(unsafe.Slice((*byte)(unsafe.Pointer(dirent)), unsafe.Sizeof(syscall.Dirent{})), buf)
- if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v {
- panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v))
- }
- if len(buf) < int(dirent.Reclen) {
- panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen))
- }
- consumed = int(dirent.Reclen)
- if dirent.Ino == 0 { // File absent in directory.
- return
- }
- name = unsafe.Slice((*byte)(unsafe.Pointer(&dirent.Name[0])), direntNamlen(dirent))
- return
- }
- func sysOpen(name []byte) (fd int, err error) {
- if len(name) == 0 || name[len(name)-1] != 0 {
- return 0, syscall.EINVAL
- }
- var dirfd int = unix.AT_FDCWD
- for {
- r0, _, e1 := syscall.Syscall(unix.SYS_OPENAT, uintptr(dirfd),
- uintptr(unsafe.Pointer(&name[0])), 0)
- if e1 == 0 {
- return int(r0), nil
- }
- if e1 == syscall.EINTR {
- // Since https://golang.org/doc/go1.14#runtime we
- // need to loop on EINTR on more places.
- continue
- }
- return 0, syscall.Errno(e1)
- }
- }
- func readDirent(fd int, buf []byte) (n int, err error) {
- for {
- nbuf, err := syscall.ReadDirent(fd, buf)
- if err != syscall.EINTR {
- return nbuf, err
- }
- }
- }
|