podman

Форк
0
269 строк · 6.6 Кб
1
package buildah
2

3
import (
4
	"archive/tar"
5
	"errors"
6
	"fmt"
7
	"hash"
8
	"io"
9
	"sync"
10
	"time"
11

12
	digest "github.com/opencontainers/go-digest"
13
)
14

15
type digester interface {
16
	io.WriteCloser
17
	ContentType() string
18
	Digest() digest.Digest
19
}
20

21
// A simple digester just digests its content as-is.
22
type simpleDigester struct {
23
	digester    digest.Digester
24
	hasher      hash.Hash
25
	contentType string
26
}
27

28
func newSimpleDigester(contentType string) digester {
29
	finalDigester := digest.Canonical.Digester()
30
	return &simpleDigester{
31
		digester:    finalDigester,
32
		hasher:      finalDigester.Hash(),
33
		contentType: contentType,
34
	}
35
}
36

37
func (s *simpleDigester) ContentType() string {
38
	return s.contentType
39
}
40

41
func (s *simpleDigester) Write(p []byte) (int, error) {
42
	return s.hasher.Write(p)
43
}
44

45
func (s *simpleDigester) Close() error {
46
	return nil
47
}
48

49
func (s *simpleDigester) Digest() digest.Digest {
50
	return s.digester.Digest()
51
}
52

53
// A tarFilterer passes a tarball through to an io.WriteCloser, potentially
54
// modifying headers as it goes.
55
type tarFilterer struct {
56
	wg         sync.WaitGroup
57
	pipeWriter *io.PipeWriter
58
	closedLock sync.Mutex
59
	closed     bool
60
	err        error
61
}
62

63
func (t *tarFilterer) Write(p []byte) (int, error) {
64
	return t.pipeWriter.Write(p)
65
}
66

67
func (t *tarFilterer) Close() error {
68
	t.closedLock.Lock()
69
	if t.closed {
70
		t.closedLock.Unlock()
71
		return errors.New("tar filter is already closed")
72
	}
73
	t.closed = true
74
	t.closedLock.Unlock()
75
	err := t.pipeWriter.Close()
76
	t.wg.Wait()
77
	if err != nil {
78
		return fmt.Errorf("closing filter pipe: %w", err)
79
	}
80
	return 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.
90
func newTarFilterer(writeCloser io.WriteCloser, filter func(hdr *tar.Header) (skip, replaceContents bool, replacementContents io.Reader)) io.WriteCloser {
91
	pipeReader, pipeWriter := io.Pipe()
92
	tarWriter := tar.NewWriter(writeCloser)
93
	filterer := &tarFilterer{
94
		pipeWriter: pipeWriter,
95
	}
96
	filterer.wg.Add(1)
97
	go func() {
98
		filterer.closedLock.Lock()
99
		closed := filterer.closed
100
		filterer.closedLock.Unlock()
101
		for !closed {
102
			tarReader := tar.NewReader(pipeReader)
103
			hdr, err := tarReader.Next()
104
			for err == nil {
105
				var skip, replaceContents bool
106
				var replacementContents io.Reader
107
				if filter != nil {
108
					skip, replaceContents, replacementContents = filter(hdr)
109
				}
110
				if !skip {
111
					err = tarWriter.WriteHeader(hdr)
112
					if err != nil {
113
						err = fmt.Errorf("filtering tar header for %q: %w", hdr.Name, err)
114
						break
115
					}
116
					if hdr.Size != 0 {
117
						var n int64
118
						var copyErr error
119
						if replaceContents {
120
							n, copyErr = io.CopyN(tarWriter, replacementContents, hdr.Size)
121
						} else {
122
							n, copyErr = io.Copy(tarWriter, tarReader)
123
						}
124
						if copyErr != nil {
125
							err = fmt.Errorf("copying content for %q: %w", hdr.Name, copyErr)
126
							break
127
						}
128
						if n != hdr.Size {
129
							err = fmt.Errorf("filtering content for %q: expected %d bytes, got %d bytes", hdr.Name, hdr.Size, n)
130
							break
131
						}
132
					}
133
				}
134
				hdr, err = tarReader.Next()
135
			}
136
			if err != io.EOF {
137
				filterer.err = fmt.Errorf("reading tar archive: %w", err)
138
				break
139
			}
140
			filterer.closedLock.Lock()
141
			closed = filterer.closed
142
			filterer.closedLock.Unlock()
143
		}
144
		pipeReader.Close()
145
		tarWriter.Close()
146
		writeCloser.Close()
147
		filterer.wg.Done()
148
	}()
149
	return 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.
155
type tarDigester struct {
156
	isOpen      bool
157
	nested      digester
158
	tarFilterer io.WriteCloser
159
}
160

161
func modifyTarHeaderForDigesting(hdr *tar.Header) (skip, replaceContents bool, replacementContents io.Reader) {
162
	zeroTime := time.Time{}
163
	hdr.ModTime = zeroTime
164
	hdr.AccessTime = zeroTime
165
	hdr.ChangeTime = zeroTime
166
	return false, false, nil
167
}
168

169
func newTarDigester(contentType string) digester {
170
	nested := newSimpleDigester(contentType)
171
	digester := &tarDigester{
172
		isOpen:      true,
173
		nested:      nested,
174
		tarFilterer: newTarFilterer(nested, modifyTarHeaderForDigesting),
175
	}
176
	return digester
177
}
178

179
func (t *tarDigester) ContentType() string {
180
	return t.nested.ContentType()
181
}
182

183
func (t *tarDigester) Digest() digest.Digest {
184
	return t.nested.Digest()
185
}
186

187
func (t *tarDigester) Write(p []byte) (int, error) {
188
	return t.tarFilterer.Write(p)
189
}
190

191
func (t *tarDigester) Close() error {
192
	if t.isOpen {
193
		t.isOpen = false
194
		return t.tarFilterer.Close()
195
	}
196
	return nil
197
}
198

199
// CompositeDigester can compute a digest over multiple items.
200
type CompositeDigester struct {
201
	digesters []digester
202
	closer    io.Closer
203
}
204

205
// closeOpenDigester closes an open sub-digester, if we have one.
206
func (c *CompositeDigester) closeOpenDigester() {
207
	if c.closer != nil {
208
		c.closer.Close()
209
		c.closer = nil
210
	}
211
}
212

213
// Restart clears all state, so that the composite digester can start over.
214
func (c *CompositeDigester) Restart() {
215
	c.closeOpenDigester()
216
	c.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.
222
func (c *CompositeDigester) Start(contentType string) {
223
	c.closeOpenDigester()
224
	switch contentType {
225
	case "":
226
		c.digesters = append(c.digesters, newSimpleDigester(""))
227
	case "file", "dir":
228
		digester := newTarDigester(contentType)
229
		c.closer = digester
230
		c.digesters = append(c.digesters, digester)
231
	default:
232
		panic(fmt.Sprintf(`unrecognized content type: expected "", "file", or "dir", got %q`, contentType))
233
	}
234
}
235

236
// Hash returns the hasher for the current item.
237
func (c *CompositeDigester) Hash() io.WriteCloser {
238
	num := len(c.digesters)
239
	if num == 0 {
240
		return nil
241
	}
242
	return c.digesters[num-1]
243
}
244

245
// Digest returns the content type and a composite digest over everything
246
// that's been digested.
247
func (c *CompositeDigester) Digest() (string, digest.Digest) {
248
	c.closeOpenDigester()
249
	num := len(c.digesters)
250
	switch num {
251
	case 0:
252
		return "", ""
253
	case 1:
254
		return c.digesters[0].ContentType(), c.digesters[0].Digest()
255
	default:
256
		content := ""
257
		for i, digester := range c.digesters {
258
			if i > 0 {
259
				content += ","
260
			}
261
			contentType := digester.ContentType()
262
			if contentType != "" {
263
				contentType += ":"
264
			}
265
			content += contentType + digester.Digest().Encoded()
266
		}
267
		return "multi", digest.Canonical.FromString(content)
268
	}
269
}
270

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.