vendor: update containerd to 1.0.1-rc0

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
docker-18.09
Tonis Tiigi 2018-01-11 17:49:09 -08:00
parent 99b32f1878
commit b8dc00de71
67 changed files with 1590 additions and 1019 deletions

View File

@ -211,7 +211,7 @@ export JAEGER_TRACE=0.0.0.0:6831
### Supported runc version ### Supported runc version
During development, BuildKit is tested with the version of runc that is being used by the containerd repository. Please refer to [runc.md](https://github.com/containerd/containerd/blob/v1.0.0/RUNC.md) for more information. During development, BuildKit is tested with the version of runc that is being used by the containerd repository. Please refer to [runc.md](https://github.com/containerd/containerd/blob/v1.0.1-rc.0/RUNC.md) for more information.
### Contributing ### Contributing

View File

@ -17,8 +17,8 @@ type buildOpt struct {
func main() { func main() {
var opt buildOpt var opt buildOpt
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker") flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
flag.StringVar(&opt.containerd, "containerd", "v1.0.0", "containerd version") flag.StringVar(&opt.containerd, "containerd", "v1.0.1-rc.0", "containerd version")
flag.StringVar(&opt.runc, "runc", "74a17296470088de3805e138d3d87c62e613dfc4", "runc version") flag.StringVar(&opt.runc, "runc", "7f24b40cc5423969b4554ef04ba0b00e2b4ba010", "runc version")
flag.Parse() flag.Parse()
bk := buildkit(opt) bk := buildkit(opt)

View File

@ -17,8 +17,8 @@ type buildOpt struct {
func main() { func main() {
var opt buildOpt var opt buildOpt
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker") flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
flag.StringVar(&opt.containerd, "containerd", "v1.0.0", "containerd version") flag.StringVar(&opt.containerd, "containerd", "v1.0.1-rc.0", "containerd version")
flag.StringVar(&opt.runc, "runc", "74a17296470088de3805e138d3d87c62e613dfc4", "runc version") flag.StringVar(&opt.runc, "runc", "7f24b40cc5423969b4554ef04ba0b00e2b4ba010", "runc version")
flag.Parse() flag.Parse()
bk := buildkit(opt) bk := buildkit(opt)

View File

@ -17,8 +17,8 @@ type buildOpt struct {
func main() { func main() {
var opt buildOpt var opt buildOpt
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker") flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
flag.StringVar(&opt.containerd, "containerd", "v1.0.0", "containerd version") flag.StringVar(&opt.containerd, "containerd", "v1.0.1-rc.0", "containerd version")
flag.StringVar(&opt.runc, "runc", "74a17296470088de3805e138d3d87c62e613dfc4", "runc version") flag.StringVar(&opt.runc, "runc", "7f24b40cc5423969b4554ef04ba0b00e2b4ba010", "runc version")
flag.Parse() flag.Parse()
bk := buildkit(opt) bk := buildkit(opt)

View File

@ -18,8 +18,8 @@ type buildOpt struct {
func main() { func main() {
var opt buildOpt var opt buildOpt
flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker") flag.BoolVar(&opt.withContainerd, "with-containerd", true, "enable containerd worker")
flag.StringVar(&opt.containerd, "containerd", "v1.0.0", "containerd version") flag.StringVar(&opt.containerd, "containerd", "v1.0.1-rc.0", "containerd version")
flag.StringVar(&opt.runc, "runc", "74a17296470088de3805e138d3d87c62e613dfc4", "runc version") flag.StringVar(&opt.runc, "runc", "7f24b40cc5423969b4554ef04ba0b00e2b4ba010", "runc version")
flag.StringVar(&opt.buildkit, "buildkit", "master", "buildkit version") flag.StringVar(&opt.buildkit, "buildkit", "master", "buildkit version")
flag.Parse() flag.Parse()

View File

@ -85,7 +85,7 @@ func (w containerdExecutor) Exec(ctx context.Context, meta executor.Meta, root c
stdin = &emptyReadCloser{} stdin = &emptyReadCloser{}
} }
task, err := container.NewTask(ctx, cio.NewIO(stdin, stdout, stderr), containerd.WithRootFS(rootMounts)) task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStreams(stdin, stdout, stderr)), containerd.WithRootFS(rootMounts))
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,5 +1,5 @@
ARG RUNC_VERSION=74a17296470088de3805e138d3d87c62e613dfc4 ARG RUNC_VERSION=7f24b40cc5423969b4554ef04ba0b00e2b4ba010
ARG CONTAINERD_VERSION=v1.0.0 ARG CONTAINERD_VERSION=v1.0.1-rc.0
# available targets: buildkitd, buildkitd.oci_only, buildkitd.containerd_only # available targets: buildkitd, buildkitd.oci_only, buildkitd.containerd_only
ARG BUILDKIT_TARGET=buildkitd ARG BUILDKIT_TARGET=buildkitd
ARG REGISTRY_VERSION=2.6 ARG REGISTRY_VERSION=2.6

View File

@ -6,21 +6,21 @@ github.com/davecgh/go-spew v1.1.0
github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib v1.0.0
golang.org/x/sys 9aade4d3a3b7e6d876cd3823ad20ec45fc035402 # update to 7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce wiht pkg/signal change golang.org/x/sys 9aade4d3a3b7e6d876cd3823ad20ec45fc035402 # update to 7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce wiht pkg/signal change
github.com/containerd/containerd v1.0.0 github.com/containerd/containerd 90553efdef0678b2609aed74926a487f8ff58d1a
github.com/containerd/typeurl f6943554a7e7e88b3c14aad190bf05932da84788 github.com/containerd/typeurl f6943554a7e7e88b3c14aad190bf05932da84788
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
github.com/sirupsen/logrus v1.0.0 github.com/sirupsen/logrus v1.0.0
google.golang.org/grpc v1.7.2 google.golang.org/grpc v1.7.4
github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448 github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448
golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6 golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6
github.com/gogo/protobuf v0.5 github.com/gogo/protobuf v0.5
github.com/golang/protobuf 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 github.com/golang/protobuf 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9
github.com/containerd/continuity cf279e6ac893682272b4479d4c67fd3abf878b4e github.com/containerd/continuity cf279e6ac893682272b4479d4c67fd3abf878b4e
github.com/opencontainers/image-spec v1.0.0 github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runc 74a17296470088de3805e138d3d87c62e613dfc4 github.com/opencontainers/runc 7f24b40cc5423969b4554ef04ba0b00e2b4ba010
github.com/Microsoft/go-winio v0.4.4 github.com/Microsoft/go-winio v0.4.5
github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6 github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6
github.com/opencontainers/runtime-spec v1.0.0 github.com/opencontainers/runtime-spec v1.0.1
github.com/containerd/go-runc ed1cbe1fc31f5fb2359d3a54b6330d1a097858b7 github.com/containerd/go-runc ed1cbe1fc31f5fb2359d3a54b6330d1a097858b7
github.com/containerd/console 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e github.com/containerd/console 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944

View File

@ -68,10 +68,20 @@ func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
return &BackupStreamReader{r, 0} return &BackupStreamReader{r, 0}
} }
// Next returns the next backup stream and prepares for calls to Write(). It skips the remainder of the current stream if // Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
// it was not completely read. // it was not completely read.
func (r *BackupStreamReader) Next() (*BackupHeader, error) { func (r *BackupStreamReader) Next() (*BackupHeader, error) {
if r.bytesLeft > 0 { if r.bytesLeft > 0 {
if s, ok := r.r.(io.Seeker); ok {
// Make sure Seek on io.SeekCurrent sometimes succeeds
// before trying the actual seek.
if _, err := s.Seek(0, io.SeekCurrent); err == nil {
if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
return nil, err
}
r.bytesLeft = 0
}
}
if _, err := io.Copy(ioutil.Discard, r); err != nil { if _, err := io.Copy(ioutil.Discard, r); err != nil {
return nil, err return nil, err
} }
@ -220,7 +230,7 @@ type BackupFileWriter struct {
ctx uintptr ctx uintptr
} }
// NewBackupFileWrtier returns a new BackupFileWriter from a file handle. If includeSecurity is true, // NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true,
// Write() will attempt to restore the security descriptor from the stream. // Write() will attempt to restore the security descriptor from the stream.
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter { func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
w := &BackupFileWriter{f, includeSecurity, 0} w := &BackupFileWriter{f, includeSecurity, 0}

137
vendor/github.com/Microsoft/go-winio/ea.go generated vendored Normal file
View File

@ -0,0 +1,137 @@
package winio
import (
"bytes"
"encoding/binary"
"errors"
)
type fileFullEaInformation struct {
NextEntryOffset uint32
Flags uint8
NameLength uint8
ValueLength uint16
}
var (
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
errEaNameTooLarge = errors.New("extended attribute name too large")
errEaValueTooLarge = errors.New("extended attribute value too large")
)
// ExtendedAttribute represents a single Windows EA.
type ExtendedAttribute struct {
Name string
Value []byte
Flags uint8
}
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
var info fileFullEaInformation
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
if err != nil {
err = errInvalidEaBuffer
return
}
nameOffset := fileFullEaInformationSize
nameLen := int(info.NameLength)
valueOffset := nameOffset + int(info.NameLength) + 1
valueLen := int(info.ValueLength)
nextOffset := int(info.NextEntryOffset)
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
err = errInvalidEaBuffer
return
}
ea.Name = string(b[nameOffset : nameOffset+nameLen])
ea.Value = b[valueOffset : valueOffset+valueLen]
ea.Flags = info.Flags
if info.NextEntryOffset != 0 {
nb = b[info.NextEntryOffset:]
}
return
}
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
for len(b) != 0 {
ea, nb, err := parseEa(b)
if err != nil {
return nil, err
}
eas = append(eas, ea)
b = nb
}
return
}
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
if int(uint8(len(ea.Name))) != len(ea.Name) {
return errEaNameTooLarge
}
if int(uint16(len(ea.Value))) != len(ea.Value) {
return errEaValueTooLarge
}
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
withPadding := (entrySize + 3) &^ 3
nextOffset := uint32(0)
if !last {
nextOffset = withPadding
}
info := fileFullEaInformation{
NextEntryOffset: nextOffset,
Flags: ea.Flags,
NameLength: uint8(len(ea.Name)),
ValueLength: uint16(len(ea.Value)),
}
err := binary.Write(buf, binary.LittleEndian, &info)
if err != nil {
return err
}
_, err = buf.Write([]byte(ea.Name))
if err != nil {
return err
}
err = buf.WriteByte(0)
if err != nil {
return err
}
_, err = buf.Write(ea.Value)
if err != nil {
return err
}
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
if err != nil {
return err
}
return nil
}
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
// buffer for use with BackupWrite, ZwSetEaFile, etc.
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
var buf bytes.Buffer
for i := range eas {
last := false
if i == len(eas)-1 {
last = true
}
err := writeEa(&buf, &eas[i], last)
if err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}

View File

@ -78,6 +78,7 @@ func initIo() {
type win32File struct { type win32File struct {
handle syscall.Handle handle syscall.Handle
wg sync.WaitGroup wg sync.WaitGroup
wgLock sync.RWMutex
closing atomicBool closing atomicBool
readDeadline deadlineHandler readDeadline deadlineHandler
writeDeadline deadlineHandler writeDeadline deadlineHandler
@ -114,14 +115,18 @@ func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
// closeHandle closes the resources associated with a Win32 handle // closeHandle closes the resources associated with a Win32 handle
func (f *win32File) closeHandle() { func (f *win32File) closeHandle() {
f.wgLock.Lock()
// Atomically set that we are closing, releasing the resources only once. // Atomically set that we are closing, releasing the resources only once.
if !f.closing.swap(true) { if !f.closing.swap(true) {
f.wgLock.Unlock()
// cancel all IO and wait for it to complete // cancel all IO and wait for it to complete
cancelIoEx(f.handle, nil) cancelIoEx(f.handle, nil)
f.wg.Wait() f.wg.Wait()
// at this point, no new IO can start // at this point, no new IO can start
syscall.Close(f.handle) syscall.Close(f.handle)
f.handle = 0 f.handle = 0
} else {
f.wgLock.Unlock()
} }
} }
@ -134,10 +139,13 @@ func (f *win32File) Close() error {
// prepareIo prepares for a new IO operation. // prepareIo prepares for a new IO operation.
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. // The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
func (f *win32File) prepareIo() (*ioOperation, error) { func (f *win32File) prepareIo() (*ioOperation, error) {
f.wgLock.RLock()
if f.closing.isSet() { if f.closing.isSet() {
f.wgLock.RUnlock()
return nil, ErrFileClosed return nil, ErrFileClosed
} }
f.wg.Add(1) f.wg.Add(1)
f.wgLock.RUnlock()
c := &ioOperation{} c := &ioOperation{}
c.ch = make(chan ioResult) c.ch = make(chan ioResult)
return c, nil return c, nil

View File

@ -265,9 +265,9 @@ func (l *win32PipeListener) listenerRoutine() {
if err == nil { if err == nil {
// Wait for the client to connect. // Wait for the client to connect.
ch := make(chan error) ch := make(chan error)
go func() { go func(p *win32File) {
ch <- connectPipe(p) ch <- connectPipe(p)
}() }(p)
select { select {
case err = <-ch: case err = <-ch:
if err != nil { if err != nil {

View File

@ -4,6 +4,7 @@
[![Build Status](https://travis-ci.org/containerd/containerd.svg?branch=master)](https://travis-ci.org/containerd/containerd) [![Build Status](https://travis-ci.org/containerd/containerd.svg?branch=master)](https://travis-ci.org/containerd/containerd)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd?ref=badge_shield) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fcontainerd%2Fcontainerd?ref=badge_shield)
[![Go Report Card](https://goreportcard.com/badge/github.com/containerd/containerd)](https://goreportcard.com/report/github.com/containerd/containerd) [![Go Report Card](https://goreportcard.com/badge/github.com/containerd/containerd)](https://goreportcard.com/report/github.com/containerd/containerd)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/1271/badge)](https://bestpractices.coreinfrastructure.org/projects/1271)
containerd is an industry-standard container runtime with an emphasis on simplicity, robustness and portability. It is available as a daemon for Linux and Windows, which can manage the complete container lifecycle of its host system: image transfer and storage, container execution and supervision, low-level storage and network attachments, etc. containerd is an industry-standard container runtime with an emphasis on simplicity, robustness and portability. It is available as a daemon for Linux and Windows, which can manage the complete container lifecycle of its host system: image transfer and storage, container execution and supervision, low-level storage and network attachments, etc.
@ -13,7 +14,7 @@ containerd is designed to be embedded into a larger system, rather than being us
## Getting Started ## Getting Started
See our documentation on [containerd.io](containerd.io): See our documentation on [containerd.io](https://containerd.io):
* [for ops and admins](docs/ops.md) * [for ops and admins](docs/ops.md)
* [namespaces](docs/namespaces.md) * [namespaces](docs/namespaces.md)
* [client options](docs/client-opts.md) * [client options](docs/client-opts.md)

View File

@ -0,0 +1,52 @@
// +build windows
package archive
import (
"strconv"
"strings"
"time"
"github.com/dmcgowan/go-tar"
)
// Forked from https://github.com/golang/go/blob/master/src/archive/tar/strconv.go
// as archive/tar doesn't support CreationTime, but does handle PAX time parsing,
// and there's no need to re-invent the wheel.
// parsePAXTime takes a string of the form %d.%d as described in the PAX
// specification. Note that this implementation allows for negative timestamps,
// which is allowed for by the PAX specification, but not always portable.
func parsePAXTime(s string) (time.Time, error) {
const maxNanoSecondDigits = 9
// Split string into seconds and sub-seconds parts.
ss, sn := s, ""
if pos := strings.IndexByte(s, '.'); pos >= 0 {
ss, sn = s[:pos], s[pos+1:]
}
// Parse the seconds.
secs, err := strconv.ParseInt(ss, 10, 64)
if err != nil {
return time.Time{}, tar.ErrHeader
}
if len(sn) == 0 {
return time.Unix(secs, 0), nil // No sub-second values
}
// Parse the nanoseconds.
if strings.Trim(sn, "0123456789") != "" {
return time.Time{}, tar.ErrHeader
}
if len(sn) < maxNanoSecondDigits {
sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad
} else {
sn = sn[:maxNanoSecondDigits] // Right truncate
}
nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed
if len(ss) > 0 && ss[0] == '-' {
return time.Unix(secs, -nsecs), nil // Negative correction
}
return time.Unix(secs, nsecs), nil
}

View File

@ -87,12 +87,23 @@ const (
// Apply applies a tar stream of an OCI style diff tar. // Apply applies a tar stream of an OCI style diff tar.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets // See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func Apply(ctx context.Context, root string, r io.Reader) (int64, error) { func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int64, error) {
root = filepath.Clean(root) root = filepath.Clean(root)
var options ApplyOptions
for _, opt := range opts {
if err := opt(&options); err != nil {
return 0, errors.Wrap(err, "failed to apply option")
}
}
return apply(ctx, root, tar.NewReader(r), options)
}
// applyNaive applies a tar stream of an OCI style diff tar.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
var ( var (
tr = tar.NewReader(r)
size int64
dirs []*tar.Header dirs []*tar.Header
// Used for handling opaque directory markers which // Used for handling opaque directory markers which
@ -285,161 +296,6 @@ func Apply(ctx context.Context, root string, r io.Reader) (int64, error) {
return size, nil return size, nil
} }
type changeWriter struct {
tw *tar.Writer
source string
whiteoutT time.Time
inodeSrc map[uint64]string
inodeRefs map[uint64][]string
}
func newChangeWriter(w io.Writer, source string) *changeWriter {
return &changeWriter{
tw: tar.NewWriter(w),
source: source,
whiteoutT: time.Now(),
inodeSrc: map[uint64]string{},
inodeRefs: map[uint64][]string{},
}
}
func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if k == fs.ChangeKindDelete {
whiteOutDir := filepath.Dir(p)
whiteOutBase := filepath.Base(p)
whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
hdr := &tar.Header{
Name: whiteOut[1:],
Size: 0,
ModTime: cw.whiteoutT,
AccessTime: cw.whiteoutT,
ChangeTime: cw.whiteoutT,
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write whiteout header")
}
} else {
var (
link string
err error
source = filepath.Join(cw.source, p)
)
if f.Mode()&os.ModeSymlink != 0 {
if link, err = os.Readlink(source); err != nil {
return err
}
}
hdr, err := tar.FileInfoHeader(f, link)
if err != nil {
return err
}
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
name := p
if strings.HasPrefix(name, string(filepath.Separator)) {
name, err = filepath.Rel(string(filepath.Separator), name)
if err != nil {
return errors.Wrap(err, "failed to make path relative")
}
}
name, err = tarName(name)
if err != nil {
return errors.Wrap(err, "cannot canonicalize path")
}
// suffix with '/' for directories
if f.IsDir() && !strings.HasSuffix(name, "/") {
name += "/"
}
hdr.Name = name
if err := setHeaderForSpecialDevice(hdr, name, f); err != nil {
return errors.Wrap(err, "failed to set device headers")
}
// additionalLinks stores file names which must be linked to
// this file when this file is added
var additionalLinks []string
inode, isHardlink := fs.GetLinkInfo(f)
if isHardlink {
// If the inode has a source, always link to it
if source, ok := cw.inodeSrc[inode]; ok {
hdr.Typeflag = tar.TypeLink
hdr.Linkname = source
hdr.Size = 0
} else {
if k == fs.ChangeKindUnmodified {
cw.inodeRefs[inode] = append(cw.inodeRefs[inode], name)
return nil
}
cw.inodeSrc[inode] = name
additionalLinks = cw.inodeRefs[inode]
delete(cw.inodeRefs, inode)
}
} else if k == fs.ChangeKindUnmodified {
// Nothing to write to diff
return nil
}
if capability, err := getxattr(source, "security.capability"); err != nil {
return errors.Wrap(err, "failed to get capabilities xattr")
} else if capability != nil {
if hdr.PAXRecords == nil {
hdr.PAXRecords = map[string]string{}
}
hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability)
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write file header")
}
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
file, err := open(source)
if err != nil {
return errors.Wrapf(err, "failed to open path: %v", source)
}
defer file.Close()
buf := bufferPool.Get().(*[]byte)
n, err := io.CopyBuffer(cw.tw, file, *buf)
bufferPool.Put(buf)
if err != nil {
return errors.Wrap(err, "failed to copy")
}
if n != hdr.Size {
return errors.New("short write copying file")
}
}
if additionalLinks != nil {
source = hdr.Name
for _, extra := range additionalLinks {
hdr.Name = extra
hdr.Typeflag = tar.TypeLink
hdr.Linkname = source
hdr.Size = 0
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write file header")
}
}
}
}
return nil
}
func (cw *changeWriter) Close() error {
if err := cw.tw.Close(); err != nil {
return errors.Wrap(err, "failed to close tar writer")
}
return nil
}
func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header, reader io.Reader) error { func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header, reader io.Reader) error {
// hdr.Mode is in linux format, which we can use for syscalls, // hdr.Mode is in linux format, which we can use for syscalls,
// but for os.Foo() calls we need the mode converted to os.FileMode, // but for os.Foo() calls we need the mode converted to os.FileMode,
@ -533,6 +389,194 @@ func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header
return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)) return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime))
} }
type changeWriter struct {
tw *tar.Writer
source string
whiteoutT time.Time
inodeSrc map[uint64]string
inodeRefs map[uint64][]string
addedDirs map[string]struct{}
}
func newChangeWriter(w io.Writer, source string) *changeWriter {
return &changeWriter{
tw: tar.NewWriter(w),
source: source,
whiteoutT: time.Now(),
inodeSrc: map[uint64]string{},
inodeRefs: map[uint64][]string{},
addedDirs: map[string]struct{}{},
}
}
func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if k == fs.ChangeKindDelete {
whiteOutDir := filepath.Dir(p)
whiteOutBase := filepath.Base(p)
whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
hdr := &tar.Header{
Typeflag: tar.TypeReg,
Name: whiteOut[1:],
Size: 0,
ModTime: cw.whiteoutT,
AccessTime: cw.whiteoutT,
ChangeTime: cw.whiteoutT,
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write whiteout header")
}
} else {
var (
link string
err error
source = filepath.Join(cw.source, p)
)
if f.Mode()&os.ModeSymlink != 0 {
if link, err = os.Readlink(source); err != nil {
return err
}
}
hdr, err := tar.FileInfoHeader(f, link)
if err != nil {
return err
}
hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
name := p
if strings.HasPrefix(name, string(filepath.Separator)) {
name, err = filepath.Rel(string(filepath.Separator), name)
if err != nil {
return errors.Wrap(err, "failed to make path relative")
}
}
name, err = tarName(name)
if err != nil {
return errors.Wrap(err, "cannot canonicalize path")
}
// suffix with '/' for directories
if f.IsDir() && !strings.HasSuffix(name, "/") {
name += "/"
}
hdr.Name = name
if err := setHeaderForSpecialDevice(hdr, name, f); err != nil {
return errors.Wrap(err, "failed to set device headers")
}
// additionalLinks stores file names which must be linked to
// this file when this file is added
var additionalLinks []string
inode, isHardlink := fs.GetLinkInfo(f)
if isHardlink {
// If the inode has a source, always link to it
if source, ok := cw.inodeSrc[inode]; ok {
hdr.Typeflag = tar.TypeLink
hdr.Linkname = source
hdr.Size = 0
} else {
if k == fs.ChangeKindUnmodified {
cw.inodeRefs[inode] = append(cw.inodeRefs[inode], name)
return nil
}
cw.inodeSrc[inode] = name
additionalLinks = cw.inodeRefs[inode]
delete(cw.inodeRefs, inode)
}
} else if k == fs.ChangeKindUnmodified {
// Nothing to write to diff
return nil
}
if capability, err := getxattr(source, "security.capability"); err != nil {
return errors.Wrap(err, "failed to get capabilities xattr")
} else if capability != nil {
if hdr.PAXRecords == nil {
hdr.PAXRecords = map[string]string{}
}
hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability)
}
if err := cw.includeParents(hdr); err != nil {
return err
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write file header")
}
if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
file, err := open(source)
if err != nil {
return errors.Wrapf(err, "failed to open path: %v", source)
}
defer file.Close()
buf := bufferPool.Get().(*[]byte)
n, err := io.CopyBuffer(cw.tw, file, *buf)
bufferPool.Put(buf)
if err != nil {
return errors.Wrap(err, "failed to copy")
}
if n != hdr.Size {
return errors.New("short write copying file")
}
}
if additionalLinks != nil {
source = hdr.Name
for _, extra := range additionalLinks {
hdr.Name = extra
hdr.Typeflag = tar.TypeLink
hdr.Linkname = source
hdr.Size = 0
if err := cw.includeParents(hdr); err != nil {
return err
}
if err := cw.tw.WriteHeader(hdr); err != nil {
return errors.Wrap(err, "failed to write file header")
}
}
}
}
return nil
}
func (cw *changeWriter) Close() error {
if err := cw.tw.Close(); err != nil {
return errors.Wrap(err, "failed to close tar writer")
}
return nil
}
func (cw *changeWriter) includeParents(hdr *tar.Header) error {
name := strings.TrimRight(hdr.Name, "/")
fname := filepath.Join(cw.source, name)
parent := filepath.Dir(name)
pname := filepath.Join(cw.source, parent)
// Do not include root directory as parent
if fname != cw.source && pname != cw.source {
_, ok := cw.addedDirs[parent]
if !ok {
cw.addedDirs[parent] = struct{}{}
fi, err := os.Stat(pname)
if err != nil {
return err
}
if err := cw.HandleChange(fs.ChangeKindModify, parent, fi, nil); err != nil {
return err
}
}
}
return nil
}
func copyBuffered(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) { func copyBuffered(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) {
buf := bufferPool.Get().(*[]byte) buf := bufferPool.Get().(*[]byte)
defer bufferPool.Put(buf) defer bufferPool.Put(buf)

View File

@ -0,0 +1,4 @@
package archive
// ApplyOpt allows setting mutable archive apply properties on creation
type ApplyOpt func(options *ApplyOptions) error

View File

@ -0,0 +1,7 @@
// +build !windows
package archive
// ApplyOptions provides additional options for an Apply operation
type ApplyOptions struct {
}

View File

@ -0,0 +1,28 @@
// +build windows
package archive
// ApplyOptions provides additional options for an Apply operation
type ApplyOptions struct {
ParentLayerPaths []string // Parent layer paths used for Windows layer apply
IsWindowsContainerLayer bool // True if the tar stream to be applied is a Windows Container Layer
}
// WithParentLayers adds parent layers to the apply process this is required
// for all Windows layers except the base layer.
func WithParentLayers(parentPaths []string) ApplyOpt {
return func(options *ApplyOptions) error {
options.ParentLayerPaths = parentPaths
return nil
}
}
// AsWindowsContainerLayer indicates that the tar stream to apply is that of
// a Windows Container Layer. The caller must be holding SeBackupPrivilege and
// SeRestorePrivilege.
func AsWindowsContainerLayer() ApplyOpt {
return func(options *ApplyOptions) error {
options.IsWindowsContainerLayer = true
return nil
}
}

View File

@ -3,6 +3,7 @@
package archive package archive
import ( import (
"context"
"os" "os"
"sync" "sync"
"syscall" "syscall"
@ -28,11 +29,14 @@ func setHeaderForSpecialDevice(hdr *tar.Header, name string, fi os.FileInfo) err
return errors.New("unsupported stat type") return errors.New("unsupported stat type")
} }
// Rdev is int32 on darwin/bsd, int64 on linux/solaris
rdev := uint64(s.Rdev) // nolint: unconvert
// Currently go does not fill in the major/minors // Currently go does not fill in the major/minors
if s.Mode&syscall.S_IFBLK != 0 || if s.Mode&syscall.S_IFBLK != 0 ||
s.Mode&syscall.S_IFCHR != 0 { s.Mode&syscall.S_IFCHR != 0 {
hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) hdr.Devmajor = int64(unix.Major(rdev))
hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) hdr.Devminor = int64(unix.Minor(rdev))
} }
return nil return nil
@ -128,3 +132,9 @@ func getxattr(path, attr string) ([]byte, error) {
func setxattr(path, key, value string) error { func setxattr(path, key, value string) error {
return sysx.LSetxattr(path, key, []byte(value), 0) return sysx.LSetxattr(path, key, []byte(value), 0)
} }
// apply applies a tar stream of an OCI style diff tar.
// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
func apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
return applyNaive(ctx, root, tr, options)
}

View File

@ -1,15 +1,55 @@
// +build windows
package archive package archive
import ( import (
"bufio"
"context"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"path"
"path/filepath"
"strconv"
"strings" "strings"
"syscall"
"github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/sys" "github.com/containerd/containerd/sys"
"github.com/dmcgowan/go-tar" "github.com/dmcgowan/go-tar"
) )
const (
// MSWINDOWS pax vendor extensions
hdrMSWindowsPrefix = "MSWINDOWS."
hdrFileAttributes = hdrMSWindowsPrefix + "fileattr"
hdrSecurityDescriptor = hdrMSWindowsPrefix + "sd"
hdrRawSecurityDescriptor = hdrMSWindowsPrefix + "rawsd"
hdrMountPoint = hdrMSWindowsPrefix + "mountpoint"
hdrEaPrefix = hdrMSWindowsPrefix + "xattr."
// LIBARCHIVE pax vendor extensions
hdrLibArchivePrefix = "LIBARCHIVE."
hdrCreateTime = hdrLibArchivePrefix + "creationtime"
)
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 // tarName returns platform-specific filepath
// to canonical posix-style path for tar archival. p is relative // to canonical posix-style path for tar archival. p is relative
// path. // path.
@ -101,3 +141,286 @@ func setxattr(path, key, value string) error {
// since xattrs should not exist in windows diff archives // since xattrs should not exist in windows diff archives
return errors.New("xattrs not supported on Windows") return errors.New("xattrs not supported on Windows")
} }
// apply 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 apply(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
if options.IsWindowsContainerLayer {
return applyWindowsLayer(ctx, root, tr, options)
}
return applyNaive(ctx, root, tr, options)
}
// 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, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
home, id := filepath.Split(root)
info := hcsshim.DriverInfo{
HomeDir: home,
}
w, err := hcsshim.NewLayerWriter(info, id, options.ParentLayerPaths)
if err != nil {
return 0, err
}
defer func() {
if err := w.Close(); err != nil {
log.G(ctx).Errorf("failed to close layer writer: %v", err)
}
}()
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 := 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
}
// fileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
// WriteTarFileFromBackupStream.
func fileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
name = hdr.Name
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
size = hdr.Size
}
fileInfo = &winio.FileBasicInfo{
LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
// Default CreationTime to ModTime, updated below if MSWINDOWS.createtime exists
CreationTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
}
if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok {
attr, err := strconv.ParseUint(attrStr, 10, 32)
if err != nil {
return "", 0, nil, err
}
fileInfo.FileAttributes = uintptr(attr)
} else {
if hdr.Typeflag == tar.TypeDir {
fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
}
}
if createStr, ok := hdr.PAXRecords[hdrCreateTime]; ok {
createTime, err := parsePAXTime(createStr)
if err != nil {
return "", 0, nil, err
}
fileInfo.CreationTime = syscall.NsecToFiletime(createTime.UnixNano())
}
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 writeBackupStreamFromTarFile(buf, t, hdr)
}
// writeBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
// tar file entries in order to collect all the alternate data streams for the file, it returns the next
// tar file that was not processed, or io.EOF is there are no more.
func writeBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
bw := winio.NewBackupStreamWriter(w)
var sd []byte
var err error
// Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written
// by this library will have raw binary for the security descriptor.
if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok {
sd, err = winio.SddlToSecurityDescriptor(sddl)
if err != nil {
return nil, err
}
}
if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok {
sd, err = base64.StdEncoding.DecodeString(sdraw)
if err != nil {
return nil, err
}
}
if len(sd) != 0 {
bhdr := winio.BackupHeader{
Id: winio.BackupSecurity,
Size: int64(len(sd)),
}
err := bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = bw.Write(sd)
if err != nil {
return nil, err
}
}
var eas []winio.ExtendedAttribute
for k, v := range hdr.PAXRecords {
if !strings.HasPrefix(k, hdrEaPrefix) {
continue
}
data, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return nil, err
}
eas = append(eas, winio.ExtendedAttribute{
Name: k[len(hdrEaPrefix):],
Value: data,
})
}
if len(eas) != 0 {
eadata, err := winio.EncodeExtendedAttributes(eas)
if err != nil {
return nil, err
}
bhdr := winio.BackupHeader{
Id: winio.BackupEaData,
Size: int64(len(eadata)),
}
err = bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = bw.Write(eadata)
if err != nil {
return nil, err
}
}
if hdr.Typeflag == tar.TypeSymlink {
_, isMountPoint := hdr.PAXRecords[hdrMountPoint]
rp := winio.ReparsePoint{
Target: filepath.FromSlash(hdr.Linkname),
IsMountPoint: isMountPoint,
}
reparse := winio.EncodeReparsePoint(&rp)
bhdr := winio.BackupHeader{
Id: winio.BackupReparseData,
Size: int64(len(reparse)),
}
err := bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = bw.Write(reparse)
if err != nil {
return nil, err
}
}
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
bhdr := winio.BackupHeader{
Id: winio.BackupData,
Size: hdr.Size,
}
err := bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = io.Copy(bw, t)
if err != nil {
return nil, err
}
}
// Copy all the alternate data streams and return the next non-ADS header.
for {
ahdr, err := t.Next()
if err != nil {
return nil, err
}
if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
return ahdr, nil
}
bhdr := winio.BackupHeader{
Id: winio.BackupAlternateData,
Size: ahdr.Size,
Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
}
err = bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = io.Copy(bw, t)
if err != nil {
return nil, err
}
}
}

View File

@ -8,7 +8,7 @@ import (
"sync" "sync"
) )
// Config holds the io configurations. // Config holds the IO configurations.
type Config struct { type Config struct {
// Terminal is true if one has been allocated // Terminal is true if one has been allocated
Terminal bool Terminal bool
@ -24,48 +24,17 @@ type Config struct {
type IO interface { type IO interface {
// Config returns the IO configuration. // Config returns the IO configuration.
Config() Config Config() Config
// Cancel aborts all current io operations // Cancel aborts all current io operations.
Cancel() Cancel()
// Wait blocks until all io copy operations have completed // Wait blocks until all io copy operations have completed.
Wait() Wait()
// Close cleans up all open io resources // Close cleans up all open io resources. Cancel() is always called before
// Close()
Close() error Close() error
} }
// cio is a basic container IO implementation. // Creator creates new IO sets for a task
type cio struct { type Creator func(id string) (IO, error)
config Config
closer *wgCloser
}
func (c *cio) Config() Config {
return c.config
}
func (c *cio) Cancel() {
if c.closer == nil {
return
}
c.closer.Cancel()
}
func (c *cio) Wait() {
if c.closer == nil {
return
}
c.closer.Wait()
}
func (c *cio) Close() error {
if c.closer == nil {
return nil
}
return c.closer.Close()
}
// Creation creates new IO sets for a task
type Creation func(id string) (IO, error)
// Attach allows callers to reattach to running tasks // Attach allows callers to reattach to running tasks
// //
@ -74,123 +43,138 @@ type Creation func(id string) (IO, error)
// will be sent only to the first reads // will be sent only to the first reads
type Attach func(*FIFOSet) (IO, error) type Attach func(*FIFOSet) (IO, error)
// NewIO returns an Creation that will provide IO sets without a terminal // FIFOSet is a set of file paths to FIFOs for a task's standard IO streams
func NewIO(stdin io.Reader, stdout, stderr io.Writer) Creation {
return NewIOWithTerminal(stdin, stdout, stderr, false)
}
// NewIOWithTerminal creates a new io set with the provied io.Reader/Writers for use with a terminal
func NewIOWithTerminal(stdin io.Reader, stdout, stderr io.Writer, terminal bool) Creation {
return func(id string) (_ IO, err error) {
paths, err := NewFifos(id)
if err != nil {
return nil, err
}
defer func() {
if err != nil && paths.Dir != "" {
os.RemoveAll(paths.Dir)
}
}()
cfg := Config{
Terminal: terminal,
Stdout: paths.Out,
Stderr: paths.Err,
Stdin: paths.In,
}
i := &cio{config: cfg}
set := &ioSet{
in: stdin,
out: stdout,
err: stderr,
}
closer, err := copyIO(paths, set, cfg.Terminal)
if err != nil {
return nil, err
}
i.closer = closer
return i, nil
}
}
// WithAttach attaches the existing io for a task to the provided io.Reader/Writers
func WithAttach(stdin io.Reader, stdout, stderr io.Writer) Attach {
return func(paths *FIFOSet) (IO, error) {
if paths == nil {
return nil, fmt.Errorf("cannot attach to existing fifos")
}
cfg := Config{
Terminal: paths.Terminal,
Stdout: paths.Out,
Stderr: paths.Err,
Stdin: paths.In,
}
i := &cio{config: cfg}
set := &ioSet{
in: stdin,
out: stdout,
err: stderr,
}
closer, err := copyIO(paths, set, cfg.Terminal)
if err != nil {
return nil, err
}
i.closer = closer
return i, nil
}
}
// Stdio returns an IO set to be used for a task
// that outputs the container's IO as the current processes Stdio
func Stdio(id string) (IO, error) {
return NewIO(os.Stdin, os.Stdout, os.Stderr)(id)
}
// StdioTerminal will setup the IO for the task to use a terminal
func StdioTerminal(id string) (IO, error) {
return NewIOWithTerminal(os.Stdin, os.Stdout, os.Stderr, true)(id)
}
// NullIO redirects the container's IO into /dev/null
func NullIO(id string) (IO, error) {
return &cio{}, nil
}
// FIFOSet is a set of fifos for use with tasks
type FIFOSet struct { type FIFOSet struct {
// Dir is the directory holding the task fifos Config
Dir string close func() error
// In, Out, and Err fifo paths
In, Out, Err string
// Terminal returns true if a terminal is being used for the task
Terminal bool
} }
type ioSet struct { // Close the FIFOSet
in io.Reader func (f *FIFOSet) Close() error {
out, err io.Writer if f.close != nil {
} return f.close()
type wgCloser struct {
wg *sync.WaitGroup
dir string
set []io.Closer
cancel context.CancelFunc
}
func (g *wgCloser) Wait() {
g.wg.Wait()
}
func (g *wgCloser) Close() error {
for _, f := range g.set {
f.Close()
}
if g.dir != "" {
return os.RemoveAll(g.dir)
} }
return nil return nil
} }
func (g *wgCloser) Cancel() { // NewFIFOSet returns a new FIFOSet from a Config and a close function
g.cancel() func NewFIFOSet(config Config, close func() error) *FIFOSet {
return &FIFOSet{Config: config, close: close}
} }
// Streams used to configure a Creator or Attach
type Streams struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
Terminal bool
}
// Opt customize options for creating a Creator or Attach
type Opt func(*Streams)
// WithStdio sets stream options to the standard input/output streams
func WithStdio(opt *Streams) {
WithStreams(os.Stdin, os.Stdout, os.Stderr)(opt)
}
// WithTerminal sets the terminal option
func WithTerminal(opt *Streams) {
opt.Terminal = true
}
// WithStreams sets the stream options to the specified Reader and Writers
func WithStreams(stdin io.Reader, stdout, stderr io.Writer) Opt {
return func(opt *Streams) {
opt.Stdin = stdin
opt.Stdout = stdout
opt.Stderr = stderr
}
}
// NewCreator returns an IO creator from the options
func NewCreator(opts ...Opt) Creator {
streams := &Streams{}
for _, opt := range opts {
opt(streams)
}
return func(id string) (IO, error) {
// TODO: accept root as a param
root := "/run/containerd/fifo"
fifos, err := NewFIFOSetInDir(root, id, streams.Terminal)
if err != nil {
return nil, err
}
return copyIO(fifos, streams)
}
}
// NewAttach attaches the existing io for a task to the provided io.Reader/Writers
func NewAttach(opts ...Opt) Attach {
streams := &Streams{}
for _, opt := range opts {
opt(streams)
}
return func(fifos *FIFOSet) (IO, error) {
if fifos == nil {
return nil, fmt.Errorf("cannot attach, missing fifos")
}
return copyIO(fifos, streams)
}
}
// NullIO redirects the container's IO into /dev/null
func NullIO(_ string) (IO, error) {
return &cio{}, nil
}
// cio is a basic container IO implementation.
type cio struct {
config Config
wg *sync.WaitGroup
closers []io.Closer
cancel context.CancelFunc
}
func (c *cio) Config() Config {
return c.config
}
func (c *cio) Wait() {
if c.wg != nil {
c.wg.Wait()
}
}
func (c *cio) Close() error {
var lastErr error
for _, closer := range c.closers {
if closer == nil {
continue
}
if err := closer.Close(); err != nil {
lastErr = err
}
}
return lastErr
}
func (c *cio) Cancel() {
if c.cancel != nil {
c.cancel()
}
}
type pipes struct {
Stdin io.WriteCloser
Stdout io.ReadCloser
Stderr io.ReadCloser
}
// DirectIO allows task IO to be handled externally by the caller
type DirectIO struct {
pipes
cio
}
var _ IO = &DirectIO{}

View File

@ -12,173 +12,115 @@ import (
"syscall" "syscall"
"github.com/containerd/fifo" "github.com/containerd/fifo"
"github.com/pkg/errors"
) )
// NewFifos returns a new set of fifos for the task // NewFIFOSetInDir returns a new FIFOSet with paths in a temporary directory under root
func NewFifos(id string) (*FIFOSet, error) { func NewFIFOSetInDir(root, id string, terminal bool) (*FIFOSet, error) {
root := "/run/containerd/fifo" if root != "" {
if err := os.MkdirAll(root, 0700); err != nil { if err := os.MkdirAll(root, 0700); err != nil {
return nil, err return nil, err
} }
}
dir, err := ioutil.TempDir(root, "") dir, err := ioutil.TempDir(root, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &FIFOSet{ closer := func() error {
Dir: dir, return os.RemoveAll(dir)
In: filepath.Join(dir, id+"-stdin"), }
Out: filepath.Join(dir, id+"-stdout"), return NewFIFOSet(Config{
Err: filepath.Join(dir, id+"-stderr"), Stdin: filepath.Join(dir, id+"-stdin"),
}, nil Stdout: filepath.Join(dir, id+"-stdout"),
Stderr: filepath.Join(dir, id+"-stderr"),
Terminal: terminal,
}, closer), nil
} }
func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) { func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
var ( var ctx, cancel = context.WithCancel(context.Background())
f io.ReadWriteCloser pipes, err := openFifos(ctx, fifos)
set []io.Closer
ctx, cancel = context.WithCancel(context.Background())
wg = &sync.WaitGroup{}
)
defer func() {
if err != nil { if err != nil {
for _, f := range set {
f.Close()
}
cancel() cancel()
return nil, err
} }
if fifos.Stdin != "" {
go func() {
io.Copy(pipes.Stdin, ioset.Stdin)
pipes.Stdin.Close()
}()
}
var wg = &sync.WaitGroup{}
wg.Add(1)
go func() {
io.Copy(ioset.Stdout, pipes.Stdout)
pipes.Stdout.Close()
wg.Done()
}() }()
if f, err = fifo.OpenFifo(ctx, fifos.In, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil { if !fifos.Terminal {
return nil, err
}
set = append(set, f)
go func(w io.WriteCloser) {
io.Copy(w, ioset.in)
w.Close()
}(f)
if f, err = fifo.OpenFifo(ctx, fifos.Out, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
return nil, err
}
set = append(set, f)
wg.Add(1) wg.Add(1)
go func(r io.ReadCloser) { go func() {
io.Copy(ioset.out, r) io.Copy(ioset.Stderr, pipes.Stderr)
r.Close() pipes.Stderr.Close()
wg.Done() wg.Done()
}(f) }()
if f, err = fifo.OpenFifo(ctx, fifos.Err, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
return nil, err
} }
set = append(set, f) return &cio{
config: fifos.Config,
if !tty {
wg.Add(1)
go func(r io.ReadCloser) {
io.Copy(ioset.err, r)
r.Close()
wg.Done()
}(f)
}
return &wgCloser{
wg: wg, wg: wg,
dir: fifos.Dir, closers: append(pipes.closers(), fifos),
set: set,
cancel: cancel, cancel: cancel,
}, nil }, nil
} }
// NewDirectIO returns an IO implementation that exposes the pipes directly func openFifos(ctx context.Context, fifos *FIFOSet) (pipes, error) {
func NewDirectIO(ctx context.Context, terminal bool) (*DirectIO, error) { var err error
set, err := NewFifos("")
if err != nil {
return nil, err
}
f := &DirectIO{
set: set,
terminal: terminal,
}
defer func() { defer func() {
if err != nil { if err != nil {
f.Delete() fifos.Close()
} }
}() }()
if f.Stdin, err = fifo.OpenFifo(ctx, set.In, syscall.O_WRONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
return nil, err 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.Stdout, err = fifo.OpenFifo(ctx, set.Out, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil { }
if fifos.Stdout != "" {
if f.Stdout, err = fifo.OpenFifo(ctx, fifos.Stdout, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
f.Stdin.Close() f.Stdin.Close()
return nil, err return f, errors.Wrapf(err, "failed to open stdout fifo")
} }
if f.Stderr, err = fifo.OpenFifo(ctx, set.Err, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil { }
if fifos.Stderr != "" {
if f.Stderr, err = fifo.OpenFifo(ctx, fifos.Stderr, syscall.O_RDONLY|syscall.O_CREAT|syscall.O_NONBLOCK, 0700); err != nil {
f.Stdin.Close() f.Stdin.Close()
f.Stdout.Close() f.Stdout.Close()
return nil, err return f, errors.Wrapf(err, "failed to open stderr fifo")
}
} }
return f, nil return f, nil
} }
// DirectIO allows task IO to be handled externally by the caller // NewDirectIO returns an IO implementation that exposes the IO streams as io.ReadCloser
type DirectIO struct { // and io.WriteCloser.
Stdin io.WriteCloser func NewDirectIO(ctx context.Context, fifos *FIFOSet) (*DirectIO, error) {
Stdout io.ReadCloser ctx, cancel := context.WithCancel(ctx)
Stderr io.ReadCloser pipes, err := openFifos(ctx, fifos)
return &DirectIO{
set *FIFOSet pipes: pipes,
terminal bool cio: cio{
config: fifos.Config,
closers: append(pipes.closers(), fifos),
cancel: cancel,
},
}, err
} }
// IOCreate returns IO avaliable for use with task creation func (p *pipes) closers() []io.Closer {
func (f *DirectIO) IOCreate(id string) (IO, error) { return []io.Closer{p.Stdin, p.Stdout, p.Stderr}
return f, nil
}
// IOAttach returns IO avaliable for use with task attachment
func (f *DirectIO) IOAttach(set *FIFOSet) (IO, error) {
return f, nil
}
// Config returns the Config
func (f *DirectIO) Config() Config {
return Config{
Terminal: f.terminal,
Stdin: f.set.In,
Stdout: f.set.Out,
Stderr: f.set.Err,
}
}
// Cancel stops any IO copy operations
//
// Not applicable for DirectIO
func (f *DirectIO) Cancel() {
// nothing to cancel as all operations are handled externally
}
// Wait on any IO copy operations
//
// Not applicable for DirectIO
func (f *DirectIO) Wait() {
// nothing to wait on as all operations are handled externally
}
// Close closes all open fds
func (f *DirectIO) Close() error {
err := f.Stdin.Close()
if err2 := f.Stdout.Close(); err == nil {
err = err2
}
if err2 := f.Stderr.Close(); err == nil {
err = err2
}
return err
}
// Delete removes the underlying directory containing fifos
func (f *DirectIO) Delete() error {
if f.set.Dir == "" {
return nil
}
return os.RemoveAll(f.set.Dir)
} }

View File

@ -13,25 +13,26 @@ import (
const pipeRoot = `\\.\pipe` const pipeRoot = `\\.\pipe`
// NewFifos returns a new set of fifos for the task // NewFIFOSetInDir returns a new set of fifos for the task
func NewFifos(id string) (*FIFOSet, error) { func NewFIFOSetInDir(_, id string, terminal bool) (*FIFOSet, error) {
return &FIFOSet{ return NewFIFOSet(Config{
In: fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id), Terminal: terminal,
Out: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id), Stdin: fmt.Sprintf(`%s\ctr-%s-stdin`, pipeRoot, id),
Err: fmt.Sprintf(`%s\ctr-%s-stderr`, pipeRoot, id), Stdout: fmt.Sprintf(`%s\ctr-%s-stdout`, pipeRoot, id),
}, nil Stderr: fmt.Sprintf(`%s\ctr-%s-stderr`, pipeRoot, id),
}, nil), nil
} }
func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) { func copyIO(fifos *FIFOSet, ioset *Streams) (*cio, error) {
var ( var (
wg sync.WaitGroup wg sync.WaitGroup
set []io.Closer set []io.Closer
) )
if fifos.In != "" { if fifos.Stdin != "" {
l, err := winio.ListenPipe(fifos.In, nil) l, err := winio.ListenPipe(fifos.Stdin, nil)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.In) return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Stdin)
} }
defer func(l net.Listener) { defer func(l net.Listener) {
if err != nil { if err != nil {
@ -43,19 +44,19 @@ func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) {
go func() { go func() {
c, err := l.Accept() c, err := l.Accept()
if err != nil { if err != nil {
log.L.WithError(err).Errorf("failed to accept stdin connection on %s", fifos.In) log.L.WithError(err).Errorf("failed to accept stdin connection on %s", fifos.Stdin)
return return
} }
io.Copy(c, ioset.in) io.Copy(c, ioset.Stdin)
c.Close() c.Close()
l.Close() l.Close()
}() }()
} }
if fifos.Out != "" { if fifos.Stdout != "" {
l, err := winio.ListenPipe(fifos.Out, nil) l, err := winio.ListenPipe(fifos.Stdout, nil)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Out) return nil, errors.Wrapf(err, "failed to create stdin pipe %s", fifos.Stdout)
} }
defer func(l net.Listener) { defer func(l net.Listener) {
if err != nil { if err != nil {
@ -69,19 +70,19 @@ func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) {
defer wg.Done() defer wg.Done()
c, err := l.Accept() c, err := l.Accept()
if err != nil { if err != nil {
log.L.WithError(err).Errorf("failed to accept stdout connection on %s", fifos.Out) log.L.WithError(err).Errorf("failed to accept stdout connection on %s", fifos.Stdout)
return return
} }
io.Copy(ioset.out, c) io.Copy(ioset.Stdout, c)
c.Close() c.Close()
l.Close() l.Close()
}() }()
} }
if !tty && fifos.Err != "" { if !fifos.Terminal && fifos.Stderr != "" {
l, err := winio.ListenPipe(fifos.Err, nil) l, err := winio.ListenPipe(fifos.Stderr, nil)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to create stderr pipe %s", fifos.Err) return nil, errors.Wrapf(err, "failed to create stderr pipe %s", fifos.Stderr)
} }
defer func(l net.Listener) { defer func(l net.Listener) {
if err != nil { if err != nil {
@ -95,23 +96,29 @@ func copyIO(fifos *FIFOSet, ioset *ioSet, tty bool) (_ *wgCloser, err error) {
defer wg.Done() defer wg.Done()
c, err := l.Accept() c, err := l.Accept()
if err != nil { if err != nil {
log.L.WithError(err).Errorf("failed to accept stderr connection on %s", fifos.Err) log.L.WithError(err).Errorf("failed to accept stderr connection on %s", fifos.Stderr)
return return
} }
io.Copy(ioset.err, c) io.Copy(ioset.Stderr, c)
c.Close() c.Close()
l.Close() l.Close()
}() }()
} }
return &wgCloser{ return &cio{config: fifos.Config, closers: set}, nil
wg: &wg,
dir: fifos.Dir,
set: set,
cancel: func() {
for _, l := range set {
l.Close()
} }
// NewDirectIO returns an IO implementation that exposes the IO streams as io.ReadCloser
// and io.WriteCloser.
func NewDirectIO(stdin io.WriteCloser, stdout, stderr io.ReadCloser, terminal bool) *DirectIO {
return &DirectIO{
pipes: pipes{
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
},
cio: cio{
config: Config{Terminal: terminal},
}, },
}, nil }
} }

View File

@ -3,6 +3,7 @@ package containerd
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -26,7 +27,7 @@ type Container interface {
// Delete removes the container // Delete removes the container
Delete(context.Context, ...DeleteOpts) error Delete(context.Context, ...DeleteOpts) error
// NewTask creates a new task based on the container metadata // NewTask creates a new task based on the container metadata
NewTask(context.Context, cio.Creation, ...NewTaskOpts) (Task, error) NewTask(context.Context, cio.Creator, ...NewTaskOpts) (Task, error)
// Spec returns the OCI runtime specification // Spec returns the OCI runtime specification
Spec(context.Context) (*specs.Spec, error) Spec(context.Context) (*specs.Spec, error)
// Task returns the current task for the container // Task returns the current task for the container
@ -162,7 +163,7 @@ func (c *container) Image(ctx context.Context) (Image, error) {
}, nil }, nil
} }
func (c *container) NewTask(ctx context.Context, ioCreate cio.Creation, opts ...NewTaskOpts) (_ Task, err error) { func (c *container) NewTask(ctx context.Context, ioCreate cio.Creator, opts ...NewTaskOpts) (_ Task, err error) {
i, err := ioCreate(c.id) i, err := ioCreate(c.id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -288,20 +289,23 @@ func (c *container) get(ctx context.Context) (containers.Container, error) {
return c.client.ContainerService().Get(ctx, c.id) return c.client.ContainerService().Get(ctx, c.id)
} }
func attachExistingIO(response *tasks.GetResponse, ioAttach cio.Attach) (cio.IO, error) {
// get the existing fifo paths from the task information stored by the daemon // get the existing fifo paths from the task information stored by the daemon
paths := &cio.FIFOSet{ func attachExistingIO(response *tasks.GetResponse, ioAttach cio.Attach) (cio.IO, error) {
Dir: getFifoDir([]string{ path := getFifoDir([]string{
response.Process.Stdin, response.Process.Stdin,
response.Process.Stdout, response.Process.Stdout,
response.Process.Stderr, response.Process.Stderr,
}), })
In: response.Process.Stdin, closer := func() error {
Out: response.Process.Stdout, return os.RemoveAll(path)
Err: response.Process.Stderr,
Terminal: response.Process.Terminal,
} }
return ioAttach(paths) fifoSet := cio.NewFIFOSet(cio.Config{
Stdin: response.Process.Stdin,
Stdout: response.Process.Stdout,
Stderr: response.Process.Stderr,
Terminal: response.Process.Terminal,
}, closer)
return ioAttach(fifoSet)
} }
// getFifoDir looks for any non-empty path for a stdio fifo // getFifoDir looks for any non-empty path for a stdio fifo

View File

@ -6,7 +6,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"syscall" "syscall"
@ -16,6 +15,7 @@ import (
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/linux/runctypes"
"github.com/containerd/containerd/mount" "github.com/containerd/containerd/mount"
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
@ -166,7 +166,7 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool
if err != nil { if err != nil {
return err return err
} }
if err := remapRootFS(mounts, uid, gid); err != nil { if err := remapRootFS(ctx, mounts, uid, gid); err != nil {
snapshotter.Remove(ctx, usernsID) snapshotter.Remove(ctx, usernsID)
return err return err
} }
@ -187,22 +187,10 @@ func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool
} }
} }
func remapRootFS(mounts []mount.Mount, uid, gid uint32) error { func remapRootFS(ctx context.Context, mounts []mount.Mount, uid, gid uint32) error {
root, err := ioutil.TempDir("", "ctd-remap") return mount.WithTempMount(ctx, mounts, func(root string) error {
if err != nil { return filepath.Walk(root, incrementFS(root, uid, gid))
return err })
}
defer os.Remove(root)
for _, m := range mounts {
if err := m.Mount(root); err != nil {
return err
}
}
err = filepath.Walk(root, incrementFS(root, uid, gid))
if uerr := mount.Unmount(root, 0); err == nil {
err = uerr
}
return err
} }
func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc { func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
@ -218,3 +206,19 @@ func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc {
return os.Lchown(path, u, g) return os.Lchown(path, u, g)
} }
} }
// WithNoPivotRoot instructs the runtime not to you pivot_root
func WithNoPivotRoot(_ context.Context, _ *Client, info *TaskInfo) error {
if info.Options == nil {
info.Options = &runctypes.CreateOptions{
NoPivotRoot: true,
}
return nil
}
copts, ok := info.Options.(*runctypes.CreateOptions)
if !ok {
return errors.New("invalid options type, expected runctypes.CreateOptions")
}
copts.NoPivotRoot = true
return nil
}

View File

@ -77,7 +77,7 @@ func Copy(ctx context.Context, cw Writer, r io.Reader, size int64, expected dige
r, err = seekReader(r, ws.Offset, size) r, err = seekReader(r, ws.Offset, size)
if err != nil { if err != nil {
if !isUnseekable(err) { if !isUnseekable(err) {
return errors.Wrapf(err, "unabled to resume write to %v", ws.Ref) return errors.Wrapf(err, "unable to resume write to %v", ws.Ref)
} }
// reader is unseekable, try to move the writer back to the start. // reader is unseekable, try to move the writer back to the start.

View File

@ -12,8 +12,7 @@ import (
func getATime(fi os.FileInfo) time.Time { func getATime(fi os.FileInfo) time.Time {
if st, ok := fi.Sys().(*syscall.Stat_t); ok { if st, ok := fi.Sys().(*syscall.Stat_t); ok {
return time.Unix(int64(sys.StatAtime(st).Sec), return sys.StatATimeAsTime(st)
int64(sys.StatAtime(st).Nsec))
} }
return fi.ModTime() return fi.ModTime()

View File

@ -2,6 +2,7 @@ package local
import ( import (
"context" "context"
"io"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -167,5 +168,8 @@ func (w *writer) Truncate(size int64) error {
} }
w.offset = 0 w.offset = 0
w.digester.Hash().Reset() w.digester.Hash().Reset()
if _, err := w.fp.Seek(0, io.SeekStart); err != nil {
return err
}
return w.fp.Truncate(0) return w.fp.Truncate(0)
} }

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os"
"strings" "strings"
"time" "time"
@ -90,28 +89,11 @@ func (s *walkingDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts
} }
} }
dir, err := ioutil.TempDir("", "extract-") var ocidesc ocispec.Descriptor
if err != nil { if err := mount.WithTempMount(ctx, mounts, func(root string) error {
return emptyDesc, errors.Wrap(err, "failed to create temporary directory")
}
// We change RemoveAll to Remove so that we either leak a temp dir
// if it fails but not RM snapshot data. refer to #1868 #1785
defer os.Remove(dir)
if err := mount.All(mounts, dir); err != nil {
return emptyDesc, errors.Wrap(err, "failed to mount")
}
defer func() {
if uerr := mount.Unmount(dir, 0); uerr != nil {
if err == nil {
err = uerr
}
}
}()
ra, err := s.store.ReaderAt(ctx, desc.Digest) ra, err := s.store.ReaderAt(ctx, desc.Digest)
if err != nil { if err != nil {
return emptyDesc, errors.Wrap(err, "failed to get reader from content store") return errors.Wrap(err, "failed to get reader from content store")
} }
defer ra.Close() defer ra.Close()
@ -119,7 +101,7 @@ func (s *walkingDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts
if isCompressed { if isCompressed {
ds, err := compression.DecompressStream(r) ds, err := compression.DecompressStream(r)
if err != nil { if err != nil {
return emptyDesc, err return err
} }
defer ds.Close() defer ds.Close()
r = ds r = ds
@ -130,20 +112,26 @@ func (s *walkingDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts
r: io.TeeReader(r, digester.Hash()), r: io.TeeReader(r, digester.Hash()),
} }
if _, err := archive.Apply(ctx, dir, rc); err != nil { if _, err := archive.Apply(ctx, root, rc); err != nil {
return emptyDesc, err return err
} }
// Read any trailing data // Read any trailing data
if _, err := io.Copy(ioutil.Discard, rc); err != nil { if _, err := io.Copy(ioutil.Discard, rc); err != nil {
return emptyDesc, err return err
} }
return ocispec.Descriptor{ ocidesc = ocispec.Descriptor{
MediaType: ocispec.MediaTypeImageLayer, MediaType: ocispec.MediaTypeImageLayer,
Size: rc.c, Size: rc.c,
Digest: digester.Digest(), Digest: digester.Digest(),
}, nil }
return nil
}); err != nil {
return emptyDesc, err
}
return ocidesc, nil
} }
// DiffMounts creates a diff between the given mounts and uploads the result // DiffMounts creates a diff between the given mounts and uploads the result
@ -168,40 +156,10 @@ func (s *walkingDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount
default: default:
return emptyDesc, errors.Wrapf(errdefs.ErrNotImplemented, "unsupported diff media type: %v", config.MediaType) return emptyDesc, errors.Wrapf(errdefs.ErrNotImplemented, "unsupported diff media type: %v", config.MediaType)
} }
aDir, err := ioutil.TempDir("", "left-")
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to create temporary directory")
}
defer os.Remove(aDir)
bDir, err := ioutil.TempDir("", "right-")
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to create temporary directory")
}
defer os.Remove(bDir)
if err := mount.All(lower, aDir); err != nil {
return emptyDesc, errors.Wrap(err, "failed to mount")
}
defer func() {
if uerr := mount.Unmount(aDir, 0); uerr != nil {
if err == nil {
err = uerr
}
}
}()
if err := mount.All(upper, bDir); err != nil {
return emptyDesc, errors.Wrap(err, "failed to mount")
}
defer func() {
if uerr := mount.Unmount(bDir, 0); uerr != nil {
if err == nil {
err = uerr
}
}
}()
var ocidesc ocispec.Descriptor
if err := mount.WithTempMount(ctx, lower, func(lowerRoot string) error {
return mount.WithTempMount(ctx, upper, func(upperRoot string) error {
var newReference bool var newReference bool
if config.Reference == "" { if config.Reference == "" {
newReference = true newReference = true
@ -210,7 +168,7 @@ func (s *walkingDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount
cw, err := s.store.Writer(ctx, config.Reference, 0, "") cw, err := s.store.Writer(ctx, config.Reference, 0, "")
if err != nil { if err != nil {
return emptyDesc, errors.Wrap(err, "failed to open writer") return errors.Wrap(err, "failed to open writer")
} }
defer func() { defer func() {
if err != nil { if err != nil {
@ -224,7 +182,7 @@ func (s *walkingDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount
}() }()
if !newReference { if !newReference {
if err := cw.Truncate(0); err != nil { if err := cw.Truncate(0); err != nil {
return emptyDesc, err return err
} }
} }
@ -232,12 +190,12 @@ func (s *walkingDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount
dgstr := digest.SHA256.Digester() dgstr := digest.SHA256.Digester()
compressed, err := compression.CompressStream(cw, compression.Gzip) compressed, err := compression.CompressStream(cw, compression.Gzip)
if err != nil { if err != nil {
return emptyDesc, errors.Wrap(err, "failed to get compressed stream") return errors.Wrap(err, "failed to get compressed stream")
} }
err = archive.WriteDiff(ctx, io.MultiWriter(compressed, dgstr.Hash()), aDir, bDir) err = archive.WriteDiff(ctx, io.MultiWriter(compressed, dgstr.Hash()), lowerRoot, upperRoot)
compressed.Close() compressed.Close()
if err != nil { if err != nil {
return emptyDesc, errors.Wrap(err, "failed to write compressed diff") return errors.Wrap(err, "failed to write compressed diff")
} }
if config.Labels == nil { if config.Labels == nil {
@ -245,8 +203,8 @@ func (s *walkingDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount
} }
config.Labels["containerd.io/uncompressed"] = dgstr.Digest().String() config.Labels["containerd.io/uncompressed"] = dgstr.Digest().String()
} else { } else {
if err = archive.WriteDiff(ctx, cw, aDir, bDir); err != nil { if err = archive.WriteDiff(ctx, cw, lowerRoot, upperRoot); err != nil {
return emptyDesc, errors.Wrap(err, "failed to write diff") return errors.Wrap(err, "failed to write diff")
} }
} }
@ -257,19 +215,26 @@ func (s *walkingDiff) DiffMounts(ctx context.Context, lower, upper []mount.Mount
dgst := cw.Digest() dgst := cw.Digest()
if err := cw.Commit(ctx, 0, dgst, commitopts...); err != nil { if err := cw.Commit(ctx, 0, dgst, commitopts...); err != nil {
return emptyDesc, errors.Wrap(err, "failed to commit") return errors.Wrap(err, "failed to commit")
} }
info, err := s.store.Info(ctx, dgst) info, err := s.store.Info(ctx, dgst)
if err != nil { if err != nil {
return emptyDesc, errors.Wrap(err, "failed to get info from content store") return errors.Wrap(err, "failed to get info from content store")
} }
return ocispec.Descriptor{ ocidesc = ocispec.Descriptor{
MediaType: config.MediaType, MediaType: config.MediaType,
Size: info.Size, Size: info.Size,
Digest: info.Digest, Digest: info.Digest,
}, nil }
return nil
})
}); err != nil {
return emptyDesc, err
}
return ocidesc, nil
} }
type readCounter struct { type readCounter struct {

View File

@ -26,9 +26,9 @@ func (e *Envelope) Field(fieldpath []string) (string, bool) {
switch fieldpath[0] { switch fieldpath[0] {
// unhandled: timestamp // unhandled: timestamp
case "namespace": case "namespace":
return string(e.Namespace), len(e.Namespace) > 0 return e.Namespace, len(e.Namespace) > 0
case "topic": case "topic":
return string(e.Topic), len(e.Topic) > 0 return e.Topic, len(e.Topic) > 0
case "event": case "event":
decoded, err := typeurl.UnmarshalAny(e.Event) decoded, err := typeurl.UnmarshalAny(e.Event)
if err != nil { if err != nil {

View File

@ -224,8 +224,6 @@ func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err
f1, f2 *currentPath f1, f2 *currentPath
rmdir string rmdir string
lastEmittedDir = string(filepath.Separator)
parents []os.FileInfo
) )
g.Go(func() error { g.Go(func() error {
defer close(c1) defer close(c1)
@ -260,10 +258,7 @@ func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err
continue continue
} }
var ( var f os.FileInfo
f os.FileInfo
emit = true
)
k, p := pathChange(f1, f2) k, p := pathChange(f1, f2)
switch k { switch k {
case ChangeKindAdd: case ChangeKindAdd:
@ -299,83 +294,17 @@ func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err
f2 = nil f2 = nil
if same { if same {
if !isLinked(f) { if !isLinked(f) {
emit = false continue
} }
k = ChangeKindUnmodified k = ChangeKindUnmodified
} }
} }
if emit {
emittedDir, emitParents := commonParents(lastEmittedDir, p, parents)
for _, pf := range emitParents {
p := filepath.Join(emittedDir, pf.Name())
if err := changeFn(ChangeKindUnmodified, p, pf, nil); err != nil {
return err
}
emittedDir = p
}
if err := changeFn(k, p, f, nil); err != nil { if err := changeFn(k, p, f, nil); err != nil {
return err return err
} }
if f != nil && f.IsDir() {
lastEmittedDir = p
} else {
lastEmittedDir = emittedDir
}
parents = parents[:0]
} else if f.IsDir() {
lastEmittedDir, parents = commonParents(lastEmittedDir, p, parents)
parents = append(parents, f)
}
} }
return nil return nil
}) })
return g.Wait() return g.Wait()
} }
func commonParents(base, updated string, dirs []os.FileInfo) (string, []os.FileInfo) {
if basePrefix := makePrefix(base); strings.HasPrefix(updated, basePrefix) {
var (
parents []os.FileInfo
last = base
)
for _, d := range dirs {
next := filepath.Join(last, d.Name())
if strings.HasPrefix(updated, makePrefix(last)) {
parents = append(parents, d)
last = next
} else {
break
}
}
return base, parents
}
baseS := strings.Split(base, string(filepath.Separator))
updatedS := strings.Split(updated, string(filepath.Separator))
commonS := []string{string(filepath.Separator)}
min := len(baseS)
if len(updatedS) < min {
min = len(updatedS)
}
for i := 0; i < min; i++ {
if baseS[i] == updatedS[i] {
commonS = append(commonS, baseS[i])
} else {
break
}
}
return filepath.Join(commonS...), []os.FileInfo{}
}
func makePrefix(d string) string {
if d == "" || d[len(d)-1] != filepath.Separator {
return d + string(filepath.Separator)
}
return d
}

View File

@ -15,6 +15,15 @@ type inode struct {
dev, ino uint64 dev, ino uint64
} }
func newInode(stat *syscall.Stat_t) inode {
return inode{
// Dev is uint32 on darwin/bsd, uint64 on linux/solaris
dev: uint64(stat.Dev), // nolint: unconvert
// Ino is uint32 on bsd, uint64 on darwin/linux/solaris
ino: uint64(stat.Ino), // nolint: unconvert
}
}
func diskUsage(roots ...string) (Usage, error) { func diskUsage(roots ...string) (Usage, error) {
var ( var (
@ -28,9 +37,7 @@ func diskUsage(roots ...string) (Usage, error) {
return err return err
} }
stat := fi.Sys().(*syscall.Stat_t) inoKey := newInode(fi.Sys().(*syscall.Stat_t))
inoKey := inode{dev: uint64(stat.Dev), ino: uint64(stat.Ino)}
if _, ok := inodes[inoKey]; !ok { if _, ok := inodes[inoKey]; !ok {
inodes[inoKey] = struct{}{} inodes[inoKey] = struct{}{}
size += fi.Size() size += fi.Size()
@ -60,9 +67,7 @@ func diffUsage(ctx context.Context, a, b string) (Usage, error) {
} }
if kind == ChangeKindAdd || kind == ChangeKindModify { if kind == ChangeKindAdd || kind == ChangeKindModify {
stat := fi.Sys().(*syscall.Stat_t) inoKey := newInode(fi.Sys().(*syscall.Stat_t))
inoKey := inode{dev: uint64(stat.Dev), ino: uint64(stat.Ino)}
if _, ok := inodes[inoKey]; !ok { if _, ok := inodes[inoKey]; !ok {
inodes[inoKey] = struct{}{} inodes[inoKey] = struct{}{}
size += fi.Size() size += fi.Size()

View File

@ -13,5 +13,6 @@ func getLinkInfo(fi os.FileInfo) (uint64, bool) {
return 0, false return 0, false
} }
return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1 // Ino is uint32 on bsd, uint64 on darwin/linux/solaris
return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1 // nolint: unconvert
} }

View File

@ -71,9 +71,9 @@ func sameFile(f1, f2 *currentPath) (bool, error) {
return false, nil return false, nil
} }
// If the timestamp may have been truncated in one of the // If the timestamp may have been truncated in both of the
// files, check content of file to determine difference // files, check content of file to determine difference
if t1.Nanosecond() == 0 || t2.Nanosecond() == 0 { if t1.Nanosecond() == 0 && t2.Nanosecond() == 0 {
var eq bool var eq bool
if (f1.f.Mode() & os.ModeSymlink) == os.ModeSymlink { if (f1.f.Mode() & os.ModeSymlink) == os.ModeSymlink {
eq, err = compareSymlinkTarget(f1.fullPath, f2.fullPath) eq, err = compareSymlinkTarget(f1.fullPath, f2.fullPath)

View File

@ -8,6 +8,7 @@ package gc
import ( import (
"context" "context"
"sync" "sync"
"time"
) )
// ResourceType represents type of resource at a node // ResourceType represents type of resource at a node
@ -21,6 +22,11 @@ type Node struct {
Key string Key string
} }
// Stats about a garbage collection run
type Stats interface {
Elapsed() time.Duration
}
// Tricolor implements basic, single-thread tri-color GC. Given the roots, the // Tricolor implements basic, single-thread tri-color GC. Given the roots, the
// complete set and a refs function, this function returns a map of all // complete set and a refs function, this function returns a map of all
// reachable objects. // reachable objects.

View File

@ -357,13 +357,5 @@ func RootFS(ctx context.Context, provider content.Provider, configDesc ocispec.D
if err := json.Unmarshal(p, &config); err != nil { if err := json.Unmarshal(p, &config); err != nil {
return nil, err return nil, err
} }
return config.RootFS.DiffIDs, nil
// TODO(stevvooe): Remove this bit when OCI structure uses correct type for
// rootfs.DiffIDs.
var diffIDs []digest.Digest
for _, diffID := range config.RootFS.DiffIDs {
diffIDs = append(diffIDs, digest.Digest(diffID))
}
return diffIDs, nil
} }

View File

@ -318,12 +318,23 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
cs.l.RLock() cs.l.RLock()
defer cs.l.RUnlock() defer cs.l.RUnlock()
var w content.Writer var (
w content.Writer
exists bool
)
if err := update(ctx, cs.db, func(tx *bolt.Tx) error { if err := update(ctx, cs.db, func(tx *bolt.Tx) error {
if expected != "" { if expected != "" {
cbkt := getBlobBucket(tx, ns, expected) cbkt := getBlobBucket(tx, ns, expected)
if cbkt != nil { if cbkt != nil {
return errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", expected) // Add content to lease to prevent other reference removals
// from effecting this object during a provided lease
if err := addContentLease(ctx, tx, expected); err != nil {
return errors.Wrap(err, "unable to lease content")
}
// Return error outside of transaction to ensure
// commit succeeds with the lease.
exists = true
return nil
} }
} }
@ -363,6 +374,9 @@ func (cs *contentStore) Writer(ctx context.Context, ref string, size int64, expe
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
if exists {
return nil, errors.Wrapf(errdefs.ErrAlreadyExists, "content %v", expected)
}
// TODO: keep the expected in the writer to use on commit // TODO: keep the expected in the writer to use on commit
// when no expected is provided there. // when no expected is provided there.

View File

@ -204,7 +204,7 @@ func (m *DB) Update(fn func(*bolt.Tx) error) error {
// RegisterMutationCallback registers a function to be called after a metadata // RegisterMutationCallback registers a function to be called after a metadata
// mutations has been performed. // mutations has been performed.
// //
// The callback function in an argument for whether a deletion has occurred // The callback function is an argument for whether a deletion has occurred
// since the last garbage collection. // since the last garbage collection.
func (m *DB) RegisterMutationCallback(fn func(bool)) { func (m *DB) RegisterMutationCallback(fn func(bool)) {
m.dirtyL.Lock() m.dirtyL.Lock()
@ -219,15 +219,20 @@ type GCStats struct {
SnapshotD map[string]time.Duration SnapshotD map[string]time.Duration
} }
// Elapsed returns the duration which elapsed during a collection
func (s GCStats) Elapsed() time.Duration {
return s.MetaD
}
// GarbageCollect starts garbage collection // GarbageCollect starts garbage collection
func (m *DB) GarbageCollect(ctx context.Context) (stats GCStats, err error) { func (m *DB) GarbageCollect(ctx context.Context) (gc.Stats, error) {
m.wlock.Lock() m.wlock.Lock()
t1 := time.Now() t1 := time.Now()
marked, err := m.getMarked(ctx) marked, err := m.getMarked(ctx)
if err != nil { if err != nil {
m.wlock.Unlock() m.wlock.Unlock()
return GCStats{}, err return nil, err
} }
m.dirtyL.Lock() m.dirtyL.Lock()
@ -259,9 +264,10 @@ func (m *DB) GarbageCollect(ctx context.Context) (stats GCStats, err error) {
}); err != nil { }); err != nil {
m.dirtyL.Unlock() m.dirtyL.Unlock()
m.wlock.Unlock() m.wlock.Unlock()
return GCStats{}, err return nil, err
} }
var stats GCStats
var wg sync.WaitGroup var wg sync.WaitGroup
if len(m.dirtySS) > 0 { if len(m.dirtySS) > 0 {
@ -303,7 +309,7 @@ func (m *DB) GarbageCollect(ctx context.Context) (stats GCStats, err error) {
wg.Wait() wg.Wait()
return return stats, err
} }
func (m *DB) getMarked(ctx context.Context) (map[gc.Node]struct{}, error) { func (m *DB) getMarked(ctx context.Context) (map[gc.Node]struct{}, error) {

View File

@ -275,7 +275,7 @@ func writeImage(bkt *bolt.Bucket, image *images.Image) error {
} }
// write the target bucket // write the target bucket
tbkt, err := bkt.CreateBucketIfNotExists([]byte(bucketKeyTarget)) tbkt, err := bkt.CreateBucketIfNotExists(bucketKeyTarget)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,5 +1,14 @@
package mount package mount
import (
"context"
"io/ioutil"
"os"
"github.com/containerd/containerd/log"
"github.com/pkg/errors"
)
// Mount is the lingua franca of containerd. A mount represents a // Mount is the lingua franca of containerd. A mount represents a
// serialized mount syscall. Components either emit or consume mounts. // serialized mount syscall. Components either emit or consume mounts.
type Mount struct { type Mount struct {
@ -22,3 +31,42 @@ func All(mounts []Mount, target string) error {
} }
return nil return nil
} }
// WithTempMount mounts the provided mounts to a temp dir, and pass the temp dir to f.
// The mounts are valid during the call to the f.
// Finally we will unmount and remove the temp dir regardless of the result of f.
func WithTempMount(ctx context.Context, mounts []Mount, f func(root string) error) (err error) {
root, uerr := ioutil.TempDir("", "containerd-WithTempMount")
if uerr != nil {
return errors.Wrapf(uerr, "failed to create temp dir")
}
// We use Remove here instead of RemoveAll.
// The RemoveAll will delete the temp dir and all children it contains.
// When the Unmount fails, RemoveAll will incorrectly delete data from
// the mounted dir. However, if we use Remove, even though we won't
// successfully delete the temp dir and it may leak, we won't loss data
// from the mounted dir.
// For details, please refer to #1868 #1785.
defer func() {
if uerr = os.Remove(root); uerr != nil {
log.G(ctx).WithError(uerr).WithField("dir", root).Errorf("failed to remove mount temp dir")
}
}()
// We should do defer first, if not we will not do Unmount when only a part of Mounts are failed.
defer func() {
if uerr = UnmountAll(root, 0); uerr != nil {
uerr = errors.Wrapf(uerr, "failed to unmount %s", root)
if err == nil {
err = uerr
} else {
err = errors.Wrap(err, uerr.Error())
}
}
}()
if uerr = All(mounts, root); uerr != nil {
return errors.Wrapf(uerr, "failed to mount %s", root)
}
return errors.Wrapf(f(root), "mount callback failed on %s", root)
}

View File

@ -6,7 +6,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -271,23 +270,8 @@ func WithUserID(uid uint32) SpecOpts {
if err != nil { if err != nil {
return err return err
} }
root, err := ioutil.TempDir("", "ctd-username")
if err != nil { return mount.WithTempMount(ctx, mounts, func(root string) error {
return err
}
defer os.Remove(root)
for _, m := range mounts {
if err := m.Mount(root); err != nil {
return err
}
}
defer func() {
if uerr := mount.Unmount(root, 0); uerr != nil {
if err == nil {
err = uerr
}
}
}()
ppath, err := fs.RootPath(root, "/etc/passwd") ppath, err := fs.RootPath(root, "/etc/passwd")
if err != nil { if err != nil {
return err return err
@ -314,6 +298,7 @@ func WithUserID(uid uint32) SpecOpts {
u := users[0] u := users[0]
s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid) s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
return nil return nil
})
} }
} }
@ -334,23 +319,7 @@ func WithUsername(username string) SpecOpts {
if err != nil { if err != nil {
return err return err
} }
root, err := ioutil.TempDir("", "ctd-username") return mount.WithTempMount(ctx, mounts, func(root string) error {
if err != nil {
return err
}
defer os.Remove(root)
for _, m := range mounts {
if err := m.Mount(root); err != nil {
return err
}
}
defer func() {
if uerr := mount.Unmount(root, 0); uerr != nil {
if err == nil {
err = uerr
}
}
}()
ppath, err := fs.RootPath(root, "/etc/passwd") ppath, err := fs.RootPath(root, "/etc/passwd")
if err != nil { if err != nil {
return err return err
@ -372,5 +341,6 @@ func WithUsername(username string) SpecOpts {
u := users[0] u := users[0]
s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid) s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
return nil return nil
})
} }
} }

View File

@ -0,0 +1,85 @@
package platforms
import (
"bufio"
"os"
"runtime"
"strings"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/log"
"github.com/pkg/errors"
)
// Present the ARM instruction set architecture, eg: v7, v8
var cpuVariant string
func init() {
if isArmArch(runtime.GOARCH) {
cpuVariant = getCPUVariant()
} else {
cpuVariant = ""
}
}
// For Linux, the kernel has already detected the ABI, ISA and Features.
// So we don't need to access the ARM registers to detect platform information
// by ourselves. We can just parse these information from /proc/cpuinfo
func getCPUInfo(pattern string) (info string, err error) {
if !isLinuxOS(runtime.GOOS) {
return "", errors.Wrapf(errdefs.ErrNotImplemented, "getCPUInfo for OS %s", runtime.GOOS)
}
cpuinfo, err := os.Open("/proc/cpuinfo")
if err != nil {
return "", err
}
defer cpuinfo.Close()
// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
// the first core is enough.
scanner := bufio.NewScanner(cpuinfo)
for scanner.Scan() {
newline := scanner.Text()
list := strings.Split(newline, ":")
if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
return strings.TrimSpace(list[1]), nil
}
}
// Check whether the scanner encountered errors
err = scanner.Err()
if err != nil {
return "", err
}
return "", errors.Wrapf(errdefs.ErrNotFound, "getCPUInfo for pattern: %s", pattern)
}
func getCPUVariant() string {
variant, err := getCPUInfo("Cpu architecture")
if err != nil {
log.L.WithError(err).Error("failure getting variant")
return ""
}
switch variant {
case "8":
variant = "v8"
case "7", "7M", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
variant = "v7"
case "6", "6TEJ":
variant = "v6"
case "5", "5T", "5TE", "5TEJ":
variant = "v5"
case "4", "4T":
variant = "v4"
case "3":
variant = "v3"
default:
variant = "unknown"
}
return variant
}

View File

@ -5,6 +5,13 @@ import (
"strings" "strings"
) )
// isLinuxOS returns true if the operating system is Linux.
//
// The OS value should be normalized before calling this function.
func isLinuxOS(os string) bool {
return os == "linux"
}
// These function are generated from from https://golang.org/src/go/build/syslist.go. // These function are generated from from https://golang.org/src/go/build/syslist.go.
// //
// We use switch statements because they are slightly faster than map lookups // We use switch statements because they are slightly faster than map lookups
@ -21,6 +28,17 @@ func isKnownOS(os string) bool {
return false return false
} }
// isArmArch returns true if the architecture is ARM.
//
// The arch value should be normalized before being passed to this function.
func isArmArch(arch string) bool {
switch arch {
case "arm", "arm64":
return true
}
return false
}
// isKnownArch returns true if we know about the architecture. // isKnownArch returns true if we know about the architecture.
// //
// The arch value should be normalized before being passed to this function. // The arch value should be normalized before being passed to this function.

View File

@ -16,6 +16,7 @@ func DefaultSpec() specs.Platform {
return specs.Platform{ return specs.Platform{
OS: runtime.GOOS, OS: runtime.GOOS,
Architecture: runtime.GOARCH, Architecture: runtime.GOARCH,
// TODO(stevvooe): Need to resolve GOARM for arm hosts. // The Variant field will be empty if arch != ARM.
Variant: cpuVariant,
} }
} }

View File

@ -191,6 +191,7 @@ func (p *process) Delete(ctx context.Context, opts ...ProcessDeleteOpts) (*ExitS
return nil, errdefs.FromGRPC(err) return nil, errdefs.FromGRPC(err)
} }
if p.io != nil { if p.io != nil {
p.io.Cancel()
p.io.Wait() p.io.Wait()
p.io.Close() p.io.Close()
} }

View File

@ -1,31 +0,0 @@
package remotes
import "strings"
// HintExists returns true if a hint of the provided kind and values exists in
// the set of provided hints.
func HintExists(kind, value string, hints ...string) bool {
for _, hint := range hints {
if strings.HasPrefix(hint, kind) && strings.HasSuffix(hint, value) {
return true
}
}
return false
}
// HintValues returns a slice of the values of the hints that match kind.
func HintValues(kind string, hints ...string) []string {
var values []string
for _, hint := range hints {
if strings.HasPrefix(hint, kind) {
parts := strings.SplitN(hint, ":", 2)
if len(parts) < 2 {
continue
}
values = append(values, parts[1])
}
}
return values
}

View File

@ -39,7 +39,7 @@ func Diff(ctx context.Context, snapshotID string, sn snapshots.Snapshotter, d di
if err != nil { if err != nil {
return ocispec.Descriptor{}, err return ocispec.Descriptor{}, err
} }
defer sn.Remove(ctx, lowerKey) defer sn.Remove(ctx, upperKey)
} }
return d.DiffMounts(ctx, lower, upper, opts...) return d.DiffMounts(ctx, lower, upper, opts...)

View File

@ -4,6 +4,7 @@ package sys
import ( import (
"syscall" "syscall"
"time"
) )
// StatAtime returns the access time from a stat struct // StatAtime returns the access time from a stat struct
@ -20,3 +21,8 @@ func StatCtime(st *syscall.Stat_t) syscall.Timespec {
func StatMtime(st *syscall.Stat_t) syscall.Timespec { func StatMtime(st *syscall.Stat_t) syscall.Timespec {
return st.Mtimespec return st.Mtimespec
} }
// StatATimeAsTime returns the access time as a time.Time
func StatATimeAsTime(st *syscall.Stat_t) time.Time {
return time.Unix(int64(st.Atimespec.Sec), int64(st.Atimespec.Nsec)) // nolint: unconvert
}

View File

@ -4,6 +4,7 @@ package sys
import ( import (
"syscall" "syscall"
"time"
) )
// StatAtime returns the Atim // StatAtime returns the Atim
@ -20,3 +21,8 @@ func StatCtime(st *syscall.Stat_t) syscall.Timespec {
func StatMtime(st *syscall.Stat_t) syscall.Timespec { func StatMtime(st *syscall.Stat_t) syscall.Timespec {
return st.Mtim return st.Mtim
} }
// StatATimeAsTime returns st.Atim as a time.Time
func StatATimeAsTime(st *syscall.Stat_t) time.Time {
return time.Unix(st.Atim.Sec, st.Atim.Nsec)
}

View File

@ -123,7 +123,7 @@ type Task interface {
// Resume the execution of the task // Resume the execution of the task
Resume(context.Context) error Resume(context.Context) error
// Exec creates a new process inside the task // Exec creates a new process inside the task
Exec(context.Context, string, *specs.Process, cio.Creation) (Process, error) Exec(context.Context, string, *specs.Process, cio.Creator) (Process, error)
// Pids returns a list of system specific process ids inside the task // Pids returns a list of system specific process ids inside the task
Pids(context.Context) ([]ProcessInfo, error) Pids(context.Context) ([]ProcessInfo, error)
// Checkpoint serializes the runtime and memory information of a task into an // Checkpoint serializes the runtime and memory information of a task into an
@ -163,6 +163,7 @@ func (t *task) Start(ctx context.Context) error {
ContainerID: t.id, ContainerID: t.id,
}) })
if err != nil { if err != nil {
t.io.Cancel()
t.io.Close() t.io.Close()
return errdefs.FromGRPC(err) return errdefs.FromGRPC(err)
} }
@ -277,7 +278,7 @@ func (t *task) Delete(ctx context.Context, opts ...ProcessDeleteOpts) (*ExitStat
return &ExitStatus{code: r.ExitStatus, exitedAt: r.ExitedAt}, nil return &ExitStatus{code: r.ExitStatus, exitedAt: r.ExitedAt}, nil
} }
func (t *task) Exec(ctx context.Context, id string, spec *specs.Process, ioCreate cio.Creation) (_ Process, err error) { func (t *task) Exec(ctx context.Context, id string, spec *specs.Process, ioCreate cio.Creator) (_ Process, err error) {
if id == "" { if id == "" {
return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "exec id must not be empty") return nil, errors.Wrapf(errdefs.ErrInvalidArgument, "exec id must not be empty")
} }

View File

@ -15,8 +15,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.0
github.com/docker/go-units v0.3.1 github.com/docker/go-units v0.3.1
github.com/gogo/protobuf v0.5 github.com/gogo/protobuf v0.5
github.com/golang/protobuf 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 github.com/golang/protobuf 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9
github.com/opencontainers/runtime-spec v1.0.0 github.com/opencontainers/runtime-spec v1.0.1
github.com/opencontainers/runc 74a17296470088de3805e138d3d87c62e613dfc4 github.com/opencontainers/runc 7f24b40cc5423969b4554ef04ba0b00e2b4ba010
github.com/sirupsen/logrus v1.0.0 github.com/sirupsen/logrus v1.0.0
github.com/containerd/btrfs cc52c4dea2ce11a44e6639e561bb5c2af9ada9e3 github.com/containerd/btrfs cc52c4dea2ce11a44e6639e561bb5c2af9ada9e3
github.com/stretchr/testify v1.1.4 github.com/stretchr/testify v1.1.4
@ -25,20 +25,19 @@ github.com/pmezard/go-difflib v1.0.0
github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6 github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6 golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6
google.golang.org/grpc v1.7.2 google.golang.org/grpc v1.7.4
github.com/pkg/errors v0.8.0 github.com/pkg/errors v0.8.0
github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448 github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448
golang.org/x/sys 314a259e304ff91bd6985da2a7149bbf91237993 https://github.com/golang/sys golang.org/x/sys 314a259e304ff91bd6985da2a7149bbf91237993 https://github.com/golang/sys
github.com/opencontainers/image-spec v1.0.0 github.com/opencontainers/image-spec v1.0.1
github.com/containerd/continuity cf279e6ac893682272b4479d4c67fd3abf878b4e github.com/containerd/continuity cf279e6ac893682272b4479d4c67fd3abf878b4e
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
github.com/BurntSushi/toml v0.2.0-21-g9906417 github.com/BurntSushi/toml v0.2.0-21-g9906417
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0 github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
github.com/Microsoft/go-winio v0.4.4 github.com/Microsoft/go-winio v0.4.5
github.com/Microsoft/hcsshim v0.6.7 github.com/Microsoft/hcsshim v0.6.7
github.com/Microsoft/opengcs v0.3.2
github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd github.com/boltdb/bolt e9cf4fae01b5a8ff89d0ec6b32f0d9c9f79aefdd
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
github.com/dmcgowan/go-tar go1.10 github.com/dmcgowan/go-tar go1.10
github.com/stevvooe/ttrpc 76e68349ad9ab4d03d764c713826d31216715e4f github.com/stevvooe/ttrpc d2710463e497617f16f26d1e715a3308609e7982

View File

@ -51,7 +51,7 @@ Find more [FAQ on the OCI site](https://www.opencontainers.org/faq).
## Roadmap ## Roadmap
The [GitHub milestones](https://github.com/opencontainers/image-spec/milestones) lay out the path to the OCI v1.0.0 release in late 2016. The [GitHub milestones](https://github.com/opencontainers/image-spec/milestones) lay out the path to the future improvements.
# Contributing # Contributing

View File

@ -22,7 +22,7 @@ const (
// VersionMinor is for functionality in a backwards-compatible manner // VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 0 VersionMinor = 0
// VersionPatch is for backwards-compatible bug fixes // VersionPatch is for backwards-compatible bug fixes
VersionPatch = 0 VersionPatch = 1
// VersionDev indicates development branch. Releases will be empty string. // VersionDev indicates development branch. Releases will be empty string.
VersionDev = "" VersionDev = ""

View File

@ -56,7 +56,7 @@ make BUILDTAGS='seccomp apparmor'
|-----------|------------------------------------|-------------| |-----------|------------------------------------|-------------|
| seccomp | Syscall filtering | libseccomp | | seccomp | Syscall filtering | libseccomp |
| selinux | selinux process and mount labeling | <none> | | selinux | selinux process and mount labeling | <none> |
| apparmor | apparmor profile support | libapparmor | | apparmor | apparmor profile support | <none> |
| ambient | ambient capability support | kernel 4.3 | | ambient | ambient capability support | kernel 4.3 |

View File

@ -1,4 +1,4 @@
// +build cgo,linux cgo,freebsd // +build cgo,linux
package system package system

View File

@ -1,38 +0,0 @@
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
package user
import (
"io"
"syscall"
)
func GetPasswdPath() (string, error) {
return "", ErrUnsupported
}
func GetPasswd() (io.ReadCloser, error) {
return nil, ErrUnsupported
}
func GetGroupPath() (string, error) {
return "", ErrUnsupported
}
func GetGroup() (io.ReadCloser, error) {
return nil, ErrUnsupported
}
// CurrentUser looks up the current user by their user id in /etc/passwd. If the
// user cannot be found (or there is no /etc/passwd file on the filesystem),
// then CurrentUser returns an error.
func CurrentUser() (User, error) {
return LookupUid(syscall.Getuid())
}
// CurrentGroup looks up the current user's group by their primary group id's
// entry in /etc/passwd. If the group cannot be found (or there is no
// /etc/group file on the filesystem), then CurrentGroup returns an error.
func CurrentGroup() (Group, error) {
return LookupGid(syscall.Getgid())
}

View File

@ -15,7 +15,7 @@ github.com/coreos/pkg v3
github.com/godbus/dbus v3 github.com/godbus/dbus v3
github.com/golang/protobuf 18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8 github.com/golang/protobuf 18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8
# Command-line interface. # Command-line interface.
github.com/docker/docker 0f5c9d301b9b1cca66b3ea0f9dec3b5317d3686d github.com/cyphar/filepath-securejoin v0.2.1
github.com/docker/go-units v0.2.0 github.com/docker/go-units v0.2.0
github.com/urfave/cli d53eb991652b1d438abdd34ce4bfa3ef1539108e github.com/urfave/cli d53eb991652b1d438abdd34ce4bfa3ef1539108e
golang.org/x/sys 7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce https://github.com/golang/sys golang.org/x/sys 7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce https://github.com/golang/sys

View File

@ -52,17 +52,12 @@ It also guarantees that the design is sound before code is written; a GitHub pul
Typos and grammatical errors can go straight to a pull-request. Typos and grammatical errors can go straight to a pull-request.
When in doubt, start on the [mailing-list](#mailing-list). When in doubt, start on the [mailing-list](#mailing-list).
### Weekly Call ### Meetings
The contributors and maintainers of all OCI projects have a weekly meeting on Wednesdays at:
* 8:00 AM (USA Pacific), during [odd weeks][iso-week].
* 2:00 PM (USA Pacific), during [even weeks][iso-week].
The contributors and maintainers of all OCI projects have monthly meetings at 2:00 PM (USA Pacific) on the first Wednesday of every month.
There is an [iCalendar][rfc5545] format for the meetings [here](meeting.ics). There is an [iCalendar][rfc5545] format for the meetings [here](meeting.ics).
Everyone is welcome to participate via [UberConference web][uberconference] or audio-only: +1 415 968 0849 (no PIN needed). Everyone is welcome to participate via [UberConference web][uberconference] or audio-only: +1 415 968 0849 (no PIN needed).
An initial agenda will be posted to the [mailing list](#mailing-list) earlier in the week, and everyone is welcome to propose additional topics or suggest other agenda alterations there. An initial agenda will be posted to the [mailing list](#mailing-list) in the week before each meeting, and everyone is welcome to propose additional topics or suggest other agenda alterations there.
Minutes are posted to the [mailing list](#mailing-list) and minutes from past calls are archived [here][minutes], with minutes from especially old meetings (September 2015 and earlier) archived [here][runtime-wiki]. Minutes are posted to the [mailing list](#mailing-list) and minutes from past calls are archived [here][minutes], with minutes from especially old meetings (September 2015 and earlier) archived [here][runtime-wiki].
### Mailing List ### Mailing List

View File

@ -4,7 +4,7 @@ import "os"
// Spec is the base configuration for the container. // Spec is the base configuration for the container.
type Spec struct { type Spec struct {
// Version of the Open Container Runtime Specification with which the bundle complies. // Version of the Open Container Initiative Runtime Specification with which the bundle complies.
Version string `json:"ociVersion"` Version string `json:"ociVersion"`
// Process configures the container process. // Process configures the container process.
Process *Process `json:"process,omitempty"` Process *Process `json:"process,omitempty"`

View File

@ -8,7 +8,7 @@ const (
// VersionMinor is for functionality in a backwards-compatible manner // VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 0 VersionMinor = 0
// VersionPatch is for backwards-compatible bug fixes // VersionPatch is for backwards-compatible bug fixes
VersionPatch = 0 VersionPatch = 1
// VersionDev indicates development branch. Releases will be empty string. // VersionDev indicates development branch. Releases will be empty string.
VersionDev = "" VersionDev = ""

View File

@ -171,7 +171,9 @@ func (ccb *ccBalancerWrapper) NewSubConn(addrs []resolver.Address, opts balancer
return nil, err return nil, err
} }
acbw := &acBalancerWrapper{ac: ac} acbw := &acBalancerWrapper{ac: ac}
ac.mu.Lock()
ac.acbw = acbw ac.acbw = acbw
ac.mu.Unlock()
return acbw, nil return acbw, nil
} }
@ -228,7 +230,9 @@ func (acbw *acBalancerWrapper) UpdateAddresses(addrs []resolver.Address) {
return return
} }
acbw.ac = ac acbw.ac = ac
ac.mu.Lock()
ac.acbw = acbw ac.acbw = acbw
ac.mu.Unlock()
if acState != connectivity.Idle { if acState != connectivity.Idle {
ac.connect(false) ac.connect(false)
} }

View File

@ -929,6 +929,16 @@ func (ac *addrConn) resetTransport() error {
newTransport, err := transport.NewClientTransport(ac.cc.ctx, sinfo, copts, timeout) newTransport, err := transport.NewClientTransport(ac.cc.ctx, sinfo, copts, timeout)
if err != nil { if err != nil {
if e, ok := err.(transport.ConnectionError); ok && !e.Temporary() { if e, ok := err.(transport.ConnectionError); ok && !e.Temporary() {
ac.mu.Lock()
if ac.state != connectivity.Shutdown {
ac.state = connectivity.TransientFailure
if ac.cc.balancerWrapper != nil {
ac.cc.balancerWrapper.handleSubConnStateChange(ac.acbw, ac.state)
} else {
ac.cc.csMgr.updateState(ac.state)
}
}
ac.mu.Unlock()
return err return err
} }
grpclog.Warningf("grpc: addrConn.resetTransport failed to create client transport: %v; Reconnecting to %v", err, addr) grpclog.Warningf("grpc: addrConn.resetTransport failed to create client transport: %v; Reconnecting to %v", err, addr)

View File

@ -91,10 +91,14 @@ type TransportCredentials interface {
// (io.EOF, context.DeadlineExceeded or err.Temporary() == true). // (io.EOF, context.DeadlineExceeded or err.Temporary() == true).
// If the returned error is a wrapper error, implementations should make sure that // If the returned error is a wrapper error, implementations should make sure that
// the error implements Temporary() to have the correct retry behaviors. // the error implements Temporary() to have the correct retry behaviors.
//
// If the returned net.Conn is closed, it MUST close the net.Conn provided.
ClientHandshake(context.Context, string, net.Conn) (net.Conn, AuthInfo, error) ClientHandshake(context.Context, string, net.Conn) (net.Conn, AuthInfo, error)
// ServerHandshake does the authentication handshake for servers. It returns // ServerHandshake does the authentication handshake for servers. It returns
// the authenticated connection and the corresponding auth information about // the authenticated connection and the corresponding auth information about
// the connection. // the connection.
//
// If the returned net.Conn is closed, it MUST close the net.Conn provided.
ServerHandshake(net.Conn) (net.Conn, AuthInfo, error) ServerHandshake(net.Conn) (net.Conn, AuthInfo, error)
// Info provides the ProtocolInfo of this TransportCredentials. // Info provides the ProtocolInfo of this TransportCredentials.
Info() ProtocolInfo Info() ProtocolInfo

View File

@ -567,6 +567,6 @@ const SupportPackageIsVersion3 = true
const SupportPackageIsVersion4 = true const SupportPackageIsVersion4 = true
// Version is the current grpc version. // Version is the current grpc version.
const Version = "1.7.2" const Version = "1.7.4"
const grpcUA = "grpc-go/" + Version const grpcUA = "grpc-go/" + Version

View File

@ -118,11 +118,13 @@ type options struct {
initialConnWindowSize int32 initialConnWindowSize int32
writeBufferSize int writeBufferSize int
readBufferSize int readBufferSize int
connectionTimeout time.Duration
} }
var defaultServerOptions = options{ var defaultServerOptions = options{
maxReceiveMessageSize: defaultServerMaxReceiveMessageSize, maxReceiveMessageSize: defaultServerMaxReceiveMessageSize,
maxSendMessageSize: defaultServerMaxSendMessageSize, maxSendMessageSize: defaultServerMaxSendMessageSize,
connectionTimeout: 120 * time.Second,
} }
// A ServerOption sets options such as credentials, codec and keepalive parameters, etc. // A ServerOption sets options such as credentials, codec and keepalive parameters, etc.
@ -291,6 +293,16 @@ func UnknownServiceHandler(streamHandler StreamHandler) ServerOption {
} }
} }
// ConnectionTimeout returns a ServerOption that sets the timeout for
// connection establishment (up to and including HTTP/2 handshaking) for all
// new connections. If this is not set, the default is 120 seconds. A zero or
// negative value will result in an immediate timeout.
func ConnectionTimeout(d time.Duration) ServerOption {
return func(o *options) {
o.connectionTimeout = d
}
}
// NewServer creates a gRPC server which has no service registered and has not // NewServer creates a gRPC server which has no service registered and has not
// started to accept requests yet. // started to accept requests yet.
func NewServer(opt ...ServerOption) *Server { func NewServer(opt ...ServerOption) *Server {
@ -499,16 +511,18 @@ func (s *Server) Serve(lis net.Listener) error {
// handleRawConn is run in its own goroutine and handles a just-accepted // handleRawConn is run in its own goroutine and handles a just-accepted
// connection that has not had any I/O performed on it yet. // connection that has not had any I/O performed on it yet.
func (s *Server) handleRawConn(rawConn net.Conn) { func (s *Server) handleRawConn(rawConn net.Conn) {
rawConn.SetDeadline(time.Now().Add(s.opts.connectionTimeout))
conn, authInfo, err := s.useTransportAuthenticator(rawConn) conn, authInfo, err := s.useTransportAuthenticator(rawConn)
if err != nil { if err != nil {
s.mu.Lock() s.mu.Lock()
s.errorf("ServerHandshake(%q) failed: %v", rawConn.RemoteAddr(), err) s.errorf("ServerHandshake(%q) failed: %v", rawConn.RemoteAddr(), err)
s.mu.Unlock() s.mu.Unlock()
grpclog.Warningf("grpc: Server.Serve failed to complete security handshake from %q: %v", rawConn.RemoteAddr(), err) grpclog.Warningf("grpc: Server.Serve failed to complete security handshake from %q: %v", rawConn.RemoteAddr(), err)
// If serverHandShake returns ErrConnDispatched, keep rawConn open. // If serverHandshake returns ErrConnDispatched, keep rawConn open.
if err != credentials.ErrConnDispatched { if err != credentials.ErrConnDispatched {
rawConn.Close() rawConn.Close()
} }
rawConn.SetDeadline(time.Time{})
return return
} }
@ -521,18 +535,21 @@ func (s *Server) handleRawConn(rawConn net.Conn) {
s.mu.Unlock() s.mu.Unlock()
if s.opts.useHandlerImpl { if s.opts.useHandlerImpl {
rawConn.SetDeadline(time.Time{})
s.serveUsingHandler(conn) s.serveUsingHandler(conn)
} else { } else {
s.serveHTTP2Transport(conn, authInfo) st := s.newHTTP2Transport(conn, authInfo)
if st == nil {
return
}
rawConn.SetDeadline(time.Time{})
s.serveStreams(st)
} }
} }
// serveHTTP2Transport sets up a http/2 transport (using the // newHTTP2Transport sets up a http/2 transport (using the
// gRPC http2 server transport in transport/http2_server.go) and // gRPC http2 server transport in transport/http2_server.go).
// serves streams on it. func (s *Server) newHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) transport.ServerTransport {
// This is run in its own goroutine (it does network I/O in
// transport.NewServerTransport).
func (s *Server) serveHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo) {
config := &transport.ServerConfig{ config := &transport.ServerConfig{
MaxStreams: s.opts.maxConcurrentStreams, MaxStreams: s.opts.maxConcurrentStreams,
AuthInfo: authInfo, AuthInfo: authInfo,
@ -552,13 +569,13 @@ func (s *Server) serveHTTP2Transport(c net.Conn, authInfo credentials.AuthInfo)
s.mu.Unlock() s.mu.Unlock()
c.Close() c.Close()
grpclog.Warningln("grpc: Server.Serve failed to create ServerTransport: ", err) grpclog.Warningln("grpc: Server.Serve failed to create ServerTransport: ", err)
return return nil
} }
if !s.addConn(st) { if !s.addConn(st) {
st.Close() st.Close()
return return nil
} }
s.serveStreams(st) return st
} }
func (s *Server) serveStreams(st transport.ServerTransport) { func (s *Server) serveStreams(st transport.ServerTransport) {

View File

@ -123,10 +123,9 @@ type serverHandlerTransport struct {
// when WriteStatus is called. // when WriteStatus is called.
writes chan func() writes chan func()
mu sync.Mutex // block concurrent WriteStatus calls
// streamDone indicates whether WriteStatus has been called and writes channel // e.g. grpc/(*serverStream).SendMsg/RecvMsg
// has been closed. writeStatusMu sync.Mutex
streamDone bool
} }
func (ht *serverHandlerTransport) Close() error { func (ht *serverHandlerTransport) Close() error {
@ -177,13 +176,9 @@ func (ht *serverHandlerTransport) do(fn func()) error {
} }
func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) error { func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) error {
ht.mu.Lock() ht.writeStatusMu.Lock()
if ht.streamDone { defer ht.writeStatusMu.Unlock()
ht.mu.Unlock()
return nil
}
ht.streamDone = true
ht.mu.Unlock()
err := ht.do(func() { err := ht.do(func() {
ht.writeCommonHeaders(s) ht.writeCommonHeaders(s)
@ -222,7 +217,11 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro
} }
} }
}) })
if err == nil { // transport has not been closed
ht.Close()
close(ht.writes) close(ht.writes)
}
return err return err
} }

View File

@ -152,12 +152,12 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err
Val: uint32(iwz)}) Val: uint32(iwz)})
} }
if err := framer.fr.WriteSettings(isettings...); err != nil { if err := framer.fr.WriteSettings(isettings...); err != nil {
return nil, connectionErrorf(true, err, "transport: %v", err) return nil, connectionErrorf(false, err, "transport: %v", err)
} }
// Adjust the connection flow control window if needed. // Adjust the connection flow control window if needed.
if delta := uint32(icwz - defaultWindowSize); delta > 0 { if delta := uint32(icwz - defaultWindowSize); delta > 0 {
if err := framer.fr.WriteWindowUpdate(0, delta); err != nil { if err := framer.fr.WriteWindowUpdate(0, delta); err != nil {
return nil, connectionErrorf(true, err, "transport: %v", err) return nil, connectionErrorf(false, err, "transport: %v", err)
} }
} }
kp := config.KeepaliveParams kp := config.KeepaliveParams
@ -223,6 +223,31 @@ func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err
t.stats.HandleConn(t.ctx, connBegin) t.stats.HandleConn(t.ctx, connBegin)
} }
t.framer.writer.Flush() t.framer.writer.Flush()
// Check the validity of client preface.
preface := make([]byte, len(clientPreface))
if _, err := io.ReadFull(t.conn, preface); err != nil {
return nil, connectionErrorf(false, err, "transport: http2Server.HandleStreams failed to receive the preface from client: %v", err)
}
if !bytes.Equal(preface, clientPreface) {
return nil, connectionErrorf(false, nil, "transport: http2Server.HandleStreams received bogus greeting from client: %q", preface)
}
frame, err := t.framer.fr.ReadFrame()
if err == io.EOF || err == io.ErrUnexpectedEOF {
t.Close()
return
}
if err != nil {
return nil, connectionErrorf(false, err, "transport: http2Server.HandleStreams failed to read initial settings frame: %v", err)
}
atomic.StoreUint32(&t.activity, 1)
sf, ok := frame.(*http2.SettingsFrame)
if !ok {
return nil, connectionErrorf(false, nil, "transport: http2Server.HandleStreams saw invalid preface type %T from client", frame)
}
t.handleSettings(sf)
go func() { go func() {
loopyWriter(t.ctx, t.controlBuf, t.itemHandler) loopyWriter(t.ctx, t.controlBuf, t.itemHandler)
t.Close() t.Close()
@ -354,41 +379,6 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(
// typically run in a separate goroutine. // typically run in a separate goroutine.
// traceCtx attaches trace to ctx and returns the new context. // traceCtx attaches trace to ctx and returns the new context.
func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context.Context, string) context.Context) { func (t *http2Server) HandleStreams(handle func(*Stream), traceCtx func(context.Context, string) context.Context) {
// Check the validity of client preface.
preface := make([]byte, len(clientPreface))
if _, err := io.ReadFull(t.conn, preface); err != nil {
// Only log if it isn't a simple tcp accept check (ie: tcp balancer doing open/close socket)
if err != io.EOF {
errorf("transport: http2Server.HandleStreams failed to receive the preface from client: %v", err)
}
t.Close()
return
}
if !bytes.Equal(preface, clientPreface) {
errorf("transport: http2Server.HandleStreams received bogus greeting from client: %q", preface)
t.Close()
return
}
frame, err := t.framer.fr.ReadFrame()
if err == io.EOF || err == io.ErrUnexpectedEOF {
t.Close()
return
}
if err != nil {
errorf("transport: http2Server.HandleStreams failed to read initial settings frame: %v", err)
t.Close()
return
}
atomic.StoreUint32(&t.activity, 1)
sf, ok := frame.(*http2.SettingsFrame)
if !ok {
errorf("transport: http2Server.HandleStreams saw invalid preface type %T from client", frame)
t.Close()
return
}
t.handleSettings(sf)
for { for {
frame, err := t.framer.fr.ReadFrame() frame, err := t.framer.fr.ReadFrame()
atomic.StoreUint32(&t.activity, 1) atomic.StoreUint32(&t.activity, 1)