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 }