vendor: update containerd to 1.0.1-rc0
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>docker-18.09
parent
99b32f1878
commit
b8dc00de71
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
12
vendor.conf
12
vendor.conf
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
package archive
|
||||||
|
|
||||||
|
// ApplyOpt allows setting mutable archive apply properties on creation
|
||||||
|
type ApplyOpt func(options *ApplyOptions) error
|
|
@ -0,0 +1,7 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package archive
|
||||||
|
|
||||||
|
// ApplyOptions provides additional options for an Apply operation
|
||||||
|
type ApplyOptions struct {
|
||||||
|
}
|
28
vendor/github.com/containerd/containerd/archive/tar_opts_windows.go
generated
vendored
Normal file
28
vendor/github.com/containerd/containerd/archive/tar_opts_windows.go
generated
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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{}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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...)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 = ""
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build cgo,linux cgo,freebsd
|
// +build cgo,linux
|
||||||
|
|
||||||
package system
|
package system
|
||||||
|
|
||||||
|
|
|
@ -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())
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"`
|
||||||
|
|
|
@ -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 = ""
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue