The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/3953

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
This is meant as a first step to allow adding a `ContainerConsole` call to connect to the container console, reusing as much as possible of the exec code
From 9167eceb4b3316dc63cf08de4114afa5493a0220 Mon Sep 17 00:00:00 2001
From: Alberto Donato <[email protected]>
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 <[email protected]>
---
 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 <[email protected]>
Date: Tue, 17 Oct 2017 10:37:14 +0200
Subject: [PATCH 02/12] lxd: extract common websocket TTY logic

Signed-off-by: Alberto Donato <[email protected]>
---
 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 <[email protected]>
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 <[email protected]>
---
 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 <[email protected]>
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 <[email protected]>
---
 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 <[email protected]>
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 <[email protected]>
---
 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 <[email protected]>
Date: Tue, 17 Oct 2017 13:55:52 +0200
Subject: [PATCH 06/12] lxd/container_exec: extract getMetadata

Signed-off-by: Alberto Donato <[email protected]>
---
 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 <[email protected]>
Date: Tue, 17 Oct 2017 17:14:31 +0200
Subject: [PATCH 07/12] lxd/container_exec: extract handleSignal

Signed-off-by: Alberto Donato <[email protected]>
---
 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 <[email protected]>
Date: Tue, 17 Oct 2017 17:16:03 +0200
Subject: [PATCH 08/12] lxd/container_exec: extract handleAbnormalClosure

Signed-off-by: Alberto Donato <[email protected]>
---
 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 <[email protected]>
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 <[email protected]>
---
 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 <[email protected]>
Date: Tue, 17 Oct 2017 17:47:43 +0200
Subject: [PATCH 10/12] lxd/container_exec extact do function

Signed-off-by: Alberto Donato <[email protected]>
---
 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 <[email protected]>
Date: Wed, 18 Oct 2017 10:13:11 +0200
Subject: [PATCH 11/12] lxd/container_exec extact startup function

Signed-off-by: Alberto Donato <[email protected]>
---
 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 <[email protected]>
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 <[email protected]>
---
 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()
+       }
+}
_______________________________________________
lxc-devel mailing list
[email protected]
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to