
Since we now have a common set of error definitions, mapped to existing error codes, we no longer need the specialized error codes used for interaction with linux processes. The main issue was that string matching was being used to map these to useful error codes. With this change, we use errors defined in the `errdefs` package, which map cleanly to GRPC error codes and are recoverable on either side of the request. The main focus of this PR was in removin these from the shim. We may need follow ups to ensure error codes are preserved by the `Tasks` service. Signed-off-by: Stephen J Day <stephen.day@docker.com>
179 lines
3.9 KiB
Go
179 lines
3.9 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
|
|
|
|
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")
|
|
}
|
|
|
|
e := &execProcess{
|
|
id: id,
|
|
parent: parent,
|
|
stdio: stdio{
|
|
stdin: r.Stdin,
|
|
stdout: r.Stdout,
|
|
stderr: r.Stderr,
|
|
terminal: r.Terminal,
|
|
},
|
|
}
|
|
var (
|
|
err error
|
|
socket *runc.Socket
|
|
io runc.IO
|
|
pidfile = filepath.Join(path, fmt.Sprintf("%s.pid", id))
|
|
)
|
|
if r.Terminal {
|
|
if socket, err = runc.NewConsoleSocket(filepath.Join(path, "pty.sock")); err != nil {
|
|
return nil, errors.Wrap(err, "failed to create runc console socket")
|
|
}
|
|
defer os.Remove(socket.Path())
|
|
} else {
|
|
// TODO: get uid/gid
|
|
if io, err = runc.NewPipeIO(0, 0); err != nil {
|
|
return nil, 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
|
|
}
|
|
// process exec request
|
|
var spec specs.Process
|
|
if err := json.Unmarshal(r.Spec.Value, &spec); err != nil {
|
|
return nil, err
|
|
}
|
|
spec.Terminal = r.Terminal
|
|
|
|
if err := parent.runtime.Exec(context, parent.id, spec, opts); err != nil {
|
|
return nil, parent.runtimeError(err, "OCI runtime exec failed")
|
|
}
|
|
if r.Stdin != "" {
|
|
sc, err := fifo.OpenFifo(context, r.Stdin, syscall.O_WRONLY|syscall.O_NONBLOCK, 0)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to open stdin fifo %s", r.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 nil, errors.Wrap(err, "failed to retrieve console master")
|
|
}
|
|
e.console = console
|
|
if err := copyConsole(context, console, r.Stdin, r.Stdout, r.Stderr, &e.WaitGroup, ©WaitGroup); err != nil {
|
|
return nil, errors.Wrap(err, "failed to start console copy")
|
|
}
|
|
} else {
|
|
if err := copyPipes(context, io, r.Stdin, r.Stdout, r.Stderr, &e.WaitGroup, ©WaitGroup); err != nil {
|
|
return nil, errors.Wrap(err, "failed to start io pipe copy")
|
|
}
|
|
}
|
|
copyWaitGroup.Wait()
|
|
pid, err := runc.ReadPidFile(opts.PidFile)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to retrieve OCI runtime exec pid")
|
|
}
|
|
e.pid = pid
|
|
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.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
|
|
}
|