diff --git a/internal/files/leveldb.go b/internal/files/leveldb.go index 027ece00..c2e9e318 100644 --- a/internal/files/leveldb.go +++ b/internal/files/leveldb.go @@ -940,3 +940,67 @@ func unmarshalTrunc(bs []byte, truncate bool) (protocol.FileIntf, error) { return tf, err } } + +func ldbCheckGlobals(db *leveldb.DB, folder []byte) { + defer runtime.GC() + + snap, err := db.GetSnapshot() + if err != nil { + panic(err) + } + if debugDB { + l.Debugf("created snapshot %p", snap) + } + defer func() { + if debugDB { + l.Debugf("close snapshot %p", snap) + } + snap.Release() + }() + + start := globalKey(folder, nil) + limit := globalKey(folder, []byte{0xff, 0xff, 0xff, 0xff}) + dbi := snap.NewIterator(&util.Range{Start: start, Limit: limit}, nil) + defer dbi.Release() + + batch := &leveldb.Batch{} + for dbi.Next() { + gk := dbi.Key() + var vl versionList + err := vl.UnmarshalXDR(dbi.Value()) + if err != nil { + panic(err) + } + + // Check the global version list for consistency. An issue in previous + // versions of goleveldb could result in reordered writes so that + // there are global entries pointing to no longer existing files. Here + // we find those and clear them out. + + name := globalKeyName(gk) + var newVL versionList + for _, version := range vl.versions { + fk := deviceKey(folder, version.device, name) + if debugDB { + l.Debugf("snap.Get %p %x", snap, fk) + } + _, err := snap.Get(fk, nil) + if err == leveldb.ErrNotFound { + continue + } + if err != nil { + panic(err) + } + newVL.versions = append(newVL.versions, version) + } + + if len(newVL.versions) != len(vl.versions) { + l.Infof("db repair: rewriting global version list for %x %x", gk[1:1+64], gk[1+64:]) + batch.Put(dbi.Key(), newVL.MustMarshalXDR()) + } + } + if debugDB { + l.Infoln("db check completed for %q", folder) + } + db.Write(batch, nil) +} diff --git a/internal/files/set.go b/internal/files/set.go index 1b7e8296..fd0d2152 100644 --- a/internal/files/set.go +++ b/internal/files/set.go @@ -53,6 +53,8 @@ func NewSet(folder string, db *leveldb.DB) *Set { blockmap: NewBlockMap(db, folder), } + ldbCheckGlobals(db, []byte(folder)) + var deviceID protocol.DeviceID ldbWithAllFolderTruncated(db, []byte(folder), func(device []byte, f protocol.FileInfoTruncated) bool { copy(deviceID[:], device)