package main import ( "path/filepath" "time" "restic" "restic/debug" "restic/errors" "restic/repository" ) type findResult struct { node *restic.Node path string } type CmdFind struct { Oldest string `short:"o" long:"oldest" description:"Oldest modification date/time"` Newest string `short:"n" long:"newest" description:"Newest modification date/time"` Snapshot string `short:"s" long:"snapshot" description:"Snapshot ID to search in"` oldest, newest time.Time pattern string global *GlobalOptions } 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", } func init() { _, err := parser.AddCommand("find", "find a file/directory", "The find command searches for files or directories in snapshots", &CmdFind{global: &globalOpts}) if err != nil { panic(err) } } 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 } } return time.Time{}, errors.Fatalf("unable to parse time: %q", str) } func (c CmdFind) findInTree(repo *repository.Repository, id restic.ID, path string) ([]findResult, error) { debug.Log("restic.find", "checking tree %v\n", id) tree, err := restic.LoadTree(repo, id) if err != nil { return nil, err } results := []findResult{} for _, node := range tree.Nodes { debug.Log("restic.find", " testing entry %q\n", node.Name) m, err := filepath.Match(c.pattern, node.Name) if err != nil { return nil, err } if m { debug.Log("restic.find", " pattern matches\n") if !c.oldest.IsZero() && node.ModTime.Before(c.oldest) { debug.Log("restic.find", " ModTime is older than %s\n", c.oldest) continue } if !c.newest.IsZero() && node.ModTime.After(c.newest) { debug.Log("restic.find", " ModTime is newer than %s\n", c.newest) continue } results = append(results, findResult{node: node, path: path}) } else { debug.Log("restic.find", " pattern does not match\n") } if node.Type == "dir" { subdirResults, err := c.findInTree(repo, *node.Subtree, filepath.Join(path, node.Name)) if err != nil { return nil, err } results = append(results, subdirResults...) } } return results, nil } func (c CmdFind) findInSnapshot(repo *repository.Repository, id restic.ID) error { debug.Log("restic.find", "searching in snapshot %s\n for entries within [%s %s]", id.Str(), c.oldest, c.newest) sn, err := restic.LoadSnapshot(repo, id) if err != nil { return err } results, err := c.findInTree(repo, *sn.Tree, "") if err != nil { return err } if len(results) == 0 { return nil } c.global.Verbosef("found %d matching entries in snapshot %s\n", len(results), id) for _, res := range results { res.node.Name = filepath.Join(res.path, res.node.Name) c.global.Printf(" %s\n", res.node) } return nil } func (CmdFind) Usage() string { return "[find-OPTIONS] PATTERN" } func (c CmdFind) Execute(args []string) error { if len(args) != 1 { return errors.Fatalf("wrong number of arguments, Usage: %s", c.Usage()) } var err error if c.Oldest != "" { c.oldest, err = parseTime(c.Oldest) if err != nil { return err } } if c.Newest != "" { c.newest, err = parseTime(c.Newest) if err != nil { return err } } repo, err := c.global.OpenRepository() if err != nil { return err } lock, err := lockRepo(repo) defer unlockRepo(lock) if err != nil { return err } err = repo.LoadIndex() if err != nil { return err } c.pattern = args[0] if c.Snapshot != "" { snapshotID, err := restic.FindSnapshot(repo, c.Snapshot) if err != nil { return errors.Fatalf("invalid id %q: %v", args[1], err) } return c.findInSnapshot(repo, snapshotID) } done := make(chan struct{}) defer close(done) for snapshotID := range repo.List(restic.SnapshotFile, done) { err := c.findInSnapshot(repo, snapshotID) if err != nil { return err } } return nil }