walk.go 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  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. // List of patterns to ignore
  34. Ignores *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.Ignores)
  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" || sn == ".stfolder" || w.Ignores.Match(rn) {
  100. // An ignored file
  101. if debug {
  102. l.Debugln("ignored:", rn)
  103. }
  104. if info.IsDir() {
  105. return filepath.SkipDir
  106. }
  107. return nil
  108. }
  109. if (runtime.GOOS == "linux" || runtime.GOOS == "windows") && !norm.NFC.IsNormalString(rn) {
  110. l.Warnf("File %q contains non-NFC UTF-8 sequences and cannot be synced. Consider renaming.", rn)
  111. return nil
  112. }
  113. if info.Mode().IsDir() {
  114. if w.CurrentFiler != nil {
  115. cf := w.CurrentFiler.CurrentFile(rn)
  116. permUnchanged := w.IgnorePerms || !protocol.HasPermissionBits(cf.Flags) || PermsEqual(cf.Flags, uint32(info.Mode()))
  117. if !protocol.IsDeleted(cf.Flags) && protocol.IsDirectory(cf.Flags) && permUnchanged {
  118. return nil
  119. }
  120. }
  121. var flags uint32 = protocol.FlagDirectory
  122. if w.IgnorePerms {
  123. flags |= protocol.FlagNoPermBits | 0777
  124. } else {
  125. flags |= uint32(info.Mode() & os.ModePerm)
  126. }
  127. f := protocol.FileInfo{
  128. Name: rn,
  129. Version: lamport.Default.Tick(0),
  130. Flags: flags,
  131. Modified: info.ModTime().Unix(),
  132. }
  133. if debug {
  134. l.Debugln("dir:", f)
  135. }
  136. fchan <- f
  137. return nil
  138. }
  139. if info.Mode().IsRegular() {
  140. if w.CurrentFiler != nil {
  141. cf := w.CurrentFiler.CurrentFile(rn)
  142. permUnchanged := w.IgnorePerms || !protocol.HasPermissionBits(cf.Flags) || PermsEqual(cf.Flags, uint32(info.Mode()))
  143. if !protocol.IsDeleted(cf.Flags) && cf.Modified == info.ModTime().Unix() && permUnchanged {
  144. return nil
  145. }
  146. if debug {
  147. l.Debugln("rescan:", cf, info.ModTime().Unix(), info.Mode()&os.ModePerm)
  148. }
  149. }
  150. var flags = uint32(info.Mode() & os.ModePerm)
  151. if w.IgnorePerms {
  152. flags = protocol.FlagNoPermBits | 0666
  153. }
  154. fchan <- protocol.FileInfo{
  155. Name: rn,
  156. Version: lamport.Default.Tick(0),
  157. Flags: flags,
  158. Modified: info.ModTime().Unix(),
  159. }
  160. }
  161. return nil
  162. }
  163. }
  164. func checkDir(dir string) error {
  165. if info, err := os.Lstat(dir); err != nil {
  166. return err
  167. } else if !info.IsDir() {
  168. return errors.New(dir + ": not a directory")
  169. } else if debug {
  170. l.Debugln("checkDir", dir, info)
  171. }
  172. return nil
  173. }
  174. func PermsEqual(a, b uint32) bool {
  175. switch runtime.GOOS {
  176. case "windows":
  177. // There is only writeable and read only, represented for user, group
  178. // and other equally. We only compare against user.
  179. return a&0600 == b&0600
  180. default:
  181. // All bits count
  182. return a&0777 == b&0777
  183. }
  184. }