podman
269 строк · 6.6 Кб
1package buildah
2
3import (
4"archive/tar"
5"errors"
6"fmt"
7"hash"
8"io"
9"sync"
10"time"
11
12digest "github.com/opencontainers/go-digest"
13)
14
15type digester interface {
16io.WriteCloser
17ContentType() string
18Digest() digest.Digest
19}
20
21// A simple digester just digests its content as-is.
22type simpleDigester struct {
23digester digest.Digester
24hasher hash.Hash
25contentType string
26}
27
28func newSimpleDigester(contentType string) digester {
29finalDigester := digest.Canonical.Digester()
30return &simpleDigester{
31digester: finalDigester,
32hasher: finalDigester.Hash(),
33contentType: contentType,
34}
35}
36
37func (s *simpleDigester) ContentType() string {
38return s.contentType
39}
40
41func (s *simpleDigester) Write(p []byte) (int, error) {
42return s.hasher.Write(p)
43}
44
45func (s *simpleDigester) Close() error {
46return nil
47}
48
49func (s *simpleDigester) Digest() digest.Digest {
50return s.digester.Digest()
51}
52
53// A tarFilterer passes a tarball through to an io.WriteCloser, potentially
54// modifying headers as it goes.
55type tarFilterer struct {
56wg sync.WaitGroup
57pipeWriter *io.PipeWriter
58closedLock sync.Mutex
59closed bool
60err error
61}
62
63func (t *tarFilterer) Write(p []byte) (int, error) {
64return t.pipeWriter.Write(p)
65}
66
67func (t *tarFilterer) Close() error {
68t.closedLock.Lock()
69if t.closed {
70t.closedLock.Unlock()
71return errors.New("tar filter is already closed")
72}
73t.closed = true
74t.closedLock.Unlock()
75err := t.pipeWriter.Close()
76t.wg.Wait()
77if err != nil {
78return fmt.Errorf("closing filter pipe: %w", err)
79}
80return t.err
81}
82
83// newTarFilterer passes one or more tar archives through to an io.WriteCloser
84// as a single archive, potentially calling filter to modify headers and
85// contents as it goes.
86//
87// Note: if "filter" indicates that a given item should be skipped, there is no
88// guarantee that there will not be a subsequent item of type TypeLink, which
89// is a hard link, which points to the skipped item as the link target.
90func newTarFilterer(writeCloser io.WriteCloser, filter func(hdr *tar.Header) (skip, replaceContents bool, replacementContents io.Reader)) io.WriteCloser {
91pipeReader, pipeWriter := io.Pipe()
92tarWriter := tar.NewWriter(writeCloser)
93filterer := &tarFilterer{
94pipeWriter: pipeWriter,
95}
96filterer.wg.Add(1)
97go func() {
98filterer.closedLock.Lock()
99closed := filterer.closed
100filterer.closedLock.Unlock()
101for !closed {
102tarReader := tar.NewReader(pipeReader)
103hdr, err := tarReader.Next()
104for err == nil {
105var skip, replaceContents bool
106var replacementContents io.Reader
107if filter != nil {
108skip, replaceContents, replacementContents = filter(hdr)
109}
110if !skip {
111err = tarWriter.WriteHeader(hdr)
112if err != nil {
113err = fmt.Errorf("filtering tar header for %q: %w", hdr.Name, err)
114break
115}
116if hdr.Size != 0 {
117var n int64
118var copyErr error
119if replaceContents {
120n, copyErr = io.CopyN(tarWriter, replacementContents, hdr.Size)
121} else {
122n, copyErr = io.Copy(tarWriter, tarReader)
123}
124if copyErr != nil {
125err = fmt.Errorf("copying content for %q: %w", hdr.Name, copyErr)
126break
127}
128if n != hdr.Size {
129err = fmt.Errorf("filtering content for %q: expected %d bytes, got %d bytes", hdr.Name, hdr.Size, n)
130break
131}
132}
133}
134hdr, err = tarReader.Next()
135}
136if err != io.EOF {
137filterer.err = fmt.Errorf("reading tar archive: %w", err)
138break
139}
140filterer.closedLock.Lock()
141closed = filterer.closed
142filterer.closedLock.Unlock()
143}
144pipeReader.Close()
145tarWriter.Close()
146writeCloser.Close()
147filterer.wg.Done()
148}()
149return filterer
150}
151
152// A tar digester digests an archive, modifying the headers it digests by
153// calling a specified function to potentially modify the header that it's
154// about to write.
155type tarDigester struct {
156isOpen bool
157nested digester
158tarFilterer io.WriteCloser
159}
160
161func modifyTarHeaderForDigesting(hdr *tar.Header) (skip, replaceContents bool, replacementContents io.Reader) {
162zeroTime := time.Time{}
163hdr.ModTime = zeroTime
164hdr.AccessTime = zeroTime
165hdr.ChangeTime = zeroTime
166return false, false, nil
167}
168
169func newTarDigester(contentType string) digester {
170nested := newSimpleDigester(contentType)
171digester := &tarDigester{
172isOpen: true,
173nested: nested,
174tarFilterer: newTarFilterer(nested, modifyTarHeaderForDigesting),
175}
176return digester
177}
178
179func (t *tarDigester) ContentType() string {
180return t.nested.ContentType()
181}
182
183func (t *tarDigester) Digest() digest.Digest {
184return t.nested.Digest()
185}
186
187func (t *tarDigester) Write(p []byte) (int, error) {
188return t.tarFilterer.Write(p)
189}
190
191func (t *tarDigester) Close() error {
192if t.isOpen {
193t.isOpen = false
194return t.tarFilterer.Close()
195}
196return nil
197}
198
199// CompositeDigester can compute a digest over multiple items.
200type CompositeDigester struct {
201digesters []digester
202closer io.Closer
203}
204
205// closeOpenDigester closes an open sub-digester, if we have one.
206func (c *CompositeDigester) closeOpenDigester() {
207if c.closer != nil {
208c.closer.Close()
209c.closer = nil
210}
211}
212
213// Restart clears all state, so that the composite digester can start over.
214func (c *CompositeDigester) Restart() {
215c.closeOpenDigester()
216c.digesters = nil
217}
218
219// Start starts recording the digest for a new item ("", "file", or "dir").
220// The caller should call Hash() immediately after to retrieve the new
221// io.WriteCloser.
222func (c *CompositeDigester) Start(contentType string) {
223c.closeOpenDigester()
224switch contentType {
225case "":
226c.digesters = append(c.digesters, newSimpleDigester(""))
227case "file", "dir":
228digester := newTarDigester(contentType)
229c.closer = digester
230c.digesters = append(c.digesters, digester)
231default:
232panic(fmt.Sprintf(`unrecognized content type: expected "", "file", or "dir", got %q`, contentType))
233}
234}
235
236// Hash returns the hasher for the current item.
237func (c *CompositeDigester) Hash() io.WriteCloser {
238num := len(c.digesters)
239if num == 0 {
240return nil
241}
242return c.digesters[num-1]
243}
244
245// Digest returns the content type and a composite digest over everything
246// that's been digested.
247func (c *CompositeDigester) Digest() (string, digest.Digest) {
248c.closeOpenDigester()
249num := len(c.digesters)
250switch num {
251case 0:
252return "", ""
253case 1:
254return c.digesters[0].ContentType(), c.digesters[0].Digest()
255default:
256content := ""
257for i, digester := range c.digesters {
258if i > 0 {
259content += ","
260}
261contentType := digester.ContentType()
262if contentType != "" {
263contentType += ":"
264}
265content += contentType + digester.Digest().Encoded()
266}
267return "multi", digest.Canonical.FromString(content)
268}
269}
270