Skip to content

Commit 91eafcb

Browse files
committed
tty: move IO of master pty to be done with epoll
This moves all console code to use github.com/containerd/console library to handle console I/O. Also move to use EpollConsole by default when user requests a terminal so we can still cope when the other side temporarily goes away. Signed-off-by: Daniel Dao <[email protected]>
1 parent e775f0f commit 91eafcb

File tree

22 files changed

+1120
-230
lines changed

22 files changed

+1120
-230
lines changed

contrib/cmd/recvtty/recvtty.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
"os"
2525
"strings"
2626

27-
"github.com/opencontainers/runc/libcontainer"
27+
"github.com/containerd/console"
2828
"github.com/opencontainers/runc/libcontainer/utils"
2929
"github.com/urfave/cli"
3030
)
@@ -101,24 +101,25 @@ func handleSingle(path string) error {
101101
if err != nil {
102102
return err
103103
}
104-
if err = libcontainer.SaneTerminal(master); err != nil {
104+
console, err := console.ConsoleFromFile(master)
105+
if err != nil {
105106
return err
106107
}
107108

108109
// Copy from our stdio to the master fd.
109110
quitChan := make(chan struct{})
110111
go func() {
111-
io.Copy(os.Stdout, master)
112+
io.Copy(os.Stdout, console)
112113
quitChan <- struct{}{}
113114
}()
114115
go func() {
115-
io.Copy(master, os.Stdin)
116+
io.Copy(console, os.Stdin)
116117
quitChan <- struct{}{}
117118
}()
118119

119120
// Only close the master fd once we've stopped copying.
120121
<-quitChan
121-
master.Close()
122+
console.Close()
122123
return nil
123124
}
124125

libcontainer/console.go

Lines changed: 0 additions & 17 deletions
This file was deleted.

libcontainer/console_freebsd.go

Lines changed: 0 additions & 13 deletions
This file was deleted.

libcontainer/console_linux.go

Lines changed: 9 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,14 @@
11
package libcontainer
22

33
import (
4-
"fmt"
54
"os"
6-
"unsafe"
75

86
"golang.org/x/sys/unix"
97
)
108

11-
func ConsoleFromFile(f *os.File) Console {
12-
return &linuxConsole{
13-
master: f,
14-
}
15-
}
16-
17-
// newConsole returns an initialized console that can be used within a container by copying bytes
18-
// from the master side to the slave that is attached as the tty for the container's init process.
19-
func newConsole() (Console, error) {
20-
master, err := os.OpenFile("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY|unix.O_CLOEXEC, 0)
21-
if err != nil {
22-
return nil, err
23-
}
24-
console, err := ptsname(master)
25-
if err != nil {
26-
return nil, err
27-
}
28-
if err := unlockpt(master); err != nil {
29-
return nil, err
30-
}
31-
return &linuxConsole{
32-
slavePath: console,
33-
master: master,
34-
}, nil
35-
}
36-
37-
// linuxConsole is a linux pseudo TTY for use within a container.
38-
type linuxConsole struct {
39-
master *os.File
40-
slavePath string
41-
}
42-
43-
func (c *linuxConsole) File() *os.File {
44-
return c.master
45-
}
46-
47-
func (c *linuxConsole) Path() string {
48-
return c.slavePath
49-
}
50-
51-
func (c *linuxConsole) Read(b []byte) (int, error) {
52-
return c.master.Read(b)
53-
}
54-
55-
func (c *linuxConsole) Write(b []byte) (int, error) {
56-
return c.master.Write(b)
57-
}
58-
59-
func (c *linuxConsole) Close() error {
60-
if m := c.master; m != nil {
61-
return m.Close()
62-
}
63-
return nil
64-
}
65-
669
// mount initializes the console inside the rootfs mounting with the specified mount label
6710
// and applying the correct ownership of the console.
68-
func (c *linuxConsole) mount() error {
11+
func mountConsole(slavePath string) error {
6912
oldMask := unix.Umask(0000)
7013
defer unix.Umask(oldMask)
7114
f, err := os.Create("/dev/console")
@@ -75,78 +18,24 @@ func (c *linuxConsole) mount() error {
7518
if f != nil {
7619
f.Close()
7720
}
78-
return unix.Mount(c.slavePath, "/dev/console", "bind", unix.MS_BIND, "")
21+
return unix.Mount(slavePath, "/dev/console", "bind", unix.MS_BIND, "")
7922
}
8023

8124
// dupStdio opens the slavePath for the console and dups the fds to the current
8225
// processes stdio, fd 0,1,2.
83-
func (c *linuxConsole) dupStdio() error {
84-
slave, err := c.open(unix.O_RDWR)
26+
func dupStdio(slavePath string) error {
27+
fd, err := unix.Open(slavePath, unix.O_RDWR, 0)
8528
if err != nil {
86-
return err
29+
return &os.PathError{
30+
Op: "open",
31+
Path: slavePath,
32+
Err: err,
33+
}
8734
}
88-
fd := int(slave.Fd())
8935
for _, i := range []int{0, 1, 2} {
9036
if err := unix.Dup3(fd, i, 0); err != nil {
9137
return err
9238
}
9339
}
9440
return nil
9541
}
96-
97-
// open is a clone of os.OpenFile without the O_CLOEXEC used to open the pty slave.
98-
func (c *linuxConsole) open(flag int) (*os.File, error) {
99-
r, e := unix.Open(c.slavePath, flag, 0)
100-
if e != nil {
101-
return nil, &os.PathError{
102-
Op: "open",
103-
Path: c.slavePath,
104-
Err: e,
105-
}
106-
}
107-
return os.NewFile(uintptr(r), c.slavePath), nil
108-
}
109-
110-
func ioctl(fd uintptr, flag, data uintptr) error {
111-
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
112-
return err
113-
}
114-
return nil
115-
}
116-
117-
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
118-
// unlockpt should be called before opening the slave side of a pty.
119-
func unlockpt(f *os.File) error {
120-
var u int32
121-
return ioctl(f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u)))
122-
}
123-
124-
// ptsname retrieves the name of the first available pts for the given master.
125-
func ptsname(f *os.File) (string, error) {
126-
n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCGPTN)
127-
if err != nil {
128-
return "", err
129-
}
130-
return fmt.Sprintf("/dev/pts/%d", n), nil
131-
}
132-
133-
// SaneTerminal sets the necessary tty_ioctl(4)s to ensure that a pty pair
134-
// created by us acts normally. In particular, a not-very-well-known default of
135-
// Linux unix98 ptys is that they have +onlcr by default. While this isn't a
136-
// problem for terminal emulators, because we relay data from the terminal we
137-
// also relay that funky line discipline.
138-
func SaneTerminal(terminal *os.File) error {
139-
termios, err := unix.IoctlGetTermios(int(terminal.Fd()), unix.TCGETS)
140-
if err != nil {
141-
return fmt.Errorf("ioctl(tty, tcgets): %s", err.Error())
142-
}
143-
144-
// Set -onlcr so we don't have to deal with \r.
145-
termios.Oflag &^= unix.ONLCR
146-
147-
if err := unix.IoctlSetTermios(int(terminal.Fd()), unix.TCSETS, termios); err != nil {
148-
return fmt.Errorf("ioctl(tty, tcsets): %s", err.Error())
149-
}
150-
151-
return nil
152-
}

libcontainer/console_solaris.go

Lines changed: 0 additions & 11 deletions
This file was deleted.

libcontainer/console_windows.go

Lines changed: 0 additions & 30 deletions
This file was deleted.

libcontainer/container_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1300,7 +1300,7 @@ func (c *linuxContainer) criuNotifications(resp *criurpc.CriuResp, process *Proc
13001300
defer master.Close()
13011301

13021302
// While we can access console.master, using the API is a good idea.
1303-
if err := utils.SendFd(process.ConsoleSocket, master); err != nil {
1303+
if err := utils.SendFd(process.ConsoleSocket, master.Name(), master.Fd()); err != nil {
13041304
return err
13051305
}
13061306
}

libcontainer/init_linux.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ import (
1212
"syscall" // only for Errno
1313
"unsafe"
1414

15+
"golang.org/x/sys/unix"
16+
17+
"github.com/containerd/console"
1518
"github.com/opencontainers/runc/libcontainer/cgroups"
1619
"github.com/opencontainers/runc/libcontainer/configs"
1720
"github.com/opencontainers/runc/libcontainer/system"
1821
"github.com/opencontainers/runc/libcontainer/user"
1922
"github.com/opencontainers/runc/libcontainer/utils"
20-
2123
"github.com/sirupsen/logrus"
2224
"github.com/vishvananda/netlink"
23-
"golang.org/x/sys/unix"
2425
)
2526

2627
type initType string
@@ -169,29 +170,25 @@ func setupConsole(socket *os.File, config *initConfig, mount bool) error {
169170
// however, that setupUser (specifically fixStdioPermissions) *will* change
170171
// the UID owner of the console to be the user the process will run as (so
171172
// they can actually control their console).
172-
console, err := newConsole()
173+
console, slavePath, err := console.NewPty()
173174
if err != nil {
174175
return err
175176
}
176177
// After we return from here, we don't need the console anymore.
177178
defer console.Close()
178179

179-
linuxConsole, ok := console.(*linuxConsole)
180-
if !ok {
181-
return fmt.Errorf("failed to cast console to *linuxConsole")
182-
}
183180
// Mount the console inside our rootfs.
184181
if mount {
185-
if err := linuxConsole.mount(); err != nil {
182+
if err := mountConsole(slavePath); err != nil {
186183
return err
187184
}
188185
}
189186
// While we can access console.master, using the API is a good idea.
190-
if err := utils.SendFd(socket, linuxConsole.File()); err != nil {
187+
if err := utils.SendFd(socket, console.Name(), console.Fd()); err != nil {
191188
return err
192189
}
193190
// Now, dup over all the things.
194-
return linuxConsole.dupStdio()
191+
return dupStdio(slavePath)
195192
}
196193

197194
// syncParentReady sends to the given pipe a JSON payload which indicates that

libcontainer/integration/execin_test.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"testing"
1111
"time"
1212

13+
"github.com/containerd/console"
1314
"github.com/opencontainers/runc/libcontainer"
1415
"github.com/opencontainers/runc/libcontainer/configs"
1516
"github.com/opencontainers/runc/libcontainer/utils"
@@ -289,7 +290,7 @@ func TestExecInTTY(t *testing.T) {
289290
defer child.Close()
290291
ps.ConsoleSocket = child
291292
type cdata struct {
292-
c libcontainer.Console
293+
c console.Console
293294
err error
294295
}
295296
dc := make(chan *cdata, 1)
@@ -299,10 +300,18 @@ func TestExecInTTY(t *testing.T) {
299300
dc <- &cdata{
300301
err: err,
301302
}
303+
return
302304
}
303-
libcontainer.SaneTerminal(f)
305+
c, err := console.ConsoleFromFile(f)
306+
if err != nil {
307+
dc <- &cdata{
308+
err: err,
309+
}
310+
return
311+
}
312+
console.SaneTerminal(f)
304313
dc <- &cdata{
305-
c: libcontainer.ConsoleFromFile(f),
314+
c: c,
306315
}
307316
}()
308317
err = container.Run(ps)

libcontainer/utils/cmsg.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,10 @@ func RecvFd(socket *os.File) (*os.File, error) {
8484
// addition, the file.Name() of the given file will also be sent as
8585
// non-auxiliary data in the same payload (allowing to send contextual
8686
// information for a file descriptor).
87-
func SendFd(socket, file *os.File) error {
88-
name := []byte(file.Name())
87+
func SendFd(socket *os.File, name string, fd uintptr) error {
8988
if len(name) >= MaxNameLen {
90-
return fmt.Errorf("sendfd: filename too long: %s", file.Name())
89+
return fmt.Errorf("sendfd: filename too long: %s", name)
9190
}
92-
oob := unix.UnixRights(int(file.Fd()))
93-
94-
return unix.Sendmsg(int(socket.Fd()), name, oob, nil, 0)
91+
oob := unix.UnixRights(int(fd))
92+
return unix.Sendmsg(int(socket.Fd()), []byte(name), oob, nil, 0)
9593
}

0 commit comments

Comments
 (0)