mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 10:03:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			591 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			591 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
   Copyright The containerd 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 continuity
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"reflect"
 | 
						|
	"sort"
 | 
						|
 | 
						|
	pb "github.com/containerd/continuity/proto"
 | 
						|
	"github.com/opencontainers/go-digest"
 | 
						|
)
 | 
						|
 | 
						|
// TODO(stevvooe): A record based model, somewhat sketched out at the bottom
 | 
						|
// of this file, will be more flexible. Another possibly is to tie the package
 | 
						|
// interface directly to the protobuf type. This will have efficiency
 | 
						|
// advantages at the cost coupling the nasty codegen types to the exported
 | 
						|
// interface.
 | 
						|
 | 
						|
type Resource interface {
 | 
						|
	// Path provides the primary resource path relative to the bundle root. In
 | 
						|
	// cases where resources have more than one path, such as with hard links,
 | 
						|
	// this will return the primary path, which is often just the first entry.
 | 
						|
	Path() string
 | 
						|
 | 
						|
	// Mode returns the
 | 
						|
	Mode() os.FileMode
 | 
						|
 | 
						|
	UID() int64
 | 
						|
	GID() int64
 | 
						|
}
 | 
						|
 | 
						|
// ByPath provides the canonical sort order for a set of resources. Use with
 | 
						|
// sort.Stable for deterministic sorting.
 | 
						|
type ByPath []Resource
 | 
						|
 | 
						|
func (bp ByPath) Len() int           { return len(bp) }
 | 
						|
func (bp ByPath) Swap(i, j int)      { bp[i], bp[j] = bp[j], bp[i] }
 | 
						|
func (bp ByPath) Less(i, j int) bool { return bp[i].Path() < bp[j].Path() }
 | 
						|
 | 
						|
type XAttrer interface {
 | 
						|
	XAttrs() map[string][]byte
 | 
						|
}
 | 
						|
 | 
						|
// Hardlinkable is an interface that a resource type satisfies if it can be a
 | 
						|
// hardlink target.
 | 
						|
type Hardlinkable interface {
 | 
						|
	// Paths returns all paths of the resource, including the primary path
 | 
						|
	// returned by Resource.Path. If len(Paths()) > 1, the resource is a hard
 | 
						|
	// link.
 | 
						|
	Paths() []string
 | 
						|
}
 | 
						|
 | 
						|
type RegularFile interface {
 | 
						|
	Resource
 | 
						|
	XAttrer
 | 
						|
	Hardlinkable
 | 
						|
 | 
						|
	Size() int64
 | 
						|
	Digests() []digest.Digest
 | 
						|
}
 | 
						|
 | 
						|
// Merge two or more Resources into new file. Typically, this should be
 | 
						|
// used to merge regular files as hardlinks. If the files are not identical,
 | 
						|
// other than Paths and Digests, the merge will fail and an error will be
 | 
						|
// returned.
 | 
						|
func Merge(fs ...Resource) (Resource, error) {
 | 
						|
	if len(fs) < 1 {
 | 
						|
		return nil, fmt.Errorf("please provide a resource to merge")
 | 
						|
	}
 | 
						|
 | 
						|
	if len(fs) == 1 {
 | 
						|
		return fs[0], nil
 | 
						|
	}
 | 
						|
 | 
						|
	var paths []string
 | 
						|
	var digests []digest.Digest
 | 
						|
	bypath := map[string][]Resource{}
 | 
						|
 | 
						|
	// The attributes are all compared against the first to make sure they
 | 
						|
	// agree before adding to the above collections. If any of these don't
 | 
						|
	// correctly validate, the merge fails.
 | 
						|
	prototype := fs[0]
 | 
						|
	xattrs := make(map[string][]byte)
 | 
						|
 | 
						|
	// initialize xattrs for use below. All files must have same xattrs.
 | 
						|
	if prototypeXAttrer, ok := prototype.(XAttrer); ok {
 | 
						|
		for attr, value := range prototypeXAttrer.XAttrs() {
 | 
						|
			xattrs[attr] = value
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, f := range fs {
 | 
						|
		h, isHardlinkable := f.(Hardlinkable)
 | 
						|
		if !isHardlinkable {
 | 
						|
			return nil, errNotAHardLink
 | 
						|
		}
 | 
						|
 | 
						|
		if f.Mode() != prototype.Mode() {
 | 
						|
			return nil, fmt.Errorf("modes do not match: %v != %v", f.Mode(), prototype.Mode())
 | 
						|
		}
 | 
						|
 | 
						|
		if f.UID() != prototype.UID() {
 | 
						|
			return nil, fmt.Errorf("uid does not match: %v != %v", f.UID(), prototype.UID())
 | 
						|
		}
 | 
						|
 | 
						|
		if f.GID() != prototype.GID() {
 | 
						|
			return nil, fmt.Errorf("gid does not match: %v != %v", f.GID(), prototype.GID())
 | 
						|
		}
 | 
						|
 | 
						|
		if xattrer, ok := f.(XAttrer); ok {
 | 
						|
			fxattrs := xattrer.XAttrs()
 | 
						|
			if !reflect.DeepEqual(fxattrs, xattrs) {
 | 
						|
				return nil, fmt.Errorf("resource %q xattrs do not match: %v != %v", f, fxattrs, xattrs)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		for _, p := range h.Paths() {
 | 
						|
			pfs, ok := bypath[p]
 | 
						|
			if !ok {
 | 
						|
				// ensure paths are unique by only appending on a new path.
 | 
						|
				paths = append(paths, p)
 | 
						|
			}
 | 
						|
 | 
						|
			bypath[p] = append(pfs, f)
 | 
						|
		}
 | 
						|
 | 
						|
		if regFile, isRegFile := f.(RegularFile); isRegFile {
 | 
						|
			prototypeRegFile, prototypeIsRegFile := prototype.(RegularFile)
 | 
						|
			if !prototypeIsRegFile {
 | 
						|
				return nil, errors.New("prototype is not a regular file")
 | 
						|
			}
 | 
						|
 | 
						|
			if regFile.Size() != prototypeRegFile.Size() {
 | 
						|
				return nil, fmt.Errorf("size does not match: %v != %v", regFile.Size(), prototypeRegFile.Size())
 | 
						|
			}
 | 
						|
 | 
						|
			digests = append(digests, regFile.Digests()...)
 | 
						|
		} else if device, isDevice := f.(Device); isDevice {
 | 
						|
			prototypeDevice, prototypeIsDevice := prototype.(Device)
 | 
						|
			if !prototypeIsDevice {
 | 
						|
				return nil, errors.New("prototype is not a device")
 | 
						|
			}
 | 
						|
 | 
						|
			if device.Major() != prototypeDevice.Major() {
 | 
						|
				return nil, fmt.Errorf("major number does not match: %v != %v", device.Major(), prototypeDevice.Major())
 | 
						|
			}
 | 
						|
			if device.Minor() != prototypeDevice.Minor() {
 | 
						|
				return nil, fmt.Errorf("minor number does not match: %v != %v", device.Minor(), prototypeDevice.Minor())
 | 
						|
			}
 | 
						|
		} else if _, isNamedPipe := f.(NamedPipe); isNamedPipe {
 | 
						|
			_, prototypeIsNamedPipe := prototype.(NamedPipe)
 | 
						|
			if !prototypeIsNamedPipe {
 | 
						|
				return nil, errors.New("prototype is not a named pipe")
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			return nil, errNotAHardLink
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	sort.Stable(sort.StringSlice(paths))
 | 
						|
 | 
						|
	// Choose a "canonical" file. Really, it is just the first file to sort
 | 
						|
	// against. We also effectively select the very first digest as the
 | 
						|
	// "canonical" one for this file.
 | 
						|
	first := bypath[paths[0]][0]
 | 
						|
 | 
						|
	resource := resource{
 | 
						|
		paths:  paths,
 | 
						|
		mode:   first.Mode(),
 | 
						|
		uid:    first.UID(),
 | 
						|
		gid:    first.GID(),
 | 
						|
		xattrs: xattrs,
 | 
						|
	}
 | 
						|
 | 
						|
	switch typedF := first.(type) {
 | 
						|
	case RegularFile:
 | 
						|
		var err error
 | 
						|
		digests, err = uniqifyDigests(digests...)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		return ®ularFile{
 | 
						|
			resource: resource,
 | 
						|
			size:     typedF.Size(),
 | 
						|
			digests:  digests,
 | 
						|
		}, nil
 | 
						|
	case Device:
 | 
						|
		return &device{
 | 
						|
			resource: resource,
 | 
						|
			major:    typedF.Major(),
 | 
						|
			minor:    typedF.Minor(),
 | 
						|
		}, nil
 | 
						|
 | 
						|
	case NamedPipe:
 | 
						|
		return &namedPipe{
 | 
						|
			resource: resource,
 | 
						|
		}, nil
 | 
						|
 | 
						|
	default:
 | 
						|
		return nil, errNotAHardLink
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
type Directory interface {
 | 
						|
	Resource
 | 
						|
	XAttrer
 | 
						|
 | 
						|
	// Directory is a no-op method to identify directory objects by interface.
 | 
						|
	Directory()
 | 
						|
}
 | 
						|
 | 
						|
type SymLink interface {
 | 
						|
	Resource
 | 
						|
 | 
						|
	// Target returns the target of the symlink contained in the .
 | 
						|
	Target() string
 | 
						|
}
 | 
						|
 | 
						|
type NamedPipe interface {
 | 
						|
	Resource
 | 
						|
	Hardlinkable
 | 
						|
	XAttrer
 | 
						|
 | 
						|
	// Pipe is a no-op method to allow consistent resolution of NamedPipe
 | 
						|
	// interface.
 | 
						|
	Pipe()
 | 
						|
}
 | 
						|
 | 
						|
type Device interface {
 | 
						|
	Resource
 | 
						|
	Hardlinkable
 | 
						|
	XAttrer
 | 
						|
 | 
						|
	Major() uint64
 | 
						|
	Minor() uint64
 | 
						|
}
 | 
						|
 | 
						|
type resource struct {
 | 
						|
	paths    []string
 | 
						|
	mode     os.FileMode
 | 
						|
	uid, gid int64
 | 
						|
	xattrs   map[string][]byte
 | 
						|
}
 | 
						|
 | 
						|
var _ Resource = &resource{}
 | 
						|
 | 
						|
func (r *resource) Path() string {
 | 
						|
	if len(r.paths) < 1 {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
 | 
						|
	return r.paths[0]
 | 
						|
}
 | 
						|
 | 
						|
func (r *resource) Mode() os.FileMode {
 | 
						|
	return r.mode
 | 
						|
}
 | 
						|
 | 
						|
func (r *resource) UID() int64 {
 | 
						|
	return r.uid
 | 
						|
}
 | 
						|
 | 
						|
func (r *resource) GID() int64 {
 | 
						|
	return r.gid
 | 
						|
}
 | 
						|
 | 
						|
type regularFile struct {
 | 
						|
	resource
 | 
						|
	size    int64
 | 
						|
	digests []digest.Digest
 | 
						|
}
 | 
						|
 | 
						|
var _ RegularFile = ®ularFile{}
 | 
						|
 | 
						|
// newRegularFile returns the RegularFile, using the populated base resource
 | 
						|
// and one or more digests of the content.
 | 
						|
func newRegularFile(base resource, paths []string, size int64, dgsts ...digest.Digest) (RegularFile, error) {
 | 
						|
	if !base.Mode().IsRegular() {
 | 
						|
		return nil, fmt.Errorf("not a regular file")
 | 
						|
	}
 | 
						|
 | 
						|
	base.paths = make([]string, len(paths))
 | 
						|
	copy(base.paths, paths)
 | 
						|
 | 
						|
	// make our own copy of digests
 | 
						|
	ds := make([]digest.Digest, len(dgsts))
 | 
						|
	copy(ds, dgsts)
 | 
						|
 | 
						|
	return ®ularFile{
 | 
						|
		resource: base,
 | 
						|
		size:     size,
 | 
						|
		digests:  ds,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (rf *regularFile) Paths() []string {
 | 
						|
	paths := make([]string, len(rf.paths))
 | 
						|
	copy(paths, rf.paths)
 | 
						|
	return paths
 | 
						|
}
 | 
						|
 | 
						|
func (rf *regularFile) Size() int64 {
 | 
						|
	return rf.size
 | 
						|
}
 | 
						|
 | 
						|
func (rf *regularFile) Digests() []digest.Digest {
 | 
						|
	digests := make([]digest.Digest, len(rf.digests))
 | 
						|
	copy(digests, rf.digests)
 | 
						|
	return digests
 | 
						|
}
 | 
						|
 | 
						|
func (rf *regularFile) XAttrs() map[string][]byte {
 | 
						|
	xattrs := make(map[string][]byte, len(rf.xattrs))
 | 
						|
 | 
						|
	for attr, value := range rf.xattrs {
 | 
						|
		xattrs[attr] = append(xattrs[attr], value...)
 | 
						|
	}
 | 
						|
 | 
						|
	return xattrs
 | 
						|
}
 | 
						|
 | 
						|
type directory struct {
 | 
						|
	resource
 | 
						|
}
 | 
						|
 | 
						|
var _ Directory = &directory{}
 | 
						|
 | 
						|
func newDirectory(base resource) (Directory, error) {
 | 
						|
	if !base.Mode().IsDir() {
 | 
						|
		return nil, fmt.Errorf("not a directory")
 | 
						|
	}
 | 
						|
 | 
						|
	return &directory{
 | 
						|
		resource: base,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (d *directory) Directory() {}
 | 
						|
 | 
						|
func (d *directory) XAttrs() map[string][]byte {
 | 
						|
	xattrs := make(map[string][]byte, len(d.xattrs))
 | 
						|
 | 
						|
	for attr, value := range d.xattrs {
 | 
						|
		xattrs[attr] = append(xattrs[attr], value...)
 | 
						|
	}
 | 
						|
 | 
						|
	return xattrs
 | 
						|
}
 | 
						|
 | 
						|
type symLink struct {
 | 
						|
	resource
 | 
						|
	target string
 | 
						|
}
 | 
						|
 | 
						|
var _ SymLink = &symLink{}
 | 
						|
 | 
						|
func newSymLink(base resource, target string) (SymLink, error) {
 | 
						|
	if base.Mode()&os.ModeSymlink == 0 {
 | 
						|
		return nil, fmt.Errorf("not a symlink")
 | 
						|
	}
 | 
						|
 | 
						|
	return &symLink{
 | 
						|
		resource: base,
 | 
						|
		target:   target,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (l *symLink) Target() string {
 | 
						|
	return l.target
 | 
						|
}
 | 
						|
 | 
						|
type namedPipe struct {
 | 
						|
	resource
 | 
						|
}
 | 
						|
 | 
						|
var _ NamedPipe = &namedPipe{}
 | 
						|
 | 
						|
func newNamedPipe(base resource, paths []string) (NamedPipe, error) {
 | 
						|
	if base.Mode()&os.ModeNamedPipe == 0 {
 | 
						|
		return nil, fmt.Errorf("not a namedpipe")
 | 
						|
	}
 | 
						|
 | 
						|
	base.paths = make([]string, len(paths))
 | 
						|
	copy(base.paths, paths)
 | 
						|
 | 
						|
	return &namedPipe{
 | 
						|
		resource: base,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (np *namedPipe) Pipe() {}
 | 
						|
 | 
						|
func (np *namedPipe) Paths() []string {
 | 
						|
	paths := make([]string, len(np.paths))
 | 
						|
	copy(paths, np.paths)
 | 
						|
	return paths
 | 
						|
}
 | 
						|
 | 
						|
func (np *namedPipe) XAttrs() map[string][]byte {
 | 
						|
	xattrs := make(map[string][]byte, len(np.xattrs))
 | 
						|
 | 
						|
	for attr, value := range np.xattrs {
 | 
						|
		xattrs[attr] = append(xattrs[attr], value...)
 | 
						|
	}
 | 
						|
 | 
						|
	return xattrs
 | 
						|
}
 | 
						|
 | 
						|
type device struct {
 | 
						|
	resource
 | 
						|
	major, minor uint64
 | 
						|
}
 | 
						|
 | 
						|
var _ Device = &device{}
 | 
						|
 | 
						|
func newDevice(base resource, paths []string, major, minor uint64) (Device, error) {
 | 
						|
	if base.Mode()&os.ModeDevice == 0 {
 | 
						|
		return nil, fmt.Errorf("not a device")
 | 
						|
	}
 | 
						|
 | 
						|
	base.paths = make([]string, len(paths))
 | 
						|
	copy(base.paths, paths)
 | 
						|
 | 
						|
	return &device{
 | 
						|
		resource: base,
 | 
						|
		major:    major,
 | 
						|
		minor:    minor,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (d *device) Paths() []string {
 | 
						|
	paths := make([]string, len(d.paths))
 | 
						|
	copy(paths, d.paths)
 | 
						|
	return paths
 | 
						|
}
 | 
						|
 | 
						|
func (d *device) XAttrs() map[string][]byte {
 | 
						|
	xattrs := make(map[string][]byte, len(d.xattrs))
 | 
						|
 | 
						|
	for attr, value := range d.xattrs {
 | 
						|
		xattrs[attr] = append(xattrs[attr], value...)
 | 
						|
	}
 | 
						|
 | 
						|
	return xattrs
 | 
						|
}
 | 
						|
 | 
						|
func (d device) Major() uint64 {
 | 
						|
	return d.major
 | 
						|
}
 | 
						|
 | 
						|
func (d device) Minor() uint64 {
 | 
						|
	return d.minor
 | 
						|
}
 | 
						|
 | 
						|
// toProto converts a resource to a protobuf record. We'd like to push this
 | 
						|
// the individual types but we want to keep this all together during
 | 
						|
// prototyping.
 | 
						|
func toProto(resource Resource) *pb.Resource {
 | 
						|
	b := &pb.Resource{
 | 
						|
		Path: []string{resource.Path()},
 | 
						|
		Mode: uint32(resource.Mode()),
 | 
						|
		Uid:  resource.UID(),
 | 
						|
		Gid:  resource.GID(),
 | 
						|
	}
 | 
						|
 | 
						|
	if xattrer, ok := resource.(XAttrer); ok {
 | 
						|
		// Sorts the XAttrs by name for consistent ordering.
 | 
						|
		keys := []string{}
 | 
						|
		xattrs := xattrer.XAttrs()
 | 
						|
		for k := range xattrs {
 | 
						|
			keys = append(keys, k)
 | 
						|
		}
 | 
						|
		sort.Strings(keys)
 | 
						|
 | 
						|
		for _, k := range keys {
 | 
						|
			b.Xattr = append(b.Xattr, &pb.XAttr{Name: k, Data: xattrs[k]})
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	switch r := resource.(type) {
 | 
						|
	case RegularFile:
 | 
						|
		b.Path = r.Paths()
 | 
						|
		b.Size = uint64(r.Size())
 | 
						|
 | 
						|
		for _, dgst := range r.Digests() {
 | 
						|
			b.Digest = append(b.Digest, dgst.String())
 | 
						|
		}
 | 
						|
	case SymLink:
 | 
						|
		b.Target = r.Target()
 | 
						|
	case Device:
 | 
						|
		b.Major, b.Minor = r.Major(), r.Minor()
 | 
						|
		b.Path = r.Paths()
 | 
						|
	case NamedPipe:
 | 
						|
		b.Path = r.Paths()
 | 
						|
	}
 | 
						|
 | 
						|
	// enforce a few stability guarantees that may not be provided by the
 | 
						|
	// resource implementation.
 | 
						|
	sort.Strings(b.Path)
 | 
						|
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// fromProto converts from a protobuf Resource to a Resource interface.
 | 
						|
func fromProto(b *pb.Resource) (Resource, error) {
 | 
						|
	base := &resource{
 | 
						|
		paths: b.Path,
 | 
						|
		mode:  os.FileMode(b.Mode),
 | 
						|
		uid:   b.Uid,
 | 
						|
		gid:   b.Gid,
 | 
						|
	}
 | 
						|
 | 
						|
	base.xattrs = make(map[string][]byte, len(b.Xattr))
 | 
						|
 | 
						|
	for _, attr := range b.Xattr {
 | 
						|
		base.xattrs[attr.Name] = attr.Data
 | 
						|
	}
 | 
						|
 | 
						|
	switch {
 | 
						|
	case base.Mode().IsRegular():
 | 
						|
		dgsts := make([]digest.Digest, len(b.Digest))
 | 
						|
		for i, dgst := range b.Digest {
 | 
						|
			// TODO(stevvooe): Should we be validating at this point?
 | 
						|
			dgsts[i] = digest.Digest(dgst)
 | 
						|
		}
 | 
						|
 | 
						|
		return newRegularFile(*base, b.Path, int64(b.Size), dgsts...)
 | 
						|
	case base.Mode().IsDir():
 | 
						|
		return newDirectory(*base)
 | 
						|
	case base.Mode()&os.ModeSymlink != 0:
 | 
						|
		return newSymLink(*base, b.Target)
 | 
						|
	case base.Mode()&os.ModeNamedPipe != 0:
 | 
						|
		return newNamedPipe(*base, b.Path)
 | 
						|
	case base.Mode()&os.ModeDevice != 0:
 | 
						|
		return newDevice(*base, b.Path, b.Major, b.Minor)
 | 
						|
	}
 | 
						|
 | 
						|
	return nil, fmt.Errorf("unknown resource record (%#v): %s", b, base.Mode())
 | 
						|
}
 | 
						|
 | 
						|
// NOTE(stevvooe): An alternative model that supports inline declaration.
 | 
						|
// Convenient for unit testing where inline declarations may be desirable but
 | 
						|
// creates an awkward API for the standard use case.
 | 
						|
 | 
						|
// type ResourceKind int
 | 
						|
 | 
						|
// const (
 | 
						|
// 	ResourceRegularFile = iota + 1
 | 
						|
// 	ResourceDirectory
 | 
						|
// 	ResourceSymLink
 | 
						|
// 	Resource
 | 
						|
// )
 | 
						|
 | 
						|
// type Resource struct {
 | 
						|
// 	Kind         ResourceKind
 | 
						|
// 	Paths        []string
 | 
						|
// 	Mode         os.FileMode
 | 
						|
// 	UID          string
 | 
						|
// 	GID          string
 | 
						|
// 	Size         int64
 | 
						|
// 	Digests      []digest.Digest
 | 
						|
// 	Target       string
 | 
						|
// 	Major, Minor int
 | 
						|
// 	XAttrs       map[string][]byte
 | 
						|
// }
 | 
						|
 | 
						|
// type RegularFile struct {
 | 
						|
// 	Paths   []string
 | 
						|
//  Size 	int64
 | 
						|
// 	Digests []digest.Digest
 | 
						|
// 	Perm    os.FileMode // os.ModePerm + sticky, setuid, setgid
 | 
						|
// }
 |