waveterm/pkg/shellexec/shellexec.go

627 lines
21 KiB
Go
Raw Normal View History

// Copyright 2025, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package shellexec
import (
"bytes"
"context"
"fmt"
"io"
"log"
"os"
"os/exec"
2024-08-01 08:47:33 +02:00
"path/filepath"
"runtime"
"strings"
2024-06-24 23:34:31 +02:00
"sync"
"syscall"
"time"
"github.com/creack/pty"
"github.com/wavetermdev/waveterm/pkg/blocklogger"
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/conncontroller"
"github.com/wavetermdev/waveterm/pkg/util/pamparse"
2024-09-05 23:25:45 +02:00
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
"github.com/wavetermdev/waveterm/pkg/wavebase"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
2024-09-05 23:25:45 +02:00
"github.com/wavetermdev/waveterm/pkg/wshutil"
"github.com/wavetermdev/waveterm/pkg/wslconn"
)
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"`
ShellPath string `json:"shellPath,omitempty"`
ShellOpts []string `json:"shellOpts,omitempty"`
SwapToken *shellutil.TokenSwapEntry `json:"swapToken,omitempty"`
2024-06-24 23:34:31 +02:00
}
type ShellProc struct {
ConnName string
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
}
func (sp *ShellProc) Close() {
sp.Cmd.KillGraceful(DefaultGracefulKillWait)
go func() {
defer func() {
panichandler.PanicHandler("ShellProc.Close", recover())
}()
waitErr := sp.Cmd.Wait()
2024-06-24 23:34:31 +02:00
sp.SetWaitErrorAndSignalDone(waitErr)
// windows cannot handle the pty being
// closed twice, so we let the pty
// close itself instead
if runtime.GOOS != "windows" {
sp.Cmd.Close()
}
}()
}
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
}
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))
}
func StartWslShellProc(ctx context.Context, termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *wslconn.WslConn) (*ShellProc, error) {
client := conn.GetClient()
conn.Infof(ctx, "WSL-NEWSESSION (StartWslShellProc)")
connRoute := wshutil.MakeConnectionRouteId(conn.GetName())
rpcClient := wshclient.GetBareRpcClient()
remoteInfo, err := wshclient.RemoteGetInfoCommand(rpcClient, &wshrpc.RpcOpts{Route: connRoute, Timeout: 2000})
if err != nil {
return nil, fmt.Errorf("unable to obtain client info: %w", err)
}
log.Printf("client info collected: %+#v", remoteInfo)
var shellPath string
if cmdOpts.ShellPath != "" {
conn.Infof(ctx, "using shell path from command opts: %s\n", cmdOpts.ShellPath)
shellPath = cmdOpts.ShellPath
}
configShellPath := conn.GetConfigShellPath()
if shellPath == "" && configShellPath != "" {
conn.Infof(ctx, "using shell path from config (conn:shellpath): %s\n", configShellPath)
shellPath = configShellPath
}
if shellPath == "" && remoteInfo.Shell != "" {
conn.Infof(ctx, "using shell path detected on remote machine: %s\n", remoteInfo.Shell)
shellPath = remoteInfo.Shell
}
if shellPath == "" {
conn.Infof(ctx, "no shell path detected, using default (/bin/bash)\n")
shellPath = "/bin/bash"
}
var shellOpts []string
var cmdCombined string
log.Printf("detected shell %q for conn %q\n", shellPath, conn.GetName())
err = wshclient.RemoteInstallRcFilesCommand(rpcClient, &wshrpc.RpcOpts{Route: connRoute, Timeout: 2000})
if err != nil {
log.Printf("error installing rc files: %v", err)
return nil, err
}
shellOpts = append(shellOpts, cmdOpts.ShellOpts...)
shellType := shellutil.GetShellTypeFromShellPath(shellPath)
conn.Infof(ctx, "detected shell type: %s\n", shellType)
if cmdStr == "" {
/* transform command in order to inject environment vars */
if shellType == shellutil.ShellType_bash {
// add --rcfile
// cant set -l or -i with --rcfile
bashPath := fmt.Sprintf("~/.waveterm/%s/.bashrc", shellutil.BashIntegrationDir)
shellOpts = append(shellOpts, "--rcfile", bashPath)
} else if shellType == shellutil.ShellType_fish {
if cmdOpts.Login {
shellOpts = append(shellOpts, "-l")
}
// source the wave.fish file
waveFishPath := fmt.Sprintf("~/.waveterm/%s/wave.fish", shellutil.FishIntegrationDir)
carg := fmt.Sprintf(`"source %s"`, waveFishPath)
shellOpts = append(shellOpts, "-C", carg)
} else if shellType == shellutil.ShellType_pwsh {
pwshPath := fmt.Sprintf("~/.waveterm/%s/wavepwsh.ps1", shellutil.PwshIntegrationDir)
// powershell is weird about quoted path executables and requires an ampersand first
shellPath = "& " + shellPath
shellOpts = append(shellOpts, "-ExecutionPolicy", "Bypass", "-NoExit", "-File", pwshPath)
} else {
if cmdOpts.Login {
shellOpts = append(shellOpts, "-l")
}
if cmdOpts.Interactive {
shellOpts = append(shellOpts, "-i")
}
// zdotdir setting moved to after session is created
}
cmdCombined = fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
} else {
// TODO check quoting of cmdStr
shellPath = cmdStr
shellOpts = append(shellOpts, "-c", cmdStr)
cmdCombined = fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
}
conn.Infof(ctx, "starting shell, using command: %s\n", cmdCombined)
conn.Infof(ctx, "WSL-NEWSESSION (StartWslShellProc)\n")
if shellType == shellutil.ShellType_zsh {
zshDir := fmt.Sprintf("~/.waveterm/%s", shellutil.ZshIntegrationDir)
conn.Infof(ctx, "setting ZDOTDIR to %s\n", zshDir)
cmdCombined = fmt.Sprintf(`ZDOTDIR=%s %s`, zshDir, cmdCombined)
}
jwtToken, ok := cmdOpts.Env[wshutil.WaveJwtTokenVarName]
if !ok {
return nil, fmt.Errorf("no jwt token provided to connection")
}
cmdCombined = fmt.Sprintf(`%s=%s %s`, wshutil.WaveJwtTokenVarName, jwtToken, cmdCombined)
log.Printf("full combined command: %s", cmdCombined)
ecmd := exec.Command("wsl.exe", "~", "-d", client.Name(), "--", "sh", "-c", cmdCombined)
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)
}
shellutil.AddTokenSwapEntry(cmdOpts.SwapToken)
cmdPty, err := pty.StartWithSize(ecmd, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
if err != nil {
return nil, err
}
cmdWrap := MakeCmdWrap(ecmd, cmdPty)
return &ShellProc{Cmd: cmdWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
}
2025-01-14 23:09:26 +01:00
func StartRemoteShellProcNoWsh(ctx context.Context, termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) {
client := conn.GetClient()
2025-01-14 23:09:26 +01:00
conn.Infof(ctx, "SSH-NEWSESSION (StartRemoteShellProcNoWsh)")
session, err := client.NewSession()
if err != nil {
return nil, err
}
2024-11-28 01:52:00 +01:00
remoteStdinRead, remoteStdinWriteOurs, err := os.Pipe()
if err != nil {
return nil, err
}
2024-11-28 01:52:00 +01:00
remoteStdoutReadOurs, remoteStdoutWrite, err := os.Pipe()
if err != nil {
return nil, err
}
2024-11-28 01:52:00 +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
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
}
return &ShellProc{Cmd: sessionWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
}
func StartRemoteShellProc(ctx context.Context, logCtx context.Context, termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) {
client := conn.GetClient()
connRoute := wshutil.MakeConnectionRouteId(conn.GetName())
rpcClient := wshclient.GetBareRpcClient()
remoteInfo, err := wshclient.RemoteGetInfoCommand(rpcClient, &wshrpc.RpcOpts{Route: connRoute, Timeout: 2000})
if err != nil {
return nil, fmt.Errorf("unable to obtain client info: %w", err)
}
log.Printf("client info collected: %+#v", remoteInfo)
2025-01-14 23:09:26 +01:00
var shellPath string
if cmdOpts.ShellPath != "" {
conn.Infof(logCtx, "using shell path from command opts: %s\n", cmdOpts.ShellPath)
2025-01-14 23:09:26 +01:00
shellPath = cmdOpts.ShellPath
}
configShellPath := conn.GetConfigShellPath()
if shellPath == "" && configShellPath != "" {
conn.Infof(logCtx, "using shell path from config (conn:shellpath): %s\n", configShellPath)
2025-01-14 23:09:26 +01:00
shellPath = configShellPath
}
if shellPath == "" && remoteInfo.Shell != "" {
conn.Infof(logCtx, "using shell path detected on remote machine: %s\n", remoteInfo.Shell)
shellPath = remoteInfo.Shell
}
2025-01-14 23:09:26 +01:00
if shellPath == "" {
conn.Infof(logCtx, "no shell path detected, using default (/bin/bash)\n")
2025-01-14 23:09:26 +01:00
shellPath = "/bin/bash"
}
var shellOpts []string
var cmdCombined string
2025-01-14 23:09:26 +01:00
log.Printf("detected shell %q for conn %q\n", shellPath, conn.GetName())
2024-10-01 06:19:07 +02:00
shellOpts = append(shellOpts, cmdOpts.ShellOpts...)
shellType := shellutil.GetShellTypeFromShellPath(shellPath)
conn.Infof(logCtx, "detected shell type: %s\n", shellType)
conn.Infof(logCtx, "swaptoken: %s\n", cmdOpts.SwapToken.Token)
if cmdStr == "" {
/* transform command in order to inject environment vars */
if shellType == shellutil.ShellType_bash {
// add --rcfile
// cant set -l or -i with --rcfile
2025-01-14 23:09:26 +01:00
bashPath := fmt.Sprintf("~/.waveterm/%s/.bashrc", shellutil.BashIntegrationDir)
shellOpts = append(shellOpts, "--rcfile", bashPath)
} else if shellType == shellutil.ShellType_fish {
2025-01-14 23:09:26 +01:00
if cmdOpts.Login {
shellOpts = append(shellOpts, "-l")
}
// source the wave.fish file
waveFishPath := fmt.Sprintf("~/.waveterm/%s/wave.fish", shellutil.FishIntegrationDir)
carg := fmt.Sprintf(`"source %s"`, waveFishPath)
2024-09-27 00:34:52 +02:00
shellOpts = append(shellOpts, "-C", carg)
} else if shellType == shellutil.ShellType_pwsh {
2025-01-14 23:09:26 +01:00
pwshPath := fmt.Sprintf("~/.waveterm/%s/wavepwsh.ps1", shellutil.PwshIntegrationDir)
// powershell is weird about quoted path executables and requires an ampersand first
shellPath = "& " + shellPath
shellOpts = append(shellOpts, "-ExecutionPolicy", "Bypass", "-NoExit", "-File", pwshPath)
} else {
if cmdOpts.Login {
shellOpts = append(shellOpts, "-l")
2025-01-14 23:09:26 +01:00
}
if cmdOpts.Interactive {
shellOpts = append(shellOpts, "-i")
}
// zdotdir setting moved to after session is created
}
cmdCombined = fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
} else {
2025-01-14 23:09:26 +01:00
// TODO check quoting of cmdStr
shellPath = cmdStr
shellOpts = append(shellOpts, "-c", cmdStr)
cmdCombined = fmt.Sprintf("%s %s", shellPath, strings.Join(shellOpts, " "))
}
conn.Infof(logCtx, "starting shell, using command: %s\n", cmdCombined)
conn.Infof(logCtx, "SSH-NEWSESSION (StartRemoteShellProc)\n")
session, err := client.NewSession()
if err != nil {
return nil, err
}
remoteStdinRead, remoteStdinWriteOurs, err := os.Pipe()
if err != nil {
return nil, err
}
remoteStdoutReadOurs, remoteStdoutWrite, err := os.Pipe()
if err != nil {
return nil, err
}
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
for envKey, envVal := range cmdOpts.Env {
// note these might fail depending on server settings, but we still try
session.Setenv(envKey, envVal)
}
if shellType == shellutil.ShellType_zsh {
2025-01-14 23:09:26 +01:00
zshDir := fmt.Sprintf("~/.waveterm/%s", shellutil.ZshIntegrationDir)
conn.Infof(logCtx, "setting ZDOTDIR to %s\n", zshDir)
cmdCombined = fmt.Sprintf(`ZDOTDIR=%s %s`, zshDir, cmdCombined)
}
packedToken, err := cmdOpts.SwapToken.PackForClient()
if err != nil {
conn.Infof(logCtx, "error packing swap token: %v", err)
} else {
conn.Debugf(logCtx, "packed swaptoken %s\n", packedToken)
cmdCombined = fmt.Sprintf(`%s=%s %s`, wavebase.WaveSwapTokenVarName, packedToken, cmdCombined)
}
shellutil.AddTokenSwapEntry(cmdOpts.SwapToken)
session.RequestPty("xterm-256color", termSize.Rows, termSize.Cols, nil)
sessionWrap := MakeSessionWrap(session, cmdCombined, pipePty)
err = sessionWrap.Start()
if err != nil {
pipePty.Close()
return nil, err
}
return &ShellProc{Cmd: sessionWrap, ConnName: conn.GetName(), CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
}
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")
}
func StartLocalShellProc(logCtx context.Context, 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()
}
shellType := shellutil.GetShellTypeFromShellPath(shellPath)
2024-10-01 06:19:07 +02:00
shellOpts = append(shellOpts, cmdOpts.ShellOpts...)
2024-06-24 23:34:31 +02:00
if cmdStr == "" {
if shellType == shellutil.ShellType_bash {
2024-08-01 08:47:33 +02:00
// add --rcfile
// cant set -l or -i with --rcfile
2025-01-14 23:09:26 +01:00
shellOpts = append(shellOpts, "--rcfile", shellutil.GetLocalBashRcFileOverride())
} else if shellType == shellutil.ShellType_fish {
2025-01-14 23:09:26 +01:00
if cmdOpts.Login {
shellOpts = append(shellOpts, "-l")
}
waveFishPath := shellutil.GetLocalWaveFishFilePath()
carg := fmt.Sprintf("source %s", shellutil.HardQuoteFish(waveFishPath))
2025-01-14 23:09:26 +01:00
shellOpts = append(shellOpts, "-C", carg)
} else if shellType == shellutil.ShellType_pwsh {
2025-01-14 23:09:26 +01:00
shellOpts = append(shellOpts, "-ExecutionPolicy", "Bypass", "-NoExit", "-File", shellutil.GetLocalWavePowershellEnv())
} else {
2024-08-01 08:47:33 +02:00
if cmdOpts.Login {
shellOpts = append(shellOpts, "-l")
2025-01-14 23:09:26 +01:00
}
if cmdOpts.Interactive {
2024-08-01 08:47:33 +02:00
shellOpts = append(shellOpts, "-i")
}
}
blocklogger.Debugf(logCtx, "[conndebug] shell:%s shellOpts:%v\n", shellPath, shellOpts)
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 shellType == shellutil.ShellType_zsh {
2025-01-14 23:09:26 +01:00
shellutil.UpdateCmdEnv(ecmd, map[string]string{"ZDOTDIR": shellutil.GetLocalZshZDotDir()})
2024-08-01 08:47:33 +02:00
}
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
}
packedToken, err := cmdOpts.SwapToken.PackForClient()
if err != nil {
blocklogger.Infof(logCtx, "error packing swap token: %v", err)
} else {
blocklogger.Debugf(logCtx, "packed swaptoken %s\n", packedToken)
shellutil.UpdateCmdEnv(ecmd, map[string]string{wavebase.WaveSwapTokenVarName: packedToken})
}
/*
For Snap installations, we need to correct the XDG environment variables as Snap
overrides them to point to snap directories. We will get the correct values, if
set, from the PAM environment. If the XDG variables are set in profile or in an
RC file, it will be overridden when the shell initializes.
*/
if os.Getenv("SNAP") != "" {
log.Printf("Detected Snap installation, correcting XDG environment variables")
varsToReplace := map[string]string{"XDG_CONFIG_HOME": "", "XDG_DATA_HOME": "", "XDG_CACHE_HOME": "", "XDG_RUNTIME_DIR": "", "XDG_CONFIG_DIRS": "", "XDG_DATA_DIRS": ""}
pamEnvs := tryGetPamEnvVars()
if len(pamEnvs) > 0 {
// We only want to set the XDG variables from the PAM environment, all others should already be correct or may have been overridden by something else out of our control
for k := range pamEnvs {
if _, ok := varsToReplace[k]; ok {
varsToReplace[k] = pamEnvs[k]
}
}
}
log.Printf("Setting XDG environment variables to: %v", varsToReplace)
shellutil.UpdateCmdEnv(ecmd, varsToReplace)
}
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)
if os.Getenv("LANG") == "" {
envToAdd["LANG"] = wavebase.DetermineLang()
}
shellutil.UpdateCmdEnv(ecmd, envToAdd)
shellutil.UpdateCmdEnv(ecmd, cmdOpts.Env)
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)
}
shellutil.AddTokenSwapEntry(cmdOpts.SwapToken)
cmdPty, err := pty.StartWithSize(ecmd, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
if err != nil {
return nil, err
}
cmdWrap := MakeCmdWrap(ecmd, cmdPty)
return &ShellProc{Cmd: cmdWrap, CloseOnce: &sync.Once{}, DoneCh: make(chan any)}, nil
}
2024-08-20 23:56:48 +02:00
func RunSimpleCmdInPty(ecmd *exec.Cmd, termSize waveobj.TermSize) ([]byte, error) {
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)
}
cmdPty, err := pty.StartWithSize(ecmd, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
if err != nil {
cmdPty.Close()
return nil, err
}
if runtime.GOOS != "windows" {
defer cmdPty.Close()
}
ioDone := make(chan bool)
var outputBuf bytes.Buffer
go func() {
panichandler.PanicHandler("RunSimpleCmdInPty:ioCopy", recover())
// 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
}
const etcEnvironmentPath = "/etc/environment"
const etcSecurityPath = "/etc/security/pam_env.conf"
const userEnvironmentPath = "~/.pam_environment"
var pamParseOpts *pamparse.PamParseOpts = pamparse.ParsePasswdSafe()
/*
tryGetPamEnvVars tries to get the environment variables from /etc/environment,
/etc/security/pam_env.conf, and ~/.pam_environment.
It then returns a map of the environment variables, overriding duplicates with
the following order of precedence:
1. /etc/environment
2. /etc/security/pam_env.conf
3. ~/.pam_environment
*/
func tryGetPamEnvVars() map[string]string {
envVars, err := pamparse.ParseEnvironmentFile(etcEnvironmentPath)
if err != nil {
log.Printf("error parsing %s: %v", etcEnvironmentPath, err)
}
envVars2, err := pamparse.ParseEnvironmentConfFile(etcSecurityPath, pamParseOpts)
if err != nil {
log.Printf("error parsing %s: %v", etcSecurityPath, err)
}
envVars3, err := pamparse.ParseEnvironmentConfFile(wavebase.ExpandHomeDirSafe(userEnvironmentPath), pamParseOpts)
if err != nil {
log.Printf("error parsing %s: %v", userEnvironmentPath, err)
}
for k, v := range envVars2 {
envVars[k] = v
}
for k, v := range envVars3 {
envVars[k] = v
}
return envVars
}