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
