diff --git a/src/restic/backend/local/backend_test.go b/src/restic/backend/local/backend_test.go index 8607f01b7..8954dc83d 100644 --- a/src/restic/backend/local/backend_test.go +++ b/src/restic/backend/local/backend_test.go @@ -51,6 +51,13 @@ func TestLocalBackendLoad(t *testing.T) { test.TestLoad(t) } +func TestLocalBackendLoadNegativeOffset(t *testing.T) { + if SkipMessage != "" { + t.Skip(SkipMessage) + } + test.TestLoadNegativeOffset(t) +} + func TestLocalBackendSave(t *testing.T) { if SkipMessage != "" { t.Skip(SkipMessage) diff --git a/src/restic/backend/mem/backend_test.go b/src/restic/backend/mem/backend_test.go index 13e95f115..6bf19580f 100644 --- a/src/restic/backend/mem/backend_test.go +++ b/src/restic/backend/mem/backend_test.go @@ -51,6 +51,13 @@ func TestMemBackendLoad(t *testing.T) { test.TestLoad(t) } +func TestMemBackendLoadNegativeOffset(t *testing.T) { + if SkipMessage != "" { + t.Skip(SkipMessage) + } + test.TestLoadNegativeOffset(t) +} + func TestMemBackendSave(t *testing.T) { if SkipMessage != "" { t.Skip(SkipMessage) diff --git a/src/restic/backend/mem/mem_backend.go b/src/restic/backend/mem/mem_backend.go index 961997ae7..5682d4915 100644 --- a/src/restic/backend/mem/mem_backend.go +++ b/src/restic/backend/mem/mem_backend.go @@ -120,7 +120,7 @@ func memLoad(be *MemoryBackend, h backend.Handle, p []byte, off int64) (int, err case off > int64(len(buf)): return 0, errors.New("offset beyond end of file") case off < -int64(len(buf)): - return 0, errors.New("offset beyond beginning of file") + off = 0 case off < 0: off = int64(len(buf)) + off } diff --git a/src/restic/backend/rest/backend_test.go b/src/restic/backend/rest/backend_test.go index 4274bfcb1..9605396d6 100644 --- a/src/restic/backend/rest/backend_test.go +++ b/src/restic/backend/rest/backend_test.go @@ -51,6 +51,13 @@ func TestRestBackendLoad(t *testing.T) { test.TestLoad(t) } +func TestRestBackendLoadNegativeOffset(t *testing.T) { + if SkipMessage != "" { + t.Skip(SkipMessage) + } + test.TestLoadNegativeOffset(t) +} + func TestRestBackendSave(t *testing.T) { if SkipMessage != "" { t.Skip(SkipMessage) diff --git a/src/restic/backend/rest/rest.go b/src/restic/backend/rest/rest.go index 00fe0192b..125331d78 100644 --- a/src/restic/backend/rest/rest.go +++ b/src/restic/backend/rest/rest.go @@ -82,11 +82,11 @@ func (b *restBackend) Load(h backend.Handle, p []byte, off int64) (n int, err er return 0, err } - if off > -info.Size { - return 0, errors.New("offset before beginning of file") + if -off > info.Size { + off = 0 + } else { + off = info.Size + off } - - off = info.Size + off } req, err := http.NewRequest("GET", restPath(b.url, h), nil) diff --git a/src/restic/backend/s3/backend_test.go b/src/restic/backend/s3/backend_test.go index 82eca2631..9fb4dd3fa 100644 --- a/src/restic/backend/s3/backend_test.go +++ b/src/restic/backend/s3/backend_test.go @@ -51,6 +51,13 @@ func TestS3BackendLoad(t *testing.T) { test.TestLoad(t) } +func TestS3BackendLoadNegativeOffset(t *testing.T) { + if SkipMessage != "" { + t.Skip(SkipMessage) + } + test.TestLoadNegativeOffset(t) +} + func TestS3BackendSave(t *testing.T) { if SkipMessage != "" { t.Skip(SkipMessage) diff --git a/src/restic/backend/sftp/backend_test.go b/src/restic/backend/sftp/backend_test.go index a812f8cd0..c28dd8c99 100644 --- a/src/restic/backend/sftp/backend_test.go +++ b/src/restic/backend/sftp/backend_test.go @@ -51,6 +51,13 @@ func TestSftpBackendLoad(t *testing.T) { test.TestLoad(t) } +func TestSftpBackendLoadNegativeOffset(t *testing.T) { + if SkipMessage != "" { + t.Skip(SkipMessage) + } + test.TestLoadNegativeOffset(t) +} + func TestSftpBackendSave(t *testing.T) { if SkipMessage != "" { t.Skip(SkipMessage) diff --git a/src/restic/backend/test/backend_test.go b/src/restic/backend/test/backend_test.go index b495ce663..c577092fb 100644 --- a/src/restic/backend/test/backend_test.go +++ b/src/restic/backend/test/backend_test.go @@ -51,6 +51,13 @@ func TestTestBackendLoad(t *testing.T) { test.TestLoad(t) } +func TestTestBackendLoadNegativeOffset(t *testing.T) { + if SkipMessage != "" { + t.Skip(SkipMessage) + } + test.TestLoadNegativeOffset(t) +} + func TestTestBackendSave(t *testing.T) { if SkipMessage != "" { t.Skip(SkipMessage) diff --git a/src/restic/backend/test/tests.go b/src/restic/backend/test/tests.go index ef0efd77a..e030e6bbf 100644 --- a/src/restic/backend/test/tests.go +++ b/src/restic/backend/test/tests.go @@ -220,9 +220,59 @@ func TestLoad(t testing.TB) { buf := make([]byte, l) n, err := b.Load(handle, buf, int64(o)) - // if we requested data beyond the end of the file, ignore + // if we requested data beyond the end of the file, require // ErrUnexpectedEOF error - if l > len(d) && err == io.ErrUnexpectedEOF { + if l > len(d) { + if err != io.ErrUnexpectedEOF { + t.Errorf("Load(%d, %d) did not return io.ErrUnexpectedEOF", len(buf), int64(o)) + } + err = nil + buf = buf[:len(d)] + } + + if err != nil { + t.Errorf("Load(%d, %d): unexpected error: %v", len(buf), int64(o), err) + continue + } + + if n != len(buf) { + t.Errorf("Load(%d, %d): wrong length returned, want %d, got %d", + len(buf), int64(o), len(buf), n) + continue + } + + buf = buf[:n] + if !bytes.Equal(buf, d) { + t.Errorf("Load(%d, %d) returned wrong bytes", len(buf), int64(o)) + continue + } + } + + // test with negative offset + for i := 0; i < 50; i++ { + l := rand.Intn(length + 2000) + o := rand.Intn(length + 2000) + + d := data + if o < len(d) { + d = d[len(d)-o:] + } else { + o = 0 + } + + if l > 0 && l < len(d) { + d = d[:l] + } + + buf := make([]byte, l) + n, err := b.Load(handle, buf, -int64(o)) + + // if we requested data beyond the end of the file, require + // ErrUnexpectedEOF error + if l > len(d) { + if err != io.ErrUnexpectedEOF { + t.Errorf("Load(%d, %d) did not return io.ErrUnexpectedEOF", len(buf), int64(o)) + } err = nil buf = buf[:len(d)] } @@ -259,6 +309,61 @@ func TestLoad(t testing.TB) { OK(t, b.Remove(backend.Data, id.String())) } +// TestLoadNegativeOffset tests the backend's Load function with negative offsets. +func TestLoadNegativeOffset(t testing.TB) { + b := open(t) + defer close(t) + + length := rand.Intn(1<<24) + 2000 + + data := Random(23, length) + id := backend.Hash(data) + + handle := backend.Handle{Type: backend.Data, Name: id.String()} + err := b.Save(handle, data) + if err != nil { + t.Fatalf("Save() error: %v", err) + } + + // test normal reads + for i := 0; i < 50; i++ { + l := rand.Intn(length + 2000) + o := -rand.Intn(length + 2000) + + buf := make([]byte, l) + n, err := b.Load(handle, buf, int64(o)) + t.Logf("data %v, load(%v, %v) -> %v %v", + len(data), len(buf), o, n, err) + + // if we requested data beyond the end of the file, require + // ErrUnexpectedEOF error + if len(buf) > -o { + if err != io.ErrUnexpectedEOF { + t.Errorf("Load(%d, %d) did not return io.ErrUnexpectedEOF", len(buf), o) + } + err = nil + buf = buf[:-o] + } + + if err != nil { + t.Errorf("Load(%d, %d) returned error: %v", len(buf), o, err) + } + + if n != len(buf) { + t.Errorf("Load(%d, %d) returned short read, only got %d bytes", len(buf), o, n) + } + + p := len(data) + o + if !bytes.Equal(buf, data[p:p+len(buf)]) { + t.Errorf("Load(%d, %d) returned wrong bytes", len(buf), o) + continue + } + + } + + OK(t, b.Remove(backend.Data, id.String())) +} + // TestSave tests saving data in the backend. func TestSave(t testing.TB) { b := open(t)