Skip to content

Commit

Permalink
Bump restic
Browse files Browse the repository at this point in the history
Upstream ref: 7facc8ccc1e3fd5cf7a396bd9e06ed4560bcc051
  • Loading branch information
rubiojr committed Dec 8, 2020
1 parent 4dcc4cc commit 2b65f02
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 160 deletions.
88 changes: 54 additions & 34 deletions backend/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,59 +220,79 @@ func (b *Local) Remove(ctx context.Context, h restic.Handle) error {
return fs.Remove(fn)
}

func isFile(fi os.FileInfo) bool {
return fi.Mode()&(os.ModeType|os.ModeCharDevice) == 0
}

// List runs fn for each file in the backend which has the type t. When an
// error occurs (or fn returns an error), List stops and returns it.
func (b *Local) List(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error {
func (b *Local) List(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) (err error) {
debug.Log("List %v", t)

basedir, subdirs := b.Basedir(t)
err := fs.Walk(basedir, func(path string, fi os.FileInfo, err error) error {
debug.Log("walk on %v\n", path)
if err != nil {
return err
}
if subdirs {
err = visitDirs(ctx, basedir, fn)
} else {
err = visitFiles(ctx, basedir, fn)
}

if path == basedir {
return nil
}
if b.IsNotExist(err) {
debug.Log("ignoring non-existing directory")
return nil
}

if !isFile(fi) {
return nil
}
return err
}

if fi.IsDir() && !subdirs {
return filepath.SkipDir
}
// The following two functions are like filepath.Walk, but visit only one or
// two levels of directory structure (including dir itself as the first level).
// Also, visitDirs assumes it sees a directory full of directories, while
// visitFiles wants a directory full or regular files.
func visitDirs(ctx context.Context, dir string, fn func(restic.FileInfo) error) error {
d, err := fs.Open(dir)
if err != nil {
return err
}
defer d.Close()

debug.Log("send %v\n", filepath.Base(path))
sub, err := d.Readdirnames(-1)
if err != nil {
return err
}

rfi := restic.FileInfo{
Name: filepath.Base(path),
Size: fi.Size(),
for _, f := range sub {
err = visitFiles(ctx, filepath.Join(dir, f), fn)
if err != nil {
return err
}
}
return ctx.Err()
}

if ctx.Err() != nil {
func visitFiles(ctx context.Context, dir string, fn func(restic.FileInfo) error) error {
d, err := fs.Open(dir)
if err != nil {
return err
}
defer d.Close()

sub, err := d.Readdir(-1)
if err != nil {
return err
}

for _, fi := range sub {
select {
case <-ctx.Done():
return ctx.Err()
default:
}

err = fn(rfi)
err := fn(restic.FileInfo{
Name: fi.Name(),
Size: fi.Size(),
})
if err != nil {
return err
}

return ctx.Err()
})

if b.IsNotExist(err) {
debug.Log("ignoring non-existing directory")
return nil
}

return err
return nil
}

// Delete removes the repository and all files.
Expand Down
18 changes: 7 additions & 11 deletions cmd/rapi/cmd_rescue_restore_all_versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,17 @@ func restoreAllVersions(c *cli.Context) error {
return err
}

snapshots, err := restic.LoadAllSnapshots(ctx, rapiRepo, nil)
if err != nil {
return err
}

filesFound := map[fileID]bool{}
for _, sn := range snapshots {
restic.ForAllSnapshots(ctx, rapiRepo, nil, func(id restic.ID, sn *restic.Snapshot, err error) error {
if err != nil {
return err
}

if sn.Tree == nil {
return fmt.Errorf("snapshot %s has nil tree", sn.ID().Str())
}
err = walker.Walk(ctx, rapiRepo, *sn.Tree, restic.NewIDSet(), rescueWalkTree(pattern, filesFound, targetDir))
if err != nil {
return fmt.Errorf("walking tree %s: %v", *sn.Tree, err)
}
}
return walker.Walk(ctx, rapiRepo, *sn.Tree, restic.NewIDSet(), rescueWalkTree(pattern, filesFound, targetDir))
})

s.Stop()
fmt.Printf("%d files matched.\n", len(filesFound))
Expand Down
98 changes: 18 additions & 80 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,86 +298,6 @@ func (e Error) Error() string {
return e.Err.Error()
}

func loadTreeFromSnapshot(ctx context.Context, repo restic.Repository, id restic.ID) (restic.ID, error) {
sn, err := restic.LoadSnapshot(ctx, repo, id)
if err != nil {
debug.Log("error loading snapshot %v: %v", id, err)
return restic.ID{}, err
}

if sn.Tree == nil {
debug.Log("snapshot %v has no tree", id)
return restic.ID{}, errors.Errorf("snapshot %v has no tree", id)
}

return *sn.Tree, nil
}

// loadSnapshotTreeIDs loads all snapshots from backend and returns the tree IDs.
func loadSnapshotTreeIDs(ctx context.Context, repo restic.Repository) (restic.IDs, []error) {
var trees struct {
IDs restic.IDs
sync.Mutex
}

var errs struct {
errs []error
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 restic.ID)

// send list of index files through ch, which is closed afterwards
wg.Go(func() error {
defer close(ch)
return repo.List(ctx, restic.SnapshotFile, func(id restic.ID, size int64) error {
select {
case <-ctx.Done():
return nil
case ch <- id:
}
return nil
})
})

// a worker receives an index ID from ch, loads the snapshot and the tree,
// and adds the result to errs and trees.
worker := func() error {
for id := range ch {
debug.Log("load snapshot %v", id)

treeID, err := loadTreeFromSnapshot(ctx, repo, id)
if err != nil {
errs.Lock()
errs.errs = append(errs.errs, err)
errs.Unlock()
continue
}

debug.Log("snapshot %v has tree %v", id, treeID)
trees.Lock()
trees.IDs = append(trees.IDs, treeID)
trees.Unlock()
}
return nil
}

for i := 0; i < defaultParallelism; i++ {
wg.Go(worker)
}

err := wg.Wait()
if err != nil {
errs.errs = append(errs.errs, err)
}

return trees.IDs, errs.errs
}

// TreeError collects several errors that occurred while processing a tree.
type TreeError struct {
ID restic.ID
Expand Down Expand Up @@ -586,6 +506,24 @@ func (c *Checker) filterTrees(ctx context.Context, backlog restic.IDs, loaderCha
}
}

func loadSnapshotTreeIDs(ctx context.Context, repo restic.Repository) (ids restic.IDs, errs []error) {
err := restic.ForAllSnapshots(ctx, repo, nil, func(id restic.ID, sn *restic.Snapshot, err error) error {
if err != nil {
errs = append(errs, err)
return nil
}
treeID := *sn.Tree
debug.Log("snapshot %v has tree %v", id, treeID)
ids = append(ids, treeID)
return nil
})
if err != nil {
errs = append(errs, err)
}

return ids, errs
}

// Structure checks that for all snapshots all referenced data blobs and
// subtrees are available in the index. errChan is closed after all trees have
// been traversed.
Expand Down
7 changes: 5 additions & 2 deletions internal/fuse/dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,11 @@ func (d *dir) Attr(ctx context.Context, a *fuse.Attr) error {
debug.Log("Attr()")
a.Inode = d.inode
a.Mode = os.ModeDir | d.node.Mode
a.Uid = d.root.uid
a.Gid = d.root.gid

if !d.root.cfg.OwnerIsRoot {
a.Uid = d.node.UID
a.Gid = d.node.GID
}
a.Atime = d.node.AccessTime
a.Ctime = d.node.ChangeTime
a.Mtime = d.node.ModTime
Expand Down
36 changes: 34 additions & 2 deletions internal/fuse/fuse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,37 @@ func TestFuseFile(t *testing.T) {
}
}

func TestFuseDir(t *testing.T) {
repo, cleanup := repository.TestRepository(t)
defer cleanup()

root := &Root{repo: repo, blobCache: newBlobCache(blobCacheSize)}

node := &restic.Node{
Mode: 0755,
UID: 42,
GID: 43,
AccessTime: time.Unix(1606773731, 0),
ChangeTime: time.Unix(1606773732, 0),
ModTime: time.Unix(1606773733, 0),
}
parentInode := fs.GenerateDynamicInode(0, "parent")
inode := fs.GenerateDynamicInode(1, "foo")
d, err := newDir(context.TODO(), root, inode, parentInode, node)
rtest.OK(t, err)

// don't open the directory as that would require setting up a proper tree blob
attr := fuse.Attr{}
rtest.OK(t, d.Attr(context.TODO(), &attr))

rtest.Equals(t, inode, attr.Inode)
rtest.Equals(t, node.UID, attr.Uid)
rtest.Equals(t, node.GID, attr.Gid)
rtest.Equals(t, node.AccessTime, attr.Atime)
rtest.Equals(t, node.ChangeTime, attr.Ctime)
rtest.Equals(t, node.ModTime, attr.Mtime)
}

// Test top-level directories for their UID and GID.
func TestTopUidGid(t *testing.T) {
repo, cleanup := repository.TestRepository(t)
Expand Down Expand Up @@ -222,8 +253,9 @@ func testTopUidGid(t *testing.T, cfg Config, repo restic.Repository, uid, gid ui
snapshotdir, err := idsdir.(fs.NodeStringLookuper).Lookup(ctx, snapID)
rtest.OK(t, err)

// restic.TestCreateSnapshot does not set the UID/GID thus it must be always zero
err = snapshotdir.Attr(ctx, &attr)
rtest.OK(t, err)
rtest.Equals(t, uid, attr.Uid)
rtest.Equals(t, gid, attr.Gid)
rtest.Equals(t, uint32(0), attr.Uid)
rtest.Equals(t, uint32(0), attr.Gid)
}
6 changes: 2 additions & 4 deletions internal/ui/termstatus/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,9 @@ func (t *Terminal) run(ctx context.Context) {
for {
select {
case <-ctx.Done():
if IsProcessBackground(t.fd) {
// ignore all messages, do nothing, we are in the background process group
continue
if !IsProcessBackground(t.fd) {
t.undoStatus(len(status))
}
t.undoStatus(len(status))

return

Expand Down
4 changes: 2 additions & 2 deletions internal/ui/termstatus/terminal_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ func canUpdateStatus(fd uintptr) bool {
return true
}

// check if the output file type is a pipe (0x0003)
if isPipe(fd) {
// check that the output file type is a pipe (0x0003)
if !isPipe(fd) {
return false
}

Expand Down
10 changes: 9 additions & 1 deletion repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ func (r *Repository) LoadAndDecrypt(ctx context.Context, buf []byte, t restic.Fi

debug.Log("load %v with id %v", t, id)

if t == restic.ConfigFile {
id = restic.ID{}
}

h := restic.Handle{Type: t, Name: id.String()}
err := r.be.Load(ctx, h, 0, 0, func(rd io.Reader) error {
// make sure this call is idempotent, in case an error occurs
Expand Down Expand Up @@ -305,7 +309,11 @@ func (r *Repository) SaveUnpacked(ctx context.Context, t restic.FileType, p []by

ciphertext = r.key.Seal(ciphertext, nonce, p, nil)

id = restic.Hash(ciphertext)
if t == restic.ConfigFile {
id = restic.ID{}
} else {
id = restic.Hash(ciphertext)
}
h := restic.Handle{Type: t, Name: id.String()}

err = r.be.Save(ctx, h, restic.NewByteReader(ciphertext))
Expand Down
8 changes: 8 additions & 0 deletions repository/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,14 @@ func TestLoadJSONUnpacked(t *testing.T) {

rtest.Equals(t, sn.Hostname, sn2.Hostname)
rtest.Equals(t, sn.Username, sn2.Username)

var cf restic.Config

// load and check Config
err = repo.LoadJSONUnpacked(context.TODO(), restic.ConfigFile, id, &cf)
rtest.OK(t, err)

rtest.Equals(t, cf.ChunkerPolynomial, repository.TestChunkerPol)
}

var repoFixture = filepath.Join("testdata", "test-repo.tar.gz")
Expand Down
Loading

0 comments on commit 2b65f02

Please sign in to comment.