mirror of https://github.com/restic/restic.git
Merge pull request #5168 from MichaelEischer/restrict-repository-unpacked
repository: restrict SaveUnpacked and RemoveUnpacked
This commit is contained in:
commit
e6f9cfb8c8
|
@ -304,7 +304,7 @@ func (opts BackupOptions) Check(gopts GlobalOptions, args []string) error {
|
||||||
// from being saved in a snapshot based on path only
|
// from being saved in a snapshot based on path only
|
||||||
func collectRejectByNameFuncs(opts BackupOptions, repo *repository.Repository) (fs []archiver.RejectByNameFunc, err error) {
|
func collectRejectByNameFuncs(opts BackupOptions, repo *repository.Repository) (fs []archiver.RejectByNameFunc, err error) {
|
||||||
// exclude restic cache
|
// exclude restic cache
|
||||||
if repo.Cache != nil {
|
if repo.Cache() != nil {
|
||||||
f, err := rejectResticCache(repo)
|
f, err := rejectResticCache(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -304,7 +304,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
||||||
if len(removeSnIDs) > 0 {
|
if len(removeSnIDs) > 0 {
|
||||||
if !opts.DryRun {
|
if !opts.DryRun {
|
||||||
bar := printer.NewCounter("files deleted")
|
bar := printer.NewCounter("files deleted")
|
||||||
err := restic.ParallelRemove(ctx, repo, removeSnIDs, restic.SnapshotFile, func(id restic.ID, err error) error {
|
err := restic.ParallelRemove(ctx, repo, removeSnIDs, restic.WriteableSnapshotFile, func(id restic.ID, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printer.E("unable to remove %v/%v from the repository\n", restic.SnapshotFile, id)
|
printer.E("unable to remove %v/%v from the repository\n", restic.SnapshotFile, id)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -171,7 +171,7 @@ func runPrune(ctx context.Context, opts PruneOptions, gopts GlobalOptions, term
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPruneWithRepo(ctx context.Context, opts PruneOptions, gopts GlobalOptions, repo *repository.Repository, ignoreSnapshots restic.IDSet, term *termstatus.Terminal) error {
|
func runPruneWithRepo(ctx context.Context, opts PruneOptions, gopts GlobalOptions, repo *repository.Repository, ignoreSnapshots restic.IDSet, term *termstatus.Terminal) error {
|
||||||
if repo.Cache == nil {
|
if repo.Cache() == nil {
|
||||||
Print("warning: running prune without a cache, this may be very slow!\n")
|
Print("warning: running prune without a cache, this may be very slow!\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -168,7 +168,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions) error {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSnapshot(ctx context.Context, name, hostname string, tags []string, repo restic.SaverUnpacked, tree *restic.ID) error {
|
func createSnapshot(ctx context.Context, name, hostname string, tags []string, repo restic.SaverUnpacked[restic.WriteableFileType], tree *restic.ID) error {
|
||||||
sn, err := restic.NewSnapshot([]string{name}, tags, hostname, time.Now())
|
sn, err := restic.NewSnapshot([]string{name}, tags, hostname, time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("unable to save snapshot: %v", err)
|
return errors.Fatalf("unable to save snapshot: %v", err)
|
||||||
|
|
|
@ -194,7 +194,7 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
|
||||||
if dryRun {
|
if dryRun {
|
||||||
Verbosef("would delete empty snapshot\n")
|
Verbosef("would delete empty snapshot\n")
|
||||||
} else {
|
} else {
|
||||||
if err = repo.RemoveUnpacked(ctx, restic.SnapshotFile, *sn.ID()); err != nil {
|
if err = repo.RemoveUnpacked(ctx, restic.WriteableSnapshotFile, *sn.ID()); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
debug.Log("removed empty snapshot %v", sn.ID())
|
debug.Log("removed empty snapshot %v", sn.ID())
|
||||||
|
@ -253,7 +253,7 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
|
||||||
Verbosef("saved new snapshot %v\n", id.Str())
|
Verbosef("saved new snapshot %v\n", id.Str())
|
||||||
|
|
||||||
if forget {
|
if forget {
|
||||||
if err = repo.RemoveUnpacked(ctx, restic.SnapshotFile, *sn.ID()); err != nil {
|
if err = repo.RemoveUnpacked(ctx, restic.WriteableSnapshotFile, *sn.ID()); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
debug.Log("removed old snapshot %v", sn.ID())
|
debug.Log("removed old snapshot %v", sn.ID())
|
||||||
|
|
|
@ -90,7 +90,7 @@ func changeTags(ctx context.Context, repo *repository.Repository, sn *restic.Sna
|
||||||
debug.Log("new snapshot saved as %v", id)
|
debug.Log("new snapshot saved as %v", id)
|
||||||
|
|
||||||
// Remove the old snapshot.
|
// Remove the old snapshot.
|
||||||
if err = repo.RemoveUnpacked(ctx, restic.SnapshotFile, *sn.ID()); err != nil {
|
if err = repo.RemoveUnpacked(ctx, restic.WriteableSnapshotFile, *sn.ID()); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,9 +45,9 @@ func runUnlock(ctx context.Context, opts UnlockOptions, gopts GlobalOptions) err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := restic.RemoveStaleLocks
|
fn := repository.RemoveStaleLocks
|
||||||
if opts.RemoveAll {
|
if opts.RemoveAll {
|
||||||
fn = restic.RemoveAllLocks
|
fn = repository.RemoveAllLocks
|
||||||
}
|
}
|
||||||
|
|
||||||
processed, err := fn(ctx, repo)
|
processed, err := fn(ctx, repo)
|
||||||
|
|
|
@ -11,12 +11,12 @@ import (
|
||||||
// rejectResticCache returns a RejectByNameFunc that rejects the restic cache
|
// rejectResticCache returns a RejectByNameFunc that rejects the restic cache
|
||||||
// directory (if set).
|
// directory (if set).
|
||||||
func rejectResticCache(repo *repository.Repository) (archiver.RejectByNameFunc, error) {
|
func rejectResticCache(repo *repository.Repository) (archiver.RejectByNameFunc, error) {
|
||||||
if repo.Cache == nil {
|
if repo.Cache() == nil {
|
||||||
return func(string) bool {
|
return func(string) bool {
|
||||||
return false
|
return false
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
cacheBase := repo.Cache.BaseDir()
|
cacheBase := repo.Cache().BaseDir()
|
||||||
|
|
||||||
if cacheBase == "" {
|
if cacheBase == "" {
|
||||||
return nil, errors.New("cacheBase is empty string")
|
return nil, errors.New("cacheBase is empty string")
|
||||||
|
|
|
@ -275,17 +275,30 @@ func listTreePacks(gopts GlobalOptions, t *testing.T) restic.IDSet {
|
||||||
return treePacks
|
return treePacks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func captureBackend(gopts *GlobalOptions) func() backend.Backend {
|
||||||
|
var be backend.Backend
|
||||||
|
gopts.backendTestHook = func(r backend.Backend) (backend.Backend, error) {
|
||||||
|
be = r
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
return func() backend.Backend {
|
||||||
|
return be
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) {
|
func removePacks(gopts GlobalOptions, t testing.TB, remove restic.IDSet) {
|
||||||
ctx, r, unlock, err := openWithExclusiveLock(context.TODO(), gopts, false)
|
be := captureBackend(&gopts)
|
||||||
|
ctx, _, unlock, err := openWithExclusiveLock(context.TODO(), gopts, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
for id := range remove {
|
for id := range remove {
|
||||||
rtest.OK(t, r.RemoveUnpacked(ctx, restic.PackFile, id))
|
rtest.OK(t, be().Remove(ctx, backend.Handle{Type: restic.PackFile, Name: id.String()}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func removePacksExcept(gopts GlobalOptions, t testing.TB, keep restic.IDSet, removeTreePacks bool) {
|
func removePacksExcept(gopts GlobalOptions, t testing.TB, keep restic.IDSet, removeTreePacks bool) {
|
||||||
|
be := captureBackend(&gopts)
|
||||||
ctx, r, unlock, err := openWithExclusiveLock(context.TODO(), gopts, false)
|
ctx, r, unlock, err := openWithExclusiveLock(context.TODO(), gopts, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
@ -305,7 +318,7 @@ func removePacksExcept(gopts GlobalOptions, t testing.TB, keep restic.IDSet, rem
|
||||||
if treePacks.Has(id) != removeTreePacks || keep.Has(id) {
|
if treePacks.Has(id) != removeTreePacks || keep.Has(id) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return r.RemoveUnpacked(ctx, restic.PackFile, id)
|
return be().Remove(ctx, backend.Handle{Type: restic.PackFile, Name: id.String()})
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,7 +74,7 @@ type ToNoder interface {
|
||||||
type archiverRepo interface {
|
type archiverRepo interface {
|
||||||
restic.Loader
|
restic.Loader
|
||||||
restic.BlobSaver
|
restic.BlobSaver
|
||||||
restic.SaverUnpacked
|
restic.SaverUnpacked[restic.WriteableFileType]
|
||||||
|
|
||||||
Config() restic.Config
|
Config() restic.Config
|
||||||
StartPackUploader(ctx context.Context, wg *errgroup.Group)
|
StartPackUploader(ctx context.Context, wg *errgroup.Group)
|
||||||
|
|
|
@ -145,11 +145,11 @@ func TestUnreferencedPack(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnreferencedBlobs(t *testing.T) {
|
func TestUnreferencedBlobs(t *testing.T) {
|
||||||
repo, _, cleanup := repository.TestFromFixture(t, checkerTestData)
|
repo, be, cleanup := repository.TestFromFixture(t, checkerTestData)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
snapshotID := restic.TestParseID("51d249d28815200d59e4be7b3f21a157b864dc343353df9d8e498220c2499b02")
|
snapshotID := restic.TestParseID("51d249d28815200d59e4be7b3f21a157b864dc343353df9d8e498220c2499b02")
|
||||||
test.OK(t, repo.RemoveUnpacked(context.TODO(), restic.SnapshotFile, snapshotID))
|
test.OK(t, be.Remove(context.TODO(), backend.Handle{Type: restic.SnapshotFile, Name: snapshotID.String()}))
|
||||||
|
|
||||||
unusedBlobsBySnapshot := restic.BlobHandles{
|
unusedBlobsBySnapshot := restic.BlobHandles{
|
||||||
restic.TestParseHandle("58c748bbe2929fdf30c73262bd8313fe828f8925b05d1d4a87fe109082acb849", restic.DataBlob),
|
restic.TestParseHandle("58c748bbe2929fdf30c73262bd8313fe828f8925b05d1d4a87fe109082acb849", restic.DataBlob),
|
||||||
|
@ -334,7 +334,7 @@ func (b *errorOnceBackend) Load(ctx context.Context, h backend.Handle, length in
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckerModifiedData(t *testing.T) {
|
func TestCheckerModifiedData(t *testing.T) {
|
||||||
repo, be := repository.TestRepositoryWithVersion(t, 0)
|
repo, _, be := repository.TestRepositoryWithVersion(t, 0)
|
||||||
sn := archiver.TestSnapshot(t, repo, ".", nil)
|
sn := archiver.TestSnapshot(t, repo, ".", nil)
|
||||||
t.Logf("archived as %v", sn.ID().Str())
|
t.Logf("archived as %v", sn.ID().Str())
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUpgradeRepoV2(t *testing.T) {
|
func TestUpgradeRepoV2(t *testing.T) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, 1)
|
repo, _, _ := repository.TestRepositoryWithVersion(t, 1)
|
||||||
if repo.Config().Version != 1 {
|
if repo.Config().Version != 1 {
|
||||||
t.Fatal("test repo has wrong version")
|
t.Fatal("test repo has wrong version")
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,9 +40,9 @@ func (e *partialReadError) Error() string {
|
||||||
func CheckPack(ctx context.Context, r *Repository, id restic.ID, blobs []restic.Blob, size int64, bufRd *bufio.Reader, dec *zstd.Decoder) error {
|
func CheckPack(ctx context.Context, r *Repository, id restic.ID, blobs []restic.Blob, size int64, bufRd *bufio.Reader, dec *zstd.Decoder) error {
|
||||||
err := checkPackInner(ctx, r, id, blobs, size, bufRd, dec)
|
err := checkPackInner(ctx, r, id, blobs, size, bufRd, dec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if r.Cache != nil {
|
if r.cache != nil {
|
||||||
// ignore error as there's not much we can do here
|
// ignore error as there's not much we can do here
|
||||||
_ = r.Cache.Forget(backend.Handle{Type: restic.PackFile, Name: id.String()})
|
_ = r.cache.Forget(backend.Handle{Type: restic.PackFile, Name: id.String()})
|
||||||
}
|
}
|
||||||
|
|
||||||
// retry pack verification to detect transient errors
|
// retry pack verification to detect transient errors
|
||||||
|
|
|
@ -18,7 +18,7 @@ func FuzzSaveLoadBlob(f *testing.F) {
|
||||||
}
|
}
|
||||||
|
|
||||||
id := restic.Hash(blob)
|
id := restic.Hash(blob)
|
||||||
repo, _ := TestRepositoryWithVersion(t, 2)
|
repo, _, _ := TestRepositoryWithVersion(t, 2)
|
||||||
|
|
||||||
var wg errgroup.Group
|
var wg errgroup.Group
|
||||||
repo.StartPackUploader(context.TODO(), &wg)
|
repo.StartPackUploader(context.TODO(), &wg)
|
||||||
|
|
|
@ -351,7 +351,7 @@ func (idx *Index) Encode(w io.Writer) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveIndex saves an index in the repository.
|
// SaveIndex saves an index in the repository.
|
||||||
func (idx *Index) SaveIndex(ctx context.Context, repo restic.SaverUnpacked) (restic.ID, error) {
|
func (idx *Index) SaveIndex(ctx context.Context, repo restic.SaverUnpacked[restic.FileType]) (restic.ID, error) {
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
err := idx.Encode(buf)
|
err := idx.Encode(buf)
|
||||||
|
|
|
@ -321,7 +321,7 @@ type MasterIndexRewriteOpts struct {
|
||||||
// This is used by repair index to only rewrite and delete the old indexes.
|
// This is used by repair index to only rewrite and delete the old indexes.
|
||||||
//
|
//
|
||||||
// Must not be called concurrently to any other MasterIndex operation.
|
// Must not be called concurrently to any other MasterIndex operation.
|
||||||
func (mi *MasterIndex) Rewrite(ctx context.Context, repo restic.Unpacked, excludePacks restic.IDSet, oldIndexes restic.IDSet, extraObsolete restic.IDs, opts MasterIndexRewriteOpts) error {
|
func (mi *MasterIndex) Rewrite(ctx context.Context, repo restic.Unpacked[restic.FileType], excludePacks restic.IDSet, oldIndexes restic.IDSet, extraObsolete restic.IDs, opts MasterIndexRewriteOpts) error {
|
||||||
for _, idx := range mi.idx {
|
for _, idx := range mi.idx {
|
||||||
if !idx.Final() {
|
if !idx.Final() {
|
||||||
panic("internal error - index must be saved before calling MasterIndex.Rewrite")
|
panic("internal error - index must be saved before calling MasterIndex.Rewrite")
|
||||||
|
@ -499,7 +499,7 @@ func (mi *MasterIndex) Rewrite(ctx context.Context, repo restic.Unpacked, exclud
|
||||||
// It is only intended for use by prune with the UnsafeRecovery option.
|
// It is only intended for use by prune with the UnsafeRecovery option.
|
||||||
//
|
//
|
||||||
// Must not be called concurrently to any other MasterIndex operation.
|
// Must not be called concurrently to any other MasterIndex operation.
|
||||||
func (mi *MasterIndex) SaveFallback(ctx context.Context, repo restic.SaverRemoverUnpacked, excludePacks restic.IDSet, p *progress.Counter) error {
|
func (mi *MasterIndex) SaveFallback(ctx context.Context, repo restic.SaverRemoverUnpacked[restic.FileType], excludePacks restic.IDSet, p *progress.Counter) error {
|
||||||
p.SetMax(uint64(len(mi.Packs(excludePacks))))
|
p.SetMax(uint64(len(mi.Packs(excludePacks))))
|
||||||
|
|
||||||
mi.idxMutex.Lock()
|
mi.idxMutex.Lock()
|
||||||
|
@ -574,7 +574,7 @@ func (mi *MasterIndex) SaveFallback(ctx context.Context, repo restic.SaverRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveIndex saves all indexes in the backend.
|
// saveIndex saves all indexes in the backend.
|
||||||
func (mi *MasterIndex) saveIndex(ctx context.Context, r restic.SaverUnpacked, indexes ...*Index) error {
|
func (mi *MasterIndex) saveIndex(ctx context.Context, r restic.SaverUnpacked[restic.FileType], indexes ...*Index) error {
|
||||||
for i, idx := range indexes {
|
for i, idx := range indexes {
|
||||||
debug.Log("Saving index %d", i)
|
debug.Log("Saving index %d", i)
|
||||||
|
|
||||||
|
@ -590,12 +590,12 @@ func (mi *MasterIndex) saveIndex(ctx context.Context, r restic.SaverUnpacked, in
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveIndex saves all new indexes in the backend.
|
// SaveIndex saves all new indexes in the backend.
|
||||||
func (mi *MasterIndex) SaveIndex(ctx context.Context, r restic.SaverUnpacked) error {
|
func (mi *MasterIndex) SaveIndex(ctx context.Context, r restic.SaverUnpacked[restic.FileType]) error {
|
||||||
return mi.saveIndex(ctx, r, mi.finalizeNotFinalIndexes()...)
|
return mi.saveIndex(ctx, r, mi.finalizeNotFinalIndexes()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveFullIndex saves all full indexes in the backend.
|
// SaveFullIndex saves all full indexes in the backend.
|
||||||
func (mi *MasterIndex) SaveFullIndex(ctx context.Context, r restic.SaverUnpacked) error {
|
func (mi *MasterIndex) SaveFullIndex(ctx context.Context, r restic.SaverUnpacked[restic.FileType]) error {
|
||||||
return mi.saveIndex(ctx, r, mi.finalizeFullIndexes()...)
|
return mi.saveIndex(ctx, r, mi.finalizeFullIndexes()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -346,13 +346,13 @@ var (
|
||||||
depth = 3
|
depth = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
func createFilledRepo(t testing.TB, snapshots int, version uint) restic.Repository {
|
func createFilledRepo(t testing.TB, snapshots int, version uint) (restic.Repository, restic.Unpacked[restic.FileType]) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, unpacked, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
|
|
||||||
for i := 0; i < snapshots; i++ {
|
for i := 0; i < snapshots; i++ {
|
||||||
restic.TestCreateSnapshot(t, repo, snapshotTime.Add(time.Duration(i)*time.Second), depth)
|
restic.TestCreateSnapshot(t, repo, snapshotTime.Add(time.Duration(i)*time.Second), depth)
|
||||||
}
|
}
|
||||||
return repo
|
return repo, unpacked
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIndexSave(t *testing.T) {
|
func TestIndexSave(t *testing.T) {
|
||||||
|
@ -362,15 +362,15 @@ func TestIndexSave(t *testing.T) {
|
||||||
func testIndexSave(t *testing.T, version uint) {
|
func testIndexSave(t *testing.T, version uint) {
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
name string
|
name string
|
||||||
saver func(idx *index.MasterIndex, repo restic.Repository) error
|
saver func(idx *index.MasterIndex, repo restic.Unpacked[restic.FileType]) error
|
||||||
}{
|
}{
|
||||||
{"rewrite no-op", func(idx *index.MasterIndex, repo restic.Repository) error {
|
{"rewrite no-op", func(idx *index.MasterIndex, repo restic.Unpacked[restic.FileType]) error {
|
||||||
return idx.Rewrite(context.TODO(), repo, nil, nil, nil, index.MasterIndexRewriteOpts{})
|
return idx.Rewrite(context.TODO(), repo, nil, nil, nil, index.MasterIndexRewriteOpts{})
|
||||||
}},
|
}},
|
||||||
{"rewrite skip-all", func(idx *index.MasterIndex, repo restic.Repository) error {
|
{"rewrite skip-all", func(idx *index.MasterIndex, repo restic.Unpacked[restic.FileType]) error {
|
||||||
return idx.Rewrite(context.TODO(), repo, nil, restic.NewIDSet(), nil, index.MasterIndexRewriteOpts{})
|
return idx.Rewrite(context.TODO(), repo, nil, restic.NewIDSet(), nil, index.MasterIndexRewriteOpts{})
|
||||||
}},
|
}},
|
||||||
{"SaveFallback", func(idx *index.MasterIndex, repo restic.Repository) error {
|
{"SaveFallback", func(idx *index.MasterIndex, repo restic.Unpacked[restic.FileType]) error {
|
||||||
err := restic.ParallelRemove(context.TODO(), repo, idx.IDs(), restic.IndexFile, nil, nil)
|
err := restic.ParallelRemove(context.TODO(), repo, idx.IDs(), restic.IndexFile, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -379,7 +379,7 @@ func testIndexSave(t *testing.T, version uint) {
|
||||||
}},
|
}},
|
||||||
} {
|
} {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
repo := createFilledRepo(t, 3, version)
|
repo, unpacked := createFilledRepo(t, 3, version)
|
||||||
|
|
||||||
idx := index.NewMasterIndex()
|
idx := index.NewMasterIndex()
|
||||||
rtest.OK(t, idx.Load(context.TODO(), repo, nil, nil))
|
rtest.OK(t, idx.Load(context.TODO(), repo, nil, nil))
|
||||||
|
@ -388,7 +388,7 @@ func testIndexSave(t *testing.T, version uint) {
|
||||||
blobs[pb] = struct{}{}
|
blobs[pb] = struct{}{}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
rtest.OK(t, test.saver(idx, repo))
|
rtest.OK(t, test.saver(idx, unpacked))
|
||||||
idx = index.NewMasterIndex()
|
idx = index.NewMasterIndex()
|
||||||
rtest.OK(t, idx.Load(context.TODO(), repo, nil, nil))
|
rtest.OK(t, idx.Load(context.TODO(), repo, nil, nil))
|
||||||
|
|
||||||
|
@ -411,7 +411,7 @@ func TestIndexSavePartial(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testIndexSavePartial(t *testing.T, version uint) {
|
func testIndexSavePartial(t *testing.T, version uint) {
|
||||||
repo := createFilledRepo(t, 3, version)
|
repo, unpacked := createFilledRepo(t, 3, version)
|
||||||
|
|
||||||
// capture blob list before adding fourth snapshot
|
// capture blob list before adding fourth snapshot
|
||||||
idx := index.NewMasterIndex()
|
idx := index.NewMasterIndex()
|
||||||
|
@ -424,14 +424,14 @@ func testIndexSavePartial(t *testing.T, version uint) {
|
||||||
// add+remove new snapshot and track its pack files
|
// add+remove new snapshot and track its pack files
|
||||||
packsBefore := listPacks(t, repo)
|
packsBefore := listPacks(t, repo)
|
||||||
sn := restic.TestCreateSnapshot(t, repo, snapshotTime.Add(time.Duration(4)*time.Second), depth)
|
sn := restic.TestCreateSnapshot(t, repo, snapshotTime.Add(time.Duration(4)*time.Second), depth)
|
||||||
rtest.OK(t, repo.RemoveUnpacked(context.TODO(), restic.SnapshotFile, *sn.ID()))
|
rtest.OK(t, repo.RemoveUnpacked(context.TODO(), restic.WriteableSnapshotFile, *sn.ID()))
|
||||||
packsAfter := listPacks(t, repo)
|
packsAfter := listPacks(t, repo)
|
||||||
newPacks := packsAfter.Sub(packsBefore)
|
newPacks := packsAfter.Sub(packsBefore)
|
||||||
|
|
||||||
// rewrite index and remove pack files of new snapshot
|
// rewrite index and remove pack files of new snapshot
|
||||||
idx = index.NewMasterIndex()
|
idx = index.NewMasterIndex()
|
||||||
rtest.OK(t, idx.Load(context.TODO(), repo, nil, nil))
|
rtest.OK(t, idx.Load(context.TODO(), repo, nil, nil))
|
||||||
rtest.OK(t, idx.Rewrite(context.TODO(), repo, newPacks, nil, nil, index.MasterIndexRewriteOpts{}))
|
rtest.OK(t, idx.Rewrite(context.TODO(), unpacked, newPacks, nil, nil, index.MasterIndexRewriteOpts{}))
|
||||||
|
|
||||||
// check blobs
|
// check blobs
|
||||||
idx = index.NewMasterIndex()
|
idx = index.NewMasterIndex()
|
||||||
|
@ -446,7 +446,7 @@ func testIndexSavePartial(t *testing.T, version uint) {
|
||||||
rtest.Equals(t, 0, len(blobs), "saved index is missing blobs")
|
rtest.Equals(t, 0, len(blobs), "saved index is missing blobs")
|
||||||
|
|
||||||
// remove pack files to make check happy
|
// remove pack files to make check happy
|
||||||
rtest.OK(t, restic.ParallelRemove(context.TODO(), repo, newPacks, restic.PackFile, nil, nil))
|
rtest.OK(t, restic.ParallelRemove(context.TODO(), unpacked, newPacks, restic.PackFile, nil, nil))
|
||||||
|
|
||||||
checker.TestCheckRepo(t, repo, false)
|
checker.TestCheckRepo(t, repo, false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
|
@ -42,13 +43,7 @@ func Lock(ctx context.Context, repo *Repository, exclusive bool, retryLock time.
|
||||||
|
|
||||||
// Lock wraps the ctx such that it is cancelled when the repository is unlocked
|
// Lock wraps the ctx such that it is cancelled when the repository is unlocked
|
||||||
// cancelling the original context also stops the lock refresh
|
// cancelling the original context also stops the lock refresh
|
||||||
func (l *locker) Lock(ctx context.Context, repo *Repository, exclusive bool, retryLock time.Duration, printRetry func(msg string), logger func(format string, args ...interface{})) (*Unlocker, context.Context, error) {
|
func (l *locker) Lock(ctx context.Context, r *Repository, exclusive bool, retryLock time.Duration, printRetry func(msg string), logger func(format string, args ...interface{})) (*Unlocker, context.Context, error) {
|
||||||
|
|
||||||
lockFn := restic.NewLock
|
|
||||||
if exclusive {
|
|
||||||
lockFn = restic.NewExclusiveLock
|
|
||||||
}
|
|
||||||
|
|
||||||
var lock *restic.Lock
|
var lock *restic.Lock
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -56,9 +51,11 @@ func (l *locker) Lock(ctx context.Context, repo *Repository, exclusive bool, ret
|
||||||
retryMessagePrinted := false
|
retryMessagePrinted := false
|
||||||
retryTimeout := time.After(retryLock)
|
retryTimeout := time.After(retryLock)
|
||||||
|
|
||||||
|
repo := &internalRepository{r}
|
||||||
|
|
||||||
retryLoop:
|
retryLoop:
|
||||||
for {
|
for {
|
||||||
lock, err = lockFn(ctx, repo)
|
lock, err = restic.NewLock(ctx, repo, exclusive)
|
||||||
if err != nil && restic.IsAlreadyLocked(err) {
|
if err != nil && restic.IsAlreadyLocked(err) {
|
||||||
|
|
||||||
if !retryMessagePrinted {
|
if !retryMessagePrinted {
|
||||||
|
@ -75,7 +72,7 @@ retryLoop:
|
||||||
case <-retryTimeout:
|
case <-retryTimeout:
|
||||||
debug.Log("repo already locked, timeout expired")
|
debug.Log("repo already locked, timeout expired")
|
||||||
// Last lock attempt
|
// Last lock attempt
|
||||||
lock, err = lockFn(ctx, repo)
|
lock, err = restic.NewLock(ctx, repo, exclusive)
|
||||||
break retryLoop
|
break retryLoop
|
||||||
case <-retrySleepCh:
|
case <-retrySleepCh:
|
||||||
retrySleep = minDuration(retrySleep*2, l.retrySleepMax)
|
retrySleep = minDuration(retrySleep*2, l.retrySleepMax)
|
||||||
|
@ -272,3 +269,39 @@ func (l *Unlocker) Unlock() {
|
||||||
l.info.cancel()
|
l.info.cancel()
|
||||||
l.info.refreshWG.Wait()
|
l.info.refreshWG.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveStaleLocks deletes all locks detected as stale from the repository.
|
||||||
|
func RemoveStaleLocks(ctx context.Context, repo *Repository) (uint, error) {
|
||||||
|
var processed uint
|
||||||
|
err := restic.ForAllLocks(ctx, repo, nil, func(id restic.ID, lock *restic.Lock, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
// ignore locks that cannot be loaded
|
||||||
|
debug.Log("ignore lock %v: %v", id, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if lock.Stale() {
|
||||||
|
err = (&internalRepository{repo}).RemoveUnpacked(ctx, restic.LockFile, id)
|
||||||
|
if err == nil {
|
||||||
|
processed++
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return processed, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAllLocks removes all locks forcefully.
|
||||||
|
func RemoveAllLocks(ctx context.Context, repo *Repository) (uint, error) {
|
||||||
|
var processed uint32
|
||||||
|
err := restic.ParallelList(ctx, repo, restic.LockFile, repo.Connections(), func(ctx context.Context, id restic.ID, _ int64) error {
|
||||||
|
err := (&internalRepository{repo}).RemoveUnpacked(ctx, restic.LockFile, id)
|
||||||
|
if err == nil {
|
||||||
|
atomic.AddUint32(&processed, 1)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return uint(processed), err
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package repository
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -301,3 +302,83 @@ func TestLockWaitSuccess(t *testing.T) {
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
lock.Unlock()
|
lock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createFakeLock(repo *Repository, t time.Time, pid int) (restic.ID, error) {
|
||||||
|
hostname, err := os.Hostname()
|
||||||
|
if err != nil {
|
||||||
|
return restic.ID{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newLock := &restic.Lock{Time: t, PID: pid, Hostname: hostname}
|
||||||
|
return restic.SaveJSONUnpacked(context.TODO(), &internalRepository{repo}, restic.LockFile, &newLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lockExists(repo restic.Lister, t testing.TB, lockID restic.ID) bool {
|
||||||
|
var exists bool
|
||||||
|
rtest.OK(t, repo.List(context.TODO(), restic.LockFile, func(id restic.ID, size int64) error {
|
||||||
|
if id == lockID {
|
||||||
|
exists = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeLock(repo *Repository, id restic.ID) error {
|
||||||
|
return (&internalRepository{repo}).RemoveUnpacked(context.TODO(), restic.LockFile, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockWithStaleLock(t *testing.T) {
|
||||||
|
repo := TestRepository(t)
|
||||||
|
|
||||||
|
id1, err := createFakeLock(repo, time.Now().Add(-time.Hour), os.Getpid())
|
||||||
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
id2, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid())
|
||||||
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
id3, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid()+500000)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
processed, err := RemoveStaleLocks(context.TODO(), repo)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
rtest.Assert(t, lockExists(repo, t, id1) == false,
|
||||||
|
"stale lock still exists after RemoveStaleLocks was called")
|
||||||
|
rtest.Assert(t, lockExists(repo, t, id2) == true,
|
||||||
|
"non-stale lock was removed by RemoveStaleLocks")
|
||||||
|
rtest.Assert(t, lockExists(repo, t, id3) == false,
|
||||||
|
"stale lock still exists after RemoveStaleLocks was called")
|
||||||
|
rtest.Assert(t, processed == 2,
|
||||||
|
"number of locks removed does not match: expected %d, got %d",
|
||||||
|
2, processed)
|
||||||
|
|
||||||
|
rtest.OK(t, removeLock(repo, id2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveAllLocks(t *testing.T) {
|
||||||
|
repo := TestRepository(t)
|
||||||
|
|
||||||
|
id1, err := createFakeLock(repo, time.Now().Add(-time.Hour), os.Getpid())
|
||||||
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
id2, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid())
|
||||||
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
id3, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid()+500000)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
processed, err := RemoveAllLocks(context.TODO(), repo)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
rtest.Assert(t, lockExists(repo, t, id1) == false,
|
||||||
|
"lock still exists after RemoveAllLocks was called")
|
||||||
|
rtest.Assert(t, lockExists(repo, t, id2) == false,
|
||||||
|
"lock still exists after RemoveAllLocks was called")
|
||||||
|
rtest.Assert(t, lockExists(repo, t, id3) == false,
|
||||||
|
"lock still exists after RemoveAllLocks was called")
|
||||||
|
rtest.Assert(t, processed == 3,
|
||||||
|
"number of locks removed does not match: expected %d, got %d",
|
||||||
|
3, processed)
|
||||||
|
}
|
||||||
|
|
|
@ -190,5 +190,5 @@ func (r *Repository) savePacker(ctx context.Context, t restic.BlobType, p *packe
|
||||||
r.idx.StorePack(id, p.Packer.Blobs())
|
r.idx.StorePack(id, p.Packer.Blobs())
|
||||||
|
|
||||||
// Save index if full
|
// Save index if full
|
||||||
return r.idx.SaveFullIndex(ctx, r)
|
return r.idx.SaveFullIndex(ctx, &internalRepository{r})
|
||||||
}
|
}
|
||||||
|
|
|
@ -544,7 +544,7 @@ func (plan *PrunePlan) Execute(ctx context.Context, printer progress.Printer) er
|
||||||
// unreferenced packs can be safely deleted first
|
// unreferenced packs can be safely deleted first
|
||||||
if len(plan.removePacksFirst) != 0 {
|
if len(plan.removePacksFirst) != 0 {
|
||||||
printer.P("deleting unreferenced packs\n")
|
printer.P("deleting unreferenced packs\n")
|
||||||
_ = deleteFiles(ctx, true, repo, plan.removePacksFirst, restic.PackFile, printer)
|
_ = deleteFiles(ctx, true, &internalRepository{repo}, plan.removePacksFirst, restic.PackFile, printer)
|
||||||
// forget unused data
|
// forget unused data
|
||||||
plan.removePacksFirst = nil
|
plan.removePacksFirst = nil
|
||||||
}
|
}
|
||||||
|
@ -588,7 +588,7 @@ func (plan *PrunePlan) Execute(ctx context.Context, printer progress.Printer) er
|
||||||
if plan.opts.UnsafeRecovery {
|
if plan.opts.UnsafeRecovery {
|
||||||
printer.P("deleting index files\n")
|
printer.P("deleting index files\n")
|
||||||
indexFiles := repo.idx.IDs()
|
indexFiles := repo.idx.IDs()
|
||||||
err := deleteFiles(ctx, false, repo, indexFiles, restic.IndexFile, printer)
|
err := deleteFiles(ctx, false, &internalRepository{repo}, indexFiles, restic.IndexFile, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("%s", err)
|
return errors.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
@ -601,14 +601,14 @@ func (plan *PrunePlan) Execute(ctx context.Context, printer progress.Printer) er
|
||||||
|
|
||||||
if len(plan.removePacks) != 0 {
|
if len(plan.removePacks) != 0 {
|
||||||
printer.P("removing %d old packs\n", len(plan.removePacks))
|
printer.P("removing %d old packs\n", len(plan.removePacks))
|
||||||
_ = deleteFiles(ctx, true, repo, plan.removePacks, restic.PackFile, printer)
|
_ = deleteFiles(ctx, true, &internalRepository{repo}, plan.removePacks, restic.PackFile, printer)
|
||||||
}
|
}
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
if plan.opts.UnsafeRecovery {
|
if plan.opts.UnsafeRecovery {
|
||||||
err := repo.idx.SaveFallback(ctx, repo, plan.ignorePacks, printer.NewCounter("packs processed"))
|
err := repo.idx.SaveFallback(ctx, &internalRepository{repo}, plan.ignorePacks, printer.NewCounter("packs processed"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("%s", err)
|
return errors.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
@ -623,7 +623,7 @@ func (plan *PrunePlan) Execute(ctx context.Context, printer progress.Printer) er
|
||||||
|
|
||||||
// deleteFiles deletes the given fileList of fileType in parallel
|
// deleteFiles deletes the given fileList of fileType in parallel
|
||||||
// if ignoreError=true, it will print a warning if there was an error, else it will abort.
|
// if ignoreError=true, it will print a warning if there was an error, else it will abort.
|
||||||
func deleteFiles(ctx context.Context, ignoreError bool, repo restic.RemoverUnpacked, fileList restic.IDSet, fileType restic.FileType, printer progress.Printer) error {
|
func deleteFiles(ctx context.Context, ignoreError bool, repo restic.RemoverUnpacked[restic.FileType], fileList restic.IDSet, fileType restic.FileType, printer progress.Printer) error {
|
||||||
bar := printer.NewCounter("files deleted")
|
bar := printer.NewCounter("files deleted")
|
||||||
defer bar.Done()
|
defer bar.Done()
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ func testPrune(t *testing.T, opts repository.PruneOptions, errOnUnused bool) {
|
||||||
random := rand.New(rand.NewSource(seed))
|
random := rand.New(rand.NewSource(seed))
|
||||||
t.Logf("rand initialized with seed %d", seed)
|
t.Logf("rand initialized with seed %d", seed)
|
||||||
|
|
||||||
repo, be := repository.TestRepositoryWithVersion(t, 0)
|
repo, _, be := repository.TestRepositoryWithVersion(t, 0)
|
||||||
createRandomBlobs(t, random, repo, 4, 0.5, true)
|
createRandomBlobs(t, random, repo, 4, 0.5, true)
|
||||||
createRandomBlobs(t, random, repo, 5, 0.5, true)
|
createRandomBlobs(t, random, repo, 5, 0.5, true)
|
||||||
keep, _ := selectBlobs(t, random, repo, 0.5)
|
keep, _ := selectBlobs(t, random, repo, 0.5)
|
||||||
|
|
|
@ -21,10 +21,10 @@ func (r *Repository) LoadRaw(ctx context.Context, t restic.FileType, id restic.I
|
||||||
// retry loading damaged data only once. If a file fails to download correctly
|
// retry loading damaged data only once. If a file fails to download correctly
|
||||||
// the second time, then it is likely corrupted at the backend.
|
// the second time, then it is likely corrupted at the backend.
|
||||||
if h.Type != backend.ConfigFile && id != restic.Hash(buf) {
|
if h.Type != backend.ConfigFile && id != restic.Hash(buf) {
|
||||||
if r.Cache != nil {
|
if r.cache != nil {
|
||||||
// Cleanup cache to make sure it's not the cached copy that is broken.
|
// Cleanup cache to make sure it's not the cached copy that is broken.
|
||||||
// Ignore error as there's not much we can do in that case.
|
// Ignore error as there's not much we can do in that case.
|
||||||
_ = r.Cache.Forget(h)
|
_ = r.cache.Forget(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err = loadRaw(ctx, r.be, h)
|
buf, err = loadRaw(ctx, r.be, h)
|
||||||
|
|
|
@ -159,14 +159,14 @@ func findPacksForBlobs(t *testing.T, repo restic.Repository, blobs restic.BlobSe
|
||||||
return packs
|
return packs
|
||||||
}
|
}
|
||||||
|
|
||||||
func repack(t *testing.T, repo restic.Repository, packs restic.IDSet, blobs restic.BlobSet) {
|
func repack(t *testing.T, repo restic.Repository, be backend.Backend, packs restic.IDSet, blobs restic.BlobSet) {
|
||||||
repackedBlobs, err := repository.Repack(context.TODO(), repo, repo, packs, blobs, nil)
|
repackedBlobs, err := repository.Repack(context.TODO(), repo, repo, packs, blobs, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for id := range repackedBlobs {
|
for id := range repackedBlobs {
|
||||||
err = repo.RemoveUnpacked(context.TODO(), restic.PackFile, id)
|
err = be.Remove(context.TODO(), backend.Handle{Type: restic.PackFile, Name: id.String()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ func TestRepack(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRepack(t *testing.T, version uint) {
|
func testRepack(t *testing.T, version uint) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, _, be := repository.TestRepositoryWithVersion(t, version)
|
||||||
|
|
||||||
seed := time.Now().UnixNano()
|
seed := time.Now().UnixNano()
|
||||||
random := rand.New(rand.NewSource(seed))
|
random := rand.New(rand.NewSource(seed))
|
||||||
|
@ -199,7 +199,7 @@ func testRepack(t *testing.T, version uint) {
|
||||||
packsBefore := listPacks(t, repo)
|
packsBefore := listPacks(t, repo)
|
||||||
|
|
||||||
// Running repack on empty ID sets should not do anything at all.
|
// Running repack on empty ID sets should not do anything at all.
|
||||||
repack(t, repo, nil, nil)
|
repack(t, repo, be, nil, nil)
|
||||||
|
|
||||||
packsAfter := listPacks(t, repo)
|
packsAfter := listPacks(t, repo)
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ func testRepack(t *testing.T, version uint) {
|
||||||
|
|
||||||
removePacks := findPacksForBlobs(t, repo, removeBlobs)
|
removePacks := findPacksForBlobs(t, repo, removeBlobs)
|
||||||
|
|
||||||
repack(t, repo, removePacks, keepBlobs)
|
repack(t, repo, be, removePacks, keepBlobs)
|
||||||
rebuildAndReloadIndex(t, repo)
|
rebuildAndReloadIndex(t, repo)
|
||||||
|
|
||||||
packsAfter = listPacks(t, repo)
|
packsAfter = listPacks(t, repo)
|
||||||
|
@ -261,8 +261,8 @@ func (r oneConnectionRepo) Connections() uint {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRepackCopy(t *testing.T, version uint) {
|
func testRepackCopy(t *testing.T, version uint) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
dstRepo, _ := repository.TestRepositoryWithVersion(t, version)
|
dstRepo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
|
|
||||||
// test with minimal possible connection count
|
// test with minimal possible connection count
|
||||||
repoWrapped := &oneConnectionRepo{repo}
|
repoWrapped := &oneConnectionRepo{repo}
|
||||||
|
|
|
@ -123,7 +123,7 @@ func rewriteIndexFiles(ctx context.Context, repo *Repository, removePacks restic
|
||||||
printer.P("rebuilding index\n")
|
printer.P("rebuilding index\n")
|
||||||
|
|
||||||
bar := printer.NewCounter("indexes processed")
|
bar := printer.NewCounter("indexes processed")
|
||||||
return repo.idx.Rewrite(ctx, repo, removePacks, oldIndexes, extraObsolete, index.MasterIndexRewriteOpts{
|
return repo.idx.Rewrite(ctx, &internalRepository{repo}, removePacks, oldIndexes, extraObsolete, index.MasterIndexRewriteOpts{
|
||||||
SaveProgress: bar,
|
SaveProgress: bar,
|
||||||
DeleteProgress: func() *progress.Counter {
|
DeleteProgress: func() *progress.Counter {
|
||||||
return printer.NewCounter("old indexes deleted")
|
return printer.NewCounter("old indexes deleted")
|
||||||
|
|
|
@ -23,7 +23,7 @@ func testRebuildIndex(t *testing.T, readAllPacks bool, damage func(t *testing.T,
|
||||||
random := rand.New(rand.NewSource(seed))
|
random := rand.New(rand.NewSource(seed))
|
||||||
t.Logf("rand initialized with seed %d", seed)
|
t.Logf("rand initialized with seed %d", seed)
|
||||||
|
|
||||||
repo, be := repository.TestRepositoryWithVersion(t, 0)
|
repo, _, be := repository.TestRepositoryWithVersion(t, 0)
|
||||||
createRandomBlobs(t, random, repo, 4, 0.5, true)
|
createRandomBlobs(t, random, repo, 4, 0.5, true)
|
||||||
createRandomBlobs(t, random, repo, 5, 0.5, true)
|
createRandomBlobs(t, random, repo, 5, 0.5, true)
|
||||||
indexes := listIndex(t, repo)
|
indexes := listIndex(t, repo)
|
||||||
|
|
|
@ -65,7 +65,7 @@ func RepairPacks(ctx context.Context, repo *Repository, ids restic.IDSet, printe
|
||||||
printer.P("removing salvaged pack files")
|
printer.P("removing salvaged pack files")
|
||||||
// if we fail to delete the damaged pack files, then prune will remove them later on
|
// if we fail to delete the damaged pack files, then prune will remove them later on
|
||||||
bar = printer.NewCounter("files deleted")
|
bar = printer.NewCounter("files deleted")
|
||||||
_ = restic.ParallelRemove(ctx, repo, ids, restic.PackFile, nil, bar)
|
_ = restic.ParallelRemove(ctx, &internalRepository{repo}, ids, restic.PackFile, nil, bar)
|
||||||
bar.Done()
|
bar.Done()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -38,7 +38,7 @@ type Repository struct {
|
||||||
key *crypto.Key
|
key *crypto.Key
|
||||||
keyID restic.ID
|
keyID restic.ID
|
||||||
idx *index.MasterIndex
|
idx *index.MasterIndex
|
||||||
Cache *cache.Cache
|
cache *cache.Cache
|
||||||
|
|
||||||
opts Options
|
opts Options
|
||||||
|
|
||||||
|
@ -53,6 +53,11 @@ type Repository struct {
|
||||||
dec *zstd.Decoder
|
dec *zstd.Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internalRepository allows using SaveUnpacked and RemoveUnpacked with all FileTypes
|
||||||
|
type internalRepository struct {
|
||||||
|
*Repository
|
||||||
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Compression CompressionMode
|
Compression CompressionMode
|
||||||
PackSize uint
|
PackSize uint
|
||||||
|
@ -149,10 +154,14 @@ func (r *Repository) UseCache(c *cache.Cache) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
debug.Log("using cache")
|
debug.Log("using cache")
|
||||||
r.Cache = c
|
r.cache = c
|
||||||
r.be = c.Wrap(r.be)
|
r.be = c.Wrap(r.be)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Repository) Cache() *cache.Cache {
|
||||||
|
return r.cache
|
||||||
|
}
|
||||||
|
|
||||||
// SetDryRun sets the repo backend into dry-run mode.
|
// SetDryRun sets the repo backend into dry-run mode.
|
||||||
func (r *Repository) SetDryRun() {
|
func (r *Repository) SetDryRun() {
|
||||||
r.be = dryrun.New(r.be)
|
r.be = dryrun.New(r.be)
|
||||||
|
@ -225,15 +234,15 @@ func (r *Repository) LoadBlob(ctx context.Context, t restic.BlobType, id restic.
|
||||||
}
|
}
|
||||||
|
|
||||||
// try cached pack files first
|
// try cached pack files first
|
||||||
sortCachedPacksFirst(r.Cache, blobs)
|
sortCachedPacksFirst(r.cache, blobs)
|
||||||
|
|
||||||
buf, err := r.loadBlob(ctx, blobs, buf)
|
buf, err := r.loadBlob(ctx, blobs, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if r.Cache != nil {
|
if r.cache != nil {
|
||||||
for _, blob := range blobs {
|
for _, blob := range blobs {
|
||||||
h := backend.Handle{Type: restic.PackFile, Name: blob.PackID.String(), IsMetadata: blob.Type.IsMetadata()}
|
h := backend.Handle{Type: restic.PackFile, Name: blob.PackID.String(), IsMetadata: blob.Type.IsMetadata()}
|
||||||
// ignore errors as there's not much we can do here
|
// ignore errors as there's not much we can do here
|
||||||
_ = r.Cache.Forget(h)
|
_ = r.cache.Forget(h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,7 +455,15 @@ func (r *Repository) decompressUnpacked(p []byte) ([]byte, error) {
|
||||||
|
|
||||||
// SaveUnpacked encrypts data and stores it in the backend. Returned is the
|
// SaveUnpacked encrypts data and stores it in the backend. Returned is the
|
||||||
// storage hash.
|
// storage hash.
|
||||||
func (r *Repository) SaveUnpacked(ctx context.Context, t restic.FileType, buf []byte) (id restic.ID, err error) {
|
func (r *Repository) SaveUnpacked(ctx context.Context, t restic.WriteableFileType, buf []byte) (id restic.ID, err error) {
|
||||||
|
return r.saveUnpacked(ctx, t.ToFileType(), buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *internalRepository) SaveUnpacked(ctx context.Context, t restic.FileType, buf []byte) (id restic.ID, err error) {
|
||||||
|
return r.Repository.saveUnpacked(ctx, t, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) saveUnpacked(ctx context.Context, t restic.FileType, buf []byte) (id restic.ID, err error) {
|
||||||
p := buf
|
p := buf
|
||||||
if t != restic.ConfigFile {
|
if t != restic.ConfigFile {
|
||||||
p, err = r.compressUnpacked(p)
|
p, err = r.compressUnpacked(p)
|
||||||
|
@ -507,8 +524,15 @@ func (r *Repository) verifyUnpacked(buf []byte, t restic.FileType, expected []by
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) RemoveUnpacked(ctx context.Context, t restic.FileType, id restic.ID) error {
|
func (r *Repository) RemoveUnpacked(ctx context.Context, t restic.WriteableFileType, id restic.ID) error {
|
||||||
// TODO prevent everything except removing snapshots for non-repository code
|
return r.removeUnpacked(ctx, t.ToFileType(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *internalRepository) RemoveUnpacked(ctx context.Context, t restic.FileType, id restic.ID) error {
|
||||||
|
return r.Repository.removeUnpacked(ctx, t, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Repository) removeUnpacked(ctx context.Context, t restic.FileType, id restic.ID) error {
|
||||||
return r.be.Remove(ctx, backend.Handle{Type: t, Name: id.String()})
|
return r.be.Remove(ctx, backend.Handle{Type: t, Name: id.String()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,7 +542,7 @@ func (r *Repository) Flush(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.idx.SaveIndex(ctx, r)
|
return r.idx.SaveIndex(ctx, &internalRepository{r})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repository) StartPackUploader(ctx context.Context, wg *errgroup.Group) {
|
func (r *Repository) StartPackUploader(ctx context.Context, wg *errgroup.Group) {
|
||||||
|
@ -702,14 +726,14 @@ func (r *Repository) createIndexFromPacks(ctx context.Context, packsize map[rest
|
||||||
// prepareCache initializes the local cache. indexIDs is the list of IDs of
|
// prepareCache initializes the local cache. indexIDs is the list of IDs of
|
||||||
// index files still present in the repo.
|
// index files still present in the repo.
|
||||||
func (r *Repository) prepareCache() error {
|
func (r *Repository) prepareCache() error {
|
||||||
if r.Cache == nil {
|
if r.cache == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
packs := r.idx.Packs(restic.NewIDSet())
|
packs := r.idx.Packs(restic.NewIDSet())
|
||||||
|
|
||||||
// clear old packs
|
// clear old packs
|
||||||
err := r.Cache.Clear(restic.PackFile, packs)
|
err := r.cache.Clear(restic.PackFile, packs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "error clearing pack files in cache: %v\n", err)
|
fmt.Fprintf(os.Stderr, "error clearing pack files in cache: %v\n", err)
|
||||||
}
|
}
|
||||||
|
@ -803,7 +827,7 @@ func (r *Repository) init(ctx context.Context, password string, cfg restic.Confi
|
||||||
r.key = key.master
|
r.key = key.master
|
||||||
r.keyID = key.ID()
|
r.keyID = key.ID()
|
||||||
r.setConfig(cfg)
|
r.setConfig(cfg)
|
||||||
return restic.SaveConfig(ctx, r, cfg)
|
return restic.SaveConfig(ctx, &internalRepository{r}, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key returns the current master key.
|
// Key returns the current master key.
|
||||||
|
@ -835,9 +859,9 @@ func (r *Repository) ListPack(ctx context.Context, id restic.ID, size int64) ([]
|
||||||
|
|
||||||
entries, hdrSize, err := pack.List(r.Key(), backend.ReaderAt(ctx, r.be, h), size)
|
entries, hdrSize, err := pack.List(r.Key(), backend.ReaderAt(ctx, r.be, h), size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if r.Cache != nil {
|
if r.cache != nil {
|
||||||
// ignore error as there is not much we can do here
|
// ignore error as there is not much we can do here
|
||||||
_ = r.Cache.Forget(h)
|
_ = r.cache.Forget(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// retry on error
|
// retry on error
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/crypto"
|
"github.com/restic/restic/internal/crypto"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/repository/index"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
@ -84,6 +85,53 @@ func BenchmarkSortCachedPacksFirst(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkLoadIndex(b *testing.B) {
|
||||||
|
BenchmarkAllVersions(b, benchmarkLoadIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkLoadIndex(b *testing.B, version uint) {
|
||||||
|
TestUseLowSecurityKDFParameters(b)
|
||||||
|
|
||||||
|
repo, _, be := TestRepositoryWithVersion(b, version)
|
||||||
|
idx := index.NewIndex()
|
||||||
|
|
||||||
|
for i := 0; i < 5000; i++ {
|
||||||
|
idx.StorePack(restic.NewRandomID(), []restic.Blob{
|
||||||
|
{
|
||||||
|
BlobHandle: restic.NewRandomBlobHandle(),
|
||||||
|
Length: 1234,
|
||||||
|
Offset: 1235,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
idx.Finalize()
|
||||||
|
|
||||||
|
id, err := idx.SaveIndex(context.TODO(), &internalRepository{repo})
|
||||||
|
rtest.OK(b, err)
|
||||||
|
|
||||||
|
b.Logf("index saved as %v", id.Str())
|
||||||
|
fi, err := be.Stat(context.TODO(), backend.Handle{Type: restic.IndexFile, Name: id.String()})
|
||||||
|
rtest.OK(b, err)
|
||||||
|
b.Logf("filesize is %v", fi.Size)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := loadIndex(context.TODO(), repo, id)
|
||||||
|
rtest.OK(b, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadIndex loads the index id from backend and returns it.
|
||||||
|
func loadIndex(ctx context.Context, repo restic.LoaderUnpacked, id restic.ID) (*index.Index, error) {
|
||||||
|
buf, err := repo.LoadUnpacked(ctx, restic.IndexFile, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return index.DecodeIndex(buf, id)
|
||||||
|
}
|
||||||
|
|
||||||
// buildPackfileWithoutHeader returns a manually built pack file without a header.
|
// buildPackfileWithoutHeader returns a manually built pack file without a header.
|
||||||
func buildPackfileWithoutHeader(blobSizes []int, key *crypto.Key, compress bool) (blobs []restic.Blob, packfile []byte) {
|
func buildPackfileWithoutHeader(blobSizes []int, key *crypto.Key, compress bool) (blobs []restic.Blob, packfile []byte) {
|
||||||
opts := []zstd.EOption{
|
opts := []zstd.EOption{
|
||||||
|
|
|
@ -43,7 +43,7 @@ func testSaveCalculateID(t *testing.T, version uint) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSave(t *testing.T, version uint, calculateID bool) {
|
func testSave(t *testing.T, version uint, calculateID bool) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
|
|
||||||
for _, size := range testSizes {
|
for _, size := range testSizes {
|
||||||
data := make([]byte, size)
|
data := make([]byte, size)
|
||||||
|
@ -86,7 +86,7 @@ func BenchmarkSaveAndEncrypt(t *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchmarkSaveAndEncrypt(t *testing.B, version uint) {
|
func benchmarkSaveAndEncrypt(t *testing.B, version uint) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
size := 4 << 20 // 4MiB
|
size := 4 << 20 // 4MiB
|
||||||
|
|
||||||
data := make([]byte, size)
|
data := make([]byte, size)
|
||||||
|
@ -112,7 +112,7 @@ func TestLoadBlob(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoadBlob(t *testing.T, version uint) {
|
func testLoadBlob(t *testing.T, version uint) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
length := 1000000
|
length := 1000000
|
||||||
buf := crypto.NewBlobBuffer(length)
|
buf := crypto.NewBlobBuffer(length)
|
||||||
_, err := io.ReadFull(rnd, buf)
|
_, err := io.ReadFull(rnd, buf)
|
||||||
|
@ -168,7 +168,7 @@ func BenchmarkLoadBlob(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchmarkLoadBlob(b *testing.B, version uint) {
|
func benchmarkLoadBlob(b *testing.B, version uint) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(b, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(b, version)
|
||||||
length := 1000000
|
length := 1000000
|
||||||
buf := crypto.NewBlobBuffer(length)
|
buf := crypto.NewBlobBuffer(length)
|
||||||
_, err := io.ReadFull(rnd, buf)
|
_, err := io.ReadFull(rnd, buf)
|
||||||
|
@ -209,7 +209,7 @@ func BenchmarkLoadUnpacked(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchmarkLoadUnpacked(b *testing.B, version uint) {
|
func benchmarkLoadUnpacked(b *testing.B, version uint) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(b, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(b, version)
|
||||||
length := 1000000
|
length := 1000000
|
||||||
buf := crypto.NewBlobBuffer(length)
|
buf := crypto.NewBlobBuffer(length)
|
||||||
_, err := io.ReadFull(rnd, buf)
|
_, err := io.ReadFull(rnd, buf)
|
||||||
|
@ -217,7 +217,7 @@ func benchmarkLoadUnpacked(b *testing.B, version uint) {
|
||||||
|
|
||||||
dataID := restic.Hash(buf)
|
dataID := restic.Hash(buf)
|
||||||
|
|
||||||
storageID, err := repo.SaveUnpacked(context.TODO(), restic.PackFile, buf)
|
storageID, err := repo.SaveUnpacked(context.TODO(), restic.WriteableSnapshotFile, buf)
|
||||||
rtest.OK(b, err)
|
rtest.OK(b, err)
|
||||||
// rtest.OK(b, repo.Flush())
|
// rtest.OK(b, repo.Flush())
|
||||||
|
|
||||||
|
@ -225,7 +225,7 @@ func benchmarkLoadUnpacked(b *testing.B, version uint) {
|
||||||
b.SetBytes(int64(length))
|
b.SetBytes(int64(length))
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
data, err := repo.LoadUnpacked(context.TODO(), restic.PackFile, storageID)
|
data, err := repo.LoadUnpacked(context.TODO(), restic.SnapshotFile, storageID)
|
||||||
rtest.OK(b, err)
|
rtest.OK(b, err)
|
||||||
|
|
||||||
// See comment in BenchmarkLoadBlob.
|
// See comment in BenchmarkLoadBlob.
|
||||||
|
@ -262,7 +262,7 @@ func loadIndex(ctx context.Context, repo restic.LoaderUnpacked, id restic.ID) (*
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepositoryLoadUnpackedBroken(t *testing.T) {
|
func TestRepositoryLoadUnpackedBroken(t *testing.T) {
|
||||||
repo, be := repository.TestRepositoryWithVersion(t, 0)
|
repo, _, be := repository.TestRepositoryWithVersion(t, 0)
|
||||||
|
|
||||||
data := rtest.Random(23, 12345)
|
data := rtest.Random(23, 12345)
|
||||||
id := restic.Hash(data)
|
id := restic.Hash(data)
|
||||||
|
@ -309,43 +309,6 @@ func TestRepositoryLoadUnpackedRetryBroken(t *testing.T) {
|
||||||
rtest.OK(t, repo.LoadIndex(context.TODO(), nil))
|
rtest.OK(t, repo.LoadIndex(context.TODO(), nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkLoadIndex(b *testing.B) {
|
|
||||||
repository.BenchmarkAllVersions(b, benchmarkLoadIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkLoadIndex(b *testing.B, version uint) {
|
|
||||||
repository.TestUseLowSecurityKDFParameters(b)
|
|
||||||
|
|
||||||
repo, be := repository.TestRepositoryWithVersion(b, version)
|
|
||||||
idx := index.NewIndex()
|
|
||||||
|
|
||||||
for i := 0; i < 5000; i++ {
|
|
||||||
idx.StorePack(restic.NewRandomID(), []restic.Blob{
|
|
||||||
{
|
|
||||||
BlobHandle: restic.NewRandomBlobHandle(),
|
|
||||||
Length: 1234,
|
|
||||||
Offset: 1235,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
idx.Finalize()
|
|
||||||
|
|
||||||
id, err := idx.SaveIndex(context.TODO(), repo)
|
|
||||||
rtest.OK(b, err)
|
|
||||||
|
|
||||||
b.Logf("index saved as %v", id.Str())
|
|
||||||
fi, err := be.Stat(context.TODO(), backend.Handle{Type: restic.IndexFile, Name: id.String()})
|
|
||||||
rtest.OK(b, err)
|
|
||||||
b.Logf("filesize is %v", fi.Size)
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := loadIndex(context.TODO(), repo, id)
|
|
||||||
rtest.OK(b, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// saveRandomDataBlobs generates random data blobs and saves them to the repository.
|
// saveRandomDataBlobs generates random data blobs and saves them to the repository.
|
||||||
func saveRandomDataBlobs(t testing.TB, repo restic.Repository, num int, sizeMax int) {
|
func saveRandomDataBlobs(t testing.TB, repo restic.Repository, num int, sizeMax int) {
|
||||||
var wg errgroup.Group
|
var wg errgroup.Group
|
||||||
|
@ -368,7 +331,7 @@ func TestRepositoryIncrementalIndex(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRepositoryIncrementalIndex(t *testing.T, version uint) {
|
func testRepositoryIncrementalIndex(t *testing.T, version uint) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
|
|
||||||
index.IndexFull = func(*index.Index) bool { return true }
|
index.IndexFull = func(*index.Index) bool { return true }
|
||||||
|
|
||||||
|
@ -453,7 +416,7 @@ func TestListPack(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoDoubleInit(t *testing.T) {
|
func TestNoDoubleInit(t *testing.T) {
|
||||||
r, be := repository.TestRepositoryWithVersion(t, restic.StableRepoVersion)
|
r, _, be := repository.TestRepositoryWithVersion(t, restic.StableRepoVersion)
|
||||||
|
|
||||||
repo, err := repository.New(be, repository.Options{})
|
repo, err := repository.New(be, repository.Options{})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
|
@ -78,30 +78,31 @@ func TestRepositoryWithBackend(t testing.TB, be backend.Backend, version uint, o
|
||||||
// instead. The directory is not removed, but left there for inspection.
|
// instead. The directory is not removed, but left there for inspection.
|
||||||
func TestRepository(t testing.TB) *Repository {
|
func TestRepository(t testing.TB) *Repository {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
repo, _ := TestRepositoryWithVersion(t, 0)
|
repo, _, _ := TestRepositoryWithVersion(t, 0)
|
||||||
return repo
|
return repo
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRepositoryWithVersion(t testing.TB, version uint) (*Repository, backend.Backend) {
|
func TestRepositoryWithVersion(t testing.TB, version uint) (*Repository, restic.Unpacked[restic.FileType], backend.Backend) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
dir := os.Getenv("RESTIC_TEST_REPO")
|
dir := os.Getenv("RESTIC_TEST_REPO")
|
||||||
opts := Options{}
|
opts := Options{}
|
||||||
|
var repo *Repository
|
||||||
|
var be backend.Backend
|
||||||
if dir != "" {
|
if dir != "" {
|
||||||
_, err := os.Stat(dir)
|
_, err := os.Stat(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
be, err := local.Create(context.TODO(), local.Config{Path: dir})
|
lbe, err := local.Create(context.TODO(), local.Config{Path: dir})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error creating local backend at %v: %v", dir, err)
|
t.Fatalf("error creating local backend at %v: %v", dir, err)
|
||||||
}
|
}
|
||||||
return TestRepositoryWithBackend(t, be, version, opts)
|
repo, be = TestRepositoryWithBackend(t, lbe, version, opts)
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
t.Logf("directory at %v already exists, using mem backend", dir)
|
t.Logf("directory at %v already exists, using mem backend", dir)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
repo, be = TestRepositoryWithBackend(t, nil, version, opts)
|
||||||
}
|
}
|
||||||
|
return repo, &internalRepository{repo}, be
|
||||||
return TestRepositoryWithBackend(t, nil, version, opts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromFixture(t testing.TB, repoFixture string) (*Repository, backend.Backend, func()) {
|
func TestFromFixture(t testing.TB, repoFixture string) (*Repository, backend.Backend, func()) {
|
||||||
|
@ -156,3 +157,8 @@ func BenchmarkAllVersions(b *testing.B, bench VersionedBenchmark) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewLock(t *testing.T, repo *Repository, exclusive bool) (*restic.Lock, error) {
|
||||||
|
// TODO get rid of this test helper
|
||||||
|
return restic.NewLock(context.TODO(), &internalRepository{repo}, exclusive)
|
||||||
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ func upgradeRepository(ctx context.Context, repo *Repository) error {
|
||||||
cfg := repo.Config()
|
cfg := repo.Config()
|
||||||
cfg.Version = 2
|
cfg.Version = 2
|
||||||
|
|
||||||
err := restic.SaveConfig(ctx, repo, cfg)
|
err := restic.SaveConfig(ctx, &internalRepository{repo}, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("save new config file failed: %w", err)
|
return fmt.Errorf("save new config file failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUpgradeRepoV2(t *testing.T) {
|
func TestUpgradeRepoV2(t *testing.T) {
|
||||||
repo, _ := TestRepositoryWithVersion(t, 1)
|
repo, _, _ := TestRepositoryWithVersion(t, 1)
|
||||||
if repo.Config().Version != 1 {
|
if repo.Config().Version != 1 {
|
||||||
t.Fatal("test repo has wrong version")
|
t.Fatal("test repo has wrong version")
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ func LoadConfig(ctx context.Context, r LoaderUnpacked) (Config, error) {
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveConfig(ctx context.Context, r SaverUnpacked, cfg Config) error {
|
func SaveConfig(ctx context.Context, r SaverUnpacked[FileType], cfg Config) error {
|
||||||
_, err := SaveJSONUnpacked(ctx, r, ConfigFile, cfg)
|
_, err := SaveJSONUnpacked(ctx, r, ConfigFile, cfg)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ func LoadJSONUnpacked(ctx context.Context, repo LoaderUnpacked, t FileType, id I
|
||||||
|
|
||||||
// SaveJSONUnpacked serialises item as JSON and encrypts and saves it in the
|
// SaveJSONUnpacked serialises item as JSON and encrypts and saves it in the
|
||||||
// backend as type t, without a pack. It returns the storage hash.
|
// backend as type t, without a pack. It returns the storage hash.
|
||||||
func SaveJSONUnpacked(ctx context.Context, repo SaverUnpacked, t FileType, item interface{}) (ID, error) {
|
func SaveJSONUnpacked[FT FileTypes](ctx context.Context, repo SaverUnpacked[FT], t FT, item interface{}) (ID, error) {
|
||||||
debug.Log("save new blob %v", t)
|
debug.Log("save new blob %v", t)
|
||||||
plaintext, err := json.Marshal(item)
|
plaintext, err := json.Marshal(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"os/user"
|
"os/user"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -39,7 +38,7 @@ type Lock struct {
|
||||||
UID uint32 `json:"uid,omitempty"`
|
UID uint32 `json:"uid,omitempty"`
|
||||||
GID uint32 `json:"gid,omitempty"`
|
GID uint32 `json:"gid,omitempty"`
|
||||||
|
|
||||||
repo Unpacked
|
repo Unpacked[FileType]
|
||||||
lockID *ID
|
lockID *ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,20 +86,6 @@ func IsInvalidLock(err error) bool {
|
||||||
|
|
||||||
var ErrRemovedLock = errors.New("lock file was removed in the meantime")
|
var ErrRemovedLock = errors.New("lock file was removed in the meantime")
|
||||||
|
|
||||||
// NewLock returns a new, non-exclusive lock for the repository. If an
|
|
||||||
// exclusive lock is already held by another process, it returns an error
|
|
||||||
// that satisfies IsAlreadyLocked.
|
|
||||||
func NewLock(ctx context.Context, repo Unpacked) (*Lock, error) {
|
|
||||||
return newLock(ctx, repo, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExclusiveLock returns a new, exclusive lock for the repository. If
|
|
||||||
// another lock (normal and exclusive) is already held by another process,
|
|
||||||
// it returns an error that satisfies IsAlreadyLocked.
|
|
||||||
func NewExclusiveLock(ctx context.Context, repo Unpacked) (*Lock, error) {
|
|
||||||
return newLock(ctx, repo, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
var waitBeforeLockCheck = 200 * time.Millisecond
|
var waitBeforeLockCheck = 200 * time.Millisecond
|
||||||
|
|
||||||
// delay increases by factor 2 on each retry
|
// delay increases by factor 2 on each retry
|
||||||
|
@ -113,11 +98,15 @@ func TestSetLockTimeout(t testing.TB, d time.Duration) {
|
||||||
initialWaitBetweenLockRetries = d
|
initialWaitBetweenLockRetries = d
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLock(ctx context.Context, repo Unpacked, excl bool) (*Lock, error) {
|
// NewLock returns a new lock for the repository. If an
|
||||||
|
// exclusive lock is already held by another process, it returns an error
|
||||||
|
// that satisfies IsAlreadyLocked. If the new lock is exclude, then other
|
||||||
|
// non-exclusive locks also result in an IsAlreadyLocked error.
|
||||||
|
func NewLock(ctx context.Context, repo Unpacked[FileType], exclusive bool) (*Lock, error) {
|
||||||
lock := &Lock{
|
lock := &Lock{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
PID: os.Getpid(),
|
PID: os.Getpid(),
|
||||||
Exclusive: excl,
|
Exclusive: exclusive,
|
||||||
repo: repo,
|
repo: repo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,42 +433,6 @@ func LoadLock(ctx context.Context, repo LoaderUnpacked, id ID) (*Lock, error) {
|
||||||
return lock, nil
|
return lock, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveStaleLocks deletes all locks detected as stale from the repository.
|
|
||||||
func RemoveStaleLocks(ctx context.Context, repo Unpacked) (uint, error) {
|
|
||||||
var processed uint
|
|
||||||
err := ForAllLocks(ctx, repo, nil, func(id ID, lock *Lock, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
// ignore locks that cannot be loaded
|
|
||||||
debug.Log("ignore lock %v: %v", id, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if lock.Stale() {
|
|
||||||
err = repo.RemoveUnpacked(ctx, LockFile, id)
|
|
||||||
if err == nil {
|
|
||||||
processed++
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return processed, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAllLocks removes all locks forcefully.
|
|
||||||
func RemoveAllLocks(ctx context.Context, repo Unpacked) (uint, error) {
|
|
||||||
var processed uint32
|
|
||||||
err := ParallelList(ctx, repo, LockFile, repo.Connections(), func(ctx context.Context, id ID, _ int64) error {
|
|
||||||
err := repo.RemoveUnpacked(ctx, LockFile, id)
|
|
||||||
if err == nil {
|
|
||||||
atomic.AddUint32(&processed, 1)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
return uint(processed), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForAllLocks reads all locks in parallel and calls the given callback.
|
// ForAllLocks reads all locks in parallel and calls the given callback.
|
||||||
// It is guaranteed that the function is not run concurrently. If the
|
// It is guaranteed that the function is not run concurrently. If the
|
||||||
// callback returns an error, this function is cancelled and also returns that error.
|
// callback returns an error, this function is cancelled and also returns that error.
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestLock(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
||||||
|
|
||||||
lock, err := restic.NewLock(context.TODO(), repo)
|
lock, err := repository.TestNewLock(t, repo, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
rtest.OK(t, lock.Unlock(context.TODO()))
|
rtest.OK(t, lock.Unlock(context.TODO()))
|
||||||
|
@ -29,7 +29,7 @@ func TestDoubleUnlock(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
||||||
|
|
||||||
lock, err := restic.NewLock(context.TODO(), repo)
|
lock, err := repository.TestNewLock(t, repo, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
rtest.OK(t, lock.Unlock(context.TODO()))
|
rtest.OK(t, lock.Unlock(context.TODO()))
|
||||||
|
@ -43,10 +43,10 @@ func TestMultipleLock(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
||||||
|
|
||||||
lock1, err := restic.NewLock(context.TODO(), repo)
|
lock1, err := repository.TestNewLock(t, repo, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
lock2, err := restic.NewLock(context.TODO(), repo)
|
lock2, err := repository.TestNewLock(t, repo, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
rtest.OK(t, lock1.Unlock(context.TODO()))
|
rtest.OK(t, lock1.Unlock(context.TODO()))
|
||||||
|
@ -69,10 +69,10 @@ func TestMultipleLockFailure(t *testing.T) {
|
||||||
repo, _ := repository.TestRepositoryWithBackend(t, be, 0, repository.Options{})
|
repo, _ := repository.TestRepositoryWithBackend(t, be, 0, repository.Options{})
|
||||||
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
||||||
|
|
||||||
lock1, err := restic.NewLock(context.TODO(), repo)
|
lock1, err := repository.TestNewLock(t, repo, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
_, err = restic.NewLock(context.TODO(), repo)
|
_, err = repository.TestNewLock(t, repo, false)
|
||||||
rtest.Assert(t, err != nil, "unreadable lock file did not result in an error")
|
rtest.Assert(t, err != nil, "unreadable lock file did not result in an error")
|
||||||
|
|
||||||
rtest.OK(t, lock1.Unlock(context.TODO()))
|
rtest.OK(t, lock1.Unlock(context.TODO()))
|
||||||
|
@ -81,7 +81,7 @@ func TestMultipleLockFailure(t *testing.T) {
|
||||||
func TestLockExclusive(t *testing.T) {
|
func TestLockExclusive(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
|
|
||||||
elock, err := restic.NewExclusiveLock(context.TODO(), repo)
|
elock, err := repository.TestNewLock(t, repo, true)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
rtest.OK(t, elock.Unlock(context.TODO()))
|
rtest.OK(t, elock.Unlock(context.TODO()))
|
||||||
}
|
}
|
||||||
|
@ -90,10 +90,10 @@ func TestLockOnExclusiveLockedRepo(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
||||||
|
|
||||||
elock, err := restic.NewExclusiveLock(context.TODO(), repo)
|
elock, err := repository.TestNewLock(t, repo, true)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
lock, err := restic.NewLock(context.TODO(), repo)
|
lock, err := repository.TestNewLock(t, repo, false)
|
||||||
rtest.Assert(t, err != nil,
|
rtest.Assert(t, err != nil,
|
||||||
"create normal lock with exclusively locked repo didn't return an error")
|
"create normal lock with exclusively locked repo didn't return an error")
|
||||||
rtest.Assert(t, restic.IsAlreadyLocked(err),
|
rtest.Assert(t, restic.IsAlreadyLocked(err),
|
||||||
|
@ -107,10 +107,10 @@ func TestExclusiveLockOnLockedRepo(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
||||||
|
|
||||||
elock, err := restic.NewLock(context.TODO(), repo)
|
elock, err := repository.TestNewLock(t, repo, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
lock, err := restic.NewExclusiveLock(context.TODO(), repo)
|
lock, err := repository.TestNewLock(t, repo, true)
|
||||||
rtest.Assert(t, err != nil,
|
rtest.Assert(t, err != nil,
|
||||||
"create normal lock with exclusively locked repo didn't return an error")
|
"create normal lock with exclusively locked repo didn't return an error")
|
||||||
rtest.Assert(t, restic.IsAlreadyLocked(err),
|
rtest.Assert(t, restic.IsAlreadyLocked(err),
|
||||||
|
@ -120,20 +120,6 @@ func TestExclusiveLockOnLockedRepo(t *testing.T) {
|
||||||
rtest.OK(t, elock.Unlock(context.TODO()))
|
rtest.OK(t, elock.Unlock(context.TODO()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFakeLock(repo restic.SaverUnpacked, t time.Time, pid int) (restic.ID, error) {
|
|
||||||
hostname, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
return restic.ID{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newLock := &restic.Lock{Time: t, PID: pid, Hostname: hostname}
|
|
||||||
return restic.SaveJSONUnpacked(context.TODO(), repo, restic.LockFile, &newLock)
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeLock(repo restic.RemoverUnpacked, id restic.ID) error {
|
|
||||||
return repo.RemoveUnpacked(context.TODO(), restic.LockFile, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
var staleLockTests = []struct {
|
var staleLockTests = []struct {
|
||||||
timestamp time.Time
|
timestamp time.Time
|
||||||
stale bool
|
stale bool
|
||||||
|
@ -190,72 +176,6 @@ func TestLockStale(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func lockExists(repo restic.Lister, t testing.TB, lockID restic.ID) bool {
|
|
||||||
var exists bool
|
|
||||||
rtest.OK(t, repo.List(context.TODO(), restic.LockFile, func(id restic.ID, size int64) error {
|
|
||||||
if id == lockID {
|
|
||||||
exists = true
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}))
|
|
||||||
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLockWithStaleLock(t *testing.T) {
|
|
||||||
repo := repository.TestRepository(t)
|
|
||||||
|
|
||||||
id1, err := createFakeLock(repo, time.Now().Add(-time.Hour), os.Getpid())
|
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
id2, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid())
|
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
id3, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid()+500000)
|
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
processed, err := restic.RemoveStaleLocks(context.TODO(), repo)
|
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
rtest.Assert(t, lockExists(repo, t, id1) == false,
|
|
||||||
"stale lock still exists after RemoveStaleLocks was called")
|
|
||||||
rtest.Assert(t, lockExists(repo, t, id2) == true,
|
|
||||||
"non-stale lock was removed by RemoveStaleLocks")
|
|
||||||
rtest.Assert(t, lockExists(repo, t, id3) == false,
|
|
||||||
"stale lock still exists after RemoveStaleLocks was called")
|
|
||||||
rtest.Assert(t, processed == 2,
|
|
||||||
"number of locks removed does not match: expected %d, got %d",
|
|
||||||
2, processed)
|
|
||||||
|
|
||||||
rtest.OK(t, removeLock(repo, id2))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoveAllLocks(t *testing.T) {
|
|
||||||
repo := repository.TestRepository(t)
|
|
||||||
|
|
||||||
id1, err := createFakeLock(repo, time.Now().Add(-time.Hour), os.Getpid())
|
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
id2, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid())
|
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
id3, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid()+500000)
|
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
processed, err := restic.RemoveAllLocks(context.TODO(), repo)
|
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
rtest.Assert(t, lockExists(repo, t, id1) == false,
|
|
||||||
"lock still exists after RemoveAllLocks was called")
|
|
||||||
rtest.Assert(t, lockExists(repo, t, id2) == false,
|
|
||||||
"lock still exists after RemoveAllLocks was called")
|
|
||||||
rtest.Assert(t, lockExists(repo, t, id3) == false,
|
|
||||||
"lock still exists after RemoveAllLocks was called")
|
|
||||||
rtest.Assert(t, processed == 3,
|
|
||||||
"number of locks removed does not match: expected %d, got %d",
|
|
||||||
3, processed)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkSingleLock(t *testing.T, repo restic.Lister) restic.ID {
|
func checkSingleLock(t *testing.T, repo restic.Lister) restic.ID {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var lockID *restic.ID
|
var lockID *restic.ID
|
||||||
|
@ -279,7 +199,7 @@ func testLockRefresh(t *testing.T, refresh func(lock *restic.Lock) error) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
||||||
|
|
||||||
lock, err := restic.NewLock(context.TODO(), repo)
|
lock, err := repository.TestNewLock(t, repo, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
time0 := lock.Time
|
time0 := lock.Time
|
||||||
|
|
||||||
|
@ -312,10 +232,10 @@ func TestLockRefreshStale(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLockRefreshStaleMissing(t *testing.T) {
|
func TestLockRefreshStaleMissing(t *testing.T) {
|
||||||
repo, be := repository.TestRepositoryWithVersion(t, 0)
|
repo, _, be := repository.TestRepositoryWithVersion(t, 0)
|
||||||
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
restic.TestSetLockTimeout(t, 5*time.Millisecond)
|
||||||
|
|
||||||
lock, err := restic.NewLock(context.TODO(), repo)
|
lock, err := repository.TestNewLock(t, repo, false)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
lockID := checkSingleLock(t, repo)
|
lockID := checkSingleLock(t, repo)
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ func ParallelList(ctx context.Context, r Lister, t FileType, parallelism uint, f
|
||||||
|
|
||||||
// ParallelRemove deletes the given fileList of fileType in parallel
|
// ParallelRemove deletes the given fileList of fileType in parallel
|
||||||
// if callback returns an error, then it will abort.
|
// if callback returns an error, then it will abort.
|
||||||
func ParallelRemove(ctx context.Context, repo RemoverUnpacked, fileList IDSet, fileType FileType, report func(id ID, err error) error, bar *progress.Counter) error {
|
func ParallelRemove[FT FileTypes](ctx context.Context, repo RemoverUnpacked[FT], fileList IDSet, fileType FT, report func(id ID, err error) error, bar *progress.Counter) error {
|
||||||
fileChan := make(chan ID)
|
fileChan := make(chan ID)
|
||||||
wg, ctx := errgroup.WithContext(ctx)
|
wg, ctx := errgroup.WithContext(ctx)
|
||||||
wg.Go(func() error {
|
wg.Go(func() error {
|
||||||
|
|
|
@ -57,14 +57,16 @@ type Repository interface {
|
||||||
LoadRaw(ctx context.Context, t FileType, id ID) (data []byte, err error)
|
LoadRaw(ctx context.Context, t FileType, id ID) (data []byte, err error)
|
||||||
// LoadUnpacked loads and decrypts the file with the given type and ID.
|
// LoadUnpacked loads and decrypts the file with the given type and ID.
|
||||||
LoadUnpacked(ctx context.Context, t FileType, id ID) (data []byte, err error)
|
LoadUnpacked(ctx context.Context, t FileType, id ID) (data []byte, err error)
|
||||||
SaveUnpacked(ctx context.Context, t FileType, buf []byte) (ID, error)
|
SaveUnpacked(ctx context.Context, t WriteableFileType, buf []byte) (ID, error)
|
||||||
// RemoveUnpacked removes a file from the repository. This will eventually be restricted to deleting only snapshots.
|
// RemoveUnpacked removes a file from the repository. This will eventually be restricted to deleting only snapshots.
|
||||||
RemoveUnpacked(ctx context.Context, t FileType, id ID) error
|
RemoveUnpacked(ctx context.Context, t WriteableFileType, id ID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileType = backend.FileType
|
type FileType = backend.FileType
|
||||||
|
|
||||||
// These are the different data types a backend can store.
|
// These are the different data types a backend can store. Only filetypes contained
|
||||||
|
// in the `WriteableFileType` subset can be modified via the Repository interface.
|
||||||
|
// All other filetypes are considered internal datastructures of the Repository.
|
||||||
const (
|
const (
|
||||||
PackFile FileType = backend.PackFile
|
PackFile FileType = backend.PackFile
|
||||||
KeyFile FileType = backend.KeyFile
|
KeyFile FileType = backend.KeyFile
|
||||||
|
@ -74,6 +76,26 @@ const (
|
||||||
ConfigFile FileType = backend.ConfigFile
|
ConfigFile FileType = backend.ConfigFile
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type WriteableFileType backend.FileType
|
||||||
|
|
||||||
|
// These are the different data types that can be modified via SaveUnpacked or RemoveUnpacked.
|
||||||
|
const (
|
||||||
|
WriteableSnapshotFile WriteableFileType = WriteableFileType(SnapshotFile)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *WriteableFileType) ToFileType() FileType {
|
||||||
|
switch *w {
|
||||||
|
case WriteableSnapshotFile:
|
||||||
|
return SnapshotFile
|
||||||
|
default:
|
||||||
|
panic("invalid WriteableFileType")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileTypes interface {
|
||||||
|
FileType | WriteableFileType
|
||||||
|
}
|
||||||
|
|
||||||
// LoaderUnpacked allows loading a blob not stored in a pack file
|
// LoaderUnpacked allows loading a blob not stored in a pack file
|
||||||
type LoaderUnpacked interface {
|
type LoaderUnpacked interface {
|
||||||
// Connections returns the maximum number of concurrent backend operations
|
// Connections returns the maximum number of concurrent backend operations
|
||||||
|
@ -82,22 +104,22 @@ type LoaderUnpacked interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaverUnpacked allows saving a blob not stored in a pack file
|
// SaverUnpacked allows saving a blob not stored in a pack file
|
||||||
type SaverUnpacked interface {
|
type SaverUnpacked[FT FileTypes] interface {
|
||||||
// Connections returns the maximum number of concurrent backend operations
|
// Connections returns the maximum number of concurrent backend operations
|
||||||
Connections() uint
|
Connections() uint
|
||||||
SaveUnpacked(ctx context.Context, t FileType, buf []byte) (ID, error)
|
SaveUnpacked(ctx context.Context, t FT, buf []byte) (ID, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoverUnpacked allows removing an unpacked blob
|
// RemoverUnpacked allows removing an unpacked blob
|
||||||
type RemoverUnpacked interface {
|
type RemoverUnpacked[FT FileTypes] interface {
|
||||||
// Connections returns the maximum number of concurrent backend operations
|
// Connections returns the maximum number of concurrent backend operations
|
||||||
Connections() uint
|
Connections() uint
|
||||||
RemoveUnpacked(ctx context.Context, t FileType, id ID) error
|
RemoveUnpacked(ctx context.Context, t FT, id ID) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type SaverRemoverUnpacked interface {
|
type SaverRemoverUnpacked[FT FileTypes] interface {
|
||||||
SaverUnpacked
|
SaverUnpacked[FT]
|
||||||
RemoverUnpacked
|
RemoverUnpacked[FT]
|
||||||
}
|
}
|
||||||
|
|
||||||
type PackBlobs struct {
|
type PackBlobs struct {
|
||||||
|
@ -126,10 +148,10 @@ type ListerLoaderUnpacked interface {
|
||||||
LoaderUnpacked
|
LoaderUnpacked
|
||||||
}
|
}
|
||||||
|
|
||||||
type Unpacked interface {
|
type Unpacked[FT FileTypes] interface {
|
||||||
ListerLoaderUnpacked
|
ListerLoaderUnpacked
|
||||||
SaverUnpacked
|
SaverUnpacked[FT]
|
||||||
RemoverUnpacked
|
RemoverUnpacked[FT]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListBlobser interface {
|
type ListBlobser interface {
|
||||||
|
|
|
@ -90,8 +90,8 @@ func LoadSnapshot(ctx context.Context, loader LoaderUnpacked, id ID) (*Snapshot,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveSnapshot saves the snapshot sn and returns its ID.
|
// SaveSnapshot saves the snapshot sn and returns its ID.
|
||||||
func SaveSnapshot(ctx context.Context, repo SaverUnpacked, sn *Snapshot) (ID, error) {
|
func SaveSnapshot(ctx context.Context, repo SaverUnpacked[WriteableFileType], sn *Snapshot) (ID, error) {
|
||||||
return SaveJSONUnpacked(ctx, repo, SnapshotFile, sn)
|
return SaveJSONUnpacked(ctx, repo, WriteableSnapshotFile, sn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForAllSnapshots reads all snapshots in parallel and calls the
|
// ForAllSnapshots reads all snapshots in parallel and calls the
|
||||||
|
|
|
@ -32,7 +32,7 @@ func TestLoadJSONUnpacked(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoadJSONUnpacked(t *testing.T, version uint) {
|
func testLoadJSONUnpacked(t *testing.T, version uint) {
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
|
|
||||||
// archive a snapshot
|
// archive a snapshot
|
||||||
sn := restic.Snapshot{}
|
sn := restic.Snapshot{}
|
||||||
|
|
|
@ -184,7 +184,7 @@ func testLoadTree(t *testing.T, version uint) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// archive a few files
|
// archive a few files
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
sn := archiver.TestSnapshot(t, repo, rtest.BenchArchiveDirectory, nil)
|
sn := archiver.TestSnapshot(t, repo, rtest.BenchArchiveDirectory, nil)
|
||||||
rtest.OK(t, repo.Flush(context.Background()))
|
rtest.OK(t, repo.Flush(context.Background()))
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ func benchmarkLoadTree(t *testing.B, version uint) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// archive a few files
|
// archive a few files
|
||||||
repo, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
sn := archiver.TestSnapshot(t, repo, rtest.BenchArchiveDirectory, nil)
|
sn := archiver.TestSnapshot(t, repo, rtest.BenchArchiveDirectory, nil)
|
||||||
rtest.OK(t, repo.Flush(context.Background()))
|
rtest.OK(t, repo.Flush(context.Background()))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue