restic/internal/restic/snapshot.go

294 lines
6.6 KiB
Go
Raw Normal View History

2014-12-05 20:45:49 +00:00
package restic
2014-08-04 18:47:04 +00:00
import (
2017-06-04 09:16:55 +00:00
"context"
2014-08-11 20:47:24 +00:00
"fmt"
"os/user"
2014-09-23 20:39:12 +00:00
"path/filepath"
2020-11-28 08:32:06 +00:00
"sync"
2014-08-04 18:47:04 +00:00
"time"
2017-07-23 12:21:03 +00:00
2020-11-28 08:32:06 +00:00
"golang.org/x/sync/errgroup"
2017-07-23 12:21:03 +00:00
"github.com/restic/restic/internal/debug"
2014-08-04 18:47:04 +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"`
2016-09-13 18:12:55 +00:00
Tags []string `json:"tags,omitempty"`
Original *ID `json:"original,omitempty"`
2016-08-31 18:29:54 +00:00
id *ID // plaintext ID, used during restore
2014-08-04 18:47:04 +00:00
}
// NewSnapshot returns an initialized snapshot struct for the current user and
// time.
func NewSnapshot(paths []string, tags []string, hostname string, time time.Time) (*Snapshot, error) {
2018-01-03 21:10:20 +00:00
absPaths := make([]string, 0, len(paths))
for _, path := range paths {
p, err := filepath.Abs(path)
if err == nil {
absPaths = append(absPaths, p)
} else {
absPaths = append(absPaths, path)
2015-03-02 13:48:47 +00:00
}
2014-09-23 20:39:12 +00:00
}
2014-08-04 18:47:04 +00:00
sn := &Snapshot{
2018-01-03 21:10:20 +00:00
Paths: absPaths,
Time: time,
2017-02-10 18:37:33 +00:00
Tags: tags,
Hostname: hostname,
2014-08-04 18:47:04 +00:00
}
2017-02-10 18:37:33 +00:00
err := sn.fillUserInfo()
if err != nil {
return nil, err
2014-08-04 18:47:04 +00:00
}
return sn, nil
2014-08-04 18:47:04 +00:00
}
// LoadSnapshot loads the snapshot with the id and returns it.
func LoadSnapshot(ctx context.Context, loader LoaderUnpacked, id ID) (*Snapshot, error) {
sn := &Snapshot{id: &id}
err := LoadJSONUnpacked(ctx, loader, 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
// SaveSnapshot saves the snapshot sn and returns its ID.
func SaveSnapshot(ctx context.Context, repo SaverUnpacked, sn *Snapshot) (ID, error) {
return SaveJSONUnpacked(ctx, repo, SnapshotFile, sn)
}
2020-11-28 08:32:06 +00:00
// ForAllSnapshots reads all snapshots in parallel and calls the
// given function. It is guaranteed that the function is not run concurrently.
// If the called function returns an error, this function is cancelled and
// also returns this error.
2020-11-28 07:59:12 +00:00
// If a snapshot ID is in excludeIDs, it will be ignored.
func ForAllSnapshots(ctx context.Context, be Lister, loader LoaderUnpacked, excludeIDs IDSet, fn func(ID, *Snapshot, error) error) error {
2020-11-28 08:32:06 +00:00
var m sync.Mutex
// track spawned goroutines using wg, create a new context which is
// cancelled as soon as an error occurs.
wg, ctx := errgroup.WithContext(ctx)
ch := make(chan ID)
// send list of snapshot files through ch, which is closed afterwards
wg.Go(func() error {
defer close(ch)
return be.List(ctx, SnapshotFile, func(fi FileInfo) error {
id, err := ParseID(fi.Name)
if err != nil {
debug.Log("unable to parse %v as an ID", fi.Name)
return nil
}
2020-11-28 08:32:06 +00:00
if excludeIDs.Has(id) {
return nil
}
select {
case <-ctx.Done():
return nil
case ch <- id:
}
2020-07-19 05:13:41 +00:00
return nil
2020-11-28 08:32:06 +00:00
})
2020-11-28 07:59:12 +00:00
})
2020-11-28 08:32:06 +00:00
// a worker receives an snapshot ID from ch, loads the snapshot
// and runs fn with id, the snapshot and the error
worker := func() error {
for id := range ch {
debug.Log("load snapshot %v", id)
sn, err := LoadSnapshot(ctx, loader, id)
2020-11-28 08:32:06 +00:00
m.Lock()
err = fn(id, sn, err)
m.Unlock()
if err != nil {
return err
}
}
return nil
}
// For most snapshots decoding is nearly for free, thus just assume were only limited by IO
for i := 0; i < int(loader.Connections()); i++ {
2020-11-28 08:32:06 +00:00
wg.Go(worker)
}
return wg.Wait()
2020-11-28 07:59:12 +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
}
2017-02-08 23:43:10 +00:00
// ID returns the snapshot's ID.
2016-08-31 18:29:54 +00:00
func (sn Snapshot) ID() *ID {
return sn.id
}
func (sn *Snapshot) fillUserInfo() error {
usr, err := user.Current()
if err != nil {
return nil
}
sn.Username = usr.Username
// set userid and groupid
sn.UID, sn.GID, err = uidGidInt(*usr)
return err
}
// AddTags adds the given tags to the snapshots tags, preventing duplicates.
// It returns true if any changes were made.
func (sn *Snapshot) AddTags(addTags []string) (changed bool) {
nextTag:
for _, add := range addTags {
for _, tag := range sn.Tags {
if tag == add {
continue nextTag
}
}
sn.Tags = append(sn.Tags, add)
changed = true
}
return
}
// RemoveTags removes the given tags from the snapshots tags and
// returns true if any changes were made.
func (sn *Snapshot) RemoveTags(removeTags []string) (changed bool) {
for _, remove := range removeTags {
for i, tag := range sn.Tags {
if tag == remove {
// https://github.com/golang/go/wiki/SliceTricks
sn.Tags[i] = sn.Tags[len(sn.Tags)-1]
sn.Tags[len(sn.Tags)-1] = ""
sn.Tags = sn.Tags[:len(sn.Tags)-1]
changed = true
break
}
}
}
return
}
func (sn *Snapshot) hasTag(tag string) bool {
for _, snTag := range sn.Tags {
if tag == snTag {
return true
}
}
return false
}
2017-07-09 07:24:02 +00:00
// HasTags returns true if the snapshot has all the tags in l.
func (sn *Snapshot) HasTags(l []string) bool {
for _, tag := range l {
if tag == "" && len(sn.Tags) == 0 {
return true
}
2017-07-09 07:24:02 +00:00
if !sn.hasTag(tag) {
return false
}
}
2017-07-09 07:24:02 +00:00
return true
}
// HasTagList returns true if either
// - the snapshot satisfies at least one TagList, so there is a TagList in l
// for which all tags are included in sn, or
// - l is empty
2017-07-09 07:47:41 +00:00
func (sn *Snapshot) HasTagList(l []TagList) bool {
debug.Log("testing snapshot with tags %v against list: %v", sn.Tags, l)
if len(l) == 0 {
return true
}
for _, tags := range l {
if sn.HasTags(tags) {
2017-09-25 12:35:37 +00:00
debug.Log(" snapshot satisfies %v %v", tags, l)
2017-07-09 07:47:41 +00:00
return true
}
}
return false
}
func (sn *Snapshot) hasPath(path string) bool {
for _, snPath := range sn.Paths {
if path == snPath {
return true
}
}
return false
}
2017-07-09 07:24:02 +00:00
// HasPaths returns true if the snapshot has all of the paths.
func (sn *Snapshot) HasPaths(paths []string) bool {
for _, path := range paths {
2017-07-09 07:24:02 +00:00
if !sn.hasPath(path) {
return false
2016-05-10 19:51:56 +00:00
}
}
2017-07-09 07:24:02 +00:00
return true
}
// HasHostname returns true if either
// - the snapshot hostname is in the list of the given hostnames, or
// - the list of given hostnames is empty
func (sn *Snapshot) HasHostname(hostnames []string) bool {
if len(hostnames) == 0 {
return true
}
for _, hostname := range hostnames {
if sn.Hostname == hostname {
return true
}
}
return false
}
// Snapshots is a list of snapshots.
type Snapshots []*Snapshot
// Len returns the number of snapshots in sn.
func (sn Snapshots) Len() int {
return len(sn)
}
// Less returns true iff the ith snapshot has been made after the jth.
func (sn Snapshots) Less(i, j int) bool {
return sn[i].Time.After(sn[j].Time)
}
// Swap exchanges the two snapshots.
func (sn Snapshots) Swap(i, j int) {
sn[i], sn[j] = sn[j], sn[i]
}