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  }