123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- /*
- 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
- // }
|