schemaupdater.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // Copyright (C) 2018 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
  7. import (
  8. "fmt"
  9. "google.golang.org/protobuf/proto"
  10. "github.com/syncthing/syncthing/internal/gen/bep"
  11. "github.com/syncthing/syncthing/lib/protocol"
  12. )
  13. // dbMigrationVersion is for migrations that do not change the schema and thus
  14. // do not put restrictions on downgrades (e.g. for repairs after a bugfix).
  15. const (
  16. dbVersion = 14
  17. dbMigrationVersion = 20
  18. dbMinSyncthingVersion = "v1.9.0"
  19. )
  20. type migration struct {
  21. schemaVersion int64
  22. migrationVersion int64
  23. minSyncthingVersion string
  24. migration func(prevSchema int) error
  25. }
  26. type databaseDowngradeError struct {
  27. minSyncthingVersion string
  28. }
  29. func (e *databaseDowngradeError) Error() string {
  30. if e.minSyncthingVersion == "" {
  31. return "newer Syncthing required"
  32. }
  33. return fmt.Sprintf("Syncthing %s required", e.minSyncthingVersion)
  34. }
  35. // UpdateSchema updates a possibly outdated database to the current schema and
  36. // also does repairs where necessary.
  37. func UpdateSchema(db *Lowlevel) error {
  38. updater := &schemaUpdater{db}
  39. return updater.updateSchema()
  40. }
  41. type schemaUpdater struct {
  42. *Lowlevel
  43. }
  44. func (db *schemaUpdater) updateSchema() error {
  45. // Updating the schema can touch any and all parts of the database. Make
  46. // sure we do not run GC concurrently with schema migrations.
  47. db.gcMut.Lock()
  48. defer db.gcMut.Unlock()
  49. miscDB := NewMiscDataNamespace(db.Lowlevel)
  50. prevVersion, _, err := miscDB.Int64("dbVersion")
  51. if err != nil {
  52. return err
  53. }
  54. if prevVersion > 0 && prevVersion < 14 {
  55. // This is a database version that is too old to be upgraded directly.
  56. // The user will have to upgrade to an older version first.
  57. return fmt.Errorf("database version %d is too old to be upgraded directly; step via Syncthing v1.27.0 to upgrade", prevVersion)
  58. }
  59. if prevVersion > dbVersion {
  60. err := &databaseDowngradeError{}
  61. if minSyncthingVersion, ok, dbErr := miscDB.String("dbMinSyncthingVersion"); dbErr != nil {
  62. return dbErr
  63. } else if ok {
  64. err.minSyncthingVersion = minSyncthingVersion
  65. }
  66. return err
  67. }
  68. prevMigration, _, err := miscDB.Int64("dbMigrationVersion")
  69. if err != nil {
  70. return err
  71. }
  72. // Cover versions before adding `dbMigrationVersion` (== 0) and possible future weirdness.
  73. if prevMigration < prevVersion {
  74. prevMigration = prevVersion
  75. }
  76. if prevVersion == dbVersion && prevMigration >= dbMigrationVersion {
  77. return nil
  78. }
  79. migrations := []migration{
  80. {14, 14, "v1.9.0", db.updateSchemaTo14},
  81. {14, 16, "v1.9.0", db.checkRepairMigration},
  82. {14, 17, "v1.9.0", db.migration17},
  83. {14, 19, "v1.9.0", db.dropAllIndexIDsMigration},
  84. {14, 20, "v1.9.0", db.dropOutgoingIndexIDsMigration},
  85. }
  86. for _, m := range migrations {
  87. if prevMigration < m.migrationVersion {
  88. l.Infof("Running database migration %d...", m.migrationVersion)
  89. if err := m.migration(int(prevVersion)); err != nil {
  90. return fmt.Errorf("failed to do migration %v: %w", m.migrationVersion, err)
  91. }
  92. if err := db.writeVersions(m, miscDB); err != nil {
  93. return fmt.Errorf("failed to write versions after migration %v: %w", m.migrationVersion, err)
  94. }
  95. }
  96. }
  97. if err := db.writeVersions(migration{
  98. schemaVersion: dbVersion,
  99. migrationVersion: dbMigrationVersion,
  100. minSyncthingVersion: dbMinSyncthingVersion,
  101. }, miscDB); err != nil {
  102. return fmt.Errorf("failed to write versions after migrations: %w", err)
  103. }
  104. l.Infoln("Compacting database after migration...")
  105. return db.Compact()
  106. }
  107. func (*schemaUpdater) writeVersions(m migration, miscDB *NamespacedKV) error {
  108. if err := miscDB.PutInt64("dbVersion", m.schemaVersion); err != nil {
  109. return err
  110. }
  111. if err := miscDB.PutString("dbMinSyncthingVersion", m.minSyncthingVersion); err != nil {
  112. return err
  113. }
  114. if err := miscDB.PutInt64("dbMigrationVersion", m.migrationVersion); err != nil {
  115. return err
  116. }
  117. return nil
  118. }
  119. func (db *schemaUpdater) updateSchemaTo14(_ int) error {
  120. // Checks for missing blocks and marks those entries as requiring a
  121. // rehash/being invalid. The db is checked/repaired afterwards, i.e.
  122. // no care is taken to get metadata and sequences right.
  123. // If the corresponding files changed on disk compared to the global
  124. // version, this will cause a conflict.
  125. var key, gk []byte
  126. for _, folderStr := range db.ListFolders() {
  127. folder := []byte(folderStr)
  128. meta := newMetadataTracker(db.keyer, db.evLogger)
  129. meta.counts.Created = 0 // Recalculate metadata afterwards
  130. t, err := db.newReadWriteTransaction(meta.CommitHook(folder))
  131. if err != nil {
  132. return err
  133. }
  134. defer t.close()
  135. key, err = t.keyer.GenerateDeviceFileKey(key, folder, protocol.LocalDeviceID[:], nil)
  136. if err != nil {
  137. return err
  138. }
  139. it, err := t.NewPrefixIterator(key)
  140. if err != nil {
  141. return err
  142. }
  143. defer it.Release()
  144. for it.Next() {
  145. var bepf bep.FileInfo
  146. if err := proto.Unmarshal(it.Value(), &bepf); err != nil {
  147. return err
  148. }
  149. fi := protocol.FileInfoFromDB(&bepf)
  150. if len(fi.Blocks) > 0 || len(fi.BlocksHash) == 0 {
  151. continue
  152. }
  153. key = t.keyer.GenerateBlockListKey(key, fi.BlocksHash)
  154. _, err := t.Get(key)
  155. if err == nil {
  156. continue
  157. }
  158. fi.SetMustRescan()
  159. if err = t.putFile(it.Key(), fi); err != nil {
  160. return err
  161. }
  162. gk, err = t.keyer.GenerateGlobalVersionKey(gk, folder, []byte(fi.Name))
  163. if err != nil {
  164. return err
  165. }
  166. key, err = t.updateGlobal(gk, key, folder, protocol.LocalDeviceID[:], fi, meta)
  167. if err != nil {
  168. return err
  169. }
  170. }
  171. it.Release()
  172. if err = t.Commit(); err != nil {
  173. return err
  174. }
  175. t.close()
  176. }
  177. return nil
  178. }
  179. func (db *schemaUpdater) checkRepairMigration(_ int) error {
  180. for _, folder := range db.ListFolders() {
  181. _, err := db.getMetaAndCheckGCLocked(folder)
  182. if err != nil {
  183. return err
  184. }
  185. }
  186. return nil
  187. }
  188. // migration17 finds all files that were pulled as invalid from an invalid
  189. // global and make sure they get scanned/pulled again.
  190. func (db *schemaUpdater) migration17(prev int) error {
  191. if prev < 16 {
  192. // Issue was introduced in migration to 16
  193. return nil
  194. }
  195. t, err := db.newReadOnlyTransaction()
  196. if err != nil {
  197. return err
  198. }
  199. defer t.close()
  200. for _, folderStr := range db.ListFolders() {
  201. folder := []byte(folderStr)
  202. meta, err := db.loadMetadataTracker(folderStr)
  203. if err != nil {
  204. return err
  205. }
  206. batch := NewFileInfoBatch(func(fs []protocol.FileInfo) error {
  207. return db.updateLocalFiles(folder, fs, meta)
  208. })
  209. var innerErr error
  210. err = t.withHave(folder, protocol.LocalDeviceID[:], nil, false, func(fi protocol.FileInfo) bool {
  211. if fi.IsInvalid() && fi.FileLocalFlags() == 0 {
  212. fi.SetMustRescan()
  213. fi.Version = protocol.Vector{}
  214. batch.Append(fi)
  215. innerErr = batch.FlushIfFull()
  216. return innerErr == nil
  217. }
  218. return true
  219. })
  220. if innerErr != nil {
  221. return innerErr
  222. }
  223. if err != nil {
  224. return err
  225. }
  226. if err := batch.Flush(); err != nil {
  227. return err
  228. }
  229. }
  230. return nil
  231. }
  232. func (db *schemaUpdater) dropAllIndexIDsMigration(_ int) error {
  233. return db.dropIndexIDs()
  234. }
  235. func (db *schemaUpdater) dropOutgoingIndexIDsMigration(_ int) error {
  236. return db.dropOtherDeviceIndexIDs()
  237. }