From 8200a312b95972def60183127722d74047188654 Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Thu, 14 Dec 2023 21:45:27 -0800 Subject: [PATCH] On MacOS allow UserShell to override /bin/bash (#137) * switch emain to using the bash from PATH, not defaulting to /bin/bash. not convinced this fixes #132 yet because the PATH set for MacOS applications is not set from .bash_profile or .zprofile. those seem to be set with launchctl setenv. * move MacUserShell() func to shexec. use dscl UserShell output as bash path on MacOS when present. fixes #132 --- src/electron/emain.ts | 2 +- waveshell/pkg/server/server.go | 2 +- waveshell/pkg/shexec/shexec.go | 65 ++++++++++++++++++++++++++++++---- wavesrv/pkg/scbase/scbase.go | 29 --------------- 4 files changed, 60 insertions(+), 38 deletions(-) diff --git a/src/electron/emain.ts b/src/electron/emain.ts index aa9c249f..469374cc 100644 --- a/src/electron/emain.ts +++ b/src/electron/emain.ts @@ -547,7 +547,7 @@ function runWaveSrv() { envCopy[WaveDevVarName] = "1"; } console.log("trying to run local server", getWaveSrvPath()); - let proc = child_process.spawn("/bin/bash", ["-c", getWaveSrvCmd()], { + let proc = child_process.spawn("bash", ["-c", getWaveSrvCmd()], { cwd: getWaveSrvCwd(), env: envCopy, }); diff --git a/waveshell/pkg/server/server.go b/waveshell/pkg/server/server.go index a31016e8..33475c29 100644 --- a/waveshell/pkg/server/server.go +++ b/waveshell/pkg/server/server.go @@ -150,7 +150,7 @@ func runSingleCompGen(cwd string, compType string, prefix string) ([]string, boo return nil, false, fmt.Errorf("invalid compgen type '%s'", compType) } compGenCmdStr := fmt.Sprintf("cd %s; compgen -A %s -- %s | sort | uniq | head -n %d", shellescape.Quote(cwd), shellescape.Quote(compType), shellescape.Quote(prefix), packet.MaxCompGenValues+1) - ecmd := exec.Command("bash", "-c", compGenCmdStr) + ecmd := exec.Command(shexec.GetLocalBashPath(), "-c", compGenCmdStr) outputBytes, err := ecmd.Output() if err != nil { return nil, false, fmt.Errorf("compgen error: %w", err) diff --git a/waveshell/pkg/shexec/shexec.go b/waveshell/pkg/shexec/shexec.go index 9d09121d..a44c0db5 100644 --- a/waveshell/pkg/shexec/shexec.go +++ b/waveshell/pkg/shexec/shexec.go @@ -14,6 +14,7 @@ import ( "os/signal" "os/user" "path" + "regexp" "runtime" "strconv" "strings" @@ -52,6 +53,8 @@ const RtnStateFdNum = 20 const ReturnStateReadWaitTime = 2 * time.Second const GetStateTimeout = 5 * time.Second +const RemoteBashPath = "bash" +const DefaultMacOSShell = "/bin/bash" const BaseBashOpts = `set +m; set +H; shopt -s extglob` @@ -62,7 +65,7 @@ var LocalBashMajorVersionOnce = &sync.Once{} var LocalBashMajorVersion = "" var GetShellStateCmds = []string{ - `echo bash v${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}.${BASH_VERSINFO[2]};`, + ShellVersionCmdStr + ";", `pwd;`, `declare -p $(compgen -A variable);`, `alias -p;`, @@ -101,6 +104,7 @@ func MakeInstallCommandStr() string { return strings.ReplaceAll(InstallCommandFmt, "[%VERSION%]", semver.MajorMinor(base.MShellVersion)) } +// TODO fix bash path in these constants const RunCommandFmt = `%s` const RunSudoCommandFmt = `sudo -n -C %d bash /dev/fd/%d` const RunSudoPasswordCommandFmt = `cat /dev/fd/%d | sudo -k -S -C %d bash -c "echo '[from-mshell]'; exec %d>&-; bash /dev/fd/%d < /dev/fd/%d"` @@ -179,6 +183,16 @@ type ShExecUPR struct { UPR packet.UnknownPacketReporter } +func GetLocalBashPath() string { + if runtime.GOOS == "darwin" { + macShell := GetMacUserShell() + if strings.Index(macShell, "bash") != -1 { + return shellescape.Quote(macShell) + } + } + return "bash" +} + func GetShellStateCmd() string { return strings.Join(GetShellStateCmds, ` printf "\x00\x00";`) } @@ -320,7 +334,7 @@ func MakeDetachedExecCmd(pk *packet.RunPacketType, cmdTty *os.File) (*exec.Cmd, if state == nil { state = &packet.ShellState{} } - ecmd := exec.Command("bash", "-c", pk.Command) + ecmd := exec.Command(GetLocalBashPath(), "-c", pk.Command) if !pk.StateComplete { ecmd.Env = os.Environ() } @@ -525,7 +539,7 @@ func (opts SSHOpts) MakeSSHExecCmd(remoteCommand string) *exec.Cmd { if homeDir == "" { homeDir = "/" } - ecmd := exec.Command("bash", "-c", remoteCommand) + ecmd := exec.Command(GetLocalBashPath(), "-c", remoteCommand) ecmd.Dir = homeDir return ecmd } else { @@ -552,7 +566,7 @@ func (opts SSHOpts) MakeSSHExecCmd(remoteCommand string) *exec.Cmd { } // note that SSHOptsStr is *not* escaped sshCmd := fmt.Sprintf("ssh %s %s %s %s", strings.Join(moreSSHOpts, " "), opts.SSHOptsStr, shellescape.Quote(opts.SSHHost), shellescape.Quote(remoteCommand)) - ecmd := exec.Command("bash", "-c", sshCmd) + ecmd := exec.Command(RemoteBashPath, "-c", sshCmd) return ecmd } } @@ -1141,9 +1155,9 @@ func RunCommandSimple(pk *packet.RunPacketType, sender *packet.PacketSender, fro rcFileName = fmt.Sprintf("/dev/fd/%d", rcFileFdNum) } if pk.UsePty { - cmd.Cmd = exec.Command("bash", "--rcfile", rcFileName, "-i", "-c", pk.Command) + cmd.Cmd = exec.Command(GetLocalBashPath(), "--rcfile", rcFileName, "-i", "-c", pk.Command) } else { - cmd.Cmd = exec.Command("bash", "--rcfile", rcFileName, "-c", pk.Command) + cmd.Cmd = exec.Command(GetLocalBashPath(), "--rcfile", rcFileName, "-c", pk.Command) } if !pk.StateComplete { cmd.Cmd.Env = os.Environ() @@ -1582,7 +1596,7 @@ func GetShellState() (*packet.ShellState, error) { ctx, cancelFn := context.WithTimeout(context.Background(), GetStateTimeout) defer cancelFn() cmdStr := BaseBashOpts + "; " + GetShellStateCmd() - ecmd := exec.CommandContext(ctx, "bash", "-l", "-i", "-c", cmdStr) + ecmd := exec.CommandContext(ctx, GetLocalBashPath(), "-l", "-i", "-c", cmdStr) outputBytes, err := runSimpleCmdInPty(ecmd) if err != nil { return nil, err @@ -1625,3 +1639,40 @@ func GetLocalBashMajorVersion() string { }) return LocalBashMajorVersion } + +var userShellRegexp = regexp.MustCompile(`^UserShell: (.*)$`) + +var cachedMacUserShell string +var macUserShellOnce = &sync.Once{} + +func GetMacUserShell() string { + if runtime.GOOS != "darwin" { + return "" + } + macUserShellOnce.Do(func() { + cachedMacUserShell = internalMacUserShell() + }) + return cachedMacUserShell +} + +// dscl . -read /User/[username] UserShell +// defaults to /bin/bash +func internalMacUserShell() string { + osUser, err := user.Current() + if err != nil { + return DefaultMacOSShell + } + ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) + defer cancelFn() + userStr := "/Users/" + osUser.Username + out, err := exec.CommandContext(ctx, "dscl", ".", "-read", userStr, "UserShell").CombinedOutput() + if err != nil { + return DefaultMacOSShell + } + outStr := strings.TrimSpace(string(out)) + m := userShellRegexp.FindStringSubmatch(outStr) + if m == nil { + return DefaultMacOSShell + } + return m[1] +} diff --git a/wavesrv/pkg/scbase/scbase.go b/wavesrv/pkg/scbase/scbase.go index 03cde663..d4accabd 100644 --- a/wavesrv/pkg/scbase/scbase.go +++ b/wavesrv/pkg/scbase/scbase.go @@ -12,7 +12,6 @@ import ( "log" "os" "os/exec" - "os/user" "path" "regexp" "runtime" @@ -39,7 +38,6 @@ const WaveAppPathVarName = "WAVETERM_APP_PATH" const WaveVersion = "v0.5.1" const WaveAuthKeyFileName = "waveterm.authkey" const MShellVersion = "v0.3.0" -const DefaultMacOSShell = "/bin/bash" var SessionDirCache = make(map[string]string) var ScreenDirCache = make(map[string]string) @@ -379,30 +377,3 @@ func MacOSRelease() string { }) return osRelease } - -var userShellRegexp = regexp.MustCompile(`^UserShell: (.*)$`) - -// dscl . -read /User/[username] UserShell -// defaults to /bin/bash -func MacUserShell() string { - osUser, err := user.Current() - if err != nil { - log.Printf("error getting current user: %v\n", err) - return DefaultMacOSShell - } - ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second) - defer cancelFn() - userStr := "/Users/" + osUser.Name - out, err := exec.CommandContext(ctx, "dscl", ".", "-read", userStr, "UserShell").CombinedOutput() - if err != nil { - log.Printf("error executing macos user shell lookup: %v %q\n", err, string(out)) - return DefaultMacOSShell - } - outStr := strings.TrimSpace(string(out)) - m := userShellRegexp.FindStringSubmatch(outStr) - if m == nil { - log.Printf("error in format of dscl output: %q\n", outStr) - return DefaultMacOSShell - } - return m[1] -}