github.com/xyproto/u-root@v6.0.1-0.20200302025726-5528e0c77a3c+incompatible/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  	"golang.org/x/sys/unix"
    23  )
    24  
    25  type Pty struct {
    26  	C        *exec.Cmd
    27  	Ptm      *os.File
    28  	Pts      *os.File
    29  	Sname    string
    30  	Kid      int
    31  	TTY      *termios.TTY
    32  	WS       *unix.Winsize
    33  	Restorer *unix.Termios
    34  }
    35  
    36  func (p *Pty) Command(cmd string, args ...string) {
    37  	p.C = exec.Command(cmd, args...)
    38  	p.C.Stdin, p.C.Stdout, p.C.Stderr = p.Pts, p.Pts, p.Pts
    39  	p.C.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
    40  }
    41  
    42  func (p *Pty) Start() error {
    43  	tty, err := termios.New()
    44  	if err != nil {
    45  		return err
    46  	}
    47  
    48  	if p.WS, err = tty.GetWinSize(); err != nil {
    49  		return err
    50  	}
    51  
    52  	if p.Restorer, err = tty.Raw(); err != nil {
    53  		return err
    54  	}
    55  
    56  	if err := p.C.Start(); err != nil {
    57  		tty.Set(p.Restorer)
    58  		return err
    59  	}
    60  	p.Kid = p.C.Process.Pid
    61  
    62  	// We make a good faith effort to set the
    63  	// WinSize of the Pts, but it's not a deal breaker
    64  	// if we can't do it.
    65  	if err := termios.SetWinSize(p.Pts.Fd(), p.WS); err != nil {
    66  		fmt.Fprintf(p.C.Stderr, "SetWinSize of Pts: %v", err)
    67  	}
    68  
    69  	return nil
    70  }
    71  
    72  func (p *Pty) Run() error {
    73  	if err := p.Start(); err != nil {
    74  		return err
    75  	}
    76  
    77  	go io.Copy(p.TTY, p.Ptm)
    78  
    79  	// The 1 byte for IO may seem weird, but ptys are for human interaction
    80  	// and, let's face it, we don't all type fast.
    81  	go func() {
    82  		var data [1]byte
    83  		for {
    84  			if _, err := p.TTY.Read(data[:]); err != nil {
    85  				return
    86  			}
    87  			// Log the error but it may be transient.
    88  			if _, err := p.Ptm.Write(data[:]); err != nil {
    89  				fmt.Fprintf(p.C.Stderr, "Error writing input to ptm: %v: give up\n", err)
    90  			}
    91  		}
    92  	}()
    93  	return p.Wait()
    94  }
    95  
    96  func (p *Pty) Wait() error {
    97  	defer p.TTY.Set(p.Restorer)
    98  	return p.C.Wait()
    99  }