helpers.go 7.2 KB

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