mirror of https://github.com/docker/cli.git
bump containerd v1.3.0
full diff: 7c1e88399e
...v1.3.0
This also adds back containerd/ttrpc as a dependency, which is referenced by the BuildKit client (indirectly)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
c07f50afab
commit
6732347e55
|
@ -3,8 +3,9 @@ github.com/agl/ed25519 5312a61534124124185d41f09206
|
|||
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
|
||||
github.com/beorn7/perks e7f67b54abbeac9c40a31de0f81159e4cafebd6a
|
||||
github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f
|
||||
github.com/containerd/containerd 7c1e88399ec0b0b077121d9d5ad97e647b11c870
|
||||
github.com/containerd/containerd 36cf5b690dcc00ff0f34ff7799209050c3d0c59a # v1.3.0
|
||||
github.com/containerd/continuity f2a389ac0a02ce21c09edd7344677a601970f41c
|
||||
github.com/containerd/ttrpc 92c8520ef9f86600c650dd540266a007bf03670f
|
||||
github.com/coreos/etcd d57e8b8d97adfc4a6c224fe116714bf1a1f3beb9 # v3.3.12
|
||||
github.com/cpuguy83/go-md2man 20f5889cbdc3c73dbd2862796665e7c465ade7d1 # v1.0.8
|
||||
github.com/creack/pty 3a6a957789163cacdfe0e291617a1c8e80612c11 # v1.1.9
|
||||
|
|
|
@ -218,7 +218,7 @@ This will be the best place to discuss design and implementation.
|
|||
For sync communication we have a community slack with a #containerd channel that everyone is welcome to join and chat about development.
|
||||
|
||||
**Slack:** Catch us in the #containerd and #containerd-dev channels on dockercommunity.slack.com.
|
||||
[Click here for an invite to docker community slack.](https://join.slack.com/t/dockercommunity/shared_invite/enQtNDY4MDc1Mzc0MzIwLTgxZDBlMmM4ZGEyNDc1N2FkMzlhODJkYmE1YTVkYjM1MDE3ZjAwZjBkOGFlOTJkZjRmZGYzNjYyY2M3ZTUxYzQ)
|
||||
[Click here for an invite to docker community slack.](https://dockr.ly/slack)
|
||||
|
||||
### Security audit
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ type Container struct {
|
|||
// This property is required and immutable.
|
||||
Runtime RuntimeInfo
|
||||
|
||||
// Spec should carry the the runtime specification used to implement the
|
||||
// Spec should carry the runtime specification used to implement the
|
||||
// container.
|
||||
//
|
||||
// This field is required but mutable.
|
||||
|
|
|
@ -55,7 +55,14 @@ func ReadBlob(ctx context.Context, provider Provider, desc ocispec.Descriptor) (
|
|||
|
||||
p := make([]byte, ra.Size())
|
||||
|
||||
_, err = ra.ReadAt(p, 0)
|
||||
n, err := ra.ReadAt(p, 0)
|
||||
if err == io.EOF {
|
||||
if int64(n) != ra.Size() {
|
||||
err = io.ErrUnexpectedEOF
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
|
@ -162,6 +169,28 @@ func CopyReaderAt(cw Writer, ra ReaderAt, n int64) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// CopyReader copies to a writer from a given reader, returning
|
||||
// the number of bytes copied.
|
||||
// Note: if the writer has a non-zero offset, the total number
|
||||
// of bytes read may be greater than those copied if the reader
|
||||
// is not an io.Seeker.
|
||||
// This copy does not commit the writer.
|
||||
func CopyReader(cw Writer, r io.Reader) (int64, error) {
|
||||
ws, err := cw.Status()
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "failed to get status")
|
||||
}
|
||||
|
||||
if ws.Offset > 0 {
|
||||
r, err = seekReader(r, ws.Offset, 0)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "unable to resume write to %v", ws.Ref)
|
||||
}
|
||||
}
|
||||
|
||||
return copyWithBuffer(cw, r)
|
||||
}
|
||||
|
||||
// seekReader attempts to seek the reader to the given offset, either by
|
||||
// resolving `io.Seeker`, by detecting `io.ReaderAt`, or discarding
|
||||
// up to the given offset.
|
||||
|
|
|
@ -35,7 +35,6 @@ import (
|
|||
"github.com/containerd/containerd/log"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/containerd/continuity"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -661,6 +660,19 @@ func writeTimestampFile(p string, t time.Time) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return continuity.AtomicWriteFile(p, b, 0666)
|
||||
return atomicWrite(p, b, 0666)
|
||||
}
|
||||
|
||||
func atomicWrite(path string, data []byte, mode os.FileMode) error {
|
||||
tmp := fmt.Sprintf("%s.tmp", path)
|
||||
f, err := os.OpenFile(tmp, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_SYNC, mode)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create tmp file")
|
||||
}
|
||||
_, err = f.Write(data)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "write atomic data")
|
||||
}
|
||||
return os.Rename(tmp, path)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,11 @@
|
|||
// client-side errors to the correct types.
|
||||
package errdefs
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Definitions of common error types used throughout containerd. All containerd
|
||||
// errors returned by most packages will map into one of these errors classes.
|
||||
|
@ -76,3 +80,14 @@ func IsUnavailable(err error) bool {
|
|||
func IsNotImplemented(err error) bool {
|
||||
return errors.Cause(err) == ErrNotImplemented
|
||||
}
|
||||
|
||||
// IsCanceled returns true if the error is due to `context.Canceled`.
|
||||
func IsCanceled(err error) bool {
|
||||
return errors.Cause(err) == context.Canceled
|
||||
}
|
||||
|
||||
// IsDeadlineExceeded returns true if the error is due to
|
||||
// `context.DeadlineExceeded`.
|
||||
func IsDeadlineExceeded(err error) bool {
|
||||
return errors.Cause(err) == context.DeadlineExceeded
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package errdefs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -55,6 +56,10 @@ func ToGRPC(err error) error {
|
|||
return status.Errorf(codes.Unavailable, err.Error())
|
||||
case IsNotImplemented(err):
|
||||
return status.Errorf(codes.Unimplemented, err.Error())
|
||||
case IsCanceled(err):
|
||||
return status.Errorf(codes.Canceled, err.Error())
|
||||
case IsDeadlineExceeded(err):
|
||||
return status.Errorf(codes.DeadlineExceeded, err.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -89,6 +94,10 @@ func FromGRPC(err error) error {
|
|||
cls = ErrFailedPrecondition
|
||||
case codes.Unimplemented:
|
||||
cls = ErrNotImplemented
|
||||
case codes.Canceled:
|
||||
cls = context.Canceled
|
||||
case codes.DeadlineExceeded:
|
||||
cls = context.DeadlineExceeded
|
||||
default:
|
||||
cls = ErrUnknown
|
||||
}
|
||||
|
|
|
@ -14,14 +14,10 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package devices
|
||||
package images
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
const (
|
||||
// AnnotationImageName is an annotation on a Descriptor in an index.json
|
||||
// containing the `Name` value as used by an `Image` struct
|
||||
AnnotationImageName = "io.containerd.image.name"
|
||||
)
|
||||
|
||||
func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
|
||||
return 0, 0, errors.Wrap(ErrNotSupported, "cannot get device info on windows")
|
||||
}
|
|
@ -117,7 +117,7 @@ func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) err
|
|||
//
|
||||
// If any handler returns an error, the dispatch session will be canceled.
|
||||
func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted, descs ...ocispec.Descriptor) error {
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg, ctx2 := errgroup.WithContext(ctx)
|
||||
for _, desc := range descs {
|
||||
desc := desc
|
||||
|
||||
|
@ -126,10 +126,11 @@ func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted,
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
desc := desc
|
||||
|
||||
children, err := handler.Handle(ctx, desc)
|
||||
children, err := handler.Handle(ctx2, desc)
|
||||
if limiter != nil {
|
||||
limiter.Release(1)
|
||||
}
|
||||
|
@ -141,7 +142,7 @@ func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted,
|
|||
}
|
||||
|
||||
if len(children) > 0 {
|
||||
return Dispatch(ctx, handler, limiter, children...)
|
||||
return Dispatch(ctx2, handler, limiter, children...)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
|
@ -119,7 +118,7 @@ func (image *Image) Size(ctx context.Context, provider content.Provider, platfor
|
|||
}
|
||||
size += desc.Size
|
||||
return nil, nil
|
||||
}), FilterPlatforms(ChildrenHandler(provider), platform)), image.Target)
|
||||
}), LimitManifests(FilterPlatforms(ChildrenHandler(provider), platform), platform, 1)), image.Target)
|
||||
}
|
||||
|
||||
type platformManifest struct {
|
||||
|
@ -142,6 +141,7 @@ type platformManifest struct {
|
|||
// this direction because this abstraction is not needed.`
|
||||
func Manifest(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform platforms.MatchComparer) (ocispec.Manifest, error) {
|
||||
var (
|
||||
limit = 1
|
||||
m []platformManifest
|
||||
wasIndex bool
|
||||
)
|
||||
|
@ -210,10 +210,22 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
|
|||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(descs, func(i, j int) bool {
|
||||
if descs[i].Platform == nil {
|
||||
return false
|
||||
}
|
||||
if descs[j].Platform == nil {
|
||||
return true
|
||||
}
|
||||
return platform.Less(*descs[i].Platform, *descs[j].Platform)
|
||||
})
|
||||
|
||||
wasIndex = true
|
||||
|
||||
if len(descs) > limit {
|
||||
return descs[:limit], nil
|
||||
}
|
||||
return descs, nil
|
||||
|
||||
}
|
||||
return nil, errors.Wrapf(errdefs.ErrNotFound, "unexpected media type %v for %v", desc.MediaType, desc.Digest)
|
||||
}), image); err != nil {
|
||||
|
@ -227,17 +239,6 @@ func Manifest(ctx context.Context, provider content.Provider, image ocispec.Desc
|
|||
}
|
||||
return ocispec.Manifest{}, err
|
||||
}
|
||||
|
||||
sort.SliceStable(m, func(i, j int) bool {
|
||||
if m[i].p == nil {
|
||||
return false
|
||||
}
|
||||
if m[j].p == nil {
|
||||
return true
|
||||
}
|
||||
return platform.Less(*m[i].p, *m[j].p)
|
||||
})
|
||||
|
||||
return *m[0].m, nil
|
||||
}
|
||||
|
||||
|
@ -356,15 +357,11 @@ func Children(ctx context.Context, provider content.Provider, desc ocispec.Descr
|
|||
}
|
||||
|
||||
descs = append(descs, index.Manifests...)
|
||||
case MediaTypeDockerSchema2Layer, MediaTypeDockerSchema2LayerGzip,
|
||||
MediaTypeDockerSchema2LayerForeign, MediaTypeDockerSchema2LayerForeignGzip,
|
||||
MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig,
|
||||
ocispec.MediaTypeImageLayer, ocispec.MediaTypeImageLayerGzip,
|
||||
ocispec.MediaTypeImageLayerNonDistributable, ocispec.MediaTypeImageLayerNonDistributableGzip,
|
||||
MediaTypeContainerd1Checkpoint, MediaTypeContainerd1CheckpointConfig:
|
||||
default:
|
||||
if IsLayerType(desc.MediaType) || IsKnownConfig(desc.MediaType) {
|
||||
// childless data types.
|
||||
return nil, nil
|
||||
default:
|
||||
}
|
||||
log.G(ctx).Warnf("encountered unknown type %v; children may not be fetched", desc.MediaType)
|
||||
}
|
||||
|
||||
|
@ -387,22 +384,3 @@ func RootFS(ctx context.Context, provider content.Provider, configDesc ocispec.D
|
|||
}
|
||||
return config.RootFS.DiffIDs, nil
|
||||
}
|
||||
|
||||
// IsCompressedDiff returns true if mediaType is a known compressed diff media type.
|
||||
// It returns false if the media type is a diff, but not compressed. If the media type
|
||||
// is not a known diff type, it returns errdefs.ErrNotImplemented
|
||||
func IsCompressedDiff(ctx context.Context, mediaType string) (bool, error) {
|
||||
switch mediaType {
|
||||
case ocispec.MediaTypeImageLayer, MediaTypeDockerSchema2Layer:
|
||||
case ocispec.MediaTypeImageLayerGzip, MediaTypeDockerSchema2LayerGzip:
|
||||
return true, nil
|
||||
default:
|
||||
// Still apply all generic media types *.tar[.+]gzip and *.tar
|
||||
if strings.HasSuffix(mediaType, ".tar.gzip") || strings.HasSuffix(mediaType, ".tar+gzip") {
|
||||
return true, nil
|
||||
} else if !strings.HasSuffix(mediaType, ".tar") {
|
||||
return false, errdefs.ErrNotImplemented
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
|
|
@ -16,6 +16,15 @@
|
|||
|
||||
package images
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// mediatype definitions for image components handled in containerd.
|
||||
//
|
||||
// oci components are generally referenced directly, although we may centralize
|
||||
|
@ -40,3 +49,78 @@ const (
|
|||
// Legacy Docker schema1 manifest
|
||||
MediaTypeDockerSchema1Manifest = "application/vnd.docker.distribution.manifest.v1+prettyjws"
|
||||
)
|
||||
|
||||
// DiffCompression returns the compression as defined by the layer diff media
|
||||
// type. For Docker media types without compression, "unknown" is returned to
|
||||
// indicate that the media type may be compressed. If the media type is not
|
||||
// recognized as a layer diff, then it returns errdefs.ErrNotImplemented
|
||||
func DiffCompression(ctx context.Context, mediaType string) (string, error) {
|
||||
base, ext := parseMediaTypes(mediaType)
|
||||
switch base {
|
||||
case MediaTypeDockerSchema2Layer, MediaTypeDockerSchema2LayerForeign:
|
||||
if len(ext) > 0 {
|
||||
// Type is wrapped
|
||||
return "", nil
|
||||
}
|
||||
// These media types may have been compressed but failed to
|
||||
// use the correct media type. The decompression function
|
||||
// should detect and handle this case.
|
||||
return "unknown", nil
|
||||
case MediaTypeDockerSchema2LayerGzip, MediaTypeDockerSchema2LayerForeignGzip:
|
||||
if len(ext) > 0 {
|
||||
// Type is wrapped
|
||||
return "", nil
|
||||
}
|
||||
return "gzip", nil
|
||||
case ocispec.MediaTypeImageLayer, ocispec.MediaTypeImageLayerNonDistributable:
|
||||
if len(ext) > 0 {
|
||||
switch ext[len(ext)-1] {
|
||||
case "gzip":
|
||||
return "gzip", nil
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
default:
|
||||
return "", errdefs.ErrNotImplemented
|
||||
}
|
||||
}
|
||||
|
||||
// parseMediaTypes splits the media type into the base type and
|
||||
// an array of sorted extensions
|
||||
func parseMediaTypes(mt string) (string, []string) {
|
||||
if mt == "" {
|
||||
return "", []string{}
|
||||
}
|
||||
|
||||
s := strings.Split(mt, "+")
|
||||
ext := s[1:]
|
||||
sort.Strings(ext)
|
||||
|
||||
return s[0], ext
|
||||
}
|
||||
|
||||
// IsLayerTypes returns true if the media type is a layer
|
||||
func IsLayerType(mt string) bool {
|
||||
if strings.HasPrefix(mt, "application/vnd.oci.image.layer.") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Parse Docker media types, strip off any + suffixes first
|
||||
base, _ := parseMediaTypes(mt)
|
||||
switch base {
|
||||
case MediaTypeDockerSchema2Layer, MediaTypeDockerSchema2LayerGzip,
|
||||
MediaTypeDockerSchema2LayerForeign, MediaTypeDockerSchema2LayerForeignGzip:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsKnownConfig returns true if the media type is a known config type
|
||||
func IsKnownConfig(mt string) bool {
|
||||
switch mt {
|
||||
case MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig,
|
||||
MediaTypeContainerd1Checkpoint, MediaTypeContainerd1CheckpointConfig:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ var (
|
|||
// messages.
|
||||
G = GetLogger
|
||||
|
||||
// L is an alias for the the standard logger.
|
||||
// L is an alias for the standard logger.
|
||||
L = logrus.NewEntry(logrus.StandardLogger())
|
||||
)
|
||||
|
||||
|
|
|
@ -36,10 +36,9 @@ type namespaceKey struct{}
|
|||
// WithNamespace sets a given namespace on the context
|
||||
func WithNamespace(ctx context.Context, namespace string) context.Context {
|
||||
ctx = context.WithValue(ctx, namespaceKey{}, namespace) // set our key for namespace
|
||||
|
||||
// also store on the grpc headers so it gets picked up by any clients that
|
||||
// also store on the grpc and ttrpc headers so it gets picked up by any clients that
|
||||
// are using this.
|
||||
return withGRPCNamespaceHeader(ctx, namespace)
|
||||
return withTTRPCNamespaceHeader(withGRPCNamespaceHeader(ctx, namespace), namespace)
|
||||
}
|
||||
|
||||
// NamespaceFromEnv uses the namespace defined in CONTAINERD_NAMESPACE or
|
||||
|
@ -58,22 +57,21 @@ func NamespaceFromEnv(ctx context.Context) context.Context {
|
|||
func Namespace(ctx context.Context) (string, bool) {
|
||||
namespace, ok := ctx.Value(namespaceKey{}).(string)
|
||||
if !ok {
|
||||
return fromGRPCHeader(ctx)
|
||||
if namespace, ok = fromGRPCHeader(ctx); !ok {
|
||||
return fromTTRPCHeader(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
return namespace, ok
|
||||
}
|
||||
|
||||
// NamespaceRequired returns the valid namepace from the context or an error.
|
||||
// NamespaceRequired returns the valid namespace from the context or an error.
|
||||
func NamespaceRequired(ctx context.Context) (string, error) {
|
||||
namespace, ok := Namespace(ctx)
|
||||
if !ok || namespace == "" {
|
||||
return "", errors.Wrapf(errdefs.ErrFailedPrecondition, "namespace is required")
|
||||
}
|
||||
|
||||
if err := Validate(namespace); err != nil {
|
||||
return "", errors.Wrap(err, "namespace validation")
|
||||
}
|
||||
|
||||
return namespace, nil
|
||||
}
|
||||
|
|
|
@ -33,5 +33,14 @@ type Store interface {
|
|||
List(ctx context.Context) ([]string, error)
|
||||
|
||||
// Delete removes the namespace. The namespace must be empty to be deleted.
|
||||
Delete(ctx context.Context, namespace string) error
|
||||
Delete(ctx context.Context, namespace string, opts ...DeleteOpts) error
|
||||
}
|
||||
|
||||
// DeleteInfo specifies information for the deletion of a namespace
|
||||
type DeleteInfo struct {
|
||||
// Name of the namespace
|
||||
Name string
|
||||
}
|
||||
|
||||
// DeleteOpts allows the caller to set options for namespace deletion
|
||||
type DeleteOpts func(context.Context, *DeleteInfo) error
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
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 namespaces
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containerd/ttrpc"
|
||||
)
|
||||
|
||||
const (
|
||||
// TTRPCHeader defines the header name for specifying a containerd namespace
|
||||
TTRPCHeader = "containerd-namespace-ttrpc"
|
||||
)
|
||||
|
||||
func copyMetadata(src ttrpc.MD) ttrpc.MD {
|
||||
md := ttrpc.MD{}
|
||||
for k, v := range src {
|
||||
md[k] = append(md[k], v...)
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
func withTTRPCNamespaceHeader(ctx context.Context, namespace string) context.Context {
|
||||
md, ok := ttrpc.GetMetadata(ctx)
|
||||
if !ok {
|
||||
md = ttrpc.MD{}
|
||||
} else {
|
||||
md = copyMetadata(md)
|
||||
}
|
||||
md.Set(TTRPCHeader, namespace)
|
||||
return ttrpc.WithMetadata(ctx, md)
|
||||
}
|
||||
|
||||
func fromTTRPCHeader(ctx context.Context) (string, bool) {
|
||||
return ttrpc.GetMetadataValue(ctx, TTRPCHeader)
|
||||
}
|
|
@ -78,7 +78,7 @@ func generateDefaultSpecWithPlatform(ctx context.Context, platform, id string, s
|
|||
return err
|
||||
}
|
||||
|
||||
// ApplyOpts applys the options to the given spec, injecting data from the
|
||||
// ApplyOpts applies the options to the given spec, injecting data from the
|
||||
// context, client and container instance.
|
||||
func ApplyOpts(ctx context.Context, client Client, c *containers.Container, s *Spec, opts ...SpecOpts) error {
|
||||
for _, o := range opts {
|
||||
|
@ -141,7 +141,6 @@ func populateDefaultUnixSpec(ctx context.Context, s *Spec, id string) error {
|
|||
Path: defaultRootfsPath,
|
||||
},
|
||||
Process: &specs.Process{
|
||||
Env: defaultUnixEnv,
|
||||
Cwd: "/",
|
||||
NoNewPrivileges: true,
|
||||
User: specs.User{
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package oci
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
@ -76,6 +77,20 @@ func setLinux(s *Spec) {
|
|||
}
|
||||
}
|
||||
|
||||
// nolint
|
||||
func setResources(s *Spec) {
|
||||
if s.Linux != nil {
|
||||
if s.Linux.Resources == nil {
|
||||
s.Linux.Resources = &specs.LinuxResources{}
|
||||
}
|
||||
}
|
||||
if s.Windows != nil {
|
||||
if s.Windows.Resources == nil {
|
||||
s.Windows.Resources = &specs.WindowsResources{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setCapabilities sets Linux Capabilities to empty if unset
|
||||
func setCapabilities(s *Spec) {
|
||||
setProcess(s)
|
||||
|
@ -104,7 +119,7 @@ func WithDefaultSpecForPlatform(platform string) SpecOpts {
|
|||
}
|
||||
}
|
||||
|
||||
// WithSpecFromBytes loads the the spec from the provided byte slice.
|
||||
// WithSpecFromBytes loads the spec from the provided byte slice.
|
||||
func WithSpecFromBytes(p []byte) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
*s = Spec{} // make sure spec is cleared.
|
||||
|
@ -137,6 +152,13 @@ func WithEnv(environmentVariables []string) SpecOpts {
|
|||
}
|
||||
}
|
||||
|
||||
// WithDefaultPathEnv sets the $PATH environment variable to the
|
||||
// default PATH defined in this package.
|
||||
func WithDefaultPathEnv(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, defaultUnixEnv)
|
||||
return nil
|
||||
}
|
||||
|
||||
// replaceOrAppendEnvValues returns the defaults with the overrides either
|
||||
// replaced by env key or appended to the list
|
||||
func replaceOrAppendEnvValues(defaults, overrides []string) []string {
|
||||
|
@ -312,7 +334,11 @@ func WithImageConfigArgs(image Image, args []string) SpecOpts {
|
|||
|
||||
setProcess(s)
|
||||
if s.Linux != nil {
|
||||
s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, config.Env)
|
||||
defaults := config.Env
|
||||
if len(defaults) == 0 {
|
||||
defaults = defaultUnixEnv
|
||||
}
|
||||
s.Process.Env = replaceOrAppendEnvValues(defaults, s.Process.Env)
|
||||
cmd := config.Cmd
|
||||
if len(args) > 0 {
|
||||
cmd = args
|
||||
|
@ -334,7 +360,7 @@ func WithImageConfigArgs(image Image, args []string) SpecOpts {
|
|||
// even if there is no specified user in the image config
|
||||
return WithAdditionalGIDs("root")(ctx, client, c, s)
|
||||
} else if s.Windows != nil {
|
||||
s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, config.Env)
|
||||
s.Process.Env = replaceOrAppendEnvValues(config.Env, s.Process.Env)
|
||||
cmd := config.Cmd
|
||||
if len(args) > 0 {
|
||||
cmd = args
|
||||
|
@ -607,7 +633,7 @@ func WithUserID(uid uint32) SpecOpts {
|
|||
}
|
||||
|
||||
// WithUsername sets the correct UID and GID for the container
|
||||
// based on the the image's /etc/passwd contents. If /etc/passwd
|
||||
// based on the image's /etc/passwd contents. If /etc/passwd
|
||||
// does not exist, or the username is not found in /etc/passwd,
|
||||
// it returns error.
|
||||
func WithUsername(username string) SpecOpts {
|
||||
|
@ -1139,3 +1165,85 @@ func WithAnnotations(annotations map[string]string) SpecOpts {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLinuxDevices adds the provided linux devices to the spec
|
||||
func WithLinuxDevices(devices []specs.LinuxDevice) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
s.Linux.Devices = append(s.Linux.Devices, devices...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var ErrNotADevice = errors.New("not a device node")
|
||||
|
||||
// WithLinuxDevice adds the device specified by path to the spec
|
||||
func WithLinuxDevice(path, permissions string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
setLinux(s)
|
||||
setResources(s)
|
||||
|
||||
dev, err := deviceFromPath(path, permissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Linux.Devices = append(s.Linux.Devices, *dev)
|
||||
|
||||
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, specs.LinuxDeviceCgroup{
|
||||
Type: dev.Type,
|
||||
Allow: true,
|
||||
Major: &dev.Major,
|
||||
Minor: &dev.Minor,
|
||||
Access: permissions,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnvFile adds environment variables from a file to the container's spec
|
||||
func WithEnvFile(path string) SpecOpts {
|
||||
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
|
||||
var vars []string
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
sc := bufio.NewScanner(f)
|
||||
for sc.Scan() {
|
||||
if sc.Err() != nil {
|
||||
return sc.Err()
|
||||
}
|
||||
vars = append(vars, sc.Text())
|
||||
}
|
||||
return WithEnv(vars)(nil, nil, nil, s)
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNoShmMount is returned when there is no /dev/shm mount specified in the config
|
||||
// and an Opts was trying to set a configuration value on the mount.
|
||||
var ErrNoShmMount = errors.New("no /dev/shm mount specified")
|
||||
|
||||
// WithDevShmSize sets the size of the /dev/shm mount for the container.
|
||||
//
|
||||
// The size value is specified in kb, kilobytes.
|
||||
func WithDevShmSize(kb int64) SpecOpts {
|
||||
return func(ctx context.Context, _ Client, c *containers.Container, s *Spec) error {
|
||||
for _, m := range s.Mounts {
|
||||
if m.Source == "shm" && m.Type == "tmpfs" {
|
||||
for i, o := range m.Options {
|
||||
if strings.HasPrefix(o, "size=") {
|
||||
m.Options[i] = fmt.Sprintf("size=%dk", kb)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
m.Options = append(m.Options, fmt.Sprintf("size=%dk", kb))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrNoShmMount
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
// +build linux
|
||||
|
||||
/*
|
||||
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 oci
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Lstat(path, &stat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
// The type is 32bit on mips.
|
||||
devNumber = uint64(stat.Rdev) // nolint: unconvert
|
||||
major = unix.Major(devNumber)
|
||||
minor = unix.Minor(devNumber)
|
||||
)
|
||||
if major == 0 {
|
||||
return nil, ErrNotADevice
|
||||
}
|
||||
|
||||
var (
|
||||
devType string
|
||||
mode = stat.Mode
|
||||
)
|
||||
switch {
|
||||
case mode&unix.S_IFBLK == unix.S_IFBLK:
|
||||
devType = "b"
|
||||
case mode&unix.S_IFCHR == unix.S_IFCHR:
|
||||
devType = "c"
|
||||
}
|
||||
fm := os.FileMode(mode)
|
||||
return &specs.LinuxDevice{
|
||||
Type: devType,
|
||||
Path: path,
|
||||
Major: int64(major),
|
||||
Minor: int64(minor),
|
||||
FileMode: &fm,
|
||||
UID: &stat.Uid,
|
||||
GID: &stat.Gid,
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// +build !linux,!windows
|
||||
|
||||
/*
|
||||
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 oci
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) {
|
||||
var stat unix.Stat_t
|
||||
if err := unix.Lstat(path, &stat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
devNumber = uint64(stat.Rdev)
|
||||
major = unix.Major(devNumber)
|
||||
minor = unix.Minor(devNumber)
|
||||
)
|
||||
if major == 0 {
|
||||
return nil, ErrNotADevice
|
||||
}
|
||||
|
||||
var (
|
||||
devType string
|
||||
mode = stat.Mode
|
||||
)
|
||||
switch {
|
||||
case mode&unix.S_IFBLK == unix.S_IFBLK:
|
||||
devType = "b"
|
||||
case mode&unix.S_IFCHR == unix.S_IFCHR:
|
||||
devType = "c"
|
||||
}
|
||||
fm := os.FileMode(mode)
|
||||
return &specs.LinuxDevice{
|
||||
Type: devType,
|
||||
Path: path,
|
||||
Major: int64(major),
|
||||
Minor: int64(minor),
|
||||
FileMode: &fm,
|
||||
UID: &stat.Uid,
|
||||
GID: &stat.Gid,
|
||||
}, nil
|
||||
}
|
|
@ -23,6 +23,7 @@ import (
|
|||
|
||||
"github.com/containerd/containerd/containers"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// WithWindowsCPUCount sets the `Windows.Resources.CPU.Count` section to the
|
||||
|
@ -65,3 +66,7 @@ func WithWindowNetworksAllowUnqualifiedDNSQuery() SpecOpts {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) {
|
||||
return nil, errors.New("device from path not supported on Windows")
|
||||
}
|
||||
|
|
|
@ -29,11 +29,48 @@ type MatchComparer interface {
|
|||
// Only returns a match comparer for a single platform
|
||||
// using default resolution logic for the platform.
|
||||
//
|
||||
// For ARMv8, will also match ARMv7, ARMv6 and ARMv5 (for 32bit runtimes)
|
||||
// For ARMv7, will also match ARMv6 and ARMv5
|
||||
// For ARMv6, will also match ARMv5
|
||||
func Only(platform specs.Platform) MatchComparer {
|
||||
platform = Normalize(platform)
|
||||
if platform.Architecture == "arm" {
|
||||
if platform.Variant == "v8" {
|
||||
return orderedPlatformComparer{
|
||||
matchers: []Matcher{
|
||||
&matcher{
|
||||
Platform: platform,
|
||||
},
|
||||
&matcher{
|
||||
Platform: specs.Platform{
|
||||
Architecture: platform.Architecture,
|
||||
OS: platform.OS,
|
||||
OSVersion: platform.OSVersion,
|
||||
OSFeatures: platform.OSFeatures,
|
||||
Variant: "v7",
|
||||
},
|
||||
},
|
||||
&matcher{
|
||||
Platform: specs.Platform{
|
||||
Architecture: platform.Architecture,
|
||||
OS: platform.OS,
|
||||
OSVersion: platform.OSVersion,
|
||||
OSFeatures: platform.OSFeatures,
|
||||
Variant: "v6",
|
||||
},
|
||||
},
|
||||
&matcher{
|
||||
Platform: specs.Platform{
|
||||
Architecture: platform.Architecture,
|
||||
OS: platform.OS,
|
||||
OSVersion: platform.OSVersion,
|
||||
OSFeatures: platform.OSFeatures,
|
||||
Variant: "v5",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if platform.Variant == "v7" {
|
||||
return orderedPlatformComparer{
|
||||
matchers: []Matcher{
|
||||
|
|
|
@ -97,7 +97,7 @@ func getCPUVariant() string {
|
|||
}
|
||||
|
||||
switch variant {
|
||||
case "8":
|
||||
case "8", "AArch64":
|
||||
variant = "v8"
|
||||
case "7", "7M", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
|
||||
variant = "v7"
|
||||
|
|
|
@ -28,7 +28,7 @@ func isLinuxOS(os string) bool {
|
|||
return os == "linux"
|
||||
}
|
||||
|
||||
// These function are generated from from https://golang.org/src/go/build/syslist.go.
|
||||
// These function are generated from https://golang.org/src/go/build/syslist.go.
|
||||
//
|
||||
// We use switch statements because they are slightly faster than map lookups
|
||||
// and use a little less memory.
|
||||
|
@ -38,7 +38,7 @@ func isLinuxOS(os string) bool {
|
|||
// The OS value should be normalized before calling this function.
|
||||
func isKnownOS(os string) bool {
|
||||
switch os {
|
||||
case "android", "darwin", "dragonfly", "freebsd", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos":
|
||||
case "aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -60,7 +60,7 @@ func isArmArch(arch string) bool {
|
|||
// The arch value should be normalized before being passed to this function.
|
||||
func isKnownArch(arch string) bool {
|
||||
switch arch {
|
||||
case "386", "amd64", "amd64p32", "arm", "armbe", "arm64", "arm64be", "ppc64", "ppc64le", "mips", "mipsle", "mips64", "mips64le", "mips64p32", "mips64p32le", "ppc", "s390", "s390x", "sparc", "sparc64":
|
||||
case "386", "amd64", "amd64p32", "arm", "armbe", "arm64", "arm64be", "ppc64", "ppc64le", "mips", "mipsle", "mips64", "mips64le", "mips64p32", "mips64p32le", "ppc", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
|
|
@ -130,7 +130,7 @@ type Matcher interface {
|
|||
// specification. The returned matcher only looks for equality based on os,
|
||||
// architecture and variant.
|
||||
//
|
||||
// One may implement their own matcher if this doesn't provide the the required
|
||||
// One may implement their own matcher if this doesn't provide the required
|
||||
// functionality.
|
||||
//
|
||||
// Applications should opt to use `Match` over directly parsing specifiers.
|
||||
|
|
|
@ -89,7 +89,12 @@ type Info struct {
|
|||
Kind Kind // active or committed snapshot
|
||||
Name string // name or key of snapshot
|
||||
Parent string `json:",omitempty"` // name of parent snapshot
|
||||
Labels map[string]string `json:",omitempty"` // Labels for snapshot
|
||||
|
||||
// Labels for a snapshot.
|
||||
//
|
||||
// Note: only labels prefixed with `containerd.io/snapshot/` will be inherited by the
|
||||
// snapshotter's `Prepare`, `View`, or `Commit` calls.
|
||||
Labels map[string]string `json:",omitempty"`
|
||||
Created time.Time `json:",omitempty"` // Created time
|
||||
Updated time.Time `json:",omitempty"` // Last update time
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
|
||||
github.com/containerd/go-runc e029b79d8cda8374981c64eba71f28ec38e5526f
|
||||
github.com/containerd/console 0650fd9eeb50bab4fc99dceb9f2e14cf58f36e7f
|
||||
github.com/containerd/cgroups 4994991857f9b0ae8dc439551e8bebdbb4bf66c1
|
||||
github.com/containerd/cgroups c4b9ac5c7601384c965b9646fc515884e091ebb9
|
||||
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
|
||||
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
|
||||
github.com/containerd/fifo bda0ff6ed73c67bfb5e62bc9c697f146b7fd7f13
|
||||
github.com/containerd/btrfs af5082808c833de0e79c1e72eea9fea239364877
|
||||
github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
|
||||
github.com/containerd/continuity f2a389ac0a02ce21c09edd7344677a601970f41c
|
||||
github.com/coreos/go-systemd 48702e0da86bd25e76cfef347e2adeb434a0d0a6
|
||||
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
|
||||
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
|
||||
|
@ -20,64 +20,70 @@ github.com/gogo/protobuf v1.2.1
|
|||
github.com/gogo/googleapis v1.2.0
|
||||
github.com/golang/protobuf v1.2.0
|
||||
github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db
|
||||
github.com/opencontainers/runc v1.0.0-rc8
|
||||
github.com/opencontainers/runc 3e425f80a8c931f88e6d94a8c831b9d5aa481657 # v1.0.0-rc8+ CVE-2019-16884
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1
|
||||
github.com/sirupsen/logrus v1.4.1
|
||||
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
|
||||
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
|
||||
google.golang.org/grpc v1.12.0
|
||||
github.com/urfave/cli v1.22.0
|
||||
golang.org/x/net f3200d17e092c607f615320ecaad13d87ad9a2b3
|
||||
google.golang.org/grpc 6eaf6f47437a6b4e2153a190160ef39a92c7eceb # v1.23.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
|
||||
golang.org/x/sys d455e41777fca6e8a5a79e34a14b8368bc11d9ba https://github.com/golang/sys
|
||||
golang.org/x/sys 9eafafc0a87e0fd0aeeba439a4573537970c44c7 https://github.com/golang/sys
|
||||
github.com/opencontainers/image-spec v1.0.1
|
||||
golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
||||
github.com/Microsoft/go-winio 84b4ab48a50763fe7b3abcef38e5205c12027fac
|
||||
github.com/Microsoft/hcsshim 8abdbb8205e4192c68b5f84c31197156f31be517
|
||||
github.com/Microsoft/go-winio v0.4.14
|
||||
github.com/Microsoft/hcsshim 9e921883ac929bbe515b39793ece99ce3a9d7706
|
||||
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
|
||||
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
|
||||
github.com/containerd/ttrpc 699c4e40d1e7416e08bf7019c7ce2e9beced4636
|
||||
github.com/containerd/ttrpc 92c8520ef9f86600c650dd540266a007bf03670f
|
||||
github.com/syndtr/gocapability d98352740cb2c55f81556b63d4a1ec64c5a319c2
|
||||
gotest.tools v2.3.0
|
||||
github.com/google/go-cmp v0.2.0
|
||||
go.etcd.io/bbolt v1.3.2
|
||||
go.etcd.io/bbolt v1.3.3
|
||||
github.com/hashicorp/errwrap v1.0.0
|
||||
github.com/hashicorp/go-multierror v1.0.0
|
||||
github.com/hashicorp/golang-lru v0.5.3
|
||||
go.opencensus.io v0.22.0
|
||||
github.com/imdario/mergo v0.3.7
|
||||
github.com/cpuguy83/go-md2man v1.0.10
|
||||
github.com/russross/blackfriday v1.5.2
|
||||
|
||||
# cri dependencies
|
||||
github.com/containerd/cri 2fc62db8146ce66f27b37306ad5fda34207835f3 # master
|
||||
github.com/containerd/go-cni 891c2a41e18144b2d7921f971d6c9789a68046b2
|
||||
github.com/containernetworking/cni v0.6.0
|
||||
github.com/containernetworking/plugins v0.7.0
|
||||
github.com/davecgh/go-spew v1.1.0
|
||||
github.com/containerd/cri 5d49e7e51b43e36a6b9c4386257c7d08c602237f # release/1.3
|
||||
github.com/containerd/go-cni 49fbd9b210f3c8ee3b7fd3cd797aabaf364627c1
|
||||
github.com/containernetworking/cni v0.7.1
|
||||
github.com/containernetworking/plugins v0.7.6
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580
|
||||
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
|
||||
github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528
|
||||
github.com/emicklei/go-restful v2.2.1
|
||||
github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c
|
||||
github.com/hashicorp/errwrap 7554cd9344cec97297fa6649b055a8c98c2a1e55
|
||||
github.com/hashicorp/go-multierror ed905158d87462226a13fe39ddf685ea65f1c11f
|
||||
github.com/json-iterator/go 1.1.5
|
||||
github.com/emicklei/go-restful v2.9.5
|
||||
github.com/google/gofuzz v1.0.0
|
||||
github.com/json-iterator/go v1.1.7
|
||||
github.com/modern-go/reflect2 1.0.1
|
||||
github.com/modern-go/concurrent 1.0.3
|
||||
github.com/opencontainers/selinux v1.2.2
|
||||
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
|
||||
github.com/seccomp/libseccomp-golang v0.9.1
|
||||
github.com/tchap/go-patricia v2.2.6
|
||||
golang.org/x/crypto 88737f569e3a9c7ab309cdc09a07fe7fc87233c3
|
||||
golang.org/x/oauth2 a6bd8cefa1811bd24b86f8902872e4e8225f74c4
|
||||
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
|
||||
gopkg.in/inf.v0 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
k8s.io/api kubernetes-1.15.0-alpha.0
|
||||
k8s.io/apimachinery kubernetes-1.15.0-alpha.0
|
||||
k8s.io/apiserver kubernetes-1.15.0-alpha.0
|
||||
k8s.io/client-go kubernetes-1.15.0-alpha.0
|
||||
k8s.io/klog 8139d8cb77af419532b33dfa7dd09fbc5f1d344f
|
||||
k8s.io/kubernetes v1.15.0-alpha.0
|
||||
golang.org/x/crypto 5c40567a22f818bd14a1ea7245dad9f8ef0691aa
|
||||
golang.org/x/oauth2 0f29369cfe4552d0e4bcddc57cc75f4d7e672a33
|
||||
golang.org/x/time 85acf8d2951cb2a3bde7632f9ff273ef0379bcbd
|
||||
gopkg.in/inf.v0 v0.9.0
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
k8s.io/api kubernetes-1.16.0-rc.2
|
||||
k8s.io/apimachinery kubernetes-1.16.0-rc.2
|
||||
k8s.io/apiserver kubernetes-1.16.0-rc.2
|
||||
k8s.io/cri-api kubernetes-1.16.0-rc.2
|
||||
k8s.io/client-go kubernetes-1.16.0-rc.2
|
||||
k8s.io/klog v0.4.0
|
||||
k8s.io/kubernetes v1.16.0-rc.2
|
||||
k8s.io/utils c2654d5206da6b7b6ace12841e8f359bb89b443c
|
||||
sigs.k8s.io/yaml v1.1.0
|
||||
|
||||
# zfs dependencies
|
||||
github.com/containerd/zfs 31af176f2ae84fe142ef2655bf7bb2aa618b3b1f
|
||||
github.com/containerd/zfs 2ceb2dbb8154202ed1b8fd32e4ea25b491d7b251
|
||||
github.com/mistifyio/go-zfs f784269be439d704d3dfa1906f45dd848fed2beb
|
||||
github.com/google/uuid v1.1.1
|
||||
|
||||
|
|
|
@ -1,673 +0,0 @@
|
|||
/*
|
||||
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 (
|
||||
"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 represents the resource not found
|
||||
ErrNotFound = fmt.Errorf("not found")
|
||||
// ErrNotSupported represents the resource not supported
|
||||
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)
|
||||
|
||||
// ContextOptions represents options to create a new context.
|
||||
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.Size(), rf.Mode())
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
root := c.root
|
||||
fi, err := c.driver.Lstat(c.root)
|
||||
if err == nil && fi.Mode()&os.ModeSymlink != 0 {
|
||||
root, err = c.driver.Readlink(c.root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return c.pathDriver.Walk(root, func(p string, fi os.FileInfo, err error) error {
|
||||
contained, err := c.containWithRoot(p, root)
|
||||
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) {
|
||||
return c.containWithRoot(p, c.root)
|
||||
}
|
||||
|
||||
// containWithRoot cleans and santizes the filesystem path p to be an absolute path,
|
||||
// effectively relative to the passed root. Extra care should be used when calling this
|
||||
// instead of contain. This is needed for Walk, as if context root is a symlink,
|
||||
// it must be evaluated prior to the Walk
|
||||
func (c *context) containWithRoot(p string, root string) (string, error) {
|
||||
sanitized, err := c.pathDriver.Rel(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
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
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 devices
|
||||
|
||||
import "fmt"
|
||||
|
||||
var ErrNotSupported = fmt.Errorf("not supported")
|
|
@ -1,74 +0,0 @@
|
|||
// +build linux darwin freebsd solaris
|
||||
|
||||
/*
|
||||
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 devices
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
|
||||
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, 0, fmt.Errorf("cannot extract device from os.FileInfo")
|
||||
}
|
||||
|
||||
dev := uint64(sys.Rdev)
|
||||
return uint64(unix.Major(dev)), uint64(unix.Minor(dev)), nil
|
||||
}
|
||||
|
||||
// mknod provides a shortcut for syscall.Mknod
|
||||
func Mknod(p string, mode os.FileMode, maj, min int) error {
|
||||
var (
|
||||
m = syscallMode(mode.Perm())
|
||||
dev uint64
|
||||
)
|
||||
|
||||
if mode&os.ModeDevice != 0 {
|
||||
dev = unix.Mkdev(uint32(maj), uint32(min))
|
||||
|
||||
if mode&os.ModeCharDevice != 0 {
|
||||
m |= unix.S_IFCHR
|
||||
} else {
|
||||
m |= unix.S_IFBLK
|
||||
}
|
||||
} else if mode&os.ModeNamedPipe != 0 {
|
||||
m |= unix.S_IFIFO
|
||||
}
|
||||
|
||||
return unix.Mknod(p, m, int(dev))
|
||||
}
|
||||
|
||||
// syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
|
||||
func syscallMode(i os.FileMode) (o uint32) {
|
||||
o |= uint32(i.Perm())
|
||||
if i&os.ModeSetuid != 0 {
|
||||
o |= unix.S_ISUID
|
||||
}
|
||||
if i&os.ModeSetgid != 0 {
|
||||
o |= unix.S_ISGID
|
||||
}
|
||||
if i&os.ModeSticky != 0 {
|
||||
o |= unix.S_ISVTX
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
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 (
|
||||
"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] }
|
|
@ -1,174 +0,0 @@
|
|||
/*
|
||||
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 driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var ErrNotSupported = fmt.Errorf("not supported")
|
||||
|
||||
// Driver provides all of the system-level functions in a common interface.
|
||||
// The context should call these with full paths and should never use the `os`
|
||||
// package or any other package to access resources on the filesystem. This
|
||||
// mechanism let's us carefully control access to the context and maintain
|
||||
// path and resource integrity. It also gives us an interface to reason about
|
||||
// direct resource access.
|
||||
//
|
||||
// Implementations don't need to do much other than meet the interface. For
|
||||
// example, it is not required to wrap os.FileInfo to return correct paths for
|
||||
// the call to Name().
|
||||
type Driver interface {
|
||||
// Note that Open() returns a File interface instead of *os.File. This
|
||||
// is because os.File is a struct, so if Open was to return *os.File,
|
||||
// the only way to fulfill the interface would be to call os.Open()
|
||||
Open(path string) (File, error)
|
||||
OpenFile(path string, flag int, perm os.FileMode) (File, error)
|
||||
|
||||
Stat(path string) (os.FileInfo, error)
|
||||
Lstat(path string) (os.FileInfo, error)
|
||||
Readlink(p string) (string, error)
|
||||
Mkdir(path string, mode os.FileMode) error
|
||||
Remove(path string) error
|
||||
|
||||
Link(oldname, newname string) error
|
||||
Lchmod(path string, mode os.FileMode) error
|
||||
Lchown(path string, uid, gid int64) error
|
||||
Symlink(oldname, newname string) error
|
||||
|
||||
MkdirAll(path string, perm os.FileMode) error
|
||||
RemoveAll(path string) error
|
||||
|
||||
// TODO(aaronl): These methods might move outside the main Driver
|
||||
// interface in the future as more platforms are added.
|
||||
Mknod(path string, mode os.FileMode, major int, minor int) error
|
||||
Mkfifo(path string, mode os.FileMode) error
|
||||
}
|
||||
|
||||
// File is the interface for interacting with files returned by continuity's Open
|
||||
// This is needed since os.File is a struct, instead of an interface, so it can't
|
||||
// be used.
|
||||
type File interface {
|
||||
io.ReadWriteCloser
|
||||
io.Seeker
|
||||
Readdir(n int) ([]os.FileInfo, error)
|
||||
}
|
||||
|
||||
func NewSystemDriver() (Driver, error) {
|
||||
// TODO(stevvooe): Consider having this take a "hint" path argument, which
|
||||
// would be the context root. The hint could be used to resolve required
|
||||
// filesystem support when assembling the driver to use.
|
||||
return &driver{}, nil
|
||||
}
|
||||
|
||||
// XAttrDriver should be implemented on operation systems and filesystems that
|
||||
// have xattr support for regular files and directories.
|
||||
type XAttrDriver interface {
|
||||
// Getxattr returns all of the extended attributes for the file at path.
|
||||
// Typically, this takes a syscall call to Listxattr and Getxattr.
|
||||
Getxattr(path string) (map[string][]byte, error)
|
||||
|
||||
// Setxattr sets all of the extended attributes on file at path, following
|
||||
// any symbolic links, if necessary. All attributes on the target are
|
||||
// replaced by the values from attr. If the operation fails to set any
|
||||
// attribute, those already applied will not be rolled back.
|
||||
Setxattr(path string, attr map[string][]byte) error
|
||||
}
|
||||
|
||||
// LXAttrDriver should be implemented by drivers on operating systems and
|
||||
// filesystems that support setting and getting extended attributes on
|
||||
// symbolic links. If this is not implemented, extended attributes will be
|
||||
// ignored on symbolic links.
|
||||
type LXAttrDriver interface {
|
||||
// LGetxattr returns all of the extended attributes for the file at path
|
||||
// and does not follow symlinks. Typically, this takes a syscall call to
|
||||
// Llistxattr and Lgetxattr.
|
||||
LGetxattr(path string) (map[string][]byte, error)
|
||||
|
||||
// LSetxattr sets all of the extended attributes on file at path, without
|
||||
// following symbolic links. All attributes on the target are replaced by
|
||||
// the values from attr. If the operation fails to set any attribute,
|
||||
// those already applied will not be rolled back.
|
||||
LSetxattr(path string, attr map[string][]byte) error
|
||||
}
|
||||
|
||||
type DeviceInfoDriver interface {
|
||||
DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error)
|
||||
}
|
||||
|
||||
// driver is a simple default implementation that sends calls out to the "os"
|
||||
// package. Extend the "driver" type in system-specific files to add support,
|
||||
// such as xattrs, which can add support at compile time.
|
||||
type driver struct{}
|
||||
|
||||
var _ File = &os.File{}
|
||||
|
||||
// LocalDriver is the exported Driver struct for convenience.
|
||||
var LocalDriver Driver = &driver{}
|
||||
|
||||
func (d *driver) Open(p string) (File, error) {
|
||||
return os.Open(p)
|
||||
}
|
||||
|
||||
func (d *driver) OpenFile(path string, flag int, perm os.FileMode) (File, error) {
|
||||
return os.OpenFile(path, flag, perm)
|
||||
}
|
||||
|
||||
func (d *driver) Stat(p string) (os.FileInfo, error) {
|
||||
return os.Stat(p)
|
||||
}
|
||||
|
||||
func (d *driver) Lstat(p string) (os.FileInfo, error) {
|
||||
return os.Lstat(p)
|
||||
}
|
||||
|
||||
func (d *driver) Mkdir(p string, mode os.FileMode) error {
|
||||
return os.Mkdir(p, mode)
|
||||
}
|
||||
|
||||
// Remove is used to unlink files and remove directories.
|
||||
// This is following the golang os package api which
|
||||
// combines the operations into a higher level Remove
|
||||
// function. If explicit unlinking or directory removal
|
||||
// to mirror system call is required, they should be
|
||||
// split up at that time.
|
||||
func (d *driver) Remove(path string) error {
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
func (d *driver) Link(oldname, newname string) error {
|
||||
return os.Link(oldname, newname)
|
||||
}
|
||||
|
||||
func (d *driver) Lchown(name string, uid, gid int64) error {
|
||||
// TODO: error out if uid excesses int bit width?
|
||||
return os.Lchown(name, int(uid), int(gid))
|
||||
}
|
||||
|
||||
func (d *driver) Symlink(oldname, newname string) error {
|
||||
return os.Symlink(oldname, newname)
|
||||
}
|
||||
|
||||
func (d *driver) MkdirAll(path string, perm os.FileMode) error {
|
||||
return os.MkdirAll(path, perm)
|
||||
}
|
||||
|
||||
func (d *driver) RemoveAll(path string) error {
|
||||
return os.RemoveAll(path)
|
||||
}
|
|
@ -1,138 +0,0 @@
|
|||
// +build linux darwin freebsd solaris
|
||||
|
||||
/*
|
||||
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 driver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/containerd/continuity/devices"
|
||||
"github.com/containerd/continuity/sysx"
|
||||
)
|
||||
|
||||
func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error {
|
||||
err := devices.Mknod(path, mode, major, minor)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "mknod", Path: path, Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *driver) Mkfifo(path string, mode os.FileMode) error {
|
||||
if mode&os.ModeNamedPipe == 0 {
|
||||
return errors.New("mode passed to Mkfifo does not have the named pipe bit set")
|
||||
}
|
||||
// mknod with a mode that has ModeNamedPipe set creates a fifo, not a
|
||||
// device.
|
||||
err := devices.Mknod(path, mode, 0, 0)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "mkfifo", Path: path, Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Getxattr returns all of the extended attributes for the file at path p.
|
||||
func (d *driver) Getxattr(p string) (map[string][]byte, error) {
|
||||
xattrs, err := sysx.Listxattr(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
|
||||
}
|
||||
|
||||
sort.Strings(xattrs)
|
||||
m := make(map[string][]byte, len(xattrs))
|
||||
|
||||
for _, attr := range xattrs {
|
||||
value, err := sysx.Getxattr(p, attr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
|
||||
}
|
||||
|
||||
// NOTE(stevvooe): This append/copy tricky relies on unique
|
||||
// xattrs. Break this out into an alloc/copy if xattrs are no
|
||||
// longer unique.
|
||||
m[attr] = append(m[attr], value...)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Setxattr sets all of the extended attributes on file at path, following
|
||||
// any symbolic links, if necessary. All attributes on the target are
|
||||
// replaced by the values from attr. If the operation fails to set any
|
||||
// attribute, those already applied will not be rolled back.
|
||||
func (d *driver) Setxattr(path string, attrMap map[string][]byte) error {
|
||||
for attr, value := range attrMap {
|
||||
if err := sysx.Setxattr(path, attr, value, 0); err != nil {
|
||||
return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LGetxattr returns all of the extended attributes for the file at path p
|
||||
// not following symbolic links.
|
||||
func (d *driver) LGetxattr(p string) (map[string][]byte, error) {
|
||||
xattrs, err := sysx.LListxattr(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
|
||||
}
|
||||
|
||||
sort.Strings(xattrs)
|
||||
m := make(map[string][]byte, len(xattrs))
|
||||
|
||||
for _, attr := range xattrs {
|
||||
value, err := sysx.LGetxattr(p, attr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
|
||||
}
|
||||
|
||||
// NOTE(stevvooe): This append/copy tricky relies on unique
|
||||
// xattrs. Break this out into an alloc/copy if xattrs are no
|
||||
// longer unique.
|
||||
m[attr] = append(m[attr], value...)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// LSetxattr sets all of the extended attributes on file at path, not
|
||||
// following any symbolic links. All attributes on the target are
|
||||
// replaced by the values from attr. If the operation fails to set any
|
||||
// attribute, those already applied will not be rolled back.
|
||||
func (d *driver) LSetxattr(path string, attrMap map[string][]byte) error {
|
||||
for attr, value := range attrMap {
|
||||
if err := sysx.LSetxattr(path, attr, value, 0); err != nil {
|
||||
return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *driver) DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) {
|
||||
return devices.DeviceInfo(fi)
|
||||
}
|
||||
|
||||
// Readlink was forked on Windows to fix a Golang bug, use the "os" package here
|
||||
func (d *driver) Readlink(p string) (string, error) {
|
||||
return os.Readlink(p)
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
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 driver
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/containerd/continuity/sysx"
|
||||
)
|
||||
|
||||
func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error {
|
||||
return &os.PathError{Op: "mknod", Path: path, Err: ErrNotSupported}
|
||||
}
|
||||
|
||||
func (d *driver) Mkfifo(path string, mode os.FileMode) error {
|
||||
return &os.PathError{Op: "mkfifo", Path: path, Err: ErrNotSupported}
|
||||
}
|
||||
|
||||
// Lchmod changes the mode of an file not following symlinks.
|
||||
func (d *driver) Lchmod(path string, mode os.FileMode) (err error) {
|
||||
// TODO: Use Window's equivalent
|
||||
return os.Chmod(path, mode)
|
||||
}
|
||||
|
||||
// Readlink is forked in order to support Volume paths which are used
|
||||
// in container layers.
|
||||
func (d *driver) Readlink(p string) (string, error) {
|
||||
return sysx.Readlink(p)
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
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 driver
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Lchmod changes the mode of a file not following symlinks.
|
||||
func (d *driver) Lchmod(path string, mode os.FileMode) error {
|
||||
// On Linux, file mode is not supported for symlinks,
|
||||
// and fchmodat() does not support AT_SYMLINK_NOFOLLOW,
|
||||
// so symlinks need to be skipped entirely.
|
||||
if st, err := os.Stat(path); err == nil && st.Mode()&os.ModeSymlink != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := unix.Fchmodat(unix.AT_FDCWD, path, uint32(mode), 0)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "lchmod", Path: path, Err: err}
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
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 driver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// ReadFile works the same as ioutil.ReadFile with the Driver abstraction
|
||||
func ReadFile(r Driver, filename string) ([]byte, error) {
|
||||
f, err := r.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// WriteFile works the same as ioutil.WriteFile with the Driver abstraction
|
||||
func WriteFile(r Driver, filename string, data []byte, perm os.FileMode) error {
|
||||
f, err := r.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
n, err := f.Write(data)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if n != len(data) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadDir works the same as ioutil.ReadDir with the Driver abstraction
|
||||
func ReadDir(r Driver, dirname string) ([]os.FileInfo, error) {
|
||||
f, err := r.Open(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
dirs, err := f.Readdir(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Sort(fileInfos(dirs))
|
||||
return dirs, nil
|
||||
}
|
||||
|
||||
// Simple implementation of the sort.Interface for os.FileInfo
|
||||
type fileInfos []os.FileInfo
|
||||
|
||||
func (fis fileInfos) Len() int {
|
||||
return len(fis)
|
||||
}
|
||||
|
||||
func (fis fileInfos) Less(i, j int) bool {
|
||||
return fis[i].Name() < fis[j].Name()
|
||||
}
|
||||
|
||||
func (fis fileInfos) Swap(i, j int) {
|
||||
fis[i], fis[j] = fis[j], fis[i]
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
/*
|
||||
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 (
|
||||
"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
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
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 (
|
||||
"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
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
// +build linux darwin freebsd solaris
|
||||
|
||||
/*
|
||||
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 (
|
||||
"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
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
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 "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
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// AtomicWriteFile atomically writes data to a file by first writing to a
|
||||
// temp file and calling rename.
|
||||
func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
return atomicWriteFile(filename, buf, int64(len(data)), perm)
|
||||
}
|
||||
|
||||
// atomicWriteFile writes data to a file by first writing to a temp
|
||||
// file and calling rename.
|
||||
func atomicWriteFile(filename string, r io.Reader, dataSize int64, perm os.FileMode) error {
|
||||
f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chmod(f.Name(), perm)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
n, err := io.Copy(f, r)
|
||||
if err == nil && n < dataSize {
|
||||
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)
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
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 (
|
||||
"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 == string(os.PathSeparator) {
|
||||
// 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
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
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 pathdriver
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// PathDriver provides all of the path manipulation functions in a common
|
||||
// interface. The context should call these and never use the `filepath`
|
||||
// package or any other package to manipulate paths.
|
||||
type PathDriver interface {
|
||||
Join(paths ...string) string
|
||||
IsAbs(path string) bool
|
||||
Rel(base, target string) (string, error)
|
||||
Base(path string) string
|
||||
Dir(path string) string
|
||||
Clean(path string) string
|
||||
Split(path string) (dir, file string)
|
||||
Separator() byte
|
||||
Abs(path string) (string, error)
|
||||
Walk(string, filepath.WalkFunc) error
|
||||
FromSlash(path string) string
|
||||
ToSlash(path string) string
|
||||
Match(pattern, name string) (matched bool, err error)
|
||||
}
|
||||
|
||||
// pathDriver is a simple default implementation calls the filepath package.
|
||||
type pathDriver struct{}
|
||||
|
||||
// LocalPathDriver is the exported pathDriver struct for convenience.
|
||||
var LocalPathDriver PathDriver = &pathDriver{}
|
||||
|
||||
func (*pathDriver) Join(paths ...string) string {
|
||||
return filepath.Join(paths...)
|
||||
}
|
||||
|
||||
func (*pathDriver) IsAbs(path string) bool {
|
||||
return filepath.IsAbs(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Rel(base, target string) (string, error) {
|
||||
return filepath.Rel(base, target)
|
||||
}
|
||||
|
||||
func (*pathDriver) Base(path string) string {
|
||||
return filepath.Base(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Dir(path string) string {
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Clean(path string) string {
|
||||
return filepath.Clean(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Split(path string) (dir, file string) {
|
||||
return filepath.Split(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Separator() byte {
|
||||
return filepath.Separator
|
||||
}
|
||||
|
||||
func (*pathDriver) Abs(path string) (string, error) {
|
||||
return filepath.Abs(path)
|
||||
}
|
||||
|
||||
// Note that filepath.Walk calls os.Stat, so if the context wants to
|
||||
// to call Driver.Stat() for Walk, they need to create a new struct that
|
||||
// overrides this method.
|
||||
func (*pathDriver) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||
return filepath.Walk(root, walkFn)
|
||||
}
|
||||
|
||||
func (*pathDriver) FromSlash(path string) string {
|
||||
return filepath.FromSlash(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) ToSlash(path string) string {
|
||||
return filepath.ToSlash(path)
|
||||
}
|
||||
|
||||
func (*pathDriver) Match(pattern, name string) (bool, error) {
|
||||
return filepath.Match(pattern, name)
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
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 proto
|
||||
|
||||
//go:generate protoc --go_out=. manifest.proto
|
|
@ -1,181 +0,0 @@
|
|||
// Code generated by protoc-gen-go.
|
||||
// source: manifest.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package proto is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
manifest.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Manifest
|
||||
Resource
|
||||
XAttr
|
||||
ADSEntry
|
||||
*/
|
||||
package proto
|
||||
|
||||
import proto1 "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto1.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// Manifest specifies the entries in a container bundle, keyed and sorted by
|
||||
// path.
|
||||
type Manifest struct {
|
||||
Resource []*Resource `protobuf:"bytes,1,rep,name=resource" json:"resource,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Manifest) Reset() { *m = Manifest{} }
|
||||
func (m *Manifest) String() string { return proto1.CompactTextString(m) }
|
||||
func (*Manifest) ProtoMessage() {}
|
||||
func (*Manifest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *Manifest) GetResource() []*Resource {
|
||||
if m != nil {
|
||||
return m.Resource
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
// Path specifies the path from the bundle root. If more than one
|
||||
// path is present, the entry may represent a hardlink, rather than using
|
||||
// a link target. The path format is operating system specific.
|
||||
Path []string `protobuf:"bytes,1,rep,name=path" json:"path,omitempty"`
|
||||
// Uid specifies the user id for the resource.
|
||||
Uid int64 `protobuf:"varint,2,opt,name=uid" json:"uid,omitempty"`
|
||||
// Gid specifies the group id for the resource.
|
||||
Gid int64 `protobuf:"varint,3,opt,name=gid" json:"gid,omitempty"`
|
||||
// user and group are not currently used but their field numbers have been
|
||||
// reserved for future use. As such, they are marked as deprecated.
|
||||
User string `protobuf:"bytes,4,opt,name=user" json:"user,omitempty"`
|
||||
Group string `protobuf:"bytes,5,opt,name=group" json:"group,omitempty"`
|
||||
// Mode defines the file mode and permissions. We've used the same
|
||||
// bit-packing from Go's os package,
|
||||
// http://golang.org/pkg/os/#FileMode, since they've done the work of
|
||||
// creating a cross-platform layout.
|
||||
Mode uint32 `protobuf:"varint,6,opt,name=mode" json:"mode,omitempty"`
|
||||
// Size specifies the size in bytes of the resource. This is only valid
|
||||
// for regular files.
|
||||
Size uint64 `protobuf:"varint,7,opt,name=size" json:"size,omitempty"`
|
||||
// Digest specifies the content digest of the target file. Only valid for
|
||||
// regular files. The strings are formatted in OCI style, i.e. <alg>:<encoded>.
|
||||
// For detailed information about the format, please refer to OCI Image Spec:
|
||||
// https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests-and-verification
|
||||
// The digests are sorted in lexical order and implementations may choose
|
||||
// which algorithms they prefer.
|
||||
Digest []string `protobuf:"bytes,8,rep,name=digest" json:"digest,omitempty"`
|
||||
// Target defines the target of a hard or soft link. Absolute links start
|
||||
// with a slash and specify the resource relative to the bundle root.
|
||||
// Relative links do not start with a slash and are relative to the
|
||||
// resource path.
|
||||
Target string `protobuf:"bytes,9,opt,name=target" json:"target,omitempty"`
|
||||
// Major specifies the major device number for character and block devices.
|
||||
Major uint64 `protobuf:"varint,10,opt,name=major" json:"major,omitempty"`
|
||||
// Minor specifies the minor device number for character and block devices.
|
||||
Minor uint64 `protobuf:"varint,11,opt,name=minor" json:"minor,omitempty"`
|
||||
// Xattr provides storage for extended attributes for the target resource.
|
||||
Xattr []*XAttr `protobuf:"bytes,12,rep,name=xattr" json:"xattr,omitempty"`
|
||||
// Ads stores one or more alternate data streams for the target resource.
|
||||
Ads []*ADSEntry `protobuf:"bytes,13,rep,name=ads" json:"ads,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Resource) Reset() { *m = Resource{} }
|
||||
func (m *Resource) String() string { return proto1.CompactTextString(m) }
|
||||
func (*Resource) ProtoMessage() {}
|
||||
func (*Resource) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *Resource) GetXattr() []*XAttr {
|
||||
if m != nil {
|
||||
return m.Xattr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Resource) GetAds() []*ADSEntry {
|
||||
if m != nil {
|
||||
return m.Ads
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// XAttr encodes extended attributes for a resource.
|
||||
type XAttr struct {
|
||||
// Name specifies the attribute name.
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
// Data specifies the associated data for the attribute.
|
||||
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (m *XAttr) Reset() { *m = XAttr{} }
|
||||
func (m *XAttr) String() string { return proto1.CompactTextString(m) }
|
||||
func (*XAttr) ProtoMessage() {}
|
||||
func (*XAttr) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||
|
||||
// ADSEntry encodes information for a Windows Alternate Data Stream.
|
||||
type ADSEntry struct {
|
||||
// Name specifices the stream name.
|
||||
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||
// Data specifies the stream data.
|
||||
// See also the description about the digest below.
|
||||
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
|
||||
// Digest is a CAS representation of the stream data.
|
||||
//
|
||||
// At least one of data or digest MUST be specified, and either one of them
|
||||
// SHOULD be specified.
|
||||
//
|
||||
// How to access the actual data using the digest is implementation-specific,
|
||||
// and implementations can choose not to implement digest.
|
||||
// So, digest SHOULD be used only when the stream data is large.
|
||||
Digest string `protobuf:"bytes,3,opt,name=digest" json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ADSEntry) Reset() { *m = ADSEntry{} }
|
||||
func (m *ADSEntry) String() string { return proto1.CompactTextString(m) }
|
||||
func (*ADSEntry) ProtoMessage() {}
|
||||
func (*ADSEntry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
||||
|
||||
func init() {
|
||||
proto1.RegisterType((*Manifest)(nil), "proto.Manifest")
|
||||
proto1.RegisterType((*Resource)(nil), "proto.Resource")
|
||||
proto1.RegisterType((*XAttr)(nil), "proto.XAttr")
|
||||
proto1.RegisterType((*ADSEntry)(nil), "proto.ADSEntry")
|
||||
}
|
||||
|
||||
func init() { proto1.RegisterFile("manifest.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 317 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x90, 0x4f, 0x4b, 0xf3, 0x40,
|
||||
0x10, 0xc6, 0x49, 0x93, 0xf4, 0x4d, 0xa7, 0xed, 0xab, 0x2c, 0x52, 0xe6, 0x18, 0x73, 0x0a, 0x08,
|
||||
0x15, 0xf4, 0xe0, 0xb9, 0xa2, 0x17, 0xc1, 0xcb, 0x7a, 0xf1, 0xba, 0xba, 0x6b, 0x5c, 0x21, 0xd9,
|
||||
0xb0, 0xd9, 0x80, 0xfa, 0xe5, 0xfc, 0x6a, 0x32, 0xb3, 0x69, 0xd1, 0x9b, 0xa7, 0x3c, 0xcf, 0x6f,
|
||||
0xfe, 0x64, 0xf6, 0x81, 0xff, 0xad, 0xea, 0xec, 0x8b, 0x19, 0xc2, 0xb6, 0xf7, 0x2e, 0x38, 0x91,
|
||||
0xf3, 0xa7, 0xba, 0x82, 0xe2, 0x7e, 0x2a, 0x88, 0x33, 0x28, 0xbc, 0x19, 0xdc, 0xe8, 0x9f, 0x0d,
|
||||
0x26, 0x65, 0x5a, 0x2f, 0x2f, 0x8e, 0x62, 0xf3, 0x56, 0x4e, 0x58, 0x1e, 0x1a, 0xaa, 0xaf, 0x19,
|
||||
0x14, 0x7b, 0x2c, 0x04, 0x64, 0xbd, 0x0a, 0xaf, 0x3c, 0xb5, 0x90, 0xac, 0xc5, 0x31, 0xa4, 0xa3,
|
||||
0xd5, 0x38, 0x2b, 0x93, 0x3a, 0x95, 0x24, 0x89, 0x34, 0x56, 0x63, 0x1a, 0x49, 0x63, 0xb5, 0xd8,
|
||||
0x40, 0x36, 0x0e, 0xc6, 0x63, 0x56, 0x26, 0xf5, 0xe2, 0x7a, 0x86, 0x89, 0x64, 0x2f, 0x10, 0xf2,
|
||||
0xc6, 0xbb, 0xb1, 0xc7, 0xfc, 0x50, 0x88, 0x80, 0xfe, 0xd4, 0x3a, 0x6d, 0x70, 0x5e, 0x26, 0xf5,
|
||||
0x5a, 0xb2, 0x26, 0x36, 0xd8, 0x4f, 0x83, 0xff, 0xca, 0xa4, 0xce, 0x24, 0x6b, 0xb1, 0x81, 0xb9,
|
||||
0xb6, 0x8d, 0x19, 0x02, 0x16, 0x7c, 0xd3, 0xe4, 0x88, 0x07, 0xe5, 0x1b, 0x13, 0x70, 0x41, 0xab,
|
||||
0xe5, 0xe4, 0xc4, 0x09, 0xe4, 0xad, 0x7a, 0x73, 0x1e, 0x81, 0x97, 0x44, 0xc3, 0xd4, 0x76, 0xce,
|
||||
0xe3, 0x72, 0xa2, 0x64, 0x44, 0x05, 0xf9, 0xbb, 0x0a, 0xc1, 0xe3, 0x8a, 0x43, 0x5a, 0x4d, 0x21,
|
||||
0x3d, 0xee, 0x42, 0xf0, 0x32, 0x96, 0xc4, 0x29, 0xa4, 0x4a, 0x0f, 0xb8, 0xfe, 0x15, 0xe3, 0xee,
|
||||
0xe6, 0xe1, 0xb6, 0x0b, 0xfe, 0x43, 0x52, 0xad, 0x3a, 0x87, 0x9c, 0x47, 0xe8, 0xfe, 0x4e, 0xb5,
|
||||
0x94, 0x39, 0x5d, 0xc4, 0x9a, 0x98, 0x56, 0x41, 0x71, 0x7c, 0x2b, 0xc9, 0xba, 0xba, 0x83, 0x62,
|
||||
0xbf, 0xe1, 0xaf, 0x33, 0x3f, 0x72, 0x48, 0xe3, 0x7b, 0xa3, 0x7b, 0x9a, 0xf3, 0x45, 0x97, 0xdf,
|
||||
0x01, 0x00, 0x00, 0xff, 0xff, 0xef, 0x27, 0x99, 0xf7, 0x17, 0x02, 0x00, 0x00,
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package proto;
|
||||
|
||||
// Manifest specifies the entries in a container bundle, keyed and sorted by
|
||||
// path.
|
||||
message Manifest {
|
||||
repeated Resource resource = 1;
|
||||
}
|
||||
|
||||
message Resource {
|
||||
// Path specifies the path from the bundle root. If more than one
|
||||
// path is present, the entry may represent a hardlink, rather than using
|
||||
// a link target. The path format is operating system specific.
|
||||
repeated string path = 1;
|
||||
|
||||
// NOTE(stevvooe): Need to define clear precedence for user/group/uid/gid precedence.
|
||||
|
||||
// Uid specifies the user id for the resource.
|
||||
int64 uid = 2;
|
||||
|
||||
// Gid specifies the group id for the resource.
|
||||
int64 gid = 3;
|
||||
|
||||
// user and group are not currently used but their field numbers have been
|
||||
// reserved for future use. As such, they are marked as deprecated.
|
||||
string user = 4 [deprecated=true]; // "deprecated" stands for "reserved" here
|
||||
string group = 5 [deprecated=true]; // "deprecated" stands for "reserved" here
|
||||
|
||||
// Mode defines the file mode and permissions. We've used the same
|
||||
// bit-packing from Go's os package,
|
||||
// http://golang.org/pkg/os/#FileMode, since they've done the work of
|
||||
// creating a cross-platform layout.
|
||||
uint32 mode = 6;
|
||||
|
||||
// NOTE(stevvooe): Beyond here, we start defining type specific fields.
|
||||
|
||||
// Size specifies the size in bytes of the resource. This is only valid
|
||||
// for regular files.
|
||||
uint64 size = 7;
|
||||
|
||||
// Digest specifies the content digest of the target file. Only valid for
|
||||
// regular files. The strings are formatted in OCI style, i.e. <alg>:<encoded>.
|
||||
// For detailed information about the format, please refer to OCI Image Spec:
|
||||
// https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests-and-verification
|
||||
// The digests are sorted in lexical order and implementations may choose
|
||||
// which algorithms they prefer.
|
||||
repeated string digest = 8;
|
||||
|
||||
// Target defines the target of a hard or soft link. Absolute links start
|
||||
// with a slash and specify the resource relative to the bundle root.
|
||||
// Relative links do not start with a slash and are relative to the
|
||||
// resource path.
|
||||
string target = 9;
|
||||
|
||||
// Major specifies the major device number for character and block devices.
|
||||
uint64 major = 10;
|
||||
|
||||
// Minor specifies the minor device number for character and block devices.
|
||||
uint64 minor = 11;
|
||||
|
||||
// Xattr provides storage for extended attributes for the target resource.
|
||||
repeated XAttr xattr = 12;
|
||||
|
||||
// Ads stores one or more alternate data streams for the target resource.
|
||||
repeated ADSEntry ads = 13;
|
||||
|
||||
}
|
||||
|
||||
// XAttr encodes extended attributes for a resource.
|
||||
message XAttr {
|
||||
// Name specifies the attribute name.
|
||||
string name = 1;
|
||||
|
||||
// Data specifies the associated data for the attribute.
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
// ADSEntry encodes information for a Windows Alternate Data Stream.
|
||||
message ADSEntry {
|
||||
// Name specifices the stream name.
|
||||
string name = 1;
|
||||
|
||||
// Data specifies the stream data.
|
||||
// See also the description about the digest below.
|
||||
bytes data = 2;
|
||||
|
||||
// Digest is a CAS representation of the stream data.
|
||||
//
|
||||
// At least one of data or digest MUST be specified, and either one of them
|
||||
// SHOULD be specified.
|
||||
//
|
||||
// How to access the actual data using the digest is implementation-specific,
|
||||
// and implementations can choose not to implement digest.
|
||||
// So, digest SHOULD be used only when the stream data is large.
|
||||
string digest = 3;
|
||||
}
|
|
@ -1,590 +0,0 @@
|
|||
/*
|
||||
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
|
||||
// }
|
|
@ -1,53 +0,0 @@
|
|||
// +build linux darwin freebsd solaris
|
||||
|
||||
/*
|
||||
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 (
|
||||
"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
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
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 "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
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
|
@ -0,0 +1,62 @@
|
|||
# ttrpc
|
||||
|
||||
[![Build Status](https://travis-ci.org/containerd/ttrpc.svg?branch=master)](https://travis-ci.org/containerd/ttrpc)
|
||||
|
||||
GRPC for low-memory environments.
|
||||
|
||||
The existing grpc-go project requires a lot of memory overhead for importing
|
||||
packages and at runtime. While this is great for many services with low density
|
||||
requirements, this can be a problem when running a large number of services on
|
||||
a single machine or on a machine with a small amount of memory.
|
||||
|
||||
Using the same GRPC definitions, this project reduces the binary size and
|
||||
protocol overhead required. We do this by eliding the `net/http`, `net/http2`
|
||||
and `grpc` package used by grpc replacing it with a lightweight framing
|
||||
protocol. The result are smaller binaries that use less resident memory with
|
||||
the same ease of use as GRPC.
|
||||
|
||||
Please note that while this project supports generating either end of the
|
||||
protocol, the generated service definitions will be incompatible with regular
|
||||
GRPC services, as they do not speak the same protocol.
|
||||
|
||||
# Usage
|
||||
|
||||
Create a gogo vanity binary (see
|
||||
[`cmd/protoc-gen-gogottrpc/main.go`](cmd/protoc-gen-gogottrpc/main.go) for an
|
||||
example with the ttrpc plugin enabled.
|
||||
|
||||
It's recommended to use [`protobuild`](https://github.com//stevvooe/protobuild)
|
||||
to build the protobufs for this project, but this will work with protoc
|
||||
directly, if required.
|
||||
|
||||
# Differences from GRPC
|
||||
|
||||
- The protocol stack has been replaced with a lighter protocol that doesn't
|
||||
require http, http2 and tls.
|
||||
- The client and server interface are identical whereas in GRPC there is a
|
||||
client and server interface that are different.
|
||||
- The Go stdlib context package is used instead.
|
||||
- No support for streams yet.
|
||||
|
||||
# Status
|
||||
|
||||
Very new. YMMV.
|
||||
|
||||
TODO:
|
||||
|
||||
- [X] Plumb error codes and GRPC status
|
||||
- [X] Remove use of any type and dependency on typeurl package
|
||||
- [X] Ensure that protocol can support streaming in the future
|
||||
- [ ] Document protocol layout
|
||||
- [ ] Add testing under concurrent load to ensure
|
||||
- [ ] Verify connection error handling
|
||||
|
||||
# Project details
|
||||
|
||||
ttrpc is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
|
||||
As a containerd sub-project, you will find the:
|
||||
* [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
|
||||
* [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
|
||||
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
|
||||
|
||||
information in our [`containerd/project`](https://github.com/containerd/project) repository.
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
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 ttrpc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const (
|
||||
messageHeaderLength = 10
|
||||
messageLengthMax = 4 << 20
|
||||
)
|
||||
|
||||
type messageType uint8
|
||||
|
||||
const (
|
||||
messageTypeRequest messageType = 0x1
|
||||
messageTypeResponse messageType = 0x2
|
||||
)
|
||||
|
||||
// messageHeader represents the fixed-length message header of 10 bytes sent
|
||||
// with every request.
|
||||
type messageHeader struct {
|
||||
Length uint32 // length excluding this header. b[:4]
|
||||
StreamID uint32 // identifies which request stream message is a part of. b[4:8]
|
||||
Type messageType // message type b[8]
|
||||
Flags uint8 // reserved b[9]
|
||||
}
|
||||
|
||||
func readMessageHeader(p []byte, r io.Reader) (messageHeader, error) {
|
||||
_, err := io.ReadFull(r, p[:messageHeaderLength])
|
||||
if err != nil {
|
||||
return messageHeader{}, err
|
||||
}
|
||||
|
||||
return messageHeader{
|
||||
Length: binary.BigEndian.Uint32(p[:4]),
|
||||
StreamID: binary.BigEndian.Uint32(p[4:8]),
|
||||
Type: messageType(p[8]),
|
||||
Flags: p[9],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func writeMessageHeader(w io.Writer, p []byte, mh messageHeader) error {
|
||||
binary.BigEndian.PutUint32(p[:4], mh.Length)
|
||||
binary.BigEndian.PutUint32(p[4:8], mh.StreamID)
|
||||
p[8] = byte(mh.Type)
|
||||
p[9] = mh.Flags
|
||||
|
||||
_, err := w.Write(p[:])
|
||||
return err
|
||||
}
|
||||
|
||||
var buffers sync.Pool
|
||||
|
||||
type channel struct {
|
||||
conn net.Conn
|
||||
bw *bufio.Writer
|
||||
br *bufio.Reader
|
||||
hrbuf [messageHeaderLength]byte // avoid alloc when reading header
|
||||
hwbuf [messageHeaderLength]byte
|
||||
}
|
||||
|
||||
func newChannel(conn net.Conn) *channel {
|
||||
return &channel{
|
||||
conn: conn,
|
||||
bw: bufio.NewWriter(conn),
|
||||
br: bufio.NewReader(conn),
|
||||
}
|
||||
}
|
||||
|
||||
// recv a message from the channel. The returned buffer contains the message.
|
||||
//
|
||||
// If a valid grpc status is returned, the message header
|
||||
// returned will be valid and caller should send that along to
|
||||
// the correct consumer. The bytes on the underlying channel
|
||||
// will be discarded.
|
||||
func (ch *channel) recv() (messageHeader, []byte, error) {
|
||||
mh, err := readMessageHeader(ch.hrbuf[:], ch.br)
|
||||
if err != nil {
|
||||
return messageHeader{}, nil, err
|
||||
}
|
||||
|
||||
if mh.Length > uint32(messageLengthMax) {
|
||||
if _, err := ch.br.Discard(int(mh.Length)); err != nil {
|
||||
return mh, nil, errors.Wrapf(err, "failed to discard after receiving oversized message")
|
||||
}
|
||||
|
||||
return mh, nil, status.Errorf(codes.ResourceExhausted, "message length %v exceed maximum message size of %v", mh.Length, messageLengthMax)
|
||||
}
|
||||
|
||||
p := ch.getmbuf(int(mh.Length))
|
||||
if _, err := io.ReadFull(ch.br, p); err != nil {
|
||||
return messageHeader{}, nil, errors.Wrapf(err, "failed reading message")
|
||||
}
|
||||
|
||||
return mh, p, nil
|
||||
}
|
||||
|
||||
func (ch *channel) send(streamID uint32, t messageType, p []byte) error {
|
||||
if err := writeMessageHeader(ch.bw, ch.hwbuf[:], messageHeader{Length: uint32(len(p)), StreamID: streamID, Type: t}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := ch.bw.Write(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ch.bw.Flush()
|
||||
}
|
||||
|
||||
func (ch *channel) getmbuf(size int) []byte {
|
||||
// we can't use the standard New method on pool because we want to allocate
|
||||
// based on size.
|
||||
b, ok := buffers.Get().(*[]byte)
|
||||
if !ok || cap(*b) < size {
|
||||
// TODO(stevvooe): It may be better to allocate these in fixed length
|
||||
// buckets to reduce fragmentation but its not clear that would help
|
||||
// with performance. An ilogb approach or similar would work well.
|
||||
bb := make([]byte, size)
|
||||
b = &bb
|
||||
} else {
|
||||
*b = (*b)[:size]
|
||||
}
|
||||
return *b
|
||||
}
|
||||
|
||||
func (ch *channel) putmbuf(p []byte) {
|
||||
buffers.Put(&p)
|
||||
}
|
|
@ -0,0 +1,350 @@
|
|||
/*
|
||||
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 ttrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// ErrClosed is returned by client methods when the underlying connection is
|
||||
// closed.
|
||||
var ErrClosed = errors.New("ttrpc: closed")
|
||||
|
||||
// Client for a ttrpc server
|
||||
type Client struct {
|
||||
codec codec
|
||||
conn net.Conn
|
||||
channel *channel
|
||||
calls chan *callRequest
|
||||
|
||||
ctx context.Context
|
||||
closed func()
|
||||
|
||||
closeOnce sync.Once
|
||||
userCloseFunc func()
|
||||
|
||||
errOnce sync.Once
|
||||
err error
|
||||
interceptor UnaryClientInterceptor
|
||||
}
|
||||
|
||||
// ClientOpts configures a client
|
||||
type ClientOpts func(c *Client)
|
||||
|
||||
// WithOnClose sets the close func whenever the client's Close() method is called
|
||||
func WithOnClose(onClose func()) ClientOpts {
|
||||
return func(c *Client) {
|
||||
c.userCloseFunc = onClose
|
||||
}
|
||||
}
|
||||
|
||||
// WithUnaryClientInterceptor sets the provided client interceptor
|
||||
func WithUnaryClientInterceptor(i UnaryClientInterceptor) ClientOpts {
|
||||
return func(c *Client) {
|
||||
c.interceptor = i
|
||||
}
|
||||
}
|
||||
|
||||
func NewClient(conn net.Conn, opts ...ClientOpts) *Client {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
c := &Client{
|
||||
codec: codec{},
|
||||
conn: conn,
|
||||
channel: newChannel(conn),
|
||||
calls: make(chan *callRequest),
|
||||
closed: cancel,
|
||||
ctx: ctx,
|
||||
userCloseFunc: func() {},
|
||||
interceptor: defaultClientInterceptor,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(c)
|
||||
}
|
||||
|
||||
go c.run()
|
||||
return c
|
||||
}
|
||||
|
||||
type callRequest struct {
|
||||
ctx context.Context
|
||||
req *Request
|
||||
resp *Response // response will be written back here
|
||||
errs chan error // error written here on completion
|
||||
}
|
||||
|
||||
func (c *Client) Call(ctx context.Context, service, method string, req, resp interface{}) error {
|
||||
payload, err := c.codec.Marshal(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
creq = &Request{
|
||||
Service: service,
|
||||
Method: method,
|
||||
Payload: payload,
|
||||
}
|
||||
|
||||
cresp = &Response{}
|
||||
)
|
||||
|
||||
if metadata, ok := GetMetadata(ctx); ok {
|
||||
metadata.setRequest(creq)
|
||||
}
|
||||
|
||||
if dl, ok := ctx.Deadline(); ok {
|
||||
creq.TimeoutNano = dl.Sub(time.Now()).Nanoseconds()
|
||||
}
|
||||
|
||||
info := &UnaryClientInfo{
|
||||
FullMethod: fullPath(service, method),
|
||||
}
|
||||
if err := c.interceptor(ctx, creq, cresp, info, c.dispatch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.codec.Unmarshal(cresp.Payload, resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cresp.Status != nil && cresp.Status.Code != int32(codes.OK) {
|
||||
return status.ErrorProto(cresp.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) dispatch(ctx context.Context, req *Request, resp *Response) error {
|
||||
errs := make(chan error, 1)
|
||||
call := &callRequest{
|
||||
ctx: ctx,
|
||||
req: req,
|
||||
resp: resp,
|
||||
errs: errs,
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case c.calls <- call:
|
||||
case <-c.ctx.Done():
|
||||
return c.error()
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case err := <-errs:
|
||||
return filterCloseErr(err)
|
||||
case <-c.ctx.Done():
|
||||
return c.error()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.closeOnce.Do(func() {
|
||||
c.closed()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
type message struct {
|
||||
messageHeader
|
||||
p []byte
|
||||
err error
|
||||
}
|
||||
|
||||
type receiver struct {
|
||||
wg *sync.WaitGroup
|
||||
messages chan *message
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *receiver) run(ctx context.Context, c *channel) {
|
||||
defer r.wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
r.err = ctx.Err()
|
||||
return
|
||||
default:
|
||||
mh, p, err := c.recv()
|
||||
if err != nil {
|
||||
_, ok := status.FromError(err)
|
||||
if !ok {
|
||||
// treat all errors that are not an rpc status as terminal.
|
||||
// all others poison the connection.
|
||||
r.err = filterCloseErr(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
select {
|
||||
case r.messages <- &message{
|
||||
messageHeader: mh,
|
||||
p: p[:mh.Length],
|
||||
err: err,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
r.err = ctx.Err()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) run() {
|
||||
var (
|
||||
streamID uint32 = 1
|
||||
waiters = make(map[uint32]*callRequest)
|
||||
calls = c.calls
|
||||
incoming = make(chan *message)
|
||||
receiversDone = make(chan struct{})
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
// broadcast the shutdown error to the remaining waiters.
|
||||
abortWaiters := func(wErr error) {
|
||||
for _, waiter := range waiters {
|
||||
waiter.errs <- wErr
|
||||
}
|
||||
}
|
||||
recv := &receiver{
|
||||
wg: &wg,
|
||||
messages: incoming,
|
||||
}
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(receiversDone)
|
||||
}()
|
||||
go recv.run(c.ctx, c.channel)
|
||||
|
||||
defer func() {
|
||||
c.conn.Close()
|
||||
c.userCloseFunc()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case call := <-calls:
|
||||
if err := c.send(streamID, messageTypeRequest, call.req); err != nil {
|
||||
call.errs <- err
|
||||
continue
|
||||
}
|
||||
|
||||
waiters[streamID] = call
|
||||
streamID += 2 // enforce odd client initiated request ids
|
||||
case msg := <-incoming:
|
||||
call, ok := waiters[msg.StreamID]
|
||||
if !ok {
|
||||
logrus.Errorf("ttrpc: received message for unknown channel %v", msg.StreamID)
|
||||
continue
|
||||
}
|
||||
|
||||
call.errs <- c.recv(call.resp, msg)
|
||||
delete(waiters, msg.StreamID)
|
||||
case <-receiversDone:
|
||||
// all the receivers have exited
|
||||
if recv.err != nil {
|
||||
c.setError(recv.err)
|
||||
}
|
||||
// don't return out, let the close of the context trigger the abort of waiters
|
||||
c.Close()
|
||||
case <-c.ctx.Done():
|
||||
abortWaiters(c.error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) error() error {
|
||||
c.errOnce.Do(func() {
|
||||
if c.err == nil {
|
||||
c.err = ErrClosed
|
||||
}
|
||||
})
|
||||
return c.err
|
||||
}
|
||||
|
||||
func (c *Client) setError(err error) {
|
||||
c.errOnce.Do(func() {
|
||||
c.err = err
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) send(streamID uint32, mtype messageType, msg interface{}) error {
|
||||
p, err := c.codec.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.channel.send(streamID, mtype, p)
|
||||
}
|
||||
|
||||
func (c *Client) recv(resp *Response, msg *message) error {
|
||||
if msg.err != nil {
|
||||
return msg.err
|
||||
}
|
||||
|
||||
if msg.Type != messageTypeResponse {
|
||||
return errors.New("unknown message type received")
|
||||
}
|
||||
|
||||
defer c.channel.putmbuf(msg.p)
|
||||
return proto.Unmarshal(msg.p, resp)
|
||||
}
|
||||
|
||||
// filterCloseErr rewrites EOF and EPIPE errors to ErrClosed. Use when
|
||||
// returning from call or handling errors from main read loop.
|
||||
//
|
||||
// This purposely ignores errors with a wrapped cause.
|
||||
func filterCloseErr(err error) error {
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil
|
||||
case err == io.EOF:
|
||||
return ErrClosed
|
||||
case errors.Cause(err) == io.EOF:
|
||||
return ErrClosed
|
||||
case strings.Contains(err.Error(), "use of closed network connection"):
|
||||
return ErrClosed
|
||||
default:
|
||||
// if we have an epipe on a write, we cast to errclosed
|
||||
if oerr, ok := err.(*net.OpError); ok && oerr.Op == "write" {
|
||||
if serr, ok := oerr.Err.(*os.SyscallError); ok && serr.Err == syscall.EPIPE {
|
||||
return ErrClosed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
// +build darwin freebsd solaris
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
|
@ -16,19 +14,29 @@
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package driver
|
||||
package ttrpc
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Lchmod changes the mode of a file not following symlinks.
|
||||
func (d *driver) Lchmod(path string, mode os.FileMode) error {
|
||||
err := unix.Fchmodat(unix.AT_FDCWD, path, uint32(mode), unix.AT_SYMLINK_NOFOLLOW)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "lchmod", Path: path, Err: err}
|
||||
type codec struct{}
|
||||
|
||||
func (c codec) Marshal(msg interface{}) ([]byte, error) {
|
||||
switch v := msg.(type) {
|
||||
case proto.Message:
|
||||
return proto.Marshal(v)
|
||||
default:
|
||||
return nil, errors.Errorf("ttrpc: cannot marshal unknown type: %T", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (c codec) Unmarshal(p []byte, msg interface{}) error {
|
||||
switch v := msg.(type) {
|
||||
case proto.Message:
|
||||
return proto.Unmarshal(p, v)
|
||||
default:
|
||||
return errors.Errorf("ttrpc: cannot unmarshal into unknown type: %T", msg)
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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 ttrpc
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
type serverConfig struct {
|
||||
handshaker Handshaker
|
||||
interceptor UnaryServerInterceptor
|
||||
}
|
||||
|
||||
// ServerOpt for configuring a ttrpc server
|
||||
type ServerOpt func(*serverConfig) error
|
||||
|
||||
// WithServerHandshaker can be passed to NewServer to ensure that the
|
||||
// handshaker is called before every connection attempt.
|
||||
//
|
||||
// Only one handshaker is allowed per server.
|
||||
func WithServerHandshaker(handshaker Handshaker) ServerOpt {
|
||||
return func(c *serverConfig) error {
|
||||
if c.handshaker != nil {
|
||||
return errors.New("only one handshaker allowed per server")
|
||||
}
|
||||
c.handshaker = handshaker
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUnaryServerInterceptor sets the provided interceptor on the server
|
||||
func WithUnaryServerInterceptor(i UnaryServerInterceptor) ServerOpt {
|
||||
return func(c *serverConfig) error {
|
||||
if c.interceptor != nil {
|
||||
return errors.New("only one interceptor allowed per server")
|
||||
}
|
||||
c.interceptor = i
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
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 ttrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
// Handshaker defines the interface for connection handshakes performed on the
|
||||
// server or client when first connecting.
|
||||
type Handshaker interface {
|
||||
// Handshake should confirm or decorate a connection that may be incoming
|
||||
// to a server or outgoing from a client.
|
||||
//
|
||||
// If this returns without an error, the caller should use the connection
|
||||
// in place of the original connection.
|
||||
//
|
||||
// The second return value can contain credential specific data, such as
|
||||
// unix socket credentials or TLS information.
|
||||
//
|
||||
// While we currently only have implementations on the server-side, this
|
||||
// interface should be sufficient to implement similar handshakes on the
|
||||
// client-side.
|
||||
Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error)
|
||||
}
|
||||
|
||||
type handshakerFunc func(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error)
|
||||
|
||||
func (fn handshakerFunc) Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) {
|
||||
return fn(ctx, conn)
|
||||
}
|
||||
|
||||
func noopHandshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) {
|
||||
return conn, nil, nil
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
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 ttrpc
|
||||
|
||||
import "context"
|
||||
|
||||
// UnaryServerInfo provides information about the server request
|
||||
type UnaryServerInfo struct {
|
||||
FullMethod string
|
||||
}
|
||||
|
||||
// UnaryClientInfo provides information about the client request
|
||||
type UnaryClientInfo struct {
|
||||
FullMethod string
|
||||
}
|
||||
|
||||
// Unmarshaler contains the server request data and allows it to be unmarshaled
|
||||
// into a concrete type
|
||||
type Unmarshaler func(interface{}) error
|
||||
|
||||
// Invoker invokes the client's request and response from the ttrpc server
|
||||
type Invoker func(context.Context, *Request, *Response) error
|
||||
|
||||
// UnaryServerInterceptor specifies the interceptor function for server request/response
|
||||
type UnaryServerInterceptor func(context.Context, Unmarshaler, *UnaryServerInfo, Method) (interface{}, error)
|
||||
|
||||
// UnaryClientInterceptor specifies the interceptor function for client request/response
|
||||
type UnaryClientInterceptor func(context.Context, *Request, *Response, *UnaryClientInfo, Invoker) error
|
||||
|
||||
func defaultServerInterceptor(ctx context.Context, unmarshal Unmarshaler, info *UnaryServerInfo, method Method) (interface{}, error) {
|
||||
return method(ctx, unmarshal)
|
||||
}
|
||||
|
||||
func defaultClientInterceptor(ctx context.Context, req *Request, resp *Response, _ *UnaryClientInfo, invoker Invoker) error {
|
||||
return invoker(ctx, req, resp)
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
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 ttrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MD is the user type for ttrpc metadata
|
||||
type MD map[string][]string
|
||||
|
||||
// Get returns the metadata for a given key when they exist.
|
||||
// If there is no metadata, a nil slice and false are returned.
|
||||
func (m MD) Get(key string) ([]string, bool) {
|
||||
key = strings.ToLower(key)
|
||||
list, ok := m[key]
|
||||
if !ok || len(list) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return list, true
|
||||
}
|
||||
|
||||
// Set sets the provided values for a given key.
|
||||
// The values will overwrite any existing values.
|
||||
// If no values provided, a key will be deleted.
|
||||
func (m MD) Set(key string, values ...string) {
|
||||
key = strings.ToLower(key)
|
||||
if len(values) == 0 {
|
||||
delete(m, key)
|
||||
return
|
||||
}
|
||||
m[key] = values
|
||||
}
|
||||
|
||||
// Append appends additional values to the given key.
|
||||
func (m MD) Append(key string, values ...string) {
|
||||
key = strings.ToLower(key)
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
current, ok := m[key]
|
||||
if ok {
|
||||
m.Set(key, append(current, values...)...)
|
||||
} else {
|
||||
m.Set(key, values...)
|
||||
}
|
||||
}
|
||||
|
||||
func (m MD) setRequest(r *Request) {
|
||||
for k, values := range m {
|
||||
for _, v := range values {
|
||||
r.Metadata = append(r.Metadata, &KeyValue{
|
||||
Key: k,
|
||||
Value: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m MD) fromRequest(r *Request) {
|
||||
for _, kv := range r.Metadata {
|
||||
m[kv.Key] = append(m[kv.Key], kv.Value)
|
||||
}
|
||||
}
|
||||
|
||||
type metadataKey struct{}
|
||||
|
||||
// GetMetadata retrieves metadata from context.Context (previously attached with WithMetadata)
|
||||
func GetMetadata(ctx context.Context) (MD, bool) {
|
||||
metadata, ok := ctx.Value(metadataKey{}).(MD)
|
||||
return metadata, ok
|
||||
}
|
||||
|
||||
// GetMetadataValue gets a specific metadata value by name from context.Context
|
||||
func GetMetadataValue(ctx context.Context, name string) (string, bool) {
|
||||
metadata, ok := GetMetadata(ctx)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
if list, ok := metadata.Get(name); ok {
|
||||
return list[0], true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// WithMetadata attaches metadata map to a context.Context
|
||||
func WithMetadata(ctx context.Context, md MD) context.Context {
|
||||
return context.WithValue(ctx, metadataKey{}, md)
|
||||
}
|
|
@ -0,0 +1,485 @@
|
|||
/*
|
||||
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 ttrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrServerClosed = errors.New("ttrpc: server closed")
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
config *serverConfig
|
||||
services *serviceSet
|
||||
codec codec
|
||||
|
||||
mu sync.Mutex
|
||||
listeners map[net.Listener]struct{}
|
||||
connections map[*serverConn]struct{} // all connections to current state
|
||||
done chan struct{} // marks point at which we stop serving requests
|
||||
}
|
||||
|
||||
func NewServer(opts ...ServerOpt) (*Server, error) {
|
||||
config := &serverConfig{}
|
||||
for _, opt := range opts {
|
||||
if err := opt(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if config.interceptor == nil {
|
||||
config.interceptor = defaultServerInterceptor
|
||||
}
|
||||
|
||||
return &Server{
|
||||
config: config,
|
||||
services: newServiceSet(config.interceptor),
|
||||
done: make(chan struct{}),
|
||||
listeners: make(map[net.Listener]struct{}),
|
||||
connections: make(map[*serverConn]struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) Register(name string, methods map[string]Method) {
|
||||
s.services.register(name, methods)
|
||||
}
|
||||
|
||||
func (s *Server) Serve(ctx context.Context, l net.Listener) error {
|
||||
s.addListener(l)
|
||||
defer s.closeListener(l)
|
||||
|
||||
var (
|
||||
backoff time.Duration
|
||||
handshaker = s.config.handshaker
|
||||
)
|
||||
|
||||
if handshaker == nil {
|
||||
handshaker = handshakerFunc(noopHandshake)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
select {
|
||||
case <-s.done:
|
||||
return ErrServerClosed
|
||||
default:
|
||||
}
|
||||
|
||||
if terr, ok := err.(interface {
|
||||
Temporary() bool
|
||||
}); ok && terr.Temporary() {
|
||||
if backoff == 0 {
|
||||
backoff = time.Millisecond
|
||||
} else {
|
||||
backoff *= 2
|
||||
}
|
||||
|
||||
if max := time.Second; backoff > max {
|
||||
backoff = max
|
||||
}
|
||||
|
||||
sleep := time.Duration(rand.Int63n(int64(backoff)))
|
||||
logrus.WithError(err).Errorf("ttrpc: failed accept; backoff %v", sleep)
|
||||
time.Sleep(sleep)
|
||||
continue
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
backoff = 0
|
||||
|
||||
approved, handshake, err := handshaker.Handshake(ctx, conn)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Errorf("ttrpc: refusing connection after handshake")
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
sc := s.newConn(approved, handshake)
|
||||
go sc.run(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
s.mu.Lock()
|
||||
select {
|
||||
case <-s.done:
|
||||
default:
|
||||
// protected by mutex
|
||||
close(s.done)
|
||||
}
|
||||
lnerr := s.closeListeners()
|
||||
s.mu.Unlock()
|
||||
|
||||
ticker := time.NewTicker(200 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
if s.closeIdleConns() {
|
||||
return lnerr
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close the server without waiting for active connections.
|
||||
func (s *Server) Close() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-s.done:
|
||||
default:
|
||||
// protected by mutex
|
||||
close(s.done)
|
||||
}
|
||||
|
||||
err := s.closeListeners()
|
||||
for c := range s.connections {
|
||||
c.close()
|
||||
delete(s.connections, c)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) addListener(l net.Listener) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.listeners[l] = struct{}{}
|
||||
}
|
||||
|
||||
func (s *Server) closeListener(l net.Listener) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
return s.closeListenerLocked(l)
|
||||
}
|
||||
|
||||
func (s *Server) closeListenerLocked(l net.Listener) error {
|
||||
defer delete(s.listeners, l)
|
||||
return l.Close()
|
||||
}
|
||||
|
||||
func (s *Server) closeListeners() error {
|
||||
var err error
|
||||
for l := range s.listeners {
|
||||
if cerr := s.closeListenerLocked(l); cerr != nil && err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) addConnection(c *serverConn) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.connections[c] = struct{}{}
|
||||
}
|
||||
|
||||
func (s *Server) closeIdleConns() bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
quiescent := true
|
||||
for c := range s.connections {
|
||||
st, ok := c.getState()
|
||||
if !ok || st != connStateIdle {
|
||||
quiescent = false
|
||||
continue
|
||||
}
|
||||
c.close()
|
||||
delete(s.connections, c)
|
||||
}
|
||||
return quiescent
|
||||
}
|
||||
|
||||
type connState int
|
||||
|
||||
const (
|
||||
connStateActive = iota + 1 // outstanding requests
|
||||
connStateIdle // no requests
|
||||
connStateClosed // closed connection
|
||||
)
|
||||
|
||||
func (cs connState) String() string {
|
||||
switch cs {
|
||||
case connStateActive:
|
||||
return "active"
|
||||
case connStateIdle:
|
||||
return "idle"
|
||||
case connStateClosed:
|
||||
return "closed"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) newConn(conn net.Conn, handshake interface{}) *serverConn {
|
||||
c := &serverConn{
|
||||
server: s,
|
||||
conn: conn,
|
||||
handshake: handshake,
|
||||
shutdown: make(chan struct{}),
|
||||
}
|
||||
c.setState(connStateIdle)
|
||||
s.addConnection(c)
|
||||
return c
|
||||
}
|
||||
|
||||
type serverConn struct {
|
||||
server *Server
|
||||
conn net.Conn
|
||||
handshake interface{} // data from handshake, not used for now
|
||||
state atomic.Value
|
||||
|
||||
shutdownOnce sync.Once
|
||||
shutdown chan struct{} // forced shutdown, used by close
|
||||
}
|
||||
|
||||
func (c *serverConn) getState() (connState, bool) {
|
||||
cs, ok := c.state.Load().(connState)
|
||||
return cs, ok
|
||||
}
|
||||
|
||||
func (c *serverConn) setState(newstate connState) {
|
||||
c.state.Store(newstate)
|
||||
}
|
||||
|
||||
func (c *serverConn) close() error {
|
||||
c.shutdownOnce.Do(func() {
|
||||
close(c.shutdown)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *serverConn) run(sctx context.Context) {
|
||||
type (
|
||||
request struct {
|
||||
id uint32
|
||||
req *Request
|
||||
}
|
||||
|
||||
response struct {
|
||||
id uint32
|
||||
resp *Response
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
ch = newChannel(c.conn)
|
||||
ctx, cancel = context.WithCancel(sctx)
|
||||
active int
|
||||
state connState = connStateIdle
|
||||
responses = make(chan response)
|
||||
requests = make(chan request)
|
||||
recvErr = make(chan error, 1)
|
||||
shutdown = c.shutdown
|
||||
done = make(chan struct{})
|
||||
)
|
||||
|
||||
defer c.conn.Close()
|
||||
defer cancel()
|
||||
defer close(done)
|
||||
|
||||
go func(recvErr chan error) {
|
||||
defer close(recvErr)
|
||||
sendImmediate := func(id uint32, st *status.Status) bool {
|
||||
select {
|
||||
case responses <- response{
|
||||
// even though we've had an invalid stream id, we send it
|
||||
// back on the same stream id so the client knows which
|
||||
// stream id was bad.
|
||||
id: id,
|
||||
resp: &Response{
|
||||
Status: st.Proto(),
|
||||
},
|
||||
}:
|
||||
return true
|
||||
case <-c.shutdown:
|
||||
return false
|
||||
case <-done:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.shutdown:
|
||||
return
|
||||
case <-done:
|
||||
return
|
||||
default: // proceed
|
||||
}
|
||||
|
||||
mh, p, err := ch.recv()
|
||||
if err != nil {
|
||||
status, ok := status.FromError(err)
|
||||
if !ok {
|
||||
recvErr <- err
|
||||
return
|
||||
}
|
||||
|
||||
// in this case, we send an error for that particular message
|
||||
// when the status is defined.
|
||||
if !sendImmediate(mh.StreamID, status) {
|
||||
return
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if mh.Type != messageTypeRequest {
|
||||
// we must ignore this for future compat.
|
||||
continue
|
||||
}
|
||||
|
||||
var req Request
|
||||
if err := c.server.codec.Unmarshal(p, &req); err != nil {
|
||||
ch.putmbuf(p)
|
||||
if !sendImmediate(mh.StreamID, status.Newf(codes.InvalidArgument, "unmarshal request error: %v", err)) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
ch.putmbuf(p)
|
||||
|
||||
if mh.StreamID%2 != 1 {
|
||||
// enforce odd client initiated identifiers.
|
||||
if !sendImmediate(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID must be odd for client initiated streams")) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Forward the request to the main loop. We don't wait on s.done
|
||||
// because we have already accepted the client request.
|
||||
select {
|
||||
case requests <- request{
|
||||
id: mh.StreamID,
|
||||
req: &req,
|
||||
}:
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}(recvErr)
|
||||
|
||||
for {
|
||||
newstate := state
|
||||
switch {
|
||||
case active > 0:
|
||||
newstate = connStateActive
|
||||
shutdown = nil
|
||||
case active == 0:
|
||||
newstate = connStateIdle
|
||||
shutdown = c.shutdown // only enable this branch in idle mode
|
||||
}
|
||||
|
||||
if newstate != state {
|
||||
c.setState(newstate)
|
||||
state = newstate
|
||||
}
|
||||
|
||||
select {
|
||||
case request := <-requests:
|
||||
active++
|
||||
go func(id uint32) {
|
||||
ctx, cancel := getRequestContext(ctx, request.req)
|
||||
defer cancel()
|
||||
|
||||
p, status := c.server.services.call(ctx, request.req.Service, request.req.Method, request.req.Payload)
|
||||
resp := &Response{
|
||||
Status: status.Proto(),
|
||||
Payload: p,
|
||||
}
|
||||
|
||||
select {
|
||||
case responses <- response{
|
||||
id: id,
|
||||
resp: resp,
|
||||
}:
|
||||
case <-done:
|
||||
}
|
||||
}(request.id)
|
||||
case response := <-responses:
|
||||
p, err := c.server.codec.Marshal(response.resp)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("failed marshaling response")
|
||||
return
|
||||
}
|
||||
|
||||
if err := ch.send(response.id, messageTypeResponse, p); err != nil {
|
||||
logrus.WithError(err).Error("failed sending message on channel")
|
||||
return
|
||||
}
|
||||
|
||||
active--
|
||||
case err := <-recvErr:
|
||||
// TODO(stevvooe): Not wildly clear what we should do in this
|
||||
// branch. Basically, it means that we are no longer receiving
|
||||
// requests due to a terminal error.
|
||||
recvErr = nil // connection is now "closing"
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
// The client went away and we should stop processing
|
||||
// requests, so that the client connection is closed
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("error receiving message")
|
||||
}
|
||||
case <-shutdown:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var noopFunc = func() {}
|
||||
|
||||
func getRequestContext(ctx context.Context, req *Request) (retCtx context.Context, cancel func()) {
|
||||
if len(req.Metadata) > 0 {
|
||||
md := MD{}
|
||||
md.fromRequest(req)
|
||||
ctx = WithMetadata(ctx, md)
|
||||
}
|
||||
|
||||
cancel = noopFunc
|
||||
if req.TimeoutNano == 0 {
|
||||
return ctx, cancel
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(req.TimeoutNano))
|
||||
return ctx, cancel
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
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 ttrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type Method func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error)
|
||||
|
||||
type ServiceDesc struct {
|
||||
Methods map[string]Method
|
||||
|
||||
// TODO(stevvooe): Add stream support.
|
||||
}
|
||||
|
||||
type serviceSet struct {
|
||||
services map[string]ServiceDesc
|
||||
interceptor UnaryServerInterceptor
|
||||
}
|
||||
|
||||
func newServiceSet(interceptor UnaryServerInterceptor) *serviceSet {
|
||||
return &serviceSet{
|
||||
services: make(map[string]ServiceDesc),
|
||||
interceptor: interceptor,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceSet) register(name string, methods map[string]Method) {
|
||||
if _, ok := s.services[name]; ok {
|
||||
panic(errors.Errorf("duplicate service %v registered", name))
|
||||
}
|
||||
|
||||
s.services[name] = ServiceDesc{
|
||||
Methods: methods,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceSet) call(ctx context.Context, serviceName, methodName string, p []byte) ([]byte, *status.Status) {
|
||||
p, err := s.dispatch(ctx, serviceName, methodName, p)
|
||||
st, ok := status.FromError(err)
|
||||
if !ok {
|
||||
st = status.New(convertCode(err), err.Error())
|
||||
}
|
||||
|
||||
return p, st
|
||||
}
|
||||
|
||||
func (s *serviceSet) dispatch(ctx context.Context, serviceName, methodName string, p []byte) ([]byte, error) {
|
||||
method, err := s.resolve(serviceName, methodName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unmarshal := func(obj interface{}) error {
|
||||
switch v := obj.(type) {
|
||||
case proto.Message:
|
||||
if err := proto.Unmarshal(p, v); err != nil {
|
||||
return status.Errorf(codes.Internal, "ttrpc: error unmarshalling payload: %v", err.Error())
|
||||
}
|
||||
default:
|
||||
return status.Errorf(codes.Internal, "ttrpc: error unsupported request type: %T", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
info := &UnaryServerInfo{
|
||||
FullMethod: fullPath(serviceName, methodName),
|
||||
}
|
||||
|
||||
resp, err := s.interceptor(ctx, unmarshal, info, method)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch v := resp.(type) {
|
||||
case proto.Message:
|
||||
r, err := proto.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "ttrpc: error marshaling payload: %v", err.Error())
|
||||
}
|
||||
|
||||
return r, nil
|
||||
default:
|
||||
return nil, status.Errorf(codes.Internal, "ttrpc: error unsupported response type: %T", v)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceSet) resolve(service, method string) (Method, error) {
|
||||
srv, ok := s.services[service]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "service %v", service)
|
||||
}
|
||||
|
||||
mthd, ok := srv.Methods[method]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "method %v", method)
|
||||
}
|
||||
|
||||
return mthd, nil
|
||||
}
|
||||
|
||||
// convertCode maps stdlib go errors into grpc space.
|
||||
//
|
||||
// This is ripped from the grpc-go code base.
|
||||
func convertCode(err error) codes.Code {
|
||||
switch err {
|
||||
case nil:
|
||||
return codes.OK
|
||||
case io.EOF:
|
||||
return codes.OutOfRange
|
||||
case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF:
|
||||
return codes.FailedPrecondition
|
||||
case os.ErrInvalid:
|
||||
return codes.InvalidArgument
|
||||
case context.Canceled:
|
||||
return codes.Canceled
|
||||
case context.DeadlineExceeded:
|
||||
return codes.DeadlineExceeded
|
||||
}
|
||||
switch {
|
||||
case os.IsExist(err):
|
||||
return codes.AlreadyExists
|
||||
case os.IsNotExist(err):
|
||||
return codes.NotFound
|
||||
case os.IsPermission(err):
|
||||
return codes.PermissionDenied
|
||||
}
|
||||
return codes.Unknown
|
||||
}
|
||||
|
||||
func fullPath(service, method string) string {
|
||||
return "/" + path.Join(service, method)
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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 ttrpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
spb "google.golang.org/genproto/googleapis/rpc/status"
|
||||
)
|
||||
|
||||
type Request struct {
|
||||
Service string `protobuf:"bytes,1,opt,name=service,proto3"`
|
||||
Method string `protobuf:"bytes,2,opt,name=method,proto3"`
|
||||
Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3"`
|
||||
TimeoutNano int64 `protobuf:"varint,4,opt,name=timeout_nano,proto3"`
|
||||
Metadata []*KeyValue `protobuf:"bytes,5,rep,name=metadata,proto3"`
|
||||
}
|
||||
|
||||
func (r *Request) Reset() { *r = Request{} }
|
||||
func (r *Request) String() string { return fmt.Sprintf("%+#v", r) }
|
||||
func (r *Request) ProtoMessage() {}
|
||||
|
||||
type Response struct {
|
||||
Status *spb.Status `protobuf:"bytes,1,opt,name=status,proto3"`
|
||||
Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3"`
|
||||
}
|
||||
|
||||
func (r *Response) Reset() { *r = Response{} }
|
||||
func (r *Response) String() string { return fmt.Sprintf("%+#v", r) }
|
||||
func (r *Response) ProtoMessage() {}
|
||||
|
||||
type StringList struct {
|
||||
List []string `protobuf:"bytes,1,rep,name=list,proto3"`
|
||||
}
|
||||
|
||||
func (r *StringList) Reset() { *r = StringList{} }
|
||||
func (r *StringList) String() string { return fmt.Sprintf("%+#v", r) }
|
||||
func (r *StringList) ProtoMessage() {}
|
||||
|
||||
func makeStringList(item ...string) StringList { return StringList{List: item} }
|
||||
|
||||
type KeyValue struct {
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3"`
|
||||
Value string `protobuf:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
func (m *KeyValue) Reset() { *m = KeyValue{} }
|
||||
func (*KeyValue) ProtoMessage() {}
|
||||
func (m *KeyValue) String() string { return fmt.Sprintf("%+#v", m) }
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
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 ttrpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type UnixCredentialsFunc func(*unix.Ucred) error
|
||||
|
||||
func (fn UnixCredentialsFunc) Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) {
|
||||
uc, err := requireUnixSocket(conn)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "ttrpc.UnixCredentialsFunc: require unix socket")
|
||||
}
|
||||
|
||||
rs, err := uc.SyscallConn()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "ttrpc.UnixCredentialsFunc: (net.UnixConn).SyscallConn failed")
|
||||
}
|
||||
var (
|
||||
ucred *unix.Ucred
|
||||
ucredErr error
|
||||
)
|
||||
if err := rs.Control(func(fd uintptr) {
|
||||
ucred, ucredErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED)
|
||||
}); err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "ttrpc.UnixCredentialsFunc: (*syscall.RawConn).Control failed")
|
||||
}
|
||||
|
||||
if ucredErr != nil {
|
||||
return nil, nil, errors.Wrapf(err, "ttrpc.UnixCredentialsFunc: failed to retrieve socket peer credentials")
|
||||
}
|
||||
|
||||
if err := fn(ucred); err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "ttrpc.UnixCredentialsFunc: credential check failed")
|
||||
}
|
||||
|
||||
return uc, ucred, nil
|
||||
}
|
||||
|
||||
// UnixSocketRequireUidGid requires specific *effective* UID/GID, rather than the real UID/GID.
|
||||
//
|
||||
// For example, if a daemon binary is owned by the root (UID 0) with SUID bit but running as an
|
||||
// unprivileged user (UID 1001), the effective UID becomes 0, and the real UID becomes 1001.
|
||||
// So calling this function with uid=0 allows a connection from effective UID 0 but rejects
|
||||
// a connection from effective UID 1001.
|
||||
//
|
||||
// See socket(7), SO_PEERCRED: "The returned credentials are those that were in effect at the time of the call to connect(2) or socketpair(2)."
|
||||
func UnixSocketRequireUidGid(uid, gid int) UnixCredentialsFunc {
|
||||
return func(ucred *unix.Ucred) error {
|
||||
return requireUidGid(ucred, uid, gid)
|
||||
}
|
||||
}
|
||||
|
||||
func UnixSocketRequireRoot() UnixCredentialsFunc {
|
||||
return UnixSocketRequireUidGid(0, 0)
|
||||
}
|
||||
|
||||
// UnixSocketRequireSameUser resolves the current effective unix user and returns a
|
||||
// UnixCredentialsFunc that will validate incoming unix connections against the
|
||||
// current credentials.
|
||||
//
|
||||
// This is useful when using abstract sockets that are accessible by all users.
|
||||
func UnixSocketRequireSameUser() UnixCredentialsFunc {
|
||||
euid, egid := os.Geteuid(), os.Getegid()
|
||||
return UnixSocketRequireUidGid(euid, egid)
|
||||
}
|
||||
|
||||
func requireRoot(ucred *unix.Ucred) error {
|
||||
return requireUidGid(ucred, 0, 0)
|
||||
}
|
||||
|
||||
func requireUidGid(ucred *unix.Ucred, uid, gid int) error {
|
||||
if (uid != -1 && uint32(uid) != ucred.Uid) || (gid != -1 && uint32(gid) != ucred.Gid) {
|
||||
return errors.Wrap(syscall.EPERM, "ttrpc: invalid credentials")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func requireUnixSocket(conn net.Conn) (*net.UnixConn, error) {
|
||||
uc, ok := conn.(*net.UnixConn)
|
||||
if !ok {
|
||||
return nil, errors.New("a unix socket connection is required")
|
||||
}
|
||||
|
||||
return uc, nil
|
||||
}
|
Loading…
Reference in New Issue