github.com/insomniacslk/u-root@v0.0.0-20200717035308-96b791510d76/pkg/pty/pty.go (about)

     1  // Copyright 2015-2017 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package pty provides basic pty support.
     6  // It implments much of exec.Command
     7  // but the Start() function starts two goroutines that relay the
     8  // data for Stdin, Stdout, and Stdout such that proper kernel pty
     9  // processing is done. We did not simply embed an exec.Command
    10  // as we can no guarantee that we can implement all aspects of it
    11  // for all time to come.
    12  package pty
    13  
    14  import (
    15  	"fmt"
    16  	"io"
    17  	"os"
    18  	"os/exec"
    19  	"syscall"
    20  
    21  	"github.com/u-root/u-root/pkg/termios"
    22  )
    23  
    24  type Pty struct {
    25  	C        *exec.Cmd
    26  	Ptm      *os.File
    27  	Pts      *os.File
    28  	Sname    string
    29  	Kid      int
    30  	TTY      *termios.TTYIO
    31  	WS       *termios.Winsize
    32  	Restorer *termios.Termios
    33  }
    34  
    35  func (p *Pty) Command(cmd string, args ...string) {
    36  	p.C = exec.Command(cmd, args...)
    37  	p.C.Stdin, p.C.Stdout, p.C.Stderr = p.Pts, p.Pts, p.Pts
    38  	p.C.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
    39  }
    40  
    41  func (p *Pty) Start() error {
    42  	tty, err := termios.New()
    43  	if err != nil {
    44  		return err
    45  	}
    46  
    47  	if p.WS, err = tty.GetWinSize(); err != nil {
    48  		return err
    49  	}
    50  
    51  	if p.Restorer, err = tty.Raw(); err != nil {
    52  		return err
    53  	}
    54  
    55  	if err := p.C.Start(); err != nil {
    56  		tty.Set(p.Restorer)
    57  		return err
    58  	}
    59  	p.Kid = p.C.Process.Pid
    60  
    61  	// We make a good faith effort to set the
    62  	// WinSize of the Pts, but it's not a deal breaker
    63  	// if we can't do it.
    64  	if err := termios.SetWinSize(p.Pts.Fd(), p.WS); err != nil {
    65  		fmt.Fprintf(p.C.Stderr, "SetWinSize of Pts: %v", err)
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  func (p *Pty) Run() error {
    72  	if err := p.Start(); err != nil {
    73  		return err
    74  	}
    75  
    76  	go io.Copy(p.TTY, p.Ptm)
    77  
    78  	// The 1 byte for IO may seem weird, but ptys are for human interaction
    79  	// and, let's face it, we don't all type fast.
    80  	go func() {
    81  		var data [1]byte
    82  		for {
    83  			if _, err := p.TTY.Read(data[:]); err != nil {
    84  				return
    85  			}
    86  			// Log the error but it may be transient.
    87  			if _, err := p.Ptm.Write(data[:]); err != nil {
    88  				fmt.Fprintf(p.C.Stderr, "Error writing input to ptm: %v: give up\n", err)
    89  			}
    90  		}
    91  	}()
    92  	return p.Wait()
    93  }
    94  
    95  func (p *Pty) Wait() error {
    96  	defer p.TTY.Set(p.Restorer)
    97  	return p.C.Wait()
    98  }