walk.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. // Copyright (C) 2014 The Syncthing Authors.
  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. "strings"
  22. "github.com/syncthing/syncthing/internal/ignore"
  23. "github.com/syncthing/syncthing/internal/lamport"
  24. "github.com/syncthing/syncthing/internal/protocol"
  25. "github.com/syncthing/syncthing/internal/symlinks"
  26. "golang.org/x/text/unicode/norm"
  27. )
  28. type Walker struct {
  29. // Dir is the base directory for the walk
  30. Dir string
  31. // Limit walking to this path within Dir, or no limit if Sub is blank
  32. Sub string
  33. // BlockSize controls the size of the block used when hashing.
  34. BlockSize int
  35. // If Matcher is not nil, it is used to identify files to ignore which were specified by the user.
  36. Matcher *ignore.Matcher
  37. // If TempNamer is not nil, it is used to ignore tempory files when walking.
  38. TempNamer TempNamer
  39. // If CurrentFiler is not nil, it is queried for the current file before rescanning.
  40. CurrentFiler CurrentFiler
  41. // If IgnorePerms is true, changes to permission bits will not be
  42. // detected. Scanned files will get zero permission bits and the
  43. // NoPermissionBits flag set.
  44. IgnorePerms bool
  45. }
  46. type TempNamer interface {
  47. // Temporary returns a temporary name for the filed referred to by filepath.
  48. TempName(path string) string
  49. // IsTemporary returns true if path refers to the name of temporary file.
  50. IsTemporary(path string) bool
  51. }
  52. type CurrentFiler interface {
  53. // CurrentFile returns the file as seen at last scan.
  54. CurrentFile(name string) protocol.FileInfo
  55. }
  56. // Walk returns the list of files found in the local folder by scanning the
  57. // file system. Files are blockwise hashed.
  58. func (w *Walker) Walk() (chan protocol.FileInfo, error) {
  59. if debug {
  60. l.Debugln("Walk", w.Dir, w.Sub, w.BlockSize, w.Matcher)
  61. }
  62. err := checkDir(w.Dir)
  63. if err != nil {
  64. return nil, err
  65. }
  66. files := make(chan protocol.FileInfo)
  67. hashedFiles := make(chan protocol.FileInfo)
  68. newParallelHasher(w.Dir, w.BlockSize, runtime.NumCPU(), hashedFiles, files)
  69. go func() {
  70. hashFiles := w.walkAndHashFiles(files)
  71. filepath.Walk(filepath.Join(w.Dir, w.Sub), hashFiles)
  72. close(files)
  73. }()
  74. return hashedFiles, nil
  75. }
  76. func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo) filepath.WalkFunc {
  77. return func(p string, info os.FileInfo, err error) error {
  78. if err != nil {
  79. if debug {
  80. l.Debugln("error:", p, info, err)
  81. }
  82. return nil
  83. }
  84. rn, err := filepath.Rel(w.Dir, p)
  85. if err != nil {
  86. if debug {
  87. l.Debugln("rel error:", p, err)
  88. }
  89. return nil
  90. }
  91. if rn == "." {
  92. return nil
  93. }
  94. if w.TempNamer != nil && w.TempNamer.IsTemporary(rn) {
  95. // A temporary file
  96. if debug {
  97. l.Debugln("temporary:", rn)
  98. }
  99. return nil
  100. }
  101. if sn := filepath.Base(rn); sn == ".stignore" || sn == ".stfolder" ||
  102. strings.HasPrefix(rn, ".stversions") || (w.Matcher != nil && w.Matcher.Match(rn)) {
  103. // An ignored file
  104. if debug {
  105. l.Debugln("ignored:", rn)
  106. }
  107. if info.IsDir() {
  108. return filepath.SkipDir
  109. }
  110. return nil
  111. }
  112. if (runtime.GOOS == "linux" || runtime.GOOS == "windows") && !norm.NFC.IsNormalString(rn) {
  113. l.Warnf("File %q contains non-NFC UTF-8 sequences and cannot be synced. Consider renaming.", rn)
  114. return nil
  115. }
  116. // Index wise symlinks are always files, regardless of what the target
  117. // is, because symlinks carry their target path as their content.
  118. if info.Mode()&os.ModeSymlink != 0 {
  119. var rval error
  120. // If the target is a directory, do NOT descend down there.
  121. // This will cause files to get tracked, and removing the symlink
  122. // will as a result remove files in their real location.
  123. // But do not SkipDir if the target is not a directory, as it will
  124. // stop scanning the current directory.
  125. if info.IsDir() {
  126. rval = filepath.SkipDir
  127. }
  128. // We always rehash symlinks as they have no modtime or
  129. // permissions.
  130. // We check if they point to the old target by checking that
  131. // their existing blocks match with the blocks in the index.
  132. // If we don't have a filer or don't support symlinks, skip.
  133. if w.CurrentFiler == nil || !symlinks.Supported {
  134. return rval
  135. }
  136. target, flags, err := symlinks.Read(p)
  137. flags = flags & protocol.SymlinkTypeMask
  138. if err != nil {
  139. if debug {
  140. l.Debugln("readlink error:", p, err)
  141. }
  142. return rval
  143. }
  144. blocks, err := Blocks(strings.NewReader(target), w.BlockSize, 0)
  145. if err != nil {
  146. if debug {
  147. l.Debugln("hash link error:", p, err)
  148. }
  149. return rval
  150. }
  151. cf := w.CurrentFiler.CurrentFile(rn)
  152. if !cf.IsDeleted() && cf.IsSymlink() && SymlinkTypeEqual(flags, cf.Flags) && BlocksEqual(cf.Blocks, blocks) {
  153. return rval
  154. }
  155. f := protocol.FileInfo{
  156. Name: rn,
  157. Version: lamport.Default.Tick(0),
  158. Flags: protocol.FlagSymlink | flags | protocol.FlagNoPermBits | 0666,
  159. Modified: 0,
  160. Blocks: blocks,
  161. }
  162. if debug {
  163. l.Debugln("symlink to hash:", p, f)
  164. }
  165. fchan <- f
  166. return rval
  167. }
  168. if info.Mode().IsDir() {
  169. if w.CurrentFiler != nil {
  170. cf := w.CurrentFiler.CurrentFile(rn)
  171. permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Flags, uint32(info.Mode()))
  172. if !cf.IsDeleted() && cf.IsDirectory() && permUnchanged && !cf.IsSymlink() {
  173. return nil
  174. }
  175. }
  176. flags := uint32(protocol.FlagDirectory)
  177. if w.IgnorePerms {
  178. flags |= protocol.FlagNoPermBits | 0777
  179. } else {
  180. flags |= uint32(info.Mode() & os.ModePerm)
  181. }
  182. f := protocol.FileInfo{
  183. Name: rn,
  184. Version: lamport.Default.Tick(0),
  185. Flags: flags,
  186. Modified: info.ModTime().Unix(),
  187. }
  188. if debug {
  189. l.Debugln("dir:", p, f)
  190. }
  191. fchan <- f
  192. return nil
  193. }
  194. if info.Mode().IsRegular() {
  195. if w.CurrentFiler != nil {
  196. cf := w.CurrentFiler.CurrentFile(rn)
  197. permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Flags, uint32(info.Mode()))
  198. if !cf.IsDeleted() && cf.Modified == info.ModTime().Unix() && permUnchanged {
  199. return nil
  200. }
  201. if debug {
  202. l.Debugln("rescan:", cf, info.ModTime().Unix(), info.Mode()&os.ModePerm)
  203. }
  204. }
  205. var flags = uint32(info.Mode() & os.ModePerm)
  206. if w.IgnorePerms {
  207. flags = protocol.FlagNoPermBits | 0666
  208. }
  209. f := protocol.FileInfo{
  210. Name: rn,
  211. Version: lamport.Default.Tick(0),
  212. Flags: flags,
  213. Modified: info.ModTime().Unix(),
  214. }
  215. if debug {
  216. l.Debugln("to hash:", p, f)
  217. }
  218. fchan <- f
  219. }
  220. return nil
  221. }
  222. }
  223. func checkDir(dir string) error {
  224. if info, err := os.Lstat(dir); err != nil {
  225. return err
  226. } else if !info.IsDir() {
  227. return errors.New(dir + ": not a directory")
  228. } else if debug {
  229. l.Debugln("checkDir", dir, info)
  230. }
  231. return nil
  232. }
  233. func PermsEqual(a, b uint32) bool {
  234. switch runtime.GOOS {
  235. case "windows":
  236. // There is only writeable and read only, represented for user, group
  237. // and other equally. We only compare against user.
  238. return a&0600 == b&0600
  239. default:
  240. // All bits count
  241. return a&0777 == b&0777
  242. }
  243. }
  244. // If the target is missing, Unix never knows what type of symlink it is
  245. // and Windows always knows even if there is no target.
  246. // Which means that without this special check a Unix node would be fighting
  247. // with a Windows node about whether or not the target is known.
  248. // Basically, if you don't know and someone else knows, just accept it.
  249. // The fact that you don't know means you are on Unix, and on Unix you don't
  250. // really care what the target type is. The moment you do know, and if something
  251. // doesn't match, that will propogate throught the cluster.
  252. func SymlinkTypeEqual(disk, index uint32) bool {
  253. if disk&protocol.FlagSymlinkMissingTarget != 0 && index&protocol.FlagSymlinkMissingTarget == 0 {
  254. return true
  255. }
  256. return disk&protocol.SymlinkTypeMask == index&protocol.SymlinkTypeMask
  257. }