From 9de10592bc44924ac2e9571871969eb669794299 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 11:47:53 -0800 Subject: [PATCH 1/5] Move console into execdriver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 8c783c1c1336d8f2d1b08b9cbd8e2298d066750c Component: engine --- components/engine/container.go | 93 +++------------- components/engine/execdriver/console.go | 137 ++++++++++++++++++++++++ components/engine/utils/utils.go | 2 +- components/engine/utils/utils_test.go | 2 +- 4 files changed, 155 insertions(+), 79 deletions(-) create mode 100644 components/engine/execdriver/console.go diff --git a/components/engine/container.go b/components/engine/container.go index 9c7fc8ffd7..f38876ed24 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -10,10 +10,8 @@ import ( "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/links" "github.com/dotcloud/docker/nat" - "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" - "github.com/kr/pty" "io" "io/ioutil" "log" @@ -57,11 +55,11 @@ type Container struct { Driver string command *execdriver.Command + console execdriver.Console stdout *utils.WriteBroadcaster stderr *utils.WriteBroadcaster stdin io.ReadCloser stdinPipe io.WriteCloser - ptyMaster io.Closer runtime *Runtime @@ -213,56 +211,6 @@ func (container *Container) generateEnvConfig(env []string) error { return nil } -func (container *Container) setupPty() error { - ptyMaster, ptySlave, err := pty.Open() - if err != nil { - return err - } - container.ptyMaster = ptyMaster - container.command.Stdout = ptySlave - container.command.Stderr = ptySlave - container.command.Console = ptySlave.Name() - - // Copy the PTYs to our broadcasters - go func() { - defer container.stdout.CloseWriters() - utils.Debugf("startPty: begin of stdout pipe") - io.Copy(container.stdout, ptyMaster) - utils.Debugf("startPty: end of stdout pipe") - }() - - // stdin - if container.Config.OpenStdin { - container.command.Stdin = ptySlave - container.command.SysProcAttr.Setctty = true - go func() { - defer container.stdin.Close() - utils.Debugf("startPty: begin of stdin pipe") - io.Copy(ptyMaster, container.stdin) - utils.Debugf("startPty: end of stdin pipe") - }() - } - return nil -} - -func (container *Container) setupStd() error { - container.command.Stdout = container.stdout - container.command.Stderr = container.stderr - if container.Config.OpenStdin { - stdin, err := container.command.StdinPipe() - if err != nil { - return err - } - go func() { - defer stdin.Close() - utils.Debugf("start: begin of stdin pipe") - io.Copy(stdin, container.stdin) - utils.Debugf("start: end of stdin pipe") - }() - } - return nil -} - func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { var cStdout, cStderr io.ReadCloser @@ -593,14 +541,13 @@ func (container *Container) Start() (err error) { } container.waitLock = make(chan struct{}) - // Setuping pipes and/or Pty - var setup func() error - if container.Config.Tty { - setup = container.setupPty - } else { - setup = container.setupStd + container.console, err = execdriver.NewConsole( + container.stdin, container.stdout, container.stderr, + container.Config.OpenStdin, container.Config.Tty) + if err != nil { + return err } - if err := setup(); err != nil { + if err := container.console.AttachTo(container.command); err != nil { return err } @@ -887,22 +834,20 @@ func (container *Container) cleanup() { link.Disable() } } - if container.Config.OpenStdin { if err := container.stdin.Close(); err != nil { utils.Errorf("%s: Error close stdin: %s", container.ID, err) } } - if err := container.stdout.CloseWriters(); err != nil { + if err := container.stdout.Close(); err != nil { utils.Errorf("%s: Error close stdout: %s", container.ID, err) } - if err := container.stderr.CloseWriters(); err != nil { + if err := container.stderr.Close(); err != nil { utils.Errorf("%s: Error close stderr: %s", container.ID, err) } - - if container.ptyMaster != nil { - if err := container.ptyMaster.Close(); err != nil { - utils.Errorf("%s: Error closing Pty master: %s", container.ID, err) + if container.console != nil { + if err := container.console.Close(); err != nil { + utils.Errorf("%s: Error closing console: %s", container.ID, err) } } @@ -994,11 +939,7 @@ func (container *Container) Wait() int { } func (container *Container) Resize(h, w int) error { - pty, ok := container.ptyMaster.(*os.File) - if !ok { - return fmt.Errorf("ptyMaster does not have Fd() method") - } - return term.SetWinsize(pty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) + return container.console.Resize(h, w) } func (container *Container) ExportRw() (archive.Archive, error) { @@ -1202,11 +1143,9 @@ func (container *Container) Exposes(p nat.Port) bool { } func (container *Container) GetPtyMaster() (*os.File, error) { - if container.ptyMaster == nil { + ttyConsole, ok := container.console.(*execdriver.TtyConsole) + if !ok { return nil, ErrNoTTY } - if pty, ok := container.ptyMaster.(*os.File); ok { - return pty, nil - } - return nil, ErrNotATTY + return ttyConsole.Master, nil } diff --git a/components/engine/execdriver/console.go b/components/engine/execdriver/console.go new file mode 100644 index 0000000000..01b45afb60 --- /dev/null +++ b/components/engine/execdriver/console.go @@ -0,0 +1,137 @@ +package execdriver + +import ( + "github.com/dotcloud/docker/pkg/term" + "github.com/kr/pty" + "io" + "os" +) + +type Console interface { + io.Closer + Resize(height, width int) error + AttachTo(command *Command) error +} + +type pipes struct { + Stdin io.ReadCloser + Stdout, Stderr io.WriteCloser +} + +func (p *pipes) Close() error { + if p.Stderr != nil { + p.Stdin.Close() + } + if p.Stdout != nil { + p.Stdout.Close() + } + if p.Stderr != nil { + p.Stderr.Close() + } + return nil +} + +func NewConsole(stdin io.ReadCloser, stdout, stderr io.WriteCloser, useStdin, tty bool) (Console, error) { + p := &pipes{ + Stdout: stdout, + Stderr: stderr, + } + if useStdin { + p.Stdin = stdin + } + if tty { + return NewTtyConsole(p) + } + return NewStdConsole(p) +} + +type TtyConsole struct { + Master *os.File + Slave *os.File + pipes *pipes +} + +func NewTtyConsole(p *pipes) (*TtyConsole, error) { + ptyMaster, ptySlave, err := pty.Open() + if err != nil { + return nil, err + } + tty := &TtyConsole{ + Master: ptyMaster, + Slave: ptySlave, + pipes: p, + } + return tty, nil +} + +func (t *TtyConsole) Resize(h, w int) error { + return term.SetWinsize(t.Master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) + +} + +func (t *TtyConsole) AttachTo(command *Command) error { + command.Stdout = t.Slave + command.Stderr = t.Slave + + command.Console = t.Slave.Name() + + go func() { + defer t.pipes.Stdout.Close() + io.Copy(t.pipes.Stdout, t.Master) + }() + + if t.pipes.Stdin != nil { + command.Stdin = t.Slave + command.SysProcAttr.Setctty = true + + go func() { + defer t.pipes.Stdin.Close() + io.Copy(t.Master, t.pipes.Stdin) + }() + } + return nil +} + +func (t *TtyConsole) Close() error { + err := t.Slave.Close() + if merr := t.Master.Close(); err == nil { + err = merr + } + return err +} + +type StdConsole struct { + pipes *pipes +} + +func NewStdConsole(p *pipes) (*StdConsole, error) { + return &StdConsole{p}, nil +} + +func (s *StdConsole) AttachTo(command *Command) error { + command.Stdout = s.pipes.Stdout + command.Stderr = s.pipes.Stderr + + if s.pipes.Stdin != nil { + stdin, err := command.StdinPipe() + if err != nil { + return err + } + + go func() { + defer stdin.Close() + io.Copy(stdin, s.pipes.Stdin) + }() + } + return nil +} + +func (s *StdConsole) Resize(h, w int) error { + // we do not need to reside a non tty + return nil +} + +func (s *StdConsole) Close() error { + // nothing to close here + return nil +} diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 1aba80ff41..3a87f76f5f 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -388,7 +388,7 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { return len(p), nil } -func (w *WriteBroadcaster) CloseWriters() error { +func (w *WriteBroadcaster) Close() error { w.Lock() defer w.Unlock() for sw := range w.writers { diff --git a/components/engine/utils/utils_test.go b/components/engine/utils/utils_test.go index 7e63a45cf7..f8b3920548 100644 --- a/components/engine/utils/utils_test.go +++ b/components/engine/utils/utils_test.go @@ -110,7 +110,7 @@ func TestWriteBroadcaster(t *testing.T) { t.Errorf("Buffer contains %v", bufferC.String()) } - writer.CloseWriters() + writer.Close() } type devNullCloser int From a808fdbf65a64c8aba7f9e56b4df6c9818247e0b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 12:32:14 -0800 Subject: [PATCH 2/5] Change Console to Terminal Move creation and attach to driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 1e742876988546efb876f67f83de6737ee5e9cf6 Component: engine --- components/engine/container.go | 27 ++-- components/engine/execdriver/chroot/driver.go | 2 +- components/engine/execdriver/console.go | 137 ------------------ components/engine/execdriver/driver.go | 6 +- components/engine/execdriver/lxc/driver.go | 7 +- components/engine/execdriver/term.go | 137 ++++++++++++++++++ components/engine/runtime.go | 4 +- 7 files changed, 159 insertions(+), 161 deletions(-) delete mode 100644 components/engine/execdriver/console.go create mode 100644 components/engine/execdriver/term.go diff --git a/components/engine/container.go b/components/engine/container.go index f38876ed24..025479f240 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -55,7 +55,6 @@ type Container struct { Driver string command *execdriver.Command - console execdriver.Console stdout *utils.WriteBroadcaster stderr *utils.WriteBroadcaster stdin io.ReadCloser @@ -531,6 +530,9 @@ func (container *Container) Start() (err error) { } populateCommand(container) + if err := execdriver.NewTerminal(container.command); err != nil { + return err + } // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { @@ -541,16 +543,6 @@ func (container *Container) Start() (err error) { } container.waitLock = make(chan struct{}) - container.console, err = execdriver.NewConsole( - container.stdin, container.stdout, container.stderr, - container.Config.OpenStdin, container.Config.Tty) - if err != nil { - return err - } - if err := container.console.AttachTo(container.command); err != nil { - return err - } - callbackLock := make(chan struct{}) callback := func(command *execdriver.Command) { container.State.SetRunning(command.Pid()) @@ -790,7 +782,8 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { populateCommand(container) err = container.runtime.RestoreCommand(container) } else { - exitCode, err = container.runtime.Run(container, callback) + pipes := execdriver.NewPipes(container.stdin, container.stdout, container.stderr, container.Config.OpenStdin) + exitCode, err = container.runtime.Run(container, pipes, callback) } if err != nil { @@ -845,9 +838,9 @@ func (container *Container) cleanup() { if err := container.stderr.Close(); err != nil { utils.Errorf("%s: Error close stderr: %s", container.ID, err) } - if container.console != nil { - if err := container.console.Close(); err != nil { - utils.Errorf("%s: Error closing console: %s", container.ID, err) + if container.command.Terminal != nil { + if err := container.command.Terminal.Close(); err != nil { + utils.Errorf("%s: Error closing terminal: %s", container.ID, err) } } @@ -939,7 +932,7 @@ func (container *Container) Wait() int { } func (container *Container) Resize(h, w int) error { - return container.console.Resize(h, w) + return container.command.Terminal.Resize(h, w) } func (container *Container) ExportRw() (archive.Archive, error) { @@ -1143,7 +1136,7 @@ func (container *Container) Exposes(p nat.Port) bool { } func (container *Container) GetPtyMaster() (*os.File, error) { - ttyConsole, ok := container.console.(*execdriver.TtyConsole) + ttyConsole, ok := container.command.Terminal.(*execdriver.TtyConsole) if !ok { return nil, ErrNoTTY } diff --git a/components/engine/execdriver/chroot/driver.go b/components/engine/execdriver/chroot/driver.go index 396df87bad..dfec680d84 100644 --- a/components/engine/execdriver/chroot/driver.go +++ b/components/engine/execdriver/chroot/driver.go @@ -37,7 +37,7 @@ func NewDriver() (*driver, error) { return &driver{}, nil } -func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallback) (int, error) { +func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { params := []string{ "chroot", c.Rootfs, diff --git a/components/engine/execdriver/console.go b/components/engine/execdriver/console.go deleted file mode 100644 index 01b45afb60..0000000000 --- a/components/engine/execdriver/console.go +++ /dev/null @@ -1,137 +0,0 @@ -package execdriver - -import ( - "github.com/dotcloud/docker/pkg/term" - "github.com/kr/pty" - "io" - "os" -) - -type Console interface { - io.Closer - Resize(height, width int) error - AttachTo(command *Command) error -} - -type pipes struct { - Stdin io.ReadCloser - Stdout, Stderr io.WriteCloser -} - -func (p *pipes) Close() error { - if p.Stderr != nil { - p.Stdin.Close() - } - if p.Stdout != nil { - p.Stdout.Close() - } - if p.Stderr != nil { - p.Stderr.Close() - } - return nil -} - -func NewConsole(stdin io.ReadCloser, stdout, stderr io.WriteCloser, useStdin, tty bool) (Console, error) { - p := &pipes{ - Stdout: stdout, - Stderr: stderr, - } - if useStdin { - p.Stdin = stdin - } - if tty { - return NewTtyConsole(p) - } - return NewStdConsole(p) -} - -type TtyConsole struct { - Master *os.File - Slave *os.File - pipes *pipes -} - -func NewTtyConsole(p *pipes) (*TtyConsole, error) { - ptyMaster, ptySlave, err := pty.Open() - if err != nil { - return nil, err - } - tty := &TtyConsole{ - Master: ptyMaster, - Slave: ptySlave, - pipes: p, - } - return tty, nil -} - -func (t *TtyConsole) Resize(h, w int) error { - return term.SetWinsize(t.Master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) - -} - -func (t *TtyConsole) AttachTo(command *Command) error { - command.Stdout = t.Slave - command.Stderr = t.Slave - - command.Console = t.Slave.Name() - - go func() { - defer t.pipes.Stdout.Close() - io.Copy(t.pipes.Stdout, t.Master) - }() - - if t.pipes.Stdin != nil { - command.Stdin = t.Slave - command.SysProcAttr.Setctty = true - - go func() { - defer t.pipes.Stdin.Close() - io.Copy(t.Master, t.pipes.Stdin) - }() - } - return nil -} - -func (t *TtyConsole) Close() error { - err := t.Slave.Close() - if merr := t.Master.Close(); err == nil { - err = merr - } - return err -} - -type StdConsole struct { - pipes *pipes -} - -func NewStdConsole(p *pipes) (*StdConsole, error) { - return &StdConsole{p}, nil -} - -func (s *StdConsole) AttachTo(command *Command) error { - command.Stdout = s.pipes.Stdout - command.Stderr = s.pipes.Stderr - - if s.pipes.Stdin != nil { - stdin, err := command.StdinPipe() - if err != nil { - return err - } - - go func() { - defer stdin.Close() - io.Copy(stdin, s.pipes.Stdin) - }() - } - return nil -} - -func (s *StdConsole) Resize(h, w int) error { - // we do not need to reside a non tty - return nil -} - -func (s *StdConsole) Close() error { - // nothing to close here - return nil -} diff --git a/components/engine/execdriver/driver.go b/components/engine/execdriver/driver.go index 32b39771b6..c9f62f500e 100644 --- a/components/engine/execdriver/driver.go +++ b/components/engine/execdriver/driver.go @@ -58,7 +58,7 @@ type Info interface { } type Driver interface { - Run(c *Command, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code + Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Command, sig int) error Restore(c *Command) error // Wait and try to re-attach on an out of process command Name() string // Driver name @@ -82,7 +82,6 @@ type Resources struct { } // Process wrapps an os/exec.Cmd to add more metadata -// TODO: Rename to Command type Command struct { exec.Cmd `json:"-"` @@ -100,7 +99,8 @@ type Command struct { Config []string `json:"config"` // generic values that specific drivers can consume Resources *Resources `json:"resources"` - Console string `json:"-"` + Terminal Term `json:"-"` + Console string `json:"-"` } // Return the pid of the process diff --git a/components/engine/execdriver/lxc/driver.go b/components/engine/execdriver/lxc/driver.go index ee4d02a6b6..da3bc1ec7c 100644 --- a/components/engine/execdriver/lxc/driver.go +++ b/components/engine/execdriver/lxc/driver.go @@ -76,7 +76,12 @@ func (d *driver) Name() string { return fmt.Sprintf("%s-%s", DriverName, version) } -func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallback) (int, error) { +func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { + if c.Terminal != nil { + if err := c.Terminal.Attach(pipes); err != nil { + return -1, err + } + } configPath, err := d.generateLXCConfig(c) if err != nil { return -1, err diff --git a/components/engine/execdriver/term.go b/components/engine/execdriver/term.go new file mode 100644 index 0000000000..e7ec46653c --- /dev/null +++ b/components/engine/execdriver/term.go @@ -0,0 +1,137 @@ +package execdriver + +import ( + "github.com/dotcloud/docker/pkg/term" + "github.com/kr/pty" + "io" + "os" +) + +type Term interface { + io.Closer + Resize(height, width int) error + Attach(pipes *Pipes) error +} + +type Pipes struct { + Stdin io.ReadCloser + Stdout, Stderr io.WriteCloser +} + +func NewPipes(stdin io.ReadCloser, stdout, stderr io.WriteCloser, useStdin bool) *Pipes { + p := &Pipes{ + Stdout: stdout, + Stderr: stderr, + } + if useStdin { + p.Stdin = stdin + } + return p +} + +func NewTerminal(command *Command) error { + var ( + term Term + err error + ) + if command.Tty { + term, err = NewTtyConsole(command) + } else { + term, err = NewStdConsole(command) + } + if err != nil { + return err + } + command.Terminal = term + return nil +} + +type TtyConsole struct { + command *Command + Master *os.File + Slave *os.File +} + +func NewTtyConsole(command *Command) (*TtyConsole, error) { + ptyMaster, ptySlave, err := pty.Open() + if err != nil { + return nil, err + } + tty := &TtyConsole{ + Master: ptyMaster, + Slave: ptySlave, + command: command, + } + return tty, nil +} + +func (t *TtyConsole) Resize(h, w int) error { + return term.SetWinsize(t.Master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) +} + +func (t *TtyConsole) Attach(pipes *Pipes) error { + t.command.Stdout = t.Slave + t.command.Stderr = t.Slave + + t.command.Console = t.Slave.Name() + + go func() { + defer pipes.Stdout.Close() + io.Copy(pipes.Stdout, t.Master) + }() + + if pipes.Stdin != nil { + t.command.Stdin = t.Slave + t.command.SysProcAttr.Setctty = true + + go func() { + defer pipes.Stdin.Close() + io.Copy(t.Master, pipes.Stdin) + }() + } + return nil +} + +func (t *TtyConsole) Close() error { + err := t.Slave.Close() + if merr := t.Master.Close(); err == nil { + err = merr + } + return err +} + +type StdConsole struct { + command *Command +} + +func NewStdConsole(command *Command) (*StdConsole, error) { + return &StdConsole{command}, nil +} + +func (s *StdConsole) Attach(pipes *Pipes) error { + s.command.Stdout = pipes.Stdout + s.command.Stderr = pipes.Stderr + + if pipes.Stdin != nil { + stdin, err := s.command.StdinPipe() + if err != nil { + return err + } + + go func() { + defer stdin.Close() + io.Copy(stdin, pipes.Stdin) + }() + } + return nil +} + +func (s *StdConsole) Resize(h, w int) error { + // we do not need to reside a non tty + return nil +} + +func (s *StdConsole) Close() error { + // nothing to close here + return nil +} diff --git a/components/engine/runtime.go b/components/engine/runtime.go index eed28f92ab..a38109cca0 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -812,8 +812,8 @@ func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { }), nil } -func (runtime *Runtime) Run(c *Container, startCallback execdriver.StartCallback) (int, error) { - return runtime.execDriver.Run(c.command, startCallback) +func (runtime *Runtime) Run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { + return runtime.execDriver.Run(c.command, pipes, startCallback) } func (runtime *Runtime) Kill(c *Container, sig int) error { From b0992a0e9909521c8229a94c4d78c8b414a326cc Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 12:42:37 -0800 Subject: [PATCH 3/5] Move term creation into driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 592c2f6f9a472bda227a03c819f73b8edc7c3320 Component: engine --- components/engine/container.go | 3 -- components/engine/execdriver/lxc/driver.go | 6 +-- components/engine/execdriver/term.go | 61 +++++++++++----------- 3 files changed, 33 insertions(+), 37 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 025479f240..733a31d5a2 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -530,9 +530,6 @@ func (container *Container) Start() (err error) { } populateCommand(container) - if err := execdriver.NewTerminal(container.command); err != nil { - return err - } // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { diff --git a/components/engine/execdriver/lxc/driver.go b/components/engine/execdriver/lxc/driver.go index da3bc1ec7c..c18b2e6ab4 100644 --- a/components/engine/execdriver/lxc/driver.go +++ b/components/engine/execdriver/lxc/driver.go @@ -77,10 +77,8 @@ func (d *driver) Name() string { } func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { - if c.Terminal != nil { - if err := c.Terminal.Attach(pipes); err != nil { - return -1, err - } + if err := execdriver.SetTerminal(c, pipes); err != nil { + return -1, err } configPath, err := d.generateLXCConfig(c) if err != nil { diff --git a/components/engine/execdriver/term.go b/components/engine/execdriver/term.go index e7ec46653c..ab399e8337 100644 --- a/components/engine/execdriver/term.go +++ b/components/engine/execdriver/term.go @@ -10,7 +10,6 @@ import ( type Term interface { io.Closer Resize(height, width int) error - Attach(pipes *Pipes) error } type Pipes struct { @@ -29,15 +28,15 @@ func NewPipes(stdin io.ReadCloser, stdout, stderr io.WriteCloser, useStdin bool) return p } -func NewTerminal(command *Command) error { +func SetTerminal(command *Command, pipes *Pipes) error { var ( term Term err error ) if command.Tty { - term, err = NewTtyConsole(command) + term, err = NewTtyConsole(command, pipes) } else { - term, err = NewStdConsole(command) + term, err = NewStdConsole(command, pipes) } if err != nil { return err @@ -47,20 +46,22 @@ func NewTerminal(command *Command) error { } type TtyConsole struct { - command *Command - Master *os.File - Slave *os.File + Master *os.File + Slave *os.File } -func NewTtyConsole(command *Command) (*TtyConsole, error) { +func NewTtyConsole(command *Command, pipes *Pipes) (*TtyConsole, error) { ptyMaster, ptySlave, err := pty.Open() if err != nil { return nil, err } tty := &TtyConsole{ - Master: ptyMaster, - Slave: ptySlave, - command: command, + Master: ptyMaster, + Slave: ptySlave, + } + if err := tty.attach(command, pipes); err != nil { + tty.Close() + return nil, err } return tty, nil } @@ -69,11 +70,10 @@ func (t *TtyConsole) Resize(h, w int) error { return term.SetWinsize(t.Master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) } -func (t *TtyConsole) Attach(pipes *Pipes) error { - t.command.Stdout = t.Slave - t.command.Stderr = t.Slave - - t.command.Console = t.Slave.Name() +func (t *TtyConsole) attach(command *Command, pipes *Pipes) error { + command.Stdout = t.Slave + command.Stderr = t.Slave + command.Console = t.Slave.Name() go func() { defer pipes.Stdout.Close() @@ -81,8 +81,8 @@ func (t *TtyConsole) Attach(pipes *Pipes) error { }() if pipes.Stdin != nil { - t.command.Stdin = t.Slave - t.command.SysProcAttr.Setctty = true + command.Stdin = t.Slave + command.SysProcAttr.Setctty = true go func() { defer pipes.Stdin.Close() @@ -93,27 +93,28 @@ func (t *TtyConsole) Attach(pipes *Pipes) error { } func (t *TtyConsole) Close() error { - err := t.Slave.Close() - if merr := t.Master.Close(); err == nil { - err = merr - } - return err + t.Slave.Close() + return t.Master.Close() } type StdConsole struct { - command *Command } -func NewStdConsole(command *Command) (*StdConsole, error) { - return &StdConsole{command}, nil +func NewStdConsole(command *Command, pipes *Pipes) (*StdConsole, error) { + std := &StdConsole{} + + if err := std.attach(command, pipes); err != nil { + return nil, err + } + return std, nil } -func (s *StdConsole) Attach(pipes *Pipes) error { - s.command.Stdout = pipes.Stdout - s.command.Stderr = pipes.Stderr +func (s *StdConsole) attach(command *Command, pipes *Pipes) error { + command.Stdout = pipes.Stdout + command.Stderr = pipes.Stderr if pipes.Stdin != nil { - stdin, err := s.command.StdinPipe() + stdin, err := command.StdinPipe() if err != nil { return err } From a3562ba47c04923c3e1a77c1d5bf0e64c08de52f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 12:52:18 -0800 Subject: [PATCH 4/5] Add CloseWriters back and do an interface cast Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: 8e2284aaa2364a8e47a8058e65d60813f9cd5089 Component: engine --- components/engine/container.go | 4 ++-- components/engine/execdriver/term.go | 10 +++++++--- components/engine/utils/utils.go | 2 +- components/engine/utils/utils_test.go | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 733a31d5a2..218ae1e4a8 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -829,10 +829,10 @@ func (container *Container) cleanup() { utils.Errorf("%s: Error close stdin: %s", container.ID, err) } } - if err := container.stdout.Close(); err != nil { + if err := container.stdout.CloseWriters(); err != nil { utils.Errorf("%s: Error close stdout: %s", container.ID, err) } - if err := container.stderr.Close(); err != nil { + if err := container.stderr.CloseWriters(); err != nil { utils.Errorf("%s: Error close stderr: %s", container.ID, err) } if container.command.Terminal != nil { diff --git a/components/engine/execdriver/term.go b/components/engine/execdriver/term.go index ab399e8337..ec3c368012 100644 --- a/components/engine/execdriver/term.go +++ b/components/engine/execdriver/term.go @@ -14,10 +14,10 @@ type Term interface { type Pipes struct { Stdin io.ReadCloser - Stdout, Stderr io.WriteCloser + Stdout, Stderr io.Writer } -func NewPipes(stdin io.ReadCloser, stdout, stderr io.WriteCloser, useStdin bool) *Pipes { +func NewPipes(stdin io.ReadCloser, stdout, stderr io.Writer, useStdin bool) *Pipes { p := &Pipes{ Stdout: stdout, Stderr: stderr, @@ -76,7 +76,11 @@ func (t *TtyConsole) attach(command *Command, pipes *Pipes) error { command.Console = t.Slave.Name() go func() { - defer pipes.Stdout.Close() + if wb, ok := pipes.Stdout.(interface { + CloseWriters() error + }); ok { + defer wb.CloseWriters() + } io.Copy(pipes.Stdout, t.Master) }() diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 3a87f76f5f..1aba80ff41 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -388,7 +388,7 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { return len(p), nil } -func (w *WriteBroadcaster) Close() error { +func (w *WriteBroadcaster) CloseWriters() error { w.Lock() defer w.Unlock() for sw := range w.writers { diff --git a/components/engine/utils/utils_test.go b/components/engine/utils/utils_test.go index f8b3920548..7e63a45cf7 100644 --- a/components/engine/utils/utils_test.go +++ b/components/engine/utils/utils_test.go @@ -110,7 +110,7 @@ func TestWriteBroadcaster(t *testing.T) { t.Errorf("Buffer contains %v", bufferC.String()) } - writer.Close() + writer.CloseWriters() } type devNullCloser int From 28cc135001feb3e5bfb593e39308fdd224622de9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 21 Feb 2014 13:27:15 -0800 Subject: [PATCH 5/5] Move current tty and pipe impl to lxc driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) Upstream-commit: aac702727ea02b3974a2848748b0713ac583fc84 Component: engine --- components/engine/container.go | 4 +- components/engine/execdriver/driver.go | 18 ++++- components/engine/execdriver/lxc/driver.go | 2 +- .../engine/execdriver/{ => lxc}/term.go | 68 +++++++------------ components/engine/execdriver/pipes.go | 23 +++++++ 5 files changed, 68 insertions(+), 47 deletions(-) rename components/engine/execdriver/{ => lxc}/term.go (58%) create mode 100644 components/engine/execdriver/pipes.go diff --git a/components/engine/container.go b/components/engine/container.go index 218ae1e4a8..ca53bb57c7 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1133,9 +1133,9 @@ func (container *Container) Exposes(p nat.Port) bool { } func (container *Container) GetPtyMaster() (*os.File, error) { - ttyConsole, ok := container.command.Terminal.(*execdriver.TtyConsole) + ttyConsole, ok := container.command.Terminal.(execdriver.TtyTerminal) if !ok { return nil, ErrNoTTY } - return ttyConsole.Master, nil + return ttyConsole.Master(), nil } diff --git a/components/engine/execdriver/driver.go b/components/engine/execdriver/driver.go index c9f62f500e..a6d865caf3 100644 --- a/components/engine/execdriver/driver.go +++ b/components/engine/execdriver/driver.go @@ -2,6 +2,8 @@ package execdriver import ( "errors" + "io" + "os" "os/exec" ) @@ -57,6 +59,18 @@ type Info interface { IsRunning() bool } +// Terminal in an interface for drivers to implement +// if they want to support Close and Resize calls from +// the core +type Terminal interface { + io.Closer + Resize(height, width int) error +} + +type TtyTerminal interface { + Master() *os.File +} + type Driver interface { Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Command, sig int) error @@ -99,8 +113,8 @@ type Command struct { Config []string `json:"config"` // generic values that specific drivers can consume Resources *Resources `json:"resources"` - Terminal Term `json:"-"` - Console string `json:"-"` + Terminal Terminal `json:"-"` // standard or tty terminal + Console string `json:"-"` // dev/console path } // Return the pid of the process diff --git a/components/engine/execdriver/lxc/driver.go b/components/engine/execdriver/lxc/driver.go index c18b2e6ab4..5be7ad2219 100644 --- a/components/engine/execdriver/lxc/driver.go +++ b/components/engine/execdriver/lxc/driver.go @@ -77,7 +77,7 @@ func (d *driver) Name() string { } func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { - if err := execdriver.SetTerminal(c, pipes); err != nil { + if err := SetTerminal(c, pipes); err != nil { return -1, err } configPath, err := d.generateLXCConfig(c) diff --git a/components/engine/execdriver/term.go b/components/engine/execdriver/lxc/term.go similarity index 58% rename from components/engine/execdriver/term.go rename to components/engine/execdriver/lxc/term.go index ec3c368012..d772f60972 100644 --- a/components/engine/execdriver/term.go +++ b/components/engine/execdriver/lxc/term.go @@ -1,36 +1,16 @@ -package execdriver +package lxc import ( + "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/pkg/term" "github.com/kr/pty" "io" "os" ) -type Term interface { - io.Closer - Resize(height, width int) error -} - -type Pipes struct { - Stdin io.ReadCloser - Stdout, Stderr io.Writer -} - -func NewPipes(stdin io.ReadCloser, stdout, stderr io.Writer, useStdin bool) *Pipes { - p := &Pipes{ - Stdout: stdout, - Stderr: stderr, - } - if useStdin { - p.Stdin = stdin - } - return p -} - -func SetTerminal(command *Command, pipes *Pipes) error { +func SetTerminal(command *execdriver.Command, pipes *execdriver.Pipes) error { var ( - term Term + term execdriver.Terminal err error ) if command.Tty { @@ -46,18 +26,18 @@ func SetTerminal(command *Command, pipes *Pipes) error { } type TtyConsole struct { - Master *os.File - Slave *os.File + master *os.File + slave *os.File } -func NewTtyConsole(command *Command, pipes *Pipes) (*TtyConsole, error) { +func NewTtyConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*TtyConsole, error) { ptyMaster, ptySlave, err := pty.Open() if err != nil { return nil, err } tty := &TtyConsole{ - Master: ptyMaster, - Slave: ptySlave, + master: ptyMaster, + slave: ptySlave, } if err := tty.attach(command, pipes); err != nil { tty.Close() @@ -66,14 +46,18 @@ func NewTtyConsole(command *Command, pipes *Pipes) (*TtyConsole, error) { return tty, nil } -func (t *TtyConsole) Resize(h, w int) error { - return term.SetWinsize(t.Master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) +func (t *TtyConsole) Master() *os.File { + return t.master } -func (t *TtyConsole) attach(command *Command, pipes *Pipes) error { - command.Stdout = t.Slave - command.Stderr = t.Slave - command.Console = t.Slave.Name() +func (t *TtyConsole) Resize(h, w int) error { + return term.SetWinsize(t.master.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)}) +} + +func (t *TtyConsole) attach(command *execdriver.Command, pipes *execdriver.Pipes) error { + command.Stdout = t.slave + command.Stderr = t.slave + command.Console = t.slave.Name() go func() { if wb, ok := pipes.Stdout.(interface { @@ -81,30 +65,30 @@ func (t *TtyConsole) attach(command *Command, pipes *Pipes) error { }); ok { defer wb.CloseWriters() } - io.Copy(pipes.Stdout, t.Master) + io.Copy(pipes.Stdout, t.master) }() if pipes.Stdin != nil { - command.Stdin = t.Slave + command.Stdin = t.slave command.SysProcAttr.Setctty = true go func() { defer pipes.Stdin.Close() - io.Copy(t.Master, pipes.Stdin) + io.Copy(t.master, pipes.Stdin) }() } return nil } func (t *TtyConsole) Close() error { - t.Slave.Close() - return t.Master.Close() + t.slave.Close() + return t.master.Close() } type StdConsole struct { } -func NewStdConsole(command *Command, pipes *Pipes) (*StdConsole, error) { +func NewStdConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*StdConsole, error) { std := &StdConsole{} if err := std.attach(command, pipes); err != nil { @@ -113,7 +97,7 @@ func NewStdConsole(command *Command, pipes *Pipes) (*StdConsole, error) { return std, nil } -func (s *StdConsole) attach(command *Command, pipes *Pipes) error { +func (s *StdConsole) attach(command *execdriver.Command, pipes *execdriver.Pipes) error { command.Stdout = pipes.Stdout command.Stderr = pipes.Stderr diff --git a/components/engine/execdriver/pipes.go b/components/engine/execdriver/pipes.go new file mode 100644 index 0000000000..158219f0c5 --- /dev/null +++ b/components/engine/execdriver/pipes.go @@ -0,0 +1,23 @@ +package execdriver + +import ( + "io" +) + +// Pipes is a wrapper around a containers output for +// stdin, stdout, stderr +type Pipes struct { + Stdin io.ReadCloser + Stdout, Stderr io.Writer +} + +func NewPipes(stdin io.ReadCloser, stdout, stderr io.Writer, useStdin bool) *Pipes { + p := &Pipes{ + Stdout: stdout, + Stderr: stderr, + } + if useStdin { + p.Stdin = stdin + } + return p +}