From 6a948d5afd8c17475aa791c440bdc4472fef4884 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Fri, 16 Jun 2017 10:55:04 +0200 Subject: [PATCH] s3: Fix backend for Google Cloud Storage --- src/restic/backend/s3/s3.go | 63 +++++++++++++++++++++++++------- src/restic/backend/test/tests.go | 10 ++--- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/src/restic/backend/s3/s3.go b/src/restic/backend/s3/s3.go index 6271910f5..abb47fa69 100644 --- a/src/restic/backend/s3/s3.go +++ b/src/restic/backend/s3/s3.go @@ -4,8 +4,6 @@ import ( "context" "fmt" "io" - "io/ioutil" - "net/url" "os" "path" "restic" @@ -16,7 +14,6 @@ import ( "restic/errors" "github.com/minio/minio-go" - "github.com/minio/minio-go/pkg/s3utils" "restic/debug" ) @@ -166,17 +163,51 @@ func (be *Backend) Path() string { return be.cfg.Prefix } -func (be *Backend) isGoogleCloudStorage() bool { - scheme := "https://" - if be.cfg.UseHTTP { - scheme = "http://" - } - url, err := url.Parse(scheme + be.cfg.Endpoint) +// nopCloserFile wraps *os.File and overwrites the Close() method with method +// that does nothing. In addition, the method Len() is implemented, which +// returns the size of the file (filesize - current offset). +type nopCloserFile struct { + *os.File +} + +func (f nopCloserFile) Close() error { + debug.Log("prevented Close()") + return nil +} + +// Len returns the remaining length of the file (filesize - current offset). +func (f nopCloserFile) Len() int { + debug.Log("Len() called") + fi, err := f.Stat() if err != nil { panic(err) } - return s3utils.IsGoogleEndpoint(*url) + pos, err := f.Seek(0, io.SeekCurrent) + if err != nil { + panic(err) + } + + size := fi.Size() - pos + debug.Log("returning file size %v", size) + return int(size) +} + +type lenner interface { + Len() int + io.Reader +} + +// nopCloserLenner wraps a lenner and overwrites the Close() method with method +// that does nothing. In addition, the method Size() is implemented, which +// returns the size of the file (filesize - current offset). +type nopCloserLenner struct { + lenner +} + +func (f *nopCloserLenner) Close() error { + debug.Log("prevented Close()") + return nil } // Save stores data in the backend at the handle. @@ -196,9 +227,15 @@ func (be *Backend) Save(ctx context.Context, h restic.Handle, rd io.Reader) (err return errors.New("key already exists") } - // prevent GCS from closing the file - if be.isGoogleCloudStorage() { - rd = ioutil.NopCloser(rd) + // prevent the HTTP client from closing a file + if f, ok := rd.(*os.File); ok { + debug.Log("reader is %#T, using nopCloserFile{}", rd) + rd = nopCloserFile{f} + } else if l, ok := rd.(lenner); ok { + debug.Log("reader is %#T, using nopCloserLenner{}", rd) + rd = nopCloserLenner{l} + } else { + debug.Log("reader is %#T, no specific workaround enabled", rd) } be.sem.GetToken() diff --git a/src/restic/backend/test/tests.go b/src/restic/backend/test/tests.go index 4447064cf..d6971a9e7 100644 --- a/src/restic/backend/test/tests.go +++ b/src/restic/backend/test/tests.go @@ -241,8 +241,8 @@ func (s *Suite) TestLoad(t *testing.T) { type errorCloser struct { io.Reader - size int64 - t testing.TB + l int + t testing.TB } func (ec errorCloser) Close() error { @@ -250,8 +250,8 @@ func (ec errorCloser) Close() error { return errors.New("forbidden method close was called") } -func (ec errorCloser) Size() int64 { - return ec.size +func (ec errorCloser) Len() int { + return ec.l } // TestSave tests saving data in the backend. @@ -325,7 +325,7 @@ func (s *Suite) TestSave(t *testing.T) { // wrap the tempfile in an errorCloser, so we can detect if the backend // closes the reader - err = b.Save(context.TODO(), h, errorCloser{t: t, size: int64(length), Reader: tmpfile}) + err = b.Save(context.TODO(), h, errorCloser{t: t, l: length, Reader: tmpfile}) if err != nil { t.Fatal(err) }