You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fastwalk_unix.go 3.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  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 fastwalk
  7. import (
  8. "fmt"
  9. "os"
  10. "syscall"
  11. "unsafe"
  12. )
  13. const blockSize = 8 << 10
  14. // unknownFileMode is a sentinel (and bogus) os.FileMode
  15. // value used to represent a syscall.DT_UNKNOWN Dirent.Type.
  16. const unknownFileMode os.FileMode = os.ModeNamedPipe | os.ModeSocket | os.ModeDevice
  17. func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) error) error {
  18. fd, err := syscall.Open(dirName, 0, 0)
  19. if err != nil {
  20. return &os.PathError{Op: "open", Path: dirName, Err: err}
  21. }
  22. defer syscall.Close(fd)
  23. // The buffer must be at least a block long.
  24. buf := make([]byte, blockSize) // stack-allocated; doesn't escape
  25. bufp := 0 // starting read position in buf
  26. nbuf := 0 // end valid data in buf
  27. skipFiles := false
  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 skipFiles && typ.IsRegular() {
  59. continue
  60. }
  61. if err := fn(dirName, name, typ); err != nil {
  62. if err == ErrSkipFiles {
  63. skipFiles = true
  64. continue
  65. }
  66. return err
  67. }
  68. }
  69. }
  70. func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) {
  71. // golang.org/issue/37269
  72. dirent := &syscall.Dirent{}
  73. copy((*[unsafe.Sizeof(syscall.Dirent{})]byte)(unsafe.Pointer(dirent))[:], buf)
  74. if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v {
  75. panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v))
  76. }
  77. if len(buf) < int(dirent.Reclen) {
  78. panic(fmt.Sprintf("buf size %d < record length %d", len(buf), dirent.Reclen))
  79. }
  80. consumed = int(dirent.Reclen)
  81. if direntInode(dirent) == 0 { // File absent in directory.
  82. return
  83. }
  84. switch dirent.Type {
  85. case syscall.DT_REG:
  86. typ = 0
  87. case syscall.DT_DIR:
  88. typ = os.ModeDir
  89. case syscall.DT_LNK:
  90. typ = os.ModeSymlink
  91. case syscall.DT_BLK:
  92. typ = os.ModeDevice
  93. case syscall.DT_FIFO:
  94. typ = os.ModeNamedPipe
  95. case syscall.DT_SOCK:
  96. typ = os.ModeSocket
  97. case syscall.DT_UNKNOWN:
  98. typ = unknownFileMode
  99. default:
  100. // Skip weird things.
  101. // It's probably a DT_WHT (http://lwn.net/Articles/325369/)
  102. // or something. Revisit if/when this package is moved outside
  103. // of goimports. goimports only cares about regular files,
  104. // symlinks, and directories.
  105. return
  106. }
  107. nameBuf := (*[unsafe.Sizeof(dirent.Name)]byte)(unsafe.Pointer(&dirent.Name[0]))
  108. nameLen := direntNamlen(dirent)
  109. // Special cases for common things:
  110. if nameLen == 1 && nameBuf[0] == '.' {
  111. name = "."
  112. } else if nameLen == 2 && nameBuf[0] == '.' && nameBuf[1] == '.' {
  113. name = ".."
  114. } else {
  115. name = string(nameBuf[:nameLen])
  116. }
  117. return
  118. }