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]
ldb, err := db.Open(dbFile)
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 {

View File

@ -9,6 +9,7 @@ package db
import (
"bytes"
"encoding/binary"
"fmt"
"os"
"sort"
"strings"
@ -60,31 +61,32 @@ func Open(file string) (*Instance, error) {
// the database and reindexing...
l.Infoln("Database corruption detected, unable to recover. Reinitializing...")
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)
}
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 {
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{
DB: db,
location: location,
}
i.folderIdx = newSmallIndex(i, []byte{KeyTypeFolderIdx})
i.deviceIdx = newSmallIndex(i, []byte{KeyTypeDeviceIdx})
i.updateSchema()
return i
err := i.updateSchema()
return i, err
}
// 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]
}
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
import (
"fmt"
"strings"
"github.com/syncthing/syncthing/lib/protocol"
"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))
prevVersion, _ := miscDB.Int64("dbVersion")
if prevVersion >= dbVersion {
return
if prevVersion > dbVersion {
err := databaseDowngradeError{}
if minSyncthingVersion, ok := miscDB.String("dbMinSyncthingVersion"); ok {
err.minSyncthingVersion = minSyncthingVersion
}
return err
}
if prevVersion == dbVersion {
return nil
}
if prevVersion < 1 {
@ -41,6 +71,9 @@ func (db *Instance) updateSchema() {
}
miscDB.PutInt64("dbVersion", dbVersion)
miscDB.PutString("dbMinSyncthingVersion", dbMinSyncthingVersion)
return nil
}
func (db *Instance) updateSchema0to1() {

View File

@ -8,6 +8,7 @@ package db
import (
"bytes"
"os"
"testing"
"github.com/syncthing/syncthing/lib/fs"
@ -159,7 +160,7 @@ func TestIgnoredFiles(t *testing.T) {
if err != nil {
t.Fatal(err)
}
db := newDBInstance(ldb, "<memory>")
db, _ := newDBInstance(ldb, "<memory>")
fs := NewFileSet("test", fs.NewFilesystem(fs.FilesystemTypeBasic, "."), db)
// The contents of the database are like this:
@ -280,7 +281,7 @@ func TestUpdate0to3(t *testing.T) {
if err != nil {
t.Fatal(err)
}
db := newDBInstance(ldb, "<memory>")
db, _ := newDBInstance(ldb, "<memory>")
folder := []byte(update0to3Folder)
@ -338,3 +339,27 @@ func TestUpdate0to3(t *testing.T) {
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)
}
}