mirror of https://github.com/restic/restic.git
restore: Removed legacy restore implementation
Signed-off-by: Igor Fedorenko <igor@ifedorenko.com>
This commit is contained in:
parent
1869930d95
commit
da57302fca
|
@ -7,9 +7,5 @@ Implementation uses threads to download and process miltiple remote
|
|||
files concurrently. To further reduce restore time, each remote
|
||||
file is downloaded using single repository request.
|
||||
|
||||
Old restore implementation can be enabled with `--signethreaded` flag.
|
||||
Use `--verify` restore flag to read restored files and verify their
|
||||
content checksum.
|
||||
|
||||
https://github.com/restic/restic/issues/1605
|
||||
https://github.com/restic/restic/pull/1719
|
||||
|
|
|
@ -28,14 +28,13 @@ repository.
|
|||
|
||||
// RestoreOptions collects all options for the restore command.
|
||||
type RestoreOptions struct {
|
||||
Exclude []string
|
||||
Include []string
|
||||
Target string
|
||||
Host string
|
||||
Paths []string
|
||||
Tags restic.TagLists
|
||||
Verify bool
|
||||
SingleThreaded bool
|
||||
Exclude []string
|
||||
Include []string
|
||||
Target string
|
||||
Host string
|
||||
Paths []string
|
||||
Tags restic.TagLists
|
||||
Verify bool
|
||||
}
|
||||
|
||||
var restoreOptions RestoreOptions
|
||||
|
@ -52,7 +51,6 @@ func init() {
|
|||
flags.Var(&restoreOptions.Tags, "tag", "only consider snapshots which include this `taglist` for snapshot ID \"latest\"")
|
||||
flags.StringArrayVar(&restoreOptions.Paths, "path", nil, "only consider snapshots which include this (absolute) `path` for snapshot ID \"latest\"")
|
||||
flags.BoolVar(&restoreOptions.Verify, "verify", false, "verify restored files content")
|
||||
flags.BoolVar(&restoreOptions.SingleThreaded, "singlethreaded", false, "use single-threaded (legacy) restore implementation")
|
||||
}
|
||||
|
||||
func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
|
||||
|
@ -157,7 +155,7 @@ func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error {
|
|||
|
||||
Verbosef("restoring %s to %s\n", res.Snapshot(), opts.Target)
|
||||
|
||||
err = res.RestoreTo(ctx, opts.Target, opts.SingleThreaded)
|
||||
err = res.RestoreTo(ctx, opts.Target)
|
||||
if err == nil && opts.Verify {
|
||||
Verbosef("verifying files in %s\n", opts.Target)
|
||||
var count int
|
||||
|
|
|
@ -135,7 +135,7 @@ func (node Node) GetExtendedAttribute(a string) []byte {
|
|||
}
|
||||
|
||||
// CreateAt creates the node at the given path but does NOT restore node meta data.
|
||||
func (node *Node) CreateAt(ctx context.Context, path string, repo Repository, idx *HardlinkIndex) error {
|
||||
func (node *Node) CreateAt(ctx context.Context, path string, repo Repository) error {
|
||||
debug.Log("create node %v at %v", node.Name, path)
|
||||
|
||||
switch node.Type {
|
||||
|
@ -144,7 +144,7 @@ func (node *Node) CreateAt(ctx context.Context, path string, repo Repository, id
|
|||
return err
|
||||
}
|
||||
case "file":
|
||||
if err := node.createFileAt(ctx, path, repo, idx); err != nil {
|
||||
if err := node.createFileAt(ctx, path, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
case "symlink":
|
||||
|
@ -259,18 +259,7 @@ func (node Node) createDirAt(path string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (node Node) createFileAt(ctx context.Context, path string, repo Repository, idx *HardlinkIndex) error {
|
||||
if node.Links > 1 && idx.Has(node.Inode, node.DeviceID) {
|
||||
if err := fs.Remove(path); !os.IsNotExist(err) {
|
||||
return errors.Wrap(err, "RemoveCreateHardlink")
|
||||
}
|
||||
err := fs.Link(idx.GetFilename(node.Inode, node.DeviceID), path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "CreateHardlink")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (node Node) createFileAt(ctx context.Context, path string, repo Repository) error {
|
||||
f, err := fs.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "OpenFile")
|
||||
|
@ -287,10 +276,6 @@ func (node Node) createFileAt(ctx context.Context, path string, repo Repository,
|
|||
return errors.Wrap(closeErr, "Close")
|
||||
}
|
||||
|
||||
if node.Links > 1 {
|
||||
idx.Add(node.Inode, node.DeviceID, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -177,11 +177,9 @@ func TestNodeRestoreAt(t *testing.T) {
|
|||
}
|
||||
}()
|
||||
|
||||
idx := restic.NewHardlinkIndex()
|
||||
|
||||
for _, test := range nodeTests {
|
||||
nodePath := filepath.Join(tempdir, test.Name)
|
||||
rtest.OK(t, test.CreateAt(context.TODO(), nodePath, nil, idx))
|
||||
rtest.OK(t, test.CreateAt(context.TODO(), nodePath, nil))
|
||||
rtest.OK(t, test.RestoreMetadata(nodePath))
|
||||
|
||||
if test.Type == "symlink" && runtime.GOOS == "windows" {
|
||||
|
|
|
@ -18,8 +18,6 @@ import (
|
|||
// TODO evaluate memory footprint for larger repositories, say 10M packs/10M files
|
||||
// TODO consider replacing pack file cache with blob cache
|
||||
// TODO avoid decrypting the same blob multiple times
|
||||
// TODO remove `restore --singlethreaded` and review/simplify hardlink support
|
||||
// (node.CreateAt shouldn't take HardlinkIndex)
|
||||
// TODO evaluate disabled debug logging overhead for large repositories
|
||||
// TODO consider logging snapshot-relative path to reduce log clutter
|
||||
|
||||
|
|
|
@ -140,10 +140,10 @@ func (res *Restorer) traverseTree(ctx context.Context, target, location string,
|
|||
return nil
|
||||
}
|
||||
|
||||
func (res *Restorer) restoreNodeTo(ctx context.Context, node *restic.Node, target, location string, idx *restic.HardlinkIndex) error {
|
||||
func (res *Restorer) restoreNodeTo(ctx context.Context, node *restic.Node, target, location string) error {
|
||||
debug.Log("restoreNode %v %v %v", node.Name, target, location)
|
||||
|
||||
err := node.CreateAt(ctx, target, res.repo, idx)
|
||||
err := node.CreateAt(ctx, target, res.repo)
|
||||
if err != nil {
|
||||
debug.Log("node.CreateAt(%s) error %v", target, err)
|
||||
}
|
||||
|
@ -163,9 +163,20 @@ func (res *Restorer) restoreNodeMetadataTo(node *restic.Node, target, location s
|
|||
return err
|
||||
}
|
||||
|
||||
func (res *Restorer) restoreHardlinkAt(node *restic.Node, target, path, location string) error {
|
||||
if err := fs.Remove(path); !os.IsNotExist(err) {
|
||||
return errors.Wrap(err, "RemoveCreateHardlink")
|
||||
}
|
||||
err := fs.Link(target, path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "CreateHardlink")
|
||||
}
|
||||
return res.restoreNodeMetadataTo(node, target, location)
|
||||
}
|
||||
|
||||
// RestoreTo creates the directories and files in the snapshot below dst.
|
||||
// Before an item is created, res.Filter is called.
|
||||
func (res *Restorer) RestoreTo(ctx context.Context, dst string, singlethreaded bool) error {
|
||||
func (res *Restorer) RestoreTo(ctx context.Context, dst string) error {
|
||||
var err error
|
||||
if !filepath.IsAbs(dst) {
|
||||
dst, err = filepath.Abs(dst)
|
||||
|
@ -180,31 +191,6 @@ func (res *Restorer) RestoreTo(ctx context.Context, dst string, singlethreaded b
|
|||
noop := func(node *restic.Node, target, location string) error { return nil }
|
||||
|
||||
idx := restic.NewHardlinkIndex()
|
||||
if singlethreaded {
|
||||
return res.traverseTree(ctx, dst, string(filepath.Separator), *res.sn.Tree, treeVisitor{
|
||||
enterDir: func(node *restic.Node, target, location string) error {
|
||||
// create dir with default permissions
|
||||
// #leaveDir restores dir metadata after visiting all children
|
||||
return fs.MkdirAll(target, 0700)
|
||||
},
|
||||
|
||||
visitNode: func(node *restic.Node, target, location string) error {
|
||||
// create parent dir with default permissions
|
||||
// #leaveDir restores dir metadata after visiting all children
|
||||
err := fs.MkdirAll(filepath.Dir(target), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return res.restoreNodeTo(ctx, node, target, location, idx)
|
||||
},
|
||||
|
||||
// Restore directory permissions and timestamp at the end. If we did it earlier
|
||||
// - children restore could fail because of restictive directory permission
|
||||
// - children restore could overwrite the timestamp of the directory they are in
|
||||
leaveDir: restoreNodeMetadata,
|
||||
})
|
||||
}
|
||||
|
||||
filerestorer := newFileRestorer(res.repo.Backend().Load, res.repo.Key(), filePackTraverser{lookup: res.repo.Index().Lookup})
|
||||
|
||||
|
@ -258,15 +244,15 @@ func (res *Restorer) RestoreTo(ctx context.Context, dst string, singlethreaded b
|
|||
return res.traverseTree(ctx, dst, string(filepath.Separator), *res.sn.Tree, treeVisitor{
|
||||
enterDir: noop,
|
||||
visitNode: func(node *restic.Node, target, location string) error {
|
||||
isHardlink := func() bool {
|
||||
return idx.Has(node.Inode, node.DeviceID) && idx.GetFilename(node.Inode, node.DeviceID) != target
|
||||
if idx.Has(node.Inode, node.DeviceID) && idx.GetFilename(node.Inode, node.DeviceID) != target {
|
||||
return res.restoreHardlinkAt(node, idx.GetFilename(node.Inode, node.DeviceID), target, location)
|
||||
}
|
||||
|
||||
if node.Type != "file" || isHardlink() {
|
||||
return res.restoreNodeTo(ctx, node, target, location, idx)
|
||||
if node.Type != "file" {
|
||||
return res.restoreNodeTo(ctx, node, target, location)
|
||||
}
|
||||
|
||||
return node.RestoreMetadata(target)
|
||||
return res.restoreNodeMetadataTo(node, target, location)
|
||||
},
|
||||
leaveDir: restoreNodeMetadata,
|
||||
})
|
||||
|
|
|
@ -340,7 +340,7 @@ func TestRestorer(t *testing.T) {
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
err = res.RestoreTo(ctx, tempdir, false)
|
||||
err = res.RestoreTo(ctx, tempdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -446,7 +446,7 @@ func TestRestorerRelative(t *testing.T) {
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
err = res.RestoreTo(ctx, "restore", false)
|
||||
err = res.RestoreTo(ctx, "restore")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue