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  }