github.com/mckael/restic@v0.8.3/internal/restic/restorer.go (about) 1 package restic 2 3 import ( 4 "context" 5 "os" 6 "path/filepath" 7 8 "github.com/restic/restic/internal/errors" 9 10 "github.com/restic/restic/internal/debug" 11 "github.com/restic/restic/internal/fs" 12 ) 13 14 // Restorer is used to restore a snapshot to a directory. 15 type Restorer struct { 16 repo Repository 17 sn *Snapshot 18 19 Error func(dir string, node *Node, err error) error 20 SelectFilter func(item string, dstpath string, node *Node) (selectedForRestore bool, childMayBeSelected bool) 21 } 22 23 var restorerAbortOnAllErrors = func(str string, node *Node, err error) error { return err } 24 25 // NewRestorer creates a restorer preloaded with the content from the snapshot id. 26 func NewRestorer(repo Repository, id ID) (*Restorer, error) { 27 r := &Restorer{ 28 repo: repo, Error: restorerAbortOnAllErrors, 29 SelectFilter: func(string, string, *Node) (bool, bool) { return true, true }, 30 } 31 32 var err error 33 34 r.sn, err = LoadSnapshot(context.TODO(), repo, id) 35 if err != nil { 36 return nil, err 37 } 38 39 return r, nil 40 } 41 42 // restoreTo restores a tree from the repo to a destination. target is the path in 43 // the file system, location within the snapshot. 44 func (res *Restorer) restoreTo(ctx context.Context, target, location string, treeID ID, idx *HardlinkIndex) error { 45 debug.Log("%v %v %v", target, location, treeID) 46 tree, err := res.repo.LoadTree(ctx, treeID) 47 if err != nil { 48 debug.Log("error loading tree %v: %v", treeID, err) 49 return res.Error(location, nil, err) 50 } 51 52 for _, node := range tree.Nodes { 53 54 // ensure that the node name does not contain anything that refers to a 55 // top-level directory. 56 nodeName := filepath.Base(filepath.Join(string(filepath.Separator), node.Name)) 57 if nodeName != node.Name { 58 debug.Log("node %q has invalid name %q", node.Name, nodeName) 59 err := res.Error(location, node, errors.New("node has invalid name")) 60 if err != nil { 61 return err 62 } 63 continue 64 } 65 66 nodeTarget := filepath.Join(target, nodeName) 67 nodeLocation := filepath.Join(location, nodeName) 68 69 if target == nodeTarget || !fs.HasPathPrefix(target, nodeTarget) { 70 debug.Log("target: %v %v", target, nodeTarget) 71 debug.Log("node %q has invalid target path %q", node.Name, nodeTarget) 72 err := res.Error(nodeLocation, node, errors.New("node has invalid path")) 73 if err != nil { 74 return err 75 } 76 continue 77 } 78 79 selectedForRestore, childMayBeSelected := res.SelectFilter(nodeLocation, nodeTarget, node) 80 debug.Log("SelectFilter returned %v %v", selectedForRestore, childMayBeSelected) 81 82 if node.Type == "dir" && childMayBeSelected { 83 if node.Subtree == nil { 84 return errors.Errorf("Dir without subtree in tree %v", treeID.Str()) 85 } 86 87 err = res.restoreTo(ctx, nodeTarget, nodeLocation, *node.Subtree, idx) 88 if err != nil { 89 err = res.Error(nodeLocation, node, err) 90 if err != nil { 91 return err 92 } 93 } 94 } 95 96 if selectedForRestore { 97 err = res.restoreNodeTo(ctx, node, nodeTarget, nodeLocation, idx) 98 if err != nil { 99 return err 100 } 101 102 // Restore directory timestamp at the end. If we would do it earlier, restoring files within 103 // the directory would overwrite the timestamp of the directory they are in. 104 err = node.RestoreTimestamps(nodeTarget) 105 if err != nil { 106 return err 107 } 108 } 109 } 110 111 return nil 112 } 113 114 func (res *Restorer) restoreNodeTo(ctx context.Context, node *Node, target, location string, idx *HardlinkIndex) error { 115 debug.Log("%v %v %v", node.Name, target, location) 116 117 err := node.CreateAt(ctx, target, res.repo, idx) 118 if err != nil { 119 debug.Log("node.CreateAt(%s) error %v", target, err) 120 } 121 122 // Did it fail because of ENOENT? 123 if err != nil && os.IsNotExist(errors.Cause(err)) { 124 debug.Log("create intermediate paths") 125 126 // Create parent directories and retry 127 err = fs.MkdirAll(filepath.Dir(target), 0700) 128 if err == nil || os.IsExist(errors.Cause(err)) { 129 err = node.CreateAt(ctx, target, res.repo, idx) 130 } 131 } 132 133 if err != nil { 134 debug.Log("error %v", err) 135 err = res.Error(location, node, err) 136 if err != nil { 137 return err 138 } 139 } 140 141 return nil 142 } 143 144 // RestoreTo creates the directories and files in the snapshot below dst. 145 // Before an item is created, res.Filter is called. 146 func (res *Restorer) RestoreTo(ctx context.Context, dst string) error { 147 var err error 148 if !filepath.IsAbs(dst) { 149 dst, err = filepath.Abs(dst) 150 if err != nil { 151 return errors.Wrap(err, "Abs") 152 } 153 } 154 155 idx := NewHardlinkIndex() 156 return res.restoreTo(ctx, dst, string(filepath.Separator), *res.sn.Tree, idx) 157 } 158 159 // Snapshot returns the snapshot this restorer is configured to use. 160 func (res *Restorer) Snapshot() *Snapshot { 161 return res.sn 162 }