mirror of https://github.com/coder/coder.git
166 lines
2.7 KiB
Go
166 lines
2.7 KiB
Go
//go:build !windows
|
|
|
|
package pty
|
|
|
|
import (
|
|
"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,
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
_ = opty.Close()
|
|
}
|
|
}()
|
|
|
|
if opts.sshReq != nil {
|
|
err = opty.control(opty.tty, func(fd uintptr) error {
|
|
return applyTerminalModesToFd(opts.logger, fd, *opts.sshReq)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return opty, nil
|
|
}
|
|
|
|
type otherPty struct {
|
|
mutex sync.Mutex
|
|
closed bool
|
|
err error
|
|
pty, tty *os.File
|
|
opts ptyOptions
|
|
}
|
|
|
|
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.tty.Name()
|
|
}
|
|
|
|
func (p *otherPty) Input() ReadWriter {
|
|
return ReadWriter{
|
|
Reader: p.tty,
|
|
Writer: p.pty,
|
|
}
|
|
}
|
|
|
|
func (p *otherPty) Output() ReadWriter {
|
|
return ReadWriter{
|
|
Reader: p.pty,
|
|
Writer: p.tty,
|
|
}
|
|
}
|
|
|
|
func (p *otherPty) Resize(height uint16, width uint16) error {
|
|
return p.control(p.pty, func(fd uintptr) error {
|
|
return termios.SetWinSize(fd, &termios.Winsize{
|
|
Winsize: unix.Winsize{
|
|
Row: height,
|
|
Col: 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()
|
|
err2 := p.tty.Close()
|
|
if err == nil {
|
|
err = err2
|
|
}
|
|
|
|
if err != nil {
|
|
p.err = err
|
|
} else {
|
|
p.err = xerrors.New("pty: closed")
|
|
}
|
|
|
|
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)
|
|
}
|