github.com/mgoltzsche/ctnr@v0.7.1-alpha/run/librunner/tty.go (about)

     1  // +build linux
     2  
     3  // This file is derived from opencontainers/runc/tty.go and opencontainers/runc/utils_linux.go (setupIO)
     4  package librunner
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"os"
    11  	"os/signal"
    12  	"sync"
    13  
    14  	"github.com/containerd/console"
    15  	"github.com/opencontainers/runc/libcontainer"
    16  	"github.com/opencontainers/runc/libcontainer/utils"
    17  )
    18  
    19  type tty struct {
    20  	epoller   *console.Epoller
    21  	console   *console.EpollConsole
    22  	stdin     console.Console
    23  	closers   []io.Closer
    24  	postStart []io.Closer
    25  	wg        sync.WaitGroup
    26  	consoleC  chan error
    27  }
    28  
    29  func setupIO(process *libcontainer.Process, container libcontainer.Container, createTTY, detach bool, sockpath string) (t *tty, err error) {
    30  	defer func() {
    31  		if err != nil {
    32  			err = fmt.Errorf("setup process IO: %s", err)
    33  		}
    34  	}()
    35  
    36  	rootuid, err := container.Config().HostRootUID()
    37  	if err != nil {
    38  		return
    39  	}
    40  	rootgid, err := container.Config().HostRootGID()
    41  	if err != nil {
    42  		return
    43  	}
    44  	if createTTY {
    45  		process.Stdin = nil
    46  		process.Stdout = nil
    47  		process.Stderr = nil
    48  		t = &tty{}
    49  		if !detach {
    50  			parent, child, err := utils.NewSockPair("console")
    51  			if err != nil {
    52  				return nil, err
    53  			}
    54  			process.ConsoleSocket = child
    55  			t.postStart = append(t.postStart, parent, child)
    56  			t.consoleC = make(chan error, 1)
    57  			go func() {
    58  				if err := t.recvtty(process, parent); err != nil {
    59  					t.consoleC <- err
    60  				}
    61  				t.consoleC <- nil
    62  			}()
    63  		} else {
    64  			// the caller of runc will handle receiving the console master
    65  			conn, err := net.Dial("unix", sockpath)
    66  			if err != nil {
    67  				return nil, err
    68  			}
    69  			uc, ok := conn.(*net.UnixConn)
    70  			if !ok {
    71  				return nil, fmt.Errorf("casting to UnixConn failed")
    72  			}
    73  			t.postStart = append(t.postStart, uc)
    74  			socket, err := uc.File()
    75  			if err != nil {
    76  				return nil, err
    77  			}
    78  			t.postStart = append(t.postStart, socket)
    79  			process.ConsoleSocket = socket
    80  		}
    81  		return t, nil
    82  	}
    83  	// when runc will detach the caller provides the stdio to runc via runc's 0,1,2
    84  	// and the container's process inherits runc's stdio.
    85  	if detach {
    86  		if err := inheritStdio(process); err != nil {
    87  			return nil, err
    88  		}
    89  		return &tty{}, nil
    90  	}
    91  	return setupProcessPipes(process, rootuid, rootgid)
    92  }
    93  
    94  // setup pipes for the process so that advanced features like c/r are able to easily checkpoint
    95  // and restore the process's IO without depending on a host specific path or device
    96  func setupProcessPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, error) {
    97  	i, err := p.InitializeIO(rootuid, rootgid)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	t := &tty{
   102  		closers: []io.Closer{
   103  			i.Stdin,
   104  			i.Stdout,
   105  			i.Stderr,
   106  		},
   107  	}
   108  	// add the process's io to the post start closers if they support close
   109  	for _, cc := range []interface{}{
   110  		p.Stdin,
   111  		p.Stdout,
   112  		p.Stderr,
   113  	} {
   114  		if c, ok := cc.(io.Closer); ok {
   115  			t.postStart = append(t.postStart, c)
   116  		}
   117  	}
   118  	go func() {
   119  		io.Copy(i.Stdin, os.Stdin)
   120  		i.Stdin.Close()
   121  	}()
   122  	t.wg.Add(2)
   123  	go t.copyIO(os.Stdout, i.Stdout)
   124  	go t.copyIO(os.Stderr, i.Stderr)
   125  	return t, nil
   126  }
   127  
   128  func inheritStdio(process *libcontainer.Process) error {
   129  	process.Stdin = os.Stdin
   130  	process.Stdout = os.Stdout
   131  	process.Stderr = os.Stderr
   132  	return nil
   133  }
   134  
   135  func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
   136  	defer t.wg.Done()
   137  	io.Copy(w, r)
   138  	r.Close()
   139  }
   140  
   141  func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) error {
   142  	f, err := utils.RecvFd(socket)
   143  	if err != nil {
   144  		return err
   145  	}
   146  	cons, err := console.ConsoleFromFile(f)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	console.ClearONLCR(cons.Fd())
   151  	epoller, err := console.NewEpoller()
   152  	if err != nil {
   153  		return err
   154  	}
   155  	epollConsole, err := epoller.Add(cons)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	go epoller.Wait()
   160  	go io.Copy(epollConsole, os.Stdin)
   161  	t.wg.Add(1)
   162  	go t.copyIO(os.Stdout, epollConsole)
   163  
   164  	// set raw mode to stdin and also handle interrupt
   165  	stdin, err := console.ConsoleFromFile(os.Stdin)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	if err := stdin.SetRaw(); err != nil {
   170  		return fmt.Errorf("failed to set the terminal from the stdin: %v", err)
   171  	}
   172  	go handleInterrupt(stdin)
   173  
   174  	t.epoller = epoller
   175  	t.stdin = stdin
   176  	t.console = epollConsole
   177  	t.closers = []io.Closer{epollConsole}
   178  	return nil
   179  }
   180  
   181  func handleInterrupt(c console.Console) {
   182  	sigchan := make(chan os.Signal, 1)
   183  	signal.Notify(sigchan, os.Interrupt)
   184  	<-sigchan
   185  	c.Reset()
   186  	os.Exit(0)
   187  }
   188  
   189  func (t *tty) waitConsole() error {
   190  	if t.consoleC != nil {
   191  		return <-t.consoleC
   192  	}
   193  	return nil
   194  }
   195  
   196  // ClosePostStart closes any fds that are provided to the container and dup2'd
   197  // so that we no longer have copy in our process.
   198  func (t *tty) ClosePostStart() error {
   199  	for _, c := range t.postStart {
   200  		c.Close()
   201  	}
   202  	return nil
   203  }
   204  
   205  // Close closes all open fds for the tty and/or restores the orignal
   206  // stdin state to what it was prior to the container execution
   207  func (t *tty) Close() error {
   208  	// ensure that our side of the fds are always closed
   209  	for _, c := range t.postStart {
   210  		c.Close()
   211  	}
   212  	// the process is gone at this point, shutting down the console if we have
   213  	// one and wait for all IO to be finished
   214  	if t.console != nil && t.epoller != nil {
   215  		t.console.Shutdown(t.epoller.CloseConsole)
   216  	}
   217  	t.wg.Wait()
   218  	for _, c := range t.closers {
   219  		c.Close()
   220  	}
   221  	if t.stdin != nil {
   222  		t.stdin.Reset()
   223  	}
   224  	return nil
   225  }
   226  
   227  func (t *tty) resize() error {
   228  	if t.console == nil {
   229  		return nil
   230  	}
   231  	return t.console.ResizeFrom(console.Current())
   232  }