fastwalk_unix.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  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. // +build linux darwin freebsd openbsd netbsd
  5. // +build !appengine
  6. package imports
  7. import (
  8. "bytes"
  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 := syscall.Open(dirName, 0, 0)
  20. if err != nil {
  21. return 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. for {
  29. if bufp >= nbuf {
  30. bufp = 0
  31. nbuf, err = syscall.ReadDirent(fd, buf)
  32. if err != nil {
  33. return os.NewSyscallError("readdirent", err)
  34. }
  35. if nbuf <= 0 {
  36. return nil
  37. }
  38. }
  39. consumed, name, typ := parseDirEnt(buf[bufp:nbuf])
  40. bufp += consumed
  41. if name == "" || name == "." || name == ".." {
  42. continue
  43. }
  44. // Fallback for filesystems (like old XFS) that don't
  45. // support Dirent.Type and have DT_UNKNOWN (0) there
  46. // instead.
  47. if typ == unknownFileMode {
  48. fi, err := os.Lstat(dirName + "/" + name)
  49. if err != nil {
  50. // It got deleted in the meantime.
  51. if os.IsNotExist(err) {
  52. continue
  53. }
  54. return err
  55. }
  56. typ = fi.Mode() & os.ModeType
  57. }
  58. if err := fn(dirName, name, typ); err != nil {
  59. return err
  60. }
  61. }
  62. }
  63. func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) {
  64. // golang.org/issue/15653
  65. dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
  66. if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v {
  67. panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v))
  68. }
  69. if len(buf) < int(dirent.Reclen) {
  70. panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen))
  71. }
  72. consumed = int(dirent.Reclen)
  73. if direntInode(dirent) == 0 { // File absent in directory.
  74. return
  75. }
  76. switch dirent.Type {
  77. case syscall.DT_REG:
  78. typ = 0
  79. case syscall.DT_DIR:
  80. typ = os.ModeDir
  81. case syscall.DT_LNK:
  82. typ = os.ModeSymlink
  83. case syscall.DT_BLK:
  84. typ = os.ModeDevice
  85. case syscall.DT_FIFO:
  86. typ = os.ModeNamedPipe
  87. case syscall.DT_SOCK:
  88. typ = os.ModeSocket
  89. case syscall.DT_UNKNOWN:
  90. typ = unknownFileMode
  91. default:
  92. // Skip weird things.
  93. // It's probably a DT_WHT (http://lwn.net/Articles/325369/)
  94. // or something. Revisit if/when this package is moved outside
  95. // of goimports. goimports only cares about regular files,
  96. // symlinks, and directories.
  97. return
  98. }
  99. nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
  100. nameLen := bytes.IndexByte(nameBuf[:], 0)
  101. if nameLen < 0 {
  102. panic("failed to find terminating 0 byte in dirent")
  103. }
  104. // Special cases for common things:
  105. if nameLen == 1 && nameBuf[0] == '.' {
  106. name = "."
  107. } else if nameLen == 2 && nameBuf[0] == '.' && nameBuf[1] == '.' {
  108. name = ".."
  109. } else {
  110. name = string(nameBuf[:nameLen])
  111. }
  112. return
  113. }