github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/content/helpers.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 content 18 19 import ( 20 "context" 21 "io" 22 "io/ioutil" 23 "math/rand" 24 "sync" 25 "time" 26 27 "github.com/containerd/containerd/errdefs" 28 "github.com/opencontainers/go-digest" 29 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 30 "github.com/pkg/errors" 31 ) 32 33 var bufPool = sync.Pool{ 34 New: func() interface{} { 35 buffer := make([]byte, 1<<20) 36 return &buffer 37 }, 38 } 39 40 // NewReader returns a io.Reader from a ReaderAt 41 func NewReader(ra ReaderAt) io.Reader { 42 rd := io.NewSectionReader(ra, 0, ra.Size()) 43 return rd 44 } 45 46 // ReadBlob retrieves the entire contents of the blob from the provider. 47 // 48 // Avoid using this for large blobs, such as layers. 49 func ReadBlob(ctx context.Context, provider Provider, desc ocispec.Descriptor) ([]byte, error) { 50 ra, err := provider.ReaderAt(ctx, desc) 51 if err != nil { 52 return nil, err 53 } 54 defer ra.Close() 55 56 p := make([]byte, ra.Size()) 57 58 n, err := ra.ReadAt(p, 0) 59 if err == io.EOF { 60 if int64(n) != ra.Size() { 61 err = io.ErrUnexpectedEOF 62 } else { 63 err = nil 64 } 65 } 66 return p, err 67 } 68 69 // WriteBlob writes data with the expected digest into the content store. If 70 // expected already exists, the method returns immediately and the reader will 71 // not be consumed. 72 // 73 // This is useful when the digest and size are known beforehand. 74 // 75 // Copy is buffered, so no need to wrap reader in buffered io. 76 func WriteBlob(ctx context.Context, cs Ingester, ref string, r io.Reader, desc ocispec.Descriptor, opts ...Opt) error { 77 cw, err := OpenWriter(ctx, cs, WithRef(ref), WithDescriptor(desc)) 78 if err != nil { 79 if !errdefs.IsAlreadyExists(err) { 80 return errors.Wrap(err, "failed to open writer") 81 } 82 83 return nil // all ready present 84 } 85 defer cw.Close() 86 87 return Copy(ctx, cw, r, desc.Size, desc.Digest, opts...) 88 } 89 90 // OpenWriter opens a new writer for the given reference, retrying if the writer 91 // is locked until the reference is available or returns an error. 92 func OpenWriter(ctx context.Context, cs Ingester, opts ...WriterOpt) (Writer, error) { 93 var ( 94 cw Writer 95 err error 96 retry = 16 97 ) 98 for { 99 cw, err = cs.Writer(ctx, opts...) 100 if err != nil { 101 if !errdefs.IsUnavailable(err) { 102 return nil, err 103 } 104 105 // TODO: Check status to determine if the writer is active, 106 // continue waiting while active, otherwise return lock 107 // error or abort. Requires asserting for an ingest manager 108 109 select { 110 case <-time.After(time.Millisecond * time.Duration(rand.Intn(retry))): 111 if retry < 2048 { 112 retry = retry << 1 113 } 114 continue 115 case <-ctx.Done(): 116 // Propagate lock error 117 return nil, err 118 } 119 120 } 121 break 122 } 123 124 return cw, err 125 } 126 127 // Copy copies data with the expected digest from the reader into the 128 // provided content store writer. This copy commits the writer. 129 // 130 // This is useful when the digest and size are known beforehand. When 131 // the size or digest is unknown, these values may be empty. 132 // 133 // Copy is buffered, so no need to wrap reader in buffered io. 134 func Copy(ctx context.Context, cw Writer, r io.Reader, size int64, expected digest.Digest, opts ...Opt) error { 135 ws, err := cw.Status() 136 if err != nil { 137 return errors.Wrap(err, "failed to get status") 138 } 139 140 if ws.Offset > 0 { 141 r, err = seekReader(r, ws.Offset, size) 142 if err != nil { 143 return errors.Wrapf(err, "unable to resume write to %v", ws.Ref) 144 } 145 } 146 147 if _, err := copyWithBuffer(cw, r); err != nil { 148 return errors.Wrap(err, "failed to copy") 149 } 150 151 if err := cw.Commit(ctx, size, expected, opts...); err != nil { 152 if !errdefs.IsAlreadyExists(err) { 153 return errors.Wrapf(err, "failed commit on ref %q", ws.Ref) 154 } 155 } 156 157 return nil 158 } 159 160 // CopyReaderAt copies to a writer from a given reader at for the given 161 // number of bytes. This copy does not commit the writer. 162 func CopyReaderAt(cw Writer, ra ReaderAt, n int64) error { 163 ws, err := cw.Status() 164 if err != nil { 165 return err 166 } 167 168 _, err = copyWithBuffer(cw, io.NewSectionReader(ra, ws.Offset, n)) 169 return err 170 } 171 172 // CopyReader copies to a writer from a given reader, returning 173 // the number of bytes copied. 174 // Note: if the writer has a non-zero offset, the total number 175 // of bytes read may be greater than those copied if the reader 176 // is not an io.Seeker. 177 // This copy does not commit the writer. 178 func CopyReader(cw Writer, r io.Reader) (int64, error) { 179 ws, err := cw.Status() 180 if err != nil { 181 return 0, errors.Wrap(err, "failed to get status") 182 } 183 184 if ws.Offset > 0 { 185 r, err = seekReader(r, ws.Offset, 0) 186 if err != nil { 187 return 0, errors.Wrapf(err, "unable to resume write to %v", ws.Ref) 188 } 189 } 190 191 return copyWithBuffer(cw, r) 192 } 193 194 // seekReader attempts to seek the reader to the given offset, either by 195 // resolving `io.Seeker`, by detecting `io.ReaderAt`, or discarding 196 // up to the given offset. 197 func seekReader(r io.Reader, offset, size int64) (io.Reader, error) { 198 // attempt to resolve r as a seeker and setup the offset. 199 seeker, ok := r.(io.Seeker) 200 if ok { 201 nn, err := seeker.Seek(offset, io.SeekStart) 202 if nn != offset { 203 return nil, errors.Wrapf(err, "failed to seek to offset %v", offset) 204 } 205 206 if err != nil { 207 return nil, err 208 } 209 210 return r, nil 211 } 212 213 // ok, let's try io.ReaderAt! 214 readerAt, ok := r.(io.ReaderAt) 215 if ok && size > offset { 216 sr := io.NewSectionReader(readerAt, offset, size) 217 return sr, nil 218 } 219 220 // well then, let's just discard up to the offset 221 n, err := copyWithBuffer(ioutil.Discard, io.LimitReader(r, offset)) 222 if err != nil { 223 return nil, errors.Wrap(err, "failed to discard to offset") 224 } 225 if n != offset { 226 return nil, errors.Errorf("unable to discard to offset") 227 } 228 229 return r, nil 230 } 231 232 func copyWithBuffer(dst io.Writer, src io.Reader) (written int64, err error) { 233 buf := bufPool.Get().(*[]byte) 234 written, err = io.CopyBuffer(dst, src, *buf) 235 bufPool.Put(buf) 236 return 237 }