waveterm/pkg/shellexec/conninterface.go
Mike Sawka 72f36a9639
wsh run (#1376)
implements `wsh run` command.  lots of fixes (and new options) for command blocks.  cleans up the UX/UI for command blocks.  lots of bug fixes for blockcontrollers.  other minor bug fixes.

also makes editor:* vars into settings override atoms.
2024-12-04 14:16:50 -08:00

228 lines
4.4 KiB
Go

package shellexec
import (
"io"
"os"
"os/exec"
"runtime"
"sync"
"syscall"
"time"
"github.com/creack/pty"
"github.com/wavetermdev/waveterm/pkg/panichandler"
"github.com/wavetermdev/waveterm/pkg/wsl"
"golang.org/x/crypto/ssh"
)
type ConnInterface interface {
Kill()
KillGraceful(time.Duration)
Wait() error
Start() error
ExitCode() int
StdinPipe() (io.WriteCloser, error)
StdoutPipe() (io.ReadCloser, error)
StderrPipe() (io.ReadCloser, error)
SetSize(w int, h int) error
pty.Pty
}
type CmdWrap struct {
Cmd *exec.Cmd
WaitOnce *sync.Once
WaitErr error
pty.Pty
}
func MakeCmdWrap(cmd *exec.Cmd, cmdPty pty.Pty) CmdWrap {
return CmdWrap{
Cmd: cmd,
WaitOnce: &sync.Once{},
Pty: cmdPty,
}
}
func (cw CmdWrap) Kill() {
cw.Cmd.Process.Kill()
}
func (cw CmdWrap) Wait() error {
cw.WaitOnce.Do(func() {
cw.WaitErr = cw.Cmd.Wait()
})
return cw.WaitErr
}
// only valid once Wait() has returned (or you know Cmd is done)
func (cw CmdWrap) ExitCode() int {
state := cw.Cmd.ProcessState
if state == nil {
return -1
}
return state.ExitCode()
}
func (cw CmdWrap) KillGraceful(timeout time.Duration) {
if cw.Cmd.Process == nil {
return
}
if cw.Cmd.ProcessState != nil && cw.Cmd.ProcessState.Exited() {
return
}
if runtime.GOOS == "windows" {
cw.Cmd.Process.Signal(os.Interrupt)
} else {
cw.Cmd.Process.Signal(syscall.SIGTERM)
}
go func() {
defer panichandler.PanicHandler("KillGraceful:Kill")
time.Sleep(timeout)
if cw.Cmd.ProcessState == nil || !cw.Cmd.ProcessState.Exited() {
cw.Cmd.Process.Kill() // force kill if it is already not exited
}
}()
}
func (cw CmdWrap) Start() error {
defer func() {
for _, extraFile := range cw.Cmd.ExtraFiles {
if extraFile != nil {
extraFile.Close()
}
}
}()
return cw.Cmd.Start()
}
func (cw CmdWrap) StdinPipe() (io.WriteCloser, error) {
return cw.Cmd.StdinPipe()
}
func (cw CmdWrap) StdoutPipe() (io.ReadCloser, error) {
return cw.Cmd.StdoutPipe()
}
func (cw CmdWrap) StderrPipe() (io.ReadCloser, error) {
return cw.Cmd.StderrPipe()
}
func (cw CmdWrap) SetSize(w int, h int) error {
err := pty.Setsize(cw.Pty, &pty.Winsize{Rows: uint16(w), Cols: uint16(h)})
if err != nil {
return err
}
return nil
}
type SessionWrap struct {
Session *ssh.Session
StartCmd string
Tty pty.Tty
WaitOnce *sync.Once
WaitErr error
pty.Pty
}
func MakeSessionWrap(session *ssh.Session, startCmd string, sessionPty pty.Pty) SessionWrap {
return SessionWrap{
Session: session,
StartCmd: startCmd,
Tty: sessionPty,
WaitOnce: &sync.Once{},
Pty: sessionPty,
}
}
func (sw SessionWrap) Kill() {
sw.Tty.Close()
sw.Session.Close()
}
func (sw SessionWrap) KillGraceful(timeout time.Duration) {
sw.Kill()
}
func (sw SessionWrap) ExitCode() int {
waitErr := sw.WaitErr
if waitErr == nil {
return -1
}
return ExitCodeFromWaitErr(waitErr)
}
func (sw SessionWrap) Wait() error {
sw.WaitOnce.Do(func() {
sw.WaitErr = sw.Session.Wait()
})
return sw.WaitErr
}
func (sw SessionWrap) Start() error {
return sw.Session.Start(sw.StartCmd)
}
func (sw SessionWrap) StdinPipe() (io.WriteCloser, error) {
return sw.Session.StdinPipe()
}
func (sw SessionWrap) StdoutPipe() (io.ReadCloser, error) {
stdoutReader, err := sw.Session.StdoutPipe()
if err != nil {
return nil, err
}
return io.NopCloser(stdoutReader), nil
}
func (sw SessionWrap) StderrPipe() (io.ReadCloser, error) {
stderrReader, err := sw.Session.StderrPipe()
if err != nil {
return nil, err
}
return io.NopCloser(stderrReader), nil
}
func (sw SessionWrap) SetSize(h int, w int) error {
return sw.Session.WindowChange(h, w)
}
type WslCmdWrap struct {
*wsl.WslCmd
Tty pty.Tty
pty.Pty
}
func (wcw WslCmdWrap) Kill() {
wcw.Tty.Close()
wcw.Close()
}
func (wcw WslCmdWrap) KillGraceful(timeout time.Duration) {
process := wcw.WslCmd.GetProcess()
if process == nil {
return
}
processState := wcw.WslCmd.GetProcessState()
if processState != nil && processState.Exited() {
return
}
process.Signal(os.Interrupt)
go func() {
defer panichandler.PanicHandler("KillGraceful-wsl:Kill")
time.Sleep(timeout)
process := wcw.WslCmd.GetProcess()
processState := wcw.WslCmd.GetProcessState()
if processState == nil || !processState.Exited() {
process.Kill() // force kill if it is already not exited
}
}()
}
/**
* SetSize does nothing for WslCmdWrap as there
* is no pty to manage.
**/
func (wcw WslCmdWrap) SetSize(w int, h int) error {
return nil
}