readdir_unix.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. // +build darwin freebsd linux netbsd openbsd
  2. package godirwalk
  3. import (
  4. "os"
  5. "path/filepath"
  6. "syscall"
  7. "unsafe"
  8. "github.com/pkg/errors"
  9. )
  10. func readdirents(osDirname string, scratchBuffer []byte) (Dirents, error) {
  11. dh, err := os.Open(osDirname)
  12. if err != nil {
  13. return nil, errors.Wrap(err, "cannot Open")
  14. }
  15. var entries Dirents
  16. fd := int(dh.Fd())
  17. if len(scratchBuffer) < MinimumScratchBufferSize {
  18. scratchBuffer = make([]byte, DefaultScratchBufferSize)
  19. }
  20. var de *syscall.Dirent
  21. for {
  22. n, err := syscall.ReadDirent(fd, scratchBuffer)
  23. if err != nil {
  24. _ = dh.Close() // ignore potential error returned by Close
  25. return nil, errors.Wrap(err, "cannot ReadDirent")
  26. }
  27. if n <= 0 {
  28. break // end of directory reached
  29. }
  30. // Loop over the bytes returned by reading the directory entries.
  31. buf := scratchBuffer[:n]
  32. for len(buf) > 0 {
  33. de = (*syscall.Dirent)(unsafe.Pointer(&buf[0])) // point entry to first syscall.Dirent in buffer
  34. buf = buf[de.Reclen:] // advance buffer
  35. if inoFromDirent(de) == 0 {
  36. continue // this item has been deleted, but not yet removed from directory
  37. }
  38. nameSlice := nameFromDirent(de)
  39. namlen := len(nameSlice)
  40. if (namlen == 0) || (namlen == 1 && nameSlice[0] == '.') || (namlen == 2 && nameSlice[0] == '.' && nameSlice[1] == '.') {
  41. continue // skip unimportant entries
  42. }
  43. osChildname := string(nameSlice)
  44. // Convert syscall constant, which is in purview of OS, to a
  45. // constant defined by Go, assumed by this project to be stable.
  46. var mode os.FileMode
  47. switch de.Type {
  48. case syscall.DT_REG:
  49. // regular file
  50. case syscall.DT_DIR:
  51. mode = os.ModeDir
  52. case syscall.DT_LNK:
  53. mode = os.ModeSymlink
  54. case syscall.DT_CHR:
  55. mode = os.ModeDevice | os.ModeCharDevice
  56. case syscall.DT_BLK:
  57. mode = os.ModeDevice
  58. case syscall.DT_FIFO:
  59. mode = os.ModeNamedPipe
  60. case syscall.DT_SOCK:
  61. mode = os.ModeSocket
  62. default:
  63. // If syscall returned unknown type (e.g., DT_UNKNOWN, DT_WHT),
  64. // then resolve actual mode by getting stat.
  65. fi, err := os.Lstat(filepath.Join(osDirname, osChildname))
  66. if err != nil {
  67. _ = dh.Close() // ignore potential error returned by Close
  68. return nil, errors.Wrap(err, "cannot Stat")
  69. }
  70. // We only care about the bits that identify the type of a file
  71. // system node, and can ignore append, exclusive, temporary,
  72. // setuid, setgid, permission bits, and sticky bits, which are
  73. // coincident to the bits that declare type of the file system
  74. // node.
  75. mode = fi.Mode() & os.ModeType
  76. }
  77. entries = append(entries, &Dirent{name: osChildname, modeType: mode})
  78. }
  79. }
  80. if err = dh.Close(); err != nil {
  81. return nil, err
  82. }
  83. return entries, nil
  84. }
  85. func readdirnames(osDirname string, scratchBuffer []byte) ([]string, error) {
  86. des, err := readdirents(osDirname, scratchBuffer)
  87. if err != nil {
  88. return nil, err
  89. }
  90. names := make([]string, len(des))
  91. for i, v := range des {
  92. names[i] = v.name
  93. }
  94. return names, nil
  95. }