Files
Daniel J Walsh 08cc87636e Vendor in containers/buildah 1.16.1
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
2020-09-11 13:37:37 -04:00

270 lines
6.6 KiB
Go

package buildah
import (
"archive/tar"
"fmt"
"hash"
"io"
"sync"
"time"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
type digester interface {
io.WriteCloser
ContentType() string
Digest() digest.Digest
}
// A simple digester just digests its content as-is.
type simpleDigester struct {
digester digest.Digester
hasher hash.Hash
contentType string
}
func newSimpleDigester(contentType string) digester {
finalDigester := digest.Canonical.Digester()
return &simpleDigester{
digester: finalDigester,
hasher: finalDigester.Hash(),
contentType: contentType,
}
}
func (s *simpleDigester) ContentType() string {
return s.contentType
}
func (s *simpleDigester) Write(p []byte) (int, error) {
return s.hasher.Write(p)
}
func (s *simpleDigester) Close() error {
return nil
}
func (s *simpleDigester) Digest() digest.Digest {
return s.digester.Digest()
}
// A tarFilterer passes a tarball through to an io.WriteCloser, potentially
// modifying headers as it goes.
type tarFilterer struct {
wg sync.WaitGroup
pipeWriter *io.PipeWriter
closedLock sync.Mutex
closed bool
err error
}
func (t *tarFilterer) Write(p []byte) (int, error) {
return t.pipeWriter.Write(p)
}
func (t *tarFilterer) Close() error {
t.closedLock.Lock()
if t.closed {
t.closedLock.Unlock()
return errors.Errorf("tar filter is already closed")
}
t.closed = true
t.closedLock.Unlock()
err := t.pipeWriter.Close()
t.wg.Wait()
if err != nil {
return errors.Wrapf(err, "error closing filter pipe")
}
return t.err
}
// newTarFilterer passes one or more tar archives through to an io.WriteCloser
// as a single archive, potentially calling filter to modify headers and
// contents as it goes.
//
// Note: if "filter" indicates that a given item should be skipped, there is no
// guarantee that there will not be a subsequent item of type TypeLink, which
// is a hard link, which points to the skipped item as the link target.
func newTarFilterer(writeCloser io.WriteCloser, filter func(hdr *tar.Header) (skip, replaceContents bool, replacementContents io.Reader)) io.WriteCloser {
pipeReader, pipeWriter := io.Pipe()
tarWriter := tar.NewWriter(writeCloser)
filterer := &tarFilterer{
pipeWriter: pipeWriter,
}
filterer.wg.Add(1)
go func() {
filterer.closedLock.Lock()
closed := filterer.closed
filterer.closedLock.Unlock()
for !closed {
tarReader := tar.NewReader(pipeReader)
hdr, err := tarReader.Next()
for err == nil {
var skip, replaceContents bool
var replacementContents io.Reader
if filter != nil {
skip, replaceContents, replacementContents = filter(hdr)
}
if !skip {
err = tarWriter.WriteHeader(hdr)
if err != nil {
err = errors.Wrapf(err, "error filtering tar header for %q", hdr.Name)
break
}
if hdr.Size != 0 {
var n int64
var copyErr error
if replaceContents {
n, copyErr = io.CopyN(tarWriter, replacementContents, hdr.Size)
} else {
n, copyErr = io.Copy(tarWriter, tarReader)
}
if copyErr != nil {
err = errors.Wrapf(copyErr, "error copying content for %q", hdr.Name)
break
}
if n != hdr.Size {
err = errors.Errorf("error filtering content for %q: expected %d bytes, got %d bytes", hdr.Name, hdr.Size, n)
break
}
}
}
hdr, err = tarReader.Next()
}
if err != io.EOF {
filterer.err = errors.Wrapf(err, "error reading tar archive")
break
}
filterer.closedLock.Lock()
closed = filterer.closed
filterer.closedLock.Unlock()
}
pipeReader.Close()
tarWriter.Close()
writeCloser.Close()
filterer.wg.Done()
}()
return filterer
}
// A tar digester digests an archive, modifying the headers it digests by
// calling a specified function to potentially modify the header that it's
// about to write.
type tarDigester struct {
isOpen bool
nested digester
tarFilterer io.WriteCloser
}
func modifyTarHeaderForDigesting(hdr *tar.Header) (skip, replaceContents bool, replacementContents io.Reader) {
zeroTime := time.Time{}
hdr.ModTime = zeroTime
hdr.AccessTime = zeroTime
hdr.ChangeTime = zeroTime
return false, false, nil
}
func newTarDigester(contentType string) digester {
nested := newSimpleDigester(contentType)
digester := &tarDigester{
isOpen: true,
nested: nested,
tarFilterer: newTarFilterer(nested, modifyTarHeaderForDigesting),
}
return digester
}
func (t *tarDigester) ContentType() string {
return t.nested.ContentType()
}
func (t *tarDigester) Digest() digest.Digest {
return t.nested.Digest()
}
func (t *tarDigester) Write(p []byte) (int, error) {
return t.tarFilterer.Write(p)
}
func (t *tarDigester) Close() error {
if t.isOpen {
t.isOpen = false
return t.tarFilterer.Close()
}
return nil
}
// CompositeDigester can compute a digest over multiple items.
type CompositeDigester struct {
digesters []digester
closer io.Closer
}
// closeOpenDigester closes an open sub-digester, if we have one.
func (c *CompositeDigester) closeOpenDigester() {
if c.closer != nil {
c.closer.Close()
c.closer = nil
}
}
// Restart clears all state, so that the composite digester can start over.
func (c *CompositeDigester) Restart() {
c.closeOpenDigester()
c.digesters = nil
}
// Start starts recording the digest for a new item ("", "file", or "dir").
// The caller should call Hash() immediately after to retrieve the new
// io.WriteCloser.
func (c *CompositeDigester) Start(contentType string) {
c.closeOpenDigester()
switch contentType {
case "":
c.digesters = append(c.digesters, newSimpleDigester(""))
case "file", "dir":
digester := newTarDigester(contentType)
c.closer = digester
c.digesters = append(c.digesters, digester)
default:
panic(fmt.Sprintf(`unrecognized content type: expected "", "file", or "dir", got %q`, contentType))
}
}
// Hash returns the hasher for the current item.
func (c *CompositeDigester) Hash() io.WriteCloser {
num := len(c.digesters)
if num == 0 {
return nil
}
return c.digesters[num-1]
}
// Digest returns the content type and a composite digest over everything
// that's been digested.
func (c *CompositeDigester) Digest() (string, digest.Digest) {
c.closeOpenDigester()
num := len(c.digesters)
switch num {
case 0:
return "", ""
case 1:
return c.digesters[0].ContentType(), c.digesters[0].Digest()
default:
content := ""
for i, digester := range c.digesters {
if i > 0 {
content += ","
}
contentType := digester.ContentType()
if contentType != "" {
contentType += ":"
}
content += contentType + digester.Digest().Encoded()
}
return "multi", digest.Canonical.FromString(content)
}
}