walk.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. // Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
  2. //
  3. // This program is free software: you can redistribute it and/or modify it
  4. // under the terms of the GNU General Public License as published by the Free
  5. // Software Foundation, either version 3 of the License, or (at your option)
  6. // any later version.
  7. //
  8. // This program is distributed in the hope that it will be useful, but WITHOUT
  9. // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10. // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  11. // more details.
  12. //
  13. // You should have received a copy of the GNU General Public License along
  14. // with this program. If not, see <http://www.gnu.org/licenses/>.
  15. package scanner
  16. import (
  17. "errors"
  18. "os"
  19. "path/filepath"
  20. "runtime"
  21. "code.google.com/p/go.text/unicode/norm"
  22. "github.com/syncthing/syncthing/internal/ignore"
  23. "github.com/syncthing/syncthing/internal/lamport"
  24. "github.com/syncthing/syncthing/internal/protocol"
  25. )
  26. type Walker struct {
  27. // Dir is the base directory for the walk
  28. Dir string
  29. // Limit walking to this path within Dir, or no limit if Sub is blank
  30. Sub string
  31. // BlockSize controls the size of the block used when hashing.
  32. BlockSize int
  33. // If Matcher is not nil, it is used to identify files to ignore which were specified by the user.
  34. Matcher *ignore.Matcher
  35. // If TempNamer is not nil, it is used to ignore tempory files when walking.
  36. TempNamer TempNamer
  37. // If CurrentFiler is not nil, it is queried for the current file before rescanning.
  38. CurrentFiler CurrentFiler
  39. // If IgnorePerms is true, changes to permission bits will not be
  40. // detected. Scanned files will get zero permission bits and the
  41. // NoPermissionBits flag set.
  42. IgnorePerms bool
  43. }
  44. type TempNamer interface {
  45. // Temporary returns a temporary name for the filed referred to by filepath.
  46. TempName(path string) string
  47. // IsTemporary returns true if path refers to the name of temporary file.
  48. IsTemporary(path string) bool
  49. }
  50. type CurrentFiler interface {
  51. // CurrentFile returns the file as seen at last scan.
  52. CurrentFile(name string) protocol.FileInfo
  53. }
  54. // Walk returns the list of files found in the local folder by scanning the
  55. // file system. Files are blockwise hashed.
  56. func (w *Walker) Walk() (chan protocol.FileInfo, error) {
  57. if debug {
  58. l.Debugln("Walk", w.Dir, w.Sub, w.BlockSize, w.Matcher)
  59. }
  60. err := checkDir(w.Dir)
  61. if err != nil {
  62. return nil, err
  63. }
  64. files := make(chan protocol.FileInfo)
  65. hashedFiles := make(chan protocol.FileInfo)
  66. newParallelHasher(w.Dir, w.BlockSize, runtime.NumCPU(), hashedFiles, files)
  67. go func() {
  68. hashFiles := w.walkAndHashFiles(files)
  69. filepath.Walk(filepath.Join(w.Dir, w.Sub), hashFiles)
  70. close(files)
  71. }()
  72. return hashedFiles, nil
  73. }
  74. func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFunc {
  75. return func(p string, info os.FileInfo, err error) error {
  76. if err != nil {
  77. if debug {
  78. l.Debugln("error:", p, info, err)
  79. }
  80. return nil
  81. }
  82. rn, err := filepath.Rel(w.Dir, p)
  83. if err != nil {
  84. if debug {
  85. l.Debugln("rel error:", p, err)
  86. }
  87. return nil
  88. }
  89. if rn == "." {
  90. return nil
  91. }
  92. if w.TempNamer != nil && w.TempNamer.IsTemporary(rn) {
  93. // A temporary file
  94. if debug {
  95. l.Debugln("temporary:", rn)
  96. }
  97. return nil
  98. }
  99. if sn := filepath.Base(rn); sn == ".stignore" || sn == ".stversions" ||
  100. sn == ".stfolder" || (w.Matcher != nil && w.Matcher.Match(rn)) {
  101. // An ignored file
  102. if debug {
  103. l.Debugln("ignored:", rn)
  104. }
  105. if info.IsDir() {
  106. return filepath.SkipDir
  107. }
  108. return nil
  109. }
  110. if (runtime.GOOS == "linux" || runtime.GOOS == "windows") && !norm.NFC.IsNormalString(rn) {
  111. l.Warnf("File %q contains non-NFC UTF-8 sequences and cannot be synced. Consider renaming.", rn)
  112. return nil
  113. }
  114. if info.Mode().IsDir() {
  115. if w.CurrentFiler != nil {
  116. cf := w.CurrentFiler.CurrentFile(rn)
  117. permUnchanged := w.IgnorePerms || !protocol.HasPermissionBits(cf.Flags) || PermsEqual(cf.Flags, uint32(info.Mode()))
  118. if !protocol.IsDeleted(cf.Flags) && protocol.IsDirectory(cf.Flags) && permUnchanged {
  119. return nil
  120. }
  121. }
  122. var flags uint32 = protocol.FlagDirectory
  123. if w.IgnorePerms {
  124. flags |= protocol.FlagNoPermBits | 0777
  125. } else {
  126. flags |= uint32(info.Mode() & os.ModePerm)
  127. }
  128. f := protocol.FileInfo{
  129. Name: rn,
  130. Version: lamport.Default.Tick(0),
  131. Flags: flags,
  132. Modified: info.ModTime().Unix(),
  133. }
  134. if debug {
  135. l.Debugln("dir:", p, f)
  136. }
  137. fchan <- f
  138. return nil
  139. }
  140. if info.Mode().IsRegular() {
  141. if w.CurrentFiler != nil {
  142. cf := w.CurrentFiler.CurrentFile(rn)
  143. permUnchanged := w.IgnorePerms || !protocol.HasPermissionBits(cf.Flags) || PermsEqual(cf.Flags, uint32(info.Mode()))
  144. if !protocol.IsDeleted(cf.Flags) && cf.Modified == info.ModTime().Unix() && permUnchanged {
  145. return nil
  146. }
  147. if debug {
  148. l.Debugln("rescan:", cf, info.ModTime().Unix(), info.Mode()&os.ModePerm)
  149. }
  150. }
  151. var flags = uint32(info.Mode() & os.ModePerm)
  152. if w.IgnorePerms {
  153. flags = protocol.FlagNoPermBits | 0666
  154. }
  155. f := protocol.FileInfo{
  156. Name: rn,
  157. Version: lamport.Default.Tick(0),
  158. Flags: flags,
  159. Modified: info.ModTime().Unix(),
  160. }
  161. if debug {
  162. l.Debugln("to hash:", p, f)
  163. }
  164. fchan <- f
  165. }
  166. return nil
  167. }
  168. }
  169. func checkDir(dir string) error {
  170. if info, err := os.Lstat(dir); err != nil {
  171. return err
  172. } else if !info.IsDir() {
  173. return errors.New(dir + ": not a directory")
  174. } else if debug {
  175. l.Debugln("checkDir", dir, info)
  176. }
  177. return nil
  178. }
  179. func PermsEqual(a, b uint32) bool {
  180. switch runtime.GOOS {
  181. case "windows":
  182. // There is only writeable and read only, represented for user, group
  183. // and other equally. We only compare against user.
  184. return a&0600 == b&0600
  185. default:
  186. // All bits count
  187. return a&0777 == b&0777
  188. }
  189. }