spiegel_podman/cmd/podman/utils.go
Giuseppe Scrivano 3f0e2367c2 libpod: fix panic when using -t and the process fails to start
We were closing resize both on the receiver and the sender side.
This was racy as the sender might have written to a closed channel.
If the container could not be created, the attach exited
immediately causing the channel to be closed before the write from
resizeTty.

Change the logic to close only from the senderSide and add another
channel to notify the resizeTty goroutine when the container exited.

Closes: https://github.com/projectatomic/libpod/issues/785

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>

Closes: #787
Approved by: mheon
2018-05-17 13:26:16 +00:00

173 lines
4.2 KiB
Go

package main
import (
"fmt"
"os"
gosignal "os/signal"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/libpod"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/client-go/tools/remotecommand"
)
// Attach to a container
func attachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool) error {
resize := make(chan remotecommand.TerminalSize)
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
// Check if we are attached to a terminal. If we are, generate resize
// events, and set the terminal to raw mode
if haveTerminal && ctr.Spec().Process.Terminal {
logrus.Debugf("Handling terminal attach")
resizeTerminate := make(chan interface{})
defer func() {
resizeTerminate <- true
close(resizeTerminate)
}()
resizeTty(resize, resizeTerminate)
oldTermState, err := term.SaveState(os.Stdin.Fd())
if err != nil {
return errors.Wrapf(err, "unable to save terminal state")
}
term.SetRawTerminal(os.Stdin.Fd())
defer term.RestoreTerminal(os.Stdin.Fd(), oldTermState)
}
streams := new(libpod.AttachStreams)
streams.OutputStream = stdout
streams.ErrorStream = stderr
streams.InputStream = stdin
streams.AttachOutput = true
streams.AttachError = true
streams.AttachInput = true
if stdout == nil {
logrus.Debugf("Not attaching to stdout")
streams.AttachOutput = false
}
if stderr == nil {
logrus.Debugf("Not attaching to stderr")
streams.AttachError = false
}
if stdin == nil {
logrus.Debugf("Not attaching to stdin")
streams.AttachInput = false
}
if sigProxy {
ProxySignals(ctr)
}
return ctr.Attach(streams, detachKeys, resize)
}
// Start and attach to a container
func startAttachCtr(ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool) error {
resize := make(chan remotecommand.TerminalSize)
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
// Check if we are attached to a terminal. If we are, generate resize
// events, and set the terminal to raw mode
if haveTerminal && ctr.Spec().Process.Terminal {
logrus.Debugf("Handling terminal attach")
resizeTerminate := make(chan interface{})
defer func() {
resizeTerminate <- true
close(resizeTerminate)
}()
resizeTty(resize, resizeTerminate)
oldTermState, err := term.SaveState(os.Stdin.Fd())
if err != nil {
return errors.Wrapf(err, "unable to save terminal state")
}
term.SetRawTerminal(os.Stdin.Fd())
defer term.RestoreTerminal(os.Stdin.Fd(), oldTermState)
}
streams := new(libpod.AttachStreams)
streams.OutputStream = stdout
streams.ErrorStream = stderr
streams.InputStream = stdin
streams.AttachOutput = true
streams.AttachError = true
streams.AttachInput = true
if stdout == nil {
logrus.Debugf("Not attaching to stdout")
streams.AttachOutput = false
}
if stderr == nil {
logrus.Debugf("Not attaching to stderr")
streams.AttachError = false
}
if stdin == nil {
logrus.Debugf("Not attaching to stdin")
streams.AttachInput = false
}
attachChan, err := ctr.StartAndAttach(getContext(), streams, detachKeys, resize)
if err != nil {
return err
}
if sigProxy {
ProxySignals(ctr)
}
if stdout == nil && stderr == nil {
fmt.Printf("%s\n", ctr.ID())
}
err = <-attachChan
if err != nil {
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
}
return nil
}
// Helper for prepareAttach - set up a goroutine to generate terminal resize events
func resizeTty(resize chan remotecommand.TerminalSize, resizeTerminate chan interface{}) {
sigchan := make(chan os.Signal, 1)
gosignal.Notify(sigchan, signal.SIGWINCH)
sendUpdate := func() {
winsize, err := term.GetWinsize(os.Stdin.Fd())
if err != nil {
logrus.Warnf("Could not get terminal size %v", err)
return
}
resize <- remotecommand.TerminalSize{
Width: winsize.Width,
Height: winsize.Height,
}
}
go func() {
defer close(resize)
// Update the terminal size immediately without waiting
// for a SIGWINCH to get the correct initial size.
sendUpdate()
for {
select {
case <-resizeTerminate:
return
case <-sigchan:
sendUpdate()
}
}
}()
}