github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/remotes/docker/httpreadseeker.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package docker 18 19 import ( 20 "bytes" 21 "io" 22 "io/ioutil" 23 24 "github.com/containerd/containerd/errdefs" 25 "github.com/containerd/containerd/log" 26 "github.com/pkg/errors" 27 ) 28 29 type httpReadSeeker struct { 30 size int64 31 offset int64 32 rc io.ReadCloser 33 open func(offset int64) (io.ReadCloser, error) 34 closed bool 35 } 36 37 func newHTTPReadSeeker(size int64, open func(offset int64) (io.ReadCloser, error)) (io.ReadCloser, error) { 38 return &httpReadSeeker{ 39 size: size, 40 open: open, 41 }, nil 42 } 43 44 func (hrs *httpReadSeeker) Read(p []byte) (n int, err error) { 45 if hrs.closed { 46 return 0, io.EOF 47 } 48 49 rd, err := hrs.reader() 50 if err != nil { 51 return 0, err 52 } 53 54 n, err = rd.Read(p) 55 hrs.offset += int64(n) 56 return 57 } 58 59 func (hrs *httpReadSeeker) Close() error { 60 if hrs.closed { 61 return nil 62 } 63 hrs.closed = true 64 if hrs.rc != nil { 65 return hrs.rc.Close() 66 } 67 68 return nil 69 } 70 71 func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) { 72 if hrs.closed { 73 return 0, errors.Wrap(errdefs.ErrUnavailable, "Fetcher.Seek: closed") 74 } 75 76 abs := hrs.offset 77 switch whence { 78 case io.SeekStart: 79 abs = offset 80 case io.SeekCurrent: 81 abs += offset 82 case io.SeekEnd: 83 if hrs.size == -1 { 84 return 0, errors.Wrap(errdefs.ErrUnavailable, "Fetcher.Seek: unknown size, cannot seek from end") 85 } 86 abs = hrs.size + offset 87 default: 88 return 0, errors.Wrap(errdefs.ErrInvalidArgument, "Fetcher.Seek: invalid whence") 89 } 90 91 if abs < 0 { 92 return 0, errors.Wrapf(errdefs.ErrInvalidArgument, "Fetcher.Seek: negative offset") 93 } 94 95 if abs != hrs.offset { 96 if hrs.rc != nil { 97 if err := hrs.rc.Close(); err != nil { 98 log.L.WithError(err).Errorf("Fetcher.Seek: failed to close ReadCloser") 99 } 100 101 hrs.rc = nil 102 } 103 104 hrs.offset = abs 105 } 106 107 return hrs.offset, nil 108 } 109 110 func (hrs *httpReadSeeker) reader() (io.Reader, error) { 111 if hrs.rc != nil { 112 return hrs.rc, nil 113 } 114 115 if hrs.size == -1 || hrs.offset < hrs.size { 116 // only try to reopen the body request if we are seeking to a value 117 // less than the actual size. 118 if hrs.open == nil { 119 return nil, errors.Wrapf(errdefs.ErrNotImplemented, "cannot open") 120 } 121 122 rc, err := hrs.open(hrs.offset) 123 if err != nil { 124 return nil, errors.Wrapf(err, "httpReaderSeeker: failed open") 125 } 126 127 if hrs.rc != nil { 128 if err := hrs.rc.Close(); err != nil { 129 log.L.WithError(err).Errorf("httpReadSeeker: failed to close ReadCloser") 130 } 131 } 132 hrs.rc = rc 133 } else { 134 // There is an edge case here where offset == size of the content. If 135 // we seek, we will probably get an error for content that cannot be 136 // sought (?). In that case, we should err on committing the content, 137 // as the length is already satisfied but we just return the empty 138 // reader instead. 139 140 hrs.rc = ioutil.NopCloser(bytes.NewReader([]byte{})) 141 } 142 143 return hrs.rc, nil 144 }