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 }