Selaa lähdekoodia

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

Simon Frei 7 vuotta sitten
vanhempi
sitoutus
881e923105

+ 1 - 1
cmd/syncthing/main.go

@@ -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 {

+ 18 - 7
lib/db/leveldb_dbinstance.go

@@ -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)
+}

+ 37 - 4
lib/db/leveldb_dbinstance_updateschema.go

@@ -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"
+)
+
+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() {
+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() {

+ 27 - 2
lib/db/leveldb_test.go

@@ -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)
+	}
+}