fastwalk_unix.go 4.2 KB

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