github.com/v2fly/tools@v0.100.0/internal/fastwalk/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 || darwin || freebsd || openbsd || netbsd) && !appengine
     6  // +build linux darwin freebsd openbsd netbsd
     7  // +build !appengine
     8  
     9  package fastwalk
    10  
    11  import (
    12  	"fmt"
    13  	"os"
    14  	"syscall"
    15  	"unsafe"
    16  )
    17  
    18  const blockSize = 8 << 10
    19  
    20  // unknownFileMode is a sentinel (and bogus) os.FileMode
    21  // value used to represent a syscall.DT_UNKNOWN Dirent.Type.
    22  const unknownFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
    23  
    24  func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error {
    25  	fd, err := open(dirName, 0, 0)
    26  	if err != nil {
    27  		return &os.PathError{Op: "open", Path: dirName, Err: err}
    28  	}
    29  	defer syscall.Close(fd)
    30  
    31  	// The buffer must be at least a block long.
    32  	buf := make([]byte, blockSize) // stack-allocated; doesn't escape
    33  	bufp := 0                      // starting read position in buf
    34  	nbuf := 0                      // end valid data in buf
    35  	skipFiles := false
    36  	for {
    37  		if bufp >= nbuf {
    38  			bufp = 0
    39  			nbuf, err = readDirent(fd, buf)
    40  			if err != nil {
    41  				return os.NewSyscallError("readdirent", err)
    42  			}
    43  			if nbuf <= 0 {
    44  				return nil
    45  			}
    46  		}
    47  		consumed, name, typ := parseDirEnt(buf[bufp:nbuf])
    48  		bufp += consumed
    49  		if name == "" || name == "." || name == ".." {
    50  			continue
    51  		}
    52  		// Fallback for filesystems (like old XFS) that don't
    53  		// support Dirent.Type and have DT_UNKNOWN (0) there
    54  		// instead.
    55  		if typ == unknownFileMode {
    56  			fi, err := os.Lstat(dirName + "/" + name)
    57  			if err != nil {
    58  				// It got deleted in the meantime.
    59  				if os.IsNotExist(err) {
    60  					continue
    61  				}
    62  				return err
    63  			}
    64  			typ = fi.Mode() & os.ModeType
    65  		}
    66  		if skipFiles && typ.IsRegular() {
    67  			continue
    68  		}
    69  		if err := fn(dirName, name, typ); err != nil {
    70  			if err == ErrSkipFiles {
    71  				skipFiles = true
    72  				continue
    73  			}
    74  			return err
    75  		}
    76  	}
    77  }
    78  
    79  func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) {
    80  	// golang.org/issue/37269
    81  	dirent := &syscall.Dirent{}
    82  	copy((*[unsafe.Sizeof(syscall.Dirent{})]byte)(unsafe.Pointer(dirent))[:], buf)
    83  	if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v {
    84  		panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v))
    85  	}
    86  	if len(buf) < int(dirent.Reclen) {
    87  		panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen))
    88  	}
    89  	consumed = int(dirent.Reclen)
    90  	if direntInode(dirent) == 0 { // File absent in directory.
    91  		return
    92  	}
    93  	switch dirent.Type {
    94  	case syscall.DT_REG:
    95  		typ = 0
    96  	case syscall.DT_DIR:
    97  		typ = os.ModeDir
    98  	case syscall.DT_LNK:
    99  		typ = os.ModeSymlink
   100  	case syscall.DT_BLK:
   101  		typ = os.ModeDevice
   102  	case syscall.DT_FIFO:
   103  		typ = os.ModeNamedPipe
   104  	case syscall.DT_SOCK:
   105  		typ = os.ModeSocket
   106  	case syscall.DT_UNKNOWN:
   107  		typ = unknownFileMode
   108  	default:
   109  		// Skip weird things.
   110  		// It's probably a DT_WHT (http://lwn.net/Articles/325369/)
   111  		// or something. Revisit if/when this package is moved outside
   112  		// of goimports. goimports only cares about regular files,
   113  		// symlinks, and directories.
   114  		return
   115  	}
   116  
   117  	nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
   118  	nameLen := direntNamlen(dirent)
   119  
   120  	// Special cases for common things:
   121  	if nameLen == 1 && nameBuf[0] == '.' {
   122  		name = "."
   123  	} else if nameLen == 2 && nameBuf[0] == '.' && nameBuf[1] == '.' {
   124  		name = ".."
   125  	} else {
   126  		name = string(nameBuf[:nameLen])
   127  	}
   128  	return
   129  }
   130  
   131  // According to https://golang.org/doc/go1.14#runtime
   132  // A consequence of the implementation of preemption is that on Unix systems, including Linux and macOS
   133  // systems, programs built with Go 1.14 will receive more signals than programs built with earlier releases.
   134  //
   135  // This causes syscall.Open and syscall.ReadDirent sometimes fail with EINTR errors.
   136  // We need to retry in this case.
   137  func open(path string, mode int, perm uint32) (fd int, err error) {
   138  	for {
   139  		fd, err := syscall.Open(path, mode, perm)
   140  		if err != syscall.EINTR {
   141  			return fd, err
   142  		}
   143  	}
   144  }
   145  
   146  func readDirent(fd int, buf []byte) (n int, err error) {
   147  	for {
   148  		nbuf, err := syscall.ReadDirent(fd, buf)
   149  		if err != syscall.EINTR {
   150  			return nbuf, err
   151  		}
   152  	}
   153  }