Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix run/exec with terminal: true in case stdin is not a terminal #2535

Merged
merged 2 commits into from
Aug 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions tests/integration/tty.bats
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ function teardown() {
teardown_busybox
}

@test "runc run [stdin not a tty]" {
# stty size fails without a tty
update_config '(.. | select(.[]? == "sh")) += ["-c", "stty size"]'
# note that stdout/stderr are already redirected by bats' run
runc run test_busybox < /dev/null
[ "$status" -eq 0 ]
}

@test "runc run [tty ptsname]" {
# Replace sh script with readlink.
# shellcheck disable=SC2016
Expand Down Expand Up @@ -61,6 +69,18 @@ function teardown() {
[[ ${lines[1]} =~ 5 ]]
}

@test "runc exec [stdin not a tty]" {
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
[ "$status" -eq 0 ]

# make sure we're running
testcontainer test_busybox running

# note that stdout/stderr are already redirected by bats' run
runc exec -t test_busybox sh -c "stty size" < /dev/null
[ "$status" -eq 0 ]
}

@test "runc exec [tty ptsname]" {
# run busybox detached
runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
Expand Down
65 changes: 46 additions & 19 deletions tty.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -71,6 +72,37 @@ func inheritStdio(process *libcontainer.Process) error {
return nil
}

func (t *tty) initHostConsole() error {
// Usually all three (stdin, stdout, and stderr) streams are open to
// the terminal, but they might be redirected, so try them all.
for _, s := range []*os.File{os.Stderr, os.Stdout, os.Stdin} {
c, err := console.ConsoleFromFile(s)
switch err {
case nil:
t.hostConsole = c
return nil
case console.ErrNotAConsole:
continue
default:
// should not happen
return errors.Wrap(err, "unable to get console")
}
}
// If all streams are redirected, but we still have a controlling
// terminal, it can be obtained by opening /dev/tty.
tty, err := os.Open("/dev/tty")
if err != nil {
return err
}
c, err := console.ConsoleFromFile(tty)
if err != nil {
return errors.Wrap(err, "unable to get console")
}

t.hostConsole = c
return nil
}

func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) (Err error) {
f, err := utils.RecvFd(socket)
if err != nil {
Expand Down Expand Up @@ -99,18 +131,13 @@ 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
}
if err := stdin.SetRaw(); err != nil {
// Set raw mode for the controlling terminal.
if err := t.hostConsole.SetRaw(); err != nil {
return fmt.Errorf("failed to set the terminal from the stdin: %v", err)
}
go handleInterrupt(stdin)
go handleInterrupt(t.hostConsole)

t.epoller = epoller
t.stdin = stdin
t.console = epollConsole
t.closers = []io.Closer{epollConsole}
return nil
Expand Down Expand Up @@ -156,15 +183,15 @@ 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
}

func (t *tty) resize() error {
if t.console == nil {
if t.console == nil || t.hostConsole == nil {
return nil
}
return t.console.ResizeFrom(console.Current())
return t.console.ResizeFrom(t.hostConsole)
}
3 changes: 3 additions & 0 deletions utils_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, det
process.Stderr = nil
t := &tty{}
if !detach {
if err := t.initHostConsole(); err != nil {
return nil, err
}
parent, child, err := utils.NewSockPair("console")
if err != nil {
return nil, err
Expand Down