github.com/containerd/Containerd@v1.4.13/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  }