go.mod: bump osbuild/images to 0.55
This commit is contained in:
parent
eab44ca8a8
commit
22140aa7c9
700 changed files with 30353 additions and 27556 deletions
41
vendor/github.com/containers/storage/pkg/archive/archive.go
generated
vendored
41
vendor/github.com/containers/storage/pkg/archive/archive.go
generated
vendored
|
|
@ -339,12 +339,43 @@ func (compression *Compression) Extension() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// nosysFileInfo hides the system-dependent info of the wrapped FileInfo to
|
||||
// prevent tar.FileInfoHeader from introspecting it and potentially calling into
|
||||
// glibc.
|
||||
type nosysFileInfo struct {
|
||||
os.FileInfo
|
||||
}
|
||||
|
||||
func (fi nosysFileInfo) Sys() interface{} {
|
||||
// A Sys value of type *tar.Header is safe as it is system-independent.
|
||||
// The tar.FileInfoHeader function copies the fields into the returned
|
||||
// header without performing any OS lookups.
|
||||
if sys, ok := fi.FileInfo.Sys().(*tar.Header); ok {
|
||||
return sys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sysStatOverride, if non-nil, populates hdr from system-dependent fields of fi.
|
||||
var sysStatOverride func(fi os.FileInfo, hdr *tar.Header) error
|
||||
|
||||
func fileInfoHeaderNoLookups(fi os.FileInfo, link string) (*tar.Header, error) {
|
||||
if sysStatOverride == nil {
|
||||
return tar.FileInfoHeader(fi, link)
|
||||
}
|
||||
hdr, err := tar.FileInfoHeader(nosysFileInfo{fi}, link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hdr, sysStatOverride(fi, hdr)
|
||||
}
|
||||
|
||||
// FileInfoHeader creates a populated Header from fi.
|
||||
// Compared to archive pkg this function fills in more information.
|
||||
// Also, regardless of Go version, this function fills file type bits (e.g. hdr.Mode |= modeISDIR),
|
||||
// which have been deleted since Go 1.9 archive/tar.
|
||||
func FileInfoHeader(name string, fi os.FileInfo, link string) (*tar.Header, error) {
|
||||
hdr, err := tar.FileInfoHeader(fi, link)
|
||||
hdr, err := fileInfoHeaderNoLookups(fi, link)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -385,7 +416,7 @@ func ReadUserXattrToTarHeader(path string, hdr *tar.Header) error {
|
|||
return err
|
||||
}
|
||||
for _, key := range xattrs {
|
||||
if strings.HasPrefix(key, "user.") {
|
||||
if strings.HasPrefix(key, "user.") && !strings.HasPrefix(key, "user.overlay.") {
|
||||
value, err := system.Lgetxattr(path, key)
|
||||
if err != nil {
|
||||
if errors.Is(err, system.E2BIG) {
|
||||
|
|
@ -477,7 +508,7 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
|||
}
|
||||
}
|
||||
if fi.Mode()&os.ModeSocket != 0 {
|
||||
logrus.Warnf("archive: skipping %q since it is a socket", path)
|
||||
logrus.Infof("archive: skipping %q since it is a socket", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -534,6 +565,10 @@ func (ta *tarAppender) addTarFile(path, name string) error {
|
|||
if ta.ChownOpts != nil {
|
||||
hdr.Uid = ta.ChownOpts.UID
|
||||
hdr.Gid = ta.ChownOpts.GID
|
||||
// Don’t expose the user names from the local system; they probably don’t match the ta.ChownOpts value anyway,
|
||||
// and they unnecessarily give recipients of the tar file potentially private data.
|
||||
hdr.Uname = ""
|
||||
hdr.Gname = ""
|
||||
}
|
||||
|
||||
maybeTruncateHeaderModTime(hdr)
|
||||
|
|
|
|||
25
vendor/github.com/containers/storage/pkg/archive/archive_unix.go
generated
vendored
25
vendor/github.com/containers/storage/pkg/archive/archive_unix.go
generated
vendored
|
|
@ -15,6 +15,31 @@ import (
|
|||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func init() {
|
||||
sysStatOverride = statUnix
|
||||
}
|
||||
|
||||
// statUnix populates hdr from system-dependent fields of fi without performing
|
||||
// any OS lookups.
|
||||
// Adapted from Moby.
|
||||
func statUnix(fi os.FileInfo, hdr *tar.Header) error {
|
||||
s, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
hdr.Uid = int(s.Uid)
|
||||
hdr.Gid = int(s.Gid)
|
||||
|
||||
if s.Mode&unix.S_IFBLK != 0 ||
|
||||
s.Mode&unix.S_IFCHR != 0 {
|
||||
hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert
|
||||
hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fixVolumePathPrefix does platform specific processing to ensure that if
|
||||
// the path being passed in is not in a volume path format, convert it to one.
|
||||
func fixVolumePathPrefix(srcPath string) string {
|
||||
|
|
|
|||
42
vendor/github.com/containers/storage/pkg/chunked/cache_linux.go
generated
vendored
42
vendor/github.com/containers/storage/pkg/chunked/cache_linux.go
generated
vendored
|
|
@ -25,7 +25,7 @@ import (
|
|||
|
||||
const (
|
||||
cacheKey = "chunked-manifest-cache"
|
||||
cacheVersion = 1
|
||||
cacheVersion = 2
|
||||
|
||||
digestSha256Empty = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||
)
|
||||
|
|
@ -207,9 +207,9 @@ func calculateHardLinkFingerprint(f *internal.FileMetadata) (string, error) {
|
|||
return string(digester.Digest()), nil
|
||||
}
|
||||
|
||||
// generateFileLocation generates a file location in the form $OFFSET@$PATH
|
||||
func generateFileLocation(path string, offset uint64) []byte {
|
||||
return []byte(fmt.Sprintf("%d@%s", offset, path))
|
||||
// generateFileLocation generates a file location in the form $OFFSET:$LEN:$PATH
|
||||
func generateFileLocation(path string, offset, len uint64) []byte {
|
||||
return []byte(fmt.Sprintf("%d:%d:%s", offset, len, path))
|
||||
}
|
||||
|
||||
// generateTag generates a tag in the form $DIGEST$OFFSET@LEN.
|
||||
|
|
@ -245,7 +245,7 @@ func writeCache(manifest []byte, format graphdriver.DifferOutputFormat, id strin
|
|||
var tags []string
|
||||
for _, k := range toc {
|
||||
if k.Digest != "" {
|
||||
location := generateFileLocation(k.Name, 0)
|
||||
location := generateFileLocation(k.Name, 0, uint64(k.Size))
|
||||
|
||||
off := uint64(vdata.Len())
|
||||
l := uint64(len(location))
|
||||
|
|
@ -276,7 +276,7 @@ func writeCache(manifest []byte, format graphdriver.DifferOutputFormat, id strin
|
|||
digestLen = len(k.Digest)
|
||||
}
|
||||
if k.ChunkDigest != "" {
|
||||
location := generateFileLocation(k.Name, uint64(k.ChunkOffset))
|
||||
location := generateFileLocation(k.Name, uint64(k.ChunkOffset), uint64(k.ChunkSize))
|
||||
off := uint64(vdata.Len())
|
||||
l := uint64(len(location))
|
||||
d := generateTag(k.ChunkDigest, off, l)
|
||||
|
|
@ -490,7 +490,9 @@ func findTag(digest string, metadata *metadata) (string, uint64, uint64) {
|
|||
if digest == d {
|
||||
startOff := i*metadata.tagLen + metadata.digestLen
|
||||
parts := strings.Split(string(metadata.tags[startOff:(i+1)*metadata.tagLen]), "@")
|
||||
|
||||
off, _ := strconv.ParseInt(parts[0], 10, 64)
|
||||
|
||||
len, _ := strconv.ParseInt(parts[1], 10, 64)
|
||||
return digest, uint64(off), uint64(len)
|
||||
}
|
||||
|
|
@ -507,12 +509,16 @@ func (c *layersCache) findDigestInternal(digest string) (string, string, int64,
|
|||
defer c.mutex.RUnlock()
|
||||
|
||||
for _, layer := range c.layers {
|
||||
digest, off, len := findTag(digest, layer.metadata)
|
||||
digest, off, tagLen := findTag(digest, layer.metadata)
|
||||
if digest != "" {
|
||||
position := string(layer.metadata.vdata[off : off+len])
|
||||
parts := strings.SplitN(position, "@", 2)
|
||||
position := string(layer.metadata.vdata[off : off+tagLen])
|
||||
parts := strings.SplitN(position, ":", 3)
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
offFile, _ := strconv.ParseInt(parts[0], 10, 64)
|
||||
return layer.target, parts[1], offFile, nil
|
||||
// parts[1] is the chunk length, currently unused.
|
||||
return layer.target, parts[2], offFile, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -578,7 +584,10 @@ func unmarshalToc(manifest []byte) (*internal.TOC, error) {
|
|||
return byteSliceAsString(buf.Bytes()[from:to])
|
||||
}
|
||||
|
||||
iter = jsoniter.ParseBytes(jsoniter.ConfigFastest, manifest)
|
||||
pool := iter.Pool()
|
||||
pool.ReturnIterator(iter)
|
||||
iter = pool.BorrowIterator(manifest)
|
||||
|
||||
for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
|
||||
if strings.ToLower(field) == "version" {
|
||||
toc.Version = iter.ReadInt()
|
||||
|
|
@ -657,8 +666,17 @@ func unmarshalToc(manifest []byte) (*internal.TOC, error) {
|
|||
}
|
||||
toc.Entries = append(toc.Entries, m)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// validate there is no extra data in the provided input. This is a security measure to avoid
|
||||
// that the digest we calculate for the TOC refers to the entire document.
|
||||
if iter.Error != nil && iter.Error != io.EOF {
|
||||
return nil, iter.Error
|
||||
}
|
||||
if iter.WhatIsNext() != jsoniter.InvalidValue || !errors.Is(iter.Error, io.EOF) {
|
||||
return nil, fmt.Errorf("unexpected data after manifest")
|
||||
}
|
||||
|
||||
toc.StringsBuf = buf
|
||||
return &toc, nil
|
||||
}
|
||||
|
|
|
|||
4
vendor/github.com/containers/storage/pkg/chunked/compression_linux.go
generated
vendored
4
vendor/github.com/containers/storage/pkg/chunked/compression_linux.go
generated
vendored
|
|
@ -257,8 +257,8 @@ func readZstdChunkedManifest(blobStream ImageSourceSeekable, blobSize int64, ann
|
|||
return decodedBlob, decodedTarSplit, int64(footerData.Offset), err
|
||||
}
|
||||
|
||||
func decodeAndValidateBlob(blob []byte, lengthUncompressed uint64, expectedUncompressedChecksum string) ([]byte, error) {
|
||||
d, err := digest.Parse(expectedUncompressedChecksum)
|
||||
func decodeAndValidateBlob(blob []byte, lengthUncompressed uint64, expectedCompressedChecksum string) ([]byte, error) {
|
||||
d, err := digest.Parse(expectedCompressedChecksum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
16
vendor/github.com/containers/storage/pkg/chunked/compressor/compressor.go
generated
vendored
16
vendor/github.com/containers/storage/pkg/chunked/compressor/compressor.go
generated
vendored
|
|
@ -420,6 +420,14 @@ func writeZstdChunkedStream(destFile io.Writer, outMetadata map[string]string, r
|
|||
zstdWriter.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// make sure the entire tarball is flushed to the output as it might contain
|
||||
// some trailing zeros that affect the checksum.
|
||||
if _, err := io.Copy(zstdWriter, its); err != nil {
|
||||
zstdWriter.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := zstdWriter.Flush(); err != nil {
|
||||
zstdWriter.Close()
|
||||
return err
|
||||
|
|
@ -452,12 +460,12 @@ type zstdChunkedWriter struct {
|
|||
}
|
||||
|
||||
func (w zstdChunkedWriter) Close() error {
|
||||
err := <-w.tarSplitErr
|
||||
if err != nil {
|
||||
w.tarSplitOut.Close()
|
||||
errClose := w.tarSplitOut.Close()
|
||||
|
||||
if err := <-w.tarSplitErr; err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
return w.tarSplitOut.Close()
|
||||
return errClose
|
||||
}
|
||||
|
||||
func (w zstdChunkedWriter) Write(p []byte) (int, error) {
|
||||
|
|
|
|||
24
vendor/github.com/containers/storage/pkg/chunked/dump/dump.go
generated
vendored
24
vendor/github.com/containers/storage/pkg/chunked/dump/dump.go
generated
vendored
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
|
@ -93,13 +94,18 @@ func getStMode(mode uint32, typ string) (uint32, error) {
|
|||
return mode, nil
|
||||
}
|
||||
|
||||
func dumpNode(out io.Writer, links map[string]int, verityDigests map[string]string, entry *internal.FileMetadata) error {
|
||||
path := entry.Name
|
||||
if path == "" {
|
||||
func sanitizeName(name string) string {
|
||||
path := filepath.Clean(name)
|
||||
if path == "." {
|
||||
path = "/"
|
||||
} else if path[0] != '/' {
|
||||
path = "/" + path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func dumpNode(out io.Writer, links map[string]int, verityDigests map[string]string, entry *internal.FileMetadata) error {
|
||||
path := sanitizeName(entry.Name)
|
||||
|
||||
if _, err := fmt.Fprint(out, escaped(path, ESCAPE_STANDARD)); err != nil {
|
||||
return err
|
||||
|
|
@ -133,9 +139,10 @@ func dumpNode(out io.Writer, links map[string]int, verityDigests map[string]stri
|
|||
|
||||
var payload string
|
||||
if entry.Linkname != "" {
|
||||
payload = entry.Linkname
|
||||
if entry.Type == internal.TypeLink && payload[0] != '/' {
|
||||
payload = "/" + payload
|
||||
if entry.Type == internal.TypeSymlink {
|
||||
payload = entry.Linkname
|
||||
} else {
|
||||
payload = sanitizeName(entry.Linkname)
|
||||
}
|
||||
} else {
|
||||
if len(entry.Digest) > 10 {
|
||||
|
|
@ -198,10 +205,13 @@ func GenerateDump(tocI interface{}, verityDigests map[string]string) (io.Reader,
|
|||
if e.Linkname == "" {
|
||||
continue
|
||||
}
|
||||
if e.Type == internal.TypeSymlink {
|
||||
continue
|
||||
}
|
||||
links[e.Linkname] = links[e.Linkname] + 1
|
||||
}
|
||||
|
||||
if len(toc.Entries) == 0 || (toc.Entries[0].Name != "" && toc.Entries[0].Name != "/") {
|
||||
if len(toc.Entries) == 0 || (sanitizeName(toc.Entries[0].Name) != "/") {
|
||||
root := &internal.FileMetadata{
|
||||
Name: "/",
|
||||
Type: internal.TypeDir,
|
||||
|
|
|
|||
401
vendor/github.com/containers/storage/pkg/chunked/storage_linux.go
generated
vendored
401
vendor/github.com/containers/storage/pkg/chunked/storage_linux.go
generated
vendored
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/containers/storage/pkg/chunked/compressor"
|
||||
"github.com/containers/storage/pkg/chunked/internal"
|
||||
"github.com/containers/storage/pkg/fsverity"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/containers/storage/types"
|
||||
|
|
@ -40,12 +41,14 @@ import (
|
|||
|
||||
const (
|
||||
maxNumberMissingChunks = 1024
|
||||
autoMergePartsThreshold = 128 // if the gap between two ranges is below this threshold, automatically merge them.
|
||||
newFileFlags = (unix.O_CREAT | unix.O_TRUNC | unix.O_EXCL | unix.O_WRONLY)
|
||||
containersOverrideXattr = "user.containers.override_stat"
|
||||
bigDataKey = "zstd-chunked-manifest"
|
||||
chunkedData = "zstd-chunked-data"
|
||||
chunkedLayerDataKey = "zstd-chunked-layer-data"
|
||||
tocKey = "toc"
|
||||
fsVerityDigestsKey = "fs-verity-digests"
|
||||
|
||||
fileTypeZstdChunked = iota
|
||||
fileTypeEstargz
|
||||
|
|
@ -71,11 +74,9 @@ type chunkedDiffer struct {
|
|||
zstdReader *zstd.Decoder
|
||||
rawReader io.Reader
|
||||
|
||||
// contentDigest is the digest of the uncompressed content
|
||||
// (diffID) when the layer is fully retrieved. If the layer
|
||||
// is not fully retrieved, instead of using the digest of the
|
||||
// uncompressed content, it refers to the digest of the TOC.
|
||||
contentDigest digest.Digest
|
||||
// tocDigest is the digest of the TOC document when the layer
|
||||
// is partially pulled.
|
||||
tocDigest digest.Digest
|
||||
|
||||
// convertedToZstdChunked is set to true if the layer needs to
|
||||
// be converted to the zstd:chunked format before it can be
|
||||
|
|
@ -86,9 +87,18 @@ type chunkedDiffer struct {
|
|||
// the layer are trusted and should not be validated.
|
||||
skipValidation bool
|
||||
|
||||
// blobDigest is the digest of the whole compressed layer. It is used if
|
||||
// convertToZstdChunked to validate a layer when it is converted since there
|
||||
// is no TOC referenced by the manifest.
|
||||
blobDigest digest.Digest
|
||||
|
||||
blobSize int64
|
||||
|
||||
storeOpts *types.StoreOptions
|
||||
|
||||
useFsVerity graphdriver.DifferFsVerity
|
||||
fsVerityDigests map[string]string
|
||||
fsVerityMutex sync.Mutex
|
||||
}
|
||||
|
||||
var xattrsToIgnore = map[string]interface{}{
|
||||
|
|
@ -188,33 +198,7 @@ func (f *seekableFile) GetBlobAt(chunks []ImageSourceChunk) (chan io.ReadCloser,
|
|||
return streams, errs, nil
|
||||
}
|
||||
|
||||
func convertTarToZstdChunked(destDirectory string, blobSize int64, iss ImageSourceSeekable) (*seekableFile, digest.Digest, map[string]string, error) {
|
||||
var payload io.ReadCloser
|
||||
var streams chan io.ReadCloser
|
||||
var errs chan error
|
||||
var err error
|
||||
|
||||
chunksToRequest := []ImageSourceChunk{
|
||||
{
|
||||
Offset: 0,
|
||||
Length: uint64(blobSize),
|
||||
},
|
||||
}
|
||||
|
||||
streams, errs, err = iss.GetBlobAt(chunksToRequest)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
}
|
||||
select {
|
||||
case p := <-streams:
|
||||
payload = p
|
||||
case err := <-errs:
|
||||
return nil, "", nil, err
|
||||
}
|
||||
if payload == nil {
|
||||
return nil, "", nil, errors.New("invalid stream returned")
|
||||
}
|
||||
|
||||
func convertTarToZstdChunked(destDirectory string, payload *os.File) (*seekableFile, digest.Digest, map[string]string, error) {
|
||||
diff, err := archive.DecompressStream(payload)
|
||||
if err != nil {
|
||||
return nil, "", nil, err
|
||||
|
|
@ -235,10 +219,8 @@ func convertTarToZstdChunked(destDirectory string, blobSize int64, iss ImageSour
|
|||
return nil, "", nil, err
|
||||
}
|
||||
|
||||
digester := digest.Canonical.Digester()
|
||||
hash := digester.Hash()
|
||||
|
||||
if _, err := io.Copy(io.MultiWriter(chunked, hash), diff); err != nil {
|
||||
convertedOutputDigester := digest.Canonical.Digester()
|
||||
if _, err := io.Copy(io.MultiWriter(chunked, convertedOutputDigester.Hash()), diff); err != nil {
|
||||
f.Close()
|
||||
return nil, "", nil, err
|
||||
}
|
||||
|
|
@ -249,27 +231,39 @@ func convertTarToZstdChunked(destDirectory string, blobSize int64, iss ImageSour
|
|||
is := seekableFile{
|
||||
file: f,
|
||||
}
|
||||
return &is, digester.Digest(), newAnnotations, nil
|
||||
|
||||
return &is, convertedOutputDigester.Digest(), newAnnotations, nil
|
||||
}
|
||||
|
||||
// GetDiffer returns a differ than can be used with ApplyDiffWithDiffer.
|
||||
func GetDiffer(ctx context.Context, store storage.Store, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) {
|
||||
storeOpts, err := types.DefaultStoreOptionsAutoDetectUID()
|
||||
func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) {
|
||||
storeOpts, err := types.DefaultStoreOptions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := annotations[internal.ManifestChecksumKey]; ok {
|
||||
if !parseBooleanPullOption(&storeOpts, "enable_partial_images", true) {
|
||||
return nil, errors.New("enable_partial_images not configured")
|
||||
}
|
||||
|
||||
_, hasZstdChunkedTOC := annotations[internal.ManifestChecksumKey]
|
||||
_, hasEstargzTOC := annotations[estargz.TOCJSONDigestAnnotation]
|
||||
|
||||
if hasZstdChunkedTOC && hasEstargzTOC {
|
||||
return nil, errors.New("both zstd:chunked and eStargz TOC found")
|
||||
}
|
||||
|
||||
if hasZstdChunkedTOC {
|
||||
return makeZstdChunkedDiffer(ctx, store, blobSize, annotations, iss, &storeOpts)
|
||||
}
|
||||
if _, ok := annotations[estargz.TOCJSONDigestAnnotation]; ok {
|
||||
if hasEstargzTOC {
|
||||
return makeEstargzChunkedDiffer(ctx, store, blobSize, annotations, iss, &storeOpts)
|
||||
}
|
||||
|
||||
return makeConvertFromRawDiffer(ctx, store, blobSize, annotations, iss, &storeOpts)
|
||||
return makeConvertFromRawDiffer(ctx, store, blobDigest, blobSize, annotations, iss, &storeOpts)
|
||||
}
|
||||
|
||||
func makeConvertFromRawDiffer(ctx context.Context, store storage.Store, blobSize int64, annotations map[string]string, iss ImageSourceSeekable, storeOpts *types.StoreOptions) (*chunkedDiffer, error) {
|
||||
func makeConvertFromRawDiffer(ctx context.Context, store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable, storeOpts *types.StoreOptions) (*chunkedDiffer, error) {
|
||||
if !parseBooleanPullOption(storeOpts, "convert_images", false) {
|
||||
return nil, errors.New("convert_images not configured")
|
||||
}
|
||||
|
|
@ -280,6 +274,8 @@ func makeConvertFromRawDiffer(ctx context.Context, store storage.Store, blobSize
|
|||
}
|
||||
|
||||
return &chunkedDiffer{
|
||||
fsVerityDigests: make(map[string]string),
|
||||
blobDigest: blobDigest,
|
||||
blobSize: blobSize,
|
||||
convertToZstdChunked: true,
|
||||
copyBuffer: makeCopyBuffer(),
|
||||
|
|
@ -299,22 +295,23 @@ func makeZstdChunkedDiffer(ctx context.Context, store storage.Store, blobSize in
|
|||
return nil, err
|
||||
}
|
||||
|
||||
contentDigest, err := digest.Parse(annotations[internal.ManifestChecksumKey])
|
||||
tocDigest, err := digest.Parse(annotations[internal.ManifestChecksumKey])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse TOC digest %q: %w", annotations[internal.ManifestChecksumKey], err)
|
||||
}
|
||||
|
||||
return &chunkedDiffer{
|
||||
blobSize: blobSize,
|
||||
contentDigest: contentDigest,
|
||||
copyBuffer: makeCopyBuffer(),
|
||||
fileType: fileTypeZstdChunked,
|
||||
layersCache: layersCache,
|
||||
manifest: manifest,
|
||||
storeOpts: storeOpts,
|
||||
stream: iss,
|
||||
tarSplit: tarSplit,
|
||||
tocOffset: tocOffset,
|
||||
fsVerityDigests: make(map[string]string),
|
||||
blobSize: blobSize,
|
||||
tocDigest: tocDigest,
|
||||
copyBuffer: makeCopyBuffer(),
|
||||
fileType: fileTypeZstdChunked,
|
||||
layersCache: layersCache,
|
||||
manifest: manifest,
|
||||
storeOpts: storeOpts,
|
||||
stream: iss,
|
||||
tarSplit: tarSplit,
|
||||
tocOffset: tocOffset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -328,21 +325,22 @@ func makeEstargzChunkedDiffer(ctx context.Context, store storage.Store, blobSize
|
|||
return nil, err
|
||||
}
|
||||
|
||||
contentDigest, err := digest.Parse(annotations[estargz.TOCJSONDigestAnnotation])
|
||||
tocDigest, err := digest.Parse(annotations[estargz.TOCJSONDigestAnnotation])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse TOC digest %q: %w", annotations[estargz.TOCJSONDigestAnnotation], err)
|
||||
}
|
||||
|
||||
return &chunkedDiffer{
|
||||
blobSize: blobSize,
|
||||
contentDigest: contentDigest,
|
||||
copyBuffer: makeCopyBuffer(),
|
||||
fileType: fileTypeEstargz,
|
||||
layersCache: layersCache,
|
||||
manifest: manifest,
|
||||
storeOpts: storeOpts,
|
||||
stream: iss,
|
||||
tocOffset: tocOffset,
|
||||
fsVerityDigests: make(map[string]string),
|
||||
blobSize: blobSize,
|
||||
tocDigest: tocDigest,
|
||||
copyBuffer: makeCopyBuffer(),
|
||||
fileType: fileTypeEstargz,
|
||||
layersCache: layersCache,
|
||||
manifest: manifest,
|
||||
storeOpts: storeOpts,
|
||||
stream: iss,
|
||||
tocOffset: tocOffset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -939,6 +937,8 @@ func (c *chunkedDiffer) appendCompressedStreamToFile(compression compressedFileT
|
|||
return nil
|
||||
}
|
||||
|
||||
type recordFsVerityFunc func(string, *os.File) error
|
||||
|
||||
type destinationFile struct {
|
||||
digester digest.Digester
|
||||
dirfd int
|
||||
|
|
@ -948,9 +948,10 @@ type destinationFile struct {
|
|||
options *archive.TarOptions
|
||||
skipValidation bool
|
||||
to io.Writer
|
||||
recordFsVerity recordFsVerityFunc
|
||||
}
|
||||
|
||||
func openDestinationFile(dirfd int, metadata *internal.FileMetadata, options *archive.TarOptions, skipValidation bool) (*destinationFile, error) {
|
||||
func openDestinationFile(dirfd int, metadata *internal.FileMetadata, options *archive.TarOptions, skipValidation bool, recordFsVerity recordFsVerityFunc) (*destinationFile, error) {
|
||||
file, err := openFileUnderRoot(metadata.Name, dirfd, newFileFlags, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -977,15 +978,32 @@ func openDestinationFile(dirfd int, metadata *internal.FileMetadata, options *ar
|
|||
options: options,
|
||||
dirfd: dirfd,
|
||||
skipValidation: skipValidation,
|
||||
recordFsVerity: recordFsVerity,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *destinationFile) Close() (Err error) {
|
||||
defer func() {
|
||||
err := d.file.Close()
|
||||
var roFile *os.File
|
||||
var err error
|
||||
|
||||
if d.recordFsVerity != nil {
|
||||
roFile, err = reopenFileReadOnly(d.file)
|
||||
if err == nil {
|
||||
defer roFile.Close()
|
||||
} else if Err == nil {
|
||||
Err = err
|
||||
}
|
||||
}
|
||||
|
||||
err = d.file.Close()
|
||||
if Err == nil {
|
||||
Err = err
|
||||
}
|
||||
|
||||
if Err == nil && roFile != nil {
|
||||
Err = d.recordFsVerity(d.metadata.Name, roFile)
|
||||
}
|
||||
}()
|
||||
|
||||
if !d.skipValidation {
|
||||
|
|
@ -1008,6 +1026,35 @@ func closeDestinationFiles(files chan *destinationFile, errors chan error) {
|
|||
close(errors)
|
||||
}
|
||||
|
||||
func (c *chunkedDiffer) recordFsVerity(path string, roFile *os.File) error {
|
||||
if c.useFsVerity == graphdriver.DifferFsVerityDisabled {
|
||||
return nil
|
||||
}
|
||||
// fsverity.EnableVerity doesn't return an error if fs-verity was already
|
||||
// enabled on the file.
|
||||
err := fsverity.EnableVerity(path, int(roFile.Fd()))
|
||||
if err != nil {
|
||||
if c.useFsVerity == graphdriver.DifferFsVerityRequired {
|
||||
return err
|
||||
}
|
||||
|
||||
// If it is not required, ignore the error if the filesystem does not support it.
|
||||
if errors.Is(err, unix.ENOTSUP) || errors.Is(err, unix.ENOTTY) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
verity, err := fsverity.MeasureVerity(path, int(roFile.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.fsVerityMutex.Lock()
|
||||
c.fsVerityDigests[path] = verity
|
||||
c.fsVerityMutex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chunkedDiffer) storeMissingFiles(streams chan io.ReadCloser, errs chan error, dest string, dirfd int, missingParts []missingPart, options *archive.TarOptions) (Err error) {
|
||||
var destFile *destinationFile
|
||||
|
||||
|
|
@ -1095,7 +1142,11 @@ func (c *chunkedDiffer) storeMissingFiles(streams chan io.ReadCloser, errs chan
|
|||
}
|
||||
filesToClose <- destFile
|
||||
}
|
||||
destFile, err = openDestinationFile(dirfd, mf.File, options, c.skipValidation)
|
||||
recordFsVerity := c.recordFsVerity
|
||||
if c.useFsVerity == graphdriver.DifferFsVerityDisabled {
|
||||
recordFsVerity = nil
|
||||
}
|
||||
destFile, err = openDestinationFile(dirfd, mf.File, options, c.skipValidation, recordFsVerity)
|
||||
if err != nil {
|
||||
Err = err
|
||||
goto exit
|
||||
|
|
@ -1130,22 +1181,12 @@ func (c *chunkedDiffer) storeMissingFiles(streams chan io.ReadCloser, errs chan
|
|||
}
|
||||
|
||||
func mergeMissingChunks(missingParts []missingPart, target int) []missingPart {
|
||||
getGap := func(missingParts []missingPart, i int) int {
|
||||
getGap := func(missingParts []missingPart, i int) uint64 {
|
||||
prev := missingParts[i-1].SourceChunk.Offset + missingParts[i-1].SourceChunk.Length
|
||||
return int(missingParts[i].SourceChunk.Offset - prev)
|
||||
}
|
||||
getCost := func(missingParts []missingPart, i int) int {
|
||||
cost := getGap(missingParts, i)
|
||||
if missingParts[i-1].OriginFile != nil {
|
||||
cost += int(missingParts[i-1].SourceChunk.Length)
|
||||
}
|
||||
if missingParts[i].OriginFile != nil {
|
||||
cost += int(missingParts[i].SourceChunk.Length)
|
||||
}
|
||||
return cost
|
||||
return missingParts[i].SourceChunk.Offset - prev
|
||||
}
|
||||
|
||||
// simple case: merge chunks from the same file.
|
||||
// simple case: merge chunks from the same file. Useful to reduce the number of parts to work with later.
|
||||
newMissingParts := missingParts[0:1]
|
||||
prevIndex := 0
|
||||
for i := 1; i < len(missingParts); i++ {
|
||||
|
|
@ -1165,28 +1206,50 @@ func mergeMissingChunks(missingParts []missingPart, target int) []missingPart {
|
|||
}
|
||||
missingParts = newMissingParts
|
||||
|
||||
if len(missingParts) <= target {
|
||||
return missingParts
|
||||
type gap struct {
|
||||
from int
|
||||
to int
|
||||
cost uint64
|
||||
}
|
||||
|
||||
// this implementation doesn't account for duplicates, so it could merge
|
||||
// more than necessary to reach the specified target. Since target itself
|
||||
// is a heuristic value, it doesn't matter.
|
||||
costs := make([]int, len(missingParts)-1)
|
||||
for i := 1; i < len(missingParts); i++ {
|
||||
costs[i-1] = getCost(missingParts, i)
|
||||
var requestGaps []gap
|
||||
lastOffset := int(-1)
|
||||
numberSourceChunks := 0
|
||||
for i, c := range missingParts {
|
||||
if c.OriginFile != nil || c.Hole {
|
||||
// it does not require a network request
|
||||
continue
|
||||
}
|
||||
numberSourceChunks++
|
||||
if lastOffset >= 0 {
|
||||
prevEnd := missingParts[lastOffset].SourceChunk.Offset + missingParts[lastOffset].SourceChunk.Length
|
||||
cost := c.SourceChunk.Offset - prevEnd
|
||||
g := gap{
|
||||
from: lastOffset,
|
||||
to: i,
|
||||
cost: cost,
|
||||
}
|
||||
requestGaps = append(requestGaps, g)
|
||||
}
|
||||
lastOffset = i
|
||||
}
|
||||
sort.Ints(costs)
|
||||
|
||||
toShrink := len(missingParts) - target
|
||||
if toShrink >= len(costs) {
|
||||
toShrink = len(costs) - 1
|
||||
sort.Slice(requestGaps, func(i, j int) bool {
|
||||
return requestGaps[i].cost < requestGaps[j].cost
|
||||
})
|
||||
toMergeMap := make([]bool, len(missingParts))
|
||||
remainingToMerge := numberSourceChunks - target
|
||||
for _, g := range requestGaps {
|
||||
if remainingToMerge < 0 && g.cost > autoMergePartsThreshold {
|
||||
continue
|
||||
}
|
||||
for i := g.from + 1; i <= g.to; i++ {
|
||||
toMergeMap[i] = true
|
||||
}
|
||||
remainingToMerge--
|
||||
}
|
||||
targetValue := costs[toShrink]
|
||||
|
||||
newMissingParts = missingParts[0:1]
|
||||
for i := 1; i < len(missingParts); i++ {
|
||||
if getCost(missingParts, i) > targetValue {
|
||||
if !toMergeMap[i] {
|
||||
newMissingParts = append(newMissingParts, missingParts[i])
|
||||
} else {
|
||||
gap := getGap(missingParts, i)
|
||||
|
|
@ -1218,6 +1281,7 @@ func (c *chunkedDiffer) retrieveMissingFiles(stream ImageSourceSeekable, dest st
|
|||
}
|
||||
}
|
||||
|
||||
missingParts = mergeMissingChunks(missingParts, maxNumberMissingChunks)
|
||||
calculateChunksToRequest()
|
||||
|
||||
// There are some missing files. Prepare a multirange request for the missing chunks.
|
||||
|
|
@ -1231,14 +1295,13 @@ func (c *chunkedDiffer) retrieveMissingFiles(stream ImageSourceSeekable, dest st
|
|||
}
|
||||
|
||||
if _, ok := err.(ErrBadRequest); ok {
|
||||
requested := len(missingParts)
|
||||
// If the server cannot handle at least 64 chunks in a single request, just give up.
|
||||
if requested < 64 {
|
||||
if len(chunksToRequest) < 64 {
|
||||
return err
|
||||
}
|
||||
|
||||
// Merge more chunks to request
|
||||
missingParts = mergeMissingChunks(missingParts, requested/2)
|
||||
missingParts = mergeMissingChunks(missingParts, len(chunksToRequest)/2)
|
||||
calculateChunksToRequest()
|
||||
continue
|
||||
}
|
||||
|
|
@ -1426,15 +1489,39 @@ type findAndCopyFileOptions struct {
|
|||
options *archive.TarOptions
|
||||
}
|
||||
|
||||
func reopenFileReadOnly(f *os.File) (*os.File, error) {
|
||||
path := fmt.Sprintf("/proc/self/fd/%d", f.Fd())
|
||||
fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.NewFile(uintptr(fd), f.Name()), nil
|
||||
}
|
||||
|
||||
func (c *chunkedDiffer) findAndCopyFile(dirfd int, r *internal.FileMetadata, copyOptions *findAndCopyFileOptions, mode os.FileMode) (bool, error) {
|
||||
finalizeFile := func(dstFile *os.File) error {
|
||||
if dstFile != nil {
|
||||
defer dstFile.Close()
|
||||
if err := setFileAttrs(dirfd, dstFile, mode, r, copyOptions.options, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if dstFile == nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
err := setFileAttrs(dirfd, dstFile, mode, r, copyOptions.options, false)
|
||||
if err != nil {
|
||||
dstFile.Close()
|
||||
return err
|
||||
}
|
||||
var roFile *os.File
|
||||
if c.useFsVerity != graphdriver.DifferFsVerityDisabled {
|
||||
roFile, err = reopenFileReadOnly(dstFile)
|
||||
}
|
||||
dstFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if roFile == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer roFile.Close()
|
||||
return c.recordFsVerity(r.Name, roFile)
|
||||
}
|
||||
|
||||
found, dstFile, _, err := findFileInOtherLayers(c.layersCache, r, dirfd, copyOptions.useHardLinks)
|
||||
|
|
@ -1491,6 +1578,43 @@ func makeEntriesFlat(mergedEntries []internal.FileMetadata) ([]internal.FileMeta
|
|||
return new, nil
|
||||
}
|
||||
|
||||
func (c *chunkedDiffer) copyAllBlobToFile(destination *os.File) (digest.Digest, error) {
|
||||
var payload io.ReadCloser
|
||||
var streams chan io.ReadCloser
|
||||
var errs chan error
|
||||
var err error
|
||||
|
||||
chunksToRequest := []ImageSourceChunk{
|
||||
{
|
||||
Offset: 0,
|
||||
Length: uint64(c.blobSize),
|
||||
},
|
||||
}
|
||||
|
||||
streams, errs, err = c.stream.GetBlobAt(chunksToRequest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
select {
|
||||
case p := <-streams:
|
||||
payload = p
|
||||
case err := <-errs:
|
||||
return "", err
|
||||
}
|
||||
if payload == nil {
|
||||
return "", errors.New("invalid stream returned")
|
||||
}
|
||||
|
||||
originalRawDigester := digest.Canonical.Digester()
|
||||
|
||||
r := io.TeeReader(payload, originalRawDigester.Hash())
|
||||
|
||||
// copy the entire tarball and compute its digest
|
||||
_, err = io.Copy(destination, r)
|
||||
|
||||
return originalRawDigester.Digest(), err
|
||||
}
|
||||
|
||||
func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, differOpts *graphdriver.DifferOptions) (graphdriver.DriverWithDifferOutput, error) {
|
||||
defer c.layersCache.release()
|
||||
defer func() {
|
||||
|
|
@ -1499,11 +1623,40 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
}
|
||||
}()
|
||||
|
||||
c.useFsVerity = differOpts.UseFsVerity
|
||||
|
||||
// stream to use for reading the zstd:chunked or Estargz file.
|
||||
stream := c.stream
|
||||
|
||||
var uncompressedDigest digest.Digest
|
||||
|
||||
if c.convertToZstdChunked {
|
||||
fileSource, diffID, annotations, err := convertTarToZstdChunked(dest, c.blobSize, c.stream)
|
||||
fd, err := unix.Open(dest, unix.O_TMPFILE|unix.O_RDWR|unix.O_CLOEXEC, 0o600)
|
||||
if err != nil {
|
||||
return graphdriver.DriverWithDifferOutput{}, err
|
||||
}
|
||||
blobFile := os.NewFile(uintptr(fd), "blob-file")
|
||||
defer func() {
|
||||
if blobFile != nil {
|
||||
blobFile.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
// calculate the checksum before accessing the file.
|
||||
compressedDigest, err := c.copyAllBlobToFile(blobFile)
|
||||
if err != nil {
|
||||
return graphdriver.DriverWithDifferOutput{}, err
|
||||
}
|
||||
|
||||
if compressedDigest != c.blobDigest {
|
||||
return graphdriver.DriverWithDifferOutput{}, fmt.Errorf("invalid digest to convert: expected %q, got %q", c.blobDigest, compressedDigest)
|
||||
}
|
||||
|
||||
if _, err := blobFile.Seek(0, io.SeekStart); err != nil {
|
||||
return graphdriver.DriverWithDifferOutput{}, err
|
||||
}
|
||||
|
||||
fileSource, diffID, annotations, err := convertTarToZstdChunked(dest, blobFile)
|
||||
if err != nil {
|
||||
return graphdriver.DriverWithDifferOutput{}, err
|
||||
}
|
||||
|
|
@ -1511,6 +1664,10 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
// need to keep it open until the entire file is processed.
|
||||
defer fileSource.Close()
|
||||
|
||||
// Close the file so that the file descriptor is released and the file is deleted.
|
||||
blobFile.Close()
|
||||
blobFile = nil
|
||||
|
||||
manifest, tarSplit, tocOffset, err := readZstdChunkedManifest(fileSource, c.blobSize, annotations)
|
||||
if err != nil {
|
||||
return graphdriver.DriverWithDifferOutput{}, fmt.Errorf("read zstd:chunked manifest: %w", err)
|
||||
|
|
@ -1523,12 +1680,12 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
c.fileType = fileTypeZstdChunked
|
||||
c.manifest = manifest
|
||||
c.tarSplit = tarSplit
|
||||
// since we retrieved the whole file and it was validated, use the diffID instead of the TOC digest.
|
||||
c.contentDigest = diffID
|
||||
c.tocOffset = tocOffset
|
||||
|
||||
// the file was generated by us and the digest for each file was already computed, no need to validate it again.
|
||||
c.skipValidation = true
|
||||
// since we retrieved the whole file and it was validated, set the uncompressed digest.
|
||||
uncompressedDigest = diffID
|
||||
}
|
||||
|
||||
lcd := chunkedLayerData{
|
||||
|
|
@ -1557,11 +1714,8 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
Artifacts: map[string]interface{}{
|
||||
tocKey: toc,
|
||||
},
|
||||
TOCDigest: c.contentDigest,
|
||||
}
|
||||
|
||||
if !parseBooleanPullOption(c.storeOpts, "enable_partial_images", false) {
|
||||
return output, errors.New("enable_partial_images not configured")
|
||||
TOCDigest: c.tocDigest,
|
||||
UncompressedDigest: uncompressedDigest,
|
||||
}
|
||||
|
||||
// When the hard links deduplication is used, file attributes are ignored because setting them
|
||||
|
|
@ -1678,13 +1832,17 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
|
||||
mode := os.FileMode(r.Mode)
|
||||
|
||||
r.Name = filepath.Clean(r.Name)
|
||||
r.Linkname = filepath.Clean(r.Linkname)
|
||||
|
||||
t, err := typeToTarType(r.Type)
|
||||
if err != nil {
|
||||
return output, err
|
||||
}
|
||||
|
||||
r.Name = filepath.Clean(r.Name)
|
||||
// do not modify the value of symlinks
|
||||
if r.Linkname != "" && t != tar.TypeSymlink {
|
||||
r.Linkname = filepath.Clean(r.Linkname)
|
||||
}
|
||||
|
||||
if whiteoutConverter != nil {
|
||||
hdr := archivetar.Header{
|
||||
Typeflag: t,
|
||||
|
|
@ -1730,6 +1888,9 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
}
|
||||
|
||||
case tar.TypeDir:
|
||||
if r.Name == "" || r.Name == "." {
|
||||
output.RootDirMode = &mode
|
||||
}
|
||||
if err := safeMkdir(dirfd, mode, r.Name, &r, options); err != nil {
|
||||
return output, err
|
||||
}
|
||||
|
|
@ -1851,7 +2012,6 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
}
|
||||
// There are some missing files. Prepare a multirange request for the missing chunks.
|
||||
if len(missingParts) > 0 {
|
||||
missingParts = mergeMissingChunks(missingParts, maxNumberMissingChunks)
|
||||
if err := c.retrieveMissingFiles(stream, dest, dirfd, missingParts, options); err != nil {
|
||||
return output, err
|
||||
}
|
||||
|
|
@ -1867,6 +2027,8 @@ func (c *chunkedDiffer) ApplyDiff(dest string, options *archive.TarOptions, diff
|
|||
logrus.Debugf("Missing %d bytes out of %d (%.2f %%)", missingPartsSize, totalChunksSize, float32(missingPartsSize*100.0)/float32(totalChunksSize))
|
||||
}
|
||||
|
||||
output.Artifacts[fsVerityDigestsKey] = c.fsVerityDigests
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
|
|
@ -1926,7 +2088,10 @@ func (c *chunkedDiffer) mergeTocEntries(fileType compressedFileType, entries []i
|
|||
|
||||
e.Chunks = make([]*internal.FileMetadata, nChunks+1)
|
||||
for j := 0; j <= nChunks; j++ {
|
||||
e.Chunks[j] = &entries[i+j]
|
||||
// we need a copy here, otherwise we override the
|
||||
// .Size later
|
||||
copy := entries[i+j]
|
||||
e.Chunks[j] = ©
|
||||
e.EndOffset = entries[i+j].EndOffset
|
||||
}
|
||||
i += nChunks
|
||||
|
|
|
|||
3
vendor/github.com/containers/storage/pkg/chunked/storage_unsupported.go
generated
vendored
3
vendor/github.com/containers/storage/pkg/chunked/storage_unsupported.go
generated
vendored
|
|
@ -9,9 +9,10 @@ import (
|
|||
|
||||
storage "github.com/containers/storage"
|
||||
graphdriver "github.com/containers/storage/drivers"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// GetDiffer returns a differ than can be used with ApplyDiffWithDiffer.
|
||||
func GetDiffer(ctx context.Context, store storage.Store, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) {
|
||||
func GetDiffer(ctx context.Context, store storage.Store, blobDigest digest.Digest, blobSize int64, annotations map[string]string, iss ImageSourceSeekable) (graphdriver.Differ, error) {
|
||||
return nil, errors.New("format not supported on this system")
|
||||
}
|
||||
|
|
|
|||
41
vendor/github.com/containers/storage/pkg/chunked/toc/toc.go
generated
vendored
Normal file
41
vendor/github.com/containers/storage/pkg/chunked/toc/toc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package toc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/containers/storage/pkg/chunked/internal"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// tocJSONDigestAnnotation is the annotation key for the digest of the estargz
|
||||
// TOC JSON.
|
||||
// It is defined in github.com/containerd/stargz-snapshotter/estargz as TOCJSONDigestAnnotation
|
||||
// Duplicate it here to avoid a dependency on the package.
|
||||
const tocJSONDigestAnnotation = "containerd.io/snapshot/stargz/toc.digest"
|
||||
|
||||
// GetTOCDigest returns the digest of the TOC as recorded in the annotations.
|
||||
// This function retrieves a digest that represents the content of a
|
||||
// table of contents (TOC) from the image's annotations.
|
||||
// This is an experimental feature and may be changed/removed in the future.
|
||||
func GetTOCDigest(annotations map[string]string) (*digest.Digest, error) {
|
||||
d1, ok1 := annotations[tocJSONDigestAnnotation]
|
||||
d2, ok2 := annotations[internal.ManifestChecksumKey]
|
||||
switch {
|
||||
case ok1 && ok2:
|
||||
return nil, errors.New("both zstd:chunked and eStargz TOC found")
|
||||
case ok1:
|
||||
d, err := digest.Parse(d1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
case ok2:
|
||||
d, err := digest.Parse(d2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
9
vendor/github.com/containers/storage/pkg/config/config.go
generated
vendored
9
vendor/github.com/containers/storage/pkg/config/config.go
generated
vendored
|
|
@ -97,6 +97,8 @@ type OverlayOptionsConfig struct {
|
|||
Inodes string `toml:"inodes,omitempty"`
|
||||
// Do not create a bind mount on the storage home
|
||||
SkipMountHome string `toml:"skip_mount_home,omitempty"`
|
||||
// Specify whether composefs must be used to mount the data layers
|
||||
UseComposefs string `toml:"use_composefs,omitempty"`
|
||||
// ForceMask indicates the permissions mask (e.g. "0755") to use for new
|
||||
// files and directories
|
||||
ForceMask string `toml:"force_mask,omitempty"`
|
||||
|
|
@ -147,6 +149,9 @@ type OptionsConfig struct {
|
|||
// ignored when building an image.
|
||||
IgnoreChownErrors string `toml:"ignore_chown_errors,omitempty"`
|
||||
|
||||
// Specify whether composefs must be used to mount the data layers
|
||||
UseComposefs string `toml:"use_composefs,omitempty"`
|
||||
|
||||
// ForceMask indicates the permissions mask (e.g. "0755") to use for new
|
||||
// files and directories.
|
||||
ForceMask os.FileMode `toml:"force_mask,omitempty"`
|
||||
|
|
@ -283,6 +288,7 @@ func GetGraphDriverOptions(driverName string, options OptionsConfig) []string {
|
|||
}
|
||||
|
||||
case "overlay", "overlay2":
|
||||
// Specify whether composefs must be used to mount the data layers
|
||||
if options.Overlay.IgnoreChownErrors != "" {
|
||||
doptions = append(doptions, fmt.Sprintf("%s.ignore_chown_errors=%s", driverName, options.Overlay.IgnoreChownErrors))
|
||||
} else if options.IgnoreChownErrors != "" {
|
||||
|
|
@ -316,6 +322,9 @@ func GetGraphDriverOptions(driverName string, options OptionsConfig) []string {
|
|||
} else if options.ForceMask != 0 {
|
||||
doptions = append(doptions, fmt.Sprintf("%s.force_mask=%s", driverName, options.ForceMask))
|
||||
}
|
||||
if options.Overlay.UseComposefs != "" {
|
||||
doptions = append(doptions, fmt.Sprintf("%s.use_composefs=%s", driverName, options.Overlay.UseComposefs))
|
||||
}
|
||||
case "vfs":
|
||||
if options.Vfs.IgnoreChownErrors != "" {
|
||||
doptions = append(doptions, fmt.Sprintf("%s.ignore_chown_errors=%s", driverName, options.Vfs.IgnoreChownErrors))
|
||||
|
|
|
|||
45
vendor/github.com/containers/storage/pkg/fsverity/fsverity_linux.go
generated
vendored
Normal file
45
vendor/github.com/containers/storage/pkg/fsverity/fsverity_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package fsverity
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// verityDigest struct represents the digest used for verifying the integrity of a file.
|
||||
type verityDigest struct {
|
||||
Fsv unix.FsverityDigest
|
||||
Buf [64]byte
|
||||
}
|
||||
|
||||
// EnableVerity enables the verity feature on a file represented by the file descriptor 'fd'. The file must be opened
|
||||
// in read-only mode.
|
||||
// The 'description' parameter is a human-readable description of the file.
|
||||
func EnableVerity(description string, fd int) error {
|
||||
enableArg := unix.FsverityEnableArg{
|
||||
Version: 1,
|
||||
Hash_algorithm: unix.FS_VERITY_HASH_ALG_SHA256,
|
||||
Block_size: 4096,
|
||||
}
|
||||
|
||||
_, _, e1 := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.FS_IOC_ENABLE_VERITY), uintptr(unsafe.Pointer(&enableArg)))
|
||||
if e1 != 0 && !errors.Is(e1, unix.EEXIST) {
|
||||
return fmt.Errorf("failed to enable verity for %q: %w", description, e1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MeasureVerity measures and returns the verity digest for the file represented by 'fd'.
|
||||
// The 'description' parameter is a human-readable description of the file.
|
||||
func MeasureVerity(description string, fd int) (string, error) {
|
||||
var digest verityDigest
|
||||
digest.Fsv.Size = 64
|
||||
_, _, e1 := syscall.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.FS_IOC_MEASURE_VERITY), uintptr(unsafe.Pointer(&digest)))
|
||||
if e1 != 0 {
|
||||
return "", fmt.Errorf("failed to measure verity for %q: %w", description, e1)
|
||||
}
|
||||
return fmt.Sprintf("%x", digest.Buf[:digest.Fsv.Size]), nil
|
||||
}
|
||||
21
vendor/github.com/containers/storage/pkg/fsverity/fsverity_unsupported.go
generated
vendored
Normal file
21
vendor/github.com/containers/storage/pkg/fsverity/fsverity_unsupported.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package fsverity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// EnableVerity enables the verity feature on a file represented by the file descriptor 'fd'. The file must be opened
|
||||
// in read-only mode.
|
||||
// The 'description' parameter is a human-readable description of the file.
|
||||
func EnableVerity(description string, fd int) error {
|
||||
return fmt.Errorf("fs-verity is not supported on this platform")
|
||||
}
|
||||
|
||||
// MeasureVerity measures and returns the verity digest for the file represented by 'fd'.
|
||||
// The 'description' parameter is a human-readable description of the file.
|
||||
func MeasureVerity(description string, fd int) (string, error) {
|
||||
return "", fmt.Errorf("fs-verity is not supported on this platform")
|
||||
}
|
||||
15
vendor/github.com/containers/storage/pkg/homedir/homedir.go
generated
vendored
15
vendor/github.com/containers/storage/pkg/homedir/homedir.go
generated
vendored
|
|
@ -6,21 +6,6 @@ import (
|
|||
"path/filepath"
|
||||
)
|
||||
|
||||
// GetConfigHome returns XDG_CONFIG_HOME.
|
||||
// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetConfigHome() (string, error) {
|
||||
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
|
||||
return xdgConfigHome, nil
|
||||
}
|
||||
home := Get()
|
||||
if home == "" {
|
||||
return "", errors.New("could not get either XDG_CONFIG_HOME or HOME")
|
||||
}
|
||||
return filepath.Join(home, ".config"), nil
|
||||
}
|
||||
|
||||
// GetDataHome returns XDG_DATA_HOME.
|
||||
// GetDataHome returns $HOME/.local/share and nil error if XDG_DATA_HOME is not set.
|
||||
//
|
||||
|
|
|
|||
21
vendor/github.com/containers/storage/pkg/homedir/homedir_others.go
generated
vendored
21
vendor/github.com/containers/storage/pkg/homedir/homedir_others.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
//go:build !linux && !darwin && !freebsd
|
||||
// +build !linux,!darwin,!freebsd
|
||||
//go:build !linux && !darwin && !freebsd && !windows
|
||||
// +build !linux,!darwin,!freebsd,!windows
|
||||
|
||||
package homedir
|
||||
|
||||
|
|
@ -8,6 +8,8 @@ package homedir
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// GetRuntimeDir is unsupported on non-linux system.
|
||||
|
|
@ -19,3 +21,18 @@ func GetRuntimeDir() (string, error) {
|
|||
func StickRuntimeDirContents(files []string) ([]string, error) {
|
||||
return nil, errors.New("homedir.StickRuntimeDirContents() is not supported on this system")
|
||||
}
|
||||
|
||||
// GetConfigHome returns XDG_CONFIG_HOME.
|
||||
// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetConfigHome() (string, error) {
|
||||
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
|
||||
return xdgConfigHome, nil
|
||||
}
|
||||
home := Get()
|
||||
if home == "" {
|
||||
return "", errors.New("could not get either XDG_CONFIG_HOME or HOME")
|
||||
}
|
||||
return filepath.Join(home, ".config"), nil
|
||||
}
|
||||
|
|
|
|||
113
vendor/github.com/containers/storage/pkg/homedir/homedir_unix.go
generated
vendored
113
vendor/github.com/containers/storage/pkg/homedir/homedir_unix.go
generated
vendored
|
|
@ -7,12 +7,16 @@ package homedir
|
|||
// NOTE: this package has originally been copied from github.com/docker/docker.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/unshare"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Key returns the env var name for the user's home dir based on
|
||||
|
|
@ -40,18 +44,6 @@ func GetShortcutString() string {
|
|||
return "~"
|
||||
}
|
||||
|
||||
// GetRuntimeDir returns XDG_RUNTIME_DIR.
|
||||
// XDG_RUNTIME_DIR is typically configured via pam_systemd.
|
||||
// GetRuntimeDir returns non-nil error if XDG_RUNTIME_DIR is not set.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetRuntimeDir() (string, error) {
|
||||
if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" {
|
||||
return filepath.EvalSymlinks(xdgRuntimeDir)
|
||||
}
|
||||
return "", errors.New("could not get XDG_RUNTIME_DIR")
|
||||
}
|
||||
|
||||
// StickRuntimeDirContents sets the sticky bit on files that are under
|
||||
// XDG_RUNTIME_DIR, so that the files won't be periodically removed by the system.
|
||||
//
|
||||
|
|
@ -94,3 +86,98 @@ func stick(f string) error {
|
|||
m |= os.ModeSticky
|
||||
return os.Chmod(f, m)
|
||||
}
|
||||
|
||||
var (
|
||||
rootlessConfigHomeDirError error
|
||||
rootlessConfigHomeDirOnce sync.Once
|
||||
rootlessConfigHomeDir string
|
||||
rootlessRuntimeDirOnce sync.Once
|
||||
rootlessRuntimeDir string
|
||||
)
|
||||
|
||||
// isWriteableOnlyByOwner checks that the specified permission mask allows write
|
||||
// access only to the owner.
|
||||
func isWriteableOnlyByOwner(perm os.FileMode) bool {
|
||||
return (perm & 0o722) == 0o700
|
||||
}
|
||||
|
||||
// GetConfigHome returns XDG_CONFIG_HOME.
|
||||
// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetConfigHome() (string, error) {
|
||||
rootlessConfigHomeDirOnce.Do(func() {
|
||||
cfgHomeDir := os.Getenv("XDG_CONFIG_HOME")
|
||||
if cfgHomeDir == "" {
|
||||
home := Get()
|
||||
resolvedHome, err := filepath.EvalSymlinks(home)
|
||||
if err != nil {
|
||||
rootlessConfigHomeDirError = fmt.Errorf("cannot resolve %s: %w", home, err)
|
||||
return
|
||||
}
|
||||
tmpDir := filepath.Join(resolvedHome, ".config")
|
||||
_ = os.MkdirAll(tmpDir, 0o700)
|
||||
st, err := os.Stat(tmpDir)
|
||||
if err != nil {
|
||||
rootlessConfigHomeDirError = err
|
||||
return
|
||||
} else if int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() {
|
||||
cfgHomeDir = tmpDir
|
||||
} else {
|
||||
rootlessConfigHomeDirError = fmt.Errorf("path %q exists and it is not owned by the current user", tmpDir)
|
||||
return
|
||||
}
|
||||
}
|
||||
rootlessConfigHomeDir = cfgHomeDir
|
||||
})
|
||||
|
||||
return rootlessConfigHomeDir, rootlessConfigHomeDirError
|
||||
}
|
||||
|
||||
// GetRuntimeDir returns a directory suitable to store runtime files.
|
||||
// The function will try to use the XDG_RUNTIME_DIR env variable if it is set.
|
||||
// XDG_RUNTIME_DIR is typically configured via pam_systemd.
|
||||
// If XDG_RUNTIME_DIR is not set, GetRuntimeDir will try to find a suitable
|
||||
// directory for the current user.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetRuntimeDir() (string, error) {
|
||||
var rootlessRuntimeDirError error
|
||||
|
||||
rootlessRuntimeDirOnce.Do(func() {
|
||||
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
||||
|
||||
if runtimeDir != "" {
|
||||
rootlessRuntimeDir, rootlessRuntimeDirError = filepath.EvalSymlinks(runtimeDir)
|
||||
return
|
||||
}
|
||||
|
||||
uid := strconv.Itoa(unshare.GetRootlessUID())
|
||||
if runtimeDir == "" {
|
||||
tmpDir := filepath.Join("/run", "user", uid)
|
||||
if err := os.MkdirAll(tmpDir, 0o700); err != nil {
|
||||
logrus.Debug(err)
|
||||
}
|
||||
st, err := os.Lstat(tmpDir)
|
||||
if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && isWriteableOnlyByOwner(st.Mode().Perm()) {
|
||||
runtimeDir = tmpDir
|
||||
}
|
||||
}
|
||||
if runtimeDir == "" {
|
||||
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("storage-run-%s", uid))
|
||||
if err := os.MkdirAll(tmpDir, 0o700); err != nil {
|
||||
logrus.Debug(err)
|
||||
}
|
||||
st, err := os.Lstat(tmpDir)
|
||||
if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && isWriteableOnlyByOwner(st.Mode().Perm()) {
|
||||
runtimeDir = tmpDir
|
||||
} else {
|
||||
rootlessRuntimeDirError = fmt.Errorf("path %q exists and it is not writeable only by the current user", tmpDir)
|
||||
return
|
||||
}
|
||||
}
|
||||
rootlessRuntimeDir = runtimeDir
|
||||
})
|
||||
|
||||
return rootlessRuntimeDir, rootlessRuntimeDirError
|
||||
}
|
||||
|
|
|
|||
29
vendor/github.com/containers/storage/pkg/homedir/homedir_windows.go
generated
vendored
29
vendor/github.com/containers/storage/pkg/homedir/homedir_windows.go
generated
vendored
|
|
@ -5,6 +5,7 @@ package homedir
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Key returns the env var name for the user's home dir based on
|
||||
|
|
@ -25,8 +26,36 @@ func Get() string {
|
|||
return home
|
||||
}
|
||||
|
||||
// GetConfigHome returns the home directory of the current user with the help of
|
||||
// environment variables depending on the target operating system.
|
||||
// Returned path should be used with "path/filepath" to form new paths.
|
||||
func GetConfigHome() (string, error) {
|
||||
return filepath.Join(Get(), ".config"), nil
|
||||
}
|
||||
|
||||
// GetShortcutString returns the string that is shortcut to user's home directory
|
||||
// in the native shell of the platform running on.
|
||||
func GetShortcutString() string {
|
||||
return "%USERPROFILE%" // be careful while using in format functions
|
||||
}
|
||||
|
||||
// StickRuntimeDirContents is a no-op on Windows
|
||||
func StickRuntimeDirContents(files []string) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetRuntimeDir returns a directory suitable to store runtime files.
|
||||
// The function will try to use the XDG_RUNTIME_DIR env variable if it is set.
|
||||
// XDG_RUNTIME_DIR is typically configured via pam_systemd.
|
||||
// If XDG_RUNTIME_DIR is not set, GetRuntimeDir will try to find a suitable
|
||||
// directory for the current user.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetRuntimeDir() (string, error) {
|
||||
data, err := GetDataHome()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
runtimeDir := filepath.Join(data, "containers", "storage")
|
||||
return runtimeDir, nil
|
||||
}
|
||||
|
|
|
|||
2
vendor/github.com/containers/storage/pkg/idtools/idtools_unix.go
generated
vendored
2
vendor/github.com/containers/storage/pkg/idtools/idtools_unix.go
generated
vendored
|
|
@ -14,7 +14,7 @@ import (
|
|||
"syscall"
|
||||
|
||||
"github.com/containers/storage/pkg/system"
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
"github.com/moby/sys/user"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
2
vendor/github.com/containers/storage/pkg/unshare/unshare_unsupported.go
generated
vendored
2
vendor/github.com/containers/storage/pkg/unshare/unshare_unsupported.go
generated
vendored
|
|
@ -17,7 +17,7 @@ const (
|
|||
|
||||
// IsRootless tells us if we are running in rootless mode
|
||||
func IsRootless() bool {
|
||||
return false
|
||||
return os.Getuid() != 0
|
||||
}
|
||||
|
||||
// GetRootlessUID returns the UID of the user in the parent userNS
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue