Merge 'v1.5.2' into master
This commit is contained in:
commit
794c0206f3
338 changed files with 4158 additions and 48549 deletions
641
vendor/github.com/containerd/continuity/context.go
generated
vendored
641
vendor/github.com/containerd/continuity/context.go
generated
vendored
|
@ -1,641 +0,0 @@
|
|||
package continuity
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/continuity/devices"
|
||||
driverpkg "github.com/containerd/continuity/driver"
|
||||
"github.com/containerd/continuity/pathdriver"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = fmt.Errorf("not found")
|
||||
ErrNotSupported = fmt.Errorf("not supported")
|
||||
)
|
||||
|
||||
// Context represents a file system context for accessing resources. The
|
||||
// responsibility of the context is to convert system specific resources to
|
||||
// generic Resource objects. Most of this is safe path manipulation, as well
|
||||
// as extraction of resource details.
|
||||
type Context interface {
|
||||
Apply(Resource) error
|
||||
Verify(Resource) error
|
||||
Resource(string, os.FileInfo) (Resource, error)
|
||||
Walk(filepath.WalkFunc) error
|
||||
}
|
||||
|
||||
// SymlinkPath is intended to give the symlink target value
|
||||
// in a root context. Target and linkname are absolute paths
|
||||
// not under the given root.
|
||||
type SymlinkPath func(root, linkname, target string) (string, error)
|
||||
|
||||
type ContextOptions struct {
|
||||
Digester Digester
|
||||
Driver driverpkg.Driver
|
||||
PathDriver pathdriver.PathDriver
|
||||
Provider ContentProvider
|
||||
}
|
||||
|
||||
// context represents a file system context for accessing resources.
|
||||
// Generally, all path qualified access and system considerations should land
|
||||
// here.
|
||||
type context struct {
|
||||
driver driverpkg.Driver
|
||||
pathDriver pathdriver.PathDriver
|
||||
root string
|
||||
digester Digester
|
||||
provider ContentProvider
|
||||
}
|
||||
|
||||
// NewContext returns a Context associated with root. The default driver will
|
||||
// be used, as returned by NewDriver.
|
||||
func NewContext(root string) (Context, error) {
|
||||
return NewContextWithOptions(root, ContextOptions{})
|
||||
}
|
||||
|
||||
// NewContextWithOptions returns a Context associate with the root.
|
||||
func NewContextWithOptions(root string, options ContextOptions) (Context, error) {
|
||||
// normalize to absolute path
|
||||
pathDriver := options.PathDriver
|
||||
if pathDriver == nil {
|
||||
pathDriver = pathdriver.LocalPathDriver
|
||||
}
|
||||
|
||||
root = pathDriver.FromSlash(root)
|
||||
root, err := pathDriver.Abs(pathDriver.Clean(root))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
driver := options.Driver
|
||||
if driver == nil {
|
||||
driver, err = driverpkg.NewSystemDriver()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
digester := options.Digester
|
||||
if digester == nil {
|
||||
digester = simpleDigester{digest.Canonical}
|
||||
}
|
||||
|
||||
// Check the root directory. Need to be a little careful here. We are
|
||||
// allowing a link for now, but this may have odd behavior when
|
||||
// canonicalizing paths. As long as all files are opened through the link
|
||||
// path, this should be okay.
|
||||
fi, err := driver.Stat(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
return nil, &os.PathError{Op: "NewContext", Path: root, Err: os.ErrInvalid}
|
||||
}
|
||||
|
||||
return &context{
|
||||
root: root,
|
||||
driver: driver,
|
||||
pathDriver: pathDriver,
|
||||
digester: digester,
|
||||
provider: options.Provider,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Resource returns the resource as path p, populating the entry with info
|
||||
// from fi. The path p should be the path of the resource in the context,
|
||||
// typically obtained through Walk or from the value of Resource.Path(). If fi
|
||||
// is nil, it will be resolved.
|
||||
func (c *context) Resource(p string, fi os.FileInfo) (Resource, error) {
|
||||
fp, err := c.fullpath(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fi == nil {
|
||||
fi, err = c.driver.Lstat(fp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
base, err := newBaseResource(p, fi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
base.xattrs, err = c.resolveXAttrs(fp, fi, base)
|
||||
if err == ErrNotSupported {
|
||||
log.Printf("resolving xattrs on %s not supported", fp)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(stevvooe): Handle windows alternate data streams.
|
||||
|
||||
if fi.Mode().IsRegular() {
|
||||
dgst, err := c.digest(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newRegularFile(*base, base.paths, fi.Size(), dgst)
|
||||
}
|
||||
|
||||
if fi.Mode().IsDir() {
|
||||
return newDirectory(*base)
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
// We handle relative links vs absolute links by including a
|
||||
// beginning slash for absolute links. Effectively, the bundle's
|
||||
// root is treated as the absolute link anchor.
|
||||
target, err := c.driver.Readlink(fp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newSymLink(*base, target)
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeNamedPipe != 0 {
|
||||
return newNamedPipe(*base, base.paths)
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeDevice != 0 {
|
||||
deviceDriver, ok := c.driver.(driverpkg.DeviceInfoDriver)
|
||||
if !ok {
|
||||
log.Printf("device extraction not supported %s", fp)
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
// character and block devices merely need to recover the
|
||||
// major/minor device number.
|
||||
major, minor, err := deviceDriver.DeviceInfo(fi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newDevice(*base, base.paths, major, minor)
|
||||
}
|
||||
|
||||
log.Printf("%q (%v) is not supported", fp, fi.Mode())
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
func (c *context) verifyMetadata(resource, target Resource) error {
|
||||
if target.Mode() != resource.Mode() {
|
||||
return fmt.Errorf("resource %q has incorrect mode: %v != %v", target.Path(), target.Mode(), resource.Mode())
|
||||
}
|
||||
|
||||
if target.UID() != resource.UID() {
|
||||
return fmt.Errorf("unexpected uid for %q: %v != %v", target.Path(), target.UID(), resource.GID())
|
||||
}
|
||||
|
||||
if target.GID() != resource.GID() {
|
||||
return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), target.GID())
|
||||
}
|
||||
|
||||
if xattrer, ok := resource.(XAttrer); ok {
|
||||
txattrer, tok := target.(XAttrer)
|
||||
if !tok {
|
||||
return fmt.Errorf("resource %q has xattrs but target does not support them", resource.Path())
|
||||
}
|
||||
|
||||
// For xattrs, only ensure that we have those defined in the resource
|
||||
// and their values match. We can ignore other xattrs. In other words,
|
||||
// we only verify that target has the subset defined by resource.
|
||||
txattrs := txattrer.XAttrs()
|
||||
for attr, value := range xattrer.XAttrs() {
|
||||
tvalue, ok := txattrs[attr]
|
||||
if !ok {
|
||||
return fmt.Errorf("resource %q target missing xattr %q", resource.Path(), attr)
|
||||
}
|
||||
|
||||
if !bytes.Equal(value, tvalue) {
|
||||
return fmt.Errorf("xattr %q value differs for resource %q", attr, resource.Path())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch r := resource.(type) {
|
||||
case RegularFile:
|
||||
// TODO(stevvooe): Another reason to use a record-based approach. We
|
||||
// have to do another type switch to get this to work. This could be
|
||||
// fixed with an Equal function, but let's study this a little more to
|
||||
// be sure.
|
||||
t, ok := target.(RegularFile)
|
||||
if !ok {
|
||||
return fmt.Errorf("resource %q target not a regular file", r.Path())
|
||||
}
|
||||
|
||||
if t.Size() != r.Size() {
|
||||
return fmt.Errorf("resource %q target has incorrect size: %v != %v", t.Path(), t.Size(), r.Size())
|
||||
}
|
||||
case Directory:
|
||||
t, ok := target.(Directory)
|
||||
if !ok {
|
||||
return fmt.Errorf("resource %q target not a directory", t.Path())
|
||||
}
|
||||
case SymLink:
|
||||
t, ok := target.(SymLink)
|
||||
if !ok {
|
||||
return fmt.Errorf("resource %q target not a symlink", t.Path())
|
||||
}
|
||||
|
||||
if t.Target() != r.Target() {
|
||||
return fmt.Errorf("resource %q target has mismatched target: %q != %q", t.Path(), t.Target(), r.Target())
|
||||
}
|
||||
case Device:
|
||||
t, ok := target.(Device)
|
||||
if !ok {
|
||||
return fmt.Errorf("resource %q is not a device", t.Path())
|
||||
}
|
||||
|
||||
if t.Major() != r.Major() || t.Minor() != r.Minor() {
|
||||
return fmt.Errorf("resource %q has mismatched major/minor numbers: %d,%d != %d,%d", t.Path(), t.Major(), t.Minor(), r.Major(), r.Minor())
|
||||
}
|
||||
case NamedPipe:
|
||||
t, ok := target.(NamedPipe)
|
||||
if !ok {
|
||||
return fmt.Errorf("resource %q is not a named pipe", t.Path())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cannot verify resource: %v", resource)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify the resource in the context. An error will be returned a discrepancy
|
||||
// is found.
|
||||
func (c *context) Verify(resource Resource) error {
|
||||
fp, err := c.fullpath(resource.Path())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fi, err := c.driver.Lstat(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target, err := c.Resource(resource.Path(), fi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if target.Path() != resource.Path() {
|
||||
return fmt.Errorf("resource paths do not match: %q != %q", target.Path(), resource.Path())
|
||||
}
|
||||
|
||||
if err := c.verifyMetadata(resource, target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
|
||||
hardlinkKey, err := newHardlinkKey(fi)
|
||||
if err == errNotAHardLink {
|
||||
if len(h.Paths()) > 1 {
|
||||
return fmt.Errorf("%q is not a hardlink to %q", h.Paths()[1], resource.Path())
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, path := range h.Paths()[1:] {
|
||||
fpLink, err := c.fullpath(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fiLink, err := c.driver.Lstat(fpLink)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetLink, err := c.Resource(path, fiLink)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hardlinkKeyLink, err := newHardlinkKey(fiLink)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hardlinkKeyLink != hardlinkKey {
|
||||
return fmt.Errorf("%q is not a hardlink to %q", path, resource.Path())
|
||||
}
|
||||
|
||||
if err := c.verifyMetadata(resource, targetLink); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch r := resource.(type) {
|
||||
case RegularFile:
|
||||
t, ok := target.(RegularFile)
|
||||
if !ok {
|
||||
return fmt.Errorf("resource %q target not a regular file", r.Path())
|
||||
}
|
||||
|
||||
// TODO(stevvooe): This may need to get a little more sophisticated
|
||||
// for digest comparison. We may want to actually calculate the
|
||||
// provided digests, rather than the implementations having an
|
||||
// overlap.
|
||||
if !digestsMatch(t.Digests(), r.Digests()) {
|
||||
return fmt.Errorf("digests for resource %q do not match: %v != %v", t.Path(), t.Digests(), r.Digests())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *context) checkoutFile(fp string, rf RegularFile) error {
|
||||
if c.provider == nil {
|
||||
return fmt.Errorf("no file provider")
|
||||
}
|
||||
var (
|
||||
r io.ReadCloser
|
||||
err error
|
||||
)
|
||||
for _, dgst := range rf.Digests() {
|
||||
r, err = c.provider.Reader(dgst)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("file content could not be provided: %v", err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
return atomicWriteFile(fp, r, rf)
|
||||
}
|
||||
|
||||
// Apply the resource to the contexts. An error will be returned if the
|
||||
// operation fails. Depending on the resource type, the resource may be
|
||||
// created. For resource that cannot be resolved, an error will be returned.
|
||||
func (c *context) Apply(resource Resource) error {
|
||||
fp, err := c.fullpath(resource.Path())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(fp, c.root) {
|
||||
return fmt.Errorf("resource %v escapes root", resource)
|
||||
}
|
||||
|
||||
var chmod = true
|
||||
fi, err := c.driver.Lstat(fp)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch r := resource.(type) {
|
||||
case RegularFile:
|
||||
if fi == nil {
|
||||
if err := c.checkoutFile(fp, r); err != nil {
|
||||
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
||||
}
|
||||
chmod = false
|
||||
} else {
|
||||
if !fi.Mode().IsRegular() {
|
||||
return fmt.Errorf("file %q should be a regular file, but is not", resource.Path())
|
||||
}
|
||||
if fi.Size() != r.Size() {
|
||||
if err := c.checkoutFile(fp, r); err != nil {
|
||||
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
||||
}
|
||||
} else {
|
||||
for _, dgst := range r.Digests() {
|
||||
f, err := os.Open(fp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failure opening file for read %q: %v", resource.Path(), err)
|
||||
}
|
||||
compared, err := dgst.Algorithm().FromReader(f)
|
||||
if err == nil && dgst != compared {
|
||||
if err := c.checkoutFile(fp, r); err != nil {
|
||||
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
||||
}
|
||||
break
|
||||
}
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("error checking digest for %q: %v", resource.Path(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case Directory:
|
||||
if fi == nil {
|
||||
if err := c.driver.Mkdir(fp, resource.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !fi.Mode().IsDir() {
|
||||
return fmt.Errorf("%q should be a directory, but is not", resource.Path())
|
||||
}
|
||||
|
||||
case SymLink:
|
||||
var target string // only possibly set if target resource is a symlink
|
||||
|
||||
if fi != nil {
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
target, err = c.driver.Readlink(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if target != r.Target() {
|
||||
if fi != nil {
|
||||
if err := c.driver.Remove(fp); err != nil { // RemoveAll in case of directory?
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.driver.Symlink(r.Target(), fp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE(stevvooe): Chmod on symlink is not supported on linux. We
|
||||
// may want to maintain support for other platforms that have it.
|
||||
chmod = false
|
||||
|
||||
case Device:
|
||||
if fi == nil {
|
||||
if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if (fi.Mode() & os.ModeDevice) == 0 {
|
||||
return fmt.Errorf("%q should be a device, but is not", resource.Path())
|
||||
} else {
|
||||
major, minor, err := devices.DeviceInfo(fi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if major != r.Major() || minor != r.Minor() {
|
||||
if err := c.driver.Remove(fp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case NamedPipe:
|
||||
if fi == nil {
|
||||
if err := c.driver.Mkfifo(fp, resource.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if (fi.Mode() & os.ModeNamedPipe) == 0 {
|
||||
return fmt.Errorf("%q should be a named pipe, but is not", resource.Path())
|
||||
}
|
||||
}
|
||||
|
||||
if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
|
||||
for _, path := range h.Paths() {
|
||||
if path == resource.Path() {
|
||||
continue
|
||||
}
|
||||
|
||||
lp, err := c.fullpath(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, fi := c.driver.Lstat(lp); fi == nil {
|
||||
c.driver.Remove(lp)
|
||||
}
|
||||
if err := c.driver.Link(fp, lp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update filemode if file was not created
|
||||
if chmod {
|
||||
if err := c.driver.Lchmod(fp, resource.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.driver.Lchown(fp, resource.UID(), resource.GID()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if xattrer, ok := resource.(XAttrer); ok {
|
||||
// For xattrs, only ensure that we have those defined in the resource
|
||||
// and their values are set. We can ignore other xattrs. In other words,
|
||||
// we only set xattres defined by resource but never remove.
|
||||
|
||||
if _, ok := resource.(SymLink); ok {
|
||||
lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported symlink xattr for resource %q", resource.Path())
|
||||
}
|
||||
if err := lxattrDriver.LSetxattr(fp, xattrer.XAttrs()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported xattr for resource %q", resource.Path())
|
||||
}
|
||||
if err := xattrDriver.Setxattr(fp, xattrer.XAttrs()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Walk provides a convenience function to call filepath.Walk correctly for
|
||||
// the context. Otherwise identical to filepath.Walk, the path argument is
|
||||
// corrected to be contained within the context.
|
||||
func (c *context) Walk(fn filepath.WalkFunc) error {
|
||||
return c.pathDriver.Walk(c.root, func(p string, fi os.FileInfo, err error) error {
|
||||
contained, err := c.contain(p)
|
||||
return fn(contained, fi, err)
|
||||
})
|
||||
}
|
||||
|
||||
// fullpath returns the system path for the resource, joined with the context
|
||||
// root. The path p must be a part of the context.
|
||||
func (c *context) fullpath(p string) (string, error) {
|
||||
p = c.pathDriver.Join(c.root, p)
|
||||
if !strings.HasPrefix(p, c.root) {
|
||||
return "", fmt.Errorf("invalid context path")
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// contain cleans and santizes the filesystem path p to be an absolute path,
|
||||
// effectively relative to the context root.
|
||||
func (c *context) contain(p string) (string, error) {
|
||||
sanitized, err := c.pathDriver.Rel(c.root, p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// ZOMBIES(stevvooe): In certain cases, we may want to remap these to a
|
||||
// "containment error", so the caller can decide what to do.
|
||||
return c.pathDriver.Join("/", c.pathDriver.Clean(sanitized)), nil
|
||||
}
|
||||
|
||||
// digest returns the digest of the file at path p, relative to the root.
|
||||
func (c *context) digest(p string) (digest.Digest, error) {
|
||||
f, err := c.driver.Open(c.pathDriver.Join(c.root, p))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return c.digester.Digest(f)
|
||||
}
|
||||
|
||||
// resolveXAttrs attempts to resolve the extended attributes for the resource
|
||||
// at the path fp, which is the full path to the resource. If the resource
|
||||
// cannot have xattrs, nil will be returned.
|
||||
func (c *context) resolveXAttrs(fp string, fi os.FileInfo, base *resource) (map[string][]byte, error) {
|
||||
if fi.Mode().IsRegular() || fi.Mode().IsDir() {
|
||||
xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
|
||||
if !ok {
|
||||
log.Println("xattr extraction not supported")
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
return xattrDriver.Getxattr(fp)
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
|
||||
if !ok {
|
||||
log.Println("xattr extraction for symlinks not supported")
|
||||
return nil, ErrNotSupported
|
||||
}
|
||||
|
||||
return lxattrDriver.LGetxattr(fp)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
88
vendor/github.com/containerd/continuity/digests.go
generated
vendored
88
vendor/github.com/containerd/continuity/digests.go
generated
vendored
|
@ -1,88 +0,0 @@
|
|||
package continuity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// Digester produces a digest for a given read stream
|
||||
type Digester interface {
|
||||
Digest(io.Reader) (digest.Digest, error)
|
||||
}
|
||||
|
||||
// ContentProvider produces a read stream for a given digest
|
||||
type ContentProvider interface {
|
||||
Reader(digest.Digest) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
type simpleDigester struct {
|
||||
algorithm digest.Algorithm
|
||||
}
|
||||
|
||||
func (sd simpleDigester) Digest(r io.Reader) (digest.Digest, error) {
|
||||
digester := sd.algorithm.Digester()
|
||||
|
||||
if _, err := io.Copy(digester.Hash(), r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return digester.Digest(), nil
|
||||
}
|
||||
|
||||
// uniqifyDigests sorts and uniqifies the provided digest, ensuring that the
|
||||
// digests are not repeated and no two digests with the same algorithm have
|
||||
// different values. Because a stable sort is used, this has the effect of
|
||||
// "zipping" digest collections from multiple resources.
|
||||
func uniqifyDigests(digests ...digest.Digest) ([]digest.Digest, error) {
|
||||
sort.Stable(digestSlice(digests)) // stable sort is important for the behavior here.
|
||||
seen := map[digest.Digest]struct{}{}
|
||||
algs := map[digest.Algorithm][]digest.Digest{} // detect different digests.
|
||||
|
||||
var out []digest.Digest
|
||||
// uniqify the digests
|
||||
for _, d := range digests {
|
||||
if _, ok := seen[d]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[d] = struct{}{}
|
||||
algs[d.Algorithm()] = append(algs[d.Algorithm()], d)
|
||||
|
||||
if len(algs[d.Algorithm()]) > 1 {
|
||||
return nil, fmt.Errorf("conflicting digests for %v found", d.Algorithm())
|
||||
}
|
||||
|
||||
out = append(out, d)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// digestsMatch compares the two sets of digests to see if they match.
|
||||
func digestsMatch(as, bs []digest.Digest) bool {
|
||||
all := append(as, bs...)
|
||||
|
||||
uniqified, err := uniqifyDigests(all...)
|
||||
if err != nil {
|
||||
// the only error uniqifyDigests returns is when the digests disagree.
|
||||
return false
|
||||
}
|
||||
|
||||
disjoint := len(as) + len(bs)
|
||||
if len(uniqified) == disjoint {
|
||||
// if these two sets have the same cardinality, we know both sides
|
||||
// didn't share any digests.
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type digestSlice []digest.Digest
|
||||
|
||||
func (p digestSlice) Len() int { return len(p) }
|
||||
func (p digestSlice) Less(i, j int) bool { return p[i] < p[j] }
|
||||
func (p digestSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
113
vendor/github.com/containerd/continuity/groups_unix.go
generated
vendored
113
vendor/github.com/containerd/continuity/groups_unix.go
generated
vendored
|
@ -1,113 +0,0 @@
|
|||
package continuity
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO(stevvooe): This needs a lot of work before we can call it useful.
|
||||
|
||||
type groupIndex struct {
|
||||
byName map[string]*group
|
||||
byGID map[int]*group
|
||||
}
|
||||
|
||||
func getGroupIndex() (*groupIndex, error) {
|
||||
f, err := os.Open("/etc/group")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
groups, err := parseGroups(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newGroupIndex(groups), nil
|
||||
}
|
||||
|
||||
func newGroupIndex(groups []group) *groupIndex {
|
||||
gi := &groupIndex{
|
||||
byName: make(map[string]*group),
|
||||
byGID: make(map[int]*group),
|
||||
}
|
||||
|
||||
for i, group := range groups {
|
||||
gi.byGID[group.gid] = &groups[i]
|
||||
gi.byName[group.name] = &groups[i]
|
||||
}
|
||||
|
||||
return gi
|
||||
}
|
||||
|
||||
type group struct {
|
||||
name string
|
||||
gid int
|
||||
members []string
|
||||
}
|
||||
|
||||
func getGroupName(gid int) (string, error) {
|
||||
f, err := os.Open("/etc/group")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
groups, err := parseGroups(f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if group.gid == gid {
|
||||
return group.name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no group for gid")
|
||||
}
|
||||
|
||||
// parseGroups parses an /etc/group file for group names, ids and membership.
|
||||
// This is unix specific.
|
||||
func parseGroups(rd io.Reader) ([]group, error) {
|
||||
var groups []group
|
||||
scanner := bufio.NewScanner(rd)
|
||||
|
||||
for scanner.Scan() {
|
||||
if strings.HasPrefix(scanner.Text(), "#") {
|
||||
continue // skip comment
|
||||
}
|
||||
|
||||
parts := strings.SplitN(scanner.Text(), ":", 4)
|
||||
|
||||
if len(parts) != 4 {
|
||||
return nil, fmt.Errorf("bad entry: %q", scanner.Text())
|
||||
}
|
||||
|
||||
name, _, sgid, smembers := parts[0], parts[1], parts[2], parts[3]
|
||||
|
||||
gid, err := strconv.Atoi(sgid)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad gid: %q", gid)
|
||||
}
|
||||
|
||||
members := strings.Split(smembers, ",")
|
||||
|
||||
groups = append(groups, group{
|
||||
name: name,
|
||||
gid: gid,
|
||||
members: members,
|
||||
})
|
||||
}
|
||||
|
||||
if scanner.Err() != nil {
|
||||
return nil, scanner.Err()
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
57
vendor/github.com/containerd/continuity/hardlinks.go
generated
vendored
57
vendor/github.com/containerd/continuity/hardlinks.go
generated
vendored
|
@ -1,57 +0,0 @@
|
|||
package continuity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotAHardLink = fmt.Errorf("invalid hardlink")
|
||||
)
|
||||
|
||||
type hardlinkManager struct {
|
||||
hardlinks map[hardlinkKey][]Resource
|
||||
}
|
||||
|
||||
func newHardlinkManager() *hardlinkManager {
|
||||
return &hardlinkManager{
|
||||
hardlinks: map[hardlinkKey][]Resource{},
|
||||
}
|
||||
}
|
||||
|
||||
// Add attempts to add the resource to the hardlink manager. If the resource
|
||||
// cannot be considered as a hardlink candidate, errNotAHardLink is returned.
|
||||
func (hlm *hardlinkManager) Add(fi os.FileInfo, resource Resource) error {
|
||||
if _, ok := resource.(Hardlinkable); !ok {
|
||||
return errNotAHardLink
|
||||
}
|
||||
|
||||
key, err := newHardlinkKey(fi)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hlm.hardlinks[key] = append(hlm.hardlinks[key], resource)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge processes the current state of the hardlink manager and merges any
|
||||
// shared nodes into hardlinked resources.
|
||||
func (hlm *hardlinkManager) Merge() ([]Resource, error) {
|
||||
var resources []Resource
|
||||
for key, linked := range hlm.hardlinks {
|
||||
if len(linked) < 1 {
|
||||
return nil, fmt.Errorf("no hardlink entrys for dev, inode pair: %#v", key)
|
||||
}
|
||||
|
||||
merged, err := Merge(linked...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error merging hardlink: %v", err)
|
||||
}
|
||||
|
||||
resources = append(resources, merged)
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
36
vendor/github.com/containerd/continuity/hardlinks_unix.go
generated
vendored
36
vendor/github.com/containerd/continuity/hardlinks_unix.go
generated
vendored
|
@ -1,36 +0,0 @@
|
|||
// +build linux darwin freebsd solaris
|
||||
|
||||
package continuity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// hardlinkKey provides a tuple-key for managing hardlinks. This is system-
|
||||
// specific.
|
||||
type hardlinkKey struct {
|
||||
dev uint64
|
||||
inode uint64
|
||||
}
|
||||
|
||||
// newHardlinkKey returns a hardlink key for the provided file info. If the
|
||||
// resource does not represent a possible hardlink, errNotAHardLink will be
|
||||
// returned.
|
||||
func newHardlinkKey(fi os.FileInfo) (hardlinkKey, error) {
|
||||
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return hardlinkKey{}, fmt.Errorf("cannot resolve (*syscall.Stat_t) from os.FileInfo")
|
||||
}
|
||||
|
||||
if sys.Nlink < 2 {
|
||||
// NOTE(stevvooe): This is not always true for all filesystems. We
|
||||
// should somehow detect this and provided a slow "polyfill" that
|
||||
// leverages os.SameFile if we detect a filesystem where link counts
|
||||
// is not really supported.
|
||||
return hardlinkKey{}, errNotAHardLink
|
||||
}
|
||||
|
||||
return hardlinkKey{dev: uint64(sys.Dev), inode: uint64(sys.Ino)}, nil
|
||||
}
|
12
vendor/github.com/containerd/continuity/hardlinks_windows.go
generated
vendored
12
vendor/github.com/containerd/continuity/hardlinks_windows.go
generated
vendored
|
@ -1,12 +0,0 @@
|
|||
package continuity
|
||||
|
||||
import "os"
|
||||
|
||||
type hardlinkKey struct{}
|
||||
|
||||
func newHardlinkKey(fi os.FileInfo) (hardlinkKey, error) {
|
||||
// NOTE(stevvooe): Obviously, this is not yet implemented. However, the
|
||||
// makings of an implementation are available in src/os/types_windows.go. More
|
||||
// investigation needs to be done to figure out exactly how to do this.
|
||||
return hardlinkKey{}, errNotAHardLink
|
||||
}
|
39
vendor/github.com/containerd/continuity/ioutils.go
generated
vendored
39
vendor/github.com/containerd/continuity/ioutils.go
generated
vendored
|
@ -1,39 +0,0 @@
|
|||
package continuity
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// atomicWriteFile writes data to a file by first writing to a temp
|
||||
// file and calling rename.
|
||||
func atomicWriteFile(filename string, r io.Reader, rf RegularFile) error {
|
||||
f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chmod(f.Name(), rf.Mode())
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
n, err := io.Copy(f, r)
|
||||
if err == nil && n < rf.Size() {
|
||||
f.Close()
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
if err := f.Sync(); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(f.Name(), filename)
|
||||
}
|
144
vendor/github.com/containerd/continuity/manifest.go
generated
vendored
144
vendor/github.com/containerd/continuity/manifest.go
generated
vendored
|
@ -1,144 +0,0 @@
|
|||
package continuity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
pb "github.com/containerd/continuity/proto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// Manifest provides the contents of a manifest. Users of this struct should
|
||||
// not typically modify any fields directly.
|
||||
type Manifest struct {
|
||||
// Resources specifies all the resources for a manifest in order by path.
|
||||
Resources []Resource
|
||||
}
|
||||
|
||||
func Unmarshal(p []byte) (*Manifest, error) {
|
||||
var bm pb.Manifest
|
||||
|
||||
if err := proto.Unmarshal(p, &bm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var m Manifest
|
||||
for _, b := range bm.Resource {
|
||||
r, err := fromProto(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.Resources = append(m.Resources, r)
|
||||
}
|
||||
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func Marshal(m *Manifest) ([]byte, error) {
|
||||
var bm pb.Manifest
|
||||
for _, resource := range m.Resources {
|
||||
bm.Resource = append(bm.Resource, toProto(resource))
|
||||
}
|
||||
|
||||
return proto.Marshal(&bm)
|
||||
}
|
||||
|
||||
func MarshalText(w io.Writer, m *Manifest) error {
|
||||
var bm pb.Manifest
|
||||
for _, resource := range m.Resources {
|
||||
bm.Resource = append(bm.Resource, toProto(resource))
|
||||
}
|
||||
|
||||
return proto.MarshalText(w, &bm)
|
||||
}
|
||||
|
||||
// BuildManifest creates the manifest for the given context
|
||||
func BuildManifest(ctx Context) (*Manifest, error) {
|
||||
resourcesByPath := map[string]Resource{}
|
||||
hardlinks := newHardlinkManager()
|
||||
|
||||
if err := ctx.Walk(func(p string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error walking %s: %v", p, err)
|
||||
}
|
||||
|
||||
if p == "/" {
|
||||
// skip root
|
||||
return nil
|
||||
}
|
||||
|
||||
resource, err := ctx.Resource(p, fi)
|
||||
if err != nil {
|
||||
if err == ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
log.Printf("error getting resource %q: %v", p, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// add to the hardlink manager
|
||||
if err := hardlinks.Add(fi, resource); err == nil {
|
||||
// Resource has been accepted by hardlink manager so we don't add
|
||||
// it to the resourcesByPath until we merge at the end.
|
||||
return nil
|
||||
} else if err != errNotAHardLink {
|
||||
// handle any other case where we have a proper error.
|
||||
return fmt.Errorf("adding hardlink %s: %v", p, err)
|
||||
}
|
||||
|
||||
resourcesByPath[p] = resource
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// merge and post-process the hardlinks.
|
||||
hardlinked, err := hardlinks.Merge()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, resource := range hardlinked {
|
||||
resourcesByPath[resource.Path()] = resource
|
||||
}
|
||||
|
||||
var resources []Resource
|
||||
for _, resource := range resourcesByPath {
|
||||
resources = append(resources, resource)
|
||||
}
|
||||
|
||||
sort.Stable(ByPath(resources))
|
||||
|
||||
return &Manifest{
|
||||
Resources: resources,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifyManifest verifies all the resources in a manifest
|
||||
// against files from the given context.
|
||||
func VerifyManifest(ctx Context, manifest *Manifest) error {
|
||||
for _, resource := range manifest.Resources {
|
||||
if err := ctx.Verify(resource); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyManifest applies on the resources in a manifest to
|
||||
// the given context.
|
||||
func ApplyManifest(ctx Context, manifest *Manifest) error {
|
||||
for _, resource := range manifest.Resources {
|
||||
if err := ctx.Apply(resource); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
23
vendor/github.com/containerd/continuity/manifest_test_darwin.go
generated
vendored
23
vendor/github.com/containerd/continuity/manifest_test_darwin.go
generated
vendored
|
@ -1,23 +0,0 @@
|
|||
// +build ignore
|
||||
|
||||
package continuity
|
||||
|
||||
import "os"
|
||||
|
||||
var (
|
||||
devNullResource = resource{
|
||||
kind: chardev,
|
||||
path: "/dev/null",
|
||||
major: 3,
|
||||
minor: 2,
|
||||
mode: 0666 | os.ModeDevice | os.ModeCharDevice,
|
||||
}
|
||||
|
||||
devZeroResource = resource{
|
||||
kind: chardev,
|
||||
path: "/dev/zero",
|
||||
major: 3,
|
||||
minor: 3,
|
||||
mode: 0666 | os.ModeDevice | os.ModeCharDevice,
|
||||
}
|
||||
)
|
574
vendor/github.com/containerd/continuity/resource.go
generated
vendored
574
vendor/github.com/containerd/continuity/resource.go
generated
vendored
|
@ -1,574 +0,0 @@
|
|||
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
|
||||
// }
|
37
vendor/github.com/containerd/continuity/resource_unix.go
generated
vendored
37
vendor/github.com/containerd/continuity/resource_unix.go
generated
vendored
|
@ -1,37 +0,0 @@
|
|||
// +build linux darwin freebsd solaris
|
||||
|
||||
package continuity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// newBaseResource returns a *resource, populated with data from p and fi,
|
||||
// where p will be populated directly.
|
||||
func newBaseResource(p string, fi os.FileInfo) (*resource, error) {
|
||||
// TODO(stevvooe): This need to be resolved for the container's root,
|
||||
// where here we are really getting the host OS's value. We need to allow
|
||||
// this be passed in and fixed up to make these uid/gid mappings portable.
|
||||
// Either this can be part of the driver or we can achieve it through some
|
||||
// other mechanism.
|
||||
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
// TODO(stevvooe): This may not be a hard error for all platforms. We
|
||||
// may want to move this to the driver.
|
||||
return nil, fmt.Errorf("unable to resolve syscall.Stat_t from (os.FileInfo).Sys(): %#v", fi)
|
||||
}
|
||||
|
||||
return &resource{
|
||||
paths: []string{p},
|
||||
mode: fi.Mode(),
|
||||
|
||||
uid: int64(sys.Uid),
|
||||
gid: int64(sys.Gid),
|
||||
|
||||
// NOTE(stevvooe): Population of shared xattrs field is deferred to
|
||||
// the resource types that populate it. Since they are a property of
|
||||
// the context, they must set there.
|
||||
}, nil
|
||||
}
|
12
vendor/github.com/containerd/continuity/resource_windows.go
generated
vendored
12
vendor/github.com/containerd/continuity/resource_windows.go
generated
vendored
|
@ -1,12 +0,0 @@
|
|||
package continuity
|
||||
|
||||
import "os"
|
||||
|
||||
// newBaseResource returns a *resource, populated with data from p and fi,
|
||||
// where p will be populated directly.
|
||||
func newBaseResource(p string, fi os.FileInfo) (*resource, error) {
|
||||
return &resource{
|
||||
paths: []string{p},
|
||||
mode: fi.Mode(),
|
||||
}, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue