diff --git a/linux/shim/exec.go b/linux/shim/exec.go index 41e4924ec..c031298f9 100644 --- a/linux/shim/exec.go +++ b/linux/shim/exec.go @@ -105,10 +105,11 @@ func newExecProcess(context context.Context, path string, r *shimapi.ExecProcess 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 { + console, err = e.parent.platform.copyConsole(context, console, r.Stdin, r.Stdout, r.Stderr, &e.WaitGroup, ©WaitGroup) + if err != nil { return nil, errors.Wrap(err, "failed to start console copy") } + e.console = console } 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") @@ -142,6 +143,7 @@ func (e *execProcess) ExitedAt() time.Time { 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 { diff --git a/linux/shim/init.go b/linux/shim/init.go index d1eb0a86e..452c1ff53 100644 --- a/linux/shim/init.go +++ b/linux/shim/init.go @@ -39,21 +39,22 @@ type initProcess struct { // the reaper interface. mu sync.Mutex - id string - bundle string - console console.Console - io runc.IO - runtime *runc.Runc - status int - exited time.Time - pid int - closers []io.Closer - stdin io.Closer - stdio stdio - rootfs string + id string + bundle string + console console.Console + platform platform + io runc.IO + runtime *runc.Runc + status int + exited time.Time + pid int + closers []io.Closer + stdin io.Closer + stdio stdio + rootfs string } -func newInitProcess(context context.Context, path, namespace string, r *shimapi.CreateTaskRequest) (*initProcess, error) { +func newInitProcess(context context.Context, plat platform, path, namespace string, r *shimapi.CreateTaskRequest) (*initProcess, error) { var success bool if err := identifiers.Validate(r.ID); err != nil { @@ -98,9 +99,10 @@ func newInitProcess(context context.Context, path, namespace string, r *shimapi. Root: filepath.Join(RuncRoot, namespace), } p := &initProcess{ - id: r.ID, - bundle: r.Bundle, - runtime: runtime, + id: r.ID, + bundle: r.Bundle, + runtime: runtime, + platform: plat, stdio: stdio{ stdin: r.Stdin, stdout: r.Stdout, @@ -170,10 +172,11 @@ func newInitProcess(context context.Context, path, namespace string, r *shimapi. if err != nil { return nil, errors.Wrap(err, "failed to retrieve console master") } - p.console = console - if err := copyConsole(context, console, r.Stdin, r.Stdout, r.Stderr, &p.WaitGroup, ©WaitGroup); err != nil { + console, err = plat.copyConsole(context, console, r.Stdin, r.Stdout, r.Stderr, &p.WaitGroup, ©WaitGroup) + if err != nil { return nil, errors.Wrap(err, "failed to start console copy") } + p.console = console } else { if err := copyPipes(context, io, r.Stdin, r.Stdout, r.Stderr, &p.WaitGroup, ©WaitGroup); err != nil { return nil, errors.Wrap(err, "failed to start io pipe copy") @@ -238,6 +241,9 @@ func (p *initProcess) Delete(context context.Context) error { return fmt.Errorf("cannot delete a running container") } p.killAll(context) + if err := p.platform.shutdownConsole(context, p.console); err != nil { + log.G(context).WithError(err).Warn("Failed to shutdown container console") + } p.Wait() err = p.runtime.Delete(context, p.id, nil) if p.io != nil { diff --git a/linux/shim/service.go b/linux/shim/service.go index 5555394a8..21b0488b7 100644 --- a/linux/shim/service.go +++ b/linux/shim/service.go @@ -56,10 +56,20 @@ func NewService(path, namespace, address string) (*Service, error) { namespace: namespace, context: context, } + if err := s.initPlatform(); err != nil { + return nil, errors.Wrap(err, "failed to initialized platform behavior") + } go s.forward(client) return s, nil } +// platform handles platform-specific behavior that may differs across +// platform implementations +type platform interface { + copyConsole(ctx context.Context, console console.Console, stdin, stdout, stderr string, wg, cwg *sync.WaitGroup) (console.Console, error) + shutdownConsole(ctx context.Context, console console.Console) error +} + type Service struct { initProcess *initProcess path string @@ -72,10 +82,12 @@ type Service struct { deferredEvent interface{} namespace string context context.Context + + platform platform } func (s *Service) Create(ctx context.Context, r *shimapi.CreateTaskRequest) (*shimapi.CreateTaskResponse, error) { - process, err := newInitProcess(ctx, s.path, s.namespace, r) + process, err := newInitProcess(ctx, s.platform, s.path, s.namespace, r) if err != nil { return nil, errdefs.ToGRPC(err) } diff --git a/linux/shim/service_linux.go b/linux/shim/service_linux.go new file mode 100644 index 000000000..2c46481e0 --- /dev/null +++ b/linux/shim/service_linux.go @@ -0,0 +1,87 @@ +package shim + +import ( + "io" + "sync" + "syscall" + + "github.com/containerd/console" + "github.com/containerd/fifo" + "github.com/pkg/errors" + "golang.org/x/net/context" +) + +type linuxPlatform struct { + epoller *console.Epoller +} + +func (p *linuxPlatform) copyConsole(ctx context.Context, console console.Console, stdin, stdout, stderr string, wg, cwg *sync.WaitGroup) (console.Console, error) { + if p.epoller == nil { + return nil, errors.New("uninitialized epoller") + } + + epollConsole, err := p.epoller.Add(console) + if err != nil { + return nil, err + } + + if stdin != "" { + in, err := fifo.OpenFifo(ctx, stdin, syscall.O_RDONLY, 0) + if err != nil { + return nil, err + } + cwg.Add(1) + go func() { + cwg.Done() + io.Copy(epollConsole, in) + }() + } + + outw, err := fifo.OpenFifo(ctx, stdout, syscall.O_WRONLY, 0) + if err != nil { + return nil, err + } + outr, err := fifo.OpenFifo(ctx, stdout, syscall.O_RDONLY, 0) + if err != nil { + return nil, err + } + wg.Add(1) + cwg.Add(1) + go func() { + cwg.Done() + io.Copy(outw, epollConsole) + epollConsole.Close() + outr.Close() + outw.Close() + wg.Done() + }() + return epollConsole, nil +} + +func (p *linuxPlatform) shutdownConsole(ctx context.Context, cons console.Console) error { + if p.epoller == nil { + return errors.New("uninitialized epoller") + } + epollConsole, ok := cons.(*console.EpollConsole) + if !ok { + return errors.Errorf("expected EpollConsole, got %#v", cons) + } + return epollConsole.Shutdown(p.epoller.CloseConsole) +} + +// initialize a single epoll fd to manage our consoles. `initPlatform` should +// only be called once. +func (s *Service) initPlatform() error { + if s.platform != nil { + return nil + } + epoller, err := console.NewEpoller() + if err != nil { + return errors.Wrap(err, "failed to initialize epoller") + } + s.platform = &linuxPlatform{ + epoller: epoller, + } + go epoller.Wait() + return nil +} diff --git a/linux/shim/service_unix.go b/linux/shim/service_unix.go new file mode 100644 index 000000000..a73786a9d --- /dev/null +++ b/linux/shim/service_unix.go @@ -0,0 +1,58 @@ +// +build !windows,!linux + +package shim + +import ( + "io" + "sync" + "syscall" + + "github.com/containerd/console" + "github.com/containerd/fifo" + "golang.org/x/net/context" +) + +type unixPlatform struct { +} + +func (p *unixPlatform) copyConsole(ctx context.Context, console console.Console, stdin, stdout, stderr string, wg, cwg *sync.WaitGroup) (console.Console, error) { + if stdin != "" { + in, err := fifo.OpenFifo(ctx, stdin, syscall.O_RDONLY, 0) + if err != nil { + return nil, err + } + cwg.Add(1) + go func() { + cwg.Done() + io.Copy(console, in) + }() + } + outw, err := fifo.OpenFifo(ctx, stdout, syscall.O_WRONLY, 0) + if err != nil { + return nil, err + } + outr, err := fifo.OpenFifo(ctx, stdout, syscall.O_RDONLY, 0) + if err != nil { + return nil, err + } + wg.Add(1) + cwg.Add(1) + go func() { + cwg.Done() + io.Copy(outw, console) + console.Close() + outr.Close() + outw.Close() + wg.Done() + }() + return console, nil +} + +func (p *unixPlatform) shutdownConsole(ctx context.Context, cons console.Console) error { + return nil +} + +func (s *Service) initPlatform() error { + s.platform = &unixPlatform{} + return nil +}