From f1c76a82861f59e4fd03c7696a669553ae9d2f96 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Wed, 5 Jun 2024 22:01:55 +0200 Subject: [PATCH] restore: fix corrupted sparse files --- internal/restorer/filerestorer.go | 6 +++++ internal/restorer/restorer_test.go | 38 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/internal/restorer/filerestorer.go b/internal/restorer/filerestorer.go index 1e67debeb..f57d58598 100644 --- a/internal/restorer/filerestorer.go +++ b/internal/restorer/filerestorer.go @@ -153,6 +153,12 @@ func (r *fileRestorer) restoreFiles(ctx context.Context) error { // in addition, a short chunk will never match r.zeroChunk which would prevent sparseness for short files file.sparse = r.sparse } + if file.state != nil { + // The restorer currently cannot punch new holes into an existing files. + // Thus sections that contained data but should be sparse after restoring + // the snapshot would still contain the old data resulting in a corrupt restore. + file.sparse = false + } if err != nil { // repository index is messed up, can't do anything diff --git a/internal/restorer/restorer_test.go b/internal/restorer/restorer_test.go index 0d43ffa9d..25ce668db 100644 --- a/internal/restorer/restorer_test.go +++ b/internal/restorer/restorer_test.go @@ -895,6 +895,44 @@ func TestRestorerSparseFiles(t *testing.T) { len(zeros), blocks, 100*sparsity) } +func TestRestorerSparseOverwrite(t *testing.T) { + baseSnapshot := Snapshot{ + Nodes: map[string]Node{ + "foo": File{Data: "content: new\n"}, + }, + } + var zero [14]byte + sparseSnapshot := Snapshot{ + Nodes: map[string]Node{ + "foo": File{Data: string(zero[:])}, + }, + } + + repo := repository.TestRepository(t) + tempdir := filepath.Join(rtest.TempDir(t), "target") + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // base snapshot + sn, id := saveSnapshot(t, repo, baseSnapshot, noopGetGenericAttributes) + t.Logf("base snapshot saved as %v", id.Str()) + + res := NewRestorer(repo, sn, Options{Sparse: true}) + err := res.RestoreTo(ctx, tempdir) + rtest.OK(t, err) + + // sparse snapshot + sn, id = saveSnapshot(t, repo, sparseSnapshot, noopGetGenericAttributes) + t.Logf("base snapshot saved as %v", id.Str()) + + res = NewRestorer(repo, sn, Options{Sparse: true, Overwrite: OverwriteAlways}) + err = res.RestoreTo(ctx, tempdir) + rtest.OK(t, err) + files, err := res.VerifyFiles(ctx, tempdir) + rtest.OK(t, err) + rtest.Equals(t, 1, files, "unexpected number of verified files") +} + func TestRestorerOverwriteBehavior(t *testing.T) { baseTime := time.Now() baseSnapshot := Snapshot{