containerd/linux/shim/exec.go
Michael Crosby 63878d14ea Add create/start to exec processes in shim
This splits up the create and start of an exec process in the shim to
have two separate steps like the initial process.  This will allow
better state reporting for individual process along with a more robust
wait for execs.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2017-08-02 13:50:08 -04:00

185 lines
4.1 KiB
Go

// +build !windows
package shim
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sync"
"syscall"
"time"
"golang.org/x/sys/unix"
"github.com/containerd/console"
"github.com/containerd/containerd/identifiers"
shimapi "github.com/containerd/containerd/linux/shim/v1"
"github.com/containerd/fifo"
runc "github.com/containerd/go-runc"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
type execProcess struct {
sync.WaitGroup
id string
console console.Console
io runc.IO
status int
exited time.Time
pid int
closers []io.Closer
stdin io.Closer
stdio stdio
path string
spec specs.Process
parent *initProcess
}
func newExecProcess(context context.Context, path string, r *shimapi.ExecProcessRequest, parent *initProcess, id string) (process, error) {
if err := identifiers.Validate(id); err != nil {
return nil, errors.Wrapf(err, "invalid exec id")
}
// process exec request
var spec specs.Process
if err := json.Unmarshal(r.Spec.Value, &spec); err != nil {
return nil, err
}
spec.Terminal = r.Terminal
e := &execProcess{
id: id,
path: path,
parent: parent,
spec: spec,
stdio: stdio{
stdin: r.Stdin,
stdout: r.Stdout,
stderr: r.Stderr,
terminal: r.Terminal,
},
}
return e, nil
}
func (e *execProcess) ID() string {
return e.id
}
func (e *execProcess) Pid() int {
return e.pid
}
func (e *execProcess) Status() int {
return e.status
}
func (e *execProcess) ExitedAt() time.Time {
return e.exited
}
func (e *execProcess) Exited(status int) {
e.status = status
e.exited = time.Now()
e.parent.platform.shutdownConsole(context.Background(), e.console)
e.Wait()
if e.io != nil {
for _, c := range e.closers {
c.Close()
}
e.io.Close()
}
}
func (e *execProcess) Delete(ctx context.Context) error {
return nil
}
func (e *execProcess) Resize(ws console.WinSize) error {
if e.console == nil {
return nil
}
return e.console.Resize(ws)
}
func (e *execProcess) Kill(ctx context.Context, sig uint32, _ bool) error {
if err := unix.Kill(e.pid, syscall.Signal(sig)); err != nil {
return errors.Wrapf(checkKillError(err), "exec kill error")
}
return nil
}
func (e *execProcess) Stdin() io.Closer {
return e.stdin
}
func (e *execProcess) Stdio() stdio {
return e.stdio
}
func (e *execProcess) Start(ctx context.Context) (err error) {
var (
socket *runc.Socket
io runc.IO
pidfile = filepath.Join(e.path, fmt.Sprintf("%s.pid", e.id))
)
if e.stdio.terminal {
if socket, err = runc.NewConsoleSocket(filepath.Join(e.path, "pty.sock")); err != nil {
return errors.Wrap(err, "failed to create runc console socket")
}
defer os.Remove(socket.Path())
} else {
if io, err = runc.NewPipeIO(0, 0); err != nil {
return errors.Wrap(err, "failed to create runc io pipes")
}
e.io = io
}
opts := &runc.ExecOpts{
PidFile: pidfile,
IO: io,
Detach: true,
}
if socket != nil {
opts.ConsoleSocket = socket
}
if err := e.parent.runtime.Exec(ctx, e.parent.id, e.spec, opts); err != nil {
return e.parent.runtimeError(err, "OCI runtime exec failed")
}
if e.stdio.stdin != "" {
sc, err := fifo.OpenFifo(ctx, e.stdio.stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0)
if err != nil {
return errors.Wrapf(err, "failed to open stdin fifo %s", e.stdio.stdin)
}
e.closers = append(e.closers, sc)
e.stdin = sc
}
var copyWaitGroup sync.WaitGroup
if socket != nil {
console, err := socket.ReceiveMaster()
if err != nil {
return errors.Wrap(err, "failed to retrieve console master")
}
e.console = console
if err := e.parent.platform.copyConsole(ctx, console, e.stdio.stdin, e.stdio.stdout, e.stdio.stderr, &e.WaitGroup, &copyWaitGroup); err != nil {
return errors.Wrap(err, "failed to start console copy")
}
} else {
if err := copyPipes(ctx, io, e.stdio.stdin, e.stdio.stdout, e.stdio.stderr, &e.WaitGroup, &copyWaitGroup); err != nil {
return errors.Wrap(err, "failed to start io pipe copy")
}
}
copyWaitGroup.Wait()
pid, err := runc.ReadPidFile(opts.PidFile)
if err != nil {
return errors.Wrap(err, "failed to retrieve OCI runtime exec pid")
}
e.pid = pid
return nil
}