2014-12-07 13:44:01 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"path/filepath"
|
2014-12-07 15:30:52 +00:00
|
|
|
"time"
|
2014-12-07 13:44:01 +00:00
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
2016-02-14 14:29:28 +00:00
|
|
|
"restic"
|
|
|
|
"restic/debug"
|
2016-09-01 20:17:37 +00:00
|
|
|
"restic/errors"
|
2016-02-14 14:29:28 +00:00
|
|
|
"restic/repository"
|
2014-12-07 13:44:01 +00:00
|
|
|
)
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
var cmdFind = &cobra.Command{
|
|
|
|
Use: "find [flags] PATTERN",
|
|
|
|
Short: "find a file or directory",
|
|
|
|
Long: `
|
|
|
|
The "find" command searches for files or directories in snapshots stored in the
|
|
|
|
repo. `,
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
return runFind(findOptions, globalOptions, args)
|
|
|
|
},
|
2014-12-07 13:44:01 +00:00
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
// FindOptions bundle all options for the find command.
|
|
|
|
type FindOptions struct {
|
|
|
|
Oldest string
|
|
|
|
Newest string
|
|
|
|
Snapshot string
|
|
|
|
}
|
|
|
|
|
|
|
|
var findOptions FindOptions
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
cmdRoot.AddCommand(cmdFind)
|
|
|
|
|
|
|
|
f := cmdFind.Flags()
|
2017-02-13 15:02:47 +00:00
|
|
|
f.StringVarP(&findOptions.Oldest, "oldest", "o", "", "oldest modification date/time")
|
|
|
|
f.StringVarP(&findOptions.Newest, "newest", "n", "", "newest modification date/time")
|
|
|
|
f.StringVarP(&findOptions.Snapshot, "snapshot", "s", "", "snapshot ID to search in")
|
2016-09-17 10:36:05 +00:00
|
|
|
}
|
2014-12-07 16:11:01 +00:00
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
type findPattern struct {
|
2014-12-07 16:11:01 +00:00
|
|
|
oldest, newest time.Time
|
|
|
|
pattern string
|
2016-09-17 10:36:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type findResult struct {
|
|
|
|
node *restic.Node
|
|
|
|
path string
|
2014-12-07 16:11:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var timeFormats = []string{
|
|
|
|
"2006-01-02",
|
|
|
|
"2006-01-02 15:04",
|
|
|
|
"2006-01-02 15:04:05",
|
|
|
|
"2006-01-02 15:04:05 -0700",
|
|
|
|
"2006-01-02 15:04:05 MST",
|
|
|
|
"02.01.2006",
|
|
|
|
"02.01.2006 15:04",
|
|
|
|
"02.01.2006 15:04:05",
|
|
|
|
"02.01.2006 15:04:05 -0700",
|
|
|
|
"02.01.2006 15:04:05 MST",
|
|
|
|
"Mon Jan 2 15:04:05 -0700 MST 2006",
|
2014-12-07 15:30:52 +00:00
|
|
|
}
|
|
|
|
|
2014-12-07 16:11:01 +00:00
|
|
|
func parseTime(str string) (time.Time, error) {
|
|
|
|
for _, fmt := range timeFormats {
|
|
|
|
if t, err := time.ParseInLocation(fmt, str, time.Local); err == nil {
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-01 20:17:37 +00:00
|
|
|
return time.Time{}, errors.Fatalf("unable to parse time: %q", str)
|
2014-12-07 16:11:01 +00:00
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
func findInTree(repo *repository.Repository, pat findPattern, id restic.ID, path string) ([]findResult, error) {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("checking tree %v\n", id)
|
2016-09-03 09:22:01 +00:00
|
|
|
tree, err := repo.LoadTree(id)
|
2014-12-07 13:44:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
results := []findResult{}
|
2015-01-10 22:40:10 +00:00
|
|
|
for _, node := range tree.Nodes {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log(" testing entry %q\n", node.Name)
|
2014-12-07 16:11:01 +00:00
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
m, err := filepath.Match(pat.pattern, node.Name)
|
2014-12-07 13:44:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if m {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log(" pattern matches\n")
|
2016-09-17 10:36:05 +00:00
|
|
|
if !pat.oldest.IsZero() && node.ModTime.Before(pat.oldest) {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log(" ModTime is older than %s\n", pat.oldest)
|
2014-12-07 16:11:01 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
if !pat.newest.IsZero() && node.ModTime.After(pat.newest) {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log(" ModTime is newer than %s\n", pat.newest)
|
2014-12-07 16:11:01 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2014-12-07 13:44:01 +00:00
|
|
|
results = append(results, findResult{node: node, path: path})
|
2014-12-07 16:11:01 +00:00
|
|
|
} else {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log(" pattern does not match\n")
|
2014-12-07 13:44:01 +00:00
|
|
|
}
|
|
|
|
|
2016-09-01 19:20:03 +00:00
|
|
|
if node.Type == "dir" {
|
2016-09-17 10:36:05 +00:00
|
|
|
subdirResults, err := findInTree(repo, pat, *node.Subtree, filepath.Join(path, node.Name))
|
2014-12-07 13:44:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
results = append(results, subdirResults...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
func findInSnapshot(repo *repository.Repository, pat findPattern, id restic.ID) error {
|
2016-09-27 20:35:08 +00:00
|
|
|
debug.Log("searching in snapshot %s\n for entries within [%s %s]", id.Str(), pat.oldest, pat.newest)
|
2014-12-07 13:44:01 +00:00
|
|
|
|
2015-05-09 11:32:52 +00:00
|
|
|
sn, err := restic.LoadSnapshot(repo, id)
|
2014-12-07 13:44:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-03-02 21:41:11 +00:00
|
|
|
results, err := findInTree(repo, pat, *sn.Tree, string(filepath.Separator))
|
2014-12-07 13:44:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(results) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2016-09-17 10:36:05 +00:00
|
|
|
Verbosef("found %d matching entries in snapshot %s\n", len(results), id)
|
2014-12-07 13:44:01 +00:00
|
|
|
for _, res := range results {
|
|
|
|
res.node.Name = filepath.Join(res.path, res.node.Name)
|
2016-09-17 10:36:05 +00:00
|
|
|
Printf(" %s\n", res.node)
|
2014-12-07 13:44:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
func runFind(opts FindOptions, gopts GlobalOptions, args []string) error {
|
2014-12-07 16:11:01 +00:00
|
|
|
if len(args) != 1 {
|
2017-02-10 18:39:49 +00:00
|
|
|
return errors.Fatal("wrong number of arguments")
|
2014-12-07 16:11:01 +00:00
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
pat findPattern
|
|
|
|
)
|
2014-12-07 16:11:01 +00:00
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
if opts.Oldest != "" {
|
|
|
|
pat.oldest, err = parseTime(opts.Oldest)
|
2014-12-07 16:11:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
if opts.Newest != "" {
|
|
|
|
pat.newest, err = parseTime(opts.Newest)
|
2014-12-07 16:11:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-12-07 15:30:52 +00:00
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
repo, err := OpenRepository(gopts)
|
2014-12-07 15:30:52 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2014-12-07 13:44:01 +00:00
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
if !gopts.NoLock {
|
|
|
|
lock, err := lockRepo(repo)
|
|
|
|
defer unlockRepo(lock)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-06-27 12:40:18 +00:00
|
|
|
}
|
|
|
|
|
2015-08-27 21:21:44 +00:00
|
|
|
err = repo.LoadIndex()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
pat.pattern = args[0]
|
2014-12-07 16:11:01 +00:00
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
if opts.Snapshot != "" {
|
|
|
|
snapshotID, err := restic.FindSnapshot(repo, opts.Snapshot)
|
2014-12-07 13:44:01 +00:00
|
|
|
if err != nil {
|
2016-09-01 20:17:37 +00:00
|
|
|
return errors.Fatalf("invalid id %q: %v", args[1], err)
|
2014-12-07 13:44:01 +00:00
|
|
|
}
|
|
|
|
|
2016-09-17 10:36:05 +00:00
|
|
|
return findInSnapshot(repo, pat, snapshotID)
|
2014-12-07 13:44:01 +00:00
|
|
|
}
|
|
|
|
|
2015-03-28 10:50:23 +00:00
|
|
|
done := make(chan struct{})
|
|
|
|
defer close(done)
|
2016-09-01 14:04:29 +00:00
|
|
|
for snapshotID := range repo.List(restic.SnapshotFile, done) {
|
2016-09-17 10:36:05 +00:00
|
|
|
err := findInSnapshot(repo, pat, snapshotID)
|
2014-12-07 13:44:01 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|