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  }