Merge pull request #3488 from MichaelEischer/rebuild-broken-index
Fix `rebuild-index` for damaged index
This commit is contained in:
commit
8fe122d675
3 changed files with 81 additions and 3 deletions
7
changelog/unreleased/pull-3488
Normal file
7
changelog/unreleased/pull-3488
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Bugfix: rebuild-index failed if an index file was damaged
|
||||||
|
|
||||||
|
The `rebuild-index` command failed with an error if an index file was damaged
|
||||||
|
or truncated. This has been fixed. A (slow) workaround is to use
|
||||||
|
`rebuild-index --read-all-packs` or to manually delete the damaged index.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/pull/3488
|
|
@ -73,7 +73,27 @@ func rebuildIndex(opts RebuildIndexOptions, gopts GlobalOptions, repo *repositor
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Verbosef("loading indexes...\n")
|
Verbosef("loading indexes...\n")
|
||||||
err := repo.LoadIndex(gopts.ctx)
|
mi := repository.NewMasterIndex()
|
||||||
|
err := repository.ForAllIndexes(ctx, repo, func(id restic.ID, idx *repository.Index, oldFormat bool, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
Warnf("removing invalid index %v: %v\n", id, err)
|
||||||
|
obsoleteIndexes = append(obsoleteIndexes, id)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mi.Insert(idx)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mi.MergeFinalIndexes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.SetIndex(mi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -1416,7 +1417,7 @@ func TestFindJSON(t *testing.T) {
|
||||||
rtest.Assert(t, matches[0].Hits == 3, "expected hits to show 3 matches (%v)", datafile)
|
rtest.Assert(t, matches[0].Hits == 3, "expected hits to show 3 matches (%v)", datafile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRebuildIndex(t *testing.T) {
|
func testRebuildIndex(t *testing.T, backendTestHook backendWrapper) {
|
||||||
env, cleanup := withTestEnvironment(t)
|
env, cleanup := withTestEnvironment(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
|
@ -1436,8 +1437,10 @@ func TestRebuildIndex(t *testing.T) {
|
||||||
t.Fatalf("did not find hint for rebuild-index command")
|
t.Fatalf("did not find hint for rebuild-index command")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
env.gopts.backendTestHook = backendTestHook
|
||||||
testRunRebuildIndex(t, env.gopts)
|
testRunRebuildIndex(t, env.gopts)
|
||||||
|
|
||||||
|
env.gopts.backendTestHook = nil
|
||||||
out, err = testRunCheckOutput(env.gopts)
|
out, err = testRunCheckOutput(env.gopts)
|
||||||
if len(out) != 0 {
|
if len(out) != 0 {
|
||||||
t.Fatalf("expected no output from the checker, got: %v", out)
|
t.Fatalf("expected no output from the checker, got: %v", out)
|
||||||
|
@ -1448,9 +1451,57 @@ func TestRebuildIndex(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRebuildIndex(t *testing.T) {
|
||||||
|
testRebuildIndex(t, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func TestRebuildIndexAlwaysFull(t *testing.T) {
|
func TestRebuildIndexAlwaysFull(t *testing.T) {
|
||||||
|
indexFull := repository.IndexFull
|
||||||
|
defer func() {
|
||||||
|
repository.IndexFull = indexFull
|
||||||
|
}()
|
||||||
repository.IndexFull = func(*repository.Index) bool { return true }
|
repository.IndexFull = func(*repository.Index) bool { return true }
|
||||||
TestRebuildIndex(t)
|
testRebuildIndex(t, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// indexErrorBackend modifies the first index after reading.
|
||||||
|
type indexErrorBackend struct {
|
||||||
|
restic.Backend
|
||||||
|
lock sync.Mutex
|
||||||
|
hasErred bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *indexErrorBackend) Load(ctx context.Context, h restic.Handle, length int, offset int64, consumer func(rd io.Reader) error) error {
|
||||||
|
return b.Backend.Load(ctx, h, length, offset, func(rd io.Reader) error {
|
||||||
|
// protect hasErred
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
if !b.hasErred && h.Type == restic.IndexFile {
|
||||||
|
b.hasErred = true
|
||||||
|
return consumer(errorReadCloser{rd})
|
||||||
|
}
|
||||||
|
return consumer(rd)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorReadCloser struct {
|
||||||
|
io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (erd errorReadCloser) Read(p []byte) (int, error) {
|
||||||
|
n, err := erd.Reader.Read(p)
|
||||||
|
if n > 0 {
|
||||||
|
p[0] ^= 1
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRebuildIndexDamage(t *testing.T) {
|
||||||
|
testRebuildIndex(t, func(r restic.Backend) (restic.Backend, error) {
|
||||||
|
return &indexErrorBackend{
|
||||||
|
Backend: r,
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type appendOnlyBackend struct {
|
type appendOnlyBackend struct {
|
||||||
|
|
Loading…
Reference in a new issue