2025-01-05 05:56:57 +01:00
// Copyright 2025, Command Line Inc.
2024-05-14 22:34:41 +02:00
// SPDX-License-Identifier: Apache-2.0
package blockcontroller
import (
2024-05-29 09:28:25 +02:00
"bytes"
2024-05-24 23:08:24 +02:00
"context"
2024-05-15 01:53:03 +02:00
"encoding/base64"
2024-05-14 22:34:41 +02:00
"fmt"
2024-05-15 08:25:21 +02:00
"io"
2024-06-24 23:34:31 +02:00
"io/fs"
2024-05-15 07:37:04 +02:00
"log"
2024-10-24 07:43:17 +02:00
"strings"
2024-05-14 22:34:41 +02:00
"sync"
2024-12-06 20:08:51 +01:00
"sync/atomic"
2024-05-24 23:08:24 +02:00
"time"
2024-05-14 22:34:41 +02:00
2025-01-16 20:17:29 +01:00
"github.com/google/uuid"
2025-01-10 23:09:32 +01:00
"github.com/wavetermdev/waveterm/pkg/blocklogger"
2024-09-05 23:25:45 +02:00
"github.com/wavetermdev/waveterm/pkg/filestore"
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/shellexec"
2024-12-04 23:16:50 +01:00
"github.com/wavetermdev/waveterm/pkg/util/envutil"
2025-01-16 20:17:29 +01:00
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
2024-12-04 23:16:50 +01: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"
2024-09-27 00:34:52 +02:00
"github.com/wavetermdev/waveterm/pkg/wconfig"
2024-09-05 23:25:45 +02:00
"github.com/wavetermdev/waveterm/pkg/wps"
"github.com/wavetermdev/waveterm/pkg/wshrpc"
2024-12-04 23:16:50 +01:00
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
2024-09-05 23:25:45 +02:00
"github.com/wavetermdev/waveterm/pkg/wshutil"
2025-01-17 00:54:58 +01:00
"github.com/wavetermdev/waveterm/pkg/wslconn"
2024-09-05 23:25:45 +02:00
"github.com/wavetermdev/waveterm/pkg/wstore"
2024-05-16 09:29:58 +02:00
)
const (
BlockController_Shell = "shell"
BlockController_Cmd = "cmd"
2024-05-14 22:34:41 +02:00
)
2024-06-14 08:54:04 +02:00
const (
2025-01-23 01:04:08 +01:00
ConnType_Local = "local"
ConnType_Wsl = "wsl"
ConnType_Ssh = "ssh"
2024-06-14 08:54:04 +02:00
)
2024-06-24 23:34:31 +02:00
const (
Status_Running = "running"
Status_Done = "done"
2024-12-04 23:16:50 +01:00
Status_Init = "init"
2024-06-24 23:34:31 +02:00
)
2024-06-21 01:01:55 +02:00
const (
DefaultTermMaxFileSize = 256 * 1024
DefaultHtmlMaxFileSize = 256 * 1024
)
2024-05-24 23:08:24 +02:00
const DefaultTimeout = 2 * time . Second
2024-05-14 22:34:41 +02:00
var globalLock = & sync . Mutex { }
var blockControllerMap = make ( map [ string ] * BlockController )
2024-06-17 18:58:28 +02:00
type BlockInputUnion struct {
2024-08-20 23:56:48 +02:00
InputData [ ] byte ` json:"inputdata,omitempty" `
SigName string ` json:"signame,omitempty" `
TermSize * waveobj . TermSize ` json:"termsize,omitempty" `
2024-06-17 18:58:28 +02:00
}
2024-05-14 22:34:41 +02:00
type BlockController struct {
2024-12-04 23:16:50 +01:00
Lock * sync . Mutex
ControllerType string
TabId string
BlockId string
BlockDef * waveobj . BlockDef
CreatedHtmlFile bool
ShellProc * shellexec . ShellProc
ShellInputCh chan * BlockInputUnion
ShellProcStatus string
ShellProcExitCode int
2024-12-06 20:08:51 +01:00
RunLock * atomic . Bool
2024-12-07 04:39:58 +01:00
StatusVersion int
2024-05-14 22:34:41 +02:00
}
2024-06-24 23:34:31 +02:00
type BlockControllerRuntimeStatus struct {
2024-09-05 09:21:08 +02:00
BlockId string ` json:"blockid" `
2024-12-07 04:39:58 +01:00
Version int ` json:"version" `
2024-09-05 09:21:08 +02:00
ShellProcStatus string ` json:"shellprocstatus,omitempty" `
ShellProcConnName string ` json:"shellprocconnname,omitempty" `
2024-12-04 23:16:50 +01:00
ShellProcExitCode int ` json:"shellprocexitcode" `
2024-06-24 23:34:31 +02:00
}
2024-05-24 23:08:24 +02:00
func ( bc * BlockController ) WithLock ( f func ( ) ) {
bc . Lock . Lock ( )
defer bc . Lock . Unlock ( )
f ( )
}
2024-06-24 23:34:31 +02:00
func ( bc * BlockController ) GetRuntimeStatus ( ) * BlockControllerRuntimeStatus {
var rtn BlockControllerRuntimeStatus
bc . WithLock ( func ( ) {
2024-12-07 04:39:58 +01:00
bc . StatusVersion ++
rtn . Version = bc . StatusVersion
2024-06-24 23:34:31 +02:00
rtn . BlockId = bc . BlockId
rtn . ShellProcStatus = bc . ShellProcStatus
2024-09-05 09:21:08 +02:00
if bc . ShellProc != nil {
rtn . ShellProcConnName = bc . ShellProc . ConnName
}
2024-12-04 23:16:50 +01:00
rtn . ShellProcExitCode = bc . ShellProcExitCode
2024-06-24 23:34:31 +02:00
} )
return & rtn
}
2024-05-15 08:25:21 +02:00
func ( bc * BlockController ) getShellProc ( ) * shellexec . ShellProc {
bc . Lock . Lock ( )
defer bc . Lock . Unlock ( )
return bc . ShellProc
2024-05-14 22:34:41 +02:00
}
2024-05-16 09:29:58 +02:00
type RunShellOpts struct {
2024-08-20 23:56:48 +02:00
TermSize waveobj . TermSize ` json:"termsize,omitempty" `
2024-05-15 07:37:04 +02:00
}
2024-06-24 23:34:31 +02:00
func ( bc * BlockController ) UpdateControllerAndSendUpdate ( updateFn func ( ) bool ) {
var sendUpdate bool
bc . WithLock ( func ( ) {
sendUpdate = updateFn ( )
} )
if sendUpdate {
2024-08-30 23:36:16 +02:00
rtStatus := bc . GetRuntimeStatus ( )
log . Printf ( "sending blockcontroller update %#v\n" , rtStatus )
2024-09-12 03:03:55 +02:00
wps . Broker . Publish ( wps . WaveEvent {
Event : wps . Event_ControllerStatus ,
2024-08-30 23:36:16 +02:00
Scopes : [ ] string {
waveobj . MakeORef ( waveobj . OType_Tab , bc . TabId ) . String ( ) ,
2024-09-05 09:21:08 +02:00
waveobj . MakeORef ( waveobj . OType_Block , bc . BlockId ) . String ( ) ,
2024-08-30 23:36:16 +02:00
} ,
Data : rtStatus ,
2024-09-12 03:03:55 +02:00
} )
2024-06-24 23:34:31 +02:00
}
}
2024-12-04 23:16:50 +01:00
func HandleTruncateBlockFile ( blockId string ) error {
2024-06-24 23:34:31 +02:00
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , DefaultTimeout )
defer cancelFn ( )
2025-01-23 01:04:08 +01:00
err := filestore . WFS . WriteFile ( ctx , blockId , wavebase . BlockFile_Term , nil )
2024-06-24 23:34:31 +02:00
if err == fs . ErrNotExist {
return nil
2024-05-16 22:40:23 +02:00
}
2024-06-24 23:34:31 +02:00
if err != nil {
return fmt . Errorf ( "error truncating blockfile: %w" , err )
}
2025-01-23 01:04:08 +01:00
err = filestore . WFS . DeleteFile ( ctx , blockId , wavebase . BlockFile_Cache )
2024-12-04 23:16:50 +01:00
if err == fs . ErrNotExist {
err = nil
}
if err != nil {
log . Printf ( "error deleting cache file (continuing): %v\n" , err )
}
2024-09-12 03:03:55 +02:00
wps . Broker . Publish ( wps . WaveEvent {
Event : wps . Event_BlockFile ,
Scopes : [ ] string { waveobj . MakeORef ( waveobj . OType_Block , blockId ) . String ( ) } ,
Data : & wps . WSFileEventData {
2024-06-24 23:34:31 +02:00
ZoneId : blockId ,
2025-01-23 01:04:08 +01:00
FileName : wavebase . BlockFile_Term ,
2024-09-12 03:03:55 +02:00
FileOp : wps . FileOp_Truncate ,
2024-06-24 23:34:31 +02:00
} ,
} )
return nil
2024-05-16 22:40:23 +02:00
}
2024-06-21 01:01:55 +02:00
func HandleAppendBlockFile ( blockId string , blockFile string , data [ ] byte ) error {
2024-05-29 06:44:47 +02:00
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , DefaultTimeout )
defer cancelFn ( )
2024-06-14 08:54:04 +02:00
err := filestore . WFS . AppendData ( ctx , blockId , blockFile , data )
2024-05-29 06:44:47 +02:00
if err != nil {
return fmt . Errorf ( "error appending to blockfile: %w" , err )
}
2024-09-12 03:03:55 +02:00
wps . Broker . Publish ( wps . WaveEvent {
Event : wps . Event_BlockFile ,
Scopes : [ ] string {
waveobj . MakeORef ( waveobj . OType_Block , blockId ) . String ( ) ,
} ,
Data : & wps . WSFileEventData {
2024-06-14 08:54:04 +02:00
ZoneId : blockId ,
FileName : blockFile ,
2024-09-12 03:03:55 +02:00
FileOp : wps . FileOp_Append ,
2024-06-14 08:54:04 +02:00
Data64 : base64 . StdEncoding . EncodeToString ( data ) ,
} ,
} )
return nil
}
2025-01-23 01:04:08 +01:00
func ( bc * BlockController ) resetTerminalState ( logCtx context . Context ) {
2024-05-29 09:28:25 +02:00
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , DefaultTimeout )
defer cancelFn ( )
2025-01-23 01:04:08 +01:00
wfile , statErr := filestore . WFS . Stat ( ctx , bc . BlockId , wavebase . BlockFile_Term )
2024-12-04 23:16:50 +01:00
if statErr == fs . ErrNotExist || wfile . Size == 0 {
2024-06-24 23:34:31 +02:00
return
}
2025-01-23 01:04:08 +01:00
blocklogger . Debugf ( logCtx , "[conndebug] resetTerminalState: resetting terminal state\n" )
2024-06-24 23:34:31 +02:00
// controller type = "shell"
2024-05-29 09:28:25 +02:00
var buf bytes . Buffer
// buf.WriteString("\x1b[?1049l") // disable alternative buffer
buf . WriteString ( "\x1b[0m" ) // reset attributes
buf . WriteString ( "\x1b[?25h" ) // show cursor
buf . WriteString ( "\x1b[?1000l" ) // disable mouse tracking
2025-01-23 01:04:08 +01:00
buf . WriteString ( "\r\n\r\n" )
err := HandleAppendBlockFile ( bc . BlockId , wavebase . BlockFile_Term , buf . Bytes ( ) )
2024-05-29 09:28:25 +02:00
if err != nil {
log . Printf ( "error appending to blockfile (terminal reset): %v\n" , err )
}
}
2025-01-23 01:04:08 +01:00
func getCustomInitScriptKeyCascade ( shellType string ) [ ] string {
if shellType == "bash" {
return [ ] string { waveobj . MetaKey_CmdInitScriptBash , waveobj . MetaKey_CmdInitScriptSh , waveobj . MetaKey_CmdInitScript }
2024-12-04 23:16:50 +01:00
}
2025-01-23 01:04:08 +01:00
if shellType == "zsh" {
return [ ] string { waveobj . MetaKey_CmdInitScriptZsh , waveobj . MetaKey_CmdInitScriptSh , waveobj . MetaKey_CmdInitScript }
2024-12-04 23:16:50 +01:00
}
2025-01-23 01:04:08 +01:00
if shellType == "pwsh" {
return [ ] string { waveobj . MetaKey_CmdInitScriptPwsh , waveobj . MetaKey_CmdInitScript }
}
if shellType == "fish" {
return [ ] string { waveobj . MetaKey_CmdInitScriptFish , waveobj . MetaKey_CmdInitScript }
}
return [ ] string { waveobj . MetaKey_CmdInitScript }
}
func getCustomInitScript ( meta waveobj . MetaMapType , connName string , shellType string ) string {
keys := getCustomInitScriptKeyCascade ( shellType )
connMeta := meta . GetConnectionOverride ( connName )
if connMeta != nil {
for _ , key := range keys {
if connMeta . HasKey ( key ) {
return connMeta . GetString ( key , "" )
}
2024-12-04 23:16:50 +01:00
}
2025-01-23 01:04:08 +01:00
}
for _ , key := range keys {
if meta . HasKey ( key ) {
return meta . GetString ( key , "" )
2024-12-04 23:16:50 +01:00
}
}
2025-01-23 01:04:08 +01:00
return ""
}
2024-12-04 23:16:50 +01:00
2025-01-23 01:04:08 +01:00
func resolveEnvMap ( blockId string , blockMeta waveobj . MetaMapType , connName string ) ( map [ string ] string , error ) {
2024-12-04 23:16:50 +01:00
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , 2 * time . Second )
defer cancelFn ( )
2025-01-23 01:04:08 +01:00
_ , envFileData , err := filestore . WFS . ReadFile ( ctx , blockId , wavebase . BlockFile_Env )
2024-12-04 23:16:50 +01:00
if err == fs . ErrNotExist {
err = nil
}
if err != nil {
2025-01-23 01:04:08 +01:00
return nil , fmt . Errorf ( "error reading command env file: %w" , err )
2024-12-04 23:16:50 +01:00
}
2025-01-23 01:04:08 +01:00
rtn := make ( map [ string ] string )
2024-12-04 23:16:50 +01:00
if len ( envFileData ) > 0 {
envMap := envutil . EnvToMap ( string ( envFileData ) )
for k , v := range envMap {
2025-01-23 01:04:08 +01:00
rtn [ k ] = v
2024-12-04 23:16:50 +01:00
}
}
cmdEnv := blockMeta . GetMap ( waveobj . MetaKey_CmdEnv )
for k , v := range cmdEnv {
if v == nil {
2025-01-23 01:04:08 +01:00
delete ( rtn , k )
2024-12-04 23:16:50 +01:00
continue
}
2025-01-23 01:04:08 +01:00
if strVal , ok := v . ( string ) ; ok {
rtn [ k ] = strVal
2024-12-04 23:16:50 +01:00
}
2025-01-23 01:04:08 +01:00
if floatVal , ok := v . ( float64 ) ; ok {
rtn [ k ] = fmt . Sprintf ( "%v" , floatVal )
}
}
return rtn , nil
}
// for "cmd" type blocks
func createCmdStrAndOpts ( blockId string , blockMeta waveobj . MetaMapType , connName string ) ( string , * shellexec . CommandOptsType , error ) {
var cmdStr string
var cmdOpts shellexec . CommandOptsType
cmdStr = blockMeta . GetString ( waveobj . MetaKey_Cmd , "" )
if cmdStr == "" {
return "" , nil , fmt . Errorf ( "missing cmd in block meta" )
}
cmdOpts . Cwd = blockMeta . GetString ( waveobj . MetaKey_CmdCwd , "" )
if cmdOpts . Cwd != "" {
cwdPath , err := wavebase . ExpandHomeDir ( cmdOpts . Cwd )
if err != nil {
return "" , nil , err
}
cmdOpts . Cwd = cwdPath
}
useShell := blockMeta . GetBool ( waveobj . MetaKey_CmdShell , true )
if ! useShell {
if strings . Contains ( cmdStr , " " ) {
return "" , nil , fmt . Errorf ( "cmd should not have spaces if cmd:shell is false (use cmd:args)" )
}
cmdArgs := blockMeta . GetStringList ( waveobj . MetaKey_CmdArgs )
// shell escape the args
for _ , arg := range cmdArgs {
cmdStr = cmdStr + " " + utilfn . ShellQuote ( arg , false , - 1 )
2024-12-04 23:16:50 +01:00
}
}
return cmdStr , & cmdOpts , nil
}
2025-01-16 20:17:29 +01:00
func ( bc * BlockController ) DoRunShellCommand ( logCtx context . Context , rc * RunShellOpts , blockMeta waveobj . MetaMapType ) error {
2025-01-23 01:04:08 +01:00
blocklogger . Debugf ( logCtx , "[conndebug] DoRunShellCommand\n" )
2025-01-16 20:17:29 +01:00
shellProc , err := bc . setupAndStartShellProcess ( logCtx , rc , blockMeta )
2025-01-02 23:15:32 +01:00
if err != nil {
return err
}
return bc . manageRunningShellProcess ( shellProc , rc , blockMeta )
}
2025-01-23 01:04:08 +01:00
func ( bc * BlockController ) makeSwapToken ( ctx context . Context , blockMeta waveobj . MetaMapType , remoteName string , shellType string ) * shellutil . TokenSwapEntry {
2025-01-16 20:17:29 +01:00
token := & shellutil . TokenSwapEntry {
Token : uuid . New ( ) . String ( ) ,
Env : make ( map [ string ] string ) ,
Exp : time . Now ( ) . Add ( 5 * time . Minute ) ,
}
token . Env [ "TERM_PROGRAM" ] = "waveterm"
token . Env [ "WAVETERM_BLOCKID" ] = bc . BlockId
token . Env [ "WAVETERM_VERSION" ] = wavebase . WaveVersion
token . Env [ "WAVETERM" ] = "1"
tabId , err := wstore . DBFindTabForBlockId ( ctx , bc . BlockId )
if err != nil {
log . Printf ( "error finding tab for block: %v\n" , err )
} else {
token . Env [ "WAVETERM_TABID" ] = tabId
}
if tabId != "" {
wsId , err := wstore . DBFindWorkspaceForTabId ( ctx , tabId )
if err != nil {
log . Printf ( "error finding workspace for tab: %v\n" , err )
} else {
token . Env [ "WAVETERM_WORKSPACEID" ] = wsId
}
}
clientData , err := wstore . DBGetSingleton [ * waveobj . Client ] ( ctx )
if err != nil {
log . Printf ( "error getting client data: %v\n" , err )
} else {
token . Env [ "WAVETERM_CLIENTID" ] = clientData . OID
}
token . Env [ "WAVETERM_CONN" ] = remoteName
2025-01-23 01:04:08 +01:00
envMap , err := resolveEnvMap ( bc . BlockId , blockMeta , remoteName )
if err != nil {
log . Printf ( "error resolving env map: %v\n" , err )
}
for k , v := range envMap {
token . Env [ k ] = v
}
token . ScriptText = getCustomInitScript ( blockMeta , remoteName , shellType )
2025-01-16 20:17:29 +01:00
return token
}
2025-01-23 01:04:08 +01:00
type ConnUnion struct {
ConnName string
ConnType string
SshConn * conncontroller . SSHConn
WslConn * wslconn . WslConn
WshEnabled bool
ShellPath string
ShellOpts [ ] string
ShellType string
}
func getLocalShellPath ( blockMeta waveobj . MetaMapType ) string {
shellPath := blockMeta . GetString ( waveobj . MetaKey_TermLocalShellPath , "" )
if shellPath != "" {
return shellPath
}
settings := wconfig . GetWatcher ( ) . GetFullConfig ( ) . Settings
if settings . TermLocalShellPath != "" {
return settings . TermLocalShellPath
}
return shellutil . DetectLocalShellPath ( )
}
func getLocalShellOpts ( blockMeta waveobj . MetaMapType ) [ ] string {
if blockMeta . HasKey ( waveobj . MetaKey_TermLocalShellOpts ) {
opts := blockMeta . GetStringList ( waveobj . MetaKey_TermLocalShellOpts )
return append ( [ ] string { } , opts ... )
}
settings := wconfig . GetWatcher ( ) . GetFullConfig ( ) . Settings
if len ( settings . TermLocalShellOpts ) > 0 {
return append ( [ ] string { } , settings . TermLocalShellOpts ... )
}
return nil
}
func ( union * ConnUnion ) getRemoteInfoAndShellType ( blockMeta waveobj . MetaMapType ) error {
if ! union . WshEnabled {
return nil
}
if union . ConnType == ConnType_Ssh || union . ConnType == ConnType_Wsl {
connRoute := wshutil . MakeConnectionRouteId ( union . ConnName )
remoteInfo , err := wshclient . RemoteGetInfoCommand ( wshclient . GetBareRpcClient ( ) , & wshrpc . RpcOpts { Route : connRoute , Timeout : 2000 } )
if err != nil {
// weird error, could flip the wshEnabled flag and allow it to go forward, but the connection should have already been vetted
return fmt . Errorf ( "unable to obtain remote info from connserver: %w" , err )
}
// TODO allow overriding remote shell path
union . ShellPath = remoteInfo . Shell
} else {
union . ShellPath = getLocalShellPath ( blockMeta )
}
union . ShellType = shellutil . GetShellTypeFromShellPath ( union . ShellPath )
return nil
}
func ( bc * BlockController ) getConnUnion ( logCtx context . Context , remoteName string , blockMeta waveobj . MetaMapType ) ( ConnUnion , error ) {
rtn := ConnUnion { ConnName : remoteName }
wshEnabled := ! blockMeta . GetBool ( waveobj . MetaKey_CmdNoWsh , false )
if strings . HasPrefix ( remoteName , "wsl://" ) {
wslName := strings . TrimPrefix ( remoteName , "wsl://" )
wslConn := wslconn . GetWslConn ( wslName )
if wslConn == nil {
return ConnUnion { } , fmt . Errorf ( "wsl connection not found: %s" , remoteName )
}
connStatus := wslConn . DeriveConnStatus ( )
if connStatus . Status != conncontroller . Status_Connected {
return ConnUnion { } , fmt . Errorf ( "wsl connection %s not connected, cannot start shellproc" , remoteName )
}
rtn . ConnType = ConnType_Wsl
rtn . WslConn = wslConn
rtn . WshEnabled = wshEnabled && wslConn . WshEnabled . Load ( )
} else if remoteName != "" {
opts , err := remote . ParseOpts ( remoteName )
if err != nil {
return ConnUnion { } , fmt . Errorf ( "invalid ssh remote name (%s): %w" , remoteName , err )
}
conn := conncontroller . GetConn ( opts )
if conn == nil {
return ConnUnion { } , fmt . Errorf ( "ssh connection not found: %s" , remoteName )
}
connStatus := conn . DeriveConnStatus ( )
if connStatus . Status != conncontroller . Status_Connected {
return ConnUnion { } , fmt . Errorf ( "ssh connection %s not connected, cannot start shellproc" , remoteName )
}
rtn . ConnType = ConnType_Ssh
rtn . SshConn = conn
rtn . WshEnabled = wshEnabled && conn . WshEnabled . Load ( )
} else {
rtn . ConnType = ConnType_Local
rtn . WshEnabled = wshEnabled
}
err := rtn . getRemoteInfoAndShellType ( blockMeta )
if err != nil {
return ConnUnion { } , err
}
return rtn , nil
}
2025-01-16 20:17:29 +01:00
func ( bc * BlockController ) setupAndStartShellProcess ( logCtx context . Context , rc * RunShellOpts , blockMeta waveobj . MetaMapType ) ( * shellexec . ShellProc , error ) {
2024-05-29 06:44:47 +02:00
// create a circular blockfile for the output
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , 2 * time . Second )
defer cancelFn ( )
2025-01-23 01:04:08 +01:00
fsErr := filestore . WFS . MakeFile ( ctx , bc . BlockId , wavebase . BlockFile_Term , nil , wshrpc . FileOpts { MaxSize : DefaultTermMaxFileSize , Circular : true } )
2025-01-02 23:15:32 +01:00
if fsErr != nil && fsErr != fs . ErrExist {
return nil , fmt . Errorf ( "error creating blockfile: %w" , fsErr )
2024-05-29 06:44:47 +02:00
}
2025-01-02 23:15:32 +01:00
if fsErr == fs . ErrExist {
2024-05-29 09:28:25 +02:00
// reset the terminal state
2025-01-23 01:04:08 +01:00
bc . resetTerminalState ( logCtx )
2024-05-29 09:28:25 +02:00
}
2024-09-05 09:21:08 +02:00
bcInitStatus := bc . GetRuntimeStatus ( )
if bcInitStatus . ShellProcStatus == Status_Running {
2025-01-02 23:15:32 +01:00
return nil , nil
2024-05-15 08:25:21 +02:00
}
2024-09-05 09:21:08 +02:00
// TODO better sync here (don't let two starts happen at the same times)
2024-08-20 23:56:48 +02:00
remoteName := blockMeta . GetString ( waveobj . MetaKey_Connection , "" )
2025-01-23 01:04:08 +01:00
connUnion , err := bc . getConnUnion ( logCtx , remoteName , blockMeta )
if err != nil {
return nil , err
}
blocklogger . Infof ( logCtx , "[conndebug] remoteName: %q, connType: %s, wshEnabled: %v, shell: %q, shellType: %s\n" , remoteName , connUnion . ConnType , connUnion . WshEnabled , connUnion . ShellPath , connUnion . ShellType )
2024-06-24 23:34:31 +02:00
var cmdStr string
2024-12-04 23:16:50 +01:00
var cmdOpts shellexec . CommandOptsType
2024-06-24 23:34:31 +02:00
if bc . ControllerType == BlockController_Shell {
2024-07-23 22:16:53 +02:00
cmdOpts . Interactive = true
cmdOpts . Login = true
2024-08-20 23:56:48 +02:00
cmdOpts . Cwd = blockMeta . GetString ( waveobj . MetaKey_CmdCwd , "" )
2024-07-26 09:48:12 +02:00
if cmdOpts . Cwd != "" {
2024-09-25 03:24:39 +02:00
cwdPath , err := wavebase . ExpandHomeDir ( cmdOpts . Cwd )
if err != nil {
2025-01-02 23:15:32 +01:00
return nil , err
2024-09-25 03:24:39 +02:00
}
cmdOpts . Cwd = cwdPath
2024-07-26 09:48:12 +02:00
}
2024-06-24 23:34:31 +02:00
} else if bc . ControllerType == BlockController_Cmd {
2024-12-04 23:16:50 +01:00
var cmdOptsPtr * shellexec . CommandOptsType
2025-01-23 01:04:08 +01:00
cmdStr , cmdOptsPtr , err = createCmdStrAndOpts ( bc . BlockId , blockMeta , remoteName )
2024-12-04 23:16:50 +01:00
if err != nil {
2025-01-02 23:15:32 +01:00
return nil , err
2024-06-24 23:34:31 +02:00
}
2024-12-04 23:16:50 +01:00
cmdOpts = * cmdOptsPtr
2024-06-24 23:34:31 +02:00
} else {
2025-01-02 23:15:32 +01:00
return nil , fmt . Errorf ( "unknown controller type %q" , bc . ControllerType )
2024-05-15 08:25:21 +02:00
}
2024-07-16 03:00:10 +02:00
var shellProc * shellexec . ShellProc
2025-01-23 01:04:08 +01:00
swapToken := bc . makeSwapToken ( ctx , blockMeta , remoteName , connUnion . ShellType )
2025-01-16 20:17:29 +01:00
cmdOpts . SwapToken = swapToken
blocklogger . Infof ( logCtx , "[conndebug] created swaptoken: %s\n" , swapToken . Token )
2025-01-23 01:04:08 +01:00
if connUnion . ConnType == ConnType_Wsl {
wslConn := connUnion . WslConn
if ! connUnion . WshEnabled {
shellProc , err = shellexec . StartWslShellProcNoWsh ( ctx , rc . TermSize , cmdStr , cmdOpts , wslConn )
if err != nil {
return nil , err
}
} else {
2025-01-17 00:54:58 +01:00
sockName := wslConn . GetDomainSocketName ( )
rpcContext := wshrpc . RpcContext { TabId : bc . TabId , BlockId : bc . BlockId , Conn : wslConn . GetName ( ) }
jwtStr , err := wshutil . MakeClientJWTToken ( rpcContext , sockName )
2024-10-24 07:43:17 +02:00
if err != nil {
2025-01-02 23:15:32 +01:00
return nil , fmt . Errorf ( "error making jwt token: %w" , err )
2024-10-24 07:43:17 +02:00
}
2025-01-17 00:54:58 +01:00
swapToken . SockName = sockName
swapToken . RpcContext = & rpcContext
2025-01-16 20:17:29 +01:00
swapToken . Env [ wshutil . WaveJwtTokenVarName ] = jwtStr
2025-01-18 03:28:46 +01:00
shellProc , err = shellexec . StartWslShellProc ( ctx , rc . TermSize , cmdStr , cmdOpts , wslConn )
if err != nil {
wslConn . SetWshError ( err )
wslConn . WshEnabled . Store ( false )
log . Printf ( "error starting wsl shell proc with wsh: %v" , err )
log . Print ( "attempting install without wsh" )
shellProc , err = shellexec . StartWslShellProcNoWsh ( ctx , rc . TermSize , cmdStr , cmdOpts , wslConn )
if err != nil {
return nil , err
}
}
2024-10-24 07:43:17 +02:00
}
2025-01-23 01:04:08 +01:00
} else if connUnion . ConnType == ConnType_Ssh {
conn := connUnion . SshConn
if ! connUnion . WshEnabled {
shellProc , err = shellexec . StartRemoteShellProcNoWsh ( ctx , rc . TermSize , cmdStr , cmdOpts , conn )
if err != nil {
return nil , err
}
} else {
2025-01-16 20:17:29 +01:00
sockName := conn . GetDomainSocketName ( )
rpcContext := wshrpc . RpcContext { TabId : bc . TabId , BlockId : bc . BlockId , Conn : conn . Opts . String ( ) }
jwtStr , err := wshutil . MakeClientJWTToken ( rpcContext , sockName )
2024-08-17 20:21:25 +02:00
if err != nil {
2025-01-02 23:15:32 +01:00
return nil , fmt . Errorf ( "error making jwt token: %w" , err )
2024-08-17 20:21:25 +02:00
}
2025-01-16 20:17:29 +01:00
swapToken . SockName = sockName
swapToken . RpcContext = & rpcContext
swapToken . Env [ wshutil . WaveJwtTokenVarName ] = jwtStr
shellProc , err = shellexec . StartRemoteShellProc ( ctx , logCtx , rc . TermSize , cmdStr , cmdOpts , conn )
2024-12-06 19:11:38 +01:00
if err != nil {
2025-01-10 23:09:32 +01:00
conn . SetWshError ( err )
2024-12-06 19:11:38 +01:00
conn . WshEnabled . Store ( false )
log . Printf ( "error starting remote shell proc with wsh: %v" , err )
log . Print ( "attempting install without wsh" )
2025-01-14 23:09:26 +01:00
shellProc , err = shellexec . StartRemoteShellProcNoWsh ( ctx , rc . TermSize , cmdStr , cmdOpts , conn )
2024-12-06 19:11:38 +01:00
if err != nil {
2025-01-02 23:15:32 +01:00
return nil , err
2024-12-06 19:11:38 +01:00
}
}
}
2025-01-23 01:04:08 +01:00
} else if connUnion . ConnType == ConnType_Local {
if connUnion . WshEnabled {
2025-01-16 20:17:29 +01:00
sockName := wavebase . GetDomainSocketName ( )
rpcContext := wshrpc . RpcContext { TabId : bc . TabId , BlockId : bc . BlockId }
jwtStr , err := wshutil . MakeClientJWTToken ( rpcContext , sockName )
2024-08-17 20:21:25 +02:00
if err != nil {
2025-01-02 23:15:32 +01:00
return nil , fmt . Errorf ( "error making jwt token: %w" , err )
2024-08-17 20:21:25 +02:00
}
2025-01-16 20:17:29 +01:00
swapToken . SockName = sockName
swapToken . RpcContext = & rpcContext
swapToken . Env [ wshutil . WaveJwtTokenVarName ] = jwtStr
2024-10-01 06:19:07 +02:00
}
2025-01-23 01:04:08 +01:00
cmdOpts . ShellPath = connUnion . ShellPath
cmdOpts . ShellOpts = getLocalShellOpts ( blockMeta )
2025-01-16 20:17:29 +01:00
shellProc , err = shellexec . StartLocalShellProc ( logCtx , rc . TermSize , cmdStr , cmdOpts )
2024-07-16 03:00:10 +02:00
if err != nil {
2025-01-02 23:15:32 +01:00
return nil , err
2024-07-16 03:00:10 +02:00
}
2025-01-23 01:04:08 +01:00
} else {
return nil , fmt . Errorf ( "unknown connection type for conn %q: %s" , remoteName , connUnion . ConnType )
2024-05-15 08:25:21 +02:00
}
2024-06-24 23:34:31 +02:00
bc . UpdateControllerAndSendUpdate ( func ( ) bool {
bc . ShellProc = shellProc
bc . ShellProcStatus = Status_Running
return true
} )
2025-01-02 23:15:32 +01:00
return shellProc , nil
}
2025-01-23 01:04:08 +01:00
func ( bc * BlockController ) getBlockData_noErr ( ) * waveobj . Block {
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , DefaultTimeout )
defer cancelFn ( )
blockData , err := wstore . DBGet [ * waveobj . Block ] ( ctx , bc . BlockId )
if err != nil {
log . Printf ( "error getting block data (getBlockData_noErr): %v\n" , err )
return nil
}
return blockData
}
2025-01-02 23:15:32 +01:00
func ( bc * BlockController ) manageRunningShellProcess ( shellProc * shellexec . ShellProc , rc * RunShellOpts , blockMeta waveobj . MetaMapType ) error {
2024-06-17 18:58:28 +02:00
shellInputCh := make ( chan * BlockInputUnion , 32 )
2024-05-15 08:25:21 +02:00
bc . ShellInputCh = shellInputCh
2024-08-14 01:52:35 +02:00
// make esc sequence wshclient wshProxy
// we don't need to authenticate this wshProxy since it is coming direct
wshProxy := wshutil . MakeRpcProxy ( )
wshProxy . SetRpcContext ( & wshrpc . RpcContext { TabId : bc . TabId , BlockId : bc . BlockId } )
2024-10-24 07:43:17 +02:00
wshutil . DefaultRouter . RegisterRoute ( wshutil . MakeControllerRouteId ( bc . BlockId ) , wshProxy , true )
2024-12-04 23:16:50 +01:00
ptyBuffer := wshutil . MakePtyBuffer ( wshutil . WaveOSCPrefix , shellProc . Cmd , wshProxy . FromRemoteCh )
2024-05-15 08:25:21 +02:00
go func ( ) {
2024-06-17 18:58:28 +02:00
// handles regular output from the pty (goes to the blockfile and xterm)
2024-12-31 18:31:55 +01:00
defer func ( ) {
panichandler . PanicHandler ( "blockcontroller:shellproc-pty-read-loop" , recover ( ) )
} ( )
2024-05-15 08:25:21 +02:00
defer func ( ) {
2024-06-24 23:34:31 +02:00
log . Printf ( "[shellproc] pty-read loop done\n" )
2024-12-04 23:16:50 +01:00
shellProc . Close ( )
2024-09-05 09:21:08 +02:00
bc . WithLock ( func ( ) {
// so no other events are sent
bc . ShellInputCh = nil
} )
2024-12-04 23:16:50 +01:00
shellProc . Cmd . Wait ( )
exitCode := shellProc . Cmd . ExitCode ( )
2025-01-23 01:04:08 +01:00
blockData := bc . getBlockData_noErr ( )
if blockData != nil && blockData . Meta . GetString ( waveobj . MetaKey_Controller , "" ) == BlockController_Cmd {
termMsg := fmt . Sprintf ( "\r\nprocess finished with exit code = %d\r\n\r\n" , exitCode )
HandleAppendBlockFile ( bc . BlockId , wavebase . BlockFile_Term , [ ] byte ( termMsg ) )
}
2024-09-05 09:21:08 +02:00
// to stop the inputCh loop
time . Sleep ( 100 * time . Millisecond )
close ( shellInputCh ) // don't use bc.ShellInputCh (it's nil)
2024-05-15 08:25:21 +02:00
} ( )
buf := make ( [ ] byte , 4096 )
for {
2024-06-14 23:43:47 +02:00
nr , err := ptyBuffer . Read ( buf )
2024-05-29 06:44:47 +02:00
if nr > 0 {
2025-01-23 01:04:08 +01:00
err := HandleAppendBlockFile ( bc . BlockId , wavebase . BlockFile_Term , buf [ : nr ] )
2024-06-14 23:43:47 +02:00
if err != nil {
log . Printf ( "error appending to blockfile: %v\n" , err )
2024-05-29 06:44:47 +02:00
}
}
2024-05-15 08:25:21 +02:00
if err == io . EOF {
break
}
if err != nil {
log . Printf ( "error reading from shell: %v\n" , err )
break
}
}
} ( )
go func ( ) {
2024-06-17 18:58:28 +02:00
// handles input from the shellInputCh, sent to pty
2024-09-05 09:21:08 +02:00
// use shellInputCh instead of bc.ShellInputCh (because we want to be attached to *this* ch. bc.ShellInputCh can be updated)
2024-12-31 18:31:55 +01:00
defer func ( ) {
panichandler . PanicHandler ( "blockcontroller:shellproc-input-loop" , recover ( ) )
} ( )
2024-05-15 08:25:21 +02:00
for ic := range shellInputCh {
2024-06-17 18:58:28 +02:00
if len ( ic . InputData ) > 0 {
2024-12-04 23:16:50 +01:00
shellProc . Cmd . Write ( ic . InputData )
2024-05-15 08:25:21 +02:00
}
if ic . TermSize != nil {
2025-01-02 23:15:32 +01:00
updateTermSize ( shellProc , bc . BlockId , * ic . TermSize )
2024-05-15 08:25:21 +02:00
}
2024-06-17 18:58:28 +02:00
}
} ( )
go func ( ) {
2024-12-31 18:31:55 +01:00
defer func ( ) {
panichandler . PanicHandler ( "blockcontroller:shellproc-output-loop" , recover ( ) )
} ( )
2024-06-17 18:58:28 +02:00
// handles outputCh -> shellInputCh
2024-08-14 01:52:35 +02:00
for msg := range wshProxy . ToRemoteCh {
2024-10-21 23:05:52 +02:00
encodedMsg , err := wshutil . EncodeWaveOSCBytes ( wshutil . WaveServerOSC , msg )
if err != nil {
log . Printf ( "error encoding OSC message: %v\n" , err )
}
2024-07-18 00:24:43 +02:00
shellInputCh <- & BlockInputUnion { InputData : encodedMsg }
2024-05-15 08:25:21 +02:00
}
} ( )
2024-06-24 23:34:31 +02:00
go func ( ) {
2024-12-31 18:31:55 +01:00
defer func ( ) {
panichandler . PanicHandler ( "blockcontroller:shellproc-wait-loop" , recover ( ) )
} ( )
2024-06-24 23:34:31 +02:00
// wait for the shell to finish
2024-12-04 23:16:50 +01:00
var exitCode int
2024-06-24 23:34:31 +02:00
defer func ( ) {
2024-08-19 06:26:44 +02:00
wshutil . DefaultRouter . UnregisterRoute ( wshutil . MakeControllerRouteId ( bc . BlockId ) )
2024-06-24 23:34:31 +02:00
bc . UpdateControllerAndSendUpdate ( func ( ) bool {
2024-12-06 20:08:51 +01:00
if bc . ShellProcStatus == Status_Running {
bc . ShellProcStatus = Status_Done
}
2024-12-04 23:16:50 +01:00
bc . ShellProcExitCode = exitCode
2024-06-24 23:34:31 +02:00
return true
} )
log . Printf ( "[shellproc] shell process wait loop done\n" )
} ( )
waitErr := shellProc . Cmd . Wait ( )
2024-12-04 23:16:50 +01:00
exitCode = shellProc . Cmd . ExitCode ( )
2024-08-24 03:12:40 +02:00
shellProc . SetWaitErrorAndSignalDone ( waitErr )
2024-12-04 23:16:50 +01:00
go checkCloseOnExit ( bc . BlockId , exitCode )
2024-06-24 23:34:31 +02:00
} ( )
2024-05-15 08:25:21 +02:00
return nil
}
2025-01-02 23:15:32 +01:00
func updateTermSize ( shellProc * shellexec . ShellProc , blockId string , termSize waveobj . TermSize ) {
err := setTermSizeInDB ( blockId , termSize )
if err != nil {
log . Printf ( "error setting pty size: %v\n" , err )
}
err = shellProc . Cmd . SetSize ( termSize . Rows , termSize . Cols )
if err != nil {
log . Printf ( "error setting pty size: %v\n" , err )
}
}
2024-12-04 23:16:50 +01:00
func checkCloseOnExit ( blockId string , exitCode int ) {
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , DefaultTimeout )
defer cancelFn ( )
blockData , err := wstore . DBMustGet [ * waveobj . Block ] ( ctx , blockId )
if err != nil {
log . Printf ( "error getting block data: %v\n" , err )
return
}
closeOnExit := blockData . Meta . GetBool ( waveobj . MetaKey_CmdCloseOnExit , false )
closeOnExitForce := blockData . Meta . GetBool ( waveobj . MetaKey_CmdCloseOnExitForce , false )
if ! closeOnExitForce && ! ( closeOnExit && exitCode == 0 ) {
return
}
delayMs := blockData . Meta . GetFloat ( waveobj . MetaKey_CmdCloseOnExitDelay , 2000 )
if delayMs < 0 {
delayMs = 0
}
time . Sleep ( time . Duration ( delayMs ) * time . Millisecond )
rpcClient := wshclient . GetBareRpcClient ( )
err = wshclient . DeleteBlockCommand ( rpcClient , wshrpc . CommandDeleteBlockData { BlockId : blockId } , nil )
if err != nil {
log . Printf ( "error deleting block data (close on exit): %v\n" , err )
}
}
2024-06-24 23:34:31 +02:00
func getBoolFromMeta ( meta map [ string ] any , key string , def bool ) bool {
ival , found := meta [ key ]
if ! found || ival == nil {
return def
}
if val , ok := ival . ( bool ) ; ok {
return val
}
return def
}
2024-08-20 23:56:48 +02:00
func getTermSize ( bdata * waveobj . Block ) waveobj . TermSize {
2024-08-13 01:14:19 +02:00
if bdata . RuntimeOpts != nil {
return bdata . RuntimeOpts . TermSize
} else {
2024-08-20 23:56:48 +02:00
return waveobj . TermSize {
2024-08-13 01:14:19 +02:00
Rows : 25 ,
Cols : 80 ,
}
}
}
2025-01-02 23:15:32 +01:00
func setTermSizeInDB ( blockId string , termSize waveobj . TermSize ) error {
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , 2 * time . Second )
defer cancelFn ( )
2024-08-30 05:13:02 +02:00
ctx = waveobj . ContextWithUpdates ( ctx )
2025-01-02 23:15:32 +01:00
bdata , err := wstore . DBMustGet [ * waveobj . Block ] ( ctx , blockId )
2024-08-30 05:13:02 +02:00
if err != nil {
return fmt . Errorf ( "error getting block data: %v" , err )
}
if bdata . RuntimeOpts == nil {
2025-01-02 23:15:32 +01:00
bdata . RuntimeOpts = & waveobj . RuntimeOpts { }
2024-08-30 05:13:02 +02:00
}
bdata . RuntimeOpts . TermSize = termSize
2025-01-02 23:15:32 +01:00
err = wstore . DBUpdate ( ctx , bdata )
if err != nil {
return fmt . Errorf ( "error updating block data: %v" , err )
}
2024-08-30 05:13:02 +02:00
updates := waveobj . ContextGetUpdatesRtn ( ctx )
2024-09-12 03:03:55 +02:00
wps . Broker . SendUpdateEvents ( updates )
2024-08-30 05:13:02 +02:00
return nil
}
2024-12-06 20:08:51 +01:00
func ( bc * BlockController ) LockRunLock ( ) bool {
rtn := bc . RunLock . CompareAndSwap ( false , true )
if rtn {
log . Printf ( "block %q run() lock\n" , bc . BlockId )
}
return rtn
}
func ( bc * BlockController ) UnlockRunLock ( ) {
bc . RunLock . Store ( false )
log . Printf ( "block %q run() unlock\n" , bc . BlockId )
}
2025-01-16 20:17:29 +01:00
func ( bc * BlockController ) run ( logCtx context . Context , bdata * waveobj . Block , blockMeta map [ string ] any , rtOpts * waveobj . RuntimeOpts , force bool ) {
2025-01-23 01:04:08 +01:00
blocklogger . Debugf ( logCtx , "[conndebug] BlockController.run() %q\n" , bc . BlockId )
2024-12-06 20:08:51 +01:00
runningShellCommand := false
ok := bc . LockRunLock ( )
if ! ok {
log . Printf ( "block %q is already executing run()\n" , bc . BlockId )
return
}
defer func ( ) {
if ! runningShellCommand {
bc . UnlockRunLock ( )
}
} ( )
2024-12-04 23:16:50 +01:00
curStatus := bc . GetRuntimeStatus ( )
2024-08-20 23:56:48 +02:00
controllerName := bdata . Meta . GetString ( waveobj . MetaKey_Controller , "" )
2024-07-30 21:33:28 +02:00
if controllerName != BlockController_Shell && controllerName != BlockController_Cmd {
log . Printf ( "unknown controller %q\n" , controllerName )
2024-06-24 23:34:31 +02:00
return
}
2024-12-04 23:16:50 +01:00
runOnce := getBoolFromMeta ( blockMeta , waveobj . MetaKey_CmdRunOnce , false )
2024-08-20 23:56:48 +02:00
runOnStart := getBoolFromMeta ( blockMeta , waveobj . MetaKey_CmdRunOnStart , true )
2024-12-04 23:16:50 +01:00
if ( ( runOnStart || runOnce ) && curStatus . ShellProcStatus == Status_Init ) || force {
if getBoolFromMeta ( blockMeta , waveobj . MetaKey_CmdClearOnStart , false ) {
err := HandleTruncateBlockFile ( bc . BlockId )
if err != nil {
log . Printf ( "error truncating term blockfile: %v\n" , err )
}
}
if runOnce {
ctx , cancelFn := context . WithTimeout ( context . Background ( ) , 2 * time . Second )
defer cancelFn ( )
metaUpdate := map [ string ] any {
waveobj . MetaKey_CmdRunOnce : false ,
waveobj . MetaKey_CmdRunOnStart : false ,
}
2024-12-09 23:48:16 +01:00
err := wstore . UpdateObjectMeta ( ctx , waveobj . MakeORef ( waveobj . OType_Block , bc . BlockId ) , metaUpdate , false )
2024-12-04 23:16:50 +01:00
if err != nil {
log . Printf ( "error updating block meta (in blockcontroller.run): %v\n" , err )
return
}
}
2024-12-06 20:08:51 +01:00
runningShellCommand = true
2024-06-24 23:34:31 +02:00
go func ( ) {
2024-12-31 18:31:55 +01:00
defer func ( ) {
panichandler . PanicHandler ( "blockcontroller:run-shell-command" , recover ( ) )
} ( )
2024-12-06 20:08:51 +01:00
defer bc . UnlockRunLock ( )
2024-09-05 09:21:08 +02:00
var termSize waveobj . TermSize
if rtOpts != nil {
termSize = rtOpts . TermSize
} else {
termSize = getTermSize ( bdata )
}
2025-01-16 20:17:29 +01:00
err := bc . DoRunShellCommand ( logCtx , & RunShellOpts { TermSize : termSize } , bdata . Meta )
2024-06-24 23:34:31 +02:00
if err != nil {
2025-01-16 20:17:29 +01:00
debugLog ( logCtx , "error running shell: %v\n" , err )
2024-06-24 23:34:31 +02:00
}
} ( )
}
2024-07-18 00:24:43 +02:00
}
2024-05-14 22:34:41 +02:00
2024-07-18 00:24:43 +02:00
func ( bc * BlockController ) SendInput ( inputUnion * BlockInputUnion ) error {
2024-09-05 09:21:08 +02:00
var shellInputCh chan * BlockInputUnion
bc . WithLock ( func ( ) {
shellInputCh = bc . ShellInputCh
} )
if shellInputCh == nil {
2024-07-18 00:24:43 +02:00
return fmt . Errorf ( "no shell input chan" )
}
2024-09-05 09:21:08 +02:00
shellInputCh <- inputUnion
2024-07-18 00:24:43 +02:00
return nil
}
2024-06-24 23:34:31 +02:00
2024-09-05 09:21:08 +02:00
func CheckConnStatus ( blockId string ) error {
bdata , err := wstore . DBMustGet [ * waveobj . Block ] ( context . Background ( ) , blockId )
if err != nil {
return fmt . Errorf ( "error getting block: %w" , err )
2024-08-24 03:12:40 +02:00
}
2024-09-05 09:21:08 +02:00
connName := bdata . Meta . GetString ( waveobj . MetaKey_Connection , "" )
if connName == "" {
return nil
}
2024-10-24 07:43:17 +02:00
if strings . HasPrefix ( connName , "wsl://" ) {
distroName := strings . TrimPrefix ( connName , "wsl://" )
2025-01-23 01:04:08 +01:00
conn := wslconn . GetWslConn ( distroName )
2024-10-24 07:43:17 +02:00
connStatus := conn . DeriveConnStatus ( )
if connStatus . Status != conncontroller . Status_Connected {
return fmt . Errorf ( "not connected: %s" , connStatus . Status )
}
return nil
}
2024-09-05 09:21:08 +02:00
opts , err := remote . ParseOpts ( connName )
if err != nil {
return fmt . Errorf ( "error parsing connection name: %w" , err )
}
2025-01-23 01:04:08 +01:00
conn := conncontroller . GetConn ( opts )
2024-09-05 09:21:08 +02:00
connStatus := conn . DeriveConnStatus ( )
if connStatus . Status != conncontroller . Status_Connected {
return fmt . Errorf ( "not connected: %s" , connStatus . Status )
}
return nil
}
2024-08-24 03:12:40 +02:00
2024-09-05 09:21:08 +02:00
func ( bc * BlockController ) StopShellProc ( shouldWait bool ) {
bc . Lock . Lock ( )
defer bc . Lock . Unlock ( )
2024-12-06 20:08:51 +01:00
if bc . ShellProc == nil || bc . ShellProcStatus == Status_Done || bc . ShellProcStatus == Status_Init {
2024-09-05 09:21:08 +02:00
return
}
bc . ShellProc . Close ( )
if shouldWait {
2024-08-24 03:12:40 +02:00
doneCh := bc . ShellProc . DoneCh
<- doneCh
}
2024-09-05 09:21:08 +02:00
}
2024-08-24 03:12:40 +02:00
2024-09-05 09:21:08 +02:00
func getOrCreateBlockController ( tabId string , blockId string , controllerName string ) * BlockController {
var createdController bool
var bc * BlockController
defer func ( ) {
if ! createdController || bc == nil {
return
}
bc . UpdateControllerAndSendUpdate ( func ( ) bool {
return true
} )
} ( )
globalLock . Lock ( )
defer globalLock . Unlock ( )
bc = blockControllerMap [ blockId ]
if bc == nil {
bc = & BlockController {
Lock : & sync . Mutex { } ,
ControllerType : controllerName ,
TabId : tabId ,
BlockId : blockId ,
2024-12-04 23:16:50 +01:00
ShellProcStatus : Status_Init ,
2024-12-06 20:08:51 +01:00
RunLock : & atomic . Bool { } ,
2024-09-05 09:21:08 +02:00
}
blockControllerMap [ blockId ] = bc
createdController = true
}
return bc
}
2025-01-10 23:09:32 +01:00
func formatConnNameForLog ( connName string ) string {
if connName == "" {
return "local"
}
return connName
}
2024-12-04 23:16:50 +01:00
func ResyncController ( ctx context . Context , tabId string , blockId string , rtOpts * waveobj . RuntimeOpts , force bool ) error {
2024-09-05 09:21:08 +02:00
if tabId == "" || blockId == "" {
return fmt . Errorf ( "invalid tabId or blockId passed to ResyncController" )
}
blockData , err := wstore . DBMustGet [ * waveobj . Block ] ( ctx , blockId )
2024-07-18 00:24:43 +02:00
if err != nil {
return fmt . Errorf ( "error getting block: %w" , err )
2024-05-14 22:34:41 +02:00
}
2024-12-04 23:16:50 +01:00
if force {
StopBlockController ( blockId )
2025-01-10 23:09:32 +01:00
time . Sleep ( 100 * time . Millisecond ) // TODO see if we can remove this (the "process finished with exit code" message comes out after we start reconnecting otherwise)
2024-12-04 23:16:50 +01:00
}
2024-09-05 09:21:08 +02:00
connName := blockData . Meta . GetString ( waveobj . MetaKey_Connection , "" )
controllerName := blockData . Meta . GetString ( waveobj . MetaKey_Controller , "" )
curBc := GetBlockController ( blockId )
if controllerName == "" {
if curBc != nil {
StopBlockController ( blockId )
}
return nil
}
2024-12-06 20:08:51 +01:00
log . Printf ( "resync controller %s %q (%q) (force %v)\n" , blockId , controllerName , connName , force )
// check if conn is different, if so, stop the current controller, and set status back to init
2024-09-05 09:21:08 +02:00
if curBc != nil {
bcStatus := curBc . GetRuntimeStatus ( )
if bcStatus . ShellProcStatus == Status_Running && bcStatus . ShellProcConnName != connName {
2025-01-10 23:09:32 +01:00
blocklogger . Infof ( ctx , "\n[conndebug] stopping blockcontroller due to conn change %q => %q\n" , formatConnNameForLog ( bcStatus . ShellProcConnName ) , formatConnNameForLog ( connName ) )
2024-12-06 20:08:51 +01:00
log . Printf ( "stopping blockcontroller %s due to conn change\n" , blockId )
StopBlockControllerAndSetStatus ( blockId , Status_Init )
2025-01-10 23:09:32 +01:00
time . Sleep ( 100 * time . Millisecond ) // TODO see if we can remove this (the "process finished with exit code" message comes out after we start reconnecting otherwise)
2024-09-05 09:21:08 +02:00
}
}
// now if there is a conn, ensure it is connected
if connName != "" {
err = CheckConnStatus ( blockId )
if err != nil {
return fmt . Errorf ( "cannot start shellproc: %w" , err )
}
}
if curBc == nil {
2024-12-04 23:16:50 +01:00
return startBlockController ( ctx , tabId , blockId , rtOpts , force )
2024-09-05 09:21:08 +02:00
}
bcStatus := curBc . GetRuntimeStatus ( )
2024-12-04 23:16:50 +01:00
if bcStatus . ShellProcStatus == Status_Init || bcStatus . ShellProcStatus == Status_Done {
return startBlockController ( ctx , tabId , blockId , rtOpts , force )
2024-07-18 00:24:43 +02:00
}
return nil
2024-05-14 22:34:41 +02:00
}
2025-01-16 20:17:29 +01:00
func debugLog ( ctx context . Context , fmtStr string , args ... interface { } ) {
blocklogger . Infof ( ctx , "[conndebug] " + fmtStr , args ... )
log . Printf ( fmtStr , args ... )
}
2024-12-04 23:16:50 +01:00
func startBlockController ( ctx context . Context , tabId string , blockId string , rtOpts * waveobj . RuntimeOpts , force bool ) error {
2024-08-20 23:56:48 +02:00
blockData , err := wstore . DBMustGet [ * waveobj . Block ] ( ctx , blockId )
2024-05-28 00:44:57 +02:00
if err != nil {
return fmt . Errorf ( "error getting block: %w" , err )
}
2024-08-20 23:56:48 +02:00
controllerName := blockData . Meta . GetString ( waveobj . MetaKey_Controller , "" )
2024-07-30 21:33:28 +02:00
if controllerName == "" {
2024-05-28 00:44:57 +02:00
// nothing to start
return nil
}
2024-07-30 21:33:28 +02:00
if controllerName != BlockController_Shell && controllerName != BlockController_Cmd {
return fmt . Errorf ( "unknown controller %q" , controllerName )
2024-05-16 09:29:58 +02:00
}
2024-09-05 09:21:08 +02:00
connName := blockData . Meta . GetString ( waveobj . MetaKey_Connection , "" )
err = CheckConnStatus ( blockId )
if err != nil {
return fmt . Errorf ( "cannot start shellproc: %w" , err )
}
bc := getOrCreateBlockController ( tabId , blockId , controllerName )
bcStatus := bc . GetRuntimeStatus ( )
2025-01-16 20:17:29 +01:00
debugLog ( ctx , "start blockcontroller %s %q (%q) (curstatus %s) (force %v)\n" , blockId , controllerName , connName , bcStatus . ShellProcStatus , force )
2024-12-04 23:16:50 +01:00
if bcStatus . ShellProcStatus == Status_Init || bcStatus . ShellProcStatus == Status_Done {
2025-01-16 20:17:29 +01:00
go bc . run ( ctx , blockData , blockData . Meta , rtOpts , force )
2024-05-15 01:53:03 +02:00
}
2024-05-28 00:44:57 +02:00
return nil
}
2024-12-06 20:08:51 +01:00
func StopBlockControllerAndSetStatus ( blockId string , newStatus string ) {
2024-05-28 00:44:57 +02:00
bc := GetBlockController ( blockId )
if bc == nil {
return
}
2024-06-24 23:34:31 +02:00
if bc . getShellProc ( ) != nil {
bc . ShellProc . Close ( )
2024-09-05 09:21:08 +02:00
<- bc . ShellProc . DoneCh
bc . UpdateControllerAndSendUpdate ( func ( ) bool {
2024-12-06 20:08:51 +01:00
bc . ShellProcStatus = newStatus
2024-09-05 09:21:08 +02:00
return true
} )
}
}
2024-12-06 20:08:51 +01:00
func StopBlockController ( blockId string ) {
StopBlockControllerAndSetStatus ( blockId , Status_Done )
}
2024-09-05 09:21:08 +02:00
func getControllerList ( ) [ ] * BlockController {
globalLock . Lock ( )
defer globalLock . Unlock ( )
var rtn [ ] * BlockController
for _ , bc := range blockControllerMap {
rtn = append ( rtn , bc )
}
return rtn
}
func StopAllBlockControllers ( ) {
clist := getControllerList ( )
for _ , bc := range clist {
if bc . ShellProcStatus == Status_Running {
go StopBlockController ( bc . BlockId )
}
2024-06-24 23:34:31 +02:00
}
2024-05-14 22:34:41 +02:00
}
func GetBlockController ( blockId string ) * BlockController {
globalLock . Lock ( )
defer globalLock . Unlock ( )
return blockControllerMap [ blockId ]
}