cmd/syncthing, lib/db: Abort execution if db version is too high (fixes #4994) (#5022)

This commit is contained in:
Simon Frei 2018-06-26 11:40:34 +02:00 committed by Jakob Borg
parent ef5ca0c218
commit 881e923105
4 changed files with 83 additions and 14 deletions

View File

@ -702,7 +702,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
dbFile := locations[locDatabase] dbFile := locations[locDatabase]
ldb, err := db.Open(dbFile) ldb, err := db.Open(dbFile)
if err != nil { if err != nil {
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?") l.Fatalln("Error opening database:", err)
} }
if runtimeOptions.resetDeltaIdxs { if runtimeOptions.resetDeltaIdxs {

View File

@ -9,6 +9,7 @@ package db
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt"
"os" "os"
"sort" "sort"
"strings" "strings"
@ -60,31 +61,32 @@ func Open(file string) (*Instance, error) {
// the database and reindexing... // the database and reindexing...
l.Infoln("Database corruption detected, unable to recover. Reinitializing...") l.Infoln("Database corruption detected, unable to recover. Reinitializing...")
if err := os.RemoveAll(file); err != nil { if err := os.RemoveAll(file); err != nil {
return nil, err return nil, errorSuggestion{err, "failed to delete corrupted database"}
} }
db, err = leveldb.OpenFile(file, opts) db, err = leveldb.OpenFile(file, opts)
} }
if err != nil { if err != nil {
return nil, err return nil, errorSuggestion{err, "is another instance of Syncthing running?"}
} }
return newDBInstance(db, file), nil return newDBInstance(db, file)
} }
func OpenMemory() *Instance { func OpenMemory() *Instance {
db, _ := leveldb.Open(storage.NewMemStorage(), nil) db, _ := leveldb.Open(storage.NewMemStorage(), nil)
return newDBInstance(db, "<memory>") ldb, _ := newDBInstance(db, "<memory>")
return ldb
} }
func newDBInstance(db *leveldb.DB, location string) *Instance { func newDBInstance(db *leveldb.DB, location string) (*Instance, error) {
i := &Instance{ i := &Instance{
DB: db, DB: db,
location: location, location: location,
} }
i.folderIdx = newSmallIndex(i, []byte{KeyTypeFolderIdx}) i.folderIdx = newSmallIndex(i, []byte{KeyTypeFolderIdx})
i.deviceIdx = newSmallIndex(i, []byte{KeyTypeDeviceIdx}) i.deviceIdx = newSmallIndex(i, []byte{KeyTypeDeviceIdx})
i.updateSchema() err := i.updateSchema()
return i return i, err
} }
// Committed returns the number of items committed to the database since startup // Committed returns the number of items committed to the database since startup
@ -935,3 +937,12 @@ func resize(k []byte, reqLen int) []byte {
} }
return k[:reqLen] return k[:reqLen]
} }
type errorSuggestion struct {
inner error
suggestion string
}
func (e errorSuggestion) Error() string {
return fmt.Sprintf("%s (%s)", e.inner.Error(), e.suggestion)
}

View File

@ -7,20 +7,50 @@
package db package db
import ( import (
"fmt"
"strings" "strings"
"github.com/syncthing/syncthing/lib/protocol" "github.com/syncthing/syncthing/lib/protocol"
"github.com/syndtr/goleveldb/leveldb/util" "github.com/syndtr/goleveldb/leveldb/util"
) )
const dbVersion = 5 // List of all dbVersion to dbMinSyncthingVersion pairs for convenience
// 0: v0.14.0
// 1: v0.14.46
// 2: v0.14.48
// 3: v0.14.49
// 4: v0.14.49
// 5: v0.14.50
const (
dbVersion = 5
dbMinSyncthingVersion = "v0.14.49"
)
func (db *Instance) updateSchema() { type databaseDowngradeError struct {
minSyncthingVersion string
}
func (e databaseDowngradeError) Error() string {
if e.minSyncthingVersion == "" {
return "newer Syncthing required"
}
return fmt.Sprintf("Syncthing %s required", e.minSyncthingVersion)
}
func (db *Instance) updateSchema() error {
miscDB := NewNamespacedKV(db, string(KeyTypeMiscData)) miscDB := NewNamespacedKV(db, string(KeyTypeMiscData))
prevVersion, _ := miscDB.Int64("dbVersion") prevVersion, _ := miscDB.Int64("dbVersion")
if prevVersion >= dbVersion { if prevVersion > dbVersion {
return err := databaseDowngradeError{}
if minSyncthingVersion, ok := miscDB.String("dbMinSyncthingVersion"); ok {
err.minSyncthingVersion = minSyncthingVersion
}
return err
}
if prevVersion == dbVersion {
return nil
} }
if prevVersion < 1 { if prevVersion < 1 {
@ -41,6 +71,9 @@ func (db *Instance) updateSchema() {
} }
miscDB.PutInt64("dbVersion", dbVersion) miscDB.PutInt64("dbVersion", dbVersion)
miscDB.PutString("dbMinSyncthingVersion", dbMinSyncthingVersion)
return nil
} }
func (db *Instance) updateSchema0to1() { func (db *Instance) updateSchema0to1() {

View File

@ -8,6 +8,7 @@ package db
import ( import (
"bytes" "bytes"
"os"
"testing" "testing"
"github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/fs"
@ -159,7 +160,7 @@ func TestIgnoredFiles(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
db := newDBInstance(ldb, "<memory>") db, _ := newDBInstance(ldb, "<memory>")
fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db) fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
// The contents of the database are like this: // The contents of the database are like this:
@ -280,7 +281,7 @@ func TestUpdate0to3(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
db := newDBInstance(ldb, "<memory>") db, _ := newDBInstance(ldb, "<memory>")
folder := []byte(update0to3Folder) folder := []byte(update0to3Folder)
@ -338,3 +339,27 @@ func TestUpdate0to3(t *testing.T) {
t.Errorf(`Missing needed file "%v"`, n) t.Errorf(`Missing needed file "%v"`, n)
} }
} }
func TestDowngrade(t *testing.T) {
loc := "testdata/downgrade.db"
db, err := Open(loc)
if err != nil {
t.Fatal(err)
}
defer func() {
db.Close()
os.RemoveAll(loc)
}()
miscDB := NewNamespacedKV(db, string(KeyTypeMiscData))
miscDB.PutInt64("dbVersion", dbVersion+1)
l.Infoln(dbVersion)
db.Close()
db, err = Open(loc)
if err, ok := err.(databaseDowngradeError); !ok {
t.Fatal("Expected error due to database downgrade, got", err)
} else if err.minSyncthingVersion != dbMinSyncthingVersion {
t.Fatalf("Error has %v as min Syncthing version, expected %v", err.minSyncthingVersion, dbMinSyncthingVersion)
}
}