restic/internal/walker/rewriter.go
Michael Eischer f88acd4503 rewrite: Fail if a tree contains an unknown field
In principle, the JSON format of Tree objects is extensible without
requiring a format change. In order to not loose information just play
it safe and reject rewriting trees for which we could loose data.
2022-11-12 19:55:22 +01:00

91 lines
2.3 KiB
Go

package walker
import (
"context"
"fmt"
"path"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
)
// SelectByNameFunc returns true for all items that should be included (files and
// dirs). If false is returned, files are ignored and dirs are not even walked.
type SelectByNameFunc func(item string) bool
type TreeFilterVisitor struct {
SelectByName SelectByNameFunc
PrintExclude func(string)
}
type BlobLoadSaver interface {
restic.BlobSaver
restic.BlobLoader
}
func FilterTree(ctx context.Context, repo BlobLoadSaver, nodepath string, nodeID restic.ID, visitor *TreeFilterVisitor) (newNodeID restic.ID, err error) {
curTree, err := restic.LoadTree(ctx, repo, nodeID)
if err != nil {
return restic.ID{}, err
}
// check that we can properly encode this tree without losing information
// The alternative of using json/Decoder.DisallowUnknownFields() doesn't work as we use
// a custom UnmarshalJSON to decode trees, see also https://github.com/golang/go/issues/41144
testID, err := restic.SaveTree(ctx, repo, curTree)
if err != nil {
return restic.ID{}, err
}
if nodeID != testID {
return restic.ID{}, fmt.Errorf("cannot encode tree at %q without loosing information", nodepath)
}
debug.Log("filterTree: %s, nodeId: %s\n", nodepath, nodeID.Str())
changed := false
tb := restic.NewTreeJSONBuilder()
for _, node := range curTree.Nodes {
path := path.Join(nodepath, node.Name)
if !visitor.SelectByName(path) {
if visitor.PrintExclude != nil {
visitor.PrintExclude(path)
}
changed = true
continue
}
if node.Subtree == nil {
err = tb.AddNode(node)
if err != nil {
return restic.ID{}, err
}
continue
}
newID, err := FilterTree(ctx, repo, path, *node.Subtree, visitor)
if err != nil {
return restic.ID{}, err
}
if !node.Subtree.Equal(newID) {
changed = true
}
node.Subtree = &newID
err = tb.AddNode(node)
if err != nil {
return restic.ID{}, err
}
}
if changed {
tree, err := tb.Finalize()
if err != nil {
return restic.ID{}, err
}
// Save new tree
newTreeID, _, _, err := repo.SaveBlob(ctx, restic.TreeBlob, tree, restic.ID{}, false)
debug.Log("filterTree: save new tree for %s as %v\n", nodepath, newTreeID)
return newTreeID, err
}
return nodeID, nil
}