scandir_unix.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. //go:build !windows
  2. // +build !windows
  3. package godirwalk
  4. import (
  5. "os"
  6. "syscall"
  7. "unsafe"
  8. )
  9. // Scanner is an iterator to enumerate the contents of a directory.
  10. type Scanner struct {
  11. scratchBuffer []byte // read directory bytes from file system into this buffer
  12. workBuffer []byte // points into scratchBuffer, from which we chunk out directory entries
  13. osDirname string
  14. childName string
  15. err error // err is the error associated with scanning directory
  16. statErr error // statErr is any error return while attempting to stat an entry
  17. dh *os.File // used to close directory after done reading
  18. de *Dirent // most recently decoded directory entry
  19. sde syscall.Dirent
  20. fd int // file descriptor used to read entries from directory
  21. }
  22. // NewScanner returns a new directory Scanner that lazily enumerates
  23. // the contents of a single directory. To prevent resource leaks,
  24. // caller must invoke either the Scanner's Close or Err method after
  25. // it has completed scanning a directory.
  26. //
  27. // scanner, err := godirwalk.NewScanner(dirname)
  28. // if err != nil {
  29. // fatal("cannot scan directory: %s", err)
  30. // }
  31. //
  32. // for scanner.Scan() {
  33. // dirent, err := scanner.Dirent()
  34. // if err != nil {
  35. // warning("cannot get dirent: %s", err)
  36. // continue
  37. // }
  38. // name := dirent.Name()
  39. // if name == "break" {
  40. // break
  41. // }
  42. // if name == "continue" {
  43. // continue
  44. // }
  45. // fmt.Printf("%v %v\n", dirent.ModeType(), dirent.Name())
  46. // }
  47. // if err := scanner.Err(); err != nil {
  48. // fatal("cannot scan directory: %s", err)
  49. // }
  50. func NewScanner(osDirname string) (*Scanner, error) {
  51. return NewScannerWithScratchBuffer(osDirname, nil)
  52. }
  53. // NewScannerWithScratchBuffer returns a new directory Scanner that
  54. // lazily enumerates the contents of a single directory. On platforms
  55. // other than Windows it uses the provided scratch buffer to read from
  56. // the file system. On Windows the scratch buffer is ignored. To
  57. // prevent resource leaks, caller must invoke either the Scanner's
  58. // Close or Err method after it has completed scanning a directory.
  59. func NewScannerWithScratchBuffer(osDirname string, scratchBuffer []byte) (*Scanner, error) {
  60. dh, err := os.Open(osDirname)
  61. if err != nil {
  62. return nil, err
  63. }
  64. if len(scratchBuffer) < MinimumScratchBufferSize {
  65. scratchBuffer = newScratchBuffer()
  66. }
  67. scanner := &Scanner{
  68. scratchBuffer: scratchBuffer,
  69. osDirname: osDirname,
  70. dh: dh,
  71. fd: int(dh.Fd()),
  72. }
  73. return scanner, nil
  74. }
  75. // Close releases resources associated with scanning a directory. Call
  76. // either this or the Err method when the directory no longer needs to
  77. // be scanned.
  78. func (s *Scanner) Close() error {
  79. return s.Err()
  80. }
  81. // Dirent returns the current directory entry while scanning a directory.
  82. func (s *Scanner) Dirent() (*Dirent, error) {
  83. if s.de == nil {
  84. s.de = &Dirent{name: s.childName, path: s.osDirname}
  85. s.de.modeType, s.statErr = modeTypeFromDirent(&s.sde, s.osDirname, s.childName)
  86. }
  87. return s.de, s.statErr
  88. }
  89. // done is called when directory scanner unable to continue, with either the
  90. // triggering error, or nil when there are simply no more entries to read from
  91. // the directory.
  92. func (s *Scanner) done(err error) {
  93. if s.dh == nil {
  94. return
  95. }
  96. s.err = err
  97. if err = s.dh.Close(); s.err == nil {
  98. s.err = err
  99. }
  100. s.osDirname, s.childName = "", ""
  101. s.scratchBuffer, s.workBuffer = nil, nil
  102. s.dh, s.de, s.statErr = nil, nil, nil
  103. s.sde = syscall.Dirent{}
  104. s.fd = 0
  105. }
  106. // Err returns any error associated with scanning a directory. It is
  107. // normal to call Err after Scan returns false, even though they both
  108. // ensure Scanner resources are released. Call either this or the
  109. // Close method when the directory no longer needs to be scanned.
  110. func (s *Scanner) Err() error {
  111. s.done(nil)
  112. return s.err
  113. }
  114. // Name returns the base name of the current directory entry while scanning a
  115. // directory.
  116. func (s *Scanner) Name() string { return s.childName }
  117. // Scan potentially reads and then decodes the next directory entry from the
  118. // file system.
  119. //
  120. // When it returns false, this releases resources used by the Scanner then
  121. // returns any error associated with closing the file system directory resource.
  122. func (s *Scanner) Scan() bool {
  123. if s.dh == nil {
  124. return false
  125. }
  126. s.de = nil
  127. for {
  128. // When the work buffer has nothing remaining to decode, we need to load
  129. // more data from disk.
  130. if len(s.workBuffer) == 0 {
  131. n, err := syscall.ReadDirent(s.fd, s.scratchBuffer)
  132. // n, err := unix.ReadDirent(s.fd, s.scratchBuffer)
  133. if err != nil {
  134. if err == syscall.EINTR /* || err == unix.EINTR */ {
  135. continue
  136. }
  137. s.done(err) // any other error forces a stop
  138. return false
  139. }
  140. if n <= 0 { // end of directory: normal exit
  141. s.done(nil)
  142. return false
  143. }
  144. s.workBuffer = s.scratchBuffer[:n] // trim work buffer to number of bytes read
  145. }
  146. // point entry to first syscall.Dirent in buffer
  147. copy((*[unsafe.Sizeof(syscall.Dirent{})]byte)(unsafe.Pointer(&s.sde))[:], s.workBuffer)
  148. s.workBuffer = s.workBuffer[reclen(&s.sde):] // advance buffer for next iteration through loop
  149. if inoFromDirent(&s.sde) == 0 {
  150. continue // inode set to 0 indicates an entry that was marked as deleted
  151. }
  152. nameSlice := nameFromDirent(&s.sde)
  153. nameLength := len(nameSlice)
  154. if nameLength == 0 || (nameSlice[0] == '.' && (nameLength == 1 || (nameLength == 2 && nameSlice[1] == '.'))) {
  155. continue
  156. }
  157. s.childName = string(nameSlice)
  158. return true
  159. }
  160. }