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 }