| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | package archiver | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/debug" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/errors" | 
					
						
							|  |  |  | 	"github.com/restic/restic/internal/fs" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Tree recursively defines how a snapshot should look like when | 
					
						
							|  |  |  | // archived. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // When `Path` is set, this is a leaf node and the contents of `Path` should be | 
					
						
							|  |  |  | // inserted at this point in the tree. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The attribute `Root` is used to distinguish between files/dirs which have | 
					
						
							|  |  |  | // the same name, but live in a separate directory on the local file system. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // `FileInfoPath` is used to extract metadata for intermediate (=non-leaf) | 
					
						
							|  |  |  | // trees. | 
					
						
							|  |  |  | type Tree struct { | 
					
						
							|  |  |  | 	Nodes        map[string]Tree | 
					
						
							|  |  |  | 	Path         string // where the files/dirs to be saved are found | 
					
						
							|  |  |  | 	FileInfoPath string // where the dir can be found that is not included itself, but its subdirs | 
					
						
							|  |  |  | 	Root         string // parent directory of the tree | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // pathComponents returns all path components of p. If a virtual directory | 
					
						
							|  |  |  | // (volume name on Windows) is added, virtualPrefix is set to true. See the | 
					
						
							|  |  |  | // tests for examples. | 
					
						
							|  |  |  | func pathComponents(fs fs.FS, p string, includeRelative bool) (components []string, virtualPrefix bool) { | 
					
						
							|  |  |  | 	volume := fs.VolumeName(p) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !fs.IsAbs(p) { | 
					
						
							|  |  |  | 		if !includeRelative { | 
					
						
							|  |  |  | 			p = fs.Join(fs.Separator(), p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	p = fs.Clean(p) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		dir, file := fs.Dir(p), fs.Base(p) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if p == dir { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		components = append(components, file) | 
					
						
							|  |  |  | 		p = dir | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// reverse components | 
					
						
							|  |  |  | 	for i := len(components)/2 - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 		opp := len(components) - 1 - i | 
					
						
							|  |  |  | 		components[i], components[opp] = components[opp], components[i] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if volume != "" { | 
					
						
							|  |  |  | 		// strip colon | 
					
						
							|  |  |  | 		if len(volume) == 2 && volume[1] == ':' { | 
					
						
							|  |  |  | 			volume = volume[:1] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		components = append([]string{volume}, components...) | 
					
						
							|  |  |  | 		virtualPrefix = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return components, virtualPrefix | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // rootDirectory returns the directory which contains the first element of target. | 
					
						
							|  |  |  | func rootDirectory(fs fs.FS, target string) string { | 
					
						
							|  |  |  | 	if target == "" { | 
					
						
							|  |  |  | 		return "" | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if fs.IsAbs(target) { | 
					
						
							|  |  |  | 		return fs.Join(fs.VolumeName(target), fs.Separator()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	target = fs.Clean(target) | 
					
						
							|  |  |  | 	pc, _ := pathComponents(fs, target, true) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rel := "." | 
					
						
							|  |  |  | 	for _, c := range pc { | 
					
						
							|  |  |  | 		if c == ".." { | 
					
						
							|  |  |  | 			rel = fs.Join(rel, c) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return rel | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Add adds a new file or directory to the tree. | 
					
						
							|  |  |  | func (t *Tree) Add(fs fs.FS, path string) error { | 
					
						
							|  |  |  | 	if path == "" { | 
					
						
							|  |  |  | 		panic("invalid path (empty string)") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if t.Nodes == nil { | 
					
						
							|  |  |  | 		t.Nodes = make(map[string]Tree) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pc, virtualPrefix := pathComponents(fs, path, false) | 
					
						
							|  |  |  | 	if len(pc) == 0 { | 
					
						
							|  |  |  | 		return errors.New("invalid path (no path components)") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	name := pc[0] | 
					
						
							|  |  |  | 	root := rootDirectory(fs, path) | 
					
						
							|  |  |  | 	tree := Tree{Root: root} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	origName := name | 
					
						
							|  |  |  | 	i := 0 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		other, ok := t.Nodes[name] | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		i++ | 
					
						
							|  |  |  | 		if other.Root == root { | 
					
						
							|  |  |  | 			tree = other | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// resolve conflict and try again | 
					
						
							|  |  |  | 		name = fmt.Sprintf("%s-%d", origName, i) | 
					
						
							|  |  |  | 		continue | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(pc) > 1 { | 
					
						
							|  |  |  | 		subroot := fs.Join(root, origName) | 
					
						
							|  |  |  | 		if virtualPrefix { | 
					
						
							|  |  |  | 			// use the original root dir if this is a virtual directory (volume name on Windows) | 
					
						
							|  |  |  | 			subroot = root | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		err := tree.add(fs, path, subroot, pc[1:]) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		tree.FileInfoPath = subroot | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		tree.Path = path | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Nodes[name] = tree | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // add adds a new target path into the tree. | 
					
						
							|  |  |  | func (t *Tree) add(fs fs.FS, target, root string, pc []string) error { | 
					
						
							|  |  |  | 	if len(pc) == 0 { | 
					
						
							|  |  |  | 		return errors.Errorf("invalid path %q", target) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if t.Nodes == nil { | 
					
						
							|  |  |  | 		t.Nodes = make(map[string]Tree) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	name := pc[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if len(pc) == 1 { | 
					
						
							|  |  |  | 		tree, ok := t.Nodes[name] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			t.Nodes[name] = Tree{Path: target} | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if tree.Path != "" { | 
					
						
							|  |  |  | 			return errors.Errorf("path is already set for target %v", target) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		tree.Path = target | 
					
						
							|  |  |  | 		t.Nodes[name] = tree | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	tree := Tree{} | 
					
						
							|  |  |  | 	if other, ok := t.Nodes[name]; ok { | 
					
						
							|  |  |  | 		tree = other | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	subroot := fs.Join(root, name) | 
					
						
							|  |  |  | 	tree.FileInfoPath = subroot | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err := tree.add(fs, target, subroot, pc[1:]) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	t.Nodes[name] = tree | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t Tree) String() string { | 
					
						
							|  |  |  | 	return formatTree(t, "") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // formatTree returns a text representation of the tree t. | 
					
						
							|  |  |  | func formatTree(t Tree, indent string) (s string) { | 
					
						
							|  |  |  | 	for name, node := range t.Nodes { | 
					
						
							| 
									
										
										
										
											2018-06-20 22:54:47 +02:00
										 |  |  | 		s += fmt.Sprintf("%v/%v, root %q, path %q, meta %q\n", indent, name, node.Root, node.Path, node.FileInfoPath) | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		s += formatTree(node, indent+"    ") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return s | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-20 22:54:47 +02:00
										 |  |  | // unrollTree unrolls the tree so that only leaf nodes have Path set. | 
					
						
							|  |  |  | func unrollTree(f fs.FS, t *Tree) error { | 
					
						
							|  |  |  | 	// if the current tree is a leaf node (Path is set) and has additional | 
					
						
							|  |  |  | 	// nodes, add the contents of Path to the nodes. | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	if t.Path != "" && len(t.Nodes) > 0 { | 
					
						
							| 
									
										
										
										
											2018-06-20 22:54:47 +02:00
										 |  |  | 		debug.Log("resolve path %v", t.Path) | 
					
						
							|  |  |  | 		entries, err := fs.ReadDirNames(f, t.Path) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for _, entry := range entries { | 
					
						
							|  |  |  | 			if node, ok := t.Nodes[entry]; ok { | 
					
						
							|  |  |  | 				if node.Path == "" { | 
					
						
							|  |  |  | 					node.Path = f.Join(t.Path, entry) | 
					
						
							|  |  |  | 					t.Nodes[entry] = node | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				if node.Path == f.Join(t.Path, entry) { | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				return errors.Errorf("tree unrollTree: collision on path, node %#v, path %q", node, f.Join(t.Path, entry)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			t.Nodes[entry] = Tree{Path: f.Join(t.Path, entry)} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		t.Path = "" | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for i, subtree := range t.Nodes { | 
					
						
							| 
									
										
										
										
											2018-06-20 22:54:47 +02:00
										 |  |  | 		err := unrollTree(f, &subtree) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 		t.Nodes[i] = subtree | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-06-20 22:54:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewTree creates a Tree from the target files/directories. | 
					
						
							|  |  |  | func NewTree(fs fs.FS, targets []string) (*Tree, error) { | 
					
						
							|  |  |  | 	debug.Log("targets: %v", targets) | 
					
						
							|  |  |  | 	tree := &Tree{} | 
					
						
							|  |  |  | 	seen := make(map[string]struct{}) | 
					
						
							|  |  |  | 	for _, target := range targets { | 
					
						
							|  |  |  | 		target = fs.Clean(target) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// skip duplicate targets | 
					
						
							|  |  |  | 		if _, ok := seen[target]; ok { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		seen[target] = struct{}{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		err := tree.Add(fs, target) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-06-20 22:54:47 +02:00
										 |  |  | 	debug.Log("before unroll:\n%v", tree) | 
					
						
							|  |  |  | 	err := unrollTree(fs, tree) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-30 22:43:18 +02:00
										 |  |  | 	debug.Log("result:\n%v", tree) | 
					
						
							|  |  |  | 	return tree, nil | 
					
						
							|  |  |  | } |