[lxc-devel] [lxd/master] Extract common logic from the ContainerExec API backend.

albertodonato on Github lxc-bot at linuxcontainers.org
Wed Oct 18 16:23:28 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 454 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20171018/27743831/attachment.bin>
-------------- next part --------------
From 9167eceb4b3316dc63cf08de4114afa5493a0220 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 17 Oct 2017 10:13:28 +0200
Subject: [PATCH 01/12] shared/api: rename/move ContainerExecControl to
 ContainerTTYControl

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxc/exec.go                  | 4 ++--
 lxd/container_exec.go        | 2 +-
 shared/api/container_exec.go | 7 -------
 shared/api/container_tty.go  | 8 ++++++++
 4 files changed, 11 insertions(+), 10 deletions(-)
 create mode 100644 shared/api/container_tty.go

diff --git a/lxc/exec.go b/lxc/exec.go
index 5422b9056..4a34d5030 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -79,7 +79,7 @@ func (c *execCmd) sendTermSize(control *websocket.Conn) error {
 		return err
 	}
 
-	msg := api.ContainerExecControl{}
+	msg := api.ContainerTTYControl{}
 	msg.Command = "window-resize"
 	msg.Args = make(map[string]string)
 	msg.Args["width"] = strconv.Itoa(width)
@@ -103,7 +103,7 @@ func (c *execCmd) forwardSignal(control *websocket.Conn, sig syscall.Signal) err
 		return err
 	}
 
-	msg := api.ContainerExecControl{}
+	msg := api.ContainerTTYControl{}
 	msg.Command = "signal"
 	msg.Signal = int(sig)
 
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index e09682ab4..2a0560530 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -187,7 +187,7 @@ func (s *execWs) Do(op *operation) error {
 					break
 				}
 
-				command := api.ContainerExecControl{}
+				command := api.ContainerTTYControl{}
 
 				if err := json.Unmarshal(buf, &command); err != nil {
 					logger.Debugf("Failed to unmarshal control socket command: %s", err)
diff --git a/shared/api/container_exec.go b/shared/api/container_exec.go
index e243749b4..fc97c4324 100644
--- a/shared/api/container_exec.go
+++ b/shared/api/container_exec.go
@@ -1,12 +1,5 @@
 package api
 
-// ContainerExecControl represents a message on the container exec "control" socket
-type ContainerExecControl struct {
-	Command string            `json:"command" yaml:"command"`
-	Args    map[string]string `json:"args" yaml:"args"`
-	Signal  int               `json:"signal" yaml:"signal"`
-}
-
 // ContainerExecPost represents a LXD container exec request
 type ContainerExecPost struct {
 	Command     []string          `json:"command" yaml:"command"`
diff --git a/shared/api/container_tty.go b/shared/api/container_tty.go
new file mode 100644
index 000000000..c10c119bc
--- /dev/null
+++ b/shared/api/container_tty.go
@@ -0,0 +1,8 @@
+package api
+
+// ContainerTTYControl represents a message on the container tty "control" socket
+type ContainerTTYControl struct {
+	Command string            `json:"command" yaml:"command"`
+	Args    map[string]string `json:"args" yaml:"args"`
+	Signal  int               `json:"signal" yaml:"signal"`
+}

From 670d3455edd0b77dece1c334010a232281f5bae6 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 17 Oct 2017 10:37:14 +0200
Subject: [PATCH 02/12] lxd: extract common websocket TTY logic

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 73 ++++-------------------------------------------
 lxd/container_tty.go  | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+), 67 deletions(-)
 create mode 100644 lxd/container_tty.go

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 2a0560530..d06a46656 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -25,74 +25,13 @@ import (
 )
 
 type execWs struct {
-	command   []string
-	container container
-	env       map[string]string
-
-	rootUid          int64
-	rootGid          int64
-	conns            map[int]*websocket.Conn
-	connsLock        sync.Mutex
-	allConnected     chan bool
-	controlConnected chan bool
-	interactive      bool
-	fds              map[int]string
-	width            int
-	height           int
-}
-
-func (s *execWs) Metadata() interface{} {
-	fds := shared.Jmap{}
-	for fd, secret := range s.fds {
-		if fd == -1 {
-			fds["control"] = secret
-		} else {
-			fds[strconv.Itoa(fd)] = secret
-		}
-	}
-
-	return shared.Jmap{"fds": fds}
-}
-
-func (s *execWs) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
-	secret := r.FormValue("secret")
-	if secret == "" {
-		return fmt.Errorf("missing secret")
-	}
-
-	for fd, fdSecret := range s.fds {
-		if secret == fdSecret {
-			conn, err := shared.WebsocketUpgrader.Upgrade(w, r, nil)
-			if err != nil {
-				return err
-			}
-
-			s.connsLock.Lock()
-			s.conns[fd] = conn
-			s.connsLock.Unlock()
-
-			if fd == -1 {
-				s.controlConnected <- true
-				return nil
-			}
-
-			s.connsLock.Lock()
-			for i, c := range s.conns {
-				if i != -1 && c == nil {
-					s.connsLock.Unlock()
-					return nil
-				}
-			}
-			s.connsLock.Unlock()
-
-			s.allConnected <- true
-			return nil
-		}
-	}
+	ttyWs
 
-	/* If we didn't find the right secret, the user provided a bad one,
-	 * which 403, not 404, since this operation actually exists */
-	return os.ErrPermission
+	command     []string
+	env         map[string]string
+	rootUid     int64
+	rootGid     int64
+	interactive bool
 }
 
 func (s *execWs) Do(op *operation) error {
diff --git a/lxd/container_tty.go b/lxd/container_tty.go
new file mode 100644
index 000000000..6ba02bf8a
--- /dev/null
+++ b/lxd/container_tty.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"os"
+	"strconv"
+	"sync"
+
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/shared"
+)
+
+type ttyWs struct {
+	container        container
+	conns            map[int]*websocket.Conn
+	connsLock        sync.Mutex
+	allConnected     chan bool
+	controlConnected chan bool
+	fds              map[int]string
+	width            int
+	height           int
+}
+
+func (s *ttyWs) Metadata() interface{} {
+	fds := shared.Jmap{}
+	for fd, secret := range s.fds {
+		if fd == -1 {
+			fds["control"] = secret
+		} else {
+			fds[strconv.Itoa(fd)] = secret
+		}
+	}
+
+	return shared.Jmap{"fds": fds}
+}
+
+func (s *ttyWs) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
+	secret := r.FormValue("secret")
+	if secret == "" {
+		return fmt.Errorf("missing secret")
+	}
+
+	for fd, fdSecret := range s.fds {
+		if secret == fdSecret {
+			conn, err := shared.WebsocketUpgrader.Upgrade(w, r, nil)
+			if err != nil {
+				return err
+			}
+
+			s.connsLock.Lock()
+			s.conns[fd] = conn
+			s.connsLock.Unlock()
+
+			if fd == -1 {
+				s.controlConnected <- true
+				return nil
+			}
+
+			s.connsLock.Lock()
+			for i, c := range s.conns {
+				if i != -1 && c == nil {
+					s.connsLock.Unlock()
+					return nil
+				}
+			}
+			s.connsLock.Unlock()
+
+			s.allConnected <- true
+			return nil
+		}
+	}
+
+	/* If we didn't find the right secret, the user provided a bad one,
+	 * which 403, not 404, since this operation actually exists */
+	return os.ErrPermission
+}

From e7205d66c8c8cea69d604babb246db2fff9bac8f Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 17 Oct 2017 12:37:31 +0200
Subject: [PATCH 03/12] lxd/container_exec: add newttyWs, extract common
 initialization code

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 49 ++++++++++++++++---------------------------------
 lxd/container_tty.go  | 31 +++++++++++++++++++++++++++++++
 2 files changed, 47 insertions(+), 33 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index d06a46656..a40d7f523 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -27,11 +27,13 @@ import (
 type execWs struct {
 	ttyWs
 
-	command     []string
-	env         map[string]string
-	rootUid     int64
-	rootGid     int64
-	interactive bool
+	command []string
+	env     map[string]string
+	rootUid int64
+	rootGid int64
+
+	ttys []*os.File
+	ptys []*os.File
 }
 
 func (s *execWs) Do(op *operation) error {
@@ -332,42 +334,23 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	}
 
 	if post.WaitForWS {
-		ws := &execWs{}
-		ws.fds = map[int]string{}
-
+		ttyWs, err := newttyWs(c, post.Interactive, post.Width, post.Height)
+		if err != nil {
+			return InternalError(err)
+		}
+		ws := &execWs{
+			ttyWs:   *ttyWs,
+			command: post.Command,
+			env:     env,
+		}
 		idmapset, err := c.IdmapSet()
 		if err != nil {
 			return InternalError(err)
 		}
-
 		if idmapset != nil {
 			ws.rootUid, ws.rootGid = idmapset.ShiftIntoNs(0, 0)
 		}
 
-		ws.conns = map[int]*websocket.Conn{}
-		ws.conns[-1] = nil
-		ws.conns[0] = nil
-		if !post.Interactive {
-			ws.conns[1] = nil
-			ws.conns[2] = nil
-		}
-		ws.allConnected = make(chan bool, 1)
-		ws.controlConnected = make(chan bool, 1)
-		ws.interactive = post.Interactive
-		for i := -1; i < len(ws.conns)-1; i++ {
-			ws.fds[i], err = shared.RandomCryptoString()
-			if err != nil {
-				return InternalError(err)
-			}
-		}
-
-		ws.command = post.Command
-		ws.container = c
-		ws.env = env
-
-		ws.width = post.Width
-		ws.height = post.Height
-
 		resources := map[string][]string{}
 		resources["containers"] = []string{ws.container.Name()}
 
diff --git a/lxd/container_tty.go b/lxd/container_tty.go
index 6ba02bf8a..4726c3bdf 100644
--- a/lxd/container_tty.go
+++ b/lxd/container_tty.go
@@ -19,10 +19,41 @@ type ttyWs struct {
 	allConnected     chan bool
 	controlConnected chan bool
 	fds              map[int]string
+	interactive      bool
 	width            int
 	height           int
 }
 
+func newttyWs(c container, interactive bool, width int, height int) (*ttyWs, error) {
+	fds := map[int]string{}
+	conns := map[int]*websocket.Conn{
+		-1: nil,
+		0:  nil,
+	}
+	if !interactive {
+		conns[1] = nil
+		conns[2] = nil
+	}
+	for i := -1; i < len(conns)-1; i++ {
+		var err error
+		fds[i], err = shared.RandomCryptoString()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return &ttyWs{
+		container:        c,
+		fds:              fds,
+		conns:            conns,
+		allConnected:     make(chan bool, 1),
+		controlConnected: make(chan bool, 1),
+		interactive:      interactive,
+		width:            width,
+		height:           height,
+	}, nil
+}
+
 func (s *ttyWs) Metadata() interface{} {
 	fds := shared.Jmap{}
 	for fd, secret := range s.fds {

From 266cf07f9dd856ed511b28f9825bd21b93fd4297 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 17 Oct 2017 11:16:39 +0200
Subject: [PATCH 04/12] lxd/container_exec: extract openTTYs and finish
 functions

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 187 +++++++++++++++++++++++++-------------------------
 1 file changed, 95 insertions(+), 92 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index a40d7f523..f4b3a85d1 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -32,62 +32,32 @@ type execWs struct {
 	rootUid int64
 	rootGid int64
 
-	ttys []*os.File
-	ptys []*os.File
+	ttys        []*os.File
+	ptys        []*os.File
+	controlExit chan bool
+	doneCh      chan bool
+	wgEOF       sync.WaitGroup
 }
 
 func (s *execWs) Do(op *operation) error {
 	<-s.allConnected
 
-	var err error
-	var ttys []*os.File
-	var ptys []*os.File
-
-	var stdin *os.File
-	var stdout *os.File
-	var stderr *os.File
-
-	if s.interactive {
-		ttys = make([]*os.File, 1)
-		ptys = make([]*os.File, 1)
-		ptys[0], ttys[0], err = shared.OpenPty(s.rootUid, s.rootGid)
-
-		stdin = ttys[0]
-		stdout = ttys[0]
-		stderr = ttys[0]
-
-		if s.width > 0 && s.height > 0 {
-			shared.SetSize(int(ptys[0].Fd()), s.width, s.height)
-		}
-	} else {
-		ttys = make([]*os.File, 3)
-		ptys = make([]*os.File, 3)
-		for i := 0; i < len(ttys); i++ {
-			ptys[i], ttys[i], err = shared.Pipe()
-			if err != nil {
-				return err
-			}
-		}
-
-		stdin = ptys[0]
-		stdout = ttys[1]
-		stderr = ttys[2]
+	stdin, stdout, stderr, err := s.openTTYs()
+	if err != nil {
+		return err
 	}
 
-	controlExit := make(chan bool)
 	attachedChildIsBorn := make(chan int)
-	attachedChildIsDead := make(chan bool, 1)
-	var wgEOF sync.WaitGroup
 
 	if s.interactive {
-		wgEOF.Add(1)
+		s.wgEOF.Add(1)
 		go func() {
 			attachedChildPid := <-attachedChildIsBorn
 			select {
 			case <-s.controlConnected:
 				break
 
-			case <-controlExit:
+			case <-s.controlExit:
 				return
 			}
 
@@ -148,7 +118,7 @@ func (s *execWs) Do(op *operation) error {
 						continue
 					}
 
-					err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight)
+					err = shared.SetSize(int(s.ptys[0].Fd()), winchWidth, winchHeight)
 					if err != nil {
 						logger.Debugf("Failed to set window size to: %dx%d", winchWidth, winchHeight)
 						continue
@@ -169,74 +139,40 @@ func (s *execWs) Do(op *operation) error {
 			s.connsLock.Unlock()
 
 			logger.Debugf("Starting to mirror websocket")
-			readDone, writeDone := shared.WebsocketExecMirror(conn, ptys[0], ptys[0], attachedChildIsDead, int(ptys[0].Fd()))
+			readDone, writeDone := shared.WebsocketExecMirror(conn, s.ptys[0], s.ptys[0], s.doneCh, int(s.ptys[0].Fd()))
 
 			<-readDone
 			<-writeDone
 			logger.Debugf("Finished to mirror websocket")
 
 			conn.Close()
-			wgEOF.Done()
+			s.wgEOF.Done()
 		}()
 
 	} else {
-		wgEOF.Add(len(ttys) - 1)
-		for i := 0; i < len(ttys); i++ {
+		s.wgEOF.Add(len(s.ttys) - 1)
+		for i := 0; i < len(s.ttys); i++ {
 			go func(i int) {
 				if i == 0 {
 					s.connsLock.Lock()
-					conn := s.conns[i]
+					conn := s.conns[0]
 					s.connsLock.Unlock()
 
-					<-shared.WebsocketRecvStream(ttys[i], conn)
-					ttys[i].Close()
+					<-shared.WebsocketRecvStream(s.ttys[0], conn)
+					s.ttys[0].Close()
 				} else {
 					s.connsLock.Lock()
 					conn := s.conns[i]
 					s.connsLock.Unlock()
 
-					<-shared.WebsocketSendStream(conn, ptys[i], -1)
-					ptys[i].Close()
-					wgEOF.Done()
+					<-shared.WebsocketSendStream(conn, s.ptys[i], -1)
+					s.ptys[i].Close()
+					s.wgEOF.Done()
 				}
 			}(i)
 		}
 	}
 
-	finisher := func(cmdResult int, cmdErr error) error {
-		for _, tty := range ttys {
-			tty.Close()
-		}
-
-		s.connsLock.Lock()
-		conn := s.conns[-1]
-		s.connsLock.Unlock()
-
-		if conn == nil {
-			if s.interactive {
-				controlExit <- true
-			}
-		} else {
-			conn.Close()
-		}
-
-		attachedChildIsDead <- true
-
-		wgEOF.Wait()
-
-		for _, pty := range ptys {
-			pty.Close()
-		}
-
-		metadata := shared.Jmap{"return": cmdResult}
-		err = op.UpdateMetadata(metadata)
-		if err != nil {
-			return err
-		}
-
-		return cmdErr
-	}
-
 	cmd, _, attachedPid, err := s.container.Exec(s.command, s.env, stdin, stdout, stderr, false)
 	if err != nil {
 		return err
@@ -248,23 +184,88 @@ func (s *execWs) Do(op *operation) error {
 
 	err = cmd.Wait()
 	if err == nil {
-		return finisher(0, nil)
+		return s.finish(op, 0)
 	}
 
 	exitErr, ok := err.(*exec.ExitError)
 	if ok {
 		status, ok := exitErr.Sys().(syscall.WaitStatus)
 		if ok {
-			return finisher(status.ExitStatus(), nil)
+			return s.finish(op, status.ExitStatus())
 		}
 
 		if status.Signaled() {
 			// 128 + n == Fatal error signal "n"
-			return finisher(128+int(status.Signal()), nil)
+			return s.finish(op, 128+int(status.Signal()))
+		}
+	}
+
+	return s.finish(op, -1)
+}
+
+// Open TTYs. Retruns stdin, stdout, stderr descriptors.
+func (s *execWs) openTTYs() (*os.File, *os.File, *os.File, error) {
+	var stdin *os.File
+	var stdout *os.File
+	var stderr *os.File
+	var err error
+
+	if s.interactive {
+		s.ttys = make([]*os.File, 1)
+		s.ptys = make([]*os.File, 1)
+		s.ptys[0], s.ttys[0], err = shared.OpenPty(s.rootUid, s.rootGid)
+
+		stdin = s.ttys[0]
+		stdout = s.ttys[0]
+		stderr = s.ttys[0]
+
+		if s.width > 0 && s.height > 0 {
+			shared.SetSize(int(s.ptys[0].Fd()), s.width, s.height)
 		}
+	} else {
+		s.ttys = make([]*os.File, 3)
+		s.ptys = make([]*os.File, 3)
+		for i := 0; i < 3; i++ {
+			s.ptys[i], s.ttys[i], err = shared.Pipe()
+			if err != nil {
+				return nil, nil, nil, err
+			}
+		}
+
+		stdin = s.ptys[0]
+		stdout = s.ttys[1]
+		stderr = s.ttys[2]
+	}
+
+	return stdin, stdout, stderr, nil
+}
+
+func (s *execWs) finish(op *operation, cmdResult int) error {
+	for _, tty := range s.ttys {
+		tty.Close()
+	}
+
+	s.connsLock.Lock()
+	conn := s.conns[-1]
+	s.connsLock.Unlock()
+
+	if conn == nil {
+		if s.interactive {
+			s.controlExit <- true
+		}
+	} else {
+		conn.Close()
+	}
+
+	s.doneCh <- true
+	s.wgEOF.Wait()
+
+	for _, pty := range s.ptys {
+		pty.Close()
 	}
 
-	return finisher(-1, nil)
+	metadata := shared.Jmap{"return": cmdResult}
+	return op.UpdateMetadata(metadata)
 }
 
 func containerExecPost(d *Daemon, r *http.Request) Response {
@@ -339,9 +340,11 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 			return InternalError(err)
 		}
 		ws := &execWs{
-			ttyWs:   *ttyWs,
-			command: post.Command,
-			env:     env,
+			ttyWs:       *ttyWs,
+			command:     post.Command,
+			env:         env,
+			controlExit: make(chan bool),
+			doneCh:      make(chan bool, 1),
 		}
 		idmapset, err := c.IdmapSet()
 		if err != nil {

From 340249b63d24c47882cbfd6665338a53e3cc5c3a Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 17 Oct 2017 13:20:08 +0200
Subject: [PATCH 05/12] lxc/container_exec: extract
 connectInteractiveStreams/connectNotInteractiveStreams functions

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 82 +++++++++++++++++++++++++++------------------------
 1 file changed, 44 insertions(+), 38 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index f4b3a85d1..78f49ae73 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -132,45 +132,9 @@ func (s *execWs) Do(op *operation) error {
 				}
 			}
 		}()
-
-		go func() {
-			s.connsLock.Lock()
-			conn := s.conns[0]
-			s.connsLock.Unlock()
-
-			logger.Debugf("Starting to mirror websocket")
-			readDone, writeDone := shared.WebsocketExecMirror(conn, s.ptys[0], s.ptys[0], s.doneCh, int(s.ptys[0].Fd()))
-
-			<-readDone
-			<-writeDone
-			logger.Debugf("Finished to mirror websocket")
-
-			conn.Close()
-			s.wgEOF.Done()
-		}()
-
+		s.connectInteractiveStreams()
 	} else {
-		s.wgEOF.Add(len(s.ttys) - 1)
-		for i := 0; i < len(s.ttys); i++ {
-			go func(i int) {
-				if i == 0 {
-					s.connsLock.Lock()
-					conn := s.conns[0]
-					s.connsLock.Unlock()
-
-					<-shared.WebsocketRecvStream(s.ttys[0], conn)
-					s.ttys[0].Close()
-				} else {
-					s.connsLock.Lock()
-					conn := s.conns[i]
-					s.connsLock.Unlock()
-
-					<-shared.WebsocketSendStream(conn, s.ptys[i], -1)
-					s.ptys[i].Close()
-					s.wgEOF.Done()
-				}
-			}(i)
-		}
+		s.connectNotInteractiveStreams()
 	}
 
 	cmd, _, attachedPid, err := s.container.Exec(s.command, s.env, stdin, stdout, stderr, false)
@@ -240,6 +204,48 @@ func (s *execWs) openTTYs() (*os.File, *os.File, *os.File, error) {
 	return stdin, stdout, stderr, nil
 }
 
+func (s *execWs) connectInteractiveStreams() {
+	go func() {
+		s.connsLock.Lock()
+		conn := s.conns[0]
+		s.connsLock.Unlock()
+
+		logger.Debugf("Starting to mirror websocket")
+		readDone, writeDone := shared.WebsocketExecMirror(conn, s.ptys[0], s.ptys[0], s.doneCh, int(s.ptys[0].Fd()))
+
+		<-readDone
+		<-writeDone
+		logger.Debugf("Finished to mirror websocket")
+
+		conn.Close()
+		s.wgEOF.Done()
+	}()
+}
+
+func (s *execWs) connectNotInteractiveStreams() {
+	s.wgEOF.Add(len(s.ttys) - 1)
+	for i := 0; i < len(s.ttys); i++ {
+		go func(i int) {
+			if i == 0 {
+				s.connsLock.Lock()
+				conn := s.conns[0]
+				s.connsLock.Unlock()
+
+				<-shared.WebsocketRecvStream(s.ttys[0], conn)
+				s.ttys[0].Close()
+			} else {
+				s.connsLock.Lock()
+				conn := s.conns[i]
+				s.connsLock.Unlock()
+
+				<-shared.WebsocketSendStream(conn, s.ptys[i], -1)
+				s.ptys[i].Close()
+				s.wgEOF.Done()
+			}
+		}(i)
+	}
+}
+
 func (s *execWs) finish(op *operation, cmdResult int) error {
 	for _, tty := range s.ttys {
 		tty.Close()

From bbf41d5725dc4e694d9e557bcbbf7b6f7743d3d3 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 17 Oct 2017 13:55:52 +0200
Subject: [PATCH 06/12] lxd/container_exec: extract getMetadata

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 37 +++++++++++++++++++------------------
 1 file changed, 19 insertions(+), 18 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 78f49ae73..6722e48e2 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -37,6 +37,7 @@ type execWs struct {
 	controlExit chan bool
 	doneCh      chan bool
 	wgEOF       sync.WaitGroup
+	cmdResult   int
 }
 
 func (s *execWs) Do(op *operation) error {
@@ -147,24 +148,23 @@ func (s *execWs) Do(op *operation) error {
 	}
 
 	err = cmd.Wait()
+	s.cmdResult = -1
 	if err == nil {
-		return s.finish(op, 0)
-	}
-
-	exitErr, ok := err.(*exec.ExitError)
-	if ok {
-		status, ok := exitErr.Sys().(syscall.WaitStatus)
+		s.cmdResult = 0
+	} else {
+		exitErr, ok := err.(*exec.ExitError)
 		if ok {
-			return s.finish(op, status.ExitStatus())
-		}
-
-		if status.Signaled() {
-			// 128 + n == Fatal error signal "n"
-			return s.finish(op, 128+int(status.Signal()))
+			status, ok := exitErr.Sys().(syscall.WaitStatus)
+			if ok {
+				s.cmdResult = status.ExitStatus()
+			} else if status.Signaled() {
+				// 128 + n == Fatal error signal "n"
+				s.cmdResult = 128 + int(status.Signal())
+			}
 		}
 	}
-
-	return s.finish(op, -1)
+	s.finish(op)
+	return op.UpdateMetadata(s.getMetadata())
 }
 
 // Open TTYs. Retruns stdin, stdout, stderr descriptors.
@@ -246,7 +246,11 @@ func (s *execWs) connectNotInteractiveStreams() {
 	}
 }
 
-func (s *execWs) finish(op *operation, cmdResult int) error {
+func (s *execWs) getMetadata() shared.Jmap {
+	return shared.Jmap{"return": s.cmdResult}
+}
+
+func (s *execWs) finish(op *operation) {
 	for _, tty := range s.ttys {
 		tty.Close()
 	}
@@ -269,9 +273,6 @@ func (s *execWs) finish(op *operation, cmdResult int) error {
 	for _, pty := range s.ptys {
 		pty.Close()
 	}
-
-	metadata := shared.Jmap{"return": cmdResult}
-	return op.UpdateMetadata(metadata)
 }
 
 func containerExecPost(d *Daemon, r *http.Request) Response {

From 2cb3d02b5d84b5929cde22b6bcfd8059f50dc658 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 17 Oct 2017 17:14:31 +0200
Subject: [PATCH 07/12] lxd/container_exec: extract handleSignal

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 38 +++++++++++++++++++++++---------------
 1 file changed, 23 insertions(+), 15 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 6722e48e2..2094e988d 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -32,12 +32,13 @@ type execWs struct {
 	rootUid int64
 	rootGid int64
 
-	ttys        []*os.File
-	ptys        []*os.File
-	controlExit chan bool
-	doneCh      chan bool
-	wgEOF       sync.WaitGroup
-	cmdResult   int
+	ttys             []*os.File
+	ptys             []*os.File
+	controlExit      chan bool
+	doneCh           chan bool
+	wgEOF            sync.WaitGroup
+	cmdResult        int
+	attachedChildPid int
 }
 
 func (s *execWs) Do(op *operation) error {
@@ -53,7 +54,7 @@ func (s *execWs) Do(op *operation) error {
 	if s.interactive {
 		s.wgEOF.Add(1)
 		go func() {
-			attachedChildPid := <-attachedChildIsBorn
+			s.attachedChildPid = <-attachedChildIsBorn
 			select {
 			case <-s.controlConnected:
 				break
@@ -84,11 +85,11 @@ func (s *execWs) Do(op *operation) error {
 					}
 
 					// If an abnormal closure occurred, kill the attached process.
-					err := syscall.Kill(attachedChildPid, syscall.SIGKILL)
+					err := syscall.Kill(s.attachedChildPid, syscall.SIGKILL)
 					if err != nil {
-						logger.Debugf("Failed to send SIGKILL to pid %d.", attachedChildPid)
+						logger.Debugf("Failed to send SIGKILL to pid %d.", s.attachedChildPid)
 					} else {
-						logger.Debugf("Sent SIGKILL to pid %d.", attachedChildPid)
+						logger.Debugf("Sent SIGKILL to pid %d.", s.attachedChildPid)
 					}
 					return
 				}
@@ -125,11 +126,7 @@ func (s *execWs) Do(op *operation) error {
 						continue
 					}
 				} else if command.Command == "signal" {
-					if err := syscall.Kill(attachedChildPid, syscall.Signal(command.Signal)); err != nil {
-						logger.Debugf("Failed forwarding signal '%s' to PID %d.", command.Signal, attachedChildPid)
-						continue
-					}
-					logger.Debugf("Forwarded signal '%d' to PID %d.", command.Signal, attachedChildPid)
+					s.handleSignal(command.Signal)
 				}
 			}
 		}()
@@ -275,6 +272,17 @@ func (s *execWs) finish(op *operation) {
 	}
 }
 
+func (s *execWs) handleSignal(signal int) {
+	if s.attachedChildPid == 0 {
+		return
+	}
+	if err := syscall.Kill(s.attachedChildPid, syscall.Signal(signal)); err != nil {
+		logger.Debugf("Failed forwarding signal '%s' to PID %d.", signal, s.attachedChildPid)
+		return
+	}
+	logger.Debugf("Forwarded signal '%d' to PID %d.", signal, s.attachedChildPid)
+}
+
 func containerExecPost(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d.State(), name)

From 400163b7da23df4d74d657813a3304a8892f440e Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 17 Oct 2017 17:16:03 +0200
Subject: [PATCH 08/12] lxd/container_exec: extract handleAbnormalClosure

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 2094e988d..781b73acc 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -84,13 +84,7 @@ func (s *execWs) Do(op *operation) error {
 						break
 					}
 
-					// If an abnormal closure occurred, kill the attached process.
-					err := syscall.Kill(s.attachedChildPid, syscall.SIGKILL)
-					if err != nil {
-						logger.Debugf("Failed to send SIGKILL to pid %d.", s.attachedChildPid)
-					} else {
-						logger.Debugf("Sent SIGKILL to pid %d.", s.attachedChildPid)
-					}
+					s.handleAbnormalClosure()
 					return
 				}
 
@@ -283,6 +277,16 @@ func (s *execWs) handleSignal(signal int) {
 	logger.Debugf("Forwarded signal '%d' to PID %d.", signal, s.attachedChildPid)
 }
 
+func (s *execWs) handleAbnormalClosure() {
+	// If an abnormal closure occurred, kill the attached process.
+	err := syscall.Kill(s.attachedChildPid, syscall.SIGKILL)
+	if err != nil {
+		logger.Debugf("Failed to send SIGKILL to pid %d.", s.attachedChildPid)
+	} else {
+		logger.Debugf("Sent SIGKILL to pid %d.", s.attachedChildPid)
+	}
+}
+
 func containerExecPost(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d.State(), name)

From 8a787d36246c54a1935464d58187246e6bc43108 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 17 Oct 2017 17:20:29 +0200
Subject: [PATCH 09/12] lxd/container_exec move attachedChildIsBorn into execWs

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 32 ++++++++++++++++----------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 781b73acc..47fa6a355 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -32,13 +32,14 @@ type execWs struct {
 	rootUid int64
 	rootGid int64
 
-	ttys             []*os.File
-	ptys             []*os.File
-	controlExit      chan bool
-	doneCh           chan bool
-	wgEOF            sync.WaitGroup
-	cmdResult        int
-	attachedChildPid int
+	ttys                []*os.File
+	ptys                []*os.File
+	controlExit         chan bool
+	doneCh              chan bool
+	wgEOF               sync.WaitGroup
+	cmdResult           int
+	attachedChildPid    int
+	attachedChildIsBorn chan int
 }
 
 func (s *execWs) Do(op *operation) error {
@@ -49,12 +50,10 @@ func (s *execWs) Do(op *operation) error {
 		return err
 	}
 
-	attachedChildIsBorn := make(chan int)
-
 	if s.interactive {
 		s.wgEOF.Add(1)
 		go func() {
-			s.attachedChildPid = <-attachedChildIsBorn
+			s.attachedChildPid = <-s.attachedChildIsBorn
 			select {
 			case <-s.controlConnected:
 				break
@@ -135,7 +134,7 @@ func (s *execWs) Do(op *operation) error {
 	}
 
 	if s.interactive {
-		attachedChildIsBorn <- attachedPid
+		s.attachedChildIsBorn <- attachedPid
 	}
 
 	err = cmd.Wait()
@@ -359,11 +358,12 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 			return InternalError(err)
 		}
 		ws := &execWs{
-			ttyWs:       *ttyWs,
-			command:     post.Command,
-			env:         env,
-			controlExit: make(chan bool),
-			doneCh:      make(chan bool, 1),
+			ttyWs:               *ttyWs,
+			command:             post.Command,
+			env:                 env,
+			controlExit:         make(chan bool),
+			doneCh:              make(chan bool, 1),
+			attachedChildIsBorn: make(chan int),
 		}
 		idmapset, err := c.IdmapSet()
 		if err != nil {

From 94c9b325babc490fc4a17574b303939b628a79ae Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Tue, 17 Oct 2017 17:47:43 +0200
Subject: [PATCH 10/12] lxd/container_exec extact do function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 47fa6a355..21897ebd1 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -128,6 +128,15 @@ func (s *execWs) Do(op *operation) error {
 		s.connectNotInteractiveStreams()
 	}
 
+	err = s.do(stdin, stdout, stderr)
+	if err != nil {
+		return err
+	}
+	s.finish(op)
+	return op.UpdateMetadata(s.getMetadata())
+}
+
+func (s *execWs) do(stdin, stdout, stderr *os.File) error {
 	cmd, _, attachedPid, err := s.container.Exec(s.command, s.env, stdin, stdout, stderr, false)
 	if err != nil {
 		return err
@@ -153,8 +162,7 @@ func (s *execWs) Do(op *operation) error {
 			}
 		}
 	}
-	s.finish(op)
-	return op.UpdateMetadata(s.getMetadata())
+	return nil
 }
 
 // Open TTYs. Retruns stdin, stdout, stderr descriptors.

From 15247e752ed66e2b5df5bd241b04d7344b8c5e75 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 18 Oct 2017 10:13:11 +0200
Subject: [PATCH 11/12] lxd/container_exec extact startup function

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 21897ebd1..fc4372b14 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -53,7 +53,7 @@ func (s *execWs) Do(op *operation) error {
 	if s.interactive {
 		s.wgEOF.Add(1)
 		go func() {
-			s.attachedChildPid = <-s.attachedChildIsBorn
+			s.startup()
 			select {
 			case <-s.controlConnected:
 				break
@@ -136,6 +136,10 @@ func (s *execWs) Do(op *operation) error {
 	return op.UpdateMetadata(s.getMetadata())
 }
 
+func (s *execWs) startup() {
+	s.attachedChildPid = <-s.attachedChildIsBorn
+}
+
 func (s *execWs) do(stdin, stdout, stderr *os.File) error {
 	cmd, _, attachedPid, err := s.container.Exec(s.command, s.env, stdin, stdout, stderr, false)
 	if err != nil {

From 7b9fe1138bf03e0531c6eff98ef168e8edb1a15b Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 18 Oct 2017 12:48:42 +0200
Subject: [PATCH 12/12] lxd/container_tty: move common functions to
 container_tty, collect exec-specific methods in a struct

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/container_exec.go | 232 +++++++-------------------------------------------
 lxd/container_tty.go  | 188 +++++++++++++++++++++++++++++++++++++++-
 2 files changed, 215 insertions(+), 205 deletions(-)

diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index fc4372b14..144453e71 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -8,13 +8,10 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
-	"strconv"
 	"strings"
-	"sync"
 	"syscall"
 
 	"github.com/gorilla/mux"
-	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -24,145 +21,44 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
-type execWs struct {
-	ttyWs
-
+type execWsOps struct {
 	command []string
 	env     map[string]string
 	rootUid int64
 	rootGid int64
 
-	ttys                []*os.File
-	ptys                []*os.File
-	controlExit         chan bool
-	doneCh              chan bool
-	wgEOF               sync.WaitGroup
 	cmdResult           int
 	attachedChildPid    int
 	attachedChildIsBorn chan int
 }
 
-func (s *execWs) Do(op *operation) error {
-	<-s.allConnected
-
-	stdin, stdout, stderr, err := s.openTTYs()
-	if err != nil {
-		return err
-	}
-
-	if s.interactive {
-		s.wgEOF.Add(1)
-		go func() {
-			s.startup()
-			select {
-			case <-s.controlConnected:
-				break
-
-			case <-s.controlExit:
-				return
-			}
-
-			for {
-				s.connsLock.Lock()
-				conn := s.conns[-1]
-				s.connsLock.Unlock()
-
-				mt, r, err := conn.NextReader()
-				if mt == websocket.CloseMessage {
-					break
-				}
-
-				if err != nil {
-					logger.Debugf("Got error getting next reader %s", err)
-					er, ok := err.(*websocket.CloseError)
-					if !ok {
-						break
-					}
-
-					if er.Code != websocket.CloseAbnormalClosure {
-						break
-					}
-
-					s.handleAbnormalClosure()
-					return
-				}
-
-				buf, err := ioutil.ReadAll(r)
-				if err != nil {
-					logger.Debugf("Failed to read message %s", err)
-					break
-				}
-
-				command := api.ContainerTTYControl{}
-
-				if err := json.Unmarshal(buf, &command); err != nil {
-					logger.Debugf("Failed to unmarshal control socket command: %s", err)
-					continue
-				}
-
-				if command.Command == "window-resize" {
-					winchWidth, err := strconv.Atoi(command.Args["width"])
-					if err != nil {
-						logger.Debugf("Unable to extract window width: %s", err)
-						continue
-					}
-
-					winchHeight, err := strconv.Atoi(command.Args["height"])
-					if err != nil {
-						logger.Debugf("Unable to extract window height: %s", err)
-						continue
-					}
-
-					err = shared.SetSize(int(s.ptys[0].Fd()), winchWidth, winchHeight)
-					if err != nil {
-						logger.Debugf("Failed to set window size to: %dx%d", winchWidth, winchHeight)
-						continue
-					}
-				} else if command.Command == "signal" {
-					s.handleSignal(command.Signal)
-				}
-			}
-		}()
-		s.connectInteractiveStreams()
-	} else {
-		s.connectNotInteractiveStreams()
-	}
-
-	err = s.do(stdin, stdout, stderr)
-	if err != nil {
-		return err
-	}
-	s.finish(op)
-	return op.UpdateMetadata(s.getMetadata())
-}
-
-func (s *execWs) startup() {
-	s.attachedChildPid = <-s.attachedChildIsBorn
+func (o *execWsOps) startup(s *ttyWs) {
+	o.attachedChildPid = <-o.attachedChildIsBorn
 }
 
-func (s *execWs) do(stdin, stdout, stderr *os.File) error {
-	cmd, _, attachedPid, err := s.container.Exec(s.command, s.env, stdin, stdout, stderr, false)
+func (o *execWsOps) do(s *ttyWs, stdin, stdout, stderr *os.File) error {
+	cmd, _, attachedPid, err := s.container.Exec(o.command, o.env, stdin, stdout, stderr, false)
 	if err != nil {
 		return err
 	}
 
 	if s.interactive {
-		s.attachedChildIsBorn <- attachedPid
+		o.attachedChildIsBorn <- attachedPid
 	}
 
 	err = cmd.Wait()
-	s.cmdResult = -1
+	o.cmdResult = -1
 	if err == nil {
-		s.cmdResult = 0
+		o.cmdResult = 0
 	} else {
 		exitErr, ok := err.(*exec.ExitError)
 		if ok {
 			status, ok := exitErr.Sys().(syscall.WaitStatus)
 			if ok {
-				s.cmdResult = status.ExitStatus()
+				o.cmdResult = status.ExitStatus()
 			} else if status.Signaled() {
 				// 128 + n == Fatal error signal "n"
-				s.cmdResult = 128 + int(status.Signal())
+				o.cmdResult = 128 + int(status.Signal())
 			}
 		}
 	}
@@ -170,7 +66,7 @@ func (s *execWs) do(stdin, stdout, stderr *os.File) error {
 }
 
 // Open TTYs. Retruns stdin, stdout, stderr descriptors.
-func (s *execWs) openTTYs() (*os.File, *os.File, *os.File, error) {
+func (o *execWsOps) openTTYs(s *ttyWs) (*os.File, *os.File, *os.File, error) {
 	var stdin *os.File
 	var stdout *os.File
 	var stderr *os.File
@@ -179,7 +75,7 @@ func (s *execWs) openTTYs() (*os.File, *os.File, *os.File, error) {
 	if s.interactive {
 		s.ttys = make([]*os.File, 1)
 		s.ptys = make([]*os.File, 1)
-		s.ptys[0], s.ttys[0], err = shared.OpenPty(s.rootUid, s.rootGid)
+		s.ptys[0], s.ttys[0], err = shared.OpenPty(o.rootUid, o.rootGid)
 
 		stdin = s.ttys[0]
 		stdout = s.ttys[0]
@@ -206,95 +102,28 @@ func (s *execWs) openTTYs() (*os.File, *os.File, *os.File, error) {
 	return stdin, stdout, stderr, nil
 }
 
-func (s *execWs) connectInteractiveStreams() {
-	go func() {
-		s.connsLock.Lock()
-		conn := s.conns[0]
-		s.connsLock.Unlock()
-
-		logger.Debugf("Starting to mirror websocket")
-		readDone, writeDone := shared.WebsocketExecMirror(conn, s.ptys[0], s.ptys[0], s.doneCh, int(s.ptys[0].Fd()))
-
-		<-readDone
-		<-writeDone
-		logger.Debugf("Finished to mirror websocket")
-
-		conn.Close()
-		s.wgEOF.Done()
-	}()
-}
-
-func (s *execWs) connectNotInteractiveStreams() {
-	s.wgEOF.Add(len(s.ttys) - 1)
-	for i := 0; i < len(s.ttys); i++ {
-		go func(i int) {
-			if i == 0 {
-				s.connsLock.Lock()
-				conn := s.conns[0]
-				s.connsLock.Unlock()
-
-				<-shared.WebsocketRecvStream(s.ttys[0], conn)
-				s.ttys[0].Close()
-			} else {
-				s.connsLock.Lock()
-				conn := s.conns[i]
-				s.connsLock.Unlock()
-
-				<-shared.WebsocketSendStream(conn, s.ptys[i], -1)
-				s.ptys[i].Close()
-				s.wgEOF.Done()
-			}
-		}(i)
-	}
-}
-
-func (s *execWs) getMetadata() shared.Jmap {
-	return shared.Jmap{"return": s.cmdResult}
-}
-
-func (s *execWs) finish(op *operation) {
-	for _, tty := range s.ttys {
-		tty.Close()
-	}
-
-	s.connsLock.Lock()
-	conn := s.conns[-1]
-	s.connsLock.Unlock()
-
-	if conn == nil {
-		if s.interactive {
-			s.controlExit <- true
-		}
-	} else {
-		conn.Close()
-	}
-
-	s.doneCh <- true
-	s.wgEOF.Wait()
-
-	for _, pty := range s.ptys {
-		pty.Close()
-	}
+func (o *execWsOps) getMetadata(s *ttyWs) shared.Jmap {
+	return shared.Jmap{"return": o.cmdResult}
 }
 
-func (s *execWs) handleSignal(signal int) {
-	if s.attachedChildPid == 0 {
+func (o *execWsOps) handleSignal(s *ttyWs, signal int) {
+	if o.attachedChildPid == 0 {
 		return
 	}
-	if err := syscall.Kill(s.attachedChildPid, syscall.Signal(signal)); err != nil {
-		logger.Debugf("Failed forwarding signal '%s' to PID %d.", signal, s.attachedChildPid)
+	if err := syscall.Kill(o.attachedChildPid, syscall.Signal(signal)); err != nil {
+		logger.Debugf("Failed forwarding signal '%s' to PID %d.", signal, o.attachedChildPid)
 		return
 	}
-	logger.Debugf("Forwarded signal '%d' to PID %d.", signal, s.attachedChildPid)
+	logger.Debugf("Forwarded signal '%d' to PID %d.", signal, o.attachedChildPid)
 }
 
-func (s *execWs) handleAbnormalClosure() {
+func (o *execWsOps) handleAbnormalClosure(s *ttyWs) {
 	// If an abnormal closure occurred, kill the attached process.
-	err := syscall.Kill(s.attachedChildPid, syscall.SIGKILL)
+	err := syscall.Kill(o.attachedChildPid, syscall.SIGKILL)
 	if err != nil {
-		logger.Debugf("Failed to send SIGKILL to pid %d.", s.attachedChildPid)
+		logger.Debugf("Failed to send SIGKILL to pid %d.", o.attachedChildPid)
 	} else {
-		logger.Debugf("Sent SIGKILL to pid %d.", s.attachedChildPid)
+		logger.Debugf("Sent SIGKILL to pid %d.", o.attachedChildPid)
 	}
 }
 
@@ -365,24 +194,21 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	}
 
 	if post.WaitForWS {
-		ttyWs, err := newttyWs(c, post.Interactive, post.Width, post.Height)
-		if err != nil {
-			return InternalError(err)
-		}
-		ws := &execWs{
-			ttyWs:               *ttyWs,
+		ops := &execWsOps{
 			command:             post.Command,
 			env:                 env,
-			controlExit:         make(chan bool),
-			doneCh:              make(chan bool, 1),
 			attachedChildIsBorn: make(chan int),
 		}
+		ws, err := newttyWs(ops, c, post.Interactive, post.Width, post.Height)
+		if err != nil {
+			return InternalError(err)
+		}
 		idmapset, err := c.IdmapSet()
 		if err != nil {
 			return InternalError(err)
 		}
 		if idmapset != nil {
-			ws.rootUid, ws.rootGid = idmapset.ShiftIntoNs(0, 0)
+			ops.rootUid, ops.rootGid = idmapset.ShiftIntoNs(0, 0)
 		}
 
 		resources := map[string][]string{}
diff --git a/lxd/container_tty.go b/lxd/container_tty.go
index 4726c3bdf..7e9707a14 100644
--- a/lxd/container_tty.go
+++ b/lxd/container_tty.go
@@ -1,7 +1,9 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
+	"io/ioutil"
 	"net/http"
 	"os"
 	"strconv"
@@ -10,21 +12,39 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/logger"
 )
 
+type ttyWsOps interface {
+	openTTYs(s *ttyWs) (*os.File, *os.File, *os.File, error)
+	startup(s *ttyWs)
+	do(s *ttyWs, stdin, stdout, stderr *os.File) error
+	handleSignal(s *ttyWs, signal int)
+	handleAbnormalClosure(s *ttyWs)
+	getMetadata(s *ttyWs) shared.Jmap
+}
+
 type ttyWs struct {
 	container        container
 	conns            map[int]*websocket.Conn
 	connsLock        sync.Mutex
 	allConnected     chan bool
 	controlConnected chan bool
-	fds              map[int]string
+	controlExit      chan bool
+	doneCh           chan bool
+	wgEOF            sync.WaitGroup
 	interactive      bool
+	fds              map[int]string
+	ttys             []*os.File
+	ptys             []*os.File
 	width            int
 	height           int
+
+	ops ttyWsOps
 }
 
-func newttyWs(c container, interactive bool, width int, height int) (*ttyWs, error) {
+func newttyWs(ops ttyWsOps, c container, interactive bool, width int, height int) (*ttyWs, error) {
 	fds := map[int]string{}
 	conns := map[int]*websocket.Conn{
 		-1: nil,
@@ -48,9 +68,12 @@ func newttyWs(c container, interactive bool, width int, height int) (*ttyWs, err
 		conns:            conns,
 		allConnected:     make(chan bool, 1),
 		controlConnected: make(chan bool, 1),
+		controlExit:      make(chan bool),
+		doneCh:           make(chan bool, 1),
 		interactive:      interactive,
 		width:            width,
 		height:           height,
+		ops:              ops,
 	}, nil
 }
 
@@ -107,3 +130,164 @@ func (s *ttyWs) Connect(op *operation, r *http.Request, w http.ResponseWriter) e
 	 * which 403, not 404, since this operation actually exists */
 	return os.ErrPermission
 }
+
+func (s *ttyWs) Do(op *operation) error {
+	<-s.allConnected
+
+	stdin, stdout, stderr, err := s.ops.openTTYs(s)
+	if err != nil {
+		return err
+	}
+
+	if s.interactive {
+		s.wgEOF.Add(1)
+		go func() {
+			s.ops.startup(s)
+			select {
+			case <-s.controlConnected:
+				break
+
+			case <-s.controlExit:
+				return
+			}
+
+			for {
+				s.connsLock.Lock()
+				conn := s.conns[-1]
+				s.connsLock.Unlock()
+
+				mt, r, err := conn.NextReader()
+				if mt == websocket.CloseMessage {
+					break
+				}
+
+				if err != nil {
+					logger.Debugf("Got error getting next reader %s", err)
+					er, ok := err.(*websocket.CloseError)
+					if !ok {
+						break
+					}
+
+					if er.Code != websocket.CloseAbnormalClosure {
+						break
+					}
+
+					s.ops.handleAbnormalClosure(s)
+					return
+				}
+
+				buf, err := ioutil.ReadAll(r)
+				if err != nil {
+					logger.Debugf("Failed to read message %s", err)
+					break
+				}
+
+				command := api.ContainerTTYControl{}
+
+				if err := json.Unmarshal(buf, &command); err != nil {
+					logger.Debugf("Failed to unmarshal control socket command: %s", err)
+					continue
+				}
+
+				if command.Command == "window-resize" {
+					winchWidth, err := strconv.Atoi(command.Args["width"])
+					if err != nil {
+						logger.Debugf("Unable to extract window width: %s", err)
+						continue
+					}
+
+					winchHeight, err := strconv.Atoi(command.Args["height"])
+					if err != nil {
+						logger.Debugf("Unable to extract window height: %s", err)
+						continue
+					}
+
+					err = shared.SetSize(int(s.ptys[0].Fd()), winchWidth, winchHeight)
+					if err != nil {
+						logger.Debugf("Failed to set window size to: %dx%d", winchWidth, winchHeight)
+						continue
+					}
+				} else if command.Command == "signal" {
+					s.ops.handleSignal(s, command.Signal)
+				}
+			}
+		}()
+		s.connectInteractiveStreams()
+	} else {
+		s.connectNotInteractiveStreams()
+	}
+
+	err = s.ops.do(s, stdin, stdout, stderr)
+	if err != nil {
+		return err
+	}
+	s.finish(op)
+	return op.UpdateMetadata(s.ops.getMetadata(s))
+}
+
+func (s *ttyWs) connectInteractiveStreams() {
+	go func() {
+		s.connsLock.Lock()
+		conn := s.conns[0]
+		s.connsLock.Unlock()
+
+		logger.Debugf("Starting to mirror websocket")
+		readDone, writeDone := shared.WebsocketExecMirror(conn, s.ptys[0], s.ptys[0], s.doneCh, int(s.ptys[0].Fd()))
+
+		<-readDone
+		<-writeDone
+		logger.Debugf("Finished to mirror websocket")
+
+		conn.Close()
+		s.wgEOF.Done()
+	}()
+}
+
+func (s *ttyWs) connectNotInteractiveStreams() {
+	s.wgEOF.Add(len(s.ttys) - 1)
+	for i := 0; i < len(s.ttys); i++ {
+		go func(i int) {
+			if i == 0 {
+				s.connsLock.Lock()
+				conn := s.conns[0]
+				s.connsLock.Unlock()
+
+				<-shared.WebsocketRecvStream(s.ttys[0], conn)
+				s.ttys[0].Close()
+			} else {
+				s.connsLock.Lock()
+				conn := s.conns[i]
+				s.connsLock.Unlock()
+
+				<-shared.WebsocketSendStream(conn, s.ptys[i], -1)
+				s.ptys[i].Close()
+				s.wgEOF.Done()
+			}
+		}(i)
+	}
+}
+
+func (s *ttyWs) finish(op *operation) {
+	for _, tty := range s.ttys {
+		tty.Close()
+	}
+
+	s.connsLock.Lock()
+	conn := s.conns[-1]
+	s.connsLock.Unlock()
+
+	if conn == nil {
+		if s.interactive {
+			s.controlExit <- true
+		}
+	} else {
+		conn.Close()
+	}
+
+	s.doneCh <- true
+	s.wgEOF.Wait()
+
+	for _, pty := range s.ptys {
+		pty.Close()
+	}
+}


More information about the lxc-devel mailing list