From c458e114d4d073f9a1f99bf24e54e5c67416fef1 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 10 Apr 2020 11:31:32 +0200 Subject: [PATCH 01/12] pass context to Find / FindSnapshot This allows proper interruption of restic while it searches for snapshots or key files. --- cmd/restic/cmd_backup.go | 2 +- cmd/restic/cmd_cat.go | 2 +- cmd/restic/cmd_diff.go | 2 +- cmd/restic/cmd_dump.go | 2 +- cmd/restic/cmd_key.go | 2 +- cmd/restic/cmd_restore.go | 2 +- cmd/restic/find.go | 2 +- internal/checker/checker_test.go | 2 +- internal/repository/key.go | 2 +- internal/repository/repository.go | 4 ++-- internal/restic/backend_find.go | 8 ++++---- internal/restic/backend_find_test.go | 14 +++++++------- internal/restic/snapshot_find.go | 4 ++-- 13 files changed, 24 insertions(+), 24 deletions(-) diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index e42ba81d9..fd138888c 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -394,7 +394,7 @@ func collectTargets(opts BackupOptions, args []string) (targets []string, err er func findParentSnapshot(ctx context.Context, repo restic.Repository, opts BackupOptions, targets []string) (parentID *restic.ID, err error) { // Force using a parent if !opts.Force && opts.Parent != "" { - id, err := restic.FindSnapshot(repo, opts.Parent) + id, err := restic.FindSnapshot(ctx, repo, opts.Parent) if err != nil { return nil, errors.Fatalf("invalid id %q: %v", opts.Parent, err) } diff --git a/cmd/restic/cmd_cat.go b/cmd/restic/cmd_cat.go index 935a03dbc..281ed875f 100644 --- a/cmd/restic/cmd_cat.go +++ b/cmd/restic/cmd_cat.go @@ -59,7 +59,7 @@ func runCat(gopts GlobalOptions, args []string) error { } // find snapshot id with prefix - id, err = restic.FindSnapshot(repo, args[1]) + id, err = restic.FindSnapshot(gopts.ctx, repo, args[1]) if err != nil { return errors.Fatalf("could not find snapshot: %v\n", err) } diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go index 1fc8b0458..a4873319f 100644 --- a/cmd/restic/cmd_diff.go +++ b/cmd/restic/cmd_diff.go @@ -53,7 +53,7 @@ func init() { } func loadSnapshot(ctx context.Context, repo *repository.Repository, desc string) (*restic.Snapshot, error) { - id, err := restic.FindSnapshot(repo, desc) + id, err := restic.FindSnapshot(ctx, repo, desc) if err != nil { return nil, err } diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go index 4a9e57a37..b33863c4f 100644 --- a/cmd/restic/cmd_dump.go +++ b/cmd/restic/cmd_dump.go @@ -160,7 +160,7 @@ func runDump(opts DumpOptions, gopts GlobalOptions, args []string) error { Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Hosts:%v", err, opts.Paths, opts.Hosts) } } else { - id, err = restic.FindSnapshot(repo, snapshotIDString) + id, err = restic.FindSnapshot(ctx, repo, snapshotIDString) if err != nil { Exitf(1, "invalid id %q: %v", snapshotIDString, err) } diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index d14ba400e..8a54d83e8 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -210,7 +210,7 @@ func runKey(gopts GlobalOptions, args []string) error { return err } - id, err := restic.Find(repo.Backend(), restic.KeyFile, args[1]) + id, err := restic.Find(ctx, repo.Backend(), restic.KeyFile, args[1]) if err != nil { return err } diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index e87bafbba..ec7d4a9e9 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -122,7 +122,7 @@ func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error { Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Hosts:%v", err, opts.Paths, opts.Hosts) } } else { - id, err = restic.FindSnapshot(repo, snapshotIDString) + id, err = restic.FindSnapshot(ctx, repo, snapshotIDString) if err != nil { Exitf(1, "invalid id %q: %v", snapshotIDString, err) } diff --git a/cmd/restic/find.go b/cmd/restic/find.go index 8d39e177f..70710e2cb 100644 --- a/cmd/restic/find.go +++ b/cmd/restic/find.go @@ -29,7 +29,7 @@ func FindFilteredSnapshots(ctx context.Context, repo *repository.Repository, hos continue } } else { - id, err = restic.FindSnapshot(repo, s) + id, err = restic.FindSnapshot(ctx, repo, s) if err != nil { Warnf("Ignoring %q, it is not a snapshot id\n", s) continue diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go index 96047569b..ce12420eb 100644 --- a/internal/checker/checker_test.go +++ b/internal/checker/checker_test.go @@ -574,7 +574,7 @@ func benchmarkSnapshotScaling(t *testing.B, newSnapshots int) { chkr, repo, cleanup := loadBenchRepository(t) defer cleanup() - snID, err := restic.FindSnapshot(repo, "51d249d2") + snID, err := restic.FindSnapshot(context.TODO(), repo, "51d249d2") if err != nil { t.Fatal(err) } diff --git a/internal/repository/key.go b/internal/repository/key.go index 69eb7e04b..42045389f 100644 --- a/internal/repository/key.go +++ b/internal/repository/key.go @@ -116,7 +116,7 @@ func SearchKey(ctx context.Context, s *Repository, password string, maxKeys int, checked := 0 if len(keyHint) > 0 { - id, err := restic.Find(s.Backend(), restic.KeyFile, keyHint) + id, err := restic.Find(ctx, s.Backend(), restic.KeyFile, keyHint) if err == nil { key, err := OpenKey(ctx, s, id, password) diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 3c6d9665f..1b4a41c31 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -72,8 +72,8 @@ func (r *Repository) UseCache(c *cache.Cache) { // PrefixLength returns the number of bytes required so that all prefixes of // all IDs of type t are unique. -func (r *Repository) PrefixLength(t restic.FileType) (int, error) { - return restic.PrefixLength(r.be, t) +func (r *Repository) PrefixLength(ctx context.Context, t restic.FileType) (int, error) { + return restic.PrefixLength(ctx, r.be, t) } // LoadAndDecrypt loads and decrypts the file with the given type and ID, using diff --git a/internal/restic/backend_find.go b/internal/restic/backend_find.go index 36b2ba4ca..ec85a957a 100644 --- a/internal/restic/backend_find.go +++ b/internal/restic/backend_find.go @@ -17,10 +17,10 @@ var ErrMultipleIDMatches = errors.New("multiple IDs with prefix found") // Find loads the list of all files of type t and searches for names which // start with prefix. If none is found, nil and ErrNoIDPrefixFound is returned. // If more than one is found, nil and ErrMultipleIDMatches is returned. -func Find(be Lister, t FileType, prefix string) (string, error) { +func Find(ctx context.Context, be Lister, t FileType, prefix string) (string, error) { match := "" - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(ctx) defer cancel() err := be.List(ctx, t, func(fi FileInfo) error { @@ -50,11 +50,11 @@ const minPrefixLength = 8 // PrefixLength returns the number of bytes required so that all prefixes of // all names of type t are unique. -func PrefixLength(be Lister, t FileType) (int, error) { +func PrefixLength(ctx context.Context, be Lister, t FileType) (int, error) { // load all IDs of the given type list := make([]string, 0, 100) - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(ctx) defer cancel() err := be.List(ctx, t, func(fi FileInfo) error { diff --git a/internal/restic/backend_find_test.go b/internal/restic/backend_find_test.go index d122b4f94..557e1367b 100644 --- a/internal/restic/backend_find_test.go +++ b/internal/restic/backend_find_test.go @@ -38,7 +38,7 @@ func TestFind(t *testing.T) { return nil } - f, err := Find(m, SnapshotFile, "20bdc1402a6fc9b633aa") + f, err := Find(context.TODO(), m, SnapshotFile, "20bdc1402a6fc9b633aa") if err != nil { t.Error(err) } @@ -47,7 +47,7 @@ func TestFind(t *testing.T) { t.Errorf("Wrong match returned want %s, got %s", expectedMatch, f) } - f, err = Find(m, SnapshotFile, "NotAPrefix") + f, err = Find(context.TODO(), m, SnapshotFile, "NotAPrefix") if err != ErrNoIDPrefixFound { t.Error("Expected no snapshots to be found.") } @@ -57,7 +57,7 @@ func TestFind(t *testing.T) { // Try to match with a prefix longer than any ID. extraLengthID := samples[0].String() + "f" - f, err = Find(m, SnapshotFile, extraLengthID) + f, err = Find(context.TODO(), m, SnapshotFile, extraLengthID) if err != ErrNoIDPrefixFound { t.Error("Expected no snapshots to be matched.") } @@ -66,7 +66,7 @@ func TestFind(t *testing.T) { } // Use a prefix that will match the prefix of multiple Ids in `samples`. - f, err = Find(m, SnapshotFile, "20bdc140") + f, err = Find(context.TODO(), m, SnapshotFile, "20bdc140") if err != ErrMultipleIDMatches { t.Error("Expected multiple snapshots to be matched.") } @@ -89,7 +89,7 @@ func TestPrefixLength(t *testing.T) { return nil } - l, err := PrefixLength(m, SnapshotFile) + l, err := PrefixLength(context.TODO(), m, SnapshotFile) if err != nil { t.Error(err) } @@ -98,7 +98,7 @@ func TestPrefixLength(t *testing.T) { } list = samples[:3] - l, err = PrefixLength(m, SnapshotFile) + l, err = PrefixLength(context.TODO(), m, SnapshotFile) if err != nil { t.Error(err) } @@ -107,7 +107,7 @@ func TestPrefixLength(t *testing.T) { } list = samples[3:] - l, err = PrefixLength(m, SnapshotFile) + l, err = PrefixLength(context.TODO(), m, SnapshotFile) if err != nil { t.Error(err) } diff --git a/internal/restic/snapshot_find.go b/internal/restic/snapshot_find.go index 184fcd38f..50395c814 100644 --- a/internal/restic/snapshot_find.go +++ b/internal/restic/snapshot_find.go @@ -74,10 +74,10 @@ func FindLatestSnapshot(ctx context.Context, repo Repository, targets []string, // 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) { +func FindSnapshot(ctx context.Context, repo Repository, s string) (ID, error) { // find snapshot id with prefix - name, err := Find(repo.Backend(), SnapshotFile, s) + name, err := Find(ctx, repo.Backend(), SnapshotFile, s) if err != nil { return ID{}, err } From 27456f6545dff58d488e0a02032ba4709b8a94a8 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 10 Apr 2020 11:34:37 +0200 Subject: [PATCH 02/12] debug: use proper context This allows the debug commands to be properly interrupted. --- cmd/restic/cmd_debug.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index abae130db..4c2eb5ec6 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -54,9 +54,9 @@ func prettyPrintJSON(wr io.Writer, item interface{}) error { return err } -func debugPrintSnapshots(repo *repository.Repository, wr io.Writer) error { - return repo.List(context.TODO(), restic.SnapshotFile, func(id restic.ID, size int64) error { - snapshot, err := restic.LoadSnapshot(context.TODO(), repo, id) +func debugPrintSnapshots(ctx context.Context, repo *repository.Repository, wr io.Writer) error { + return repo.List(ctx, restic.SnapshotFile, func(id restic.ID, size int64) error { + snapshot, err := restic.LoadSnapshot(ctx, repo, id) if err != nil { return err } @@ -82,9 +82,9 @@ type Blob struct { Offset uint `json:"offset"` } -func printPacks(repo *repository.Repository, wr io.Writer) error { +func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer) error { - return repo.List(context.TODO(), restic.PackFile, func(id restic.ID, size int64) error { + return repo.List(ctx, restic.PackFile, func(id restic.ID, size int64) error { h := restic.Handle{Type: restic.PackFile, Name: id.String()} blobs, err := pack.List(repo.Key(), restic.ReaderAt(repo.Backend(), h), size) @@ -110,11 +110,11 @@ func printPacks(repo *repository.Repository, wr io.Writer) error { }) } -func dumpIndexes(repo restic.Repository, wr io.Writer) error { - return repo.List(context.TODO(), restic.IndexFile, func(id restic.ID, size int64) error { +func dumpIndexes(ctx context.Context, repo restic.Repository, wr io.Writer) error { + return repo.List(ctx, restic.IndexFile, func(id restic.ID, size int64) error { Printf("index_id: %v\n", id) - idx, err := repository.LoadIndex(context.TODO(), repo, id) + idx, err := repository.LoadIndex(ctx, repo, id) if err != nil { return err } @@ -145,20 +145,20 @@ func runDebugDump(gopts GlobalOptions, args []string) error { switch tpe { case "indexes": - return dumpIndexes(repo, gopts.stdout) + return dumpIndexes(gopts.ctx, repo, gopts.stdout) case "snapshots": - return debugPrintSnapshots(repo, gopts.stdout) + return debugPrintSnapshots(gopts.ctx, repo, gopts.stdout) case "packs": - return printPacks(repo, gopts.stdout) + return printPacks(gopts.ctx, repo, gopts.stdout) case "all": Printf("snapshots:\n") - err := debugPrintSnapshots(repo, gopts.stdout) + err := debugPrintSnapshots(gopts.ctx, repo, gopts.stdout) if err != nil { return err } Printf("\nindexes:\n") - err = dumpIndexes(repo, gopts.stdout) + err = dumpIndexes(gopts.ctx, repo, gopts.stdout) if err != nil { return err } From 603bb0e30976a70b7bb018d62c14bb6f368d87e5 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 10 Apr 2020 11:36:14 +0200 Subject: [PATCH 03/12] restore: Use proper context while loading snapshot --- cmd/restic/cmd_restore.go | 2 +- internal/restorer/restorer.go | 4 ++-- internal/restorer/restorer_test.go | 6 +++--- internal/restorer/restorer_unix_test.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index ec7d4a9e9..2ab7916f5 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -128,7 +128,7 @@ func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error { } } - res, err := restorer.NewRestorer(repo, id) + res, err := restorer.NewRestorer(ctx, repo, id) if err != nil { Exitf(2, "creating restorer failed: %v\n", err) } diff --git a/internal/restorer/restorer.go b/internal/restorer/restorer.go index 06e590532..e8966b50a 100644 --- a/internal/restorer/restorer.go +++ b/internal/restorer/restorer.go @@ -24,7 +24,7 @@ type Restorer struct { var restorerAbortOnAllErrors = func(location string, err error) error { return err } // NewRestorer creates a restorer preloaded with the content from the snapshot id. -func NewRestorer(repo restic.Repository, id restic.ID) (*Restorer, error) { +func NewRestorer(ctx context.Context, repo restic.Repository, id restic.ID) (*Restorer, error) { r := &Restorer{ repo: repo, Error: restorerAbortOnAllErrors, @@ -33,7 +33,7 @@ func NewRestorer(repo restic.Repository, id restic.ID) (*Restorer, error) { var err error - r.sn, err = restic.LoadSnapshot(context.TODO(), repo, id) + r.sn, err = restic.LoadSnapshot(ctx, repo, id) if err != nil { return nil, err } diff --git a/internal/restorer/restorer_test.go b/internal/restorer/restorer_test.go index 00c56bccd..38c24e9ff 100644 --- a/internal/restorer/restorer_test.go +++ b/internal/restorer/restorer_test.go @@ -316,7 +316,7 @@ func TestRestorer(t *testing.T) { _, id := saveSnapshot(t, repo, test.Snapshot) t.Logf("snapshot saved as %v", id.Str()) - res, err := NewRestorer(repo, id) + res, err := NewRestorer(context.TODO(), repo, id) if err != nil { t.Fatal(err) } @@ -434,7 +434,7 @@ func TestRestorerRelative(t *testing.T) { _, id := saveSnapshot(t, repo, test.Snapshot) t.Logf("snapshot saved as %v", id.Str()) - res, err := NewRestorer(repo, id) + res, err := NewRestorer(context.TODO(), repo, id) if err != nil { t.Fatal(err) } @@ -665,7 +665,7 @@ func TestRestorerTraverseTree(t *testing.T) { defer cleanup() sn, id := saveSnapshot(t, repo, test.Snapshot) - res, err := NewRestorer(repo, id) + res, err := NewRestorer(context.TODO(), repo, id) if err != nil { t.Fatal(err) } diff --git a/internal/restorer/restorer_unix_test.go b/internal/restorer/restorer_unix_test.go index fc80015c1..f3f23cd16 100644 --- a/internal/restorer/restorer_unix_test.go +++ b/internal/restorer/restorer_unix_test.go @@ -29,7 +29,7 @@ func TestRestorerRestoreEmptyHardlinkedFileds(t *testing.T) { }, }) - res, err := NewRestorer(repo, id) + res, err := NewRestorer(context.TODO(), repo, id) rtest.OK(t, err) res.SelectFilter = func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) { From a44945002161de31ce3d08222c291ebcb4e13527 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 10 Apr 2020 11:37:39 +0200 Subject: [PATCH 04/12] init: pass proper context to master key generation This is no change in behavior as a canceled context did later on cause the config file creation to fail. Therefore this change just lets the repository initialization fail a bit earlier. --- internal/repository/key.go | 4 ++-- internal/repository/repository.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/repository/key.go b/internal/repository/key.go index 42045389f..2c60f59e8 100644 --- a/internal/repository/key.go +++ b/internal/repository/key.go @@ -57,8 +57,8 @@ var ( // createMasterKey creates a new master key in the given backend and encrypts // it with the password. -func createMasterKey(s *Repository, password string) (*Key, error) { - return AddKey(context.TODO(), s, password, "", "", nil) +func createMasterKey(ctx context.Context, s *Repository, password string) (*Key, error) { + return AddKey(ctx, s, password, "", "", nil) } // OpenKey tries do decrypt the key specified by name with the given password. diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 1b4a41c31..fc58a660e 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -638,7 +638,7 @@ func (r *Repository) Init(ctx context.Context, password string, chunkerPolynomia // init creates a new master key with the supplied password and uses it to save // the config into the repo. func (r *Repository) init(ctx context.Context, password string, cfg restic.Config) error { - key, err := createMasterKey(r, password) + key, err := createMasterKey(ctx, r, password) if err != nil { return err } From dc2e664209cd00495b5c23a7d55ae0669d915c36 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 10 Apr 2020 11:42:19 +0200 Subject: [PATCH 05/12] integration_fuse_test: use global context No need to use the TODO context. --- cmd/restic/integration_fuse_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/restic/integration_fuse_test.go b/cmd/restic/integration_fuse_test.go index 57a57ae97..3ddf33907 100644 --- a/cmd/restic/integration_fuse_test.go +++ b/cmd/restic/integration_fuse_test.go @@ -6,7 +6,6 @@ package main import ( - "context" "fmt" "os" "path/filepath" @@ -122,7 +121,7 @@ func checkSnapshots(t testing.TB, global GlobalOptions, repo *repository.Reposit } for _, id := range snapshotIDs { - snapshot, err := restic.LoadSnapshot(context.TODO(), repo, id) + snapshot, err := restic.LoadSnapshot(global.ctx, repo, id) rtest.OK(t, err) ts := snapshot.Time.Format(time.RFC3339) From 645a6efaf25694a4aad085de2fd7e5e9eb17d435 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 10 Apr 2020 11:43:42 +0200 Subject: [PATCH 06/12] restorer: remove redundant type specification --- internal/restorer/filerestorer_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/restorer/filerestorer_test.go b/internal/restorer/filerestorer_test.go index a1b9b17f8..16fce6271 100644 --- a/internal/restorer/filerestorer_test.go +++ b/internal/restorer/filerestorer_test.go @@ -179,26 +179,26 @@ func TestFileRestorerBasic(t *testing.T) { defer cleanup() restoreAndVerify(t, tempdir, []TestFile{ - TestFile{ + { name: "file1", blobs: []TestBlob{ - TestBlob{"data1-1", "pack1-1"}, - TestBlob{"data1-2", "pack1-2"}, + {"data1-1", "pack1-1"}, + {"data1-2", "pack1-2"}, }, }, - TestFile{ + { name: "file2", blobs: []TestBlob{ - TestBlob{"data2-1", "pack2-1"}, - TestBlob{"data2-2", "pack2-2"}, + {"data2-1", "pack2-1"}, + {"data2-2", "pack2-2"}, }, }, - TestFile{ + { name: "file3", blobs: []TestBlob{ // same blob multiple times - TestBlob{"data3-1", "pack3-1"}, - TestBlob{"data3-1", "pack3-1"}, + {"data3-1", "pack3-1"}, + {"data3-1", "pack3-1"}, }, }, }) From 4b0fcaed456675f6e7cb276be94421d92a09def9 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 10 Apr 2020 11:44:15 +0200 Subject: [PATCH 07/12] unlock: use proper context for locks cleanup The list operation used by RemoveStaleLocks or RemoveAllLocks will already be canceled by the passed in context. Therefore we can also just cancel the remove operation as the unlock command won't process all lock files anyways. --- internal/restic/lock.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/restic/lock.go b/internal/restic/lock.go index 28807ce69..7047bb52d 100644 --- a/internal/restic/lock.go +++ b/internal/restic/lock.go @@ -284,7 +284,7 @@ func RemoveStaleLocks(ctx context.Context, repo Repository) error { } if lock.Stale() { - return repo.Backend().Remove(context.TODO(), Handle{Type: LockFile, Name: id.String()}) + return repo.Backend().Remove(ctx, Handle{Type: LockFile, Name: id.String()}) } return nil @@ -294,6 +294,6 @@ func RemoveStaleLocks(ctx context.Context, repo Repository) error { // RemoveAllLocks removes all locks forcefully. func RemoveAllLocks(ctx context.Context, repo Repository) error { return repo.List(ctx, LockFile, func(id ID, size int64) error { - return repo.Backend().Remove(context.TODO(), Handle{Type: LockFile, Name: id.String()}) + return repo.Backend().Remove(ctx, Handle{Type: LockFile, Name: id.String()}) }) } From 37a5e2d6817c94c4a157c4b01e171941b5983021 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Fri, 10 Apr 2020 12:08:52 +0200 Subject: [PATCH 08/12] rest: use global context on repository creation --- cmd/restic/global.go | 4 ++-- internal/backend/rclone/backend.go | 6 +++--- internal/backend/rclone/backend_test.go | 3 ++- internal/backend/rest/rest.go | 4 ++-- internal/backend/rest/rest_test.go | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cmd/restic/global.go b/cmd/restic/global.go index bf2efde70..8cae12907 100644 --- a/cmd/restic/global.go +++ b/cmd/restic/global.go @@ -768,9 +768,9 @@ func create(s string, opts options.Options) (restic.Backend, error) { case "b2": return b2.Create(globalOptions.ctx, cfg.(b2.Config), rt) case "rest": - return rest.Create(cfg.(rest.Config), rt) + return rest.Create(globalOptions.ctx, cfg.(rest.Config), rt) case "rclone": - return rclone.Create(cfg.(rclone.Config)) + return rclone.Create(globalOptions.ctx, cfg.(rclone.Config)) } debug.Log("invalid repository scheme: %v", s) diff --git a/internal/backend/rclone/backend.go b/internal/backend/rclone/backend.go index e18d8a998..2f730f238 100644 --- a/internal/backend/rclone/backend.go +++ b/internal/backend/rclone/backend.go @@ -275,8 +275,8 @@ func Open(cfg Config, lim limiter.Limiter) (*Backend, error) { return be, nil } -// Create initializes a new restic repo with clone. -func Create(cfg Config) (*Backend, error) { +// Create initializes a new restic repo with rclone. +func Create(ctx context.Context, cfg Config) (*Backend, error) { be, err := newBackend(cfg, nil) if err != nil { return nil, err @@ -294,7 +294,7 @@ func Create(cfg Config) (*Backend, error) { URL: url, } - restBackend, err := rest.Create(restConfig, debug.RoundTripper(be.tr)) + restBackend, err := rest.Create(ctx, restConfig, debug.RoundTripper(be.tr)) if err != nil { _ = be.Close() return nil, err diff --git a/internal/backend/rclone/backend_test.go b/internal/backend/rclone/backend_test.go index b7f3cebb4..0a8f91aea 100644 --- a/internal/backend/rclone/backend_test.go +++ b/internal/backend/rclone/backend_test.go @@ -1,6 +1,7 @@ package rclone_test import ( + "context" "os/exec" "testing" @@ -27,7 +28,7 @@ func newTestSuite(t testing.TB) *test.Suite { Create: func(config interface{}) (restic.Backend, error) { t.Logf("Create()") cfg := config.(rclone.Config) - be, err := rclone.Create(cfg) + be, err := rclone.Create(context.TODO(), cfg) if e, ok := errors.Cause(err).(*exec.Error); ok && e.Err == exec.ErrNotFound { t.Skipf("program %q not found", e.Name) return nil, nil diff --git a/internal/backend/rest/rest.go b/internal/backend/rest/rest.go index 6473a98b3..4dc271f38 100644 --- a/internal/backend/rest/rest.go +++ b/internal/backend/rest/rest.go @@ -63,13 +63,13 @@ func Open(cfg Config, rt http.RoundTripper) (*Backend, error) { } // Create creates a new REST on server configured in config. -func Create(cfg Config, rt http.RoundTripper) (*Backend, error) { +func Create(ctx context.Context, cfg Config, rt http.RoundTripper) (*Backend, error) { be, err := Open(cfg, rt) if err != nil { return nil, err } - _, err = be.Stat(context.TODO(), restic.Handle{Type: restic.ConfigFile}) + _, err = be.Stat(ctx, restic.Handle{Type: restic.ConfigFile}) if err == nil { return nil, errors.Fatal("config file already exists") } diff --git a/internal/backend/rest/rest_test.go b/internal/backend/rest/rest_test.go index 486f241a9..3f6859626 100644 --- a/internal/backend/rest/rest_test.go +++ b/internal/backend/rest/rest_test.go @@ -86,7 +86,7 @@ func newTestSuite(ctx context.Context, t testing.TB, url *url.URL, minimalData b // CreateFn is a function that creates a temporary repository for the tests. Create: func(config interface{}) (restic.Backend, error) { cfg := config.(rest.Config) - return rest.Create(cfg, tr) + return rest.Create(context.TODO(), cfg, tr) }, // OpenFn is a function that opens a previously created temporary repository. From 0c9efa9c2aa26ef7e92251de761a00e6718c5fb9 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 9 Aug 2020 13:24:47 +0200 Subject: [PATCH 09/12] Pass context to lockRepo --- cmd/restic/cmd_backup.go | 2 +- cmd/restic/cmd_cat.go | 2 +- cmd/restic/cmd_check.go | 2 +- cmd/restic/cmd_copy.go | 4 ++-- cmd/restic/cmd_debug.go | 2 +- cmd/restic/cmd_diff.go | 2 +- cmd/restic/cmd_dump.go | 2 +- cmd/restic/cmd_find.go | 2 +- cmd/restic/cmd_forget.go | 2 +- cmd/restic/cmd_key.go | 8 ++++---- cmd/restic/cmd_list.go | 2 +- cmd/restic/cmd_migrate.go | 2 +- cmd/restic/cmd_mount.go | 2 +- cmd/restic/cmd_prune.go | 2 +- cmd/restic/cmd_rebuild_index.go | 2 +- cmd/restic/cmd_recover.go | 2 +- cmd/restic/cmd_restore.go | 2 +- cmd/restic/cmd_snapshots.go | 2 +- cmd/restic/cmd_stats.go | 2 +- cmd/restic/cmd_tag.go | 2 +- cmd/restic/lock.go | 12 ++++++------ 21 files changed, 30 insertions(+), 30 deletions(-) diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go index fd138888c..a43c5a17a 100644 --- a/cmd/restic/cmd_backup.go +++ b/cmd/restic/cmd_backup.go @@ -496,7 +496,7 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, term *termstatus.Termina if !gopts.JSON { p.V("lock repository") } - lock, err := lockRepo(repo) + lock, err := lockRepo(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_cat.go b/cmd/restic/cmd_cat.go index 281ed875f..ff240572f 100644 --- a/cmd/restic/cmd_cat.go +++ b/cmd/restic/cmd_cat.go @@ -42,7 +42,7 @@ func runCat(gopts GlobalOptions, args []string) error { return err } - lock, err := lockRepo(repo) + lock, err := lockRepo(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_check.go b/cmd/restic/cmd_check.go index 3c638ff5a..a4553b3c9 100644 --- a/cmd/restic/cmd_check.go +++ b/cmd/restic/cmd_check.go @@ -158,7 +158,7 @@ func runCheck(opts CheckOptions, gopts GlobalOptions, args []string) error { if !gopts.NoLock { Verbosef("create exclusive lock for repository\n") - lock, err := lockRepoExclusive(repo) + lock, err := lockRepoExclusive(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_copy.go b/cmd/restic/cmd_copy.go index 788c9a7e8..7c37b61e0 100644 --- a/cmd/restic/cmd_copy.go +++ b/cmd/restic/cmd_copy.go @@ -65,13 +65,13 @@ func runCopy(opts CopyOptions, gopts GlobalOptions, args []string) error { return err } - srcLock, err := lockRepo(srcRepo) + srcLock, err := lockRepo(ctx, srcRepo) defer unlockRepo(srcLock) if err != nil { return err } - dstLock, err := lockRepo(dstRepo) + dstLock, err := lockRepo(ctx, dstRepo) defer unlockRepo(dstLock) if err != nil { return err diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index 4c2eb5ec6..c242d0a25 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -134,7 +134,7 @@ func runDebugDump(gopts GlobalOptions, args []string) error { } if !gopts.NoLock { - lock, err := lockRepo(repo) + lock, err := lockRepo(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go index a4873319f..f1b099ede 100644 --- a/cmd/restic/cmd_diff.go +++ b/cmd/restic/cmd_diff.go @@ -332,7 +332,7 @@ func runDiff(opts DiffOptions, gopts GlobalOptions, args []string) error { } if !gopts.NoLock { - lock, err := lockRepo(repo) + lock, err := lockRepo(ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go index b33863c4f..d3ca87923 100644 --- a/cmd/restic/cmd_dump.go +++ b/cmd/restic/cmd_dump.go @@ -140,7 +140,7 @@ func runDump(opts DumpOptions, gopts GlobalOptions, args []string) error { } if !gopts.NoLock { - lock, err := lockRepo(repo) + lock, err := lockRepo(ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go index 84a709b15..c57207c1f 100644 --- a/cmd/restic/cmd_find.go +++ b/cmd/restic/cmd_find.go @@ -529,7 +529,7 @@ func runFind(opts FindOptions, gopts GlobalOptions, args []string) error { } if !gopts.NoLock { - lock, err := lockRepo(repo) + lock, err := lockRepo(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index d2cc85075..3edaa76e9 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -88,7 +88,7 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error { return err } - lock, err := lockRepoExclusive(repo) + lock, err := lockRepoExclusive(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go index 8a54d83e8..bbd4f0f90 100644 --- a/cmd/restic/cmd_key.go +++ b/cmd/restic/cmd_key.go @@ -188,7 +188,7 @@ func runKey(gopts GlobalOptions, args []string) error { switch args[0] { case "list": - lock, err := lockRepo(repo) + lock, err := lockRepo(ctx, repo) defer unlockRepo(lock) if err != nil { return err @@ -196,7 +196,7 @@ func runKey(gopts GlobalOptions, args []string) error { return listKeys(ctx, repo, gopts) case "add": - lock, err := lockRepo(repo) + lock, err := lockRepo(ctx, repo) defer unlockRepo(lock) if err != nil { return err @@ -204,7 +204,7 @@ func runKey(gopts GlobalOptions, args []string) error { return addKey(gopts, repo) case "remove": - lock, err := lockRepoExclusive(repo) + lock, err := lockRepoExclusive(ctx, repo) defer unlockRepo(lock) if err != nil { return err @@ -217,7 +217,7 @@ func runKey(gopts GlobalOptions, args []string) error { return deleteKey(gopts.ctx, repo, id) case "passwd": - lock, err := lockRepoExclusive(repo) + lock, err := lockRepoExclusive(ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_list.go b/cmd/restic/cmd_list.go index 3cde6714d..50b55e857 100644 --- a/cmd/restic/cmd_list.go +++ b/cmd/restic/cmd_list.go @@ -40,7 +40,7 @@ func runList(cmd *cobra.Command, opts GlobalOptions, args []string) error { } if !opts.NoLock { - lock, err := lockRepo(repo) + lock, err := lockRepo(opts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_migrate.go b/cmd/restic/cmd_migrate.go index 887641957..4af98005e 100644 --- a/cmd/restic/cmd_migrate.go +++ b/cmd/restic/cmd_migrate.go @@ -99,7 +99,7 @@ func runMigrate(opts MigrateOptions, gopts GlobalOptions, args []string) error { return err } - lock, err := lockRepoExclusive(repo) + lock, err := lockRepoExclusive(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go index 626915dfe..820f68949 100644 --- a/cmd/restic/cmd_mount.go +++ b/cmd/restic/cmd_mount.go @@ -91,7 +91,7 @@ func mount(opts MountOptions, gopts GlobalOptions, mountpoint string) error { } if !gopts.NoLock { - lock, err := lockRepo(repo) + lock, err := lockRepo(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_prune.go b/cmd/restic/cmd_prune.go index 2a386793c..1bb1a51e5 100644 --- a/cmd/restic/cmd_prune.go +++ b/cmd/restic/cmd_prune.go @@ -50,7 +50,7 @@ func runPrune(gopts GlobalOptions) error { return err } - lock, err := lockRepoExclusive(repo) + lock, err := lockRepoExclusive(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_rebuild_index.go b/cmd/restic/cmd_rebuild_index.go index b5f23aad7..87b78b4b4 100644 --- a/cmd/restic/cmd_rebuild_index.go +++ b/cmd/restic/cmd_rebuild_index.go @@ -38,7 +38,7 @@ func runRebuildIndex(gopts GlobalOptions) error { return err } - lock, err := lockRepoExclusive(repo) + lock, err := lockRepoExclusive(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_recover.go b/cmd/restic/cmd_recover.go index 9eca92115..8f11b6860 100644 --- a/cmd/restic/cmd_recover.go +++ b/cmd/restic/cmd_recover.go @@ -43,7 +43,7 @@ func runRecover(gopts GlobalOptions) error { return err } - lock, err := lockRepo(repo) + lock, err := lockRepo(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index 2ab7916f5..618dfadf8 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -102,7 +102,7 @@ func runRestore(opts RestoreOptions, gopts GlobalOptions, args []string) error { } if !gopts.NoLock { - lock, err := lockRepo(repo) + lock, err := lockRepo(ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_snapshots.go b/cmd/restic/cmd_snapshots.go index cc58479c2..8bc5d5984 100644 --- a/cmd/restic/cmd_snapshots.go +++ b/cmd/restic/cmd_snapshots.go @@ -61,7 +61,7 @@ func runSnapshots(opts SnapshotOptions, gopts GlobalOptions, args []string) erro } if !gopts.NoLock { - lock, err := lockRepo(repo) + lock, err := lockRepo(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_stats.go b/cmd/restic/cmd_stats.go index f26d5a6cc..81ec66843 100644 --- a/cmd/restic/cmd_stats.go +++ b/cmd/restic/cmd_stats.go @@ -91,7 +91,7 @@ func runStats(gopts GlobalOptions, args []string) error { } if !gopts.NoLock { - lock, err := lockRepo(repo) + lock, err := lockRepo(ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/cmd_tag.go b/cmd/restic/cmd_tag.go index 4479f6894..4e1ac46c3 100644 --- a/cmd/restic/cmd_tag.go +++ b/cmd/restic/cmd_tag.go @@ -119,7 +119,7 @@ func runTag(opts TagOptions, gopts GlobalOptions, args []string) error { if !gopts.NoLock { Verbosef("create exclusive lock for repository\n") - lock, err := lockRepoExclusive(repo) + lock, err := lockRepoExclusive(gopts.ctx, repo) defer unlockRepo(lock) if err != nil { return err diff --git a/cmd/restic/lock.go b/cmd/restic/lock.go index 2b4ac94d5..ad5ac6841 100644 --- a/cmd/restic/lock.go +++ b/cmd/restic/lock.go @@ -18,21 +18,21 @@ var globalLocks struct { sync.Mutex } -func lockRepo(repo *repository.Repository) (*restic.Lock, error) { - return lockRepository(repo, false) +func lockRepo(ctx context.Context, repo *repository.Repository) (*restic.Lock, error) { + return lockRepository(ctx, repo, false) } -func lockRepoExclusive(repo *repository.Repository) (*restic.Lock, error) { - return lockRepository(repo, true) +func lockRepoExclusive(ctx context.Context, repo *repository.Repository) (*restic.Lock, error) { + return lockRepository(ctx, repo, true) } -func lockRepository(repo *repository.Repository, exclusive bool) (*restic.Lock, error) { +func lockRepository(ctx context.Context, repo *repository.Repository, exclusive bool) (*restic.Lock, error) { lockFn := restic.NewLock if exclusive { lockFn = restic.NewExclusiveLock } - lock, err := lockFn(context.TODO(), repo) + lock, err := lockFn(ctx, repo) if err != nil { return nil, errors.WithMessage(err, "unable to create lock in backend") } From 2964d2ad158fc3e7d37874e097c19ece0b52eefd Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 9 Aug 2020 13:25:13 +0200 Subject: [PATCH 10/12] Skip unlocking for nil locks Now that lockRepo receives a context, it is possible that it is canceled before a lock was created. Thus `unlockRepo` must be able to handle this case. --- cmd/restic/lock.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/restic/lock.go b/cmd/restic/lock.go index ad5ac6841..b0df2b685 100644 --- a/cmd/restic/lock.go +++ b/cmd/restic/lock.go @@ -86,6 +86,10 @@ func refreshLocks(wg *sync.WaitGroup, done <-chan struct{}) { } func unlockRepo(lock *restic.Lock) error { + if lock == nil { + return nil + } + globalLocks.Lock() defer globalLocks.Unlock() From d6cfe857b716022eec4fd80f859341ec977010c8 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Sun, 9 Aug 2020 13:26:07 +0200 Subject: [PATCH 11/12] pass proper context into MasterIndex.RebuildIndex --- internal/repository/master_index.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/repository/master_index.go b/internal/repository/master_index.go index 57b2985a6..6f19a0aad 100644 --- a/internal/repository/master_index.go +++ b/internal/repository/master_index.go @@ -267,7 +267,7 @@ func (mi *MasterIndex) MergeFinalIndexes() { // RebuildIndex combines all known indexes to a new index, leaving out any // packs whose ID is contained in packBlacklist. The new index contains the IDs // of all known indexes in the "supersedes" field. -func (mi *MasterIndex) RebuildIndex(packBlacklist restic.IDSet) (*Index, error) { +func (mi *MasterIndex) RebuildIndex(ctx context.Context, packBlacklist restic.IDSet) (*Index, error) { mi.idxMutex.Lock() defer mi.idxMutex.Unlock() @@ -275,7 +275,7 @@ func (mi *MasterIndex) RebuildIndex(packBlacklist restic.IDSet) (*Index, error) newIndex := NewIndex() - ctx, cancel := context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(ctx) defer cancel() for i, idx := range mi.idx { From e638b46a1303ace658a3f4dc6239dd5ddbb774b4 Mon Sep 17 00:00:00 2001 From: Michael Eischer Date: Tue, 6 Oct 2020 15:59:00 +0200 Subject: [PATCH 12/12] Embed context into ReaderAt The io.Reader interface does not support contexts, such that it is necessary to embed the context into the backendReaderAt struct. This has the problem that a reader might suddenly stop working when it's contained context is canceled. However, this is now problem here as the reader instances never escape the calling function. --- cmd/restic/cmd_debug.go | 2 +- internal/pack/pack_test.go | 4 ++-- internal/repository/repository.go | 2 +- internal/restic/readerat.go | 15 +++++++++------ 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go index c242d0a25..b36eefa07 100644 --- a/cmd/restic/cmd_debug.go +++ b/cmd/restic/cmd_debug.go @@ -87,7 +87,7 @@ func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer) return repo.List(ctx, restic.PackFile, func(id restic.ID, size int64) error { h := restic.Handle{Type: restic.PackFile, Name: id.String()} - blobs, err := pack.List(repo.Key(), restic.ReaderAt(repo.Backend(), h), size) + blobs, err := pack.List(repo.Key(), restic.ReaderAt(ctx, repo.Backend(), h), size) if err != nil { Warnf("error for pack %v: %v\n", id.Str(), err) return nil diff --git a/internal/pack/pack_test.go b/internal/pack/pack_test.go index 71aac082d..e40e5bd2a 100644 --- a/internal/pack/pack_test.go +++ b/internal/pack/pack_test.go @@ -128,7 +128,7 @@ func TestUnpackReadSeeker(t *testing.T) { handle := restic.Handle{Type: restic.PackFile, Name: id.String()} rtest.OK(t, b.Save(context.TODO(), handle, restic.NewByteReader(packData))) - verifyBlobs(t, bufs, k, restic.ReaderAt(b, handle), packSize) + verifyBlobs(t, bufs, k, restic.ReaderAt(context.TODO(), b, handle), packSize) } func TestShortPack(t *testing.T) { @@ -141,5 +141,5 @@ func TestShortPack(t *testing.T) { handle := restic.Handle{Type: restic.PackFile, Name: id.String()} rtest.OK(t, b.Save(context.TODO(), handle, restic.NewByteReader(packData))) - verifyBlobs(t, bufs, k, restic.ReaderAt(b, handle), packSize) + verifyBlobs(t, bufs, k, restic.ReaderAt(context.TODO(), b, handle), packSize) } diff --git a/internal/repository/repository.go b/internal/repository/repository.go index fc58a660e..635b15253 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -679,7 +679,7 @@ func (r *Repository) List(ctx context.Context, t restic.FileType, fn func(restic func (r *Repository) ListPack(ctx context.Context, id restic.ID, size int64) ([]restic.Blob, int64, error) { h := restic.Handle{Type: restic.PackFile, Name: id.String()} - blobs, err := pack.List(r.Key(), restic.ReaderAt(r.Backend(), h), size) + blobs, err := pack.List(r.Key(), restic.ReaderAt(ctx, r.Backend(), h), size) if err != nil { return nil, 0, err } diff --git a/internal/restic/readerat.go b/internal/restic/readerat.go index 6e945b43a..6ed2e83f5 100644 --- a/internal/restic/readerat.go +++ b/internal/restic/readerat.go @@ -9,17 +9,20 @@ import ( ) type backendReaderAt struct { - be Backend - h Handle + ctx context.Context + be Backend + h Handle } func (brd backendReaderAt) ReadAt(p []byte, offset int64) (n int, err error) { - return ReadAt(context.TODO(), brd.be, brd.h, offset, p) + return ReadAt(brd.ctx, brd.be, brd.h, offset, p) } -// ReaderAt returns an io.ReaderAt for a file in the backend. -func ReaderAt(be Backend, h Handle) io.ReaderAt { - return backendReaderAt{be: be, h: h} +// ReaderAt returns an io.ReaderAt for a file in the backend. The returned reader +// should not escape the caller function to avoid unexpected interactions with the +// embedded context +func ReaderAt(ctx context.Context, be Backend, h Handle) io.ReaderAt { + return backendReaderAt{ctx: ctx, be: be, h: h} } // ReadAt reads from the backend handle h at the given position.