From d731e7b3b0ef0c1add1efe326b719e9dd84d9054 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Thu, 30 Jul 2020 20:20:54 -0700 Subject: [PATCH] runc run/exec: fix terminal wrt stdin redirection This fixes the following failure: > sudo runc run -b bundle ctr WARN[0000] exit status 2 > ERRO[0000] container_linux.go:367: starting container process caused: process_linux.go:459: container init caused: The "exit status 2" with no error message is most probably caused by the panic in console.Current(), and is addressed by [1]. Otherwise, the issue here is simple: the code assumes stdin is opened to a terminal, and fails to work otherwise. Some standard Linux tools (e.g. stty, top) do the same (modulo panic), while some others (reset, tput) use the trick of trying all the three std streams (starting with stderr as it is least likely to be redirected), and if all three fails, open /dev/tty. This commit does the same, except that /dev/tty is not tried. It also replaces the call to console.Current() which might panic by reusing the t.hostConsole. Fixes: https://github.com/opencontainers/runc/issues/2485 [1] https://github.com/containerd/console/pull/37 Signed-off-by: Kir Kolyshkin --- tty.go | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/tty.go b/tty.go index 6106c2db368..3d954b78679 100644 --- a/tty.go +++ b/tty.go @@ -12,16 +12,17 @@ import ( "github.com/containerd/console" "github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/runc/libcontainer/utils" + "github.com/pkg/errors" ) type tty struct { - epoller *console.Epoller - console *console.EpollConsole - stdin console.Console - closers []io.Closer - postStart []io.Closer - wg sync.WaitGroup - consoleC chan error + epoller *console.Epoller + console *console.EpollConsole + hostConsole console.Console + closers []io.Closer + postStart []io.Closer + wg sync.WaitGroup + consoleC chan error } func (t *tty) copyIO(w io.Writer, r io.ReadCloser) { @@ -99,18 +100,32 @@ func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) (Err error t.wg.Add(1) go t.copyIO(os.Stdout, epollConsole) - // set raw mode to stdin and also handle interrupt - stdin, err := console.ConsoleFromFile(os.Stdin) - if err != nil { - return err + // Set raw mode for the controlling terminal. Usually all three + // (stdin, stdout, and stderr) streams are open to the terminal, + // but they might be redirected, so try them all. + var hostConsole console.Console + for _, s := range []*os.File{os.Stderr, os.Stdout, os.Stdin} { + c, err := console.ConsoleFromFile(s) + switch err { + case console.ErrNotAConsole: + continue + case nil: + hostConsole = c + default: + return errors.Wrap(err, "unable to get console") + } } - if err := stdin.SetRaw(); err != nil { + if hostConsole == nil { + return errors.New("unable to find console (are all streams redirected?)") + } + + if err := hostConsole.SetRaw(); err != nil { return fmt.Errorf("failed to set the terminal from the stdin: %v", err) } - go handleInterrupt(stdin) + go handleInterrupt(hostConsole) t.epoller = epoller - t.stdin = stdin + t.hostConsole = hostConsole t.console = epollConsole t.closers = []io.Closer{epollConsole} return nil @@ -156,8 +171,8 @@ func (t *tty) Close() error { for _, c := range t.closers { c.Close() } - if t.stdin != nil { - t.stdin.Reset() + if t.hostConsole != nil { + t.hostConsole.Reset() } return nil } @@ -166,5 +181,5 @@ func (t *tty) resize() error { if t.console == nil { return nil } - return t.console.ResizeFrom(console.Current()) + return t.console.ResizeFrom(t.hostConsole) }