161 lines
3.9 KiB
Go
161 lines
3.9 KiB
Go
package runcexecutor
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/containerd/console"
|
|
runc "github.com/containerd/go-runc"
|
|
"github.com/docker/docker/pkg/signal"
|
|
"github.com/moby/buildkit/executor"
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
func updateRuncFieldsForHostOS(runtime *runc.Runc) {
|
|
// PdeathSignal only supported on unix platforms
|
|
runtime.PdeathSignal = syscall.SIGKILL // this can still leak the process
|
|
}
|
|
|
|
func (w *runcExecutor) run(ctx context.Context, id, bundle string, process executor.ProcessInfo) error {
|
|
return w.callWithIO(ctx, id, bundle, process, func(ctx context.Context, started chan<- int, io runc.IO) error {
|
|
_, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{
|
|
NoPivot: w.noPivot,
|
|
Started: started,
|
|
IO: io,
|
|
})
|
|
return err
|
|
})
|
|
}
|
|
|
|
func (w *runcExecutor) exec(ctx context.Context, id, bundle string, specsProcess *specs.Process, process executor.ProcessInfo) error {
|
|
return w.callWithIO(ctx, id, bundle, process, func(ctx context.Context, started chan<- int, io runc.IO) error {
|
|
return w.runc.Exec(ctx, id, *specsProcess, &runc.ExecOpts{
|
|
Started: started,
|
|
IO: io,
|
|
})
|
|
})
|
|
}
|
|
|
|
type runcCall func(ctx context.Context, started chan<- int, io runc.IO) error
|
|
|
|
func (w *runcExecutor) callWithIO(ctx context.Context, id, bundle string, process executor.ProcessInfo, call runcCall) error {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
if !process.Meta.Tty {
|
|
return call(ctx, nil, &forwardIO{stdin: process.Stdin, stdout: process.Stdout, stderr: process.Stderr})
|
|
}
|
|
|
|
ptm, ptsName, err := console.NewPty()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pts, err := os.OpenFile(ptsName, os.O_RDWR|syscall.O_NOCTTY, 0)
|
|
if err != nil {
|
|
ptm.Close()
|
|
return err
|
|
}
|
|
|
|
eg, ctx := errgroup.WithContext(ctx)
|
|
|
|
defer func() {
|
|
if process.Stdin != nil {
|
|
process.Stdin.Close()
|
|
}
|
|
pts.Close()
|
|
ptm.Close()
|
|
cancel() // this will shutdown resize loop
|
|
err := eg.Wait()
|
|
if err != nil {
|
|
logrus.Warningf("error while shutting down tty io: %s", err)
|
|
}
|
|
}()
|
|
|
|
if process.Stdin != nil {
|
|
eg.Go(func() error {
|
|
_, err := io.Copy(ptm, process.Stdin)
|
|
// stdin might be a pipe, so this is like EOF
|
|
if errors.Is(err, io.ErrClosedPipe) {
|
|
return nil
|
|
}
|
|
return err
|
|
})
|
|
}
|
|
|
|
if process.Stdout != nil {
|
|
eg.Go(func() error {
|
|
_, err := io.Copy(process.Stdout, ptm)
|
|
// ignore `read /dev/ptmx: input/output error` when ptm is closed
|
|
var ptmClosedError *os.PathError
|
|
if errors.As(err, &ptmClosedError) {
|
|
if ptmClosedError.Op == "read" &&
|
|
ptmClosedError.Path == "/dev/ptmx" &&
|
|
ptmClosedError.Err == syscall.EIO {
|
|
return nil
|
|
}
|
|
}
|
|
return err
|
|
})
|
|
}
|
|
|
|
started := make(chan int, 1)
|
|
|
|
eg.Go(func() error {
|
|
startedCtx, timeout := context.WithTimeout(ctx, 10*time.Second)
|
|
defer timeout()
|
|
var runcProcess *os.Process
|
|
select {
|
|
case <-startedCtx.Done():
|
|
return errors.New("runc started message never received")
|
|
case pid, ok := <-started:
|
|
if !ok {
|
|
return errors.New("runc process failed to send pid")
|
|
}
|
|
runcProcess, err = os.FindProcess(pid)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to find runc process for pid %d", pid)
|
|
}
|
|
defer runcProcess.Release()
|
|
}
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
case resize := <-process.Resize:
|
|
err = ptm.Resize(console.WinSize{
|
|
Height: uint16(resize.Rows),
|
|
Width: uint16(resize.Cols),
|
|
})
|
|
if err != nil {
|
|
logrus.Errorf("failed to resize ptm: %s", err)
|
|
}
|
|
err = runcProcess.Signal(signal.SIGWINCH)
|
|
if err != nil {
|
|
logrus.Errorf("failed to send SIGWINCH to process: %s", err)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
runcIO := &forwardIO{}
|
|
if process.Stdin != nil {
|
|
runcIO.stdin = pts
|
|
}
|
|
if process.Stdout != nil {
|
|
runcIO.stdout = pts
|
|
}
|
|
if process.Stderr != nil {
|
|
runcIO.stderr = pts
|
|
}
|
|
|
|
return call(ctx, started, runcIO)
|
|
}
|