// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build aix || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris package fastwalk import ( "os" "syscall" "github.com/charlievieth/fastwalk/internal/dirent" ) // More than 5760 to work around https://golang.org/issue/24015. const blockSize = 8192 // unknownFileMode is a sentinel (and bogus) os.FileMode // value used to represent a syscall.DT_UNKNOWN Dirent.Type. const unknownFileMode os.FileMode = ^os.FileMode(0) func (w *walker) readDir(dirName string) error { fd, err := open(dirName, 0, 0) if err != nil { return &os.PathError{Op: "open", Path: dirName, Err: err} } defer syscall.Close(fd) var p *[]*unixDirent if w.sortMode != SortNone { p = direntSlicePool.Get().(*[]*unixDirent) } defer putDirentSlice(p) // The buffer must be at least a block long. buf := make([]byte, blockSize) // stack-allocated; doesn't escape bufp := 0 // starting read position in buf nbuf := 0 // end valid data in buf skipFiles := false for { if bufp >= nbuf { bufp = 0 nbuf, err = readDirent(fd, buf) if err != nil { return os.NewSyscallError("readdirent", err) } if nbuf <= 0 { break // exit loop } } consumed, name, typ := dirent.Parse(buf[bufp:nbuf]) bufp += consumed if name == "" || name == "." || name == ".." { continue } // Fallback for filesystems (like old XFS) that don't // support Dirent.Type and have DT_UNKNOWN (0) there // instead. if typ == unknownFileMode { fi, err := os.Lstat(dirName + "/" + name) if err != nil { // It got deleted in the meantime. if os.IsNotExist(err) { continue } return err } typ = fi.Mode() & os.ModeType } if skipFiles && typ.IsRegular() { continue } de := newUnixDirent(dirName, name, typ) if w.sortMode == SortNone { if err := w.onDirEnt(dirName, name, de); err != nil { if err == ErrSkipFiles { skipFiles = true continue } return err } } else { *p = append(*p, de) } } if w.sortMode == SortNone { return nil } dents := *p sortDirents(w.sortMode, dents) for _, d := range dents { d := d if skipFiles && d.typ.IsRegular() { continue } if err := w.onDirEnt(dirName, d.Name(), d); err != nil { if err != ErrSkipFiles { return err } skipFiles = true } } return nil } // According to https://golang.org/doc/go1.14#runtime // A consequence of the implementation of preemption is that on Unix systems, including Linux and macOS // systems, programs built with Go 1.14 will receive more signals than programs built with earlier releases. // // This causes syscall.Open and syscall.ReadDirent sometimes fail with EINTR errors. // We need to retry in this case. func open(path string, mode int, perm uint32) (fd int, err error) { for { fd, err := syscall.Open(path, mode, perm) if err != syscall.EINTR { return fd, err } } } func readDirent(fd int, buf []byte) (n int, err error) { for { nbuf, err := syscall.ReadDirent(fd, buf) if err != syscall.EINTR { return nbuf, err } } }