Skip to content

Commit 9ca1b80

Browse files
committed
Fix Footer to make it compatible with RFC1952
This commit reflects the recent change on eStargz's footer format for making it compatible with RFC1952[0]. Since this commit the footer is the following 51 bytes: ``` 10 bytes gzip header 2 bytes XLEN (length of Extra field) = 26 (4 bytes header + 16 hex digits + len("STARGZ")) 2 bytes Extra: SI1 = 'S', SI2 = 'G' 2 bytes Extra: LEN = 22 (16 hex digits + len("STARGZ")) 22 bytes Extra: subfield = fmt.Sprintf("%016xSTARGZ", offsetOfTOC) 5 bytes flate header 8 bytes gzip footer (End of the eStargz blob) ``` According to RFC1952, Extra field is defined as the following (quoted from [1]): ``` If the FLG.FEXTRA bit is set, an "extra field" is present in the header, with total length XLEN bytes. It consists of a series of subfields, each of the form: +---+---+---+---+==================================+ |SI1|SI2| LEN |... LEN bytes of subfield data ...| +---+---+---+---+==================================+ SI1 and SI2 provide a subfield ID, typically two ASCII letters with some mnemonic value. Jean-Loup Gailly <[email protected]> is maintaining a registry of subfield IDs; please send him any subfield ID you wish to use. Subfield IDs with SI2 = 0 are reserved for future use. ``` This commit adds SI1, SI2 and LEN fields to Extra field, which are lacked in the stargz. As SI1 and SI2, we use (SI1, SI2) = ('S', 'G') for eStargz footer. (P.S. ('A', 'P') is used by "Apollo file type information"[1] and ('R', 'A') is used by dictzip[2]) Notes: - This commit forks stargz lib for applying this change. - This change makes eStargz incompatible with stargz (but still compatible with tar.gz). Images provided by this commit's `ctr-remote` cannot be lazily pulled by the stargz snapshotter before this commit. `--stargz-only` option is also deprecated since this commit. Users should use `--no-optimize` option instead. - As a fallback behaviour, the legacy footer is still supported by Stargz Snapshotter plugin (i.e. it can lazily pull eStargz images older than this commit). Some minor changes for making linter happy are also included in this patch. [0] https://tools.ietf.org/html/rfc1952 [1] https://tools.ietf.org/html/rfc1952#section-2.3.1.1 [2] https://linux.die.net/man/1/dictzip Signed-off-by: Kohei Tokunaga <[email protected]>
1 parent b1920d3 commit 9ca1b80

File tree

20 files changed

+1012
-295
lines changed

20 files changed

+1012
-295
lines changed

cmd/ctr-remote/commands/optimize.go

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ import (
4545
"github.com/containerd/stargz-snapshotter/cmd/ctr-remote/logger"
4646
"github.com/containerd/stargz-snapshotter/cmd/ctr-remote/sampler"
4747
"github.com/containerd/stargz-snapshotter/estargz"
48+
"github.com/containerd/stargz-snapshotter/estargz/stargz"
4849
"github.com/containerd/stargz-snapshotter/util/tempfiles"
49-
"github.com/google/crfs/stargz"
5050
"github.com/google/go-containerregistry/pkg/authn"
5151
reglogs "github.com/google/go-containerregistry/pkg/logs"
5252
"github.com/google/go-containerregistry/pkg/name"
@@ -77,10 +77,6 @@ var OptimizeCommand = cli.Command{
7777
Name: "plain-http",
7878
Usage: "allow HTTP connections to the registry which has the prefix \"http://\"",
7979
},
80-
cli.BoolFlag{
81-
Name: "stargz-only",
82-
Usage: "only stargzify and do not optimize layers",
83-
},
8480
cli.BoolFlag{
8581
Name: "reuse",
8682
Usage: "reuse eStargz (already optimized) layers without further conversion",
@@ -384,27 +380,7 @@ func convertImage(ctx gocontext.Context, clicontext *cli.Context, srcImg regpkg.
384380
return nil, errors.Wrap(err, "failed to get image layers")
385381
}
386382
addendums := make([]mutate.Addendum, len(layers))
387-
if clicontext.Bool("stargz-only") {
388-
// TODO: enable to reuse layers
389-
var eg errgroup.Group
390-
var addendumsMu sync.Mutex
391-
for i, l := range layers {
392-
i, l := i, l
393-
eg.Go(func() error {
394-
newL, err := buildStargzLayer(l, tf)
395-
if err != nil {
396-
return err
397-
}
398-
addendumsMu.Lock()
399-
addendums[i] = mutate.Addendum{Layer: newL}
400-
addendumsMu.Unlock()
401-
return nil
402-
})
403-
}
404-
if err := eg.Wait(); err != nil {
405-
return nil, errors.Wrapf(err, "failed to convert layer to stargz")
406-
}
407-
} else if clicontext.Bool("no-optimize") || !platforms.NewMatcher(platforms.DefaultSpec()).Match(*platform) {
383+
if clicontext.Bool("no-optimize") || !platforms.NewMatcher(platforms.DefaultSpec()).Match(*platform) {
408384
// Do not run the optimization container if the option requires it or
409385
// the source image doesn't match to the platform where this command runs on.
410386
log.G(ctx).Warn("Platform mismatch or optimization disabled; converting without optimization")
@@ -703,27 +679,6 @@ func converters(cs ...func() (mutate.Addendum, error)) func() (mutate.Addendum,
703679
}
704680
}
705681

706-
func buildStargzLayer(uncompressed regpkg.Layer, tf *tempfiles.TempFiles) (regpkg.Layer, error) {
707-
r, err := uncompressed.Uncompressed()
708-
if err != nil {
709-
return nil, err
710-
}
711-
pr, pw := io.Pipe()
712-
go func() {
713-
w := stargz.NewWriter(pw)
714-
if err := w.AppendTar(r); err != nil {
715-
pw.CloseWithError(err)
716-
return
717-
}
718-
if err := w.Close(); err != nil {
719-
pw.CloseWithError(err)
720-
return
721-
}
722-
pw.Close()
723-
}()
724-
return newStaticCompressedLayer(pr, tf)
725-
}
726-
727682
func buildEStargzLayer(uncompressed regpkg.Layer, tf *tempfiles.TempFiles) (regpkg.Layer, ocidigest.Digest, error) {
728683
tftmp := tempfiles.NewTempFiles() // Shorter lifetime than tempfiles passed by argument
729684
defer tftmp.CleanupAll()

estargz/build.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ import (
2727
"strings"
2828
"sync"
2929

30+
"github.com/containerd/stargz-snapshotter/estargz/stargz"
3031
"github.com/containerd/stargz-snapshotter/util/positionwatcher"
3132
"github.com/containerd/stargz-snapshotter/util/tempfiles"
32-
"github.com/google/crfs/stargz"
3333
digest "github.com/opencontainers/go-digest"
3434
"github.com/pkg/errors"
3535
"golang.org/x/sync/errgroup"

estargz/build_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
"testing"
3030
"time"
3131

32-
"github.com/google/crfs/stargz"
32+
"github.com/containerd/stargz-snapshotter/estargz/stargz"
3333
)
3434

3535
// TestBuild tests the resulting stargz blob built by this pkg has the same
@@ -367,8 +367,8 @@ func equalEntry(a, b *stargz.TOCEntry) bool {
367367
a.Stat().ModTime().Equal(b.Stat().ModTime()) && // modTime time.Time
368368
a.LinkName == b.LinkName &&
369369
a.Mode == b.Mode &&
370-
a.Uid == b.Uid &&
371-
a.Gid == b.Gid &&
370+
a.UID == b.UID &&
371+
a.GID == b.GID &&
372372
a.Uname == b.Uname &&
373373
a.Gname == b.Gname &&
374374
(a.Offset > 0) == (b.Offset > 0) &&

estargz/parse.go

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,24 @@ import (
2323
"encoding/json"
2424
"fmt"
2525
"io"
26-
"strconv"
2726

28-
"github.com/google/crfs/stargz"
27+
"github.com/containerd/stargz-snapshotter/estargz/stargz"
2928
digest "github.com/opencontainers/go-digest"
3029
"github.com/pkg/errors"
3130
)
3231

3332
// parseStargz parses the footer and TOCJSON of the stargz file
3433
func parseStargz(sgz *io.SectionReader) (blob *stargzBlob, err error) {
3534
// Parse stargz footer and get the offset of TOC JSON
36-
if sgz.Size() < stargz.FooterSize {
37-
return nil, errors.New("stargz data is too small")
38-
}
39-
footerReader := io.NewSectionReader(sgz, sgz.Size()-stargz.FooterSize, stargz.FooterSize)
40-
zr, err := gzip.NewReader(footerReader)
35+
tocOffset, footerSize, err := stargz.OpenFooter(sgz)
4136
if err != nil {
42-
return nil, errors.Wrap(err, "failed to uncompress footer")
43-
}
44-
defer zr.Close()
45-
if len(zr.Header.Extra) != 22 {
46-
return nil, errors.Wrap(err, "invalid extra size; must be 22 bytes")
47-
} else if string(zr.Header.Extra[16:]) != "STARGZ" {
48-
return nil, errors.New("invalid footer; extra must contain magic string \"STARGZ\"")
49-
}
50-
tocOffset, err := strconv.ParseInt(string(zr.Header.Extra[:16]), 16, 64)
51-
if err != nil {
52-
return nil, errors.Wrap(err, "invalid footer; failed to get the offset of TOC JSON")
53-
} else if tocOffset > sgz.Size() {
54-
return nil, fmt.Errorf("invalid footer; offset of TOC JSON is too large (%d > %d)",
55-
tocOffset, sgz.Size())
37+
return nil, errors.Wrapf(err, "failed to parse footer")
5638
}
5739

5840
// Decode the TOC JSON
59-
tocReader := io.NewSectionReader(sgz, tocOffset, sgz.Size()-tocOffset-stargz.FooterSize)
60-
if err := zr.Reset(tocReader); err != nil {
41+
tocReader := io.NewSectionReader(sgz, tocOffset, sgz.Size()-tocOffset-footerSize)
42+
zr, err := gzip.NewReader(tocReader)
43+
if err != nil {
6144
return nil, errors.Wrap(err, "failed to uncompress TOC JSON targz entry")
6245
}
6346
tr := tar.NewReader(zr)
@@ -87,7 +70,7 @@ func parseStargz(sgz *io.SectionReader) (blob *stargzBlob, err error) {
8770
jtocDigest: dgstr.Digest(),
8871
jtocOffset: tocOffset,
8972
payload: io.NewSectionReader(sgz, 0, tocOffset),
90-
footer: footerReader,
73+
footer: bytes.NewReader(stargz.FooterBytes(tocOffset)),
9174
}, nil
9275
}
9376

@@ -128,21 +111,10 @@ func combineBlobs(sgz ...*io.SectionReader) (newSgz io.Reader, tocDgst digest.Di
128111
if err != nil {
129112
return nil, "", err
130113
}
131-
footerBuf := bytes.NewBuffer(make([]byte, 0, stargz.FooterSize))
132-
zw, err := gzip.NewWriterLevel(footerBuf, gzip.BestCompression)
133-
if err != nil {
134-
return nil, "", err
135-
}
136-
zw.Extra = []byte(fmt.Sprintf("%016xSTARGZ", currentOffset)) // Extra header indicating the offset of TOCJSON: https://github.com/google/crfs/blob/71d77da419c90be7b05d12e59945ac7a8c94a543/stargz/stargz.go#L46
137-
zw.Close()
138-
if footerBuf.Len() != stargz.FooterSize {
139-
return nil, "", fmt.Errorf("failed to make the footer: invalid size %d; must be %d",
140-
footerBuf.Len(), stargz.FooterSize)
141-
}
142114
return io.MultiReader(
143115
io.MultiReader(mpayload...),
144116
tocjson,
145-
bytes.NewReader(footerBuf.Bytes()),
117+
bytes.NewReader(stargz.FooterBytes(currentOffset)),
146118
), tocDgst, nil
147119
}
148120

@@ -159,7 +131,6 @@ func marshalTOCJSON(toc *jtoc) (io.Reader, digest.Digest, error) {
159131
pw.CloseWithError(err)
160132
return
161133
}
162-
zw.Extra = []byte("stargz.toc") // this magic string might not be necessary but let's follow the official behaviour: https://github.com/google/crfs/blob/71d77da419c90be7b05d12e59945ac7a8c94a543/stargz/stargz.go#L596
163134
tw := tar.NewWriter(zw)
164135
if err := tw.WriteHeader(&tar.Header{
165136
Typeflag: tar.TypeReg,

0 commit comments

Comments
 (0)