vendor: update buildkit with typed errors support

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2020-06-24 22:20:05 -07:00
parent 0269388aa7
commit 2d720a1e0b
619 changed files with 38296 additions and 104947 deletions

View File

@ -92,7 +92,11 @@ func NewLabeledStore(root string, ls LabelStore) (content.Store, error) {
}
func (s *store) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
p := s.blobPath(dgst)
p, err := s.blobPath(dgst)
if err != nil {
return content.Info{}, errors.Wrapf(err, "calculating blob info path")
}
fi, err := os.Stat(p)
if err != nil {
if os.IsNotExist(err) {
@ -123,7 +127,10 @@ func (s *store) info(dgst digest.Digest, fi os.FileInfo, labels map[string]strin
// ReaderAt returns an io.ReaderAt for the blob.
func (s *store) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
p := s.blobPath(desc.Digest)
p, err := s.blobPath(desc.Digest)
if err != nil {
return nil, errors.Wrapf(err, "calculating blob path for ReaderAt")
}
fi, err := os.Stat(p)
if err != nil {
if !os.IsNotExist(err) {
@ -150,7 +157,12 @@ func (s *store) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.
// While this is safe to do concurrently, safe exist-removal logic must hold
// some global lock on the store.
func (s *store) Delete(ctx context.Context, dgst digest.Digest) error {
if err := os.RemoveAll(s.blobPath(dgst)); err != nil {
bp, err := s.blobPath(dgst)
if err != nil {
return errors.Wrapf(err, "calculating blob path for delete")
}
if err := os.RemoveAll(bp); err != nil {
if !os.IsNotExist(err) {
return err
}
@ -166,7 +178,11 @@ func (s *store) Update(ctx context.Context, info content.Info, fieldpaths ...str
return content.Info{}, errors.Wrapf(errdefs.ErrFailedPrecondition, "update not supported on immutable content store")
}
p := s.blobPath(info.Digest)
p, err := s.blobPath(info.Digest)
if err != nil {
return content.Info{}, errors.Wrapf(err, "calculating blob path for update")
}
fi, err := os.Stat(p)
if err != nil {
if os.IsNotExist(err) {
@ -512,7 +528,10 @@ func (s *store) writer(ctx context.Context, ref string, total int64, expected di
// TODO(stevvooe): Need to actually store expected here. We have
// code in the service that shouldn't be dealing with this.
if expected != "" {
p := s.blobPath(expected)
p, err := s.blobPath(expected)
if err != nil {
return nil, errors.Wrap(err, "calculating expected blob path for writer")
}
if _, err := os.Stat(p); err == nil {
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", expected)
}
@ -607,11 +626,17 @@ func (s *store) Abort(ctx context.Context, ref string) error {
return nil
}
func (s *store) blobPath(dgst digest.Digest) string {
return filepath.Join(s.root, "blobs", dgst.Algorithm().String(), dgst.Hex())
func (s *store) blobPath(dgst digest.Digest) (string, error) {
if err := dgst.Validate(); err != nil {
return "", errors.Wrapf(errdefs.ErrInvalidArgument, "cannot calculate blob path from invalid digest: %v", err)
}
return filepath.Join(s.root, "blobs", dgst.Algorithm().String(), dgst.Hex()), nil
}
func (s *store) ingestRoot(ref string) string {
// we take a digest of the ref to keep the ingest paths constant length.
// Note that this is not the current or potential digest of incoming content.
dgst := digest.FromString(ref)
return filepath.Join(s.root, "ingest", dgst.Hex())
}

View File

@ -115,8 +115,8 @@ func (w *writer) Commit(ctx context.Context, size int64, expected digest.Digest,
}
var (
ingest = filepath.Join(w.path, "data")
target = w.s.blobPath(dgst)
ingest = filepath.Join(w.path, "data")
target, _ = w.s.blobPath(dgst) // ignore error because we calculated this dgst
)
// make sure parent directories of blob exist

View File

@ -51,43 +51,43 @@ var (
// IsInvalidArgument returns true if the error is due to an invalid argument
func IsInvalidArgument(err error) bool {
return errors.Cause(err) == ErrInvalidArgument
return errors.Is(err, ErrInvalidArgument)
}
// IsNotFound returns true if the error is due to a missing object
func IsNotFound(err error) bool {
return errors.Cause(err) == ErrNotFound
return errors.Is(err, ErrNotFound)
}
// IsAlreadyExists returns true if the error is due to an already existing
// metadata item
func IsAlreadyExists(err error) bool {
return errors.Cause(err) == ErrAlreadyExists
return errors.Is(err, ErrAlreadyExists)
}
// IsFailedPrecondition returns true if an operation could not proceed to the
// lack of a particular condition
func IsFailedPrecondition(err error) bool {
return errors.Cause(err) == ErrFailedPrecondition
return errors.Is(err, ErrFailedPrecondition)
}
// IsUnavailable returns true if the error is due to a resource being unavailable
func IsUnavailable(err error) bool {
return errors.Cause(err) == ErrUnavailable
return errors.Is(err, ErrUnavailable)
}
// IsNotImplemented returns true if the error is due to not being implemented
func IsNotImplemented(err error) bool {
return errors.Cause(err) == ErrNotImplemented
return errors.Is(err, ErrNotImplemented)
}
// IsCanceled returns true if the error is due to `context.Canceled`.
func IsCanceled(err error) bool {
return errors.Cause(err) == context.Canceled
return errors.Is(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
return errors.Is(err, context.DeadlineExceeded)
}

View File

@ -64,7 +64,7 @@ func Handlers(handlers ...Handler) HandlerFunc {
for _, handler := range handlers {
ch, err := handler.Handle(ctx, desc)
if err != nil {
if errors.Cause(err) == ErrStopHandler {
if errors.Is(err, ErrStopHandler) {
break
}
return nil, err
@ -87,7 +87,7 @@ func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) err
children, err := handler.Handle(ctx, desc)
if err != nil {
if errors.Cause(err) == ErrSkipDesc {
if errors.Is(err, ErrSkipDesc) {
continue // don't traverse the children.
}
return err
@ -136,7 +136,7 @@ func Dispatch(ctx context.Context, handler Handler, limiter *semaphore.Weighted,
limiter.Release(1)
}
if err != nil {
if errors.Cause(err) == ErrSkipDesc {
if errors.Is(err, ErrSkipDesc) {
return nil // don't traverse the children.
}
return err
@ -170,6 +170,19 @@ func ChildrenHandler(provider content.Provider) HandlerFunc {
// the children returned by the handler and passes through the children.
// Must follow a handler that returns the children to be labeled.
func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc {
return SetChildrenMappedLabels(manager, f, nil)
}
// SetChildrenMappedLabels is a handler wrapper which sets labels for the content on
// the children returned by the handler and passes through the children.
// Must follow a handler that returns the children to be labeled.
// The label map allows the caller to control the labels per child descriptor.
// For returned labels, the index of the child will be appended to the end
// except for the first index when the returned label does not end with '.'.
func SetChildrenMappedLabels(manager content.Manager, f HandlerFunc, labelMap func(ocispec.Descriptor) []string) HandlerFunc {
if labelMap == nil {
labelMap = ChildGCLabels
}
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
children, err := f(ctx, desc)
if err != nil {
@ -177,14 +190,26 @@ func SetChildrenLabels(manager content.Manager, f HandlerFunc) HandlerFunc {
}
if len(children) > 0 {
info := content.Info{
Digest: desc.Digest,
Labels: map[string]string{},
}
fields := []string{}
for i, ch := range children {
info.Labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = ch.Digest.String()
fields = append(fields, fmt.Sprintf("labels.containerd.io/gc.ref.content.%d", i))
var (
info = content.Info{
Digest: desc.Digest,
Labels: map[string]string{},
}
fields = []string{}
keys = map[string]uint{}
)
for _, ch := range children {
labelKeys := labelMap(ch)
for _, key := range labelKeys {
idx := keys[key]
keys[key] = idx + 1
if idx > 0 || key[len(key)-1] == '.' {
key = fmt.Sprintf("%s%d", key, idx)
}
info.Labels[key] = ch.Digest.String()
fields = append(fields, "labels."+key)
}
}
_, err := manager.Update(ctx, info, fields...)

View File

@ -362,7 +362,7 @@ func Children(ctx context.Context, provider content.Provider, desc ocispec.Descr
// childless data types.
return nil, nil
}
log.G(ctx).Warnf("encountered unknown type %v; children may not be fetched", desc.MediaType)
log.G(ctx).Debugf("encountered unknown type %v; children may not be fetched", desc.MediaType)
}
return descs, nil

View File

@ -23,6 +23,7 @@ import (
"github.com/containerd/containerd/errdefs"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
// mediatype definitions for image components handled in containerd.
@ -81,7 +82,7 @@ func DiffCompression(ctx context.Context, mediaType string) (string, error) {
}
return "", nil
default:
return "", errdefs.ErrNotImplemented
return "", errors.Wrapf(errdefs.ErrNotImplemented, "unrecognised mediatype %s", mediaType)
}
}
@ -124,3 +125,31 @@ func IsKnownConfig(mt string) bool {
}
return false
}
// ChildGCLabels returns the label for a given descriptor to reference it
func ChildGCLabels(desc ocispec.Descriptor) []string {
mt := desc.MediaType
if IsKnownConfig(mt) {
return []string{"containerd.io/gc.ref.content.config"}
}
switch mt {
case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
return []string{"containerd.io/gc.ref.content.m."}
}
if IsLayerType(mt) {
return []string{"containerd.io/gc.ref.content.l."}
}
return []string{"containerd.io/gc.ref.content."}
}
// ChildGCLabelsFilterLayers returns the labels for a given descriptor to
// reference it, skipping layer media types
func ChildGCLabelsFilterLayers(desc ocispec.Descriptor) []string {
if IsLayerType(desc.MediaType) {
return nil
}
return ChildGCLabels(desc)
}

View File

@ -74,8 +74,8 @@ func getCPUInfo(pattern string) (info string, err error) {
}
func getCPUVariant() string {
if runtime.GOOS == "windows" {
// Windows only supports v7 for ARM32 and v8 for ARM64 and so we can use
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
// runtime.GOARCH to determine the variants
var variant string
switch runtime.GOARCH {

View File

@ -0,0 +1,206 @@
/*
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 auth
import (
"context"
"encoding/json"
"net/http"
"net/url"
"strings"
"time"
"github.com/containerd/containerd/log"
remoteserrors "github.com/containerd/containerd/remotes/errors"
"github.com/pkg/errors"
"golang.org/x/net/context/ctxhttp"
)
var (
// ErrNoToken is returned if a request is successful but the body does not
// contain an authorization token.
ErrNoToken = errors.New("authorization server did not include a token in the response")
)
// GenerateTokenOptions generates options for fetching a token based on a challenge
func GenerateTokenOptions(ctx context.Context, host, username, secret string, c Challenge) (TokenOptions, error) {
realm, ok := c.Parameters["realm"]
if !ok {
return TokenOptions{}, errors.New("no realm specified for token auth challenge")
}
realmURL, err := url.Parse(realm)
if err != nil {
return TokenOptions{}, errors.Wrap(err, "invalid token auth challenge realm")
}
to := TokenOptions{
Realm: realmURL.String(),
Service: c.Parameters["service"],
Username: username,
Secret: secret,
}
scope, ok := c.Parameters["scope"]
if ok {
to.Scopes = append(to.Scopes, scope)
} else {
log.G(ctx).WithField("host", host).Debug("no scope specified for token auth challenge")
}
return to, nil
}
// TokenOptions are optios for requesting a token
type TokenOptions struct {
Realm string
Service string
Scopes []string
Username string
Secret string
}
// OAuthTokenResponse is response from fetching token with a OAuth POST request
type OAuthTokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
IssuedAt time.Time `json:"issued_at"`
Scope string `json:"scope"`
}
// FetchTokenWithOAuth fetches a token using a POST request
func FetchTokenWithOAuth(ctx context.Context, client *http.Client, headers http.Header, clientID string, to TokenOptions) (*OAuthTokenResponse, error) {
form := url.Values{}
if len(to.Scopes) > 0 {
form.Set("scope", strings.Join(to.Scopes, " "))
}
form.Set("service", to.Service)
form.Set("client_id", clientID)
if to.Username == "" {
form.Set("grant_type", "refresh_token")
form.Set("refresh_token", to.Secret)
} else {
form.Set("grant_type", "password")
form.Set("username", to.Username)
form.Set("password", to.Secret)
}
req, err := http.NewRequest("POST", to.Realm, strings.NewReader(form.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
if headers != nil {
for k, v := range headers {
req.Header[k] = append(req.Header[k], v...)
}
}
resp, err := ctxhttp.Do(ctx, client, req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return nil, errors.WithStack(remoteserrors.NewUnexpectedStatusErr(resp))
}
decoder := json.NewDecoder(resp.Body)
var tr OAuthTokenResponse
if err = decoder.Decode(&tr); err != nil {
return nil, errors.Wrap(err, "unable to decode token response")
}
if tr.AccessToken == "" {
return nil, errors.WithStack(ErrNoToken)
}
return &tr, nil
}
// FetchTokenResponse is response from fetching token with GET request
type FetchTokenResponse struct {
Token string `json:"token"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
IssuedAt time.Time `json:"issued_at"`
RefreshToken string `json:"refresh_token"`
}
// FetchToken fetches a token using a GET request
func FetchToken(ctx context.Context, client *http.Client, headers http.Header, to TokenOptions) (*FetchTokenResponse, error) {
req, err := http.NewRequest("GET", to.Realm, nil)
if err != nil {
return nil, err
}
if headers != nil {
for k, v := range headers {
req.Header[k] = append(req.Header[k], v...)
}
}
reqParams := req.URL.Query()
if to.Service != "" {
reqParams.Add("service", to.Service)
}
for _, scope := range to.Scopes {
reqParams.Add("scope", scope)
}
if to.Secret != "" {
req.SetBasicAuth(to.Username, to.Secret)
}
req.URL.RawQuery = reqParams.Encode()
resp, err := ctxhttp.Do(ctx, client, req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
return nil, errors.WithStack(remoteserrors.NewUnexpectedStatusErr(resp))
}
decoder := json.NewDecoder(resp.Body)
var tr FetchTokenResponse
if err = decoder.Decode(&tr); err != nil {
return nil, errors.Wrap(err, "unable to decode token response")
}
// `access_token` is equivalent to `token` and if both are specified
// the choice is undefined. Canonicalize `access_token` by sticking
// things in `token`.
if tr.AccessToken != "" {
tr.Token = tr.AccessToken
}
if tr.Token == "" {
return nil, errors.WithStack(ErrNoToken)
}
return &tr, nil
}

View File

@ -14,7 +14,7 @@
limitations under the License.
*/
package docker
package auth
import (
"net/http"
@ -22,31 +22,35 @@ import (
"strings"
)
type authenticationScheme byte
// AuthenticationScheme defines scheme of the authentication method
type AuthenticationScheme byte
const (
basicAuth authenticationScheme = 1 << iota // Defined in RFC 7617
digestAuth // Defined in RFC 7616
bearerAuth // Defined in RFC 6750
// BasicAuth is scheme for Basic HTTP Authentication RFC 7617
BasicAuth AuthenticationScheme = 1 << iota
// DigestAuth is scheme for HTTP Digest Access Authentication RFC 7616
DigestAuth
// BearerAuth is scheme for OAuth 2.0 Bearer Tokens RFC 6750
BearerAuth
)
// challenge carries information from a WWW-Authenticate response header.
// Challenge carries information from a WWW-Authenticate response header.
// See RFC 2617.
type challenge struct {
type Challenge struct {
// scheme is the auth-scheme according to RFC 2617
scheme authenticationScheme
Scheme AuthenticationScheme
// parameters are the auth-params according to RFC 2617
parameters map[string]string
Parameters map[string]string
}
type byScheme []challenge
type byScheme []Challenge
func (bs byScheme) Len() int { return len(bs) }
func (bs byScheme) Swap(i, j int) { bs[i], bs[j] = bs[j], bs[i] }
// Sort in priority order: token > digest > basic
func (bs byScheme) Less(i, j int) bool { return bs[i].scheme > bs[j].scheme }
func (bs byScheme) Less(i, j int) bool { return bs[i].Scheme > bs[j].Scheme }
// Octet types from RFC 2616.
type octetType byte
@ -90,22 +94,23 @@ func init() {
}
}
func parseAuthHeader(header http.Header) []challenge {
challenges := []challenge{}
// ParseAuthHeader parses challenges from WWW-Authenticate header
func ParseAuthHeader(header http.Header) []Challenge {
challenges := []Challenge{}
for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
v, p := parseValueAndParams(h)
var s authenticationScheme
var s AuthenticationScheme
switch v {
case "basic":
s = basicAuth
s = BasicAuth
case "digest":
s = digestAuth
s = DigestAuth
case "bearer":
s = bearerAuth
s = BearerAuth
default:
continue
}
challenges = append(challenges, challenge{scheme: s, parameters: p})
challenges = append(challenges, Challenge{Scheme: s, Parameters: p})
}
sort.Stable(byScheme(challenges))
return challenges

View File

@ -19,21 +19,17 @@ package docker
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/remotes/docker/auth"
remoteerrors "github.com/containerd/containerd/remotes/errors"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context/ctxhttp"
)
type dockerAuthorizer struct {
@ -135,8 +131,8 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
a.mu.Lock()
defer a.mu.Unlock()
for _, c := range parseAuthHeader(last.Header) {
if c.scheme == bearerAuth {
for _, c := range auth.ParseAuthHeader(last.Header) {
if c.Scheme == auth.BearerAuth {
if err := invalidAuthorization(c, responses); err != nil {
delete(a.handlers, host)
return err
@ -152,26 +148,35 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
return nil
}
common, err := a.generateTokenOptions(ctx, host, c)
var username, secret string
if a.credentials != nil {
var err error
username, secret, err = a.credentials(host)
if err != nil {
return err
}
}
common, err := auth.GenerateTokenOptions(ctx, host, username, secret, c)
if err != nil {
return err
}
a.handlers[host] = newAuthHandler(a.client, a.header, c.scheme, common)
a.handlers[host] = newAuthHandler(a.client, a.header, c.Scheme, common)
return nil
} else if c.scheme == basicAuth && a.credentials != nil {
} else if c.Scheme == auth.BasicAuth && a.credentials != nil {
username, secret, err := a.credentials(host)
if err != nil {
return err
}
if username != "" && secret != "" {
common := tokenOptions{
username: username,
secret: secret,
common := auth.TokenOptions{
Username: username,
Secret: secret,
}
a.handlers[host] = newAuthHandler(a.client, a.header, c.scheme, common)
a.handlers[host] = newAuthHandler(a.client, a.header, c.Scheme, common)
return nil
}
}
@ -179,38 +184,6 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R
return errors.Wrap(errdefs.ErrNotImplemented, "failed to find supported auth scheme")
}
func (a *dockerAuthorizer) generateTokenOptions(ctx context.Context, host string, c challenge) (tokenOptions, error) {
realm, ok := c.parameters["realm"]
if !ok {
return tokenOptions{}, errors.New("no realm specified for token auth challenge")
}
realmURL, err := url.Parse(realm)
if err != nil {
return tokenOptions{}, errors.Wrap(err, "invalid token auth challenge realm")
}
to := tokenOptions{
realm: realmURL.String(),
service: c.parameters["service"],
}
scope, ok := c.parameters["scope"]
if ok {
to.scopes = append(to.scopes, scope)
} else {
log.G(ctx).WithField("host", host).Debug("no scope specified for token auth challenge")
}
if a.credentials != nil {
to.username, to.secret, err = a.credentials(host)
if err != nil {
return tokenOptions{}, err
}
}
return to, nil
}
// authResult is used to control limit rate.
type authResult struct {
sync.WaitGroup
@ -227,17 +200,17 @@ type authHandler struct {
client *http.Client
// only support basic and bearer schemes
scheme authenticationScheme
scheme auth.AuthenticationScheme
// common contains common challenge answer
common tokenOptions
common auth.TokenOptions
// scopedTokens caches token indexed by scopes, which used in
// bearer auth case
scopedTokens map[string]*authResult
}
func newAuthHandler(client *http.Client, hdr http.Header, scheme authenticationScheme, opts tokenOptions) *authHandler {
func newAuthHandler(client *http.Client, hdr http.Header, scheme auth.AuthenticationScheme, opts auth.TokenOptions) *authHandler {
return &authHandler{
header: hdr,
client: client,
@ -249,17 +222,17 @@ func newAuthHandler(client *http.Client, hdr http.Header, scheme authenticationS
func (ah *authHandler) authorize(ctx context.Context) (string, error) {
switch ah.scheme {
case basicAuth:
case auth.BasicAuth:
return ah.doBasicAuth(ctx)
case bearerAuth:
case auth.BearerAuth:
return ah.doBearerAuth(ctx)
default:
return "", errors.Wrap(errdefs.ErrNotImplemented, "failed to find supported auth scheme")
return "", errors.Wrapf(errdefs.ErrNotImplemented, "failed to find supported auth scheme: %s", string(ah.scheme))
}
}
func (ah *authHandler) doBasicAuth(ctx context.Context) (string, error) {
username, secret := ah.common.username, ah.common.secret
username, secret := ah.common.Username, ah.common.Secret
if username == "" || secret == "" {
return "", fmt.Errorf("failed to handle basic auth because missing username or secret")
@ -269,14 +242,14 @@ func (ah *authHandler) doBasicAuth(ctx context.Context) (string, error) {
return fmt.Sprintf("Basic %s", auth), nil
}
func (ah *authHandler) doBearerAuth(ctx context.Context) (string, error) {
func (ah *authHandler) doBearerAuth(ctx context.Context) (token string, err error) {
// copy common tokenOptions
to := ah.common
to.scopes = getTokenScopes(ctx, to.scopes)
to.Scopes = GetTokenScopes(ctx, to.Scopes)
// Docs: https://docs.docker.com/registry/spec/auth/scope
scoped := strings.Join(to.scopes, " ")
scoped := strings.Join(to.Scopes, " ")
ah.Lock()
if r, exist := ah.scopedTokens[scoped]; exist {
@ -291,174 +264,52 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (string, error) {
ah.scopedTokens[scoped] = r
ah.Unlock()
defer func() {
token = fmt.Sprintf("Bearer %s", token)
r.token, r.err = token, err
r.Done()
}()
// fetch token for the resource scope
var (
token string
err error
)
if to.secret != "" {
if to.Secret != "" {
defer func() {
err = errors.Wrap(err, "failed to fetch oauth token")
}()
// credential information is provided, use oauth POST endpoint
token, err = ah.fetchTokenWithOAuth(ctx, to)
err = errors.Wrap(err, "failed to fetch oauth token")
} else {
// do request anonymously
token, err = ah.fetchToken(ctx, to)
err = errors.Wrap(err, "failed to fetch anonymous token")
}
token = fmt.Sprintf("Bearer %s", token)
r.token, r.err = token, err
r.Done()
return r.token, r.err
}
type tokenOptions struct {
realm string
service string
scopes []string
username string
secret string
}
type postTokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
IssuedAt time.Time `json:"issued_at"`
Scope string `json:"scope"`
}
func (ah *authHandler) fetchTokenWithOAuth(ctx context.Context, to tokenOptions) (string, error) {
form := url.Values{}
if len(to.scopes) > 0 {
form.Set("scope", strings.Join(to.scopes, " "))
}
form.Set("service", to.service)
// TODO: Allow setting client_id
form.Set("client_id", "containerd-client")
if to.username == "" {
form.Set("grant_type", "refresh_token")
form.Set("refresh_token", to.secret)
} else {
form.Set("grant_type", "password")
form.Set("username", to.username)
form.Set("password", to.secret)
}
req, err := http.NewRequest("POST", to.realm, strings.NewReader(form.Encode()))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
if ah.header != nil {
for k, v := range ah.header {
req.Header[k] = append(req.Header[k], v...)
// TODO: Allow setting client_id
resp, err := auth.FetchTokenWithOAuth(ctx, ah.client, ah.header, "containerd-client", to)
if err != nil {
var errStatus remoteerrors.ErrUnexpectedStatus
if errors.As(err, &errStatus) {
// Registries without support for POST may return 404 for POST /v2/token.
// As of September 2017, GCR is known to return 404.
// As of February 2018, JFrog Artifactory is known to return 401.
if (errStatus.StatusCode == 405 && to.Username != "") || errStatus.StatusCode == 404 || errStatus.StatusCode == 401 {
resp, err := auth.FetchToken(ctx, ah.client, ah.header, to)
if err != nil {
return "", err
}
return resp.Token, nil
}
log.G(ctx).WithFields(logrus.Fields{
"status": errStatus.Status,
"body": string(errStatus.Body),
}).Debugf("token request failed")
}
return "", err
}
return resp.AccessToken, nil
}
resp, err := ctxhttp.Do(ctx, ah.client, req)
// do request anonymously
resp, err := auth.FetchToken(ctx, ah.client, ah.header, to)
if err != nil {
return "", err
return "", errors.Wrap(err, "failed to fetch anonymous token")
}
defer resp.Body.Close()
// Registries without support for POST may return 404 for POST /v2/token.
// As of September 2017, GCR is known to return 404.
// As of February 2018, JFrog Artifactory is known to return 401.
if (resp.StatusCode == 405 && to.username != "") || resp.StatusCode == 404 || resp.StatusCode == 401 {
return ah.fetchToken(ctx, to)
} else if resp.StatusCode < 200 || resp.StatusCode >= 400 {
b, _ := ioutil.ReadAll(io.LimitReader(resp.Body, 64000)) // 64KB
log.G(ctx).WithFields(logrus.Fields{
"status": resp.Status,
"body": string(b),
}).Debugf("token request failed")
// TODO: handle error body and write debug output
return "", errors.Errorf("unexpected status: %s", resp.Status)
}
decoder := json.NewDecoder(resp.Body)
var tr postTokenResponse
if err = decoder.Decode(&tr); err != nil {
return "", fmt.Errorf("unable to decode token response: %s", err)
}
return tr.AccessToken, nil
return resp.Token, nil
}
type getTokenResponse struct {
Token string `json:"token"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
IssuedAt time.Time `json:"issued_at"`
RefreshToken string `json:"refresh_token"`
}
// fetchToken fetches a token using a GET request
func (ah *authHandler) fetchToken(ctx context.Context, to tokenOptions) (string, error) {
req, err := http.NewRequest("GET", to.realm, nil)
if err != nil {
return "", err
}
if ah.header != nil {
for k, v := range ah.header {
req.Header[k] = append(req.Header[k], v...)
}
}
reqParams := req.URL.Query()
if to.service != "" {
reqParams.Add("service", to.service)
}
for _, scope := range to.scopes {
reqParams.Add("scope", scope)
}
if to.secret != "" {
req.SetBasicAuth(to.username, to.secret)
}
req.URL.RawQuery = reqParams.Encode()
resp, err := ctxhttp.Do(ctx, ah.client, req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
// TODO: handle error body and write debug output
return "", errors.Errorf("unexpected status: %s", resp.Status)
}
decoder := json.NewDecoder(resp.Body)
var tr getTokenResponse
if err = decoder.Decode(&tr); err != nil {
return "", fmt.Errorf("unable to decode token response: %s", err)
}
// `access_token` is equivalent to `token` and if both are specified
// the choice is undefined. Canonicalize `access_token` by sticking
// things in `token`.
if tr.AccessToken != "" {
tr.Token = tr.AccessToken
}
if tr.Token == "" {
return "", ErrNoToken
}
return tr.Token, nil
}
func invalidAuthorization(c challenge, responses []*http.Response) error {
errStr := c.parameters["error"]
func invalidAuthorization(c auth.Challenge, responses []*http.Response) error {
errStr := c.Parameters["error"]
if errStr == "" {
return nil
}

View File

@ -98,6 +98,9 @@ func (r dockerFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.R
var firstErr error
for _, host := range r.hosts {
req := r.request(host, http.MethodGet, "manifests", desc.Digest.String())
if err := req.addNamespace(r.refspec.Hostname()); err != nil {
return nil, err
}
rc, err := r.open(ctx, req, desc.MediaType, offset)
if err != nil {
@ -118,6 +121,9 @@ func (r dockerFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.R
var firstErr error
for _, host := range r.hosts {
req := r.request(host, http.MethodGet, "blobs", desc.Digest.String())
if err := req.addNamespace(r.refspec.Hostname()); err != nil {
return nil, err
}
rc, err := r.open(ctx, req, desc.MediaType, offset)
if err != nil {

View File

@ -30,6 +30,7 @@ import (
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/remotes"
remoteserrors "github.com/containerd/containerd/remotes/errors"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
@ -86,7 +87,7 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten
resp, err := req.doWithRetries(ctx, nil)
if err != nil {
if errors.Cause(err) != ErrInvalidAuthorization {
if !errors.Is(err, ErrInvalidAuthorization) {
return nil, err
}
log.G(ctx).WithError(err).Debugf("Unable to check existence, continuing with push")
@ -112,8 +113,9 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v on remote", desc.Digest)
}
} else if resp.StatusCode != http.StatusNotFound {
// TODO: log error
return nil, errors.Errorf("unexpected response: %s", resp.Status)
err := remoteserrors.NewUnexpectedStatusErr(resp)
log.G(ctx).WithField("resp", resp).WithField("body", string(err.(remoteserrors.ErrUnexpectedStatus).Body)).Debug("unexpected response")
return nil, err
}
}
@ -166,8 +168,9 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten
})
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v on remote", desc.Digest)
default:
// TODO: log error
return nil, errors.Errorf("unexpected response: %s", resp.Status)
err := remoteserrors.NewUnexpectedStatusErr(resp)
log.G(ctx).WithField("resp", resp).WithField("body", string(err.(remoteserrors.ErrUnexpectedStatus).Body)).Debug("unexpected response")
return nil, err
}
var (
@ -235,7 +238,7 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten
go func() {
defer close(respC)
resp, err = req.do(ctx)
resp, err := req.do(ctx)
if err != nil {
pr.CloseWithError(err)
return
@ -244,8 +247,9 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusNoContent:
default:
// TODO: log error
pr.CloseWithError(errors.Errorf("unexpected response: %s", resp.Status))
err := remoteserrors.NewUnexpectedStatusErr(resp)
log.G(ctx).WithField("resp", resp).WithField("body", string(err.(remoteserrors.ErrUnexpectedStatus).Body)).Debug("unexpected response")
pr.CloseWithError(err)
}
respC <- resp
}()

View File

@ -70,6 +70,16 @@ type RegistryHost struct {
Scheme string
Path string
Capabilities HostCapabilities
Header http.Header
}
func (h RegistryHost) isProxy(refhost string) bool {
if refhost != h.Host {
if refhost != "docker.io" || h.Host != "registry-1.docker.io" {
return true
}
}
return false
}
// RegistryHosts fetches the registry hosts for a given namespace,

View File

@ -22,6 +22,7 @@ import (
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
@ -40,10 +41,6 @@ import (
)
var (
// ErrNoToken is returned if a request is successful but the body does not
// contain an authorization token.
ErrNoToken = errors.New("authorization server did not include a token in the response")
// ErrInvalidAuthorization is used when credentials are passed to a server but
// those credentials are rejected.
ErrInvalidAuthorization = errors.New("authorization failed")
@ -276,6 +273,10 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
ctx := log.WithLogger(ctx, log.G(ctx).WithField("host", host.Host))
req := base.request(host, http.MethodHead, u...)
if err := req.addNamespace(base.refspec.Hostname()); err != nil {
return "", ocispec.Descriptor{}, err
}
for key, value := range r.resolveHeader {
req.header[key] = append(req.header[key], value...)
}
@ -283,7 +284,7 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
log.G(ctx).Debug("resolving")
resp, err := req.doWithRetries(ctx, nil)
if err != nil {
if errors.Cause(err) == ErrInvalidAuthorization {
if errors.Is(err, ErrInvalidAuthorization) {
err = errors.Wrapf(err, "pull access denied, repository does not exist or may require authorization")
}
// Store the error for referencing later
@ -323,6 +324,10 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
log.G(ctx).Debug("no Docker-Content-Digest header, fetching manifest instead")
req = base.request(host, http.MethodGet, u...)
if err := req.addNamespace(base.refspec.Hostname()); err != nil {
return "", ocispec.Descriptor{}, err
}
for key, value := range r.resolveHeader {
req.header[key] = append(req.header[key], value...)
}
@ -416,10 +421,10 @@ func (r *dockerResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher
}
type dockerBase struct {
refspec reference.Spec
namespace string
hosts []RegistryHost
header http.Header
refspec reference.Spec
repository string
hosts []RegistryHost
header http.Header
}
func (r *dockerResolver) base(refspec reference.Spec) (*dockerBase, error) {
@ -429,10 +434,10 @@ func (r *dockerResolver) base(refspec reference.Spec) (*dockerBase, error) {
return nil, err
}
return &dockerBase{
refspec: refspec,
namespace: strings.TrimPrefix(refspec.Locator, host+"/"),
hosts: hosts,
header: r.header,
refspec: refspec,
repository: strings.TrimPrefix(refspec.Locator, host+"/"),
hosts: hosts,
header: r.header,
}, nil
}
@ -450,7 +455,10 @@ func (r *dockerBase) request(host RegistryHost, method string, ps ...string) *re
for key, value := range r.header {
header[key] = append(header[key], value...)
}
parts := append([]string{"/", host.Path, r.namespace}, ps...)
for key, value := range host.Header {
header[key] = append(header[key], value...)
}
parts := append([]string{"/", host.Path, r.repository}, ps...)
p := path.Join(parts...)
// Join strips trailing slash, re-add ending "/" if included
if len(parts) > 0 && strings.HasSuffix(parts[len(parts)-1], "/") {
@ -475,6 +483,29 @@ func (r *request) authorize(ctx context.Context, req *http.Request) error {
return nil
}
func (r *request) addNamespace(ns string) (err error) {
if !r.host.isProxy(ns) {
return nil
}
var q url.Values
// Parse query
if i := strings.IndexByte(r.path, '?'); i > 0 {
r.path = r.path[:i+1]
q, err = url.ParseQuery(r.path[i+1:])
if err != nil {
return
}
} else {
r.path = r.path + "?"
q = url.Values{}
}
q.Add("ns", ns)
r.path = r.path + q.Encode()
return
}
type request struct {
method string
path string

View File

@ -72,8 +72,8 @@ func contextWithAppendPullRepositoryScope(ctx context.Context, repo string) cont
return WithScope(ctx, fmt.Sprintf("repository:%s:pull", repo))
}
// getTokenScopes returns deduplicated and sorted scopes from ctx.Value(tokenScopesKey{}) and common scopes.
func getTokenScopes(ctx context.Context, common []string) []string {
// GetTokenScopes returns deduplicated and sorted scopes from ctx.Value(tokenScopesKey{}) and common scopes.
func GetTokenScopes(ctx context.Context, common []string) []string {
var scopes []string
if x := ctx.Value(tokenScopesKey{}); x != nil {
scopes = append(scopes, x.([]string)...)

View File

@ -0,0 +1,46 @@
/*
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 errors
import (
"fmt"
"io"
"io/ioutil"
"net/http"
)
var _ error = ErrUnexpectedStatus{}
// ErrUnexpectedStatus is returned if a registry API request returned with unexpected HTTP status
type ErrUnexpectedStatus struct {
Status string
StatusCode int
Body []byte
}
func (e ErrUnexpectedStatus) Error() string {
return fmt.Sprintf("unexpected status: %s", e.Status)
}
// NewUnexpectedStatusErr creates an ErrUnexpectedStatus from HTTP response
func NewUnexpectedStatusErr(resp *http.Response) error {
var b []byte
if resp.Body != nil {
b, _ = ioutil.ReadAll(io.LimitReader(resp.Body, 64000)) // 64KB
}
return ErrUnexpectedStatus{Status: resp.Status, StatusCode: resp.StatusCode, Body: b}
}

View File

@ -20,17 +20,14 @@ package sys
import "golang.org/x/sys/unix"
// EpollCreate1 directly calls unix.EpollCreate1
func EpollCreate1(flag int) (int, error) {
return unix.EpollCreate1(flag)
}
// EpollCreate1 is an alias for unix.EpollCreate1
// Deprecated: use golang.org/x/sys/unix.EpollCreate1
var EpollCreate1 = unix.EpollCreate1
// EpollCtl directly calls unix.EpollCtl
func EpollCtl(epfd int, op int, fd int, event *unix.EpollEvent) error {
return unix.EpollCtl(epfd, op, fd, event)
}
// EpollCtl is an alias for unix.EpollCtl
// Deprecated: use golang.org/x/sys/unix.EpollCtl
var EpollCtl = unix.EpollCtl
// EpollWait directly calls unix.EpollWait
func EpollWait(epfd int, events []unix.EpollEvent, msec int) (int, error) {
return unix.EpollWait(epfd, events, msec)
}
// EpollWait is an alias for unix.EpollWait
// Deprecated: use golang.org/x/sys/unix.EpollWait
var EpollWait = unix.EpollWait

35
vendor/github.com/containerd/containerd/sys/filesys.go generated vendored Normal file
View File

@ -0,0 +1,35 @@
/*
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 sys
import "os"
// IsFifo checks if a file is a (named pipe) fifo
// if the file does not exist then it returns false
func IsFifo(path string) (bool, error) {
stat, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
if stat.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
return true, nil
}
return false, nil
}

View File

@ -26,8 +26,8 @@ import (
"syscall"
"unsafe"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim"
"golang.org/x/sys/windows"
)
const (
@ -41,7 +41,8 @@ func MkdirAllWithACL(path string, perm os.FileMode) error {
return mkdirall(path, true)
}
// MkdirAll implementation that is volume path aware for Windows.
// MkdirAll implementation that is volume path aware for Windows. It can be used
// as a drop-in replacement for os.MkdirAll()
func MkdirAll(path string, _ os.FileMode) error {
return mkdirall(path, false)
}
@ -111,26 +112,26 @@ func mkdirall(path string, adminAndLocalSystem bool) error {
// mkdirWithACL creates a new directory. If there is an error, it will be of
// type *PathError. .
//
// This is a modified and combined version of os.Mkdir and syscall.Mkdir
// This is a modified and combined version of os.Mkdir and windows.Mkdir
// in golang to cater for creating a directory am ACL permitting full
// access, with inheritance, to any subfolder/file for Built-in Administrators
// and Local System.
func mkdirWithACL(name string) error {
sa := syscall.SecurityAttributes{Length: 0}
sd, err := winio.SddlToSecurityDescriptor(SddlAdministratorsLocalSystem)
sa := windows.SecurityAttributes{Length: 0}
sd, err := windows.SecurityDescriptorFromString(SddlAdministratorsLocalSystem)
if err != nil {
return &os.PathError{Op: "mkdir", Path: name, Err: err}
}
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
sa.SecurityDescriptor = uintptr(unsafe.Pointer(&sd[0]))
sa.SecurityDescriptor = sd
namep, err := syscall.UTF16PtrFromString(name)
namep, err := windows.UTF16PtrFromString(name)
if err != nil {
return &os.PathError{Op: "mkdir", Path: name, Err: err}
}
e := syscall.CreateDirectory(namep, &sa)
e := windows.CreateDirectory(namep, &sa)
if e != nil {
return &os.PathError{Op: "mkdir", Path: name, Err: e}
}
@ -153,7 +154,7 @@ func IsAbs(path string) bool {
return true
}
// The origin of the functions below here are the golang OS and syscall packages,
// The origin of the functions below here are the golang OS and windows packages,
// slightly modified to only cope with files, not directories due to the
// specific use case.
//
@ -185,74 +186,74 @@ func OpenFileSequential(name string, flag int, _ os.FileMode) (*os.File, error)
if name == "" {
return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT}
}
r, errf := syscallOpenFileSequential(name, flag, 0)
r, errf := windowsOpenFileSequential(name, flag, 0)
if errf == nil {
return r, nil
}
return nil, &os.PathError{Op: "open", Path: name, Err: errf}
}
func syscallOpenFileSequential(name string, flag int, _ os.FileMode) (file *os.File, err error) {
r, e := syscallOpenSequential(name, flag|syscall.O_CLOEXEC, 0)
func windowsOpenFileSequential(name string, flag int, _ os.FileMode) (file *os.File, err error) {
r, e := windowsOpenSequential(name, flag|windows.O_CLOEXEC, 0)
if e != nil {
return nil, e
}
return os.NewFile(uintptr(r), name), nil
}
func makeInheritSa() *syscall.SecurityAttributes {
var sa syscall.SecurityAttributes
func makeInheritSa() *windows.SecurityAttributes {
var sa windows.SecurityAttributes
sa.Length = uint32(unsafe.Sizeof(sa))
sa.InheritHandle = 1
return &sa
}
func syscallOpenSequential(path string, mode int, _ uint32) (fd syscall.Handle, err error) {
func windowsOpenSequential(path string, mode int, _ uint32) (fd windows.Handle, err error) {
if len(path) == 0 {
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
return windows.InvalidHandle, windows.ERROR_FILE_NOT_FOUND
}
pathp, err := syscall.UTF16PtrFromString(path)
pathp, err := windows.UTF16PtrFromString(path)
if err != nil {
return syscall.InvalidHandle, err
return windows.InvalidHandle, err
}
var access uint32
switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
case syscall.O_RDONLY:
access = syscall.GENERIC_READ
case syscall.O_WRONLY:
access = syscall.GENERIC_WRITE
case syscall.O_RDWR:
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
switch mode & (windows.O_RDONLY | windows.O_WRONLY | windows.O_RDWR) {
case windows.O_RDONLY:
access = windows.GENERIC_READ
case windows.O_WRONLY:
access = windows.GENERIC_WRITE
case windows.O_RDWR:
access = windows.GENERIC_READ | windows.GENERIC_WRITE
}
if mode&syscall.O_CREAT != 0 {
access |= syscall.GENERIC_WRITE
if mode&windows.O_CREAT != 0 {
access |= windows.GENERIC_WRITE
}
if mode&syscall.O_APPEND != 0 {
access &^= syscall.GENERIC_WRITE
access |= syscall.FILE_APPEND_DATA
if mode&windows.O_APPEND != 0 {
access &^= windows.GENERIC_WRITE
access |= windows.FILE_APPEND_DATA
}
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE)
var sa *syscall.SecurityAttributes
if mode&syscall.O_CLOEXEC == 0 {
sharemode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE)
var sa *windows.SecurityAttributes
if mode&windows.O_CLOEXEC == 0 {
sa = makeInheritSa()
}
var createmode uint32
switch {
case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
createmode = syscall.CREATE_NEW
case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
createmode = syscall.CREATE_ALWAYS
case mode&syscall.O_CREAT == syscall.O_CREAT:
createmode = syscall.OPEN_ALWAYS
case mode&syscall.O_TRUNC == syscall.O_TRUNC:
createmode = syscall.TRUNCATE_EXISTING
case mode&(windows.O_CREAT|windows.O_EXCL) == (windows.O_CREAT | windows.O_EXCL):
createmode = windows.CREATE_NEW
case mode&(windows.O_CREAT|windows.O_TRUNC) == (windows.O_CREAT | windows.O_TRUNC):
createmode = windows.CREATE_ALWAYS
case mode&windows.O_CREAT == windows.O_CREAT:
createmode = windows.OPEN_ALWAYS
case mode&windows.O_TRUNC == windows.O_TRUNC:
createmode = windows.TRUNCATE_EXISTING
default:
createmode = syscall.OPEN_EXISTING
createmode = windows.OPEN_EXISTING
}
// Use FILE_FLAG_SEQUENTIAL_SCAN rather than FILE_ATTRIBUTE_NORMAL as implemented in golang.
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
const fileFlagSequentialScan = 0x08000000 // FILE_FLAG_SEQUENTIAL_SCAN
h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, fileFlagSequentialScan, 0)
h, e := windows.CreateFile(pathp, access, sharemode, sa, createmode, fileFlagSequentialScan, 0)
return h, e
}

View File

@ -21,6 +21,7 @@ import (
"syscall"
"unsafe"
"github.com/containerd/containerd/log"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
@ -30,9 +31,8 @@ func FMountat(dirfd uintptr, source, target, fstype string, flags uintptr, data
var (
sourceP, targetP, fstypeP, dataP *byte
pid uintptr
ws unix.WaitStatus
err error
errno syscall.Errno
errno, status syscall.Errno
)
sourceP, err = syscall.BytePtrFromString(source)
@ -60,37 +60,62 @@ func FMountat(dirfd uintptr, source, target, fstype string, flags uintptr, data
runtime.LockOSThread()
defer runtime.UnlockOSThread()
var pipefds [2]int
if err := syscall.Pipe2(pipefds[:], syscall.O_CLOEXEC); err != nil {
return errors.Wrap(err, "failed to open pipe")
}
defer func() {
// close both ends of the pipe in a deferred function, since open file
// descriptor table is shared with child
syscall.Close(pipefds[0])
syscall.Close(pipefds[1])
}()
pid, errno = forkAndMountat(dirfd,
uintptr(unsafe.Pointer(sourceP)),
uintptr(unsafe.Pointer(targetP)),
uintptr(unsafe.Pointer(fstypeP)),
flags,
uintptr(unsafe.Pointer(dataP)))
uintptr(unsafe.Pointer(dataP)),
pipefds[1],
)
if errno != 0 {
return errors.Wrap(errno, "failed to fork thread")
}
_, err = unix.Wait4(int(pid), &ws, 0, nil)
for err == syscall.EINTR {
_, err = unix.Wait4(int(pid), &ws, 0, nil)
}
defer func() {
_, err := unix.Wait4(int(pid), nil, 0, nil)
for err == syscall.EINTR {
_, err = unix.Wait4(int(pid), nil, 0, nil)
}
if err != nil {
return errors.Wrapf(err, "failed to find pid=%d process", pid)
}
if err != nil {
log.L.WithError(err).Debugf("failed to find pid=%d process", pid)
}
}()
errno = syscall.Errno(ws.ExitStatus())
_, _, errno = syscall.RawSyscall(syscall.SYS_READ,
uintptr(pipefds[0]),
uintptr(unsafe.Pointer(&status)),
unsafe.Sizeof(status))
if errno != 0 {
return errors.Wrap(errno, "failed to mount")
return errors.Wrap(errno, "failed to read pipe")
}
if status != 0 {
return errors.Wrap(status, "failed to mount")
}
return nil
}
// forkAndMountat will fork thread, change working dir and mount.
//
// precondition: the runtime OS thread must be locked.
func forkAndMountat(dirfd uintptr, source, target, fstype, flags, data uintptr) (pid uintptr, errno syscall.Errno) {
func forkAndMountat(dirfd uintptr, source, target, fstype, flags, data uintptr, pipefd int) (pid uintptr, errno syscall.Errno) {
// block signal during clone
beforeFork()
@ -114,6 +139,7 @@ func forkAndMountat(dirfd uintptr, source, target, fstype, flags, data uintptr)
_, _, errno = syscall.RawSyscall6(syscall.SYS_MOUNT, source, target, fstype, flags, data, 0)
childerr:
_, _, errno = syscall.RawSyscall(syscall.SYS_WRITE, uintptr(pipefd), uintptr(unsafe.Pointer(&errno)), unsafe.Sizeof(errno))
syscall.RawSyscall(syscall.SYS_EXIT, uintptr(errno), 0, 0)
panic("unreachable")
}

View File

@ -24,8 +24,6 @@ import (
"os"
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer/system"
)
// OOMScoreMaxKillable is the maximum score keeping the process killable by the oom killer
@ -40,7 +38,7 @@ func SetOOMScore(pid, score int) error {
}
defer f.Close()
if _, err = f.WriteString(strconv.Itoa(score)); err != nil {
if os.IsPermission(err) && (system.RunningInUserNS() || RunningUnprivileged()) {
if os.IsPermission(err) && (RunningInUserNS() || RunningUnprivileged()) {
return nil
}
return err

View File

@ -1,80 +0,0 @@
// +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 sys
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer/system"
)
const nanoSecondsPerSecond = 1e9
var clockTicksPerSecond = uint64(system.GetClockTicks())
// GetSystemCPUUsage returns the host system's cpu usage in
// nanoseconds. An error is returned if the format of the underlying
// file does not match.
//
// Uses /proc/stat defined by POSIX. Looks for the cpu
// statistics line and then sums up the first seven fields
// provided. See `man 5 proc` for details on specific field
// information.
func GetSystemCPUUsage() (uint64, error) {
var line string
f, err := os.Open("/proc/stat")
if err != nil {
return 0, err
}
bufReader := bufio.NewReaderSize(nil, 128)
defer func() {
bufReader.Reset(nil)
f.Close()
}()
bufReader.Reset(f)
err = nil
for err == nil {
line, err = bufReader.ReadString('\n')
if err != nil {
break
}
parts := strings.Fields(line)
switch parts[0] {
case "cpu":
if len(parts) < 8 {
return 0, fmt.Errorf("bad format of cpu stats")
}
var totalClockTicks uint64
for _, i := range parts[1:8] {
v, err := strconv.ParseUint(i, 10, 64)
if err != nil {
return 0, fmt.Errorf("error parsing cpu stats")
}
totalClockTicks += v
}
return (totalClockTicks * nanoSecondsPerSecond) /
clockTicksPerSecond, nil
}
}
return 0, fmt.Errorf("bad stats format")
}

View File

@ -1,69 +0,0 @@
// +build !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 sys
import (
"golang.org/x/sys/unix"
)
// Exit is the wait4 information from an exited process
type Exit struct {
Pid int
Status int
}
// Reap reaps all child processes for the calling process and returns their
// exit information
func Reap(wait bool) (exits []Exit, err error) {
var (
ws unix.WaitStatus
rus unix.Rusage
)
flag := unix.WNOHANG
if wait {
flag = 0
}
for {
pid, err := unix.Wait4(-1, &ws, flag, &rus)
if err != nil {
if err == unix.ECHILD {
return exits, nil
}
return exits, err
}
if pid <= 0 {
return exits, nil
}
exits = append(exits, Exit{
Pid: pid,
Status: exitStatus(ws),
})
}
}
const exitSignalOffset = 128
// exitStatus returns the correct exit status for a process based on if it
// was signaled or exited cleanly
func exitStatus(status unix.WaitStatus) int {
if status.Signaled() {
return exitSignalOffset + int(status.Signal())
}
return status.ExitStatus()
}

View File

@ -1,52 +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 sys
import (
"unsafe"
"golang.org/x/sys/unix"
)
// If arg2 is nonzero, set the "child subreaper" attribute of the
// calling process; if arg2 is zero, unset the attribute. When a
// process is marked as a child subreaper, all of the children
// that it creates, and their descendants, will be marked as
// having a subreaper. In effect, a subreaper fulfills the role
// of init(1) for its descendant processes. Upon termination of
// a process that is orphaned (i.e., its immediate parent has
// already terminated) and marked as having a subreaper, the
// nearest still living ancestor subreaper will receive a SIGCHLD
// signal and be able to wait(2) on the process to discover its
// termination status.
const setChildSubreaper = 36
// SetSubreaper sets the value i as the subreaper setting for the calling process
func SetSubreaper(i int) error {
return unix.Prctl(setChildSubreaper, uintptr(i), 0, 0, 0)
}
// GetSubreaper returns the subreaper setting for the calling process
func GetSubreaper() (int, error) {
var i uintptr
if err := unix.Prctl(unix.PR_GET_CHILD_SUBREAPER, uintptr(unsafe.Pointer(&i)), 0, 0, 0); err != nil {
return -1, err
}
return int(i), nil
}

View File

@ -0,0 +1,62 @@
/*
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 sys
import (
"bufio"
"fmt"
"os"
"sync"
)
var (
inUserNS bool
nsOnce sync.Once
)
// RunningInUserNS detects whether we are currently running in a user namespace.
// Originally copied from github.com/lxc/lxd/shared/util.go
func RunningInUserNS() bool {
nsOnce.Do(func() {
file, err := os.Open("/proc/self/uid_map")
if err != nil {
// This kernel-provided file only exists if user namespaces are supported
return
}
defer file.Close()
buf := bufio.NewReader(file)
l, _, err := buf.ReadLine()
if err != nil {
return
}
line := string(l)
var a, b, c int64
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
/*
* We assume we are in the initial user namespace if we have a full
* range - 4294967295 uids starting at uid 0.
*/
if a == 0 && b == 0 && c == 4294967295 {
return
}
inUserNS = true
})
return inUserNS
}

View File

@ -1,4 +1,4 @@
// +build !windows
// +build !linux
/*
Copyright The containerd Authors.
@ -16,11 +16,10 @@
limitations under the License.
*/
package syscallx
package sys
import "syscall"
// Readlink returns the destination of the named symbolic link.
func Readlink(path string, buf []byte) (n int, err error) {
return syscall.Readlink(path, buf)
// RunningInUserNS is a stub for non-Linux systems
// Always returns false
func RunningInUserNS() bool {
return false
}

View File

@ -23,7 +23,7 @@ var (
Package = "github.com/containerd/containerd"
// Version holds the complete version number. Filled in at linking time.
Version = "1.3.0+unknown"
Version = "1.4.0+unknown"
// Revision is filled with the VCS (e.g. git) revision being used to build
// the program at linking time.

View File

@ -1,16 +1,36 @@
Aaron Lehmann <aaron.lehmann@docker.com>
Akash Gupta <akagup@microsoft.com>
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
Akihiro Suda <suda.kyoto@gmail.com>
Andrew Pennebaker <apennebaker@datapipe.com>
Brandon Philips <brandon.philips@coreos.com>
Brian Goff <cpuguy83@gmail.com>
Christopher Jones <tophj@linux.vnet.ibm.com>
Daniel, Dao Quang Minh <dqminh89@gmail.com>
Darren Stahl <darst@microsoft.com>
Derek McGowan <derek@mcg.dev>
Derek McGowan <derek@mcgstyle.net>
Edward Pilatowicz <edward.pilatowicz@oracle.com>
Ian Campbell <ijc@docker.com>
Ivan Markin <sw@nogoegst.net>
Justin Cormack <justin.cormack@docker.com>
Justin Cummins <sul3n3t@gmail.com>
Kasper Fabæch Brandt <poizan@poizan.dk>
Kir Kolyshkin <kolyshkin@gmail.com>
Michael Crosby <crosbymichael@gmail.com>
Michael Wan <zirenwan@gmail.com>
Niels de Vos <ndevos@redhat.com>
Phil Estes <estesp@gmail.com>
Phil Estes <estesp@linux.vnet.ibm.com>
Sam Whited <sam@samwhited.com>
Shengjing Zhu <zhsj@debian.org>
Stephen J Day <stephen.day@docker.com>
Tibor Vass <tibor@docker.com>
Tobias Klauser <tklauser@distanz.ch>
Tom Faulhaber <tffaulha@amazon.com>
Tonis Tiigi <tonistiigi@gmail.com>
Trevor Porter <trkporter@ucdavis.edu>
Wei Fu <fuweid89@gmail.com>
Wilbert van de Ridder <wilbert.ridder@gmail.com>
Xiaodong Ye <xiaodongy@vmware.com>

View File

@ -1,112 +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 syscallx
import (
"syscall"
"unsafe"
)
type reparseDataBuffer struct {
ReparseTag uint32
ReparseDataLength uint16
Reserved uint16
// GenericReparseBuffer
reparseBuffer byte
}
type mountPointReparseBuffer struct {
SubstituteNameOffset uint16
SubstituteNameLength uint16
PrintNameOffset uint16
PrintNameLength uint16
PathBuffer [1]uint16
}
type symbolicLinkReparseBuffer struct {
SubstituteNameOffset uint16
SubstituteNameLength uint16
PrintNameOffset uint16
PrintNameLength uint16
Flags uint32
PathBuffer [1]uint16
}
const (
_IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
_SYMLINK_FLAG_RELATIVE = 1
)
// Readlink returns the destination of the named symbolic link.
func Readlink(path string, buf []byte) (n int, err error) {
fd, err := syscall.CreateFile(syscall.StringToUTF16Ptr(path), syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING,
syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
if err != nil {
return -1, err
}
defer syscall.CloseHandle(fd)
rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
var bytesReturned uint32
err = syscall.DeviceIoControl(fd, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
if err != nil {
return -1, err
}
rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0]))
var s string
switch rdb.ReparseTag {
case syscall.IO_REPARSE_TAG_SYMLINK:
data := (*symbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer))
p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))
s = syscall.UTF16ToString(p[data.SubstituteNameOffset/2 : (data.SubstituteNameOffset+data.SubstituteNameLength)/2])
if data.Flags&_SYMLINK_FLAG_RELATIVE == 0 {
if len(s) >= 4 && s[:4] == `\??\` {
s = s[4:]
switch {
case len(s) >= 2 && s[1] == ':': // \??\C:\foo\bar
// do nothing
case len(s) >= 4 && s[:4] == `UNC\`: // \??\UNC\foo\bar
s = `\\` + s[4:]
default:
// unexpected; do nothing
}
} else {
// unexpected; do nothing
}
}
case _IO_REPARSE_TAG_MOUNT_POINT:
data := (*mountPointReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer))
p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))
s = syscall.UTF16ToString(p[data.SubstituteNameOffset/2 : (data.SubstituteNameOffset+data.SubstituteNameLength)/2])
if len(s) >= 4 && s[:4] == `\??\` { // \??\C:\foo\bar
if len(s) < 48 || s[:11] != `\??\Volume{` {
s = s[4:]
}
} else {
// unexpected; do nothing
}
default:
// the path is not a symlink or junction but another type of reparse
// point
return -1, syscall.ENOENT
}
n = copy(buf, []byte(s))
return n, nil
}

View File

@ -1,128 +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 sysx
import (
"os"
"path/filepath"
"github.com/containerd/continuity/syscallx"
)
// Readlink returns the destination of the named symbolic link.
// If there is an error, it will be of type *PathError.
func Readlink(name string) (string, error) {
for len := 128; ; len *= 2 {
b := make([]byte, len)
n, e := fixCount(syscallx.Readlink(fixLongPath(name), b))
if e != nil {
return "", &os.PathError{Op: "readlink", Path: name, Err: e}
}
if n < len {
return string(b[0:n]), nil
}
}
}
// Many functions in package syscall return a count of -1 instead of 0.
// Using fixCount(call()) instead of call() corrects the count.
func fixCount(n int, err error) (int, error) {
if n < 0 {
n = 0
}
return n, err
}
// fixLongPath returns the extended-length (\\?\-prefixed) form of
// path when needed, in order to avoid the default 260 character file
// path limit imposed by Windows. If path is not easily converted to
// the extended-length form (for example, if path is a relative path
// or contains .. elements), or is short enough, fixLongPath returns
// path unmodified.
//
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
func fixLongPath(path string) string {
// Do nothing (and don't allocate) if the path is "short".
// Empirically (at least on the Windows Server 2013 builder),
// the kernel is arbitrarily okay with < 248 bytes. That
// matches what the docs above say:
// "When using an API to create a directory, the specified
// path cannot be so long that you cannot append an 8.3 file
// name (that is, the directory name cannot exceed MAX_PATH
// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
//
// The MSDN docs appear to say that a normal path that is 248 bytes long
// will work; empirically the path must be less then 248 bytes long.
if len(path) < 248 {
// Don't fix. (This is how Go 1.7 and earlier worked,
// not automatically generating the \\?\ form)
return path
}
// The extended form begins with \\?\, as in
// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
// The extended form disables evaluation of . and .. path
// elements and disables the interpretation of / as equivalent
// to \. The conversion here rewrites / to \ and elides
// . elements as well as trailing or duplicate separators. For
// simplicity it avoids the conversion entirely for relative
// paths or paths containing .. elements. For now,
// \\server\share paths are not converted to
// \\?\UNC\server\share paths because the rules for doing so
// are less well-specified.
if len(path) >= 2 && path[:2] == `\\` {
// Don't canonicalize UNC paths.
return path
}
if !filepath.IsAbs(path) {
// Relative path
return path
}
const prefix = `\\?`
pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
copy(pathbuf, prefix)
n := len(path)
r, w := 0, len(prefix)
for r < n {
switch {
case os.IsPathSeparator(path[r]):
// empty block
r++
case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
// /./
r++
case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
// /../ is currently unhandled
return path
default:
pathbuf[w] = '\\'
w++
for ; r < n && !os.IsPathSeparator(path[r]); r++ {
pathbuf[w] = path[r]
w++
}
}
}
// A drive's root directory needs a trailing \
if w == len(`\\?\c:`) {
pathbuf[w] = '\\'
w++
}
return string(pathbuf[:w])
}

View File

@ -20,7 +20,6 @@ package sysx
import (
"bytes"
"syscall"
"golang.org/x/sys/unix"
)
@ -66,60 +65,53 @@ func LGetxattr(path, attr string) ([]byte, error) {
return getxattrAll(path, attr, unix.Lgetxattr)
}
const defaultXattrBufferSize = 5
const defaultXattrBufferSize = 128
type listxattrFunc func(path string, dest []byte) (int, error)
func listxattrAll(path string, listFunc listxattrFunc) ([]string, error) {
var p []byte // nil on first execution
for {
n, err := listFunc(path, p) // first call gets buffer size.
buf := make([]byte, defaultXattrBufferSize)
n, err := listFunc(path, buf)
for err == unix.ERANGE {
// Buffer too small, use zero-sized buffer to get the actual size
n, err = listFunc(path, []byte{})
if err != nil {
return nil, err
}
if n > len(p) {
p = make([]byte, n)
continue
}
p = p[:n]
ps := bytes.Split(bytes.TrimSuffix(p, []byte{0}), []byte{0})
var entries []string
for _, p := range ps {
s := string(p)
if s != "" {
entries = append(entries, s)
}
}
return entries, nil
buf = make([]byte, n)
n, err = listFunc(path, buf)
}
if err != nil {
return nil, err
}
ps := bytes.Split(bytes.TrimSuffix(buf[:n], []byte{0}), []byte{0})
var entries []string
for _, p := range ps {
if len(p) > 0 {
entries = append(entries, string(p))
}
}
return entries, nil
}
type getxattrFunc func(string, string, []byte) (int, error)
func getxattrAll(path, attr string, getFunc getxattrFunc) ([]byte, error) {
p := make([]byte, defaultXattrBufferSize)
for {
n, err := getFunc(path, attr, p)
buf := make([]byte, defaultXattrBufferSize)
n, err := getFunc(path, attr, buf)
for err == unix.ERANGE {
// Buffer too small, use zero-sized buffer to get the actual size
n, err = getFunc(path, attr, []byte{})
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERANGE {
p = make([]byte, len(p)*2) // this can't be ideal.
continue // try again!
}
return nil, err
}
// realloc to correct size and repeat
if n > len(p) {
p = make([]byte, n)
continue
}
return p[:n], nil
buf = make([]byte, n)
n, err = getFunc(path, attr, buf)
}
if err != nil {
return nil, err
}
return buf[:n], nil
}

View File

@ -23,7 +23,7 @@ import (
"runtime"
)
var unsupported = errors.New("extended attributes unsupported on " + runtime.GOOS)
var errUnsupported = errors.New("extended attributes unsupported on " + runtime.GOOS)
// Listxattr calls syscall listxattr and reads all content
// and returns a string array
@ -33,17 +33,17 @@ func Listxattr(path string) ([]string, error) {
// Removexattr calls syscall removexattr
func Removexattr(path string, attr string) (err error) {
return unsupported
return errUnsupported
}
// Setxattr calls syscall setxattr
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
return unsupported
return errUnsupported
}
// Getxattr calls syscall getxattr
func Getxattr(path, attr string) ([]byte, error) {
return []byte{}, unsupported
return []byte{}, errUnsupported
}
// LListxattr lists xattrs, not following symlinks
@ -53,12 +53,12 @@ func LListxattr(path string) ([]string, error) {
// LRemovexattr removes an xattr, not following symlinks
func LRemovexattr(path string, attr string) (err error) {
return unsupported
return errUnsupported
}
// LSetxattr sets an xattr, not following symlinks
func LSetxattr(path string, attr string, data []byte, flags int) (err error) {
return unsupported
return errUnsupported
}
// LGetxattr gets an xattr, not following symlinks

2
vendor/github.com/containerd/typeurl/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
*.test
coverage.txt

191
vendor/github.com/containerd/typeurl/LICENSE generated vendored Normal file
View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
https://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
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
https://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.

19
vendor/github.com/containerd/typeurl/README.md generated vendored Normal file
View File

@ -0,0 +1,19 @@
# typeurl
[![Build Status](https://travis-ci.org/containerd/typeurl.svg?branch=master)](https://travis-ci.org/containerd/typeurl)
[![codecov](https://codecov.io/gh/containerd/typeurl/branch/master/graph/badge.svg)](https://codecov.io/gh/containerd/typeurl)
A Go package for managing the registration, marshaling, and unmarshaling of encoded types.
This package helps when types are sent over a GRPC API and marshaled as a [protobuf.Any](https://github.com/gogo/protobuf/blob/master/protobuf/google/protobuf/any.proto).
## Project details
**typeurl** 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.

83
vendor/github.com/containerd/typeurl/doc.go generated vendored Normal file
View File

@ -0,0 +1,83 @@
/*
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 typeurl
// Package typeurl assists with managing the registration, marshaling, and
// unmarshaling of types encoded as protobuf.Any.
//
// A protobuf.Any is a proto message that can contain any arbitrary data. It
// consists of two components, a TypeUrl and a Value, and its proto definition
// looks like this:
//
// message Any {
// string type_url = 1;
// bytes value = 2;
// }
//
// The TypeUrl is used to distinguish the contents from other proto.Any
// messages. This typeurl library manages these URLs to enable automagic
// marshaling and unmarshaling of the contents.
//
// For example, consider this go struct:
//
// type Foo struct {
// Field1 string
// Field2 string
// }
//
// To use typeurl, types must first be registered. This is typically done in
// the init function
//
// func init() {
// typeurl.Register(&Foo{}, "Foo")
// }
//
// This will register the type Foo with the url path "Foo". The arguments to
// Register are variadic, and are used to construct a url path. Consider this
// example, from the github.com/containerd/containerd/client package:
//
// func init() {
// const prefix = "types.containerd.io"
// // register TypeUrls for commonly marshaled external types
// major := strconv.Itoa(specs.VersionMajor)
// typeurl.Register(&specs.Spec{}, prefix, "opencontainers/runtime-spec", major, "Spec")
// // this function has more Register calls, which are elided.
// }
//
// This registers several types under a more complex url, which ends up mapping
// to `types.containerd.io/opencontainers/runtime-spec/1/Spec` (or some other
// value for major).
//
// Once a type is registered, it can be marshaled to a proto.Any message simply
// by calling `MarshalAny`, like this:
//
// foo := &Foo{Field1: "value1", Field2: "value2"}
// anyFoo, err := typeurl.MarshalAny(foo)
//
// MarshalAny will resolve the correct URL for the type. If the type in
// question implements the proto.Message interface, then it will be marshaled
// as a proto message. Otherwise, it will be marshaled as json. This means that
// typeurl will work on any arbitrary data, whether or not it has a proto
// definition, as long as it can be serialized to json.
//
// To unmarshal, the process is simply inverse:
//
// iface, err := typeurl.UnmarshalAny(anyFoo)
// foo := iface.(*Foo)
//
// The correct type is automatically chosen from the type registry, and the
// returned interface can be cast straight to that type.

8
vendor/github.com/containerd/typeurl/go.mod generated vendored Normal file
View File

@ -0,0 +1,8 @@
module github.com/containerd/typeurl
go 1.13
require (
github.com/gogo/protobuf v1.3.1
github.com/pkg/errors v0.9.1
)

7
vendor/github.com/containerd/typeurl/go.sum generated vendored Normal file
View File

@ -0,0 +1,7 @@
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

195
vendor/github.com/containerd/typeurl/types.go generated vendored Normal file
View File

@ -0,0 +1,195 @@
/*
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 typeurl
import (
"encoding/json"
"path"
"reflect"
"sync"
"github.com/gogo/protobuf/proto"
"github.com/gogo/protobuf/types"
"github.com/pkg/errors"
)
var (
mu sync.Mutex
registry = make(map[reflect.Type]string)
)
var ErrNotFound = errors.New("not found")
// Register a type with a base URL for JSON marshaling. When the MarshalAny and
// UnmarshalAny functions are called they will treat the Any type value as JSON.
// To use protocol buffers for handling the Any value the proto.Register
// function should be used instead of this function.
func Register(v interface{}, args ...string) {
var (
t = tryDereference(v)
p = path.Join(args...)
)
mu.Lock()
defer mu.Unlock()
if et, ok := registry[t]; ok {
if et != p {
panic(errors.Errorf("type registered with alternate path %q != %q", et, p))
}
return
}
registry[t] = p
}
// TypeURL returns the type url for a registered type.
func TypeURL(v interface{}) (string, error) {
mu.Lock()
u, ok := registry[tryDereference(v)]
mu.Unlock()
if !ok {
// fallback to the proto registry if it is a proto message
pb, ok := v.(proto.Message)
if !ok {
return "", errors.Wrapf(ErrNotFound, "type %s", reflect.TypeOf(v))
}
return proto.MessageName(pb), nil
}
return u, nil
}
// Is returns true if the type of the Any is the same as v.
func Is(any *types.Any, v interface{}) bool {
// call to check that v is a pointer
tryDereference(v)
url, err := TypeURL(v)
if err != nil {
return false
}
return any.TypeUrl == url
}
// MarshalAny marshals the value v into an any with the correct TypeUrl.
// If the provided object is already a proto.Any message, then it will be
// returned verbatim. If it is of type proto.Message, it will be marshaled as a
// protocol buffer. Otherwise, the object will be marshaled to json.
func MarshalAny(v interface{}) (*types.Any, error) {
var marshal func(v interface{}) ([]byte, error)
switch t := v.(type) {
case *types.Any:
// avoid reserializing the type if we have an any.
return t, nil
case proto.Message:
marshal = func(v interface{}) ([]byte, error) {
return proto.Marshal(t)
}
default:
marshal = json.Marshal
}
url, err := TypeURL(v)
if err != nil {
return nil, err
}
data, err := marshal(v)
if err != nil {
return nil, err
}
return &types.Any{
TypeUrl: url,
Value: data,
}, nil
}
// UnmarshalAny unmarshals the any type into a concrete type.
func UnmarshalAny(any *types.Any) (interface{}, error) {
return UnmarshalByTypeURL(any.TypeUrl, any.Value)
}
func UnmarshalByTypeURL(typeURL string, value []byte) (interface{}, error) {
return unmarshal(typeURL, value, nil)
}
func UnmarshalTo(any *types.Any, out interface{}) error {
return UnmarshalToByTypeURL(any.TypeUrl, any.Value, out)
}
func UnmarshalToByTypeURL(typeURL string, value []byte, out interface{}) error {
_, err := unmarshal(typeURL, value, out)
return err
}
func unmarshal(typeURL string, value []byte, v interface{}) (interface{}, error) {
t, err := getTypeByUrl(typeURL)
if err != nil {
return nil, err
}
if v == nil {
v = reflect.New(t.t).Interface()
} else {
// Validate interface type provided by client
vURL, err := TypeURL(v)
if err != nil {
return nil, err
}
if typeURL != vURL {
return nil, errors.Errorf("can't unmarshal type %q to output %q", typeURL, vURL)
}
}
if t.isProto {
err = proto.Unmarshal(value, v.(proto.Message))
} else {
err = json.Unmarshal(value, v)
}
return v, err
}
type urlType struct {
t reflect.Type
isProto bool
}
func getTypeByUrl(url string) (urlType, error) {
for t, u := range registry {
if u == url {
return urlType{
t: t,
}, nil
}
}
// fallback to proto registry
t := proto.MessageType(url)
if t != nil {
return urlType{
// get the underlying Elem because proto returns a pointer to the type
t: t.Elem(),
isProto: true,
}, nil
}
return urlType{}, errors.Wrapf(ErrNotFound, "type with url %s", url)
}
func tryDereference(v interface{}) reflect.Type {
t := reflect.TypeOf(v)
if t.Kind() == reflect.Ptr {
// require check of pointer but dereference to register
return t.Elem()
}
panic("v is not a pointer to a type")
}