...

Source file src/github.com/containerd/continuity/resource.go

Documentation: github.com/containerd/continuity

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package continuity
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"reflect"
    24  	"sort"
    25  
    26  	pb "github.com/containerd/continuity/proto"
    27  	"github.com/opencontainers/go-digest"
    28  )
    29  
    30  // TODO(stevvooe): A record based model, somewhat sketched out at the bottom
    31  // of this file, will be more flexible. Another possibly is to tie the package
    32  // interface directly to the protobuf type. This will have efficiency
    33  // advantages at the cost coupling the nasty codegen types to the exported
    34  // interface.
    35  
    36  type Resource interface {
    37  	// Path provides the primary resource path relative to the bundle root. In
    38  	// cases where resources have more than one path, such as with hard links,
    39  	// this will return the primary path, which is often just the first entry.
    40  	Path() string
    41  
    42  	// Mode returns the
    43  	Mode() os.FileMode
    44  
    45  	UID() int64
    46  	GID() int64
    47  }
    48  
    49  // ByPath provides the canonical sort order for a set of resources. Use with
    50  // sort.Stable for deterministic sorting.
    51  type ByPath []Resource
    52  
    53  func (bp ByPath) Len() int           { return len(bp) }
    54  func (bp ByPath) Swap(i, j int)      { bp[i], bp[j] = bp[j], bp[i] }
    55  func (bp ByPath) Less(i, j int) bool { return bp[i].Path() < bp[j].Path() }
    56  
    57  type XAttrer interface {
    58  	XAttrs() map[string][]byte
    59  }
    60  
    61  // Hardlinkable is an interface that a resource type satisfies if it can be a
    62  // hardlink target.
    63  type Hardlinkable interface {
    64  	// Paths returns all paths of the resource, including the primary path
    65  	// returned by Resource.Path. If len(Paths()) > 1, the resource is a hard
    66  	// link.
    67  	Paths() []string
    68  }
    69  
    70  type RegularFile interface {
    71  	Resource
    72  	XAttrer
    73  	Hardlinkable
    74  
    75  	Size() int64
    76  	Digests() []digest.Digest
    77  }
    78  
    79  // Merge two or more Resources into new file. Typically, this should be
    80  // used to merge regular files as hardlinks. If the files are not identical,
    81  // other than Paths and Digests, the merge will fail and an error will be
    82  // returned.
    83  func Merge(fs ...Resource) (Resource, error) {
    84  	if len(fs) < 1 {
    85  		return nil, fmt.Errorf("please provide a resource to merge")
    86  	}
    87  
    88  	if len(fs) == 1 {
    89  		return fs[0], nil
    90  	}
    91  
    92  	var paths []string
    93  	var digests []digest.Digest
    94  	bypath := map[string][]Resource{}
    95  
    96  	// The attributes are all compared against the first to make sure they
    97  	// agree before adding to the above collections. If any of these don't
    98  	// correctly validate, the merge fails.
    99  	prototype := fs[0]
   100  	xattrs := make(map[string][]byte)
   101  
   102  	// initialize xattrs for use below. All files must have same xattrs.
   103  	if prototypeXAttrer, ok := prototype.(XAttrer); ok {
   104  		for attr, value := range prototypeXAttrer.XAttrs() {
   105  			xattrs[attr] = value
   106  		}
   107  	}
   108  
   109  	for _, f := range fs {
   110  		h, isHardlinkable := f.(Hardlinkable)
   111  		if !isHardlinkable {
   112  			return nil, errNotAHardLink
   113  		}
   114  
   115  		if f.Mode() != prototype.Mode() {
   116  			return nil, fmt.Errorf("modes do not match: %v != %v", f.Mode(), prototype.Mode())
   117  		}
   118  
   119  		if f.UID() != prototype.UID() {
   120  			return nil, fmt.Errorf("uid does not match: %v != %v", f.UID(), prototype.UID())
   121  		}
   122  
   123  		if f.GID() != prototype.GID() {
   124  			return nil, fmt.Errorf("gid does not match: %v != %v", f.GID(), prototype.GID())
   125  		}
   126  
   127  		if xattrer, ok := f.(XAttrer); ok {
   128  			fxattrs := xattrer.XAttrs()
   129  			if !reflect.DeepEqual(fxattrs, xattrs) {
   130  				return nil, fmt.Errorf("resource %q xattrs do not match: %v != %v", f, fxattrs, xattrs)
   131  			}
   132  		}
   133  
   134  		for _, p := range h.Paths() {
   135  			pfs, ok := bypath[p]
   136  			if !ok {
   137  				// ensure paths are unique by only appending on a new path.
   138  				paths = append(paths, p)
   139  			}
   140  
   141  			bypath[p] = append(pfs, f)
   142  		}
   143  
   144  		if regFile, isRegFile := f.(RegularFile); isRegFile {
   145  			prototypeRegFile, prototypeIsRegFile := prototype.(RegularFile)
   146  			if !prototypeIsRegFile {
   147  				return nil, errors.New("prototype is not a regular file")
   148  			}
   149  
   150  			if regFile.Size() != prototypeRegFile.Size() {
   151  				return nil, fmt.Errorf("size does not match: %v != %v", regFile.Size(), prototypeRegFile.Size())
   152  			}
   153  
   154  			digests = append(digests, regFile.Digests()...)
   155  		} else if device, isDevice := f.(Device); isDevice {
   156  			prototypeDevice, prototypeIsDevice := prototype.(Device)
   157  			if !prototypeIsDevice {
   158  				return nil, errors.New("prototype is not a device")
   159  			}
   160  
   161  			if device.Major() != prototypeDevice.Major() {
   162  				return nil, fmt.Errorf("major number does not match: %v != %v", device.Major(), prototypeDevice.Major())
   163  			}
   164  			if device.Minor() != prototypeDevice.Minor() {
   165  				return nil, fmt.Errorf("minor number does not match: %v != %v", device.Minor(), prototypeDevice.Minor())
   166  			}
   167  		} else if _, isNamedPipe := f.(NamedPipe); isNamedPipe {
   168  			_, prototypeIsNamedPipe := prototype.(NamedPipe)
   169  			if !prototypeIsNamedPipe {
   170  				return nil, errors.New("prototype is not a named pipe")
   171  			}
   172  		} else {
   173  			return nil, errNotAHardLink
   174  		}
   175  	}
   176  
   177  	sort.Stable(sort.StringSlice(paths))
   178  
   179  	// Choose a "canonical" file. Really, it is just the first file to sort
   180  	// against. We also effectively select the very first digest as the
   181  	// "canonical" one for this file.
   182  	first := bypath[paths[0]][0]
   183  
   184  	resource := resource{
   185  		paths:  paths,
   186  		mode:   first.Mode(),
   187  		uid:    first.UID(),
   188  		gid:    first.GID(),
   189  		xattrs: xattrs,
   190  	}
   191  
   192  	switch typedF := first.(type) {
   193  	case RegularFile:
   194  		var err error
   195  		digests, err = uniqifyDigests(digests...)
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  
   200  		return &regularFile{
   201  			resource: resource,
   202  			size:     typedF.Size(),
   203  			digests:  digests,
   204  		}, nil
   205  	case Device:
   206  		return &device{
   207  			resource: resource,
   208  			major:    typedF.Major(),
   209  			minor:    typedF.Minor(),
   210  		}, nil
   211  
   212  	case NamedPipe:
   213  		return &namedPipe{
   214  			resource: resource,
   215  		}, nil
   216  
   217  	default:
   218  		return nil, errNotAHardLink
   219  	}
   220  }
   221  
   222  type Directory interface {
   223  	Resource
   224  	XAttrer
   225  
   226  	// Directory is a no-op method to identify directory objects by interface.
   227  	Directory()
   228  }
   229  
   230  type SymLink interface {
   231  	Resource
   232  
   233  	// Target returns the target of the symlink contained in the .
   234  	Target() string
   235  }
   236  
   237  type NamedPipe interface {
   238  	Resource
   239  	Hardlinkable
   240  	XAttrer
   241  
   242  	// Pipe is a no-op method to allow consistent resolution of NamedPipe
   243  	// interface.
   244  	Pipe()
   245  }
   246  
   247  type Device interface {
   248  	Resource
   249  	Hardlinkable
   250  	XAttrer
   251  
   252  	Major() uint64
   253  	Minor() uint64
   254  }
   255  
   256  type resource struct {
   257  	paths    []string
   258  	mode     os.FileMode
   259  	uid, gid int64
   260  	xattrs   map[string][]byte
   261  }
   262  
   263  var _ Resource = &resource{}
   264  
   265  func (r *resource) Path() string {
   266  	if len(r.paths) < 1 {
   267  		return ""
   268  	}
   269  
   270  	return r.paths[0]
   271  }
   272  
   273  func (r *resource) Mode() os.FileMode {
   274  	return r.mode
   275  }
   276  
   277  func (r *resource) UID() int64 {
   278  	return r.uid
   279  }
   280  
   281  func (r *resource) GID() int64 {
   282  	return r.gid
   283  }
   284  
   285  type regularFile struct {
   286  	resource
   287  	size    int64
   288  	digests []digest.Digest
   289  }
   290  
   291  var _ RegularFile = &regularFile{}
   292  
   293  // newRegularFile returns the RegularFile, using the populated base resource
   294  // and one or more digests of the content.
   295  func newRegularFile(base resource, paths []string, size int64, dgsts ...digest.Digest) (RegularFile, error) {
   296  	if !base.Mode().IsRegular() {
   297  		return nil, fmt.Errorf("not a regular file")
   298  	}
   299  
   300  	base.paths = make([]string, len(paths))
   301  	copy(base.paths, paths)
   302  
   303  	// make our own copy of digests
   304  	ds := make([]digest.Digest, len(dgsts))
   305  	copy(ds, dgsts)
   306  
   307  	return &regularFile{
   308  		resource: base,
   309  		size:     size,
   310  		digests:  ds,
   311  	}, nil
   312  }
   313  
   314  func (rf *regularFile) Paths() []string {
   315  	paths := make([]string, len(rf.paths))
   316  	copy(paths, rf.paths)
   317  	return paths
   318  }
   319  
   320  func (rf *regularFile) Size() int64 {
   321  	return rf.size
   322  }
   323  
   324  func (rf *regularFile) Digests() []digest.Digest {
   325  	digests := make([]digest.Digest, len(rf.digests))
   326  	copy(digests, rf.digests)
   327  	return digests
   328  }
   329  
   330  func (rf *regularFile) XAttrs() map[string][]byte {
   331  	xattrs := make(map[string][]byte, len(rf.xattrs))
   332  
   333  	for attr, value := range rf.xattrs {
   334  		xattrs[attr] = append(xattrs[attr], value...)
   335  	}
   336  
   337  	return xattrs
   338  }
   339  
   340  type directory struct {
   341  	resource
   342  }
   343  
   344  var _ Directory = &directory{}
   345  
   346  func newDirectory(base resource) (Directory, error) {
   347  	if !base.Mode().IsDir() {
   348  		return nil, fmt.Errorf("not a directory")
   349  	}
   350  
   351  	return &directory{
   352  		resource: base,
   353  	}, nil
   354  }
   355  
   356  func (d *directory) Directory() {}
   357  
   358  func (d *directory) XAttrs() map[string][]byte {
   359  	xattrs := make(map[string][]byte, len(d.xattrs))
   360  
   361  	for attr, value := range d.xattrs {
   362  		xattrs[attr] = append(xattrs[attr], value...)
   363  	}
   364  
   365  	return xattrs
   366  }
   367  
   368  type symLink struct {
   369  	resource
   370  	target string
   371  }
   372  
   373  var _ SymLink = &symLink{}
   374  
   375  func newSymLink(base resource, target string) (SymLink, error) {
   376  	if base.Mode()&os.ModeSymlink == 0 {
   377  		return nil, fmt.Errorf("not a symlink")
   378  	}
   379  
   380  	return &symLink{
   381  		resource: base,
   382  		target:   target,
   383  	}, nil
   384  }
   385  
   386  func (l *symLink) Target() string {
   387  	return l.target
   388  }
   389  
   390  type namedPipe struct {
   391  	resource
   392  }
   393  
   394  var _ NamedPipe = &namedPipe{}
   395  
   396  func newNamedPipe(base resource, paths []string) (NamedPipe, error) {
   397  	if base.Mode()&os.ModeNamedPipe == 0 {
   398  		return nil, fmt.Errorf("not a namedpipe")
   399  	}
   400  
   401  	base.paths = make([]string, len(paths))
   402  	copy(base.paths, paths)
   403  
   404  	return &namedPipe{
   405  		resource: base,
   406  	}, nil
   407  }
   408  
   409  func (np *namedPipe) Pipe() {}
   410  
   411  func (np *namedPipe) Paths() []string {
   412  	paths := make([]string, len(np.paths))
   413  	copy(paths, np.paths)
   414  	return paths
   415  }
   416  
   417  func (np *namedPipe) XAttrs() map[string][]byte {
   418  	xattrs := make(map[string][]byte, len(np.xattrs))
   419  
   420  	for attr, value := range np.xattrs {
   421  		xattrs[attr] = append(xattrs[attr], value...)
   422  	}
   423  
   424  	return xattrs
   425  }
   426  
   427  type device struct {
   428  	resource
   429  	major, minor uint64
   430  }
   431  
   432  var _ Device = &device{}
   433  
   434  func newDevice(base resource, paths []string, major, minor uint64) (Device, error) {
   435  	if base.Mode()&os.ModeDevice == 0 {
   436  		return nil, fmt.Errorf("not a device")
   437  	}
   438  
   439  	base.paths = make([]string, len(paths))
   440  	copy(base.paths, paths)
   441  
   442  	return &device{
   443  		resource: base,
   444  		major:    major,
   445  		minor:    minor,
   446  	}, nil
   447  }
   448  
   449  func (d *device) Paths() []string {
   450  	paths := make([]string, len(d.paths))
   451  	copy(paths, d.paths)
   452  	return paths
   453  }
   454  
   455  func (d *device) XAttrs() map[string][]byte {
   456  	xattrs := make(map[string][]byte, len(d.xattrs))
   457  
   458  	for attr, value := range d.xattrs {
   459  		xattrs[attr] = append(xattrs[attr], value...)
   460  	}
   461  
   462  	return xattrs
   463  }
   464  
   465  func (d device) Major() uint64 {
   466  	return d.major
   467  }
   468  
   469  func (d device) Minor() uint64 {
   470  	return d.minor
   471  }
   472  
   473  // toProto converts a resource to a protobuf record. We'd like to push this
   474  // the individual types but we want to keep this all together during
   475  // prototyping.
   476  func toProto(resource Resource) *pb.Resource {
   477  	b := &pb.Resource{
   478  		Path: []string{resource.Path()},
   479  		Mode: uint32(resource.Mode()),
   480  		Uid:  resource.UID(),
   481  		Gid:  resource.GID(),
   482  	}
   483  
   484  	if xattrer, ok := resource.(XAttrer); ok {
   485  		// Sorts the XAttrs by name for consistent ordering.
   486  		keys := []string{}
   487  		xattrs := xattrer.XAttrs()
   488  		for k := range xattrs {
   489  			keys = append(keys, k)
   490  		}
   491  		sort.Strings(keys)
   492  
   493  		for _, k := range keys {
   494  			b.Xattr = append(b.Xattr, &pb.XAttr{Name: k, Data: xattrs[k]})
   495  		}
   496  	}
   497  
   498  	switch r := resource.(type) {
   499  	case RegularFile:
   500  		b.Path = r.Paths()
   501  		b.Size = uint64(r.Size())
   502  
   503  		for _, dgst := range r.Digests() {
   504  			b.Digest = append(b.Digest, dgst.String())
   505  		}
   506  	case SymLink:
   507  		b.Target = r.Target()
   508  	case Device:
   509  		b.Major, b.Minor = r.Major(), r.Minor()
   510  		b.Path = r.Paths()
   511  	case NamedPipe:
   512  		b.Path = r.Paths()
   513  	}
   514  
   515  	// enforce a few stability guarantees that may not be provided by the
   516  	// resource implementation.
   517  	sort.Strings(b.Path)
   518  
   519  	return b
   520  }
   521  
   522  // fromProto converts from a protobuf Resource to a Resource interface.
   523  func fromProto(b *pb.Resource) (Resource, error) {
   524  	base := &resource{
   525  		paths: b.Path,
   526  		mode:  os.FileMode(b.Mode),
   527  		uid:   b.Uid,
   528  		gid:   b.Gid,
   529  	}
   530  
   531  	base.xattrs = make(map[string][]byte, len(b.Xattr))
   532  
   533  	for _, attr := range b.Xattr {
   534  		base.xattrs[attr.Name] = attr.Data
   535  	}
   536  
   537  	switch {
   538  	case base.Mode().IsRegular():
   539  		dgsts := make([]digest.Digest, len(b.Digest))
   540  		for i, dgst := range b.Digest {
   541  			// TODO(stevvooe): Should we be validating at this point?
   542  			dgsts[i] = digest.Digest(dgst)
   543  		}
   544  
   545  		return newRegularFile(*base, b.Path, int64(b.Size), dgsts...)
   546  	case base.Mode().IsDir():
   547  		return newDirectory(*base)
   548  	case base.Mode()&os.ModeSymlink != 0:
   549  		return newSymLink(*base, b.Target)
   550  	case base.Mode()&os.ModeNamedPipe != 0:
   551  		return newNamedPipe(*base, b.Path)
   552  	case base.Mode()&os.ModeDevice != 0:
   553  		return newDevice(*base, b.Path, b.Major, b.Minor)
   554  	}
   555  
   556  	return nil, fmt.Errorf("unknown resource record (%#v): %s", b, base.Mode())
   557  }
   558  
   559  // NOTE(stevvooe): An alternative model that supports inline declaration.
   560  // Convenient for unit testing where inline declarations may be desirable but
   561  // creates an awkward API for the standard use case.
   562  
   563  // type ResourceKind int
   564  
   565  // const (
   566  // 	ResourceRegularFile = iota + 1
   567  // 	ResourceDirectory
   568  // 	ResourceSymLink
   569  // 	Resource
   570  // )
   571  
   572  // type Resource struct {
   573  // 	Kind         ResourceKind
   574  // 	Paths        []string
   575  // 	Mode         os.FileMode
   576  // 	UID          string
   577  // 	GID          string
   578  // 	Size         int64
   579  // 	Digests      []digest.Digest
   580  // 	Target       string
   581  // 	Major, Minor int
   582  // 	XAttrs       map[string][]byte
   583  // }
   584  
   585  // type RegularFile struct {
   586  // 	Paths   []string
   587  //  Size 	int64
   588  // 	Digests []digest.Digest
   589  // 	Perm    os.FileMode // os.ModePerm + sticky, setuid, setgid
   590  // }
   591  

View as plain text