2024-05-15 01:53:03 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package shellexec
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2024-10-24 07:43:17 +02:00
|
|
|
"context"
|
2024-05-15 01:53:03 +02:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2024-07-16 03:00:10 +02:00
|
|
|
"log"
|
2024-05-15 01:53:03 +02:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
2024-08-01 08:47:33 +02:00
|
|
|
"path/filepath"
|
2024-08-10 03:49:35 +02:00
|
|
|
"runtime"
|
2024-07-16 03:00:10 +02:00
|
|
|
"strings"
|
2024-06-24 23:34:31 +02:00
|
|
|
"sync"
|
2024-05-15 01:53:03 +02:00
|
|
|
"syscall"
|
2024-09-05 09:21:08 +02:00
|
|
|
"time"
|
2024-05-15 01:53:03 +02:00
|
|
|
|
|
|
|
"github.com/creack/pty"
|
2024-11-21 03:05:13 +01:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/panichandler"
|
2024-09-05 23:25:45 +02:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/remote"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/remote/conncontroller"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
|
2024-09-27 00:34:52 +02:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
|
2024-09-05 23:25:45 +02:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/wavebase"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/wshutil"
|
2024-10-24 07:43:17 +02:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/wsl"
|
2024-05-15 01:53:03 +02:00
|
|
|
)
|
|
|
|
|
2024-09-05 09:21:08 +02:00
|
|
|
const DefaultGracefulKillWait = 400 * time.Millisecond
|
|
|
|
|
2024-06-24 23:34:31 +02:00
|
|
|
type CommandOptsType struct {
|
|
|
|
Interactive bool `json:"interactive,omitempty"`
|
|
|
|
Login bool `json:"login,omitempty"`
|
|
|
|
Cwd string `json:"cwd,omitempty"`
|
|
|
|
Env map[string]string `json:"env,omitempty"`
|
2024-09-27 00:34:52 +02:00
|
|
|
ShellPath string `json:"shellPath,omitempty"`
|
2024-10-01 06:19:07 +02:00
|
|
|
ShellOpts []string `json:"shellOpts,omitempty"`
|
2024-06-24 23:34:31 +02:00
|
|
|
}
|
|
|
|
|
2024-05-15 08:25:21 +02:00
|
|
|
type ShellProc struct {
|
2024-09-05 09:21:08 +02:00
|
|
|
ConnName string
|
2024-07-16 03:00:10 +02:00
|
|
|
Cmd ConnInterface
|
2024-06-24 23:34:31 +02:00
|
|
|
CloseOnce *sync.Once
|
|
|
|
DoneCh chan any // closed after proc.Wait() returns
|
|
|
|
WaitErr error // WaitErr is synchronized by DoneCh (written before DoneCh is closed) and CloseOnce
|
2024-05-15 08:25:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (sp *ShellProc) Close() {
|
2024-09-05 09:21:08 +02:00
|
|
|
sp.Cmd.KillGraceful(DefaultGracefulKillWait)
|
2024-05-15 08:25:21 +02:00
|
|
|
go func() {
|
2024-11-21 03:05:13 +01:00
|
|
|
defer panichandler.PanicHandler("ShellProc.Close")
|
2024-07-16 03:00:10 +02:00
|
|
|
waitErr := sp.Cmd.Wait()
|
2024-06-24 23:34:31 +02:00
|
|
|
sp.SetWaitErrorAndSignalDone(waitErr)
|
2024-08-10 03:49:35 +02:00
|
|
|
|
|
|
|
// windows cannot handle the pty being
|
|
|
|
// closed twice, so we let the pty
|
|
|
|
// close itself instead
|
|
|
|
if runtime.GOOS != "windows" {
|
|
|
|
sp.Cmd.Close()
|
|
|
|
}
|
2024-05-15 08:25:21 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2024-06-24 23:34:31 +02:00
|
|
|
func (sp *ShellProc) SetWaitErrorAndSignalDone(waitErr error) {
|
|
|
|
sp.CloseOnce.Do(func() {
|
|
|
|
sp.WaitErr = waitErr
|
|
|
|
close(sp.DoneCh)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sp *ShellProc) Wait() error {
|
|
|
|
<-sp.DoneCh
|
|
|
|
return sp.WaitErr
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns (done, waitError)
|
|
|
|
func (sp *ShellProc) WaitNB() (bool, error) {
|
|
|
|
select {
|
|
|
|
case <-sp.DoneCh:
|
|
|
|
return true, sp.WaitErr
|
|
|
|
default:
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func ExitCodeFromWaitErr(err error) int {
|
|
|
|
if err == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
|
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
|
|
|
return status.ExitStatus()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkCwd(cwd string) error {
|
|
|
|
if cwd == "" {
|
|
|
|
return fmt.Errorf("cwd is empty")
|
|
|
|
}
|
|
|
|
if _, err := os.Stat(cwd); err != nil {
|
|
|
|
return fmt.Errorf("error statting cwd %q: %w", cwd, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-10 03:49:35 +02:00
|
|
|
type PipePty struct {
|
|
|
|
remoteStdinWrite *os.File
|
|
|
|
remoteStdoutRead *os.File
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pp *PipePty) Fd() uintptr {
|
|
|
|
return pp.remoteStdinWrite.Fd()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pp *PipePty) Name() string {
|
|
|
|
return "pipe-pty"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pp *PipePty) Read(p []byte) (n int, err error) {
|
|
|
|
return pp.remoteStdoutRead.Read(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pp *PipePty) Write(p []byte) (n int, err error) {
|
|
|
|
return pp.remoteStdinWrite.Write(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pp *PipePty) Close() error {
|
|
|
|
err1 := pp.remoteStdinWrite.Close()
|
|
|
|
err2 := pp.remoteStdoutRead.Close()
|
|
|
|
|
|
|
|
if err1 != nil {
|
|
|
|
return err1
|
|
|
|
}
|
|
|
|
return err2
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pp *PipePty) WriteString(s string) (n int, err error) {
|
|
|
|
return pp.Write([]byte(s))
|
|
|
|
}
|
|
|
|
|
2024-10-24 07:43:17 +02:00
|
|
|
func StartWslShellProc(ctx context.Context, termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *wsl.WslConn) (*ShellProc, error) {
|
|
|
|
client := conn.GetClient()
|
|
|
|
shellPath := cmdOpts.ShellPath
|
|
|
|
if shellPath == "" {
|
|
|
|
remoteShellPath, err := wsl.DetectShell(conn.Context, client)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
shellPath = remoteShellPath
|
|
|
|
}
|
|
|
|
var shellOpts []string
|
|
|
|
log.Printf("detected shell: %s", shellPath)
|
|
|
|
|
|
|
|
err := wsl.InstallClientRcFiles(conn.Context, client)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("error installing rc files: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
homeDir := wsl.GetHomeDir(conn.Context, client)
|
|
|
|
shellOpts = append(shellOpts, "~", "-d", client.Name())
|
|
|
|
|
|
|
|
var subShellOpts []string
|
|
|
|
|
|
|
|
if cmdStr == "" {
|
|
|
|
/* transform command in order to inject environment vars */
|
|
|
|
if isBashShell(shellPath) {
|
|
|
|
log.Printf("recognized as bash shell")
|
|
|
|
// add --rcfile
|
|
|
|
// cant set -l or -i with --rcfile
|
|
|
|
subShellOpts = append(subShellOpts, "--rcfile", fmt.Sprintf(`%s/.waveterm/%s/.bashrc`, homeDir, shellutil.BashIntegrationDir))
|
|
|
|
} else if isFishShell(shellPath) {
|
|
|
|
carg := fmt.Sprintf(`"set -x PATH \"%s\"/.waveterm/%s $PATH"`, homeDir, shellutil.WaveHomeBinDir)
|
|
|
|
subShellOpts = append(subShellOpts, "-C", carg)
|
|
|
|
} else if wsl.IsPowershell(shellPath) {
|
|
|
|
// powershell is weird about quoted path executables and requires an ampersand first
|
|
|
|
shellPath = "& " + shellPath
|
|
|
|
subShellOpts = append(subShellOpts, "-ExecutionPolicy", "Bypass", "-NoExit", "-File", homeDir+fmt.Sprintf("/.waveterm/%s/wavepwsh.ps1", shellutil.PwshIntegrationDir))
|
|
|
|
} else {
|
|
|
|
if cmdOpts.Login {
|
|
|
|
subShellOpts = append(subShellOpts, "-l")
|
|
|
|
}
|
|
|
|
if cmdOpts.Interactive {
|
|
|
|
subShellOpts = append(subShellOpts, "-i")
|
|
|
|
}
|
|
|
|
// can't set environment vars this way
|
|
|
|
// will try to do later if possible
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
shellPath = cmdStr
|
|
|
|
if cmdOpts.Login {
|
|
|
|
subShellOpts = append(subShellOpts, "-l")
|
|
|
|
}
|
|
|
|
if cmdOpts.Interactive {
|
|
|
|
subShellOpts = append(subShellOpts, "-i")
|
|
|
|
}
|
|
|
|
subShellOpts = append(subShellOpts, "-c", cmdStr)
|
|
|
|
}
|
|
|
|
|
|
|
|
jwtToken, ok := cmdOpts.Env[wshutil.WaveJwtTokenVarName]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("no jwt token provided to connection")
|
|
|
|
}
|
|
|
|
if remote.IsPowershell(shellPath) {
|
|
|
|
shellOpts = append(shellOpts, "--", fmt.Sprintf(`$env:%s=%s;`, wshutil.WaveJwtTokenVarName, jwtToken))
|
|
|
|
} else {
|
|
|
|
shellOpts = append(shellOpts, "--", fmt.Sprintf(`%s=%s`, wshutil.WaveJwtTokenVarName, jwtToken))
|
|
|
|
}
|
2024-12-17 22:00:55 +01:00
|
|
|
|
|
|
|
if isZshShell(shellPath) {
|
2024-12-19 21:43:22 +01:00
|
|
|
shellOpts = append(shellOpts, fmt.Sprintf(`ZDOTDIR=%s/.waveterm/%s`, homeDir, shellutil.ZshIntegrationDir))
|
2024-12-17 22:00:55 +01:00
|
|
|
}
|
2024-10-24 07:43:17 +02:00
|
|
|
shellOpts = append(shellOpts, shellPath)
|
|
|
|
shellOpts = append(shellOpts, subShellOpts...)
|
|
|
|
log.Printf("full cmd is: %s %s", "wsl.exe", strings.Join(shellOpts, " "))
|
|
|
|
|
|
|
|
ecmd := exec.Command("wsl.exe", shellOpts...)
|
|
|
|
if termSize.Rows == 0 || termSize.Cols == 0 {
|
|
|
|
termSize.Rows = shellutil.DefaultTermRows
|
|
|
|
termSize.Cols = shellutil.DefaultTermCols
|
|
|
|
}
|
|
|
|
if termSize.Rows <= 0 || termSize.Cols <= 0 {
|
|
|
|
return nil, fmt.Errorf("invalid term size: %v", termSize)
|
|
|
|
}
|
|
|
|
cmdPty, err := pty.StartWithSize(ecmd, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-12-04 23:16:50 +01:00
|
|
|
cmdWrap := MakeCmdWrap(ecmd, cmdPty)
|
|
|
|
return &ShellProc{Cmd: cmdWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
|
2024-10-24 07:43:17 +02:00
|
|
|
}
|
|
|
|
|
2024-12-06 19:11:38 +01:00
|
|
|
func StartRemoteShellProcNoWsh(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) {
|
2024-09-05 09:21:08 +02:00
|
|
|
client := conn.GetClient()
|
2024-12-06 19:11:38 +01:00
|
|
|
session, err := client.NewSession()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-11-28 01:52:00 +01:00
|
|
|
|
2024-12-06 19:11:38 +01:00
|
|
|
remoteStdinRead, remoteStdinWriteOurs, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-11-28 01:52:00 +01:00
|
|
|
|
2024-12-06 19:11:38 +01:00
|
|
|
remoteStdoutReadOurs, remoteStdoutWrite, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-11-28 01:52:00 +01:00
|
|
|
|
2024-12-06 19:11:38 +01:00
|
|
|
pipePty := &PipePty{
|
|
|
|
remoteStdinWrite: remoteStdinWriteOurs,
|
|
|
|
remoteStdoutRead: remoteStdoutReadOurs,
|
|
|
|
}
|
|
|
|
if termSize.Rows == 0 || termSize.Cols == 0 {
|
|
|
|
termSize.Rows = shellutil.DefaultTermRows
|
|
|
|
termSize.Cols = shellutil.DefaultTermCols
|
|
|
|
}
|
|
|
|
if termSize.Rows <= 0 || termSize.Cols <= 0 {
|
|
|
|
return nil, fmt.Errorf("invalid term size: %v", termSize)
|
|
|
|
}
|
|
|
|
session.Stdin = remoteStdinRead
|
|
|
|
session.Stdout = remoteStdoutWrite
|
|
|
|
session.Stderr = remoteStdoutWrite
|
2024-11-28 01:52:00 +01:00
|
|
|
|
2024-12-06 19:11:38 +01:00
|
|
|
session.RequestPty("xterm-256color", termSize.Rows, termSize.Cols, nil)
|
|
|
|
sessionWrap := MakeSessionWrap(session, "", pipePty)
|
|
|
|
err = session.Shell()
|
|
|
|
if err != nil {
|
|
|
|
pipePty.Close()
|
|
|
|
return nil, err
|
2024-11-28 01:52:00 +01:00
|
|
|
}
|
2024-12-06 19:11:38 +01:00
|
|
|
return &ShellProc{Cmd: sessionWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) {
|
|
|
|
client := conn.GetClient()
|
2024-09-27 00:34:52 +02:00
|
|
|
shellPath := cmdOpts.ShellPath
|
|
|
|
if shellPath == "" {
|
|
|
|
remoteShellPath, err := remote.DetectShell(client)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
shellPath = remoteShellPath
|
2024-08-16 06:32:08 +02:00
|
|
|
}
|
|
|
|
var shellOpts []string
|
|
|
|
var cmdCombined string
|
|
|
|
log.Printf("detected shell: %s", shellPath)
|
|
|
|
|
2024-09-27 00:34:52 +02:00
|
|
|
err := remote.InstallClientRcFiles(client)
|
2024-08-16 06:32:08 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Printf("error installing rc files: %v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-10-01 06:19:07 +02:00
|
|
|
shellOpts = append(shellOpts, cmdOpts.ShellOpts...)
|
2024-08-16 06:32:08 +02:00
|
|
|
|
|
|
|
homeDir := remote.GetHomeDir(client)
|
2024-07-16 03:00:10 +02:00
|
|
|
|
|
|
|
if cmdStr == "" {
|
2024-08-16 06:32:08 +02:00
|
|
|
/* transform command in order to inject environment vars */
|
|
|
|
if isBashShell(shellPath) {
|
|
|
|
log.Printf("recognized as bash shell")
|
|
|
|
// add --rcfile
|
|
|
|
// cant set -l or -i with --rcfile
|
2024-09-10 07:26:10 +02:00
|
|
|
shellOpts = append(shellOpts, "--rcfile", fmt.Sprintf(`"%s"/.waveterm/%s/.bashrc`, homeDir, shellutil.BashIntegrationDir))
|
2024-09-27 00:34:52 +02:00
|
|
|
} else if isFishShell(shellPath) {
|
|
|
|
carg := fmt.Sprintf(`"set -x PATH \"%s\"/.waveterm/%s $PATH"`, homeDir, shellutil.WaveHomeBinDir)
|
|
|
|
shellOpts = append(shellOpts, "-C", carg)
|
2024-09-04 11:13:00 +02:00
|
|
|
} else if remote.IsPowershell(shellPath) {
|
|
|
|
// powershell is weird about quoted path executables and requires an ampersand first
|
|
|
|
shellPath = "& " + shellPath
|
2024-09-20 21:48:29 +02:00
|
|
|
shellOpts = append(shellOpts, "-ExecutionPolicy", "Bypass", "-NoExit", "-File", homeDir+fmt.Sprintf("/.waveterm/%s/wavepwsh.ps1", shellutil.PwshIntegrationDir))
|
2024-08-16 06:32:08 +02:00
|
|
|
} else {
|
|
|
|
if cmdOpts.Login {
|
|
|
|
shellOpts = append(shellOpts, "-l")
|
2024-09-30 21:36:05 +02:00
|
|
|
} else if cmdOpts.Interactive {
|
2024-08-16 06:32:08 +02:00
|
|
|
shellOpts = append(shellOpts, "-i")
|
|
|
|
}
|
|
|
|
// zdotdir setting moved to after session is created
|
|
|
|
}
|
|
|
|
cmdCombined = fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
|
|
|
|
log.Printf("combined command is: %s", cmdCombined)
|
2024-07-16 03:00:10 +02:00
|
|
|
} else {
|
|
|
|
shellPath = cmdStr
|
2024-08-16 06:32:08 +02:00
|
|
|
shellOpts = append(shellOpts, "-c", cmdStr)
|
|
|
|
cmdCombined = fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
|
2024-08-19 06:26:44 +02:00
|
|
|
log.Printf("combined command is: %s", cmdCombined)
|
2024-07-16 03:00:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
session, err := client.NewSession()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-08-10 03:49:35 +02:00
|
|
|
remoteStdinRead, remoteStdinWriteOurs, err := os.Pipe()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
remoteStdoutReadOurs, remoteStdoutWrite, err := os.Pipe()
|
2024-07-16 03:00:10 +02:00
|
|
|
if err != nil {
|
2024-08-10 03:49:35 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pipePty := &PipePty{
|
|
|
|
remoteStdinWrite: remoteStdinWriteOurs,
|
|
|
|
remoteStdoutRead: remoteStdoutReadOurs,
|
2024-07-16 03:00:10 +02:00
|
|
|
}
|
|
|
|
if termSize.Rows == 0 || termSize.Cols == 0 {
|
|
|
|
termSize.Rows = shellutil.DefaultTermRows
|
|
|
|
termSize.Cols = shellutil.DefaultTermCols
|
|
|
|
}
|
|
|
|
if termSize.Rows <= 0 || termSize.Cols <= 0 {
|
|
|
|
return nil, fmt.Errorf("invalid term size: %v", termSize)
|
|
|
|
}
|
2024-08-10 03:49:35 +02:00
|
|
|
session.Stdin = remoteStdinRead
|
|
|
|
session.Stdout = remoteStdoutWrite
|
|
|
|
session.Stderr = remoteStdoutWrite
|
2024-08-16 06:32:08 +02:00
|
|
|
|
2024-07-23 22:16:53 +02:00
|
|
|
for envKey, envVal := range cmdOpts.Env {
|
|
|
|
// note these might fail depending on server settings, but we still try
|
|
|
|
session.Setenv(envKey, envVal)
|
|
|
|
}
|
|
|
|
|
2024-08-16 06:32:08 +02:00
|
|
|
if isZshShell(shellPath) {
|
2024-09-10 07:26:10 +02:00
|
|
|
cmdCombined = fmt.Sprintf(`ZDOTDIR="%s/.waveterm/%s" %s`, homeDir, shellutil.ZshIntegrationDir, cmdCombined)
|
2024-08-16 06:32:08 +02:00
|
|
|
}
|
|
|
|
|
2024-08-17 20:21:25 +02:00
|
|
|
jwtToken, ok := cmdOpts.Env[wshutil.WaveJwtTokenVarName]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("no jwt token provided to connection")
|
|
|
|
}
|
2024-09-04 11:13:00 +02:00
|
|
|
|
|
|
|
if remote.IsPowershell(shellPath) {
|
|
|
|
cmdCombined = fmt.Sprintf(`$env:%s="%s"; %s`, wshutil.WaveJwtTokenVarName, jwtToken, cmdCombined)
|
|
|
|
} else {
|
|
|
|
cmdCombined = fmt.Sprintf(`%s=%s %s`, wshutil.WaveJwtTokenVarName, jwtToken, cmdCombined)
|
|
|
|
}
|
2024-08-17 20:21:25 +02:00
|
|
|
|
2024-07-16 03:00:10 +02:00
|
|
|
session.RequestPty("xterm-256color", termSize.Rows, termSize.Cols, nil)
|
2024-12-04 23:16:50 +01:00
|
|
|
sessionWrap := MakeSessionWrap(session, cmdCombined, pipePty)
|
2024-07-16 03:00:10 +02:00
|
|
|
err = sessionWrap.Start()
|
|
|
|
if err != nil {
|
2024-08-10 03:49:35 +02:00
|
|
|
pipePty.Close()
|
2024-07-16 03:00:10 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
2024-09-05 09:21:08 +02:00
|
|
|
return &ShellProc{Cmd: sessionWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
|
2024-07-16 03:00:10 +02:00
|
|
|
}
|
|
|
|
|
2024-08-01 08:47:33 +02:00
|
|
|
func isZshShell(shellPath string) bool {
|
|
|
|
// get the base path, and then check contains
|
|
|
|
shellBase := filepath.Base(shellPath)
|
|
|
|
return strings.Contains(shellBase, "zsh")
|
|
|
|
}
|
|
|
|
|
|
|
|
func isBashShell(shellPath string) bool {
|
|
|
|
// get the base path, and then check contains
|
|
|
|
shellBase := filepath.Base(shellPath)
|
|
|
|
return strings.Contains(shellBase, "bash")
|
|
|
|
}
|
|
|
|
|
2024-09-27 00:34:52 +02:00
|
|
|
func isFishShell(shellPath string) bool {
|
|
|
|
// get the base path, and then check contains
|
|
|
|
shellBase := filepath.Base(shellPath)
|
|
|
|
return strings.Contains(shellBase, "fish")
|
|
|
|
}
|
|
|
|
|
2024-08-20 23:56:48 +02:00
|
|
|
func StartShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType) (*ShellProc, error) {
|
2024-08-01 08:47:33 +02:00
|
|
|
shellutil.InitCustomShellStartupFiles()
|
2024-06-24 23:34:31 +02:00
|
|
|
var ecmd *exec.Cmd
|
|
|
|
var shellOpts []string
|
2024-09-27 00:34:52 +02:00
|
|
|
shellPath := cmdOpts.ShellPath
|
|
|
|
if shellPath == "" {
|
|
|
|
shellPath = shellutil.DetectLocalShellPath()
|
|
|
|
}
|
2024-10-01 06:19:07 +02:00
|
|
|
shellOpts = append(shellOpts, cmdOpts.ShellOpts...)
|
2024-06-24 23:34:31 +02:00
|
|
|
if cmdStr == "" {
|
2024-08-01 08:47:33 +02:00
|
|
|
if isBashShell(shellPath) {
|
|
|
|
// add --rcfile
|
|
|
|
// cant set -l or -i with --rcfile
|
|
|
|
shellOpts = append(shellOpts, "--rcfile", shellutil.GetBashRcFileOverride())
|
2024-09-27 00:34:52 +02:00
|
|
|
} else if isFishShell(shellPath) {
|
Update data and config paths to match platform defaults (#1047)
Going forward for new installations, config and data files will be
stored at the platform default paths, as defined by
[env-paths](https://www.npmjs.com/package/env-paths).
For backwards compatibility, if the `~/.waveterm` or `WAVETERM_HOME`
directory exists and contains valid data, it will be used. If this check
fails, then `WAVETERM_DATA_HOME` and `WAVETERM_CONFIG_HOME` will be
used. If these are not defined, then `XDG_DATA_HOME` and
`XDG_CONFIG_HOME` will be used. Finally, if none of these are defined,
the [env-paths](https://www.npmjs.com/package/env-paths) defaults will
be used.
As with the existing app, dev instances will write to `waveterm-dev`
directories, while all others will write to `waveterm`.
2024-10-22 18:26:58 +02:00
|
|
|
wshBinDir := filepath.Join(wavebase.GetWaveDataDir(), shellutil.WaveHomeBinDir)
|
2024-09-27 00:34:52 +02:00
|
|
|
quotedWshBinDir := utilfn.ShellQuote(wshBinDir, false, 300)
|
|
|
|
shellOpts = append(shellOpts, "-C", fmt.Sprintf("set -x PATH %s $PATH", quotedWshBinDir))
|
2024-09-04 11:13:00 +02:00
|
|
|
} else if remote.IsPowershell(shellPath) {
|
2024-09-20 21:48:29 +02:00
|
|
|
shellOpts = append(shellOpts, "-ExecutionPolicy", "Bypass", "-NoExit", "-File", shellutil.GetWavePowershellEnv())
|
2024-09-04 11:13:00 +02:00
|
|
|
} else {
|
2024-08-01 08:47:33 +02:00
|
|
|
if cmdOpts.Login {
|
|
|
|
shellOpts = append(shellOpts, "-l")
|
2024-09-30 21:36:05 +02:00
|
|
|
} else if cmdOpts.Interactive {
|
2024-08-01 08:47:33 +02:00
|
|
|
shellOpts = append(shellOpts, "-i")
|
|
|
|
}
|
|
|
|
}
|
2024-06-24 23:34:31 +02:00
|
|
|
ecmd = exec.Command(shellPath, shellOpts...)
|
2024-08-01 08:47:33 +02:00
|
|
|
ecmd.Env = os.Environ()
|
|
|
|
if isZshShell(shellPath) {
|
|
|
|
shellutil.UpdateCmdEnv(ecmd, map[string]string{"ZDOTDIR": shellutil.GetZshZDotDir()})
|
|
|
|
}
|
2024-06-24 23:34:31 +02:00
|
|
|
} else {
|
|
|
|
shellOpts = append(shellOpts, "-c", cmdStr)
|
|
|
|
ecmd = exec.Command(shellPath, shellOpts...)
|
2024-08-01 08:47:33 +02:00
|
|
|
ecmd.Env = os.Environ()
|
2024-06-24 23:34:31 +02:00
|
|
|
}
|
|
|
|
if cmdOpts.Cwd != "" {
|
|
|
|
ecmd.Dir = cmdOpts.Cwd
|
|
|
|
}
|
|
|
|
if cwdErr := checkCwd(ecmd.Dir); cwdErr != nil {
|
|
|
|
ecmd.Dir = wavebase.GetHomeDir()
|
|
|
|
}
|
2024-08-01 08:47:33 +02:00
|
|
|
envToAdd := shellutil.WaveshellLocalEnvVars(shellutil.DefaultTermType)
|
2024-05-16 09:29:58 +02:00
|
|
|
if os.Getenv("LANG") == "" {
|
|
|
|
envToAdd["LANG"] = wavebase.DetermineLang()
|
|
|
|
}
|
|
|
|
shellutil.UpdateCmdEnv(ecmd, envToAdd)
|
2024-07-23 22:16:53 +02:00
|
|
|
shellutil.UpdateCmdEnv(ecmd, cmdOpts.Env)
|
2024-05-15 08:25:21 +02:00
|
|
|
if termSize.Rows == 0 || termSize.Cols == 0 {
|
|
|
|
termSize.Rows = shellutil.DefaultTermRows
|
|
|
|
termSize.Cols = shellutil.DefaultTermCols
|
|
|
|
}
|
|
|
|
if termSize.Rows <= 0 || termSize.Cols <= 0 {
|
|
|
|
return nil, fmt.Errorf("invalid term size: %v", termSize)
|
|
|
|
}
|
2024-08-10 03:49:35 +02:00
|
|
|
cmdPty, err := pty.StartWithSize(ecmd, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
|
2024-05-15 08:25:21 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-12-04 23:16:50 +01:00
|
|
|
cmdWrap := MakeCmdWrap(ecmd, cmdPty)
|
|
|
|
return &ShellProc{Cmd: cmdWrap, CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
|
2024-05-15 08:25:21 +02:00
|
|
|
}
|
|
|
|
|
2024-08-20 23:56:48 +02:00
|
|
|
func RunSimpleCmdInPty(ecmd *exec.Cmd, termSize waveobj.TermSize) ([]byte, error) {
|
2024-05-15 01:53:03 +02:00
|
|
|
ecmd.Env = os.Environ()
|
2024-08-01 08:47:33 +02:00
|
|
|
shellutil.UpdateCmdEnv(ecmd, shellutil.WaveshellLocalEnvVars(shellutil.DefaultTermType))
|
2024-05-15 07:37:04 +02:00
|
|
|
if termSize.Rows == 0 || termSize.Cols == 0 {
|
|
|
|
termSize.Rows = shellutil.DefaultTermRows
|
|
|
|
termSize.Cols = shellutil.DefaultTermCols
|
|
|
|
}
|
|
|
|
if termSize.Rows <= 0 || termSize.Cols <= 0 {
|
|
|
|
return nil, fmt.Errorf("invalid term size: %v", termSize)
|
|
|
|
}
|
2024-08-10 03:49:35 +02:00
|
|
|
cmdPty, err := pty.StartWithSize(ecmd, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
|
2024-05-15 01:53:03 +02:00
|
|
|
if err != nil {
|
|
|
|
cmdPty.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-08-10 03:49:35 +02:00
|
|
|
if runtime.GOOS != "windows" {
|
|
|
|
defer cmdPty.Close()
|
|
|
|
}
|
2024-05-15 01:53:03 +02:00
|
|
|
ioDone := make(chan bool)
|
|
|
|
var outputBuf bytes.Buffer
|
|
|
|
go func() {
|
2024-11-21 03:05:13 +01:00
|
|
|
panichandler.PanicHandler("RunSimpleCmdInPty:ioCopy")
|
2024-05-15 01:53:03 +02:00
|
|
|
// ignore error (/dev/ptmx has read error when process is done)
|
|
|
|
defer close(ioDone)
|
|
|
|
io.Copy(&outputBuf, cmdPty)
|
|
|
|
}()
|
|
|
|
exitErr := ecmd.Wait()
|
|
|
|
if exitErr != nil {
|
|
|
|
return nil, exitErr
|
|
|
|
}
|
|
|
|
<-ioDone
|
|
|
|
return outputBuf.Bytes(), nil
|
|
|
|
}
|