1// +build linux
2
3package main
4
5import (
6	"fmt"
7	"io"
8	"os"
9	"os/signal"
10	"sync"
11
12	"github.com/containerd/console"
13	"github.com/opencontainers/runc/libcontainer"
14	"github.com/opencontainers/runc/libcontainer/utils"
15	"github.com/pkg/errors"
16)
17
18type tty struct {
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
26}
27
28func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
29	defer t.wg.Done()
30	io.Copy(w, r)
31	r.Close()
32}
33
34// setup pipes for the process so that advanced features like c/r are able to easily checkpoint
35// and restore the process's IO without depending on a host specific path or device
36func setupProcessPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, error) {
37	i, err := p.InitializeIO(rootuid, rootgid)
38	if err != nil {
39		return nil, err
40	}
41	t := &tty{
42		closers: []io.Closer{
43			i.Stdin,
44			i.Stdout,
45			i.Stderr,
46		},
47	}
48	// add the process's io to the post start closers if they support close
49	for _, cc := range []interface{}{
50		p.Stdin,
51		p.Stdout,
52		p.Stderr,
53	} {
54		if c, ok := cc.(io.Closer); ok {
55			t.postStart = append(t.postStart, c)
56		}
57	}
58	go func() {
59		io.Copy(i.Stdin, os.Stdin)
60		i.Stdin.Close()
61	}()
62	t.wg.Add(2)
63	go t.copyIO(os.Stdout, i.Stdout)
64	go t.copyIO(os.Stderr, i.Stderr)
65	return t, nil
66}
67
68func inheritStdio(process *libcontainer.Process) error {
69	process.Stdin = os.Stdin
70	process.Stdout = os.Stdout
71	process.Stderr = os.Stderr
72	return nil
73}
74
75func (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
106func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) (Err error) {
107	f, err := utils.RecvFd(socket)
108	if err != nil {
109		return err
110	}
111	cons, err := console.ConsoleFromFile(f)
112	if err != nil {
113		return err
114	}
115	err = console.ClearONLCR(cons.Fd())
116	if err != nil {
117		return err
118	}
119	epoller, err := console.NewEpoller()
120	if err != nil {
121		return err
122	}
123	epollConsole, err := epoller.Add(cons)
124	if err != nil {
125		return err
126	}
127	defer func() {
128		if Err != nil {
129			epollConsole.Close()
130		}
131	}()
132	go epoller.Wait()
133	go io.Copy(epollConsole, os.Stdin)
134	t.wg.Add(1)
135	go t.copyIO(os.Stdout, epollConsole)
136
137	// Set raw mode for the controlling terminal.
138	if err := t.hostConsole.SetRaw(); err != nil {
139		return fmt.Errorf("failed to set the terminal from the stdin: %v", err)
140	}
141	go handleInterrupt(t.hostConsole)
142
143	t.epoller = epoller
144	t.console = epollConsole
145	t.closers = []io.Closer{epollConsole}
146	return nil
147}
148
149func handleInterrupt(c console.Console) {
150	sigchan := make(chan os.Signal, 1)
151	signal.Notify(sigchan, os.Interrupt)
152	<-sigchan
153	c.Reset()
154	os.Exit(0)
155}
156
157func (t *tty) waitConsole() error {
158	if t.consoleC != nil {
159		return <-t.consoleC
160	}
161	return nil
162}
163
164// ClosePostStart closes any fds that are provided to the container and dup2'd
165// so that we no longer have copy in our process.
166func (t *tty) ClosePostStart() error {
167	for _, c := range t.postStart {
168		c.Close()
169	}
170	return nil
171}
172
173// Close closes all open fds for the tty and/or restores the original
174// stdin state to what it was prior to the container execution
175func (t *tty) Close() error {
176	// ensure that our side of the fds are always closed
177	for _, c := range t.postStart {
178		c.Close()
179	}
180	// the process is gone at this point, shutting down the console if we have
181	// one and wait for all IO to be finished
182	if t.console != nil && t.epoller != nil {
183		t.console.Shutdown(t.epoller.CloseConsole)
184	}
185	t.wg.Wait()
186	for _, c := range t.closers {
187		c.Close()
188	}
189	if t.hostConsole != nil {
190		t.hostConsole.Reset()
191	}
192	return nil
193}
194
195func (t *tty) resize() error {
196	if t.console == nil || t.hostConsole == nil {
197		return nil
198	}
199	return t.console.ResizeFrom(t.hostConsole)
200}
201