set.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. // Package db provides a set type to track local/remote files with newness
  7. // checks. We must do a certain amount of normalization in here. We will get
  8. // fed paths with either native or wire-format separators and encodings
  9. // depending on who calls us. We transform paths to wire-format (NFC and
  10. // slashes) on the way to the database, and transform to native format
  11. // (varying separator and encoding) on the way back out.
  12. package db
  13. import (
  14. "os"
  15. "sort"
  16. "time"
  17. "github.com/syncthing/syncthing/lib/fs"
  18. "github.com/syncthing/syncthing/lib/osutil"
  19. "github.com/syncthing/syncthing/lib/protocol"
  20. "github.com/syncthing/syncthing/lib/sync"
  21. "github.com/syndtr/goleveldb/leveldb/util"
  22. )
  23. type FileSet struct {
  24. folder string
  25. fs fs.Filesystem
  26. db *instance
  27. blockmap *BlockMap
  28. meta *metadataTracker
  29. updateMutex sync.Mutex // protects database updates and the corresponding metadata changes
  30. }
  31. // FileIntf is the set of methods implemented by both protocol.FileInfo and
  32. // FileInfoTruncated.
  33. type FileIntf interface {
  34. FileSize() int64
  35. FileName() string
  36. FileLocalFlags() uint32
  37. IsDeleted() bool
  38. IsInvalid() bool
  39. IsIgnored() bool
  40. IsUnsupported() bool
  41. MustRescan() bool
  42. IsDirectory() bool
  43. IsSymlink() bool
  44. ShouldConflict() bool
  45. HasPermissionBits() bool
  46. SequenceNo() int64
  47. BlockSize() int
  48. FileVersion() protocol.Vector
  49. }
  50. // The Iterator is called with either a protocol.FileInfo or a
  51. // FileInfoTruncated (depending on the method) and returns true to
  52. // continue iteration, false to stop.
  53. type Iterator func(f FileIntf) bool
  54. var databaseRecheckInterval = 30 * 24 * time.Hour
  55. func init() {
  56. if dur, err := time.ParseDuration(os.Getenv("STRECHECKDBEVERY")); err == nil {
  57. databaseRecheckInterval = dur
  58. }
  59. }
  60. func NewFileSet(folder string, fs fs.Filesystem, ll *Lowlevel) *FileSet {
  61. db := newInstance(ll)
  62. var s = FileSet{
  63. folder: folder,
  64. fs: fs,
  65. db: db,
  66. blockmap: NewBlockMap(ll, folder),
  67. meta: newMetadataTracker(),
  68. updateMutex: sync.NewMutex(),
  69. }
  70. if err := s.meta.fromDB(db, []byte(folder)); err != nil {
  71. l.Infof("No stored folder metadata for %q: recalculating", folder)
  72. s.recalcCounts()
  73. } else if age := time.Since(s.meta.Created()); age > databaseRecheckInterval {
  74. l.Infof("Stored folder metadata for %q is %v old; recalculating", folder, age)
  75. s.recalcCounts()
  76. }
  77. return &s
  78. }
  79. func (s *FileSet) recalcCounts() {
  80. s.meta = newMetadataTracker()
  81. s.db.checkGlobals([]byte(s.folder), s.meta)
  82. var deviceID protocol.DeviceID
  83. s.db.withAllFolderTruncated([]byte(s.folder), func(device []byte, f FileInfoTruncated) bool {
  84. copy(deviceID[:], device)
  85. s.meta.addFile(deviceID, f)
  86. return true
  87. })
  88. s.meta.SetCreated()
  89. s.meta.toDB(s.db, []byte(s.folder))
  90. }
  91. func (s *FileSet) Drop(device protocol.DeviceID) {
  92. l.Debugf("%s Drop(%v)", s.folder, device)
  93. s.updateMutex.Lock()
  94. defer s.updateMutex.Unlock()
  95. s.db.dropDeviceFolder(device[:], []byte(s.folder), s.meta)
  96. if device == protocol.LocalDeviceID {
  97. s.blockmap.Drop()
  98. s.meta.resetCounts(device)
  99. // We deliberately do not reset the sequence number here. Dropping
  100. // all files for the local device ID only happens in testing - which
  101. // expects the sequence to be retained, like an old Replace() of all
  102. // files would do. However, if we ever did it "in production" we
  103. // would anyway want to retain the sequence for delta indexes to be
  104. // happy.
  105. } else {
  106. // Here, on the other hand, we want to make sure that any file
  107. // announced from the remote is newer than our current sequence
  108. // number.
  109. s.meta.resetAll(device)
  110. }
  111. s.meta.toDB(s.db, []byte(s.folder))
  112. }
  113. func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
  114. l.Debugf("%s Update(%v, [%d])", s.folder, device, len(fs))
  115. // do not modify fs in place, it is still used in outer scope
  116. fs = append([]protocol.FileInfo(nil), fs...)
  117. normalizeFilenames(fs)
  118. s.updateMutex.Lock()
  119. defer s.updateMutex.Unlock()
  120. defer s.meta.toDB(s.db, []byte(s.folder))
  121. if device != protocol.LocalDeviceID {
  122. // Easy case, just update the files and we're done.
  123. s.db.updateFiles([]byte(s.folder), device[:], fs, s.meta)
  124. return
  125. }
  126. // For the local device we have a bunch of metadata to track however...
  127. discards := make([]protocol.FileInfo, 0, len(fs))
  128. updates := make([]protocol.FileInfo, 0, len(fs))
  129. // db.UpdateFiles will sort unchanged files out -> save one db lookup
  130. // filter slice according to https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
  131. oldFs := fs
  132. fs = fs[:0]
  133. var dk []byte
  134. folder := []byte(s.folder)
  135. for _, nf := range oldFs {
  136. dk = s.db.keyer.GenerateDeviceFileKey(dk, folder, device[:], []byte(osutil.NormalizedFilename(nf.Name)))
  137. ef, ok := s.db.getFile(dk)
  138. if ok && ef.Version.Equal(nf.Version) && ef.IsInvalid() == nf.IsInvalid() {
  139. continue
  140. }
  141. nf.Sequence = s.meta.nextSeq(protocol.LocalDeviceID)
  142. fs = append(fs, nf)
  143. if ok {
  144. discards = append(discards, ef)
  145. }
  146. updates = append(updates, nf)
  147. }
  148. // The ordering here is important. We first remove stuff that point to
  149. // files we are going to update, then update them, then add new index
  150. // pointers etc. In addition, we do the discards in reverse order so
  151. // that a reader traversing the sequence index will get a consistent
  152. // view up until the point they meet the writer.
  153. sort.Slice(discards, func(a, b int) bool {
  154. // n.b. "b < a" instead of the usual "a < b"
  155. return discards[b].Sequence < discards[a].Sequence
  156. })
  157. s.blockmap.Discard(discards)
  158. s.db.removeSequences(folder, discards)
  159. s.db.updateFiles([]byte(s.folder), device[:], fs, s.meta)
  160. s.db.addSequences(folder, updates)
  161. s.blockmap.Update(updates)
  162. }
  163. func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
  164. l.Debugf("%s WithNeed(%v)", s.folder, device)
  165. s.db.withNeed([]byte(s.folder), device[:], false, nativeFileIterator(fn))
  166. }
  167. func (s *FileSet) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
  168. l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
  169. s.db.withNeed([]byte(s.folder), device[:], true, nativeFileIterator(fn))
  170. }
  171. func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) {
  172. l.Debugf("%s WithHave(%v)", s.folder, device)
  173. s.db.withHave([]byte(s.folder), device[:], nil, false, nativeFileIterator(fn))
  174. }
  175. func (s *FileSet) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
  176. l.Debugf("%s WithHaveTruncated(%v)", s.folder, device)
  177. s.db.withHave([]byte(s.folder), device[:], nil, true, nativeFileIterator(fn))
  178. }
  179. func (s *FileSet) WithHaveSequence(startSeq int64, fn Iterator) {
  180. l.Debugf("%s WithHaveSequence(%v)", s.folder, startSeq)
  181. s.db.withHaveSequence([]byte(s.folder), startSeq, nativeFileIterator(fn))
  182. }
  183. // Except for an item with a path equal to prefix, only children of prefix are iterated.
  184. // E.g. for prefix "dir", "dir/file" is iterated, but "dir.file" is not.
  185. func (s *FileSet) WithPrefixedHaveTruncated(device protocol.DeviceID, prefix string, fn Iterator) {
  186. l.Debugf(`%s WithPrefixedHaveTruncated(%v, "%v")`, s.folder, device, prefix)
  187. s.db.withHave([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn))
  188. }
  189. func (s *FileSet) WithGlobal(fn Iterator) {
  190. l.Debugf("%s WithGlobal()", s.folder)
  191. s.db.withGlobal([]byte(s.folder), nil, false, nativeFileIterator(fn))
  192. }
  193. func (s *FileSet) WithGlobalTruncated(fn Iterator) {
  194. l.Debugf("%s WithGlobalTruncated()", s.folder)
  195. s.db.withGlobal([]byte(s.folder), nil, true, nativeFileIterator(fn))
  196. }
  197. // Except for an item with a path equal to prefix, only children of prefix are iterated.
  198. // E.g. for prefix "dir", "dir/file" is iterated, but "dir.file" is not.
  199. func (s *FileSet) WithPrefixedGlobalTruncated(prefix string, fn Iterator) {
  200. l.Debugf(`%s WithPrefixedGlobalTruncated("%v")`, s.folder, prefix)
  201. s.db.withGlobal([]byte(s.folder), []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn))
  202. }
  203. func (s *FileSet) Get(device protocol.DeviceID, file string) (protocol.FileInfo, bool) {
  204. f, ok := s.db.getFile(s.db.keyer.GenerateDeviceFileKey(nil, []byte(s.folder), device[:], []byte(osutil.NormalizedFilename(file))))
  205. f.Name = osutil.NativeFilename(f.Name)
  206. return f, ok
  207. }
  208. func (s *FileSet) GetGlobal(file string) (protocol.FileInfo, bool) {
  209. fi, ok := s.db.getGlobal([]byte(s.folder), []byte(osutil.NormalizedFilename(file)), false)
  210. if !ok {
  211. return protocol.FileInfo{}, false
  212. }
  213. f := fi.(protocol.FileInfo)
  214. f.Name = osutil.NativeFilename(f.Name)
  215. return f, true
  216. }
  217. func (s *FileSet) GetGlobalTruncated(file string) (FileInfoTruncated, bool) {
  218. fi, ok := s.db.getGlobal([]byte(s.folder), []byte(osutil.NormalizedFilename(file)), true)
  219. if !ok {
  220. return FileInfoTruncated{}, false
  221. }
  222. f := fi.(FileInfoTruncated)
  223. f.Name = osutil.NativeFilename(f.Name)
  224. return f, true
  225. }
  226. func (s *FileSet) Availability(file string) []protocol.DeviceID {
  227. return s.db.availability([]byte(s.folder), []byte(osutil.NormalizedFilename(file)))
  228. }
  229. func (s *FileSet) Sequence(device protocol.DeviceID) int64 {
  230. return s.meta.Counts(device, 0).Sequence
  231. }
  232. func (s *FileSet) LocalSize() Counts {
  233. local := s.meta.Counts(protocol.LocalDeviceID, 0)
  234. recvOnlyChanged := s.meta.Counts(protocol.LocalDeviceID, protocol.FlagLocalReceiveOnly)
  235. return local.Add(recvOnlyChanged)
  236. }
  237. func (s *FileSet) ReceiveOnlyChangedSize() Counts {
  238. return s.meta.Counts(protocol.LocalDeviceID, protocol.FlagLocalReceiveOnly)
  239. }
  240. func (s *FileSet) GlobalSize() Counts {
  241. global := s.meta.Counts(protocol.GlobalDeviceID, 0)
  242. recvOnlyChanged := s.meta.Counts(protocol.GlobalDeviceID, protocol.FlagLocalReceiveOnly)
  243. return global.Add(recvOnlyChanged)
  244. }
  245. func (s *FileSet) IndexID(device protocol.DeviceID) protocol.IndexID {
  246. id := s.db.getIndexID(device[:], []byte(s.folder))
  247. if id == 0 && device == protocol.LocalDeviceID {
  248. // No index ID set yet. We create one now.
  249. id = protocol.NewIndexID()
  250. s.db.setIndexID(device[:], []byte(s.folder), id)
  251. }
  252. return id
  253. }
  254. func (s *FileSet) SetIndexID(device protocol.DeviceID, id protocol.IndexID) {
  255. if device == protocol.LocalDeviceID {
  256. panic("do not explicitly set index ID for local device")
  257. }
  258. s.db.setIndexID(device[:], []byte(s.folder), id)
  259. }
  260. func (s *FileSet) MtimeFS() *fs.MtimeFS {
  261. prefix := s.db.keyer.GenerateMtimesKey(nil, []byte(s.folder))
  262. kv := NewNamespacedKV(s.db.Lowlevel, string(prefix))
  263. return fs.NewMtimeFS(s.fs, kv)
  264. }
  265. func (s *FileSet) ListDevices() []protocol.DeviceID {
  266. return s.meta.devices()
  267. }
  268. // DropFolder clears out all information related to the given folder from the
  269. // database.
  270. func DropFolder(ll *Lowlevel, folder string) {
  271. db := newInstance(ll)
  272. db.dropFolder([]byte(folder))
  273. db.dropMtimes([]byte(folder))
  274. db.dropFolderMeta([]byte(folder))
  275. bm := NewBlockMap(ll, folder)
  276. bm.Drop()
  277. // Also clean out the folder ID mapping.
  278. db.folderIdx.Delete([]byte(folder))
  279. }
  280. // DropDeltaIndexIDs removes all delta index IDs from the database.
  281. // This will cause a full index transmission on the next connection.
  282. func DropDeltaIndexIDs(db *Lowlevel) {
  283. dbi := db.NewIterator(util.BytesPrefix([]byte{KeyTypeIndexID}), nil)
  284. defer dbi.Release()
  285. for dbi.Next() {
  286. db.Delete(dbi.Key(), nil)
  287. }
  288. }
  289. func normalizeFilenames(fs []protocol.FileInfo) {
  290. for i := range fs {
  291. fs[i].Name = osutil.NormalizedFilename(fs[i].Name)
  292. }
  293. }
  294. func nativeFileIterator(fn Iterator) Iterator {
  295. return func(fi FileIntf) bool {
  296. switch f := fi.(type) {
  297. case protocol.FileInfo:
  298. f.Name = osutil.NativeFilename(f.Name)
  299. return fn(f)
  300. case FileInfoTruncated:
  301. f.Name = osutil.NativeFilename(f.Name)
  302. return fn(f)
  303. default:
  304. panic("unknown interface type")
  305. }
  306. }
  307. }