...

Source file src/github.com/opencontainers/runc/tty.go

Documentation: github.com/opencontainers/runc

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

View as plain text