1
0
Fork 0
mirror of https://github.com/restic/restic.git synced 2025-03-09 00:00:02 +01:00
This commit is contained in:
phillipp 2025-02-27 03:40:41 +00:00 committed by GitHub
commit f6eca81e70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 193 additions and 63 deletions

View file

@ -76,56 +76,138 @@ func splitPath(p string) []string {
return append(s, f)
}
func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoader, prefix string, pathComponents []string, d *dump.Dumper, canWriteArchiveFunc func() error) error {
func preparePathList(pathComponentsList []string) [][]string {
pathSet := make(map[string]struct{})
for _, p := range pathComponentsList {
p = filepath.Clean(p)
splittedPath := splitPath(p)
if len(splittedPath) == 1 && splittedPath[0] == "" {
return [][]string{{""}}
}
pathStr := path.Join(splittedPath...)
pathSet[pathStr] = struct{}{}
}
// Use a set to store the filtered paths
filteredSet := make(map[string][]string)
for _, p := range pathComponentsList {
splittedPath := splitPath(path.Clean(p))
isCovered := false
// Check if any prefix of the path exists in the pathSet
for i := len(splittedPath) - 1; i >= 1; i-- {
prefixPath := path.Join(splittedPath[0:i]...)
if _, exists := pathSet[prefixPath]; exists {
isCovered = true
break
}
}
if !isCovered {
pathStr := path.Join(splittedPath...)
filteredSet[pathStr] = splittedPath
}
}
// Convert the values of the set to a list
filteredList := make([][]string, 0, len(filteredSet))
for _, splittedPath := range filteredSet {
filteredList = append(filteredList, splittedPath)
}
return filteredList
}
func filterPathComponents(pathComponentsList [][]string, prefix string) [][]string {
var filteredList [][]string
for _, pathComponents := range pathComponentsList {
if len(pathComponents) > 1 && pathComponents[0] == prefix {
filteredList = append(filteredList, pathComponents[1:])
}
}
return filteredList
}
func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoader, prefix string, pathComponentsList [][]string, d *dump.Dumper, canWriteArchiveFunc func() error) error {
// If we print / we need to assume that there are multiple nodes at that
// level in the tree.
if pathComponents[0] == "" {
if pathComponentsList[0][0] == "" {
if err := canWriteArchiveFunc(); err != nil {
return err
}
return d.DumpTree(ctx, tree, "/")
}
item := filepath.Join(prefix, pathComponents[0])
l := len(pathComponents)
// make a set of the nodes that we dumped
dumpedNodes := make(map[string]struct{})
for _, node := range tree.Nodes {
if ctx.Err() != nil {
return ctx.Err()
}
// If dumping something in the highest level it will just take the
// first item it finds and dump that according to the switch case below.
if node.Name == pathComponents[0] {
switch {
case l == 1 && node.Type == restic.NodeTypeFile:
return d.WriteNode(ctx, node)
case l > 1 && node.Type == restic.NodeTypeDir:
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
if err != nil {
return errors.Wrapf(err, "cannot load subtree for %q", item)
out:
// iterate over all pathComponents
for _, pathComponents := range pathComponentsList {
item := filepath.Join(prefix, pathComponents[0])
l := len(pathComponents)
// If dumping something in the highest level it will just take the
// first item it finds and dump that according to the switch case below.
if node.Name == pathComponents[0] {
switch {
case l == 1 && node.Type == restic.NodeTypeFile:
d.WriteNode(ctx, node)
dumpedNodes[node.Name] = struct{}{}
break out
case l > 1 && node.Type == restic.NodeTypeDir:
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
if err != nil {
return errors.Wrapf(err, "cannot load subtree for %q", item)
}
newComponentsList := filterPathComponents(pathComponentsList, node.Name)
err = printFromTree(ctx, subtree, repo, item, newComponentsList, d, canWriteArchiveFunc)
if err != nil {
return err
}
dumpedNodes[node.Name] = struct{}{}
break out
case node.Type == restic.NodeTypeDir:
if err := canWriteArchiveFunc(); err != nil {
return err
}
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
if err != nil {
return err
}
d.DumpTree(ctx, subtree, item)
dumpedNodes[node.Name] = struct{}{}
break out
case l > 1:
return fmt.Errorf("%q should be a dir, but is a %q", item, node.Type)
case node.Type != restic.NodeTypeFile:
return fmt.Errorf("%q should be a file, but is a %q", item, node.Type)
}
return printFromTree(ctx, subtree, repo, item, pathComponents[1:], d, canWriteArchiveFunc)
case node.Type == restic.NodeTypeDir:
if err := canWriteArchiveFunc(); err != nil {
return err
}
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
if err != nil {
return err
}
return d.DumpTree(ctx, subtree, item)
case l > 1:
return fmt.Errorf("%q should be a dir, but is a %q", item, node.Type)
case node.Type != restic.NodeTypeFile:
return fmt.Errorf("%q should be a file, but is a %q", item, node.Type)
}
}
}
return fmt.Errorf("path %q not found in snapshot", item)
// Check if all paths were found in the snapshot
for _, item := range pathComponentsList {
if _, ok := dumpedNodes[item[0]]; !ok {
return fmt.Errorf("path %q not found in snapshot", filepath.Join(prefix, item[0]))
}
}
return nil
}
func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []string) error {
if len(args) != 2 {
if len(args) < 2 {
return errors.Fatal("no file and no snapshot ID specified")
}
@ -136,11 +218,8 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
}
snapshotIDString := args[0]
pathToPrint := args[1]
debug.Log("dump file %q from %q", pathToPrint, snapshotIDString)
splittedPath := splitPath(path.Clean(pathToPrint))
debug.Log("dump file started for %d paths from %q", (len(args) - 1), snapshotIDString)
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, gopts.NoLock)
if err != nil {
@ -190,7 +269,11 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
}
d := dump.New(opts.Archive, repo, outputFileWriter)
err = printFromTree(ctx, tree, repo, "/", splittedPath, d, canWriteArchiveFunc)
defer d.Close()
splittedPathList := preparePathList(args[1:])
err = printFromTree(ctx, tree, repo, "/", splittedPathList, d, canWriteArchiveFunc)
if err != nil {
return errors.Fatalf("cannot dump file: %v", err)
}

View file

@ -25,3 +25,31 @@ func TestDumpSplitPath(t *testing.T) {
rtest.Equals(t, path.result, parts)
}
}
func TestDumpPreparePathList(t *testing.T) {
testPaths := []struct {
paths []string
result [][]string
}{
{
[]string{"test", "test/dir", "test/dir/sub"},
[][]string{{"test"}},
},
{
[]string{"/", "man", "doc", "doc/icons"},
[][]string{{""}},
},
{
[]string{"doc"},
[][]string{{"doc"}},
},
{
[]string{"man/", "doc", "doc/icons"},
[][]string{{"man"}, {"doc"}},
},
}
for _, path := range testPaths {
parts := preparePathList(path.paths)
rtest.Equals(t, path.result, parts)
}
}

View file

@ -1,6 +1,9 @@
package dump
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"context"
"io"
"path"
@ -14,10 +17,13 @@ import (
// A Dumper writes trees and files from a repository to a Writer
// in an archive format.
type Dumper struct {
cache *bloblru.Cache
format string
repo restic.Loader
w io.Writer
cache *bloblru.Cache
format string
repo restic.Loader
w io.Writer
gzipWriter *gzip.Writer
zipWriter *zip.Writer
tarWriter *tar.Writer
}
func New(format string, repo restic.Loader, w io.Writer) *Dumper {
@ -29,6 +35,28 @@ func New(format string, repo restic.Loader, w io.Writer) *Dumper {
}
}
func (d *Dumper) Close() error {
if d.tarWriter != nil {
err := d.tarWriter.Close()
if err != nil {
return errors.Wrap(err, "Close tarWriter")
}
if d.gzipWriter != nil {
err := d.gzipWriter.Close()
if err != nil {
return errors.Wrap(err, "Close gzipWriter")
}
}
}
if d.zipWriter != nil {
return d.zipWriter.Close()
}
return nil
}
func (d *Dumper) DumpTree(ctx context.Context, tree *restic.Tree, rootPath string) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()

View file

@ -89,6 +89,8 @@ func WriteTest(t *testing.T, format string, cd CheckDump) {
if err := d.DumpTree(ctx, tree, tt.target); err != nil {
t.Fatalf("Dumper.Run error = %v", err)
}
d.Close()
if err := cd(t, tmpdir, dst); err != nil {
t.Errorf("WriteDump() = does not match: %v", err)
}

View file

@ -8,22 +8,16 @@ import (
"path/filepath"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
)
func (d *Dumper) dumpTar(ctx context.Context, ch <-chan *restic.Node) (err error) {
w := tar.NewWriter(d.w)
defer func() {
if err == nil {
err = w.Close()
err = errors.Wrap(err, "Close")
}
}()
if d.tarWriter == nil {
d.tarWriter = tar.NewWriter(d.w)
}
for node := range ch {
if err := d.dumpNodeTar(ctx, node, w); err != nil {
if err := d.dumpNodeTar(ctx, node); err != nil {
return err
}
}
@ -48,7 +42,7 @@ func tarIdentifier(id uint32) int {
return int(id)
}
func (d *Dumper) dumpNodeTar(ctx context.Context, node *restic.Node, w *tar.Writer) error {
func (d *Dumper) dumpNodeTar(ctx context.Context, node *restic.Node) error {
relPath, err := filepath.Rel("/", node.Path)
if err != nil {
return err
@ -93,11 +87,11 @@ func (d *Dumper) dumpNodeTar(ctx context.Context, node *restic.Node, w *tar.Writ
header.Name += "/"
}
err = w.WriteHeader(header)
err = d.tarWriter.WriteHeader(header)
if err != nil {
return fmt.Errorf("writing header for %q: %w", node.Path, err)
}
return d.writeNode(ctx, w, node)
return d.writeNode(ctx, d.tarWriter, node)
}
func parseXattrs(xattrs []restic.ExtendedAttribute) map[string]string {

View file

@ -10,24 +10,19 @@ import (
)
func (d *Dumper) dumpZip(ctx context.Context, ch <-chan *restic.Node) (err error) {
w := zip.NewWriter(d.w)
defer func() {
if err == nil {
err = w.Close()
err = errors.Wrap(err, "Close")
}
}()
if d.zipWriter == nil {
d.zipWriter = zip.NewWriter(d.w)
}
for node := range ch {
if err := d.dumpNodeZip(ctx, node, w); err != nil {
if err := d.dumpNodeZip(ctx, node); err != nil {
return err
}
}
return nil
}
func (d *Dumper) dumpNodeZip(ctx context.Context, node *restic.Node, zw *zip.Writer) error {
func (d *Dumper) dumpNodeZip(ctx context.Context, node *restic.Node) error {
relPath, err := filepath.Rel("/", node.Path)
if err != nil {
return err
@ -47,7 +42,7 @@ func (d *Dumper) dumpNodeZip(ctx context.Context, node *restic.Node, zw *zip.Wri
header.Name += "/"
}
w, err := zw.CreateHeader(header)
w, err := d.zipWriter.CreateHeader(header)
if err != nil {
return errors.Wrap(err, "ZipHeader")
}