From ccada7d89a8587666affe88ede9d2508f5e9c7f2 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 1 Jan 2015 14:50:31 +0100 Subject: [PATCH 1/5] Don't skip subtree on false filter condition when restoring We still need to descend into subtrees and check if their filter matches. --- restorer.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/restorer.go b/restorer.go index f26b30354..7523b2cbd 100644 --- a/restorer.go +++ b/restorer.go @@ -51,15 +51,14 @@ func (res *Restorer) to(dir string, tree_id backend.ID) error { for _, node := range tree { p := filepath.Join(dir, node.Name) - if !res.Filter(p, node) { - continue - } - err := node.CreateAt(res.ch, p) - if err != nil { - err = res.Error(p, node, arrar.Annotate(err, "create node")) + if res.Filter(p, node) { + err := node.CreateAt(res.ch, p) if err != nil { - return err + err = res.Error(p, node, arrar.Annotate(err, "create node")) + if err != nil { + return err + } } } From 10b99e53e407fea6ceca21cadf9e7ae8664c469e Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 1 Jan 2015 15:20:21 +0100 Subject: [PATCH 2/5] Pass "source" path to restorer filter function The source path is probably more useful here as the user, when restoring, probably wants to filter for /usr/local/foo instead of /tmp/restore-whatever/{usr/local/,}foo. --- restorer.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/restorer.go b/restorer.go index 7523b2cbd..87ddc0084 100644 --- a/restorer.go +++ b/restorer.go @@ -16,7 +16,7 @@ type Restorer struct { sn *Snapshot Error func(dir string, node *Node, err error) error - Filter func(item string, node *Node) bool + Filter func(item string, dstpath string, node *Node) bool } // NewRestorer creates a restorer preloaded with the content from the snapshot snid. @@ -37,12 +37,12 @@ func NewRestorer(s Server, snid backend.ID) (*Restorer, error) { // abort on all errors r.Error = func(string, *Node, error) error { return err } // allow all files - r.Filter = func(string, *Node) bool { return true } + r.Filter = func(string, string, *Node) bool { return true } return r, nil } -func (res *Restorer) to(dir string, tree_id backend.ID) error { +func (res *Restorer) to(dst string, dir string, tree_id backend.ID) error { tree := Tree{} err := res.ch.LoadJSON(backend.Tree, tree_id, &tree) if err != nil { @@ -50,12 +50,12 @@ func (res *Restorer) to(dir string, tree_id backend.ID) error { } for _, node := range tree { - p := filepath.Join(dir, node.Name) + dstpath := filepath.Join(dst, dir, node.Name) - if res.Filter(p, node) { - err := node.CreateAt(res.ch, p) + if res.Filter(filepath.Join(res.sn.Dir, dir, node.Name), dstpath, node) { + err := node.CreateAt(res.ch, dstpath) if err != nil { - err = res.Error(p, node, arrar.Annotate(err, "create node")) + err = res.Error(dstpath, node, arrar.Annotate(err, "create node")) if err != nil { return err } @@ -67,9 +67,10 @@ func (res *Restorer) to(dir string, tree_id backend.ID) error { return errors.New(fmt.Sprintf("Dir without subtree in tree %s", tree_id)) } - err = res.to(p, node.Subtree) + subp := filepath.Join(dir, node.Name) + err = res.to(dst, subp, node.Subtree) if err != nil { - err = res.Error(p, node, arrar.Annotate(err, "restore subtree")) + err = res.Error(subp, node, arrar.Annotate(err, "restore subtree")) if err != nil { return err } @@ -88,7 +89,7 @@ func (res *Restorer) RestoreTo(dir string) error { return err } - return res.to(dir, res.sn.Tree) + return res.to(dir, "", res.sn.Tree) } func (res *Restorer) Snapshot() *Snapshot { From fb874ea7cccd3828930f8e952688a01d043e9968 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 1 Jan 2015 15:25:40 +0100 Subject: [PATCH 3/5] Allow nil 'Filter's for restorer Allow Filters to be nil and avoid joining the path again if no filter is used at all. --- restorer.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/restorer.go b/restorer.go index 87ddc0084..47c30fd85 100644 --- a/restorer.go +++ b/restorer.go @@ -36,8 +36,6 @@ func NewRestorer(s Server, snid backend.ID) (*Restorer, error) { // abort on all errors r.Error = func(string, *Node, error) error { return err } - // allow all files - r.Filter = func(string, string, *Node) bool { return true } return r, nil } @@ -52,7 +50,8 @@ func (res *Restorer) to(dst string, dir string, tree_id backend.ID) error { for _, node := range tree { dstpath := filepath.Join(dst, dir, node.Name) - if res.Filter(filepath.Join(res.sn.Dir, dir, node.Name), dstpath, node) { + if res.Filter == nil || + res.Filter(filepath.Join(res.sn.Dir, dir, node.Name), dstpath, node) { err := node.CreateAt(res.ch, dstpath) if err != nil { err = res.Error(dstpath, node, arrar.Annotate(err, "create node")) From bd43e27debdfcadb41f6499717fff29bc5f4c7b0 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 1 Jan 2015 16:17:33 +0100 Subject: [PATCH 4/5] Create parent directories for restore items When restoring an item fails with ENOENT, create parent directories and try again. This is needed for restoring partial trees (as in: the Filter function didn't return true for the paths leading up to this restore item). --- restorer.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/restorer.go b/restorer.go index 47c30fd85..b57d83e77 100644 --- a/restorer.go +++ b/restorer.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "syscall" "github.com/juju/arrar" "github.com/restic/restic/backend" @@ -53,6 +54,22 @@ func (res *Restorer) to(dst string, dir string, tree_id backend.ID) error { if res.Filter == nil || res.Filter(filepath.Join(res.sn.Dir, dir, node.Name), dstpath, node) { err := node.CreateAt(res.ch, dstpath) + + // Did it fail because of ENOENT? + if arrar.Check(err, func(err error) bool { + if pe, ok := err.(*os.PathError); ok { + errn, ok := pe.Err.(syscall.Errno) + return ok && errn == syscall.ENOENT + } + return false + }) { + // Create parent directories and retry + err = os.MkdirAll(filepath.Dir(dstpath), 0700) + if err == nil || err == os.ErrExist { + err = node.CreateAt(res.ch, dstpath) + } + } + if err != nil { err = res.Error(dstpath, node, arrar.Annotate(err, "create node")) if err != nil { @@ -83,11 +100,6 @@ func (res *Restorer) to(dst string, dir string, tree_id backend.ID) error { // RestoreTo creates the directories and files in the snapshot below dir. // Before an item is created, res.Filter is called. func (res *Restorer) RestoreTo(dir string) error { - err := os.MkdirAll(dir, 0700) - if err != nil && err != os.ErrExist { - return err - } - return res.to(dir, "", res.sn.Tree) } From b1dbc6f062b993f97cc90348ecaa61d325d59c6e Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Thu, 1 Jan 2015 16:29:41 +0100 Subject: [PATCH 5/5] Support for pattern in restore command This just matches the passed pattern against the full source path with filepath.Match which, in contrast go filepath.Glob, doesn't match the directory separator with '*' and is not terribly useful that way. Someone should replace that by a more sophisticated matcher. --- cmd/restic/cmd_restore.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index b165f2805..ad1986b7f 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "path/filepath" "github.com/restic/restic" "github.com/restic/restic/backend" @@ -21,11 +22,11 @@ func init() { } func (cmd CmdRestore) Usage() string { - return "snapshot-ID TARGETDIR" + return "snapshot-ID TARGETDIR [PATTERN]" } func (cmd CmdRestore) Execute(args []string) error { - if len(args) != 2 { + if len(args) < 2 || len(args) > 3 { return fmt.Errorf("wrong number of arguments, Usage: %s", cmd.Usage()) } @@ -64,6 +65,18 @@ func (cmd CmdRestore) Execute(args []string) error { return err } + // TODO: a filter against the full path sucks as filepath.Match doesn't match + // directory separators on '*'. still, it's better than nothing. + if len(args) > 2 { + res.Filter = func(item string, dstpath string, node *restic.Node) bool { + matched, err := filepath.Match(item, args[2]) + if err != nil { + panic(err) + } + return matched + } + } + fmt.Printf("restoring %s to %s\n", res.Snapshot(), target) err = res.RestoreTo(target)