| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | package dump | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"path" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 15:38:23 +02:00
										 |  |  | 	"github.com/restic/restic/internal/bloblru" | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | 	"github.com/restic/restic/internal/errors" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/restic" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/walker" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | // 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.Repository | 
					
						
							|  |  |  | 	w      io.Writer | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | func New(format string, repo restic.Repository, w io.Writer) *Dumper { | 
					
						
							|  |  |  | 	return &Dumper{ | 
					
						
							|  |  |  | 		cache:  bloblru.New(64 << 20), | 
					
						
							|  |  |  | 		format: format, | 
					
						
							|  |  |  | 		repo:   repo, | 
					
						
							|  |  |  | 		w:      w, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-09-24 15:38:23 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | func (d *Dumper) DumpTree(ctx context.Context, tree *restic.Tree, rootPath string) error { | 
					
						
							|  |  |  | 	ctx, cancel := context.WithCancel(ctx) | 
					
						
							|  |  |  | 	defer cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// ch is buffered to deal with variable download/write speeds. | 
					
						
							|  |  |  | 	ch := make(chan *restic.Node, 10) | 
					
						
							|  |  |  | 	go sendTrees(ctx, d.repo, tree, rootPath, ch) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch d.format { | 
					
						
							|  |  |  | 	case "tar": | 
					
						
							|  |  |  | 		return d.dumpTar(ctx, ch) | 
					
						
							|  |  |  | 	case "zip": | 
					
						
							|  |  |  | 		return d.dumpZip(ctx, ch) | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		panic("unknown dump format") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-09-24 15:38:23 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | func sendTrees(ctx context.Context, repo restic.Repository, tree *restic.Tree, rootPath string, ch chan *restic.Node) { | 
					
						
							|  |  |  | 	defer close(ch) | 
					
						
							| 
									
										
										
										
											2020-12-19 02:42:46 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | 	for _, root := range tree.Nodes { | 
					
						
							|  |  |  | 		root.Path = path.Join(rootPath, root.Name) | 
					
						
							|  |  |  | 		if sendNodes(ctx, repo, root, ch) != nil { | 
					
						
							|  |  |  | 			break | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | func sendNodes(ctx context.Context, repo restic.Repository, root *restic.Node, ch chan *restic.Node) error { | 
					
						
							|  |  |  | 	select { | 
					
						
							|  |  |  | 	case ch <- root: | 
					
						
							|  |  |  | 	case <-ctx.Done(): | 
					
						
							|  |  |  | 		return ctx.Err() | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// If this is no directory we are finished | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | 	if !IsDir(root) { | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | 	err := walker.Walk(ctx, repo, *root.Subtree, nil, func(_ restic.ID, nodepath string, node *restic.Node, err error) (bool, error) { | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return false, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if node == nil { | 
					
						
							|  |  |  | 			return false, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | 		node.Path = path.Join(root.Path, nodepath) | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | 		if !IsFile(node) && !IsDir(node) && !IsLink(node) { | 
					
						
							|  |  |  | 			return false, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		select { | 
					
						
							|  |  |  | 		case ch <- node: | 
					
						
							|  |  |  | 		case <-ctx.Done(): | 
					
						
							|  |  |  | 			return false, ctx.Err() | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | // WriteNode writes a file node's contents directly to d's Writer, | 
					
						
							|  |  |  | // without caring about d's format. | 
					
						
							|  |  |  | func (d *Dumper) WriteNode(ctx context.Context, node *restic.Node) error { | 
					
						
							|  |  |  | 	return d.writeNode(ctx, d.w, node) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (d *Dumper) writeNode(ctx context.Context, w io.Writer, node *restic.Node) error { | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | 	var ( | 
					
						
							|  |  |  | 		buf []byte | 
					
						
							|  |  |  | 		err error | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	for _, id := range node.Content { | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | 		blob, ok := d.cache.Get(id) | 
					
						
							| 
									
										
										
										
											2021-09-24 15:38:23 +02:00
										 |  |  | 		if !ok { | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | 			blob, err = d.repo.LoadBlob(ctx, restic.DataBlob, id, buf) | 
					
						
							| 
									
										
										
										
											2021-09-24 15:38:23 +02:00
										 |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 23:51:51 +02:00
										 |  |  | 			buf = d.cache.Add(id, blob) // Reuse evicted buffer. | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-24 15:38:23 +02:00
										 |  |  | 		if _, err := w.Write(blob); err != nil { | 
					
						
							| 
									
										
										
										
											2020-11-10 01:22:27 +03:00
										 |  |  | 			return errors.Wrap(err, "Write") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsDir checks if the given node is a directory. | 
					
						
							|  |  |  | func IsDir(node *restic.Node) bool { | 
					
						
							|  |  |  | 	return node.Type == "dir" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsLink checks if the given node as a link. | 
					
						
							|  |  |  | func IsLink(node *restic.Node) bool { | 
					
						
							|  |  |  | 	return node.Type == "symlink" | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsFile checks if the given node is a file. | 
					
						
							|  |  |  | func IsFile(node *restic.Node) bool { | 
					
						
							|  |  |  | 	return node.Type == "file" | 
					
						
							|  |  |  | } |