2024-07-16 03:00:10 +02:00
|
|
|
package shellexec
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
2024-09-05 09:21:08 +02:00
|
|
|
"os"
|
2024-07-16 03:00:10 +02:00
|
|
|
"os/exec"
|
2024-10-25 01:07:18 +02:00
|
|
|
"runtime"
|
2024-12-04 23:16:50 +01:00
|
|
|
"sync"
|
2024-10-25 01:07:18 +02:00
|
|
|
"syscall"
|
2024-09-05 09:21:08 +02:00
|
|
|
"time"
|
2024-07-16 03:00:10 +02:00
|
|
|
|
2024-08-10 03:49:35 +02:00
|
|
|
"github.com/creack/pty"
|
2024-11-21 03:05:13 +01:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/panichandler"
|
2024-10-24 07:43:17 +02:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/wsl"
|
2024-07-16 03:00:10 +02:00
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ConnInterface interface {
|
|
|
|
Kill()
|
2024-09-05 09:21:08 +02:00
|
|
|
KillGraceful(time.Duration)
|
2024-07-16 03:00:10 +02:00
|
|
|
Wait() error
|
|
|
|
Start() error
|
2024-12-04 23:16:50 +01:00
|
|
|
ExitCode() int
|
2024-07-16 03:00:10 +02:00
|
|
|
StdinPipe() (io.WriteCloser, error)
|
|
|
|
StdoutPipe() (io.ReadCloser, error)
|
|
|
|
StderrPipe() (io.ReadCloser, error)
|
2024-07-19 01:56:00 +02:00
|
|
|
SetSize(w int, h int) error
|
2024-08-10 03:49:35 +02:00
|
|
|
pty.Pty
|
2024-07-16 03:00:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type CmdWrap struct {
|
2024-12-04 23:16:50 +01:00
|
|
|
Cmd *exec.Cmd
|
|
|
|
WaitOnce *sync.Once
|
|
|
|
WaitErr error
|
2024-08-10 03:49:35 +02:00
|
|
|
pty.Pty
|
2024-07-16 03:00:10 +02:00
|
|
|
}
|
|
|
|
|
2024-12-04 23:16:50 +01:00
|
|
|
func MakeCmdWrap(cmd *exec.Cmd, cmdPty pty.Pty) CmdWrap {
|
|
|
|
return CmdWrap{
|
|
|
|
Cmd: cmd,
|
|
|
|
WaitOnce: &sync.Once{},
|
|
|
|
Pty: cmdPty,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-16 03:00:10 +02:00
|
|
|
func (cw CmdWrap) Kill() {
|
|
|
|
cw.Cmd.Process.Kill()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cw CmdWrap) Wait() error {
|
2024-12-04 23:16:50 +01:00
|
|
|
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()
|
2024-07-16 03:00:10 +02:00
|
|
|
}
|
|
|
|
|
2024-09-05 09:21:08 +02:00
|
|
|
func (cw CmdWrap) KillGraceful(timeout time.Duration) {
|
|
|
|
if cw.Cmd.Process == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if cw.Cmd.ProcessState != nil && cw.Cmd.ProcessState.Exited() {
|
|
|
|
return
|
|
|
|
}
|
2024-10-25 01:07:18 +02:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
cw.Cmd.Process.Signal(os.Interrupt)
|
|
|
|
} else {
|
|
|
|
cw.Cmd.Process.Signal(syscall.SIGTERM)
|
|
|
|
}
|
2024-09-05 09:21:08 +02:00
|
|
|
go func() {
|
2024-11-21 03:05:13 +01:00
|
|
|
defer panichandler.PanicHandler("KillGraceful:Kill")
|
2024-09-05 09:21:08 +02:00
|
|
|
time.Sleep(timeout)
|
|
|
|
if cw.Cmd.ProcessState == nil || !cw.Cmd.ProcessState.Exited() {
|
|
|
|
cw.Cmd.Process.Kill() // force kill if it is already not exited
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2024-07-16 03:00:10 +02:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2024-07-19 01:56:00 +02:00
|
|
|
func (cw CmdWrap) SetSize(w int, h int) error {
|
2024-08-10 03:49:35 +02:00
|
|
|
err := pty.Setsize(cw.Pty, &pty.Winsize{Rows: uint16(w), Cols: uint16(h)})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-07-19 01:56:00 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-16 03:00:10 +02:00
|
|
|
type SessionWrap struct {
|
|
|
|
Session *ssh.Session
|
|
|
|
StartCmd string
|
2024-08-10 03:49:35 +02:00
|
|
|
Tty pty.Tty
|
2024-12-04 23:16:50 +01:00
|
|
|
WaitOnce *sync.Once
|
|
|
|
WaitErr error
|
2024-08-10 03:49:35 +02:00
|
|
|
pty.Pty
|
2024-07-16 03:00:10 +02:00
|
|
|
}
|
|
|
|
|
2024-12-04 23:16:50 +01:00
|
|
|
func MakeSessionWrap(session *ssh.Session, startCmd string, sessionPty pty.Pty) SessionWrap {
|
|
|
|
return SessionWrap{
|
|
|
|
Session: session,
|
|
|
|
StartCmd: startCmd,
|
|
|
|
Tty: sessionPty,
|
|
|
|
WaitOnce: &sync.Once{},
|
|
|
|
Pty: sessionPty,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-16 03:00:10 +02:00
|
|
|
func (sw SessionWrap) Kill() {
|
|
|
|
sw.Tty.Close()
|
|
|
|
sw.Session.Close()
|
|
|
|
}
|
|
|
|
|
2024-09-05 09:21:08 +02:00
|
|
|
func (sw SessionWrap) KillGraceful(timeout time.Duration) {
|
|
|
|
sw.Kill()
|
|
|
|
}
|
|
|
|
|
2024-12-04 23:16:50 +01:00
|
|
|
func (sw SessionWrap) ExitCode() int {
|
|
|
|
waitErr := sw.WaitErr
|
|
|
|
if waitErr == nil {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
return ExitCodeFromWaitErr(waitErr)
|
|
|
|
}
|
|
|
|
|
2024-07-16 03:00:10 +02:00
|
|
|
func (sw SessionWrap) Wait() error {
|
2024-12-04 23:16:50 +01:00
|
|
|
sw.WaitOnce.Do(func() {
|
|
|
|
sw.WaitErr = sw.Session.Wait()
|
|
|
|
})
|
|
|
|
return sw.WaitErr
|
2024-07-16 03:00:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2024-07-19 01:56:00 +02:00
|
|
|
|
|
|
|
func (sw SessionWrap) SetSize(h int, w int) error {
|
|
|
|
return sw.Session.WindowChange(h, w)
|
|
|
|
}
|
2024-10-24 07:43:17 +02:00
|
|
|
|
|
|
|
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() {
|
2024-11-21 03:05:13 +01:00
|
|
|
defer panichandler.PanicHandler("KillGraceful-wsl:Kill")
|
2024-10-24 07:43:17 +02:00
|
|
|
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
|
|
|
|
}
|