diff --git a/src/cmds/restic/cmd_prune.go b/src/cmds/restic/cmd_prune.go index 4705644c7..71b83369e 100644 --- a/src/cmds/restic/cmd_prune.go +++ b/src/cmds/restic/cmd_prune.go @@ -152,6 +152,10 @@ func pruneRepository(gopts GlobalOptions, repo restic.Repository) error { err = restic.FindUsedBlobs(ctx, repo, *sn.Tree, usedBlobs, seenBlobs) if err != nil { + if repo.Backend().IsNotExist(err) { + return errors.Fatal("unable to load a tree from the repo: " + err.Error()) + } + return err } diff --git a/src/restic/backend.go b/src/restic/backend.go index 0020a76a9..b3d91cc62 100644 --- a/src/restic/backend.go +++ b/src/restic/backend.go @@ -36,6 +36,10 @@ type Backend interface { // arbitrary order. A goroutine is started for this, which is stopped when // ctx is cancelled. List(ctx context.Context, t FileType) <-chan string + + // IsNotExist returns true if the error was caused by a non-existing file + // in the backend. + IsNotExist(err error) bool } // FileInfo is returned by Stat() and contains information about a file in the diff --git a/src/restic/backend/b2/b2.go b/src/restic/backend/b2/b2.go index c19915038..69ccb9bdc 100644 --- a/src/restic/backend/b2/b2.go +++ b/src/restic/backend/b2/b2.go @@ -151,6 +151,11 @@ func (wr *wrapReader) Close() error { return err } +// IsNotExist returns true if the error is caused by a non-existing file. +func (be *b2Backend) IsNotExist(err error) bool { + return b2.IsNotExist(errors.Cause(err)) +} + // Load returns the data stored in the backend for h at the given offset // and saves it in p. Load has the same semantics as io.ReaderAt. func (be *b2Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) { diff --git a/src/restic/backend/local/local.go b/src/restic/backend/local/local.go index 1a3c0158c..4d58b187d 100644 --- a/src/restic/backend/local/local.go +++ b/src/restic/backend/local/local.go @@ -75,6 +75,11 @@ func (b *Local) Location() string { return b.Path } +// IsNotExist returns true if the error is caused by a non existing file. +func (b *Local) IsNotExist(err error) bool { + return os.IsNotExist(errors.Cause(err)) +} + // Save stores data in the backend at the handle. func (b *Local) Save(ctx context.Context, h restic.Handle, rd io.Reader) (err error) { debug.Log("Save %v", h) diff --git a/src/restic/backend/mem/mem_backend.go b/src/restic/backend/mem/mem_backend.go index bbb4dbd1a..cee81799a 100644 --- a/src/restic/backend/mem/mem_backend.go +++ b/src/restic/backend/mem/mem_backend.go @@ -19,6 +19,8 @@ type memMap map[restic.Handle][]byte // make sure that MemoryBackend implements backend.Backend var _ restic.Backend = &MemoryBackend{} +var errNotFound = errors.New("not found") + // MemoryBackend is a mock backend that uses a map for storing all data in // memory. This should only be used for tests. type MemoryBackend struct { @@ -51,6 +53,11 @@ func (be *MemoryBackend) Test(ctx context.Context, h restic.Handle) (bool, error return false, nil } +// IsNotExist returns true if the file does not exist. +func (be *MemoryBackend) IsNotExist(err error) bool { + return errors.Cause(err) == errNotFound +} + // Save adds new Data to the backend. func (be *MemoryBackend) Save(ctx context.Context, h restic.Handle, rd io.Reader) error { if err := h.Valid(); err != nil { @@ -101,7 +108,7 @@ func (be *MemoryBackend) Load(ctx context.Context, h restic.Handle, length int, } if _, ok := be.data[h]; !ok { - return nil, errors.New("no such data") + return nil, errNotFound } buf := be.data[h] @@ -134,7 +141,7 @@ func (be *MemoryBackend) Stat(ctx context.Context, h restic.Handle) (restic.File e, ok := be.data[h] if !ok { - return restic.FileInfo{}, errors.New("no such data") + return restic.FileInfo{}, errNotFound } return restic.FileInfo{Size: int64(len(e))}, nil @@ -148,7 +155,7 @@ func (be *MemoryBackend) Remove(ctx context.Context, h restic.Handle) error { debug.Log("Remove %v", h) if _, ok := be.data[h]; !ok { - return errors.New("no such data") + return errNotFound } delete(be.data, h) diff --git a/src/restic/backend/rest/rest.go b/src/restic/backend/rest/rest.go index 4145a2a32..99dc2ba63 100644 --- a/src/restic/backend/rest/rest.go +++ b/src/restic/backend/rest/rest.go @@ -138,6 +138,23 @@ func (b *restBackend) Save(ctx context.Context, h restic.Handle, rd io.Reader) ( return nil } +// ErrIsNotExist is returned whenever the requested file does not exist on the +// server. +type ErrIsNotExist struct { + restic.Handle +} + +func (e ErrIsNotExist) Error() string { + return fmt.Sprintf("%v does not exist", e.Handle) +} + +// IsNotExist returns true if the error was caused by a non-existing file. +func (b *restBackend) IsNotExist(err error) bool { + err = errors.Cause(err) + _, ok := err.(ErrIsNotExist) + return ok +} + // Load returns a reader that yields the contents of the file at h at the // given offset. If length is nonzero, only a portion of the file is // returned. rd must be closed after use. @@ -179,6 +196,11 @@ func (b *restBackend) Load(ctx context.Context, h restic.Handle, length int, off return nil, errors.Wrap(err, "client.Do") } + if resp.StatusCode == http.StatusNotFound { + _ = resp.Body.Close() + return nil, ErrIsNotExist{h} + } + if resp.StatusCode != 200 && resp.StatusCode != 206 { _ = resp.Body.Close() return nil, errors.Errorf("unexpected HTTP response (%v): %v", resp.StatusCode, resp.Status) @@ -205,6 +227,11 @@ func (b *restBackend) Stat(ctx context.Context, h restic.Handle) (restic.FileInf return restic.FileInfo{}, errors.Wrap(err, "Close") } + if resp.StatusCode == http.StatusNotFound { + _ = resp.Body.Close() + return restic.FileInfo{}, ErrIsNotExist{h} + } + if resp.StatusCode != 200 { return restic.FileInfo{}, errors.Errorf("unexpected HTTP response (%v): %v", resp.StatusCode, resp.Status) } @@ -248,6 +275,11 @@ func (b *restBackend) Remove(ctx context.Context, h restic.Handle) error { return errors.Wrap(err, "client.Do") } + if resp.StatusCode == http.StatusNotFound { + _ = resp.Body.Close() + return ErrIsNotExist{h} + } + if resp.StatusCode != 200 { return errors.Errorf("blob not removed, server response: %v (%v)", resp.Status, resp.StatusCode) } diff --git a/src/restic/mock/backend.go b/src/restic/mock/backend.go index 10effe045..ead50efcb 100644 --- a/src/restic/mock/backend.go +++ b/src/restic/mock/backend.go @@ -10,15 +10,16 @@ import ( // Backend implements a mock backend. type Backend struct { - CloseFn func() error - SaveFn func(ctx context.Context, h restic.Handle, rd io.Reader) error - LoadFn func(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) - StatFn func(ctx context.Context, h restic.Handle) (restic.FileInfo, error) - ListFn func(ctx context.Context, t restic.FileType) <-chan string - RemoveFn func(ctx context.Context, h restic.Handle) error - TestFn func(ctx context.Context, h restic.Handle) (bool, error) - DeleteFn func(ctx context.Context) error - LocationFn func() string + CloseFn func() error + IsNotExistFn func(err error) bool + SaveFn func(ctx context.Context, h restic.Handle, rd io.Reader) error + LoadFn func(ctx context.Context, h restic.Handle, length int, offset int64) (io.ReadCloser, error) + StatFn func(ctx context.Context, h restic.Handle) (restic.FileInfo, error) + ListFn func(ctx context.Context, t restic.FileType) <-chan string + RemoveFn func(ctx context.Context, h restic.Handle) error + TestFn func(ctx context.Context, h restic.Handle) (bool, error) + DeleteFn func(ctx context.Context) error + LocationFn func() string } // Close the backend. @@ -39,6 +40,15 @@ func (m *Backend) Location() string { return m.LocationFn() } +// IsNotExist returns true if the error is caused by a missing file. +func (m *Backend) IsNotExist(err error) bool { + if m.IsNotExistFn == nil { + return false + } + + return m.IsNotExistFn(err) +} + // Save data in the backend. func (m *Backend) Save(ctx context.Context, h restic.Handle, rd io.Reader) error { if m.SaveFn == nil {