diff --git a/api/grpc/server/server.go b/api/grpc/server/server.go index 7b9bb57..6e105cc 100644 --- a/api/grpc/server/server.go +++ b/api/grpc/server/server.go @@ -36,6 +36,7 @@ func (s *apiServer) CreateContainer(ctx context.Context, c *types.CreateContaine e.Stdout = c.Stdout e.Stderr = c.Stderr e.Stdin = c.Stdin + e.Console = c.Console if c.Checkpoint != "" { e.Checkpoint = &runtime.Checkpoint{ Name: c.Checkpoint, diff --git a/ctr/container.go b/ctr/container.go index 900e707..efd06cb 100644 --- a/ctr/container.go +++ b/ctr/container.go @@ -11,6 +11,8 @@ import ( "github.com/codegangsta/cli" "github.com/docker/containerd/api/grpc/types" + "github.com/docker/docker/pkg/term" + "github.com/opencontainers/runc/libcontainer" netcontext "golang.org/x/net/context" "google.golang.org/grpc" ) @@ -72,6 +74,10 @@ var StartCommand = cli.Command{ Name: "interactive,i", Usage: "connect to the stdio of the container", }, + cli.BoolFlag{ + Name: "tty,t", + Usage: "allocate a tty for use with the container", + }, }, Action: func(context *cli.Context) { var ( @@ -84,6 +90,11 @@ var StartCommand = cli.Command{ if id == "" { fatal("container id cannot be empty", 1) } + c := getClient() + events, err := c.Events(netcontext.Background(), &types.EventsRequest{}) + if err != nil { + fatal(err.Error(), 1) + } r := &types.CreateContainerRequest{ Id: id, BundlePath: path, @@ -94,17 +105,57 @@ var StartCommand = cli.Command{ fatal(err.Error(), 1) } } - c := getClient() + if context.Bool("tty") { + if err := attachTty(r); err != nil { + fatal(err.Error(), 1) + } + } if _, err := c.CreateContainer(netcontext.Background(), r); err != nil { fatal(err.Error(), 1) } if stdin != nil { - io.Copy(stdin, os.Stdin) + go func() { + io.Copy(stdin, os.Stdin) + if state != nil { + term.RestoreTerminal(os.Stdin.Fd(), state) + } + }() + for { + e, err := events.Recv() + if err != nil { + fatal(err.Error(), 1) + } + if e.Id == id && e.Type == "exit" { + os.Exit(int(e.Status)) + } + } } }, } -var stdin io.WriteCloser +var ( + stdin io.WriteCloser + state *term.State +) + +func attachTty(r *types.CreateContainerRequest) error { + console, err := libcontainer.NewConsole(os.Getuid(), os.Getgid()) + if err != nil { + return err + } + r.Console = console.Path() + stdin = console + go func() { + io.Copy(os.Stdout, console) + console.Close() + }() + s, err := term.SetRawTerminal(os.Stdin.Fd()) + if err != nil { + return err + } + state = s + return nil +} func attachStdio(r *types.CreateContainerRequest) error { dir, err := ioutil.TempDir("", "ctr-") diff --git a/event.go b/event.go index 632fa1a..f316ff3 100644 --- a/event.go +++ b/event.go @@ -41,6 +41,7 @@ type Event struct { Stdout string Stderr string Stdin string + Console string Pid int Status int Signal os.Signal diff --git a/io.go b/io.go index 0c4a0d0..8162807 100644 --- a/io.go +++ b/io.go @@ -53,8 +53,10 @@ type copier struct { func (l *copier) Close() (err error) { for _, c := range append(l.closers, l.config.Stdin, l.config.Stdout, l.config.Stderr) { - if cerr := c.Close(); err == nil { - err = cerr + if c != nil { + if cerr := c.Close(); err == nil { + err = cerr + } } } return err diff --git a/linux/linux.go b/linux/linux.go index a3a94b6..1ea8b6d 100644 --- a/linux/linux.go +++ b/linux/linux.go @@ -188,9 +188,18 @@ func (p *libcontainerProcess) Signal(s os.Signal) error { func (p *libcontainerProcess) Close() error { // in close we always need to call wait to close/flush any pipes _, err := p.process.Wait() - p.process.Stdin.(io.Closer).Close() - p.process.Stdout.(io.Closer).Close() - p.process.Stderr.(io.Closer).Close() + // explicitly close any open fd on the process + for _, cl := range []interface{}{ + p.process.Stderr, + p.process.Stdout, + p.process.Stdin, + } { + if cl != nil { + if c, ok := cl.(io.Closer); ok { + c.Close() + } + } + } return err } @@ -375,7 +384,7 @@ func (r *libcontainerRuntime) Type() string { return "libcontainer" } -func (r *libcontainerRuntime) Create(id, bundlePath string) (runtime.Container, *runtime.IO, error) { +func (r *libcontainerRuntime) Create(id, bundlePath, consolePath string) (runtime.Container, *runtime.IO, error) { spec, rspec, err := r.loadSpec( filepath.Join(bundlePath, "config.json"), filepath.Join(bundlePath, "runtime.json"), @@ -397,11 +406,9 @@ func (r *libcontainerRuntime) Create(id, bundlePath string) (runtime.Container, } var rio runtime.IO if spec.Process.Terminal { - console, err := process.NewConsole(int(spec.Process.User.UID)) - if err != nil { + if err := process.ConsoleFromPath(consolePath); err != nil { return nil, nil, err } - rio.Console = console } else { i, err := process.InitializeIO(int(spec.Process.User.UID)) if err != nil { diff --git a/runc/runc.go b/runc/runc.go index 9de6a4e..a2bd77b 100644 --- a/runc/runc.go +++ b/runc/runc.go @@ -147,7 +147,7 @@ func (r *runcRuntime) Type() string { return "runc" } -func (r *runcRuntime) Create(id, bundlePath string) (runtime.Container, *runtime.IO, error) { +func (r *runcRuntime) Create(id, bundlePath, consolePath string) (runtime.Container, *runtime.IO, error) { var s specs.Spec f, err := os.Open(filepath.Join(bundlePath, "config.json")) if err != nil { diff --git a/runtime/container.go b/runtime/container.go index 16d5283..d2e5e5e 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -32,10 +32,9 @@ type Console interface { } type IO struct { - Stdin io.WriteCloser - Stdout io.ReadCloser - Stderr io.ReadCloser - Console Console + Stdin io.WriteCloser + Stdout io.ReadCloser + Stderr io.ReadCloser } func (i *IO) Close() error { @@ -44,7 +43,6 @@ func (i *IO) Close() error { i.Stdin, i.Stdout, i.Stderr, - i.Console, } { if c != nil { if err := c.Close(); oerr == nil { diff --git a/runtime/runtime.go b/runtime/runtime.go index aabe055..500067d 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -20,7 +20,7 @@ type Runtime interface { // Type of the runtime Type() string // Create creates a new container initialized but without it starting it - Create(id, bundlePath string) (Container, *IO, error) + Create(id, bundlePath, consolePath string) (Container, *IO, error) // StartProcess adds a new process to the container StartProcess(Container, specs.Process) (Process, *IO, error) } diff --git a/start.go b/start.go index 1f9ec8b..8bf6775 100644 --- a/start.go +++ b/start.go @@ -5,7 +5,7 @@ type StartEvent struct { } func (h *StartEvent) Handle(e *Event) error { - container, io, err := h.s.runtime.Create(e.ID, e.BundlePath) + container, io, err := h.s.runtime.Create(e.ID, e.BundlePath, e.Console) if err != nil { return err }