lib/db: Handle indirection error repairing sequences (fixes #7026) (#7525)

This commit is contained in:
Simon Frei 2021-04-05 10:24:16 +02:00 committed by GitHub
parent 96ba5c2b23
commit f30f9c50f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 20 deletions

View File

@ -516,7 +516,7 @@ func TestCheckGlobals(t *testing.T) {
}
// Clean up global entry of the now missing file
if repaired, err := db.checkGlobals([]byte(fs.folder)); err != nil {
if repaired, err := db.checkGlobals(fs.folder); err != nil {
t.Fatal(err)
} else if repaired != 1 {
t.Error("Expected 1 repaired global item, got", repaired)

View File

@ -441,13 +441,14 @@ func (db *Lowlevel) dropDeviceFolder(device, folder []byte, meta *metadataTracke
return t.Commit()
}
func (db *Lowlevel) checkGlobals(folder []byte) (int, error) {
func (db *Lowlevel) checkGlobals(folderStr string) (int, error) {
t, err := db.newReadWriteTransaction()
if err != nil {
return 0, err
}
defer t.close()
folder := []byte(folderStr)
key, err := db.keyer.GenerateGlobalVersionKey(nil, folder, nil)
if err != nil {
return 0, err
@ -509,7 +510,7 @@ func (db *Lowlevel) checkGlobals(folder []byte) (int, error) {
return 0, err
}
l.Debugf("global db check completed for %q", folder)
l.Debugf("global db check completed for %v", folder)
return fixed, t.Commit()
}
@ -834,8 +835,10 @@ func (b *bloomFilter) hash(id []byte) uint64 {
// checkRepair checks folder metadata and sequences for miscellaneous errors.
func (db *Lowlevel) checkRepair() error {
db.gcMut.RLock()
defer db.gcMut.RUnlock()
for _, folder := range db.ListFolders() {
if _, err := db.getMetaAndCheck(folder); err != nil {
if _, err := db.getMetaAndCheckGCLocked(folder); err != nil {
return err
}
}
@ -858,6 +861,14 @@ func (db *Lowlevel) getMetaAndCheckGCLocked(folder string) (*metadataTracker, er
l.Infof("Repaired %d local need entries for folder %v in database", fixed, folder)
}
fixed, err = db.checkGlobals(folder)
if err != nil {
return nil, fmt.Errorf("checking globals: %w", err)
}
if fixed != 0 {
l.Infof("Repaired %d global entries for folder %v in database", fixed, folder)
}
meta, err := db.recalcMeta(folder)
if err != nil {
return nil, fmt.Errorf("recalculating metadata: %w", err)
@ -869,6 +880,10 @@ func (db *Lowlevel) getMetaAndCheckGCLocked(folder string) (*metadataTracker, er
}
if fixed != 0 {
l.Infof("Repaired %d sequence entries for folder %v in database", fixed, folder)
meta, err = db.recalcMeta(folder)
if err != nil {
return nil, fmt.Errorf("recalculating metadata: %w", err)
}
}
return meta, nil
@ -906,11 +921,6 @@ func (db *Lowlevel) recalcMeta(folderStr string) (*metadataTracker, error) {
folder := []byte(folderStr)
meta := newMetadataTracker(db.keyer, db.evLogger)
if fixed, err := db.checkGlobals(folder); err != nil {
return nil, fmt.Errorf("checking globals: %w", err)
} else if fixed > 0 {
l.Infof("Repaired %d global entries for folder %v in database", fixed, folderStr)
}
t, err := db.newReadWriteTransaction(meta.CommitHook(folder))
if err != nil {
@ -1022,6 +1032,34 @@ func (db *Lowlevel) repairSequenceGCLocked(folderStr string, meta *metadataTrack
for it.Next() {
intf, err := t.unmarshalTrunc(it.Value(), false)
if err != nil {
// Delete local items with invalid indirected blocks/versions.
// They will be rescanned.
var ierr *blocksIndirectionError
if ok := errors.As(err, &ierr); ok && backend.IsNotFound(err) {
intf, err = t.unmarshalTrunc(it.Value(), true)
if err != nil {
return 0, err
}
name := []byte(intf.FileName())
gk, err := t.keyer.GenerateGlobalVersionKey(nil, folder, name)
if err != nil {
return 0, err
}
_, err = t.removeFromGlobal(gk, nil, folder, protocol.LocalDeviceID[:], name, nil)
if err != nil {
return 0, err
}
sk, err = db.keyer.GenerateSequenceKey(sk, folder, intf.SequenceNo())
if err != nil {
return 0, err
}
if err := t.Delete(sk); err != nil {
return 0, err
}
if err := t.Delete(it.Key()); err != nil {
return 0, err
}
}
return 0, err
}
fi := intf.(protocol.FileInfo)

View File

@ -100,7 +100,6 @@ func (db *schemaUpdater) updateSchema() error {
{11, 11, "v1.6.0", db.updateSchemaTo11},
{13, 13, "v1.7.0", db.updateSchemaTo13},
{14, 14, "v1.9.0", db.updateSchemaTo14},
{14, 15, "v1.9.0", db.migration15},
{14, 16, "v1.9.0", db.checkRepairMigration},
}
@ -774,15 +773,6 @@ func (db *schemaUpdater) updateSchemaTo14(_ int) error {
return nil
}
func (db *schemaUpdater) migration15(_ int) error {
for _, folder := range db.ListFolders() {
if _, err := db.recalcMeta(folder); err != nil {
return err
}
}
return nil
}
func (db *schemaUpdater) checkRepairMigration(_ int) error {
for _, folder := range db.ListFolders() {
_, err := db.getMetaAndCheckGCLocked(folder)

View File

@ -103,6 +103,18 @@ func (t readOnlyTransaction) unmarshalTrunc(bs []byte, trunc bool) (protocol.Fil
return fi, nil
}
type blocksIndirectionError struct {
err error
}
func (e *blocksIndirectionError) Error() string {
return fmt.Sprintf("filling Blocks: %v", e.err)
}
func (e *blocksIndirectionError) Unwrap() error {
return e.err
}
// fillFileInfo follows the (possible) indirection of blocks and version
// vector and fills it out.
func (t readOnlyTransaction) fillFileInfo(fi *protocol.FileInfo) error {
@ -113,7 +125,7 @@ func (t readOnlyTransaction) fillFileInfo(fi *protocol.FileInfo) error {
key = t.keyer.GenerateBlockListKey(key, fi.BlocksHash)
bs, err := t.Get(key)
if err != nil {
return fmt.Errorf("filling Blocks: %w", err)
return &blocksIndirectionError{err}
}
var bl BlockList
if err := bl.Unmarshal(bs); err != nil {