|
|
@@ -0,0 +1,242 @@
|
|
|
+// Copyright (C) 2018 The Syncthing Authors.
|
|
|
+//
|
|
|
+// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
+// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
+// You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
+
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "encoding/binary"
|
|
|
+ "fmt"
|
|
|
+
|
|
|
+ "github.com/syncthing/syncthing/lib/db"
|
|
|
+ "github.com/syncthing/syncthing/lib/protocol"
|
|
|
+)
|
|
|
+
|
|
|
+type fileInfoKey struct {
|
|
|
+ folder uint32
|
|
|
+ device uint32
|
|
|
+ name string
|
|
|
+}
|
|
|
+
|
|
|
+type globalKey struct {
|
|
|
+ folder uint32
|
|
|
+ name string
|
|
|
+}
|
|
|
+
|
|
|
+type sequenceKey struct {
|
|
|
+ folder uint32
|
|
|
+ sequence uint64
|
|
|
+}
|
|
|
+
|
|
|
+func idxck(ldb *db.Lowlevel) (success bool) {
|
|
|
+ folders := make(map[uint32]string)
|
|
|
+ devices := make(map[uint32]string)
|
|
|
+ deviceToIDs := make(map[string]uint32)
|
|
|
+ fileInfos := make(map[fileInfoKey]protocol.FileInfo)
|
|
|
+ globals := make(map[globalKey]db.VersionList)
|
|
|
+ sequences := make(map[sequenceKey]string)
|
|
|
+ needs := make(map[globalKey]struct{})
|
|
|
+ var localDeviceKey uint32
|
|
|
+ success = true
|
|
|
+
|
|
|
+ it := ldb.NewIterator(nil, nil)
|
|
|
+ for it.Next() {
|
|
|
+ key := it.Key()
|
|
|
+ switch key[0] {
|
|
|
+ case db.KeyTypeDevice:
|
|
|
+ folder := binary.BigEndian.Uint32(key[1:])
|
|
|
+ device := binary.BigEndian.Uint32(key[1+4:])
|
|
|
+ name := nulString(key[1+4+4:])
|
|
|
+
|
|
|
+ var f protocol.FileInfo
|
|
|
+ err := f.Unmarshal(it.Value())
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("Unable to unmarshal FileInfo:", err)
|
|
|
+ success = false
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ fileInfos[fileInfoKey{folder, device, name}] = f
|
|
|
+
|
|
|
+ case db.KeyTypeGlobal:
|
|
|
+ folder := binary.BigEndian.Uint32(key[1:])
|
|
|
+ name := nulString(key[1+4:])
|
|
|
+ var flv db.VersionList
|
|
|
+ if err := flv.Unmarshal(it.Value()); err != nil {
|
|
|
+ fmt.Println("Unable to unmarshal VersionList:", err)
|
|
|
+ success = false
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ globals[globalKey{folder, name}] = flv
|
|
|
+
|
|
|
+ case db.KeyTypeFolderIdx:
|
|
|
+ key := binary.BigEndian.Uint32(it.Key()[1:])
|
|
|
+ folders[key] = string(it.Value())
|
|
|
+
|
|
|
+ case db.KeyTypeDeviceIdx:
|
|
|
+ key := binary.BigEndian.Uint32(it.Key()[1:])
|
|
|
+ devices[key] = string(it.Value())
|
|
|
+ deviceToIDs[string(it.Value())] = key
|
|
|
+ if bytes.Equal(it.Value(), protocol.LocalDeviceID[:]) {
|
|
|
+ localDeviceKey = key
|
|
|
+ }
|
|
|
+
|
|
|
+ case db.KeyTypeSequence:
|
|
|
+ folder := binary.BigEndian.Uint32(key[1:])
|
|
|
+ seq := binary.BigEndian.Uint64(key[5:])
|
|
|
+ val := it.Value()
|
|
|
+ sequences[sequenceKey{folder, seq}] = string(val[9:])
|
|
|
+
|
|
|
+ case db.KeyTypeNeed:
|
|
|
+ folder := binary.BigEndian.Uint32(key[1:])
|
|
|
+ name := nulString(key[1+4:])
|
|
|
+ needs[globalKey{folder, name}] = struct{}{}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if localDeviceKey == 0 {
|
|
|
+ fmt.Println("Missing key for local device in device index (bailing out)")
|
|
|
+ success = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ for fk, fi := range fileInfos {
|
|
|
+ if fk.name != fi.Name {
|
|
|
+ fmt.Printf("Mismatching FileInfo name, %q (key) != %q (actual)\n", fk.name, fi.Name)
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+
|
|
|
+ folder := folders[fk.folder]
|
|
|
+ if folder == "" {
|
|
|
+ fmt.Printf("Unknown folder ID %d for FileInfo %q\n", fk.folder, fk.name)
|
|
|
+ success = false
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if devices[fk.device] == "" {
|
|
|
+ fmt.Printf("Unknown device ID %d for FileInfo %q, folder %q\n", fk.folder, fk.name, folder)
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+
|
|
|
+ if fk.device == localDeviceKey {
|
|
|
+ name, ok := sequences[sequenceKey{fk.folder, uint64(fi.Sequence)}]
|
|
|
+ if !ok {
|
|
|
+ fmt.Printf("Sequence entry missing for FileInfo %q, folder %q, seq %d\n", fi.Name, folder, fi.Sequence)
|
|
|
+ success = false
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if name != fi.Name {
|
|
|
+ fmt.Printf("Sequence entry refers to wrong name, %q (seq) != %q (FileInfo), folder %q, seq %d\n", name, fi.Name, folder, fi.Sequence)
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for gk, vl := range globals {
|
|
|
+ folder := folders[gk.folder]
|
|
|
+ if folder == "" {
|
|
|
+ fmt.Printf("Unknown folder ID %d for VersionList %q\n", gk.folder, gk.name)
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+ for i, fv := range vl.Versions {
|
|
|
+ dev, ok := deviceToIDs[string(fv.Device)]
|
|
|
+ if !ok {
|
|
|
+ fmt.Printf("VersionList %q, folder %q refers to unknown device %q\n", gk.name, folder, fv.Device)
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+ fi, ok := fileInfos[fileInfoKey{gk.folder, dev, gk.name}]
|
|
|
+ if !ok {
|
|
|
+ fmt.Printf("VersionList %q, folder %q, entry %d refers to unknown FileInfo\n", gk.name, folder, i)
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+ if !fi.Version.Equal(fv.Version) {
|
|
|
+ fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo version mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, fv.Version, fi.Version)
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+ if fi.IsInvalid() != fv.Invalid {
|
|
|
+ fmt.Printf("VersionList %q, folder %q, entry %d, FileInfo invalid mismatch, %v (VersionList) != %v (FileInfo)\n", gk.name, folder, i, fv.Invalid, fi.IsInvalid())
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // If we need this file we should have a need entry for it. False
|
|
|
+ // positives from needsLocally for deleted files, where we might
|
|
|
+ // legitimately lack an entry if we never had it, and ignored files.
|
|
|
+ if needsLocally(vl) {
|
|
|
+ _, ok := needs[gk]
|
|
|
+ if !ok {
|
|
|
+ dev, _ := deviceToIDs[string(vl.Versions[0].Device)]
|
|
|
+ fi, _ := fileInfos[fileInfoKey{gk.folder, dev, gk.name}]
|
|
|
+ if !fi.IsDeleted() && !fi.IsIgnored() {
|
|
|
+ fmt.Printf("Missing need entry for needed file %q, folder %q\n", gk.name, folder)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ seenSeq := make(map[fileInfoKey]uint64)
|
|
|
+ for sk, name := range sequences {
|
|
|
+ folder := folders[sk.folder]
|
|
|
+ if folder == "" {
|
|
|
+ fmt.Printf("Unknown folder ID %d for sequence entry %d, %q\n", sk.folder, sk.sequence, name)
|
|
|
+ success = false
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if prev, ok := seenSeq[fileInfoKey{folder: sk.folder, name: name}]; ok {
|
|
|
+ fmt.Printf("Duplicate sequence entry for %q, folder %q, seq %d (prev %d)\n", name, folder, sk.sequence, prev)
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+ seenSeq[fileInfoKey{folder: sk.folder, name: name}] = sk.sequence
|
|
|
+
|
|
|
+ fi, ok := fileInfos[fileInfoKey{sk.folder, localDeviceKey, name}]
|
|
|
+ if !ok {
|
|
|
+ fmt.Printf("Missing FileInfo for sequence entry %d, folder %q, %q\n", sk.sequence, folder, name)
|
|
|
+ success = false
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ if fi.Sequence != int64(sk.sequence) {
|
|
|
+ fmt.Printf("Sequence mismatch for %q, folder %q, %d (key) != %d (FileInfo)\n", name, folder, sk.sequence, fi.Sequence)
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for nk := range needs {
|
|
|
+ folder := folders[nk.folder]
|
|
|
+ if folder == "" {
|
|
|
+ fmt.Printf("Unknown folder ID %d for need entry %q\n", nk.folder, nk.name)
|
|
|
+ success = false
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ vl, ok := globals[nk]
|
|
|
+ if !ok {
|
|
|
+ fmt.Printf("Missing global for need entry %q, folder %q\n", nk.name, folder)
|
|
|
+ success = false
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if !needsLocally(vl) {
|
|
|
+ fmt.Printf("Need entry for file we don't need, %q, folder %q\n", nk.name, folder)
|
|
|
+ success = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+func needsLocally(vl db.VersionList) bool {
|
|
|
+ var lv *protocol.Vector
|
|
|
+ for _, fv := range vl.Versions {
|
|
|
+ if bytes.Equal(fv.Device, protocol.LocalDeviceID[:]) {
|
|
|
+ lv = &fv.Version
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if lv == nil {
|
|
|
+ return true // proviosinally, it looks like we need the file
|
|
|
+ }
|
|
|
+ return !lv.GreaterEqual(vl.Versions[0].Version)
|
|
|
+}
|