[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