vendor: github.com/containerd/containerd v1.5.2

full diff: 19ee068f93...v1.5.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2021-06-04 16:32:37 +02:00
parent 989cd6e8f6
commit 7c1c123555
No known key found for this signature in database
GPG key ID: 76698F39D527CE8C
76 changed files with 1554 additions and 742 deletions

View file

@ -421,7 +421,7 @@ func (pm *Manager) Push(ctx context.Context, name string, metaHeader http.Header
// Make sure we can authenticate the request since the auth scope for plugin repos is different than a normal repo.
ctx = docker.WithScope(ctx, scope(ref, true))
if err := remotes.PushContent(ctx, pusher, desc, pm.blobStore, nil, func(h images.Handler) images.Handler {
if err := remotes.PushContent(ctx, pusher, desc, pm.blobStore, nil, nil, func(h images.Handler) images.Handler {
return images.Handlers(progressHandler, h)
}); err != nil {
// Try fallback to http.
@ -433,7 +433,7 @@ func (pm *Manager) Push(ctx context.Context, name string, metaHeader http.Header
pusher, _ := resolver.Pusher(ctx, ref.String())
if pusher != nil {
logrus.WithField("ref", ref).Debug("Re-attmpting push with http-fallback")
err2 := remotes.PushContent(ctx, pusher, desc, pm.blobStore, nil, func(h images.Handler) images.Handler {
err2 := remotes.PushContent(ctx, pusher, desc, pm.blobStore, nil, nil, func(h images.Handler) images.Handler {
return images.Handlers(progressHandler, h)
})
if err2 == nil {

View file

@ -132,7 +132,7 @@ github.com/googleapis/gax-go bd5b16380fd03dc758d11cef74ba
google.golang.org/genproto 3f1135a288c9a07e340ae8ba4cc6c7065a3160e8
# containerd
github.com/containerd/containerd 19ee068f93c91f7b9b2a858457f1af2cabc7bc06 # master (v1.5.0-dev)
github.com/containerd/containerd 36cc874494a56a253cd181a1a685b44b58a2e34a # v1.5.2
github.com/containerd/fifo 650e8a8a179d040123db61f016cb133143e7a581 # v1.0.0
github.com/containerd/continuity bce1c3f9669b6f3e7f6656ee715b0b4d75fa64a6 # v0.1.0
github.com/containerd/cgroups b9de8a2212026c07cec67baf3323f1fc0121e048 # v1.0.1

View file

@ -0,0 +1,88 @@
// Package ociwclayer provides functions for importing and exporting Windows
// container layers from and to their OCI tar representation.
package ociwclayer
import (
"archive/tar"
"context"
"io"
"path/filepath"
"github.com/Microsoft/go-winio/backuptar"
"github.com/Microsoft/hcsshim"
)
var driverInfo = hcsshim.DriverInfo{}
// ExportLayerToTar writes an OCI layer tar stream from the provided on-disk layer.
// The caller must specify the parent layers, if any, ordered from lowest to
// highest layer.
//
// The layer will be mounted for this process, so the caller should ensure that
// it is not currently mounted.
func ExportLayerToTar(ctx context.Context, w io.Writer, path string, parentLayerPaths []string) error {
err := hcsshim.ActivateLayer(driverInfo, path)
if err != nil {
return err
}
defer func() {
_ = hcsshim.DeactivateLayer(driverInfo, path)
}()
// Prepare and unprepare the layer to ensure that it has been initialized.
err = hcsshim.PrepareLayer(driverInfo, path, parentLayerPaths)
if err != nil {
return err
}
err = hcsshim.UnprepareLayer(driverInfo, path)
if err != nil {
return err
}
r, err := hcsshim.NewLayerReader(driverInfo, path, parentLayerPaths)
if err != nil {
return err
}
err = writeTarFromLayer(ctx, r, w)
cerr := r.Close()
if err != nil {
return err
}
return cerr
}
func writeTarFromLayer(ctx context.Context, r hcsshim.LayerReader, w io.Writer) error {
t := tar.NewWriter(w)
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
name, size, fileInfo, err := r.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
if fileInfo == nil {
// Write a whiteout file.
hdr := &tar.Header{
Name: filepath.ToSlash(filepath.Join(filepath.Dir(name), whiteoutPrefix+filepath.Base(name))),
}
err := t.WriteHeader(hdr)
if err != nil {
return err
}
} else {
err = backuptar.WriteTarFileFromBackupStream(t, r, name, size, fileInfo)
if err != nil {
return err
}
}
}
return t.Close()
}

View file

@ -0,0 +1,148 @@
package ociwclayer
import (
"archive/tar"
"bufio"
"context"
"io"
"os"
"path"
"path/filepath"
"strings"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/go-winio/backuptar"
"github.com/Microsoft/hcsshim"
)
const whiteoutPrefix = ".wh."
var (
// mutatedFiles is a list of files that are mutated by the import process
// and must be backed up and restored.
mutatedFiles = map[string]string{
"UtilityVM/Files/EFI/Microsoft/Boot/BCD": "bcd.bak",
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG": "bcd.log.bak",
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG1": "bcd.log1.bak",
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG2": "bcd.log2.bak",
}
)
// ImportLayerFromTar reads a layer from an OCI layer tar stream and extracts it to the
// specified path. The caller must specify the parent layers, if any, ordered
// from lowest to highest layer.
//
// The caller must ensure that the thread or process has acquired backup and
// restore privileges.
//
// This function returns the total size of the layer's files, in bytes.
func ImportLayerFromTar(ctx context.Context, r io.Reader, path string, parentLayerPaths []string) (int64, error) {
err := os.MkdirAll(path, 0)
if err != nil {
return 0, err
}
w, err := hcsshim.NewLayerWriter(hcsshim.DriverInfo{}, path, parentLayerPaths)
if err != nil {
return 0, err
}
n, err := writeLayerFromTar(ctx, r, w, path)
cerr := w.Close()
if err != nil {
return 0, err
}
if cerr != nil {
return 0, cerr
}
return n, nil
}
func writeLayerFromTar(ctx context.Context, r io.Reader, w hcsshim.LayerWriter, root string) (int64, error) {
t := tar.NewReader(r)
hdr, err := t.Next()
totalSize := int64(0)
buf := bufio.NewWriter(nil)
for err == nil {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
base := path.Base(hdr.Name)
if strings.HasPrefix(base, whiteoutPrefix) {
name := path.Join(path.Dir(hdr.Name), base[len(whiteoutPrefix):])
err = w.Remove(filepath.FromSlash(name))
if err != nil {
return 0, err
}
hdr, err = t.Next()
} else if hdr.Typeflag == tar.TypeLink {
err = w.AddLink(filepath.FromSlash(hdr.Name), filepath.FromSlash(hdr.Linkname))
if err != nil {
return 0, err
}
hdr, err = t.Next()
} else {
var (
name string
size int64
fileInfo *winio.FileBasicInfo
)
name, size, fileInfo, err = backuptar.FileInfoFromHeader(hdr)
if err != nil {
return 0, err
}
err = w.Add(filepath.FromSlash(name), fileInfo)
if err != nil {
return 0, err
}
hdr, err = writeBackupStreamFromTarAndSaveMutatedFiles(buf, w, t, hdr, root)
totalSize += size
}
}
if err != io.EOF {
return 0, err
}
return totalSize, nil
}
// writeBackupStreamFromTarAndSaveMutatedFiles reads data from a tar stream and
// writes it to a backup stream, and also saves any files that will be mutated
// by the import layer process to a backup location.
func writeBackupStreamFromTarAndSaveMutatedFiles(buf *bufio.Writer, w io.Writer, t *tar.Reader, hdr *tar.Header, root string) (nextHdr *tar.Header, err error) {
var bcdBackup *os.File
var bcdBackupWriter *winio.BackupFileWriter
if backupPath, ok := mutatedFiles[hdr.Name]; ok {
bcdBackup, err = os.Create(filepath.Join(root, backupPath))
if err != nil {
return nil, err
}
defer func() {
cerr := bcdBackup.Close()
if err == nil {
err = cerr
}
}()
bcdBackupWriter = winio.NewBackupFileWriter(bcdBackup, false)
defer func() {
cerr := bcdBackupWriter.Close()
if err == nil {
err = cerr
}
}()
buf.Reset(io.MultiWriter(w, bcdBackupWriter))
} else {
buf.Reset(w)
}
defer func() {
ferr := buf.Flush()
if err == nil {
err = ferr
}
}()
return backuptar.WriteBackupStreamFromTarFile(buf, t, hdr)
}

View file

@ -49,7 +49,8 @@ Please be aware: nightly builds might have critical bugs, it's not recommended f
Runtime requirements for containerd are very minimal. Most interactions with
the Linux and Windows container feature sets are handled via [runc](https://github.com/opencontainers/runc) and/or
OS-specific libraries (e.g. [hcsshim](https://github.com/Microsoft/hcsshim) for Microsoft). The current required version of `runc` is always listed in [RUNC.md](/docs/RUNC.md).
OS-specific libraries (e.g. [hcsshim](https://github.com/Microsoft/hcsshim) for Microsoft).
The current required version of `runc` is described in [RUNC.md](docs/RUNC.md).
There are specific features
used by containerd core code and snapshotters that will require a minimum kernel

View file

@ -63,13 +63,34 @@ func Diff(ctx context.Context, a, b string) io.ReadCloser {
}
// WriteDiff writes a tar stream of the computed difference between the
// provided directories.
// provided paths.
//
// Produces a tar using OCI style file markers for deletions. Deleted
// files will be prepended with the prefix ".wh.". This style is
// based off AUFS whiteouts.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md
func WriteDiff(ctx context.Context, w io.Writer, a, b string) error {
func WriteDiff(ctx context.Context, w io.Writer, a, b string, opts ...WriteDiffOpt) error {
var options WriteDiffOptions
for _, opt := range opts {
if err := opt(&options); err != nil {
return errors.Wrap(err, "failed to apply option")
}
}
if options.writeDiffFunc == nil {
options.writeDiffFunc = writeDiffNaive
}
return options.writeDiffFunc(ctx, w, a, b, options)
}
// writeDiffNaive writes a tar stream of the computed difference between the
// provided directories on disk.
//
// Produces a tar using OCI style file markers for deletions. Deleted
// files will be prepended with the prefix ".wh.". This style is
// based off AUFS whiteouts.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md
func writeDiffNaive(ctx context.Context, w io.Writer, a, b string, _ WriteDiffOptions) error {
cw := newChangeWriter(w, b)
err := fs.Changes(ctx, a, b, cw.HandleChange)
if err != nil {

View file

@ -29,7 +29,7 @@ func mknod(path string, mode uint32, dev uint64) error {
// lsetxattrCreate wraps unix.Lsetxattr with FreeBSD-specific flags and errors
func lsetxattrCreate(link string, attr string, data []byte) error {
err := unix.Lsetxattr(link, attr, data, 0)
if err == unix.ENOTSUP|| err == unix.EEXIST {
if err == unix.ENOTSUP || err == unix.EEXIST {
return nil
}
return err

View file

@ -73,3 +73,13 @@ func WithParents(p []string) ApplyOpt {
return nil
}
}
// WriteDiffOptions provides additional options for a WriteDiff operation
type WriteDiffOptions struct {
ParentLayers []string // Windows needs the full list of parent layers
writeDiffFunc func(context.Context, io.Writer, string, string, WriteDiffOptions) error
}
// WriteDiffOpt allows setting mutable archive write properties on creation
type WriteDiffOpt func(options *WriteDiffOptions) error

View file

@ -18,6 +18,19 @@
package archive
import (
"context"
"io"
"github.com/Microsoft/hcsshim/pkg/ociwclayer"
)
// applyWindowsLayer applies a tar stream of an OCI style diff tar of a Windows layer
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func applyWindowsLayer(ctx context.Context, root string, r io.Reader, options ApplyOptions) (size int64, err error) {
return ociwclayer.ImportLayerFromTar(ctx, r, root, options.Parents)
}
// AsWindowsContainerLayer indicates that the tar stream to apply is that of
// a Windows Container Layer. The caller must be holding SeBackupPrivilege and
// SeRestorePrivilege.
@ -27,3 +40,33 @@ func AsWindowsContainerLayer() ApplyOpt {
return nil
}
}
// writeDiffWindowsLayers writes a tar stream of the computed difference between the
// provided Windows layers
//
// Produces a tar using OCI style file markers for deletions. Deleted
// files will be prepended with the prefix ".wh.". This style is
// based off AUFS whiteouts.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md
func writeDiffWindowsLayers(ctx context.Context, w io.Writer, _, layer string, options WriteDiffOptions) error {
return ociwclayer.ExportLayerToTar(ctx, w, layer, options.ParentLayers)
}
// AsWindowsContainerLayerPair indicates that the paths to diff are a pair of
// Windows Container Layers. The caller must be holding SeBackupPrivilege.
func AsWindowsContainerLayerPair() WriteDiffOpt {
return func(options *WriteDiffOptions) error {
options.writeDiffFunc = writeDiffWindowsLayers
return nil
}
}
// WithParentLayers provides the Windows Container Layers that are the parents
// of the target (right-hand, "upper") layer, if any. The source (left-hand, "lower")
// layer passed to WriteDiff should be "" in this case.
func WithParentLayers(p []string) WriteDiffOpt {
return func(options *WriteDiffOptions) error {
options.ParentLayers = p
return nil
}
}

View file

@ -24,7 +24,7 @@ import (
"strings"
"syscall"
"github.com/containerd/containerd/sys"
"github.com/containerd/containerd/pkg/userns"
"github.com/containerd/continuity/fs"
"github.com/containerd/continuity/sysx"
"github.com/pkg/errors"
@ -87,7 +87,7 @@ func skipFile(hdr *tar.Header) bool {
switch hdr.Typeflag {
case tar.TypeBlock, tar.TypeChar:
// cannot create a device if running in user namespace
return sys.RunningInUserNS()
return userns.RunningInUserNS()
default:
return false
}

View file

@ -20,33 +20,14 @@ package archive
import (
"archive/tar"
"bufio"
"context"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"github.com/Microsoft/go-winio"
"github.com/Microsoft/go-winio/backuptar"
"github.com/Microsoft/hcsshim"
"github.com/containerd/containerd/sys"
"github.com/pkg/errors"
)
var (
// mutatedFiles is a list of files that are mutated by the import process
// and must be backed up and restored.
mutatedFiles = map[string]string{
"UtilityVM/Files/EFI/Microsoft/Boot/BCD": "bcd.bak",
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG": "bcd.log.bak",
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG1": "bcd.log1.bak",
"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG2": "bcd.log2.bak",
}
)
// tarName returns platform-specific filepath
// to canonical posix-style path for tar archival. p is relative
// path.
@ -141,121 +122,3 @@ func copyDirInfo(fi os.FileInfo, path string) error {
func copyUpXAttrs(dst, src string) error {
return nil
}
// applyWindowsLayer applies a tar stream of an OCI style diff tar of a Windows
// layer using the hcsshim layer writer and backup streams.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func applyWindowsLayer(ctx context.Context, root string, r io.Reader, options ApplyOptions) (size int64, err error) {
home, id := filepath.Split(root)
info := hcsshim.DriverInfo{
HomeDir: home,
}
w, err := hcsshim.NewLayerWriter(info, id, options.Parents)
if err != nil {
return 0, err
}
defer func() {
if err2 := w.Close(); err2 != nil {
// This error should not be discarded as a failure here
// could result in an invalid layer on disk
if err == nil {
err = err2
}
}
}()
tr := tar.NewReader(r)
buf := bufio.NewWriter(nil)
hdr, nextErr := tr.Next()
// Iterate through the files in the archive.
for {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
if nextErr == io.EOF {
// end of tar archive
break
}
if nextErr != nil {
return 0, nextErr
}
// Note: path is used instead of filepath to prevent OS specific handling
// of the tar path
base := path.Base(hdr.Name)
if strings.HasPrefix(base, whiteoutPrefix) {
dir := path.Dir(hdr.Name)
originalBase := base[len(whiteoutPrefix):]
originalPath := path.Join(dir, originalBase)
if err := w.Remove(filepath.FromSlash(originalPath)); err != nil {
return 0, err
}
hdr, nextErr = tr.Next()
} else if hdr.Typeflag == tar.TypeLink {
err := w.AddLink(filepath.FromSlash(hdr.Name), filepath.FromSlash(hdr.Linkname))
if err != nil {
return 0, err
}
hdr, nextErr = tr.Next()
} else {
name, fileSize, fileInfo, err := backuptar.FileInfoFromHeader(hdr)
if err != nil {
return 0, err
}
if err := w.Add(filepath.FromSlash(name), fileInfo); err != nil {
return 0, err
}
size += fileSize
hdr, nextErr = tarToBackupStreamWithMutatedFiles(buf, w, tr, hdr, root)
}
}
return
}
// tarToBackupStreamWithMutatedFiles reads data from a tar stream and
// writes it to a backup stream, and also saves any files that will be mutated
// by the import layer process to a backup location.
func tarToBackupStreamWithMutatedFiles(buf *bufio.Writer, w io.Writer, t *tar.Reader, hdr *tar.Header, root string) (nextHdr *tar.Header, err error) {
var (
bcdBackup *os.File
bcdBackupWriter *winio.BackupFileWriter
)
if backupPath, ok := mutatedFiles[hdr.Name]; ok {
bcdBackup, err = os.Create(filepath.Join(root, backupPath))
if err != nil {
return nil, err
}
defer func() {
cerr := bcdBackup.Close()
if err == nil {
err = cerr
}
}()
bcdBackupWriter = winio.NewBackupFileWriter(bcdBackup, false)
defer func() {
cerr := bcdBackupWriter.Close()
if err == nil {
err = cerr
}
}()
buf.Reset(io.MultiWriter(w, bcdBackupWriter))
} else {
buf.Reset(w)
}
defer func() {
ferr := buf.Flush()
if err == nil {
err = ferr
}
}()
return backuptar.WriteBackupStreamFromTarFile(buf, t, hdr)
}

View file

@ -80,7 +80,7 @@ type FIFOSet struct {
// Close the FIFOSet
func (f *FIFOSet) Close() error {
if f.close != nil {
if f != nil && f.close != nil {
return f.close()
}
return nil

View file

@ -103,38 +103,36 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
}, nil
}
func openFifos(ctx context.Context, fifos *FIFOSet) (pipes, error) {
var err error
func openFifos(ctx context.Context, fifos *FIFOSet) (f pipes, retErr error) {
defer func() {
if err != nil {
if retErr != nil {
fifos.Close()
}
}()
var f pipes
if fifos.Stdin != "" {
if f.Stdin, err = fifo.OpenFifo(ctx, fifos.Stdin, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
return f, errors.Wrapf(err, "failed to open stdin fifo")
if f.Stdin, retErr = fifo.OpenFifo(ctx, fifos.Stdin, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {
return f, errors.Wrapf(retErr, "failed to open stdin fifo")
}
defer func() {
if err != nil && f.Stdin != nil {
if retErr != nil && f.Stdin != nil {
f.Stdin.Close()
}
}()
}
if fifos.Stdout != "" {
if f.Stdout, err = fifo.OpenFifo(ctx, fifos.Stdout, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
return f, errors.Wrapf(err, "failed to open stdout fifo")
if f.Stdout, retErr = fifo.OpenFifo(ctx, fifos.Stdout, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {
return f, errors.Wrapf(retErr, "failed to open stdout fifo")
}
defer func() {
if err != nil && f.Stdout != nil {
if retErr != nil && f.Stdout != nil {
f.Stdout.Close()
}
}()
}
if !fifos.Terminal && fifos.Stderr != "" {
if f.Stderr, err = fifo.OpenFifo(ctx, fifos.Stderr, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
return f, errors.Wrapf(err, "failed to open stderr fifo")
if f.Stderr, retErr = fifo.OpenFifo(ctx, fifos.Stderr, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); retErr != nil {
return f, errors.Wrapf(retErr, "failed to open stderr fifo")
}
}
return f, nil

View file

@ -20,7 +20,6 @@ import (
"context"
"fmt"
"io"
"net"
winio "github.com/Microsoft/go-winio"
"github.com/containerd/containerd/log"
@ -43,22 +42,21 @@ func NewFIFOSetInDir(_, id string, terminal bool) (*FIFOSet, error) {
}, nil), nil
}
func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
var (
set []io.Closer
)
func copyIO(fifos *FIFOSet, ioset *Streams) (_ *cio, retErr error) {
cios := &cio{config: fifos.Config}
defer func() {
if retErr != nil {
_ = cios.Close()
}
}()
if fifos.Stdin != "" {
l, err := winio.ListenPipe(fifos.Stdin, nil)
if err != nil {
return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Stdin)
}
defer func(l net.Listener) {
if err != nil {
l.Close()
}
}(l)
set = append(set, l)
cios.closers = append(cios.closers, l)
go func() {
c, err := l.Accept()
@ -81,12 +79,7 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
if err != nil {
return nil, errors.Wrapf(err, "failed to create stdout pipe %s", fifos.Stdout)
}
defer func(l net.Listener) {
if err != nil {
l.Close()
}
}(l)
set = append(set, l)
cios.closers = append(cios.closers, l)
go func() {
c, err := l.Accept()
@ -109,12 +102,7 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
if err != nil {
return nil, errors.Wrapf(err, "failed to create stderr pipe %s", fifos.Stderr)
}
defer func(l net.Listener) {
if err != nil {
l.Close()
}
}(l)
set = append(set, l)
cios.closers = append(cios.closers, l)
go func() {
c, err := l.Accept()
@ -132,7 +120,7 @@ func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
}()
}
return &cio{config: fifos.Config, closers: set}, nil
return cios, nil
}
// NewDirectIO returns an IO implementation that exposes the IO streams as io.ReadCloser

View file

@ -63,6 +63,7 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"golang.org/x/sync/semaphore"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/health/grpc_health_v1"
@ -226,6 +227,11 @@ func (c *Client) Reconnect() error {
return nil
}
// Runtime returns the name of the runtime being used
func (c *Client) Runtime() string {
return c.runtime
}
// IsServing returns true if the client can successfully connect to the
// containerd daemon and the healthcheck service returns the SERVING
// response.
@ -350,6 +356,9 @@ type RemoteContext struct {
// MaxConcurrentDownloads is the max concurrent content downloads for each pull.
MaxConcurrentDownloads int
// MaxConcurrentUploadedLayers is the max concurrent uploaded layers for each push.
MaxConcurrentUploadedLayers int
// AllMetadata downloads all manifests and known-configuration files
AllMetadata bool
@ -458,7 +467,12 @@ func (c *Client) Push(ctx context.Context, ref string, desc ocispec.Descriptor,
wrapper = pushCtx.HandlerWrapper
}
return remotes.PushContent(ctx, pusher, desc, c.ContentStore(), pushCtx.PlatformMatcher, wrapper)
var limiter *semaphore.Weighted
if pushCtx.MaxConcurrentUploadedLayers > 0 {
limiter = semaphore.NewWeighted(int64(pushCtx.MaxConcurrentUploadedLayers))
}
return remotes.PushContent(ctx, pusher, desc, c.ContentStore(), limiter, pushCtx.PlatformMatcher, wrapper)
}
// GetImage returns an existing image
@ -715,10 +729,12 @@ func (c *Client) Version(ctx context.Context) (Version, error) {
}, nil
}
// ServerInfo represents the introspected server information
type ServerInfo struct {
UUID string
}
// Server returns server information from the introspection service
func (c *Client) Server(ctx context.Context) (ServerInfo, error) {
c.connMu.Lock()
if c.conn == nil {
@ -784,6 +800,8 @@ func CheckRuntime(current, expected string) bool {
return true
}
// GetSnapshotterSupportedPlatforms returns a platform matchers which represents the
// supported platforms for the given snapshotters
func (c *Client) GetSnapshotterSupportedPlatforms(ctx context.Context, snapshotterName string) (platforms.MatchComparer, error) {
filters := []string{fmt.Sprintf("type==%s, id==%s", plugin.SnapshotPlugin, snapshotterName)}
in := c.IntrospectionService()

View file

@ -228,6 +228,14 @@ func WithMaxConcurrentDownloads(max int) RemoteOpt {
}
}
// WithMaxConcurrentUploadedLayers sets max concurrent uploaded layer limit.
func WithMaxConcurrentUploadedLayers(max int) RemoteOpt {
return func(client *Client, c *RemoteContext) error {
c.MaxConcurrentUploadedLayers = max
return nil
}
}
// WithAllMetadata downloads all manifests and known-configuration files
func WithAllMetadata() RemoteOpt {
return func(_ *Client, c *RemoteContext) error {

View file

@ -32,7 +32,7 @@ import (
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/oci"
"github.com/containerd/containerd/runtime/v2/runc/options"
"github.com/containerd/containerd/sys"
"github.com/containerd/fifo"
"github.com/containerd/typeurl"
prototypes "github.com/gogo/protobuf/types"
ver "github.com/opencontainers/image-spec/specs-go"
@ -435,12 +435,12 @@ func loadFifos(response *tasks.GetResponse) *cio.FIFOSet {
err error
dirs = map[string]struct{}{}
)
for _, fifo := range fifos {
if isFifo, _ := sys.IsFifo(fifo); isFifo {
if rerr := os.Remove(fifo); err == nil {
for _, f := range fifos {
if isFifo, _ := fifo.IsFifo(f); isFifo {
if rerr := os.Remove(f); err == nil {
err = rerr
}
dirs[filepath.Dir(fifo)] = struct{}{}
dirs[filepath.Dir(f)] = struct{}{}
}
}
for dir := range dirs {

View file

@ -22,7 +22,7 @@ import (
"github.com/containerd/containerd/filters"
)
// AdoptInfo returns `filters.Adaptor` that handles `content.Info`.
// AdaptInfo returns `filters.Adaptor` that handles `content.Info`.
func AdaptInfo(info Info) filters.Adaptor {
return filters.AdapterFunc(func(fieldpath []string) (string, bool) {
if len(fieldpath) == 0 {

View file

@ -37,7 +37,7 @@ type Provider interface {
// ReaderAt only requires desc.Digest to be set.
// Other fields in the descriptor may be used internally for resolving
// the location of the actual data.
ReaderAt(ctx context.Context, dec ocispec.Descriptor) (ReaderAt, error)
ReaderAt(ctx context.Context, desc ocispec.Descriptor) (ReaderAt, error)
}
// Ingester writes content

View file

@ -18,6 +18,11 @@ package local
import (
"os"
"github.com/pkg/errors"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
)
// readerat implements io.ReaderAt in a completely stateless manner by opening
@ -27,6 +32,29 @@ type sizeReaderAt struct {
fp *os.File
}
// OpenReader creates ReaderAt from a file
func OpenReader(p string) (content.ReaderAt, error) {
fi, err := os.Stat(p)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
return nil, errors.Wrap(errdefs.ErrNotFound, "blob not found")
}
fp, err := os.Open(p)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
return nil, errors.Wrap(errdefs.ErrNotFound, "blob not found")
}
return sizeReaderAt{size: fi.Size(), fp: fp}, nil
}
func (ra sizeReaderAt) ReadAt(p []byte, offset int64) (int, error) {
return ra.fp.ReadAt(p, offset)
}

View file

@ -131,25 +131,13 @@ func (s *store) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.
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) {
return nil, err
}
return nil, errors.Wrapf(errdefs.ErrNotFound, "blob %s expected at %s", desc.Digest, p)
reader, err := OpenReader(p)
if err != nil {
return nil, errors.Wrapf(err, "blob %s expected at %s", desc.Digest, p)
}
fp, err := os.Open(p)
if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
return nil, errors.Wrapf(errdefs.ErrNotFound, "blob %s expected at %s", desc.Digest, p)
}
return sizeReaderAt{size: fi.Size(), fp: fp}, nil
return reader, nil
}
// Delete removes a blob by its digest.
@ -477,7 +465,6 @@ func (s *store) Writer(ctx context.Context, opts ...content.WriterOpt) (content.
}
var lockErr error
for count := uint64(0); count < 10; count++ {
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1<<count)))
if err := tryLock(wOpts.Ref); err != nil {
if !errdefs.IsUnavailable(err) {
return nil, err
@ -488,6 +475,7 @@ func (s *store) Writer(ctx context.Context, opts ...content.WriterOpt) (content.
lockErr = nil
break
}
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1<<count)))
}
if lockErr != nil {

View file

@ -97,7 +97,14 @@ func (rw *remoteWriter) Write(p []byte) (n int, err error) {
return
}
func (rw *remoteWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
func (rw *remoteWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) (err error) {
defer func() {
err1 := rw.Close()
if err == nil {
err = err1
}
}()
var base content.Info
for _, opt := range opts {
if err := opt(&base); err != nil {

View file

@ -34,4 +34,6 @@ const (
DefaultFIFODir = "/run/containerd/fifo"
// DefaultRuntime is the default linux runtime
DefaultRuntime = "io.containerd.runc.v2"
// DefaultConfigDir is the default location for config files.
DefaultConfigDir = "/etc/containerd"
)

View file

@ -30,6 +30,9 @@ var (
// DefaultStateDir is the default location used by containerd to store
// transient data
DefaultStateDir = filepath.Join(os.Getenv("ProgramData"), "containerd", "state")
// DefaultConfigDir is the default location for config files.
DefaultConfigDir = filepath.Join(os.Getenv("programfiles"), "containerd")
)
const (

View file

@ -168,6 +168,10 @@ func (c *compressedProcessor) Close() error {
return c.rc.Close()
}
// BinaryHandler creates a new stream processor handler which calls out to the given binary.
// The id is used to identify the stream processor and allows the caller to send
// payloads specific for that stream processor (i.e. decryption keys for decrypt stream processor).
// The binary will be called for the provided mediaTypes and return the given media type.
func BinaryHandler(id, returnsMediaType string, mediaTypes []string, path string, args, env []string) Handler {
set := make(map[string]struct{}, len(mediaTypes))
for _, m := range mediaTypes {

View file

@ -1,26 +1,24 @@
module github.com/containerd/containerd
go 1.15
go 1.16
require (
github.com/BurntSushi/toml v0.3.1
github.com/Microsoft/go-winio v0.4.16
github.com/Microsoft/hcsshim v0.8.14
github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4
github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e
github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102
github.com/containerd/console v1.0.1
github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7
github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c
github.com/containerd/go-cni v1.0.1
github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328
github.com/containerd/imgcrypt v1.0.1
github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164
github.com/Microsoft/go-winio v0.4.17
github.com/Microsoft/hcsshim v0.8.16
github.com/containerd/aufs v1.0.0
github.com/containerd/btrfs v1.0.0
github.com/containerd/cgroups v1.0.1
github.com/containerd/console v1.0.2
github.com/containerd/continuity v0.1.0
github.com/containerd/fifo v1.0.0
github.com/containerd/go-cni v1.0.2
github.com/containerd/go-runc v1.0.0
github.com/containerd/imgcrypt v1.1.1
github.com/containerd/nri v0.1.0
github.com/containerd/ttrpc v1.0.2
github.com/containerd/typeurl v1.0.1
github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2
github.com/containernetworking/plugins v0.8.6
github.com/containerd/typeurl v1.0.2
github.com/containerd/zfs v1.0.0
github.com/containernetworking/plugins v0.9.1
github.com/coreos/go-systemd/v22 v22.1.0
github.com/davecgh/go-spew v1.1.1
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c
@ -31,43 +29,50 @@ require (
github.com/gogo/googleapis v1.4.0
github.com/gogo/protobuf v1.3.2
github.com/golang/protobuf v1.4.3
github.com/google/go-cmp v0.5.2
github.com/google/uuid v1.1.2
github.com/google/go-cmp v0.5.4
github.com/google/uuid v1.2.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/hashicorp/go-multierror v1.0.0
github.com/imdario/mergo v0.3.10
github.com/klauspost/compress v1.11.3
github.com/moby/sys/mountinfo v0.4.0
github.com/imdario/mergo v0.3.11
github.com/klauspost/compress v1.11.13
github.com/moby/locker v1.0.1
github.com/moby/sys/mountinfo v0.4.1
github.com/moby/sys/symlink v0.1.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runc v1.0.0-rc92
github.com/opencontainers/runtime-spec v1.0.3-0.20200728170252-4d89ac9fbff6
github.com/opencontainers/runc v1.0.0-rc93
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d
github.com/opencontainers/selinux v1.8.0
github.com/pelletier/go-toml v1.8.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.7.1
github.com/prometheus/procfs v0.6.0 // indirect; temporarily force v0.6.0, which was previously defined in imgcrypt as explicit version
github.com/satori/go.uuid v1.2.0 // indirect
github.com/sirupsen/logrus v1.7.0
github.com/stretchr/testify v1.6.1
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
github.com/tchap/go-patricia v2.2.6+incompatible
github.com/urfave/cli v1.22.2
go.etcd.io/bbolt v1.3.5
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/sys v0.0.0-20201202213521-69691e467435
google.golang.org/grpc v1.30.0
gotest.tools/v3 v3.0.2
k8s.io/api v0.20.1
k8s.io/apimachinery v0.20.1
k8s.io/apiserver v0.20.1
k8s.io/client-go v0.20.1
k8s.io/component-base v0.20.1
k8s.io/cri-api v0.20.1
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/sys v0.0.0-20210324051608-47abb6519492
google.golang.org/grpc v1.33.2
gotest.tools/v3 v3.0.3
k8s.io/api v0.20.6
k8s.io/apimachinery v0.20.6
k8s.io/apiserver v0.20.6
k8s.io/client-go v0.20.6
k8s.io/component-base v0.20.6
k8s.io/cri-api v0.20.6
k8s.io/klog/v2 v2.4.0
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
)
// When updating replace rules, make sure to also update the rules in integration/client/go.mod
replace (
// prevent transitional dependencies due to containerd having a circular
// dependency on itself through plugins. see .empty-mod/go.mod for details
github.com/containerd/containerd => ./.empty-mod/
github.com/gogo/googleapis => github.com/gogo/googleapis v1.3.2
github.com/golang/protobuf => github.com/golang/protobuf v1.3.5
// urfave/cli must be <= v1.22.1 due to a regression: https://github.com/urfave/cli/issues/1092

View file

@ -17,10 +17,10 @@
package images
import (
"compress/gzip"
"context"
"io"
"github.com/containerd/containerd/archive/compression"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/labels"
"github.com/opencontainers/go-digest"
@ -55,13 +55,14 @@ func GetDiffID(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (
}
defer ra.Close()
r := content.NewReader(ra)
gzR, err := gzip.NewReader(r)
uR, err := compression.DecompressStream(r)
if err != nil {
return "", err
}
defer uR.Close()
digester := digest.Canonical.Digester()
hashW := digester.Hash()
if _, err := io.Copy(hashW, gzR); err != nil {
if _, err := io.Copy(hashW, uR); err != nil {
return "", err
}
if err := ra.Close(); err != nil {

View file

@ -49,6 +49,9 @@ const (
MediaTypeContainerd1CheckpointRuntimeOptions = "application/vnd.containerd.container.checkpoint.runtime.options+proto"
// Legacy Docker schema1 manifest
MediaTypeDockerSchema1Manifest = "application/vnd.docker.distribution.manifest.v1+prettyjws"
// Encypted media types
MediaTypeImageLayerEncrypted = ocispec.MediaTypeImageLayer + "+encrypted"
MediaTypeImageLayerGzipEncrypted = ocispec.MediaTypeImageLayerGzip + "+encrypted"
)
// DiffCompression returns the compression as defined by the layer diff media

View file

@ -19,6 +19,8 @@ package boltutil
import (
"time"
"github.com/gogo/protobuf/proto"
"github.com/gogo/protobuf/types"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
@ -28,6 +30,7 @@ var (
bucketKeyLabels = []byte("labels")
bucketKeyCreatedAt = []byte("createdat")
bucketKeyUpdatedAt = []byte("updatedat")
bucketKeyExtensions = []byte("extensions")
)
// ReadLabels reads the labels key from the bucket
@ -145,3 +148,88 @@ func WriteTimestamps(bkt *bolt.Bucket, created, updated time.Time) error {
return nil
}
// WriteExtensions will write a KV map to the given bucket,
// where `K` is a string key and `V` is a protobuf's Any type that represents a generic extension.
func WriteExtensions(bkt *bolt.Bucket, extensions map[string]types.Any) error {
if len(extensions) == 0 {
return nil
}
ebkt, err := bkt.CreateBucketIfNotExists(bucketKeyExtensions)
if err != nil {
return err
}
for name, ext := range extensions {
p, err := proto.Marshal(&ext)
if err != nil {
return err
}
if err := ebkt.Put([]byte(name), p); err != nil {
return err
}
}
return nil
}
// ReadExtensions will read back a map of extensions from the given bucket, previously written by WriteExtensions
func ReadExtensions(bkt *bolt.Bucket) (map[string]types.Any, error) {
var (
extensions = make(map[string]types.Any)
ebkt = bkt.Bucket(bucketKeyExtensions)
)
if ebkt == nil {
return extensions, nil
}
if err := ebkt.ForEach(func(k, v []byte) error {
var t types.Any
if err := proto.Unmarshal(v, &t); err != nil {
return err
}
extensions[string(k)] = t
return nil
}); err != nil {
return nil, err
}
return extensions, nil
}
// WriteAny write a protobuf's Any type to the bucket
func WriteAny(bkt *bolt.Bucket, name []byte, any *types.Any) error {
if any == nil {
return nil
}
data, err := proto.Marshal(any)
if err != nil {
return err
}
if err := bkt.Put(name, data); err != nil {
return err
}
return nil
}
// ReadAny reads back protobuf's Any type from the bucket
func ReadAny(bkt *bolt.Bucket, name []byte) (*types.Any, error) {
bytes := bkt.Get(name)
if bytes == nil {
return nil, nil
}
out := types.Any{}
if err := proto.Unmarshal(bytes, &out); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal any")
}
return &out, nil
}

View file

@ -336,16 +336,11 @@ func readContainer(container *containers.Container, bkt *bolt.Bucket) error {
container.Runtime.Name = string(n)
}
obkt := rbkt.Get(bucketKeyOptions)
if obkt == nil {
return nil
}
var any types.Any
if err := proto.Unmarshal(obkt, &any); err != nil {
any, err := boltutil.ReadAny(rbkt, bucketKeyOptions)
if err != nil {
return err
}
container.Runtime.Options = &any
container.Runtime.Options = any
case string(bucketKeySpec):
var any types.Any
if err := proto.Unmarshal(v, &any); err != nil {
@ -357,22 +352,8 @@ func readContainer(container *containers.Container, bkt *bolt.Bucket) error {
case string(bucketKeySnapshotter):
container.Snapshotter = string(v)
case string(bucketKeyExtensions):
ebkt := bkt.Bucket(bucketKeyExtensions)
if ebkt == nil {
return nil
}
extensions := make(map[string]types.Any)
if err := ebkt.ForEach(func(k, v []byte) error {
var a types.Any
if err := proto.Unmarshal(v, &a); err != nil {
return err
}
extensions[string(k)] = a
return nil
}); err != nil {
extensions, err := boltutil.ReadExtensions(bkt)
if err != nil {
return err
}
@ -388,15 +369,8 @@ func writeContainer(bkt *bolt.Bucket, container *containers.Container) error {
return err
}
if container.Spec != nil {
spec, err := container.Spec.Marshal()
if err != nil {
return err
}
if err := bkt.Put(bucketKeySpec, spec); err != nil {
return err
}
if err := boltutil.WriteAny(bkt, bucketKeySpec, container.Spec); err != nil {
return err
}
for _, v := range [][2][]byte{
@ -424,33 +398,12 @@ func writeContainer(bkt *bolt.Bucket, container *containers.Container) error {
return err
}
if len(container.Extensions) > 0 {
ebkt, err := bkt.CreateBucketIfNotExists(bucketKeyExtensions)
if err != nil {
return err
}
for name, ext := range container.Extensions {
p, err := proto.Marshal(&ext)
if err != nil {
return err
}
if err := ebkt.Put([]byte(name), p); err != nil {
return err
}
}
if err := boltutil.WriteExtensions(bkt, container.Extensions); err != nil {
return err
}
if container.Runtime.Options != nil {
data, err := proto.Marshal(container.Runtime.Options)
if err != nil {
return err
}
if err := rbkt.Put(bucketKeyOptions, data); err != nil {
return err
}
if err := boltutil.WriteAny(rbkt, bucketKeyOptions, container.Runtime.Options); err != nil {
return err
}
return boltutil.WriteLabels(bkt, container.Labels)

View file

@ -18,6 +18,7 @@ package metadata
import (
"context"
"strings"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/identifiers"
@ -140,10 +141,17 @@ func (s *namespaceStore) Delete(ctx context.Context, namespace string, opts ...n
}
}
bkt := getBucket(s.tx, bucketKeyVersion)
if empty, err := s.namespaceEmpty(ctx, namespace); err != nil {
types, err := s.listNs(namespace)
if err != nil {
return err
} else if !empty {
return errors.Wrapf(errdefs.ErrFailedPrecondition, "namespace %q must be empty", namespace)
}
if len(types) > 0 {
return errors.Wrapf(
errdefs.ErrFailedPrecondition,
"namespace %q must be empty, but it still has %s",
namespace, strings.Join(types, ", "),
)
}
if err := bkt.DeleteBucket([]byte(namespace)); err != nil {
@ -157,32 +165,35 @@ func (s *namespaceStore) Delete(ctx context.Context, namespace string, opts ...n
return nil
}
func (s *namespaceStore) namespaceEmpty(ctx context.Context, namespace string) (bool, error) {
// Get all data buckets
buckets := []*bolt.Bucket{
getImagesBucket(s.tx, namespace),
getBlobsBucket(s.tx, namespace),
getContainersBucket(s.tx, namespace),
// listNs returns the types of the remaining objects inside the given namespace.
// It doesn't return exact objects due to performance concerns.
func (s *namespaceStore) listNs(namespace string) ([]string, error) {
var out []string
if !isBucketEmpty(getImagesBucket(s.tx, namespace)) {
out = append(out, "images")
}
if !isBucketEmpty(getBlobsBucket(s.tx, namespace)) {
out = append(out, "blobs")
}
if !isBucketEmpty(getContainersBucket(s.tx, namespace)) {
out = append(out, "containers")
}
if snbkt := getSnapshottersBucket(s.tx, namespace); snbkt != nil {
if err := snbkt.ForEach(func(k, v []byte) error {
if v == nil {
buckets = append(buckets, snbkt.Bucket(k))
if !isBucketEmpty(snbkt.Bucket(k)) {
out = append(out, "snapshot-"+string(k))
}
}
return nil
}); err != nil {
return false, err
return nil, err
}
}
// Ensure data buckets are empty
for _, bkt := range buckets {
if !isBucketEmpty(bkt) {
return false, nil
}
}
return true, nil
return out, nil
}
func isBucketEmpty(bkt *bolt.Bucket) bool {

View file

@ -69,7 +69,10 @@ func getFreeLoopDev() (uint32, error) {
return uint32(num), nil
}
func setupLoopDev(backingFile, loopDev string, param LoopParams) error {
// setupLoopDev attaches the backing file to the loop device and returns
// the file handle for the loop device. The caller is responsible for
// closing the file handle.
func setupLoopDev(backingFile, loopDev string, param LoopParams) (_ *os.File, retErr error) {
// 1. Open backing file and loop device
flags := os.O_RDWR
if param.Readonly {
@ -78,19 +81,23 @@ func setupLoopDev(backingFile, loopDev string, param LoopParams) error {
back, err := os.OpenFile(backingFile, flags, 0)
if err != nil {
return errors.Wrapf(err, "could not open backing file: %s", backingFile)
return nil, errors.Wrapf(err, "could not open backing file: %s", backingFile)
}
defer back.Close()
loop, err := os.OpenFile(loopDev, flags, 0)
if err != nil {
return errors.Wrapf(err, "could not open loop device: %s", loopDev)
return nil, errors.Wrapf(err, "could not open loop device: %s", loopDev)
}
defer loop.Close()
defer func() {
if retErr != nil {
loop.Close()
}
}()
// 2. Set FD
if _, _, err = ioctl(loop.Fd(), unix.LOOP_SET_FD, back.Fd()); err != nil {
return errors.Wrapf(err, "could not set loop fd for device: %s", loopDev)
return nil, errors.Wrapf(err, "could not set loop fd for device: %s", loopDev)
}
// 3. Set Info
@ -110,7 +117,7 @@ func setupLoopDev(backingFile, loopDev string, param LoopParams) error {
_, _, err = ioctl(loop.Fd(), unix.LOOP_SET_STATUS64, uintptr(unsafe.Pointer(&info)))
if err == nil {
return nil
return loop, nil
}
if param.Direct {
@ -119,13 +126,12 @@ func setupLoopDev(backingFile, loopDev string, param LoopParams) error {
info.Flags &= ^(uint32(unix.LO_FLAGS_DIRECT_IO))
_, _, err = ioctl(loop.Fd(), unix.LOOP_SET_STATUS64, uintptr(unsafe.Pointer(&info)))
if err == nil {
return nil
return loop, nil
}
}
// Cleanup loop fd and return error
_, _, _ = ioctl(loop.Fd(), unix.LOOP_CLR_FD, 0)
return errors.Errorf("failed to set loop device info: %v", err)
return nil, errors.Errorf("failed to set loop device info: %v", err)
}
// setupLoop looks for (and possibly creates) a free loop device, and
@ -142,15 +148,16 @@ func setupLoopDev(backingFile, loopDev string, param LoopParams) error {
// the loop device when done with it.
//
// Upon success, the file handle to the loop device is returned.
func setupLoop(backingFile string, param LoopParams) (string, error) {
func setupLoop(backingFile string, param LoopParams) (*os.File, error) {
for retry := 1; retry < 100; retry++ {
num, err := getFreeLoopDev()
if err != nil {
return "", err
return nil, err
}
loopDev := fmt.Sprintf(loopDevFormat, num)
if err := setupLoopDev(backingFile, loopDev, param); err != nil {
file, err := setupLoopDev(backingFile, loopDev, param)
if err != nil {
// Per util-linux/sys-utils/losetup.c:create_loop(),
// free loop device can race and we end up failing
// with EBUSY when trying to set it up.
@ -159,13 +166,13 @@ func setupLoop(backingFile string, param LoopParams) (string, error) {
time.Sleep(time.Millisecond * time.Duration(rand.Intn(retry*10)))
continue
}
return "", err
return nil, err
}
return loopDev, nil
return file, nil
}
return "", errors.New("timeout creating new loopback device")
return nil, errors.New("timeout creating new loopback device")
}
func removeLoop(loopdev string) error {
@ -179,12 +186,17 @@ func removeLoop(loopdev string) error {
return err
}
// Attach a specified backing file to a loop device
// AttachLoopDevice attaches a specified backing file to a loop device
func AttachLoopDevice(backingFile string) (string, error) {
return setupLoop(backingFile, LoopParams{})
file, err := setupLoop(backingFile, LoopParams{})
if err != nil {
return "", err
}
defer file.Close()
return file.Name(), nil
}
// Detach a loop device
// DetachLoopDevice detaches the provided loop devices
func DetachLoopDevice(devices ...string) error {
for _, dev := range devices {
if err := removeLoop(dev); err != nil {

View file

@ -79,14 +79,16 @@ func (m *Mount) Mount(target string) (err error) {
// or remount with changed data
source := m.Source
if losetup {
devFile, err := setupLoop(m.Source, LoopParams{
loFile, err := setupLoop(m.Source, LoopParams{
Readonly: oflags&unix.MS_RDONLY == unix.MS_RDONLY,
Autoclear: true})
if err != nil {
return err
}
defer loFile.Close()
// Mount the loop device instead
source = devFile
source = loFile.Name()
}
if err := mountAt(chdir, source, target, m.Type, uintptr(oflags), data); err != nil {
return err

View file

@ -38,7 +38,6 @@ import (
"github.com/opencontainers/runc/libcontainer/user"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/syndtr/gocapability/capability"
)
// SpecOpts sets spec specific information to a newly generated OCI spec
@ -274,6 +273,28 @@ func WithMounts(mounts []specs.Mount) SpecOpts {
}
}
// WithoutMounts removes mounts
func WithoutMounts(dests ...string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
var (
mounts []specs.Mount
current = s.Mounts
)
mLoop:
for _, m := range current {
mDestination := filepath.Clean(m.Destination)
for _, dest := range dests {
if mDestination == dest {
continue mLoop
}
}
mounts = append(mounts, m)
}
s.Mounts = mounts
return nil
}
}
// WithHostNamespace allows a task to run inside the host's linux namespace
func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
@ -295,10 +316,7 @@ func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
setLinux(s)
for i, n := range s.Linux.Namespaces {
if n.Type == ns.Type {
before := s.Linux.Namespaces[:i]
after := s.Linux.Namespaces[i+1:]
s.Linux.Namespaces = append(before, ns)
s.Linux.Namespaces = append(s.Linux.Namespaces, after...)
s.Linux.Namespaces[i] = ns
return nil
}
}
@ -776,29 +794,6 @@ func WithCapabilities(caps []string) SpecOpts {
}
}
// WithAllCapabilities sets all linux capabilities for the process
var WithAllCapabilities = func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
return WithCapabilities(GetAllCapabilities())(ctx, client, c, s)
}
// GetAllCapabilities returns all caps up to CAP_LAST_CAP
// or CAP_BLOCK_SUSPEND on RHEL6
func GetAllCapabilities() []string {
last := capability.CAP_LAST_CAP
// hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap
if last == capability.Cap(63) {
last = capability.CAP_BLOCK_SUSPEND
}
var caps []string
for _, cap := range capability.List() {
if cap > last {
continue
}
caps = append(caps, "CAP_"+strings.ToUpper(cap.String()))
}
return caps
}
func capsContain(caps []string, s string) bool {
for _, c := range caps {
if c == s {
@ -954,16 +949,13 @@ func WithReadonlyPaths(paths []string) SpecOpts {
// WithWriteableSysfs makes any sysfs mounts writeable
func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
for i, m := range s.Mounts {
for _, m := range s.Mounts {
if m.Type == "sysfs" {
var options []string
for _, o := range m.Options {
for i, o := range m.Options {
if o == "ro" {
o = "rw"
m.Options[i] = "rw"
}
options = append(options, o)
}
s.Mounts[i].Options = options
}
}
return nil
@ -971,16 +963,13 @@ func WithWriteableSysfs(_ context.Context, _ Client, _ *containers.Container, s
// WithWriteableCgroupfs makes any cgroup mounts writeable
func WithWriteableCgroupfs(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
for i, m := range s.Mounts {
for _, m := range s.Mounts {
if m.Type == "cgroup" {
var options []string
for _, o := range m.Options {
for i, o := range m.Options {
if o == "ro" {
o = "rw"
m.Options[i] = "rw"
}
options = append(options, o)
}
s.Mounts[i].Options = options
}
}
return nil
@ -1132,7 +1121,7 @@ func WithDefaultUnixDevices(_ context.Context, _ Client, _ *containers.Container
// WithPrivileged sets up options for a privileged container
var WithPrivileged = Compose(
WithAllCapabilities,
WithAllCurrentCapabilities,
WithMaskedPaths(nil),
WithReadonlyPaths(nil),
WithWriteableSysfs,
@ -1205,15 +1194,13 @@ func WithLinuxDevices(devices []specs.LinuxDevice) SpecOpts {
}
}
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)
dev, err := deviceFromPath(path)
if err != nil {
return err
}

View file

@ -20,20 +20,17 @@ package oci
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/pkg/cap"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
)
// WithHostDevices adds all the hosts device nodes to the container's spec
func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
devs, err := getDevices("/dev")
devs, err := HostDevices()
if err != nil {
return err
}
@ -41,83 +38,28 @@ func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Sp
return nil
}
func getDevices(path string) ([]specs.LinuxDevice, error) {
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
var out []specs.LinuxDevice
for _, f := range files {
switch {
case f.IsDir():
switch f.Name() {
// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
// ".udev" added to address https://github.com/opencontainers/runc/issues/2093
case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev":
continue
default:
sub, err := getDevices(filepath.Join(path, f.Name()))
if err != nil {
return nil, err
}
out = append(out, sub...)
continue
}
case f.Name() == "console":
continue
}
device, err := deviceFromPath(filepath.Join(path, f.Name()), "rwm")
// WithDevices recursively adds devices from the passed in path and associated cgroup rules for that device.
// If devicePath is a dir it traverses the dir to add all devices in that dir.
// If devicePath is not a dir, it attempts to add the single device.
// If containerPath is not set then the device path is used for the container path.
func WithDevices(devicePath, containerPath, permissions string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
devs, err := getDevices(devicePath, containerPath)
if err != nil {
if err == ErrNotADevice {
continue
}
if os.IsNotExist(err) {
continue
}
return nil, err
return err
}
out = append(out, *device)
for _, dev := range devs {
s.Linux.Devices = append(s.Linux.Devices, dev)
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, specs.LinuxDeviceCgroup{
Allow: true,
Type: dev.Type,
Major: &dev.Major,
Minor: &dev.Minor,
Access: permissions,
})
}
return nil
}
return out, nil
}
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
}
// WithMemorySwap sets the container's swap in bytes
@ -180,3 +122,24 @@ func WithCPUCFS(quota int64, period uint64) SpecOpts {
return nil
}
}
// WithAllCurrentCapabilities propagates the effective capabilities of the caller process to the container process.
// The capability set may differ from WithAllKnownCapabilities when running in a container.
var WithAllCurrentCapabilities = func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
caps, err := cap.Current()
if err != nil {
return err
}
return WithCapabilities(caps)(ctx, client, c, s)
}
// WithAllKnownCapabilities sets all the the known linux capabilities for the container process
var WithAllKnownCapabilities = func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
caps := cap.Known()
return WithCapabilities(caps)(ctx, client, c, s)
}
// WithoutRunMount removes the `/run` inside the spec
func WithoutRunMount(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
return WithoutMounts("/run")(ctx, client, c, s)
}

View file

@ -0,0 +1,38 @@
// +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 (
"context"
"github.com/containerd/containerd/containers"
)
// WithAllCurrentCapabilities propagates the effective capabilities of the caller process to the container process.
// The capability set may differ from WithAllKnownCapabilities when running in a container.
//nolint: deadcode, unused
var WithAllCurrentCapabilities = func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
return WithCapabilities(nil)(ctx, client, c, s)
}
// WithAllKnownCapabilities sets all the the known linux capabilities for the container process
//nolint: deadcode, unused
var WithAllKnownCapabilities = func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
return WithCapabilities(nil)(ctx, client, c, s)
}

View file

@ -20,20 +20,15 @@ package oci
import (
"context"
"io/ioutil"
"os"
"path/filepath"
"github.com/containerd/containerd/containers"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
)
// WithHostDevices adds all the hosts device nodes to the container's spec
func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
setLinux(s)
devs, err := getDevices("/dev")
devs, err := HostDevices()
if err != nil {
return err
}
@ -41,82 +36,18 @@ func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Sp
return nil
}
func getDevices(path string) ([]specs.LinuxDevice, error) {
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
var out []specs.LinuxDevice
for _, f := range files {
switch {
case f.IsDir():
switch f.Name() {
// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
// ".udev" added to address https://github.com/opencontainers/runc/issues/2093
case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev":
continue
default:
sub, err := getDevices(filepath.Join(path, f.Name()))
if err != nil {
return nil, err
}
out = append(out, sub...)
continue
}
case f.Name() == "console":
continue
}
device, err := deviceFromPath(filepath.Join(path, f.Name()), "rwm")
// WithDevices recursively adds devices from the passed in path and associated cgroup rules for that device.
// If devicePath is a dir it traverses the dir to add all devices in that dir.
// If devicePath is not a dir, it attempts to add the single device.
func WithDevices(devicePath, containerPath, permissions string) SpecOpts {
return func(_ context.Context, _ Client, _ *containers.Container, s *Spec) error {
devs, err := getDevices(devicePath, containerPath)
if err != nil {
if err == ErrNotADevice {
continue
}
if os.IsNotExist(err) {
continue
}
return nil, err
return err
}
out = append(out, *device)
s.Linux.Devices = append(s.Linux.Devices, devs...)
return nil
}
return out, nil
}
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
}
// WithCPUCFS sets the container's Completely fair scheduling (CFS) quota and period

View file

@ -74,6 +74,6 @@ func WithHostDevices(_ context.Context, _ Client, _ *containers.Container, s *Sp
return nil
}
func deviceFromPath(path, permissions string) (*specs.LinuxDevice, error) {
func deviceFromPath(path string) (*specs.LinuxDevice, error) {
return nil, errors.New("device from path not supported on Windows")
}

View file

@ -0,0 +1,137 @@
// +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 oci
import (
"io/ioutil"
"os"
"path/filepath"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
var errNotADevice = errors.New("not a device node")
// HostDevices returns all devices that can be found under /dev directory.
func HostDevices() ([]specs.LinuxDevice, error) {
return getDevices("/dev", "")
}
func getDevices(path, containerPath string) ([]specs.LinuxDevice, error) {
stat, err := os.Stat(path)
if err != nil {
return nil, errors.Wrap(err, "error stating device path")
}
if !stat.IsDir() {
dev, err := deviceFromPath(path)
if err != nil {
return nil, err
}
if containerPath != "" {
dev.Path = containerPath
}
return []specs.LinuxDevice{*dev}, nil
}
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
var out []specs.LinuxDevice
for _, f := range files {
switch {
case f.IsDir():
switch f.Name() {
// ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825
// ".udev" added to address https://github.com/opencontainers/runc/issues/2093
case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts", ".udev":
continue
default:
var cp string
if containerPath != "" {
cp = filepath.Join(containerPath, filepath.Base(f.Name()))
}
sub, err := getDevices(filepath.Join(path, f.Name()), cp)
if err != nil {
return nil, err
}
out = append(out, sub...)
continue
}
case f.Name() == "console":
continue
}
device, err := deviceFromPath(filepath.Join(path, f.Name()))
if err != nil {
if err == errNotADevice {
continue
}
if os.IsNotExist(err) {
continue
}
return nil, err
}
if containerPath != "" {
device.Path = filepath.Join(containerPath, filepath.Base(f.Name()))
}
out = append(out, *device)
}
return out, nil
}
func deviceFromPath(path 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) //nolint: unconvert // the type is 32bit on mips.
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 &^ unix.S_IFMT)
return &specs.LinuxDevice{
Type: devType,
Path: path,
Major: int64(major),
Minor: int64(minor),
FileMode: &fm,
UID: &stat.Uid,
GID: &stat.Gid,
}, nil
}

View file

@ -1,5 +1,3 @@
// +build linux
/*
Copyright The containerd Authors.
@ -18,31 +16,12 @@
package apparmor
import (
"io/ioutil"
"os"
"sync"
)
var (
appArmorSupported bool
checkAppArmor sync.Once
)
// HostSupports returns true if apparmor is enabled for the host, if
// apparmor_parser is enabled, and if we are not running docker-in-docker.
// HostSupports returns true if apparmor is enabled for the host, // On non-Linux returns false
// On Linux returns true if apparmor_parser is enabled, and if we
// are not running docker-in-docker.
//
// It is a modified version of libcontainer/apparmor.IsEnabled(), which does not
// check for apparmor_parser to be present, or if we're running docker-in-docker.
// It is a modified version of libcontainer/apparmor.IsEnabled(), which does not
// check for apparmor_parser to be present, or if we're running docker-in-docker.
func HostSupports() bool {
checkAppArmor.Do(func() {
// see https://github.com/docker/docker/commit/de191e86321f7d3136ff42ff75826b8107399497
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
appArmorSupported = err == nil && len(buf) > 1 && buf[0] == 'Y'
}
}
})
return appArmorSupported
return hostSupports()
}

View file

@ -0,0 +1,48 @@
// +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 apparmor
import (
"io/ioutil"
"os"
"sync"
)
var (
appArmorSupported bool
checkAppArmor sync.Once
)
// hostSupports returns true if apparmor is enabled for the host, if
// apparmor_parser is enabled, and if we are not running docker-in-docker.
//
// It is a modified version of libcontainer/apparmor.IsEnabled(), which does not
// check for apparmor_parser to be present, or if we're running docker-in-docker.
func hostSupports() bool {
checkAppArmor.Do(func() {
// see https://github.com/docker/docker/commit/de191e86321f7d3136ff42ff75826b8107399497
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
appArmorSupported = err == nil && len(buf) > 1 && buf[0] == 'Y'
}
}
})
return appArmorSupported
}

View file

@ -18,7 +18,6 @@
package apparmor
//nolint: deadcode, unused
func HostSupports() bool {
func hostSupports() bool {
return false
}

View file

@ -0,0 +1,192 @@
/*
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 cap provides Linux capability utility
package cap
import (
"bufio"
"io"
"os"
"strconv"
"strings"
"github.com/pkg/errors"
)
// FromNumber returns a cap string like "CAP_SYS_ADMIN"
// that corresponds to the given number like 21.
//
// FromNumber returns an empty string for unknown cap number.
func FromNumber(num int) string {
if num < 0 || num > len(capsLatest)-1 {
return ""
}
return capsLatest[num]
}
// FromBitmap parses an uint64 bitmap into string slice like
// []{"CAP_SYS_ADMIN", ...}.
//
// Unknown cap numbers are returned as []int.
func FromBitmap(v uint64) ([]string, []int) {
var (
res []string
unknown []int
)
for i := 0; i <= 63; i++ {
if b := (v >> i) & 0x1; b == 0x1 {
if s := FromNumber(i); s != "" {
res = append(res, s)
} else {
unknown = append(unknown, i)
}
}
}
return res, unknown
}
// Type is the type of capability
type Type int
const (
// Effective is CapEff
Effective Type = 1 << iota
// Permitted is CapPrm
Permitted
// Inheritable is CapInh
Inheritable
// Bounding is CapBnd
Bounding
// Ambient is CapAmb
Ambient
)
// ParseProcPIDStatus returns uint64 bitmap value from /proc/<PID>/status file
func ParseProcPIDStatus(r io.Reader) (map[Type]uint64, error) {
res := make(map[Type]uint64)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
pair := strings.SplitN(line, ":", 2)
if len(pair) != 2 {
continue
}
k := strings.TrimSpace(pair[0])
v := strings.TrimSpace(pair[1])
switch k {
case "CapInh", "CapPrm", "CapEff", "CapBnd", "CapAmb":
ui64, err := strconv.ParseUint(v, 16, 64)
if err != nil {
return nil, errors.Errorf("failed to parse line %q", line)
}
switch k {
case "CapInh":
res[Inheritable] = ui64
case "CapPrm":
res[Permitted] = ui64
case "CapEff":
res[Effective] = ui64
case "CapBnd":
res[Bounding] = ui64
case "CapAmb":
res[Ambient] = ui64
}
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return res, nil
}
// Current returns the list of the effective and the known caps of
// the current process.
//
// The result is like []string{"CAP_SYS_ADMIN", ...}.
//
// The result does not contain caps that are not recognized by
// the "github.com/syndtr/gocapability" library.
func Current() ([]string, error) {
f, err := os.Open("/proc/self/status")
if err != nil {
return nil, err
}
defer f.Close()
caps, err := ParseProcPIDStatus(f)
if err != nil {
return nil, err
}
capEff := caps[Effective]
names, _ := FromBitmap(capEff)
return names, nil
}
var (
// caps35 is the caps of kernel 3.5 (37 entries)
caps35 = []string{
"CAP_CHOWN", // 2.2
"CAP_DAC_OVERRIDE", // 2.2
"CAP_DAC_READ_SEARCH", // 2.2
"CAP_FOWNER", // 2.2
"CAP_FSETID", // 2.2
"CAP_KILL", // 2.2
"CAP_SETGID", // 2.2
"CAP_SETUID", // 2.2
"CAP_SETPCAP", // 2.2
"CAP_LINUX_IMMUTABLE", // 2.2
"CAP_NET_BIND_SERVICE", // 2.2
"CAP_NET_BROADCAST", // 2.2
"CAP_NET_ADMIN", // 2.2
"CAP_NET_RAW", // 2.2
"CAP_IPC_LOCK", // 2.2
"CAP_IPC_OWNER", // 2.2
"CAP_SYS_MODULE", // 2.2
"CAP_SYS_RAWIO", // 2.2
"CAP_SYS_CHROOT", // 2.2
"CAP_SYS_PTRACE", // 2.2
"CAP_SYS_PACCT", // 2.2
"CAP_SYS_ADMIN", // 2.2
"CAP_SYS_BOOT", // 2.2
"CAP_SYS_NICE", // 2.2
"CAP_SYS_RESOURCE", // 2.2
"CAP_SYS_TIME", // 2.2
"CAP_SYS_TTY_CONFIG", // 2.2
"CAP_MKNOD", // 2.4
"CAP_LEASE", // 2.4
"CAP_AUDIT_WRITE", // 2.6.11
"CAP_AUDIT_CONTROL", // 2.6.11
"CAP_SETFCAP", // 2.6.24
"CAP_MAC_OVERRIDE", // 2.6.25
"CAP_MAC_ADMIN", // 2.6.25
"CAP_SYSLOG", // 2.6.37
"CAP_WAKE_ALARM", // 3.0
"CAP_BLOCK_SUSPEND", // 3.5
}
// caps316 is the caps of kernel 3.16 (38 entries)
caps316 = append(caps35, "CAP_AUDIT_READ")
// caps58 is the caps of kernel 5.8 (40 entries)
caps58 = append(caps316, []string{"CAP_PERFMON", "CAP_BPF"}...)
// caps59 is the caps of kernel 5.9 (41 entries)
caps59 = append(caps58, "CAP_CHECKPOINT_RESTORE")
capsLatest = caps59
)
// Known returns the known cap strings of the latest kernel.
// The current latest kernel is 5.9.
func Known() []string {
return capsLatest
}

View file

@ -193,11 +193,15 @@ func (p *Init) createCheckpointedState(r *CreateConfig, pidFile *pidFile) error
ParentPath: r.ParentCheckpoint,
},
PidFile: pidFile.Path(),
IO: p.io.IO(),
NoPivot: p.NoPivotRoot,
Detach: true,
NoSubreaper: true,
}
if p.io != nil {
opts.IO = p.io.IO()
}
p.initState = &createdCheckpointState{
p: p,
opts: opts,
@ -441,7 +445,7 @@ func (p *Init) checkpoint(ctx context.Context, r *CheckpointConfig) error {
}, actions...); err != nil {
dumpLog := filepath.Join(p.Bundle, "criu-dump.log")
if cerr := copyFile(dumpLog, filepath.Join(work, "dump.log")); cerr != nil {
log.G(ctx).Error(err)
log.G(ctx).WithError(cerr).Error("failed to copy dump.log to criu-dump.log")
}
return fmt.Errorf("%s path= %s", criuError(err), dumpLog)
}

View file

@ -34,7 +34,6 @@ import (
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/pkg/stdio"
"github.com/containerd/containerd/sys"
"github.com/containerd/fifo"
runc "github.com/containerd/go-runc"
"github.com/hashicorp/go-multierror"
@ -179,7 +178,7 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w
},
},
} {
ok, err := sys.IsFifo(i.name)
ok, err := fifo.IsFifo(i.name)
if err != nil {
return err
}
@ -252,14 +251,6 @@ func NewBinaryIO(ctx context.Context, id string, uri *url.URL) (_ runc.IO, err e
return nil, err
}
var args []string
for k, vs := range uri.Query() {
args = append(args, k)
if len(vs) > 0 {
args = append(args, vs[0])
}
}
var closers []func() error
defer func() {
if err == nil {
@ -290,12 +281,7 @@ func NewBinaryIO(ctx context.Context, id string, uri *url.URL) (_ runc.IO, err e
}
closers = append(closers, r.Close, w.Close)
cmd := exec.Command(uri.Path, args...)
cmd.Env = append(cmd.Env,
"CONTAINER_ID="+id,
"CONTAINER_NAMESPACE="+ns,
)
cmd := NewBinaryCmd(uri, id, ns)
cmd.ExtraFiles = append(cmd.ExtraFiles, out.r, serr.r, w)
// don't need to register this with the reaper or wait when
// running inside a shim

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package runtime
package process
import (
"net/url"

View file

@ -172,7 +172,7 @@ func (p *pidFile) Read() (int, error) {
func waitTimeout(ctx context.Context, wg *sync.WaitGroup, timeout time.Duration) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
done := make(chan struct{}, 1)
done := make(chan struct{})
go func() {
wg.Wait()
close(done)

View file

@ -14,7 +14,7 @@
limitations under the License.
*/
package sys
package userns
import (
"bufio"

View file

@ -16,7 +16,7 @@
limitations under the License.
*/
package sys
package userns
// RunningInUserNS is a stub for non-Linux systems
// Always returns false

View file

@ -19,15 +19,63 @@
package platforms
import (
"fmt"
"runtime"
"strconv"
"strings"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"golang.org/x/sys/windows"
)
// Default returns the default matcher for the platform.
func Default() MatchComparer {
return Ordered(DefaultSpec(), specs.Platform{
OS: "linux",
Architecture: runtime.GOARCH,
})
type matchComparer struct {
defaults Matcher
osVersionPrefix string
}
// Match matches platform with the same windows major, minor
// and build version.
func (m matchComparer) Match(p imagespec.Platform) bool {
if m.defaults.Match(p) {
// TODO(windows): Figure out whether OSVersion is deprecated.
return strings.HasPrefix(p.OSVersion, m.osVersionPrefix)
}
return false
}
// Less sorts matched platforms in front of other platforms.
// For matched platforms, it puts platforms with larger revision
// number in front.
func (m matchComparer) Less(p1, p2 imagespec.Platform) bool {
m1, m2 := m.Match(p1), m.Match(p2)
if m1 && m2 {
r1, r2 := revision(p1.OSVersion), revision(p2.OSVersion)
return r1 > r2
}
return m1 && !m2
}
func revision(v string) int {
parts := strings.Split(v, ".")
if len(parts) < 4 {
return 0
}
r, err := strconv.Atoi(parts[3])
if err != nil {
return 0
}
return r
}
// Default returns the current platform's default platform specification.
func Default() MatchComparer {
major, minor, build := windows.RtlGetNtVersionNumbers()
return matchComparer{
defaults: Ordered(DefaultSpec(), specs.Platform{
OS: "linux",
Architecture: runtime.GOARCH,
}),
osVersionPrefix: fmt.Sprintf("%d.%d.%d", major, minor, build),
}
}

View file

@ -44,17 +44,47 @@ type dockerPusher struct {
tracker StatusTracker
}
// Writer implements Ingester API of content store. This allows the client
// to receive ErrUnavailable when there is already an on-going upload.
// Note that the tracker MUST implement StatusTrackLocker interface to avoid
// race condition on StatusTracker.
func (p dockerPusher) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
var wOpts content.WriterOpts
for _, opt := range opts {
if err := opt(&wOpts); err != nil {
return nil, err
}
}
if wOpts.Ref == "" {
return nil, errors.Wrap(errdefs.ErrInvalidArgument, "ref must not be empty")
}
return p.push(ctx, wOpts.Desc, wOpts.Ref, true)
}
func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) {
return p.push(ctx, desc, remotes.MakeRefKey(ctx, desc), false)
}
func (p dockerPusher) push(ctx context.Context, desc ocispec.Descriptor, ref string, unavailableOnFail bool) (content.Writer, error) {
if l, ok := p.tracker.(StatusTrackLocker); ok {
l.Lock(ref)
defer l.Unlock(ref)
}
ctx, err := ContextWithRepositoryScope(ctx, p.refspec, true)
if err != nil {
return nil, err
}
ref := remotes.MakeRefKey(ctx, desc)
status, err := p.tracker.GetStatus(ref)
if err == nil {
if status.Offset == status.Total {
if status.Committed && status.Offset == status.Total {
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "ref %v", ref)
}
if unavailableOnFail {
// Another push of this ref is happening elsewhere. The rest of function
// will continue only when `errdefs.IsNotFound(err) == true` (i.e. there
// is no actively-tracked ref already).
return nil, errors.Wrap(errdefs.ErrUnavailable, "push is on-going")
}
// TODO: Handle incomplete status
} else if !errdefs.IsNotFound(err) {
return nil, errors.Wrap(err, "failed to get status")
@ -105,8 +135,11 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten
if exists {
p.tracker.SetStatus(ref, Status{
Committed: true,
Status: content.Status{
Ref: ref,
Ref: ref,
Total: desc.Size,
Offset: desc.Size,
// TODO: Set updated time?
},
})
@ -162,8 +195,11 @@ func (p dockerPusher) Push(ctx context.Context, desc ocispec.Descriptor) (conten
case http.StatusOK, http.StatusAccepted, http.StatusNoContent:
case http.StatusCreated:
p.tracker.SetStatus(ref, Status{
Committed: true,
Status: content.Status{
Ref: ref,
Ref: ref,
Total: desc.Size,
Offset: desc.Size,
},
})
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v on remote", desc.Digest)
@ -341,8 +377,6 @@ func (pw *pushWriter) Commit(ctx context.Context, size int64, expected digest.Di
if err := pw.pipe.Close(); err != nil {
return err
}
// TODO: Update status to determine committing
// TODO: timeout waiting for response
resp := <-pw.responseC
if resp.err != nil {
@ -354,7 +388,7 @@ func (pw *pushWriter) Commit(ctx context.Context, size int64, expected digest.Di
switch resp.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
default:
return errors.Errorf("unexpected status: %s", resp.Status)
return remoteserrors.NewUnexpectedStatusErr(resp.Response)
}
status, err := pw.tracker.GetStatus(pw.ref)
@ -379,6 +413,10 @@ func (pw *pushWriter) Commit(ctx context.Context, size int64, expected digest.Di
return errors.Errorf("got digest %s, expected %s", actual, expected)
}
status.Committed = true
status.UpdatedAt = time.Now()
pw.tracker.SetStatus(pw.ref, status)
return nil
}

View file

@ -17,7 +17,10 @@
package docker
import (
"net"
"net/http"
"github.com/pkg/errors"
)
// HostCapabilities represent the capabilities of the registry
@ -56,6 +59,7 @@ const (
// Reserved for future capabilities (i.e. search, catalog, remove)
)
// Has checks whether the capabilities list has the provide capability
func (c HostCapabilities) Has(t HostCapabilities) bool {
return c&t == t
}
@ -201,12 +205,41 @@ func MatchAllHosts(string) (bool, error) {
// MatchLocalhost is a host match function which returns true for
// localhost.
//
// Note: this does not handle matching of ip addresses in octal,
// decimal or hex form.
func MatchLocalhost(host string) (bool, error) {
for _, s := range []string{"localhost", "127.0.0.1", "[::1]"} {
if len(host) >= len(s) && host[0:len(s)] == s && (len(host) == len(s) || host[len(s)] == ':') {
return true, nil
}
switch {
case host == "::1":
return true, nil
case host == "[::1]":
return true, nil
}
return host == "::1", nil
h, p, err := net.SplitHostPort(host)
// addrError helps distinguish between errors of form
// "no colon in address" and "too many colons in address".
// The former is fine as the host string need not have a
// port. Latter needs to be handled.
addrError := &net.AddrError{
Err: "missing port in address",
Addr: host,
}
if err != nil {
if err.Error() != addrError.Error() {
return false, err
}
// host string without any port specified
h = host
} else if len(p) == 0 {
return false, errors.New("invalid host name format")
}
// use ipv4 dotted decimal for further checking
if h == "localhost" {
h = "127.0.0.1"
}
ip := net.ParseIP(h)
return ip.IsLoopback(), nil
}

View file

@ -286,12 +286,14 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
if lastErr == nil {
lastErr = err
}
log.G(ctx).WithError(err).Info("trying next host")
continue // try another host
}
resp.Body.Close() // don't care about body contents.
if resp.StatusCode > 299 {
if resp.StatusCode == http.StatusNotFound {
log.G(ctx).Info("trying next host - response was http.StatusNotFound")
continue
}
return "", ocispec.Descriptor{}, errors.Errorf("unexpected status code %v: %v", u, resp.Status)

View file

@ -21,6 +21,7 @@ import (
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/moby/locker"
"github.com/pkg/errors"
)
@ -28,6 +29,8 @@ import (
type Status struct {
content.Status
Committed bool
// UploadUUID is used by the Docker registry to reference blob uploads
UploadUUID string
}
@ -38,15 +41,24 @@ type StatusTracker interface {
SetStatus(string, Status)
}
// StatusTrackLocker to track status of operations with lock
type StatusTrackLocker interface {
StatusTracker
Lock(string)
Unlock(string)
}
type memoryStatusTracker struct {
statuses map[string]Status
m sync.Mutex
locker *locker.Locker
}
// NewInMemoryTracker returns a StatusTracker that tracks content status in-memory
func NewInMemoryTracker() StatusTracker {
func NewInMemoryTracker() StatusTrackLocker {
return &memoryStatusTracker{
statuses: map[string]Status{},
locker: locker.New(),
}
}
@ -65,3 +77,11 @@ func (t *memoryStatusTracker) SetStatus(ref string, status Status) {
t.statuses[ref] = status
t.m.Unlock()
}
func (t *memoryStatusTracker) Lock(ref string) {
t.locker.Lock(ref)
}
func (t *memoryStatusTracker) Unlock(ref string) {
t.locker.Unlock(ref)
}

View file

@ -27,9 +27,10 @@ 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
Status string
StatusCode int
Body []byte
RequestURL, RequestMethod string
}
func (e ErrUnexpectedStatus) Error() string {
@ -42,5 +43,14 @@ func NewUnexpectedStatusErr(resp *http.Response) error {
if resp.Body != nil {
b, _ = ioutil.ReadAll(io.LimitReader(resp.Body, 64000)) // 64KB
}
return ErrUnexpectedStatus{Status: resp.Status, StatusCode: resp.StatusCode, Body: b}
err := ErrUnexpectedStatus{
Body: b,
Status: resp.Status,
StatusCode: resp.StatusCode,
RequestMethod: resp.Request.Method,
}
if resp.Request.URL != nil {
err.RequestURL = resp.Request.URL.String()
}
return err
}

View file

@ -31,6 +31,7 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/semaphore"
)
type refKeyPrefix struct{}
@ -55,25 +56,32 @@ func WithMediaTypeKeyPrefix(ctx context.Context, mediaType, prefix string) conte
// used to lookup ongoing processes related to the descriptor. This function
// may look to the context to namespace the reference appropriately.
func MakeRefKey(ctx context.Context, desc ocispec.Descriptor) string {
key := desc.Digest.String()
if desc.Annotations != nil {
if name, ok := desc.Annotations[ocispec.AnnotationRefName]; ok {
key = fmt.Sprintf("%s@%s", name, desc.Digest.String())
}
}
if v := ctx.Value(refKeyPrefix{}); v != nil {
values := v.(map[string]string)
if prefix := values[desc.MediaType]; prefix != "" {
return prefix + "-" + desc.Digest.String()
return prefix + "-" + key
}
}
switch mt := desc.MediaType; {
case mt == images.MediaTypeDockerSchema2Manifest || mt == ocispec.MediaTypeImageManifest:
return "manifest-" + desc.Digest.String()
return "manifest-" + key
case mt == images.MediaTypeDockerSchema2ManifestList || mt == ocispec.MediaTypeImageIndex:
return "index-" + desc.Digest.String()
return "index-" + key
case images.IsLayerType(mt):
return "layer-" + desc.Digest.String()
return "layer-" + key
case images.IsKnownConfig(mt):
return "config-" + desc.Digest.String()
return "config-" + key
default:
log.G(ctx).Warnf("reference for unknown type: %s", mt)
return "unknown-" + desc.Digest.String()
return "unknown-" + key
}
}
@ -115,6 +123,12 @@ func fetch(ctx context.Context, ingester content.Ingester, fetcher Fetcher, desc
return err
}
if desc.Size == 0 {
// most likely a poorly configured registry/web front end which responded with no
// Content-Length header; unable (not to mention useless) to commit a 0-length entry
// into the content store. Error out here otherwise the error sent back is confusing
return errors.Wrapf(errdefs.ErrInvalidArgument, "unable to fetch descriptor (%s) which reports content size of zero", desc.Digest)
}
if ws.Offset == desc.Size {
// If writer is already complete, commit and return
err := cw.Commit(ctx, desc.Size, desc.Digest)
@ -151,7 +165,15 @@ func PushHandler(pusher Pusher, provider content.Provider) images.HandlerFunc {
func push(ctx context.Context, provider content.Provider, pusher Pusher, desc ocispec.Descriptor) error {
log.G(ctx).Debug("push")
cw, err := pusher.Push(ctx, desc)
var (
cw content.Writer
err error
)
if cs, ok := pusher.(content.Ingester); ok {
cw, err = content.OpenWriter(ctx, cs, content.WithRef(MakeRefKey(ctx, desc)), content.WithDescriptor(desc))
} else {
cw, err = pusher.Push(ctx, desc)
}
if err != nil {
if !errdefs.IsAlreadyExists(err) {
return err
@ -175,7 +197,8 @@ func push(ctx context.Context, provider content.Provider, pusher Pusher, desc oc
//
// Base handlers can be provided which will be called before any push specific
// handlers.
func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, store content.Store, platform platforms.MatchComparer, wrapper func(h images.Handler) images.Handler) error {
func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, store content.Store, limiter *semaphore.Weighted, platform platforms.MatchComparer, wrapper func(h images.Handler) images.Handler) error {
var m sync.Mutex
manifestStack := []ocispec.Descriptor{}
@ -207,7 +230,7 @@ func PushContent(ctx context.Context, pusher Pusher, desc ocispec.Descriptor, st
handler = wrapper(handler)
}
if err := images.Dispatch(ctx, handler, nil, desc); err != nil {
if err := images.Dispatch(ctx, handler, limiter, desc); err != nil {
return err
}

View file

@ -45,6 +45,8 @@ type Resolver interface {
Fetcher(ctx context.Context, ref string) (Fetcher, error)
// Pusher returns a new pusher for the provided reference
// The returned Pusher should satisfy content.Ingester and concurrent attempts
// to push the same blob using the Ingester API should result in ErrUnavailable.
Pusher(ctx context.Context, ref string) (Pusher, error)
}

View file

@ -67,7 +67,7 @@ func InitRootFS(ctx context.Context, name string, parent digest.Digest, readonly
return snapshotter.Prepare(ctx, name, parentS)
}
func createInitLayer(ctx context.Context, parent, initName string, initFn func(string) error, snapshotter snapshots.Snapshotter, mounter Mounter) (string, error) {
func createInitLayer(ctx context.Context, parent, initName string, initFn func(string) error, snapshotter snapshots.Snapshotter, mounter Mounter) (_ string, retErr error) {
initS := fmt.Sprintf("%s %s", parent, initName)
if _, err := snapshotter.Stat(ctx, initS); err == nil {
return initS, nil
@ -87,7 +87,7 @@ func createInitLayer(ctx context.Context, parent, initName string, initFn func(s
}
defer func() {
if err != nil {
if retErr != nil {
if rerr := snapshotter.Remove(ctx, td); rerr != nil {
log.G(ctx).Errorf("Failed to remove snapshot %s: %v", td, rerr)
}

View file

@ -63,14 +63,14 @@ type PlatformRuntime interface {
// ID of the runtime
ID() string
// Create creates a task with the provided id and options.
Create(ctx context.Context, id string, opts CreateOpts) (Task, error)
Create(ctx context.Context, taskID string, opts CreateOpts) (Task, error)
// Get returns a task.
Get(context.Context, string) (Task, error)
Get(ctx context.Context, taskID string) (Task, error)
// Tasks returns all the current tasks for the runtime.
// Any container runs at most one task at a time.
Tasks(context.Context, bool) ([]Task, error)
Tasks(ctx context.Context, all bool) ([]Task, error)
// Add adds a task into runtime.
Add(context.Context, Task) error
Add(ctx context.Context, task Task) error
// Delete remove a task.
Delete(context.Context, string)
Delete(ctx context.Context, taskID string)
}

View file

@ -36,19 +36,19 @@ type Process interface {
// ID of the process
ID() string
// State returns the process state
State(context.Context) (State, error)
State(ctx context.Context) (State, error)
// Kill signals a container
Kill(context.Context, uint32, bool) error
// Pty resizes the processes pty/console
ResizePty(context.Context, ConsoleSize) error
// CloseStdin closes the processes stdin
CloseIO(context.Context) error
Kill(ctx context.Context, signal uint32, all bool) error
// ResizePty resizes the processes pty/console
ResizePty(ctx context.Context, size ConsoleSize) error
// CloseIO closes the processes IO
CloseIO(ctx context.Context) error
// Start the container's user defined process
Start(context.Context) error
Start(ctx context.Context) error
// Wait for the process to exit
Wait(context.Context) (*Exit, error)
Wait(ctx context.Context) (*Exit, error)
// Delete deletes the process
Delete(context.Context) (*Exit, error)
Delete(ctx context.Context) (*Exit, error)
}
// Task is the runtime object for an executing container
@ -60,21 +60,21 @@ type Task interface {
// Namespace that the task exists in
Namespace() string
// Pause pauses the container process
Pause(context.Context) error
Pause(ctx context.Context) error
// Resume unpauses the container process
Resume(context.Context) error
Resume(ctx context.Context) error
// Exec adds a process into the container
Exec(context.Context, string, ExecOpts) (Process, error)
Exec(ctx context.Context, id string, opts ExecOpts) (Process, error)
// Pids returns all pids
Pids(context.Context) ([]ProcessInfo, error)
Pids(ctx context.Context) ([]ProcessInfo, error)
// Checkpoint checkpoints a container to an image with live system data
Checkpoint(context.Context, string, *types.Any) error
Checkpoint(ctx context.Context, path string, opts *types.Any) error
// Update sets the provided resources to a running task
Update(context.Context, *types.Any, map[string]string) error
Update(ctx context.Context, resources *types.Any, annotations map[string]string) error
// Process returns a process within the task for the provided id
Process(context.Context, string) (Process, error)
Process(ctx context.Context, id string) (Process, error)
// Stats returns runtime specific metrics for a task
Stats(context.Context) (*types.Any, error)
Stats(ctx context.Context) (*types.Any, error)
}
// ExecOpts provides additional options for additional processes running in a task

View file

@ -219,7 +219,7 @@ func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts
namespaces.WithNamespace(context.TODO(), namespace), cleanupTimeout)
defer deferCancel()
if kerr := s.KillShim(deferCtx); kerr != nil {
log.G(ctx).WithError(err).Error("failed to kill shim")
log.G(ctx).WithError(kerr).Error("failed to kill shim")
}
}
}()

View file

@ -182,10 +182,7 @@ func setupOOMScore(shimPid int) error {
return errors.Wrap(err, "get daemon OOM score")
}
shimScore := score + 1
if shimScore > sys.OOMScoreAdjMax {
shimScore = sys.OOMScoreAdjMax
}
if err := sys.SetOOMScore(shimPid, shimScore); err != nil {
if err := sys.AdjustOOMScore(shimPid, shimScore); err != nil {
return errors.Wrap(err, "set shim OOM score")
}
return nil

View file

@ -397,6 +397,9 @@ func (s *Service) ListPids(ctx context.Context, r *shimapi.ListPidsRequest) (*sh
return nil, errdefs.ToGRPC(err)
}
var processes []*task.ProcessInfo
s.mu.Lock()
defer s.mu.Unlock()
for _, pid := range pids {
pInfo := task.ProcessInfo{
Pid: pid,

View file

@ -26,7 +26,7 @@ import (
"github.com/containerd/console"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/runtime"
"github.com/containerd/containerd/pkg/process"
"github.com/containerd/fifo"
"github.com/pkg/errors"
)
@ -75,14 +75,14 @@ func (p *linuxPlatform) CopyConsole(ctx context.Context, console console.Console
return nil, err
}
cmd := runtime.NewBinaryCmd(uri, id, ns)
cmd := process.NewBinaryCmd(uri, id, ns)
// In case of unexpected errors during logging binary start, close open pipes
var filesToClose []*os.File
defer func() {
if retErr != nil {
runtime.CloseFiles(filesToClose...)
process.CloseFiles(filesToClose...)
}
}()

View file

@ -28,7 +28,7 @@ import (
"github.com/containerd/console"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/runtime"
"github.com/containerd/containerd/pkg/process"
"github.com/containerd/fifo"
"github.com/pkg/errors"
)
@ -63,15 +63,14 @@ func (p *unixPlatform) CopyConsole(ctx context.Context, console console.Console,
if err != nil {
return nil, err
}
cmd := runtime.NewBinaryCmd(uri, id, ns)
cmd := process.NewBinaryCmd(uri, id, ns)
// In case of unexpected errors during logging binary start, close open pipes
var filesToClose []*os.File
defer func() {
if retErr != nil {
runtime.CloseFiles(filesToClose...)
process.CloseFiles(filesToClose...)
}
}()

View file

@ -25,6 +25,7 @@ import (
ptypes "github.com/gogo/protobuf/types"
)
// Service defines the instrospection service interface
type Service interface {
Plugins(context.Context, []string) (*api.PluginsResponse, error)
Server(context.Context, *ptypes.Empty) (*api.ServerResponse, error)
@ -36,6 +37,7 @@ type introspectionRemote struct {
var _ = (Service)(&introspectionRemote{})
// NewIntrospectionServiceFromClient creates a new introspection service from an API client
func NewIntrospectionServiceFromClient(c api.IntrospectionClient) Service {
return &introspectionRemote{client: c}
}

View file

@ -54,6 +54,7 @@ func init() {
})
}
// Local is a local implementation of the introspection service
type Local struct {
mu sync.Mutex
plugins []api.Plugin
@ -62,6 +63,7 @@ type Local struct {
var _ = (api.IntrospectionClient)(&Local{})
// UpdateLocal updates the local introspection service
func (l *Local) UpdateLocal(root string, plugins []api.Plugin) {
l.mu.Lock()
defer l.mu.Unlock()
@ -69,6 +71,7 @@ func (l *Local) UpdateLocal(root string, plugins []api.Plugin) {
l.plugins = plugins
}
// Plugins returns the locally defined plugins
func (l *Local) Plugins(ctx context.Context, req *api.PluginsRequest, _ ...grpc.CallOption) (*api.PluginsResponse, error) {
filter, err := filters.ParseAll(req.Filters...)
if err != nil {
@ -96,6 +99,7 @@ func (l *Local) getPlugins() []api.Plugin {
return l.plugins
}
// Server returns the local server information
func (l *Local) Server(ctx context.Context, _ *ptypes.Empty, _ ...grpc.CallOption) (*api.ServerResponse, error) {
u, err := l.getUUID()
if err != nil {

View file

@ -20,9 +20,10 @@ import (
"path/filepath"
"strings"
"github.com/BurntSushi/toml"
"github.com/imdario/mergo"
"github.com/pelletier/go-toml"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/plugin"
@ -55,7 +56,7 @@ type Config struct {
// required plugin doesn't exist or fails to be initialized or started.
RequiredPlugins []string `toml:"required_plugins"`
// Plugins provides plugin specific configuration for the initialization of a plugin
Plugins map[string]toml.Primitive `toml:"plugins"`
Plugins map[string]toml.Tree `toml:"plugins"`
// OOMScore adjust the containerd's oom score
OOMScore int `toml:"oom_score"`
// Cgroup specifies cgroup information for the containerd daemon process
@ -94,7 +95,9 @@ func (c *Config) GetVersion() int {
// ValidateV2 validates the config for a v2 file
func (c *Config) ValidateV2() error {
if c.GetVersion() != 2 {
version := c.GetVersion()
if version < 2 {
logrus.Warnf("deprecated version : `%d`, please switch to version `2`", version)
return nil
}
for _, p := range c.DisabledPlugins {
@ -209,7 +212,7 @@ func (c *Config) Decode(p *plugin.Registration) (interface{}, error) {
if !ok {
return p.Config, nil
}
if err := toml.PrimitiveDecode(data, p.Config); err != nil {
if err := data.Unmarshal(p.Config); err != nil {
return nil, err
}
return p.Config, nil
@ -258,16 +261,26 @@ func LoadConfig(path string, out *Config) error {
out.Imports = append(out.Imports, path)
}
return out.ValidateV2()
err := out.ValidateV2()
if err != nil {
return errors.Wrapf(err, "failed to load TOML from %s", path)
}
return nil
}
// loadConfigFile decodes a TOML file at the given path
func loadConfigFile(path string) (*Config, error) {
config := &Config{}
_, err := toml.DecodeFile(path, &config)
file, err := toml.LoadFile(path)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to load TOML: %s", path)
}
if err := file.Unmarshal(config); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal TOML")
}
return config, nil
}

View file

@ -28,7 +28,8 @@ import (
const (
// UnpackKeyPrefix is the beginning of the key format used for snapshots that will have
// image content unpacked into them.
UnpackKeyPrefix = "extract"
UnpackKeyPrefix = "extract"
// UnpackKeyFormat is the format for the snapshotter keys used for extraction
UnpackKeyFormat = UnpackKeyPrefix + "-%s %s"
inheritedLabelsPrefix = "containerd.io/snapshot/"
labelSnapshotRef = "containerd.io/snapshot.ref"

View file

@ -1,35 +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 "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

@ -22,11 +22,14 @@ import (
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"syscall"
"unsafe"
"github.com/Microsoft/hcsshim"
"github.com/pkg/errors"
"golang.org/x/sys/windows"
)
@ -257,12 +260,71 @@ func windowsOpenSequential(path string, mode int, _ uint32) (fd windows.Handle,
return h, e
}
// ForceRemoveAll is the same as os.RemoveAll, but uses hcsshim.DestroyLayer in order
// to delete container layers.
// ForceRemoveAll is the same as os.RemoveAll, but is aware of io.containerd.snapshotter.v1.windows
// and uses hcsshim to unmount and delete container layers contained therein, in the correct order,
// when passed a containerd root data directory (i.e. the `--root` directory for containerd).
func ForceRemoveAll(path string) error {
info := hcsshim.DriverInfo{
HomeDir: filepath.Dir(path),
// snapshots/windows/windows.go init()
const snapshotPlugin = "io.containerd.snapshotter.v1" + "." + "windows"
// snapshots/windows/windows.go NewSnapshotter()
snapshotDir := filepath.Join(path, snapshotPlugin, "snapshots")
if stat, err := os.Stat(snapshotDir); err == nil && stat.IsDir() {
if err := cleanupWCOWLayers(snapshotDir); err != nil {
return errors.Wrapf(err, "failed to cleanup WCOW layers in %s", snapshotDir)
}
}
return hcsshim.DestroyLayer(info, filepath.Base(path))
return os.RemoveAll(path)
}
func cleanupWCOWLayers(root string) error {
// See snapshots/windows/windows.go getSnapshotDir()
var layerNums []int
if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if path != root && info.IsDir() {
if layerNum, err := strconv.Atoi(filepath.Base(path)); err == nil {
layerNums = append(layerNums, layerNum)
} else {
return err
}
return filepath.SkipDir
}
return nil
}); err != nil {
return err
}
sort.Sort(sort.Reverse(sort.IntSlice(layerNums)))
for _, layerNum := range layerNums {
if err := cleanupWCOWLayer(filepath.Join(root, strconv.Itoa(layerNum))); err != nil {
return err
}
}
return nil
}
func cleanupWCOWLayer(layerPath string) error {
info := hcsshim.DriverInfo{
HomeDir: filepath.Dir(layerPath),
}
// ERROR_DEV_NOT_EXIST is returned if the layer is not currently prepared.
if err := hcsshim.UnprepareLayer(info, filepath.Base(layerPath)); err != nil {
if hcserror, ok := err.(*hcsshim.HcsError); !ok || hcserror.Err != windows.ERROR_DEV_NOT_EXIST {
return errors.Wrapf(err, "failed to unprepare %s", layerPath)
}
}
if err := hcsshim.DeactivateLayer(info, filepath.Base(layerPath)); err != nil {
return errors.Wrapf(err, "failed to deactivate %s", layerPath)
}
if err := hcsshim.DestroyLayer(info, filepath.Base(layerPath)); err != nil {
return errors.Wrapf(err, "failed to destroy %s", layerPath)
}
return nil
}

View file

@ -1,5 +1,3 @@
// +build !windows
/*
Copyright The containerd Authors.
@ -24,17 +22,34 @@ import (
"os"
"strconv"
"strings"
"github.com/containerd/containerd/pkg/userns"
"golang.org/x/sys/unix"
)
const (
// OOMScoreMaxKillable is the maximum score keeping the process killable by the oom killer
OOMScoreMaxKillable = -999
// OOMScoreAdjMax is from OOM_SCORE_ADJ_MAX https://github.com/torvalds/linux/blob/master/include/uapi/linux/oom.h
// OOMScoreAdjMin is from OOM_SCORE_ADJ_MIN https://github.com/torvalds/linux/blob/v5.10/include/uapi/linux/oom.h#L9
OOMScoreAdjMin = -1000
// OOMScoreAdjMax is from OOM_SCORE_ADJ_MAX https://github.com/torvalds/linux/blob/v5.10/include/uapi/linux/oom.h#L10
OOMScoreAdjMax = 1000
)
// AdjustOOMScore sets the oom score for the provided pid. If the provided score
// is out of range (-1000 - 1000), it is clipped to the min/max value.
func AdjustOOMScore(pid, score int) error {
if score > OOMScoreAdjMax {
score = OOMScoreAdjMax
} else if score < OOMScoreAdjMin {
score = OOMScoreAdjMin
}
return SetOOMScore(pid, score)
}
// SetOOMScore sets the oom score for the provided pid
func SetOOMScore(pid, score int) error {
if score > OOMScoreAdjMax || score < OOMScoreAdjMin {
return fmt.Errorf("value out of range (%d): OOM score must be between %d and %d", score, OOMScoreAdjMin, OOMScoreAdjMax)
}
path := fmt.Sprintf("/proc/%d/oom_score_adj", pid)
f, err := os.OpenFile(path, os.O_WRONLY, 0)
if err != nil {
@ -42,7 +57,7 @@ func SetOOMScore(pid, score int) error {
}
defer f.Close()
if _, err = f.WriteString(strconv.Itoa(score)); err != nil {
if os.IsPermission(err) && (RunningInUserNS() || RunningUnprivileged()) {
if os.IsPermission(err) && (!runningPrivileged() || userns.RunningInUserNS()) {
return nil
}
return err
@ -50,7 +65,8 @@ func SetOOMScore(pid, score int) error {
return nil
}
// GetOOMScoreAdj gets the oom score for a process
// GetOOMScoreAdj gets the oom score for a process. It returns 0 (zero) if either
// no oom score is set, or a sore is set to 0.
func GetOOMScoreAdj(pid int) (int, error) {
path := fmt.Sprintf("/proc/%d/oom_score_adj", pid)
data, err := ioutil.ReadFile(path)
@ -59,3 +75,9 @@ func GetOOMScoreAdj(pid int) (int, error) {
}
return strconv.Atoi(strings.TrimSpace(string(data)))
}
// runningPrivileged returns true if the effective user ID of the
// calling process is 0
func runningPrivileged() bool {
return unix.Geteuid() == 0
}

View file

@ -1,3 +1,5 @@
// +build !linux
/*
Copyright The containerd Authors.
@ -17,10 +19,20 @@
package sys
const (
// OOMScoreAdjMax is not implemented on Windows
// OOMScoreMaxKillable is not implemented on non Linux
OOMScoreMaxKillable = 0
// OOMScoreAdjMax is not implemented on non Linux
OOMScoreAdjMax = 0
)
// AdjustOOMScore sets the oom score for the provided pid. If the provided score
// is out of range (-1000 - 1000), it is clipped to the min/max value.
//
// Not implemented on Windows
func AdjustOOMScore(pid, score int) error {
return nil
}
// SetOOMScore sets the oom score for the process
//
// Not implemented on Windows

View file

@ -1,5 +1,3 @@
// +build !windows
/*
Copyright The containerd Authors.
@ -18,16 +16,8 @@
package sys
import "golang.org/x/sys/unix"
import "github.com/containerd/containerd/pkg/userns"
// RunningPrivileged returns true if the effective user ID of the
// calling process is 0
func RunningPrivileged() bool {
return unix.Geteuid() == 0
}
// RunningUnprivileged returns true if the effective user ID of the
// calling process is not 0
func RunningUnprivileged() bool {
return !RunningPrivileged()
}
// RunningInUserNS detects whether we are currently running in a user namespace.
// Deprecated: use github.com/containerd/containerd/pkg/userns.RunningInUserNS instead.
var RunningInUserNS = userns.RunningInUserNS

View file

@ -451,11 +451,20 @@ func (t *task) Checkpoint(ctx context.Context, opts ...CheckpointTaskOpts) (Imag
}
request.Options = any
}
// make sure we pause it and resume after all other filesystem operations are completed
if err := t.Pause(ctx); err != nil {
status, err := t.Status(ctx)
if err != nil {
return nil, err
}
defer t.Resume(ctx)
if status.Status != Paused {
// make sure we pause it and resume after all other filesystem operations are completed
if err := t.Pause(ctx); err != nil {
return nil, err
}
defer t.Resume(ctx)
}
index := v1.Index{
Versioned: is.Versioned{
SchemaVersion: 2,

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.5.0-beta.0+unknown"
Version = "1.5.2+unknown"
// Revision is filled with the VCS (e.g. git) revision being used to build
// the program at linking time.