Skip to content

Commit b79cb04

Browse files
committed
runc run/exec: fix terminal wrt stdin redirection
This fixes the following failure: > sudo runc run -b bundle ctr </dev/null > 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 caused by SIGHUP which is sent to init by the kernel when we are losing the controlling terminal. If we choose to ignore that, we'll get panic in console.Current(), which 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 a similar thing (see initHostConsole). It also replaces the call to console.Current(), which may panic (see [1]), by reusing the t.hostConsole. Finally, a simple test case is added. Fixes: opencontainers#2485 [1] containerd/console#37 Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
1 parent e949339 commit b79cb04

File tree

2 files changed

+49
-19
lines changed

2 files changed

+49
-19
lines changed

tty.go

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@ import (
1212
"github.com/containerd/console"
1313
"github.com/opencontainers/runc/libcontainer"
1414
"github.com/opencontainers/runc/libcontainer/utils"
15+
"github.com/pkg/errors"
1516
)
1617

1718
type tty struct {
18-
epoller *console.Epoller
19-
console *console.EpollConsole
20-
stdin console.Console
21-
closers []io.Closer
22-
postStart []io.Closer
23-
wg sync.WaitGroup
24-
consoleC chan error
19+
epoller *console.Epoller
20+
console *console.EpollConsole
21+
hostConsole console.Console
22+
closers []io.Closer
23+
postStart []io.Closer
24+
wg sync.WaitGroup
25+
consoleC chan error
2526
}
2627

2728
func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
@@ -71,6 +72,37 @@ func inheritStdio(process *libcontainer.Process) error {
7172
return nil
7273
}
7374

75+
func (t *tty) initHostConsole() error {
76+
// Usually all three (stdin, stdout, and stderr) streams are open to
77+
// the terminal, but they might be redirected, so try them all.
78+
for _, s := range []*os.File{os.Stderr, os.Stdout, os.Stdin} {
79+
c, err := console.ConsoleFromFile(s)
80+
switch err {
81+
case nil:
82+
t.hostConsole = c
83+
return nil
84+
case console.ErrNotAConsole:
85+
continue
86+
default:
87+
// should not happen
88+
return errors.Wrap(err, "unable to get console")
89+
}
90+
}
91+
// If all streams are redirected, but we still have a controlling
92+
// terminal, it can be obtained by opening /dev/tty.
93+
tty, err := os.Open("/dev/tty")
94+
if err != nil {
95+
return err
96+
}
97+
c, err := console.ConsoleFromFile(tty)
98+
if err != nil {
99+
return errors.Wrap(err, "unable to get console")
100+
}
101+
102+
t.hostConsole = c
103+
return nil
104+
}
105+
74106
func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) (Err error) {
75107
f, err := utils.RecvFd(socket)
76108
if err != nil {
@@ -99,18 +131,13 @@ func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) (Err error
99131
t.wg.Add(1)
100132
go t.copyIO(os.Stdout, epollConsole)
101133

102-
// set raw mode to stdin and also handle interrupt
103-
stdin, err := console.ConsoleFromFile(os.Stdin)
104-
if err != nil {
105-
return err
106-
}
107-
if err := stdin.SetRaw(); err != nil {
134+
// Set raw mode for the controlling terminal.
135+
if err := t.hostConsole.SetRaw(); err != nil {
108136
return fmt.Errorf("failed to set the terminal from the stdin: %v", err)
109137
}
110-
go handleInterrupt(stdin)
138+
go handleInterrupt(t.hostConsole)
111139

112140
t.epoller = epoller
113-
t.stdin = stdin
114141
t.console = epollConsole
115142
t.closers = []io.Closer{epollConsole}
116143
return nil
@@ -156,15 +183,15 @@ func (t *tty) Close() error {
156183
for _, c := range t.closers {
157184
c.Close()
158185
}
159-
if t.stdin != nil {
160-
t.stdin.Reset()
186+
if t.hostConsole != nil {
187+
t.hostConsole.Reset()
161188
}
162189
return nil
163190
}
164191

165192
func (t *tty) resize() error {
166-
if t.console == nil {
193+
if t.console == nil || t.hostConsole == nil {
167194
return nil
168195
}
169-
return t.console.ResizeFrom(console.Current())
196+
return t.console.ResizeFrom(t.hostConsole)
170197
}

utils_linux.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, det
157157
process.Stderr = nil
158158
t := &tty{}
159159
if !detach {
160+
if err := t.initHostConsole(); err != nil {
161+
return nil, err
162+
}
160163
parent, child, err := utils.NewSockPair("console")
161164
if err != nil {
162165
return nil, err

0 commit comments

Comments
 (0)