File: zinfo_builder.go

package info (click to toggle)
golang-github-awslabs-soci-snapshotter 0.4.1-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,952 kB
  • sloc: ansic: 459; sh: 237; makefile: 90
file content (131 lines) | stat: -rw-r--r-- 3,753 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
   Copyright The Soci Snapshotter Authors.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package ztoc

import (
	"fmt"
	"io"
	"os"

	"github.com/awslabs/soci-snapshotter/ztoc/compression"
	"github.com/opencontainers/go-digest"
)

// ZinfoBuilder builds the `zinfo` part of a ztoc. This interface should be
// implemented for each compression algorithm we support.
type ZinfoBuilder interface {
	// ZinfoFromFile builds zinfo given a compressed tar filename and span size, and calculate the size of the file.
	ZinfoFromFile(filename string, spanSize int64) (zinfo CompressionInfo, fs compression.Offset, err error)
}

type gzipZinfoBuilder struct{}

// ZinfoFromFile creates zinfo for a gzip file. The underlying zinfo object (i.e. `GzipZinfo`)
// is stored in `CompressionInfo.Checkpoints` as byte slice.
func (gzb gzipZinfoBuilder) ZinfoFromFile(filename string, spanSize int64) (zinfo CompressionInfo, fs compression.Offset, err error) {
	index, err := compression.NewZinfoFromFile(compression.Gzip, filename, spanSize)
	if err != nil {
		return
	}
	defer index.Close()

	fs, err = getFileSize(filename)
	if err != nil {
		return
	}

	digests, err := getPerSpanDigests(filename, int64(fs), index)
	if err != nil {
		return
	}

	checkpoints, err := index.Bytes()
	if err != nil {
		return
	}

	return CompressionInfo{
		MaxSpanID:            index.MaxSpanID(),
		SpanDigests:          digests,
		Checkpoints:          checkpoints,
		CompressionAlgorithm: compression.Gzip,
	}, fs, nil
}

type tarZinfoBuilder struct{}

func (tzb tarZinfoBuilder) ZinfoFromFile(filename string, spanSize int64) (zinfo CompressionInfo, fs compression.Offset, err error) {
	index, err := compression.NewZinfoFromFile(compression.Uncompressed, filename, spanSize)
	if err != nil {
		return
	}
	defer index.Close()

	fs, err = getFileSize(filename)
	if err != nil {
		return
	}

	digests, err := getPerSpanDigests(filename, int64(fs), index)
	if err != nil {
		return
	}

	checkpoints, err := index.Bytes()
	if err != nil {
		return
	}

	return CompressionInfo{
		MaxSpanID:            index.MaxSpanID(),
		SpanDigests:          digests,
		Checkpoints:          checkpoints,
		CompressionAlgorithm: compression.Uncompressed,
	}, fs, nil
}

func getPerSpanDigests(filename string, fileSize int64, index compression.Zinfo) ([]digest.Digest, error) {
	file, err := os.Open(filename)
	if err != nil {
		return nil, fmt.Errorf("could not open file for reading: %w", err)
	}
	defer file.Close()

	var digests []digest.Digest
	var i compression.SpanID
	maxSpanID := index.MaxSpanID()
	for i = 0; i <= maxSpanID; i++ {
		startOffset := index.StartCompressedOffset(i)
		endOffset := index.EndCompressedOffset(i, compression.Offset(fileSize))

		section := io.NewSectionReader(file, int64(startOffset), int64(endOffset-startOffset))
		dgst, err := digest.FromReader(section)
		if err != nil {
			return nil, fmt.Errorf("unable to compute digest for section; start=%d, end=%d, file=%s, size=%d", startOffset, endOffset, filename, fileSize)
		}
		digests = append(digests, dgst)
	}
	return digests, nil
}

func getFileSize(file string) (compression.Offset, error) {
	st, err := os.Stat(file)
	if err != nil {
		return 0, err
	}
	return compression.Offset(st.Size()), nil
}