Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanbagabas committed Sep 21, 2023
1 parent b34e00b commit 38b63e0
Show file tree
Hide file tree
Showing 44 changed files with 3,091 additions and 730 deletions.
File renamed without changes.
131 changes: 131 additions & 0 deletions _old/pty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package pty

import (
"io"

"golang.org/x/xerrors"
)

// ErrClosed is returned when a PTY is used after it has been closed.
var ErrClosed = xerrors.New("pty: closed")

// PTYCmd is an interface for interacting with a pseudo-TTY where we control
// only one end, and the other end has been passed to a running os.Process.
// nolint:revive
type PTYCmd interface {
io.Closer

// Resize sets the size of the PTY.
Resize(width int, height int) error

// OutputReader returns an io.Reader for reading the output from the process
// controlled by the pseudo-TTY
OutputReader() io.Reader

// InputWriter returns an io.Writer for writing into to the process
// controlled by the pseudo-TTY
InputWriter() io.Writer
}

// PTY is a minimal interface for interacting with pseudo-TTY where this
// process retains access to _both_ ends of the pseudo-TTY (i.e. `ptm` & `pts`
// on Linux).
type PTY interface {
io.Closer

// Resize sets the size of the PTY.
Resize(width int, height int) error

// Name of the TTY. Example on Linux would be "/dev/pts/1".
Name() string

// Output handles TTY output.
//
// cmd.SetOutput(pty.Output()) would be used to specify a command
// uses the output stream for writing.
//
// The same stream could be read to validate output.
Output() io.ReadWriter

// Input handles TTY input.
//
// cmd.SetInput(pty.Input()) would be used to specify a command
// uses the PTY input for reading.
//
// The same stream would be used to provide user input: pty.Input().Write(...)
Input() io.ReadWriter
}

// Process represents a process running in a PTY. We need to trigger special processing on the PTY
// on process completion, meaning that we will have goroutines calling Wait() on the process. Since
// the caller will also typically wait for the process, and it is not safe for multiple goroutines
// to Wait() on a process, this abstraction provides a goroutine-safe interface for interacting with
// the process.
type Process interface {
// Wait for the command to complete. Returned error is as for exec.Cmd.Wait()
Wait() error

// Kill the command process. Returned error is as for os.Process.Kill()
Kill() error
}

// WithFlags represents a PTY whose flags can be inspected, in particular
// to determine whether local echo is enabled.
type WithFlags interface {
PTY

// EchoEnabled determines whether local echo is currently enabled for this terminal.
EchoEnabled() (bool, error)
}

// Controllable represents a PTY that can be controlled via the syscall.RawConn
// interface.
type Controllable interface {
PTY

// ControlPTY allows the caller to control the PTY via the syscall.RawConn interface.
ControlPTY(func(uintptr) error) error

// ControlTTY allows the caller to control the TTY via the syscall.RawConn interface.
ControlTTY(func(uintptr) error) error
}

// Options represents a an option for a PTY.
type Option func(*ptyOptions)

type ptyOptions struct {
setSize bool

height int
width int
}

// WithSize sets the size of the PTY.
func WithSize(width int, height int) Option {
return func(opts *ptyOptions) {
opts.setSize = true
opts.height = height
opts.width = width
}
}

// New constructs a new Pty.
func New(opts ...Option) (PTY, error) {
return newPty(opts...)
}

// readWriter is an implementation of io.ReadWriter that wraps two separate
// underlying file descriptors, one for reading and one for writing, and allows
// them to be accessed separately.
type readWriter struct {
Reader io.Reader
Writer io.Writer
}

func (rw readWriter) Read(p []byte) (int, error) {
return rw.Reader.Read(p)
}

func (rw readWriter) Write(p []byte) (int, error) {
return rw.Writer.Write(p)
}
File renamed without changes.
203 changes: 203 additions & 0 deletions _old/pty_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
//go:build !windows

package pty

import (
"io"
"io/fs"
"os"
"os/exec"
"runtime"
"sync"

"github.com/creack/pty"
"github.com/u-root/u-root/pkg/termios"
"golang.org/x/sys/unix"
"golang.org/x/xerrors"
)

func newPty(opt ...Option) (retPTY *otherPty, err error) {
var opts ptyOptions
for _, o := range opt {
o(&opts)
}

ptyFile, ttyFile, err := pty.Open()
if err != nil {
return nil, err
}
opty := &otherPty{
pty: ptyFile,
tty: ttyFile,
opts: opts,
name: ttyFile.Name(),
}
defer func() {
if err != nil {
_ = opty.Close()
}
}()

if opts.setSize {
if err := opty.Resize(opts.width, opts.height); err != nil {
return nil, err
}
}

return opty, err
}

type otherPty struct {
mutex sync.Mutex
closed bool
err error
pty, tty *os.File
opts ptyOptions
name string
}

func (p *otherPty) ControlPTY(fn func(fd uintptr) error) error {
return p.control(p.pty, fn)
}

func (p *otherPty) ControlTTY(fn func(fd uintptr) error) error {
return p.control(p.tty, fn)
}

func (p *otherPty) control(tty *os.File, fn func(fd uintptr) error) (err error) {
defer func() {
// Always echo the close error for closed ptys.
p.mutex.Lock()
defer p.mutex.Unlock()
if p.closed {
err = p.err
}
}()

rawConn, err := tty.SyscallConn()
if err != nil {
return err
}

var ctlErr error
err = rawConn.Control(func(fd uintptr) {
ctlErr = fn(fd)
})
switch {
case err != nil:
return err
case ctlErr != nil:
return ctlErr
default:
return nil
}
}

func (p *otherPty) Name() string {
return p.name
}

func (p *otherPty) Input() io.ReadWriter {
return readWriter{
Reader: p.tty,
Writer: p.pty,
}
}

func (p *otherPty) InputWriter() io.Writer {
return p.pty
}

func (p *otherPty) Output() io.ReadWriter {
return readWriter{
Reader: &ptmReader{p.pty},
Writer: p.tty,
}
}

func (p *otherPty) OutputReader() io.Reader {
return &ptmReader{p.pty}
}

func (p *otherPty) Resize(width int, height int) error {
return p.control(p.pty, func(fd uintptr) error {
return termios.SetWinSize(fd, &termios.Winsize{
Winsize: unix.Winsize{
Row: uint16(height),
Col: uint16(width),
},
})
})
}

func (p *otherPty) Close() error {
p.mutex.Lock()
defer p.mutex.Unlock()

if p.closed {
return p.err
}
p.closed = true

err := p.pty.Close()
// tty is closed & unset if we Start() a new process
if p.tty != nil {
err2 := p.tty.Close()
if err == nil {
err = err2
}
}

if err != nil {
p.err = err
} else {
p.err = ErrClosed
}

return err
}

type otherProcess struct {
pty *os.File
cmd *exec.Cmd

// cmdDone protects access to cmdErr: anything reading cmdErr should read from cmdDone first.
cmdDone chan any
cmdErr error
}

func (p *otherProcess) Wait() error {
<-p.cmdDone
return p.cmdErr
}

func (p *otherProcess) Kill() error {
return p.cmd.Process.Kill()
}

func (p *otherProcess) waitInternal() {
// The GC can garbage collect the TTY FD before the command
// has finished running. See:
// https://github.com/creack/pty/issues/127#issuecomment-932764012
p.cmdErr = p.cmd.Wait()
runtime.KeepAlive(p.pty)
close(p.cmdDone)
}

// ptmReader wraps a reference to the ptm side of a pseudo-TTY for portability
type ptmReader struct {
ptm io.Reader
}

func (r *ptmReader) Read(p []byte) (n int, err error) {
n, err = r.ptm.Read(p)
// output from the ptm will hit a PathErr when the process hangs up the
// other side (typically when the process exits, but could be earlier). For
// portability, and to fit with our use of io.Copy() to copy from the PTY,
// we want to translate this error into io.EOF
pathErr := &fs.PathError{}
if xerrors.As(err, &pathErr) {
return n, io.EOF
}
return n, err
}
Loading

0 comments on commit 38b63e0

Please sign in to comment.