github.com/charlievieth/fastwalk@v1.0.3/fastwalk_unix.go (about)

     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 (linux || freebsd || openbsd || netbsd) && !appengine
     6  // +build linux freebsd openbsd netbsd
     7  // +build !appengine
     8  
     9  package fastwalk
    10  
    11  import (
    12  	"io/fs"
    13  	"os"
    14  	"syscall"
    15  
    16  	"github.com/charlievieth/fastwalk/internal/dirent"
    17  )
    18  
    19  // More than 5760 to work around https://golang.org/issue/24015.
    20  const blockSize = 8192
    21  
    22  // unknownFileMode is a sentinel (and bogus) os.FileMode
    23  // value used to represent a syscall.DT_UNKNOWN Dirent.Type.
    24  const unknownFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
    25  
    26  func readDir(dirName string, fn func(dirName, entName string, de fs.DirEntry) error) error {
    27  	fd, err := open(dirName, 0, 0)
    28  	if err != nil {
    29  		return &os.PathError{Op: "open", Path: dirName, Err: err}
    30  	}
    31  	defer syscall.Close(fd)
    32  
    33  	// The buffer must be at least a block long.
    34  	buf := make([]byte, blockSize) // stack-allocated; doesn't escape
    35  	bufp := 0                      // starting read position in buf
    36  	nbuf := 0                      // end valid data in buf
    37  	skipFiles := false
    38  	for {
    39  		if bufp >= nbuf {
    40  			bufp = 0
    41  			nbuf, err = readDirent(fd, buf)
    42  			if err != nil {
    43  				return os.NewSyscallError("readdirent", err)
    44  			}
    45  			if nbuf <= 0 {
    46  				return nil
    47  			}
    48  		}
    49  		consumed, name, typ := dirent.Parse(buf[bufp:nbuf])
    50  		bufp += consumed
    51  
    52  		if name == "" || name == "." || name == ".." {
    53  			continue
    54  		}
    55  		// Fallback for filesystems (like old XFS) that don't
    56  		// support Dirent.Type and have DT_UNKNOWN (0) there
    57  		// instead.
    58  		if typ == unknownFileMode {
    59  			fi, err := os.Lstat(dirName + "/" + name)
    60  			if err != nil {
    61  				// It got deleted in the meantime.
    62  				if os.IsNotExist(err) {
    63  					continue
    64  				}
    65  				return err
    66  			}
    67  			typ = fi.Mode() & os.ModeType
    68  		}
    69  		if skipFiles && typ.IsRegular() {
    70  			continue
    71  		}
    72  		de := newUnixDirent(dirName, name, typ)
    73  		if err := fn(dirName, name, de); err != nil {
    74  			if err == ErrSkipFiles {
    75  				skipFiles = true
    76  				continue
    77  			}
    78  			return err
    79  		}
    80  	}
    81  }
    82  
    83  // According to https://golang.org/doc/go1.14#runtime
    84  // A consequence of the implementation of preemption is that on Unix systems, including Linux and macOS
    85  // systems, programs built with Go 1.14 will receive more signals than programs built with earlier releases.
    86  //
    87  // This causes syscall.Open and syscall.ReadDirent sometimes fail with EINTR errors.
    88  // We need to retry in this case.
    89  func open(path string, mode int, perm uint32) (fd int, err error) {
    90  	for {
    91  		fd, err := syscall.Open(path, mode, perm)
    92  		if err != syscall.EINTR {
    93  			return fd, err
    94  		}
    95  	}
    96  }
    97  
    98  func readDirent(fd int, buf []byte) (n int, err error) {
    99  	for {
   100  		nbuf, err := syscall.ReadDirent(fd, buf)
   101  		if err != syscall.EINTR {
   102  			return nbuf, err
   103  		}
   104  	}
   105  }