github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/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.Str()) 46 tree, err := res.repo.LoadTree(ctx, treeID) 47 if err != nil { 48 debug.Log("error loading tree %v: %v", treeID.Str(), 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 selectedForRestore { 83 err = res.restoreNodeTo(ctx, node, nodeTarget, nodeLocation, idx) 84 if err != nil { 85 return err 86 } 87 } 88 89 if node.Type == "dir" && childMayBeSelected { 90 if node.Subtree == nil { 91 return errors.Errorf("Dir without subtree in tree %v", treeID.Str()) 92 } 93 94 err = res.restoreTo(ctx, nodeTarget, nodeLocation, *node.Subtree, idx) 95 if err != nil { 96 err = res.Error(nodeLocation, node, err) 97 if err != nil { 98 return err 99 } 100 } 101 102 if selectedForRestore { 103 // Restore directory timestamp at the end. If we would do it earlier, restoring files within 104 // the directory would overwrite the timestamp of the directory they are in. 105 err = node.RestoreTimestamps(nodeTarget) 106 if err != nil { 107 return err 108 } 109 } 110 } 111 } 112 113 return nil 114 } 115 116 func (res *Restorer) restoreNodeTo(ctx context.Context, node *Node, target, location string, idx *HardlinkIndex) error { 117 debug.Log("%v %v %v", node.Name, target, location) 118 119 err := node.CreateAt(ctx, target, res.repo, idx) 120 if err != nil { 121 debug.Log("node.CreateAt(%s) error %v", target, err) 122 } 123 124 // Did it fail because of ENOENT? 125 if err != nil && os.IsNotExist(errors.Cause(err)) { 126 debug.Log("create intermediate paths") 127 128 // Create parent directories and retry 129 err = fs.MkdirAll(filepath.Dir(target), 0700) 130 if err == nil || os.IsExist(errors.Cause(err)) { 131 err = node.CreateAt(ctx, target, res.repo, idx) 132 } 133 } 134 135 if err != nil { 136 debug.Log("error %v", err) 137 err = res.Error(location, node, err) 138 if err != nil { 139 return err 140 } 141 } 142 143 return nil 144 } 145 146 // RestoreTo creates the directories and files in the snapshot below dst. 147 // Before an item is created, res.Filter is called. 148 func (res *Restorer) RestoreTo(ctx context.Context, dst string) error { 149 var err error 150 if !filepath.IsAbs(dst) { 151 dst, err = filepath.Abs(dst) 152 if err != nil { 153 return errors.Wrap(err, "Abs") 154 } 155 } 156 157 idx := NewHardlinkIndex() 158 return res.restoreTo(ctx, dst, string(filepath.Separator), *res.sn.Tree, idx) 159 } 160 161 // Snapshot returns the snapshot this restorer is configured to use. 162 func (res *Restorer) Snapshot() *Snapshot { 163 return res.sn 164 }