fastwalk_unix.go

  1// Copyright 2016 The Go Authors. All rights reserved.
  2// Use of this source code is governed by a BSD-style
  3// license that can be found in the LICENSE file.
  4
  5//go:build aix || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris
  6
  7package fastwalk
  8
  9import (
 10	"os"
 11	"syscall"
 12
 13	"github.com/charlievieth/fastwalk/internal/dirent"
 14)
 15
 16// More than 5760 to work around https://golang.org/issue/24015.
 17const blockSize = 8192
 18
 19// unknownFileMode is a sentinel (and bogus) os.FileMode
 20// value used to represent a syscall.DT_UNKNOWN Dirent.Type.
 21const unknownFileMode os.FileMode = ^os.FileMode(0)
 22
 23func (w *walker) readDir(dirName string, depth int) error {
 24	fd, err := open(dirName, 0, 0)
 25	if err != nil {
 26		return &os.PathError{Op: "open", Path: dirName, Err: err}
 27	}
 28	defer syscall.Close(fd)
 29
 30	var p *[]*unixDirent
 31	if w.sortMode != SortNone {
 32		p = direntSlicePool.Get().(*[]*unixDirent)
 33	}
 34	defer putDirentSlice(p)
 35
 36	// The buffer must be at least a block long.
 37	buf := make([]byte, blockSize) // stack-allocated; doesn't escape
 38	bufp := 0                      // starting read position in buf
 39	nbuf := 0                      // end valid data in buf
 40	skipFiles := false
 41	for {
 42		if bufp >= nbuf {
 43			bufp = 0
 44			nbuf, err = readDirent(fd, buf)
 45			if err != nil {
 46				return os.NewSyscallError("readdirent", err)
 47			}
 48			if nbuf <= 0 {
 49				break // exit loop
 50			}
 51		}
 52		consumed, name, typ := dirent.Parse(buf[bufp:nbuf])
 53		bufp += consumed
 54
 55		if name == "" || name == "." || name == ".." {
 56			continue
 57		}
 58		// Fallback for filesystems (like old XFS) that don't
 59		// support Dirent.Type and have DT_UNKNOWN (0) there
 60		// instead.
 61		if typ == unknownFileMode {
 62			fi, err := os.Lstat(dirName + "/" + name)
 63			if err != nil {
 64				// It got deleted in the meantime.
 65				if os.IsNotExist(err) {
 66					continue
 67				}
 68				return err
 69			}
 70			typ = fi.Mode() & os.ModeType
 71		}
 72		if skipFiles && typ.IsRegular() {
 73			continue
 74		}
 75		de := newUnixDirent(dirName, name, typ, depth)
 76		if w.sortMode == SortNone {
 77			if err := w.onDirEnt(dirName, name, de); err != nil {
 78				if err == ErrSkipFiles {
 79					skipFiles = true
 80					continue
 81				}
 82				return err
 83			}
 84		} else {
 85			*p = append(*p, de)
 86		}
 87	}
 88	if w.sortMode == SortNone {
 89		return nil
 90	}
 91
 92	dents := *p
 93	sortDirents(w.sortMode, dents)
 94	for _, d := range dents {
 95		d := d
 96		if skipFiles && d.typ.IsRegular() {
 97			continue
 98		}
 99		if err := w.onDirEnt(dirName, d.Name(), d); err != nil {
100			if err != ErrSkipFiles {
101				return err
102			}
103			skipFiles = true
104		}
105	}
106	return nil
107}
108
109// According to https://golang.org/doc/go1.14#runtime
110// A consequence of the implementation of preemption is that on Unix systems, including Linux and macOS
111// systems, programs built with Go 1.14 will receive more signals than programs built with earlier releases.
112//
113// This causes syscall.Open and syscall.ReadDirent sometimes fail with EINTR errors.
114// We need to retry in this case.
115func open(path string, mode int, perm uint32) (fd int, err error) {
116	for {
117		fd, err := syscall.Open(path, mode, perm)
118		if err != syscall.EINTR {
119			return fd, err
120		}
121	}
122}
123
124func readDirent(fd int, buf []byte) (n int, err error) {
125	for {
126		nbuf, err := syscall.ReadDirent(fd, buf)
127		if err != syscall.EINTR {
128			return nbuf, err
129		}
130	}
131}