| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | package buildah | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	"archive/tar" | 
					
						
							| 
									
										
										
										
											2022-07-06 17:14:06 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | 	"hash" | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	"io" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2020-07-15 04:31:00 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	digest "github.com/opencontainers/go-digest" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 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 | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 	closedLock sync.Mutex | 
					
						
							|  |  |  | 	closed     bool | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	err        error | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *tarFilterer) Write(p []byte) (int, error) { | 
					
						
							|  |  |  | 	return t.pipeWriter.Write(p) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (t *tarFilterer) Close() error { | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 	t.closedLock.Lock() | 
					
						
							|  |  |  | 	if t.closed { | 
					
						
							|  |  |  | 		t.closedLock.Unlock() | 
					
						
							| 
									
										
										
										
											2022-07-06 17:14:06 +08:00
										 |  |  | 		return errors.New("tar filter is already closed") | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	t.closed = true | 
					
						
							|  |  |  | 	t.closedLock.Unlock() | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	err := t.pipeWriter.Close() | 
					
						
							|  |  |  | 	t.wg.Wait() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-18 18:36:08 +08:00
										 |  |  | 		return fmt.Errorf("closing filter pipe: %w", err) | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return t.err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | // 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.
 | 
					
						
							| 
									
										
										
										
											2019-07-25 22:10:03 +08:00
										 |  |  | //
 | 
					
						
							|  |  |  | // 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.
 | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | func newTarFilterer(writeCloser io.WriteCloser, filter func(hdr *tar.Header) (skip, replaceContents bool, replacementContents io.Reader)) io.WriteCloser { | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	pipeReader, pipeWriter := io.Pipe() | 
					
						
							|  |  |  | 	tarWriter := tar.NewWriter(writeCloser) | 
					
						
							|  |  |  | 	filterer := &tarFilterer{ | 
					
						
							|  |  |  | 		pipeWriter: pipeWriter, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	filterer.wg.Add(1) | 
					
						
							|  |  |  | 	go func() { | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 		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) | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 				if !skip { | 
					
						
							|  |  |  | 					err = tarWriter.WriteHeader(hdr) | 
					
						
							|  |  |  | 					if err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-18 18:36:08 +08:00
										 |  |  | 						err = fmt.Errorf("filtering tar header for %q: %w", hdr.Name, err) | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 						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 { | 
					
						
							| 
									
										
										
										
											2022-09-18 18:36:08 +08:00
										 |  |  | 							err = fmt.Errorf("copying content for %q: %w", hdr.Name, copyErr) | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 							break | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						if n != hdr.Size { | 
					
						
							| 
									
										
										
										
											2022-09-18 18:36:08 +08:00
										 |  |  | 							err = fmt.Errorf("filtering content for %q: expected %d bytes, got %d bytes", hdr.Name, hdr.Size, n) | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 							break | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 				hdr, err = tarReader.Next() | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 			if err != io.EOF { | 
					
						
							| 
									
										
										
										
											2022-09-18 18:36:08 +08:00
										 |  |  | 				filterer.err = fmt.Errorf("reading tar archive: %w", err) | 
					
						
							| 
									
										
										
										
											2020-07-28 23:11:18 +08:00
										 |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			filterer.closedLock.Lock() | 
					
						
							|  |  |  | 			closed = filterer.closed | 
					
						
							|  |  |  | 			filterer.closedLock.Unlock() | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-05-01 03:31:01 +08:00
										 |  |  | 		err1 := tarWriter.Close() | 
					
						
							|  |  |  | 		err := writeCloser.Close() | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			err = err1 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		pipeReader.CloseWithError(err) | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 		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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-15 04:31:00 +08:00
										 |  |  | 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | func newTarDigester(contentType string) digester { | 
					
						
							|  |  |  | 	nested := newSimpleDigester(contentType) | 
					
						
							|  |  |  | 	digester := &tarDigester{ | 
					
						
							|  |  |  | 		isOpen:      true, | 
					
						
							|  |  |  | 		nested:      nested, | 
					
						
							| 
									
										
										
										
											2020-07-15 04:31:00 +08:00
										 |  |  | 		tarFilterer: newTarFilterer(nested, modifyTarHeaderForDigesting), | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	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 | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CompositeDigester can compute a digest over multiple items.
 | 
					
						
							|  |  |  | type CompositeDigester struct { | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	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 | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Restart clears all state, so that the composite digester can start over.
 | 
					
						
							|  |  |  | func (c *CompositeDigester) Restart() { | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	c.closeOpenDigester() | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | 	c.digesters = nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | // 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)) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Hash returns the hasher for the current item.
 | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | func (c *CompositeDigester) Hash() io.WriteCloser { | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | 	num := len(c.digesters) | 
					
						
							|  |  |  | 	if num == 0 { | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	return c.digesters[num-1] | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | // Digest returns the content type and a composite digest over everything
 | 
					
						
							|  |  |  | // that's been digested.
 | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | func (c *CompositeDigester) Digest() (string, digest.Digest) { | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 	c.closeOpenDigester() | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | 	num := len(c.digesters) | 
					
						
							|  |  |  | 	switch num { | 
					
						
							|  |  |  | 	case 0: | 
					
						
							|  |  |  | 		return "", "" | 
					
						
							|  |  |  | 	case 1: | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 		return c.digesters[0].ContentType(), c.digesters[0].Digest() | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		content := "" | 
					
						
							|  |  |  | 		for i, digester := range c.digesters { | 
					
						
							|  |  |  | 			if i > 0 { | 
					
						
							|  |  |  | 				content += "," | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 			contentType := digester.ContentType() | 
					
						
							|  |  |  | 			if contentType != "" { | 
					
						
							|  |  |  | 				contentType += ":" | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-07-15 04:22:49 +08:00
										 |  |  | 			content += contentType + digester.Digest().Encoded() | 
					
						
							| 
									
										
										
										
											2019-08-10 04:21:24 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		return "multi", digest.Canonical.FromString(content) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |