2014-12-05 20:45:49 +00:00
|
|
|
package restic
|
2014-08-04 18:47:04 +00:00
|
|
|
|
|
|
|
import (
|
2014-08-11 20:47:24 +00:00
|
|
|
"fmt"
|
2014-08-04 18:47:04 +00:00
|
|
|
"os"
|
2015-04-29 21:35:02 +00:00
|
|
|
"os/user"
|
2014-09-23 20:39:12 +00:00
|
|
|
"path/filepath"
|
2014-08-04 18:47:04 +00:00
|
|
|
"time"
|
2014-09-23 20:39:12 +00:00
|
|
|
|
2016-08-21 15:46:23 +00:00
|
|
|
"github.com/pkg/errors"
|
2014-08-04 18:47:04 +00:00
|
|
|
)
|
|
|
|
|
2016-05-08 20:38:38 +00:00
|
|
|
// Snapshot is the state of a resource at one point in time.
|
2014-08-04 18:47:04 +00:00
|
|
|
type Snapshot struct {
|
2016-08-31 18:29:54 +00:00
|
|
|
Time time.Time `json:"time"`
|
|
|
|
Parent *ID `json:"parent,omitempty"`
|
|
|
|
Tree *ID `json:"tree"`
|
|
|
|
Paths []string `json:"paths"`
|
|
|
|
Hostname string `json:"hostname,omitempty"`
|
|
|
|
Username string `json:"username,omitempty"`
|
|
|
|
UID uint32 `json:"uid,omitempty"`
|
|
|
|
GID uint32 `json:"gid,omitempty"`
|
|
|
|
Excludes []string `json:"excludes,omitempty"`
|
|
|
|
|
|
|
|
id *ID // plaintext ID, used during restore
|
2014-08-04 18:47:04 +00:00
|
|
|
}
|
|
|
|
|
2016-05-08 20:38:38 +00:00
|
|
|
// NewSnapshot returns an initialized snapshot struct for the current user and
|
|
|
|
// time.
|
2015-03-02 13:48:47 +00:00
|
|
|
func NewSnapshot(paths []string) (*Snapshot, error) {
|
|
|
|
for i, path := range paths {
|
|
|
|
if p, err := filepath.Abs(path); err != nil {
|
|
|
|
paths[i] = p
|
|
|
|
}
|
2014-09-23 20:39:12 +00:00
|
|
|
}
|
|
|
|
|
2014-08-04 18:47:04 +00:00
|
|
|
sn := &Snapshot{
|
2015-03-02 13:48:47 +00:00
|
|
|
Paths: paths,
|
|
|
|
Time: time.Now(),
|
2014-08-04 18:47:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
hn, err := os.Hostname()
|
|
|
|
if err == nil {
|
|
|
|
sn.Hostname = hn
|
|
|
|
}
|
|
|
|
|
2015-02-03 21:04:09 +00:00
|
|
|
err = sn.fillUserInfo()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2014-08-04 18:47:04 +00:00
|
|
|
}
|
|
|
|
|
2014-12-21 16:20:49 +00:00
|
|
|
return sn, nil
|
2014-08-04 18:47:04 +00:00
|
|
|
}
|
|
|
|
|
2016-05-08 20:38:38 +00:00
|
|
|
// LoadSnapshot loads the snapshot with the id and returns it.
|
2016-08-31 18:29:54 +00:00
|
|
|
func LoadSnapshot(repo Repository, id ID) (*Snapshot, error) {
|
2015-07-25 15:05:45 +00:00
|
|
|
sn := &Snapshot{id: &id}
|
2016-08-31 18:29:54 +00:00
|
|
|
err := repo.LoadJSONUnpacked(SnapshotFile, id, sn)
|
2014-08-04 20:46:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return sn, nil
|
2014-08-04 18:47:04 +00:00
|
|
|
}
|
2014-08-11 20:47:24 +00:00
|
|
|
|
2016-05-08 20:38:38 +00:00
|
|
|
// LoadAllSnapshots returns a list of all snapshots in the repo.
|
2016-08-31 18:29:54 +00:00
|
|
|
func LoadAllSnapshots(repo Repository) (snapshots []*Snapshot, err error) {
|
2016-04-10 14:51:46 +00:00
|
|
|
done := make(chan struct{})
|
|
|
|
defer close(done)
|
|
|
|
|
2016-08-31 18:29:54 +00:00
|
|
|
for id := range repo.List(SnapshotFile, done) {
|
2016-04-10 14:51:46 +00:00
|
|
|
sn, err := LoadSnapshot(repo, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
snapshots = append(snapshots, sn)
|
|
|
|
}
|
|
|
|
|
|
|
|
return snapshots, nil
|
|
|
|
}
|
|
|
|
|
2014-11-24 20:12:32 +00:00
|
|
|
func (sn Snapshot) String() string {
|
2016-08-19 18:50:52 +00:00
|
|
|
return fmt.Sprintf("<Snapshot %s of %v at %s by %s@%s>",
|
|
|
|
sn.id.Str(), sn.Paths, sn.Time, sn.Username, sn.Hostname)
|
2014-08-11 20:47:24 +00:00
|
|
|
}
|
2014-11-24 20:12:32 +00:00
|
|
|
|
2016-05-08 20:38:38 +00:00
|
|
|
// ID retuns the snapshot's ID.
|
2016-08-31 18:29:54 +00:00
|
|
|
func (sn Snapshot) ID() *ID {
|
2014-11-24 20:12:32 +00:00
|
|
|
return sn.id
|
|
|
|
}
|
2015-04-29 21:35:02 +00:00
|
|
|
|
|
|
|
func (sn *Snapshot) fillUserInfo() error {
|
|
|
|
usr, err := user.Current()
|
|
|
|
if err != nil {
|
2015-05-14 21:13:00 +00:00
|
|
|
return nil
|
2015-04-29 21:35:02 +00:00
|
|
|
}
|
|
|
|
sn.Username = usr.Username
|
2015-05-14 21:13:00 +00:00
|
|
|
|
2015-08-16 11:16:02 +00:00
|
|
|
// set userid and groupid
|
|
|
|
sn.UID, sn.GID, err = uidGidInt(*usr)
|
|
|
|
return err
|
2015-04-29 21:35:02 +00:00
|
|
|
}
|
2015-05-17 18:48:59 +00:00
|
|
|
|
2016-05-10 19:51:56 +00:00
|
|
|
// SamePaths compares the Snapshot's paths and provided paths are exactly the same
|
2016-05-10 19:23:18 +00:00
|
|
|
func SamePaths(expected, actual []string) bool {
|
|
|
|
if expected == nil || actual == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := range expected {
|
2016-05-10 19:51:56 +00:00
|
|
|
found := false
|
|
|
|
for j := range actual {
|
|
|
|
if expected[i] == actual[j] {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
2016-05-10 19:23:18 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-08-19 18:36:24 +00:00
|
|
|
// ErrNoSnapshotFound is returned when no snapshot for the given criteria could be found.
|
2016-05-10 19:23:18 +00:00
|
|
|
var ErrNoSnapshotFound = errors.New("no snapshot found")
|
|
|
|
|
2016-05-10 19:51:56 +00:00
|
|
|
// FindLatestSnapshot finds latest snapshot with optional target/directory and source filters
|
2016-08-31 18:29:54 +00:00
|
|
|
func FindLatestSnapshot(repo Repository, targets []string, source string) (ID, error) {
|
2016-05-10 19:23:18 +00:00
|
|
|
var (
|
|
|
|
latest time.Time
|
2016-08-31 18:29:54 +00:00
|
|
|
latestID ID
|
2016-05-10 19:23:18 +00:00
|
|
|
found bool
|
|
|
|
)
|
|
|
|
|
2016-08-31 18:29:54 +00:00
|
|
|
for snapshotID := range repo.List(SnapshotFile, make(chan struct{})) {
|
2016-05-10 19:23:18 +00:00
|
|
|
snapshot, err := LoadSnapshot(repo, snapshotID)
|
|
|
|
if err != nil {
|
2016-08-31 18:29:54 +00:00
|
|
|
return ID{}, errors.Errorf("Error listing snapshot: %v", err)
|
2016-05-10 19:23:18 +00:00
|
|
|
}
|
|
|
|
if snapshot.Time.After(latest) && SamePaths(snapshot.Paths, targets) && (source == "" || source == snapshot.Hostname) {
|
|
|
|
latest = snapshot.Time
|
|
|
|
latestID = snapshotID
|
|
|
|
found = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !found {
|
2016-08-31 18:29:54 +00:00
|
|
|
return ID{}, ErrNoSnapshotFound
|
2016-05-10 19:23:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return latestID, nil
|
|
|
|
}
|
2016-09-01 14:04:29 +00:00
|
|
|
|
|
|
|
// FindSnapshot takes a string and tries to find a snapshot whose ID matches
|
|
|
|
// the string as closely as possible.
|
|
|
|
func FindSnapshot(repo Repository, s string) (ID, error) {
|
|
|
|
|
|
|
|
// find snapshot id with prefix
|
|
|
|
name, err := Find(repo.Backend(), SnapshotFile, s)
|
|
|
|
if err != nil {
|
|
|
|
return ID{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ParseID(name)
|
|
|
|
}
|