github.com/ratrocket/u-root@v0.0.0-20180201221235-1cf9f48ee2cf/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  
    20  	"github.com/u-root/u-root/pkg/termios"
    21  	"golang.org/x/sys/unix"
    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.TTY
    31  	WS       *unix.Winsize
    32  	Restorer *unix.Termios
    33  }
    34  
    35  func (p *Pty) Start() error {
    36  	tty, err := termios.New()
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	if p.WS, err = tty.GetWinSize(); err != nil {
    42  		return err
    43  	}
    44  
    45  	if p.Restorer, err = tty.Raw(); err != nil {
    46  		return err
    47  	}
    48  
    49  	if err := p.C.Start(); err != nil {
    50  		tty.Set(p.Restorer)
    51  		return err
    52  	}
    53  	p.Kid = p.C.Process.Pid
    54  
    55  	// We make a good faith effort to set the
    56  	// WinSize of the Pts, but it's not a deal breaker
    57  	// if we can't do it.
    58  	if err := termios.SetWinSize(p.Pts.Fd(), p.WS); err != nil {
    59  		fmt.Fprintf(p.C.Stderr, "SetWinSize of Pts: %v", err)
    60  	}
    61  
    62  	go io.Copy(p.C.Stdout, p.Ptm)
    63  
    64  	// The 1 byte for IO may seem weird, but ptys are for human interacxtion
    65  	// and, let's face it, we don't all type fast.
    66  	go func() {
    67  		var data [1]byte
    68  		for {
    69  			if _, err := p.C.Stdin.Read(data[:]); err != nil {
    70  				return
    71  			}
    72  			// TODO: should we really echo this? Or pass it to the
    73  			// shell? I think we need to echo it but not pass it
    74  			// on.
    75  			if data[0] == '\r' {
    76  				if _, err := p.C.Stdout.Write(data[:]); err != nil {
    77  					fmt.Fprintf(p.C.Stderr, "error on echo %v: %v", data, err)
    78  				}
    79  				data[0] = '\n'
    80  			}
    81  			// Log the error but it may be transient.
    82  			if _, err := p.Ptm.Write(data[:]); err != nil {
    83  				fmt.Fprintf(p.C.Stderr, "Error writing input to ptm: %v: give up\n", err)
    84  			}
    85  		}
    86  	}()
    87  	return nil
    88  }
    89  
    90  func (p *Pty) Run() error {
    91  	if err := p.Start(); err != nil {
    92  		return err
    93  	}
    94  
    95  	return p.Wait()
    96  }
    97  
    98  func (p *Pty) Wait() error {
    99  	defer p.TTY.Set(p.Restorer)
   100  	return p.C.Wait()
   101  }