2022-07-16 02:37:32 +02:00
package cmdrunner
import (
2022-08-23 23:01:52 +02:00
"bytes"
2022-07-16 02:37:32 +02:00
"context"
"fmt"
2022-09-01 21:47:10 +02:00
"os"
2022-08-09 23:24:57 +02:00
"path"
"path/filepath"
2022-08-11 03:33:32 +02:00
"regexp"
"sort"
2022-07-16 02:37:32 +02:00
"strconv"
"strings"
2022-08-12 08:45:15 +02:00
"time"
2022-07-16 02:37:32 +02:00
2022-08-23 03:53:38 +02:00
"github.com/alessio/shellescape"
2022-07-16 02:37:32 +02:00
"github.com/google/uuid"
"github.com/scripthaus-dev/mshell/pkg/base"
"github.com/scripthaus-dev/mshell/pkg/packet"
2022-08-23 03:38:52 +02:00
"github.com/scripthaus-dev/mshell/pkg/shexec"
2022-07-16 02:37:32 +02:00
"github.com/scripthaus-dev/sh2-server/pkg/remote"
2022-09-21 02:37:49 +02:00
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
2022-07-16 02:37:32 +02:00
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
)
2022-08-31 22:28:52 +02:00
const (
HistoryTypeWindow = "window"
HistoryTypeSession = "session"
HistoryTypeGlobal = "global"
)
2022-07-16 02:37:32 +02:00
const DefaultUserId = "sawka"
2022-08-27 01:21:19 +02:00
const MaxNameLen = 50
2022-09-01 21:47:10 +02:00
const MaxRemoteAliasLen = 50
2022-10-04 04:04:48 +02:00
const PasswordUnchangedSentinel = "--unchanged--"
2022-08-27 01:21:19 +02:00
2022-08-27 06:44:18 +02:00
var ColorNames = [ ] string { "black" , "red" , "green" , "yellow" , "blue" , "magenta" , "cyan" , "white" , "orange" }
2022-09-01 21:47:10 +02:00
var RemoteColorNames = [ ] string { "red" , "green" , "yellow" , "blue" , "magenta" , "cyan" , "white" , "orange" }
2022-10-04 04:04:48 +02:00
var RemoteSetArgs = [ ] string { "alias" , "connectmode" , "key" , "password" , "autoinstall" , "color" }
2022-08-27 06:44:18 +02:00
2022-10-27 09:33:50 +02:00
var WindowCmds = [ ] string { "run" , "comment" , "cd" , "cr" , "clear" , "sw" , "alias" , "unalias" , "function" , "reset" }
2022-10-22 23:46:39 +02:00
var NoHistCmds = [ ] string { "compgen" , "line" , "history" }
var GlobalCmds = [ ] string { "session" , "screen" , "remote" }
2022-09-01 21:47:10 +02:00
var hostNameRe = regexp . MustCompile ( "^[a-z][a-z0-9.-]*$" )
2022-10-03 21:25:43 +02:00
var userHostRe = regexp . MustCompile ( "^(sudo@)?([a-z][a-z0-9-]*)@([a-z][a-z0-9.-]*)(?::([0-9]+))?$" )
2022-09-01 21:47:10 +02:00
var remoteAliasRe = regexp . MustCompile ( "^[a-zA-Z][a-zA-Z0-9_-]*$" )
2022-08-27 01:24:07 +02:00
var genericNameRe = regexp . MustCompile ( "^[a-zA-Z][a-zA-Z0-9_ .()<>,/\"'\\[\\]{}=+$@!*-]*$" )
2022-10-07 10:08:03 +02:00
var positionRe = regexp . MustCompile ( "^((S?\\+|E?-)?[0-9]+|(\\+|-|S|E))$" )
2022-08-27 01:24:07 +02:00
var wsRe = regexp . MustCompile ( "\\s+" )
2022-08-27 01:21:19 +02:00
2022-09-13 21:06:12 +02:00
type contextType string
var historyContextKey = contextType ( "history" )
type historyContextType struct {
LineId string
CmdId string
RemotePtr * sstore . RemotePtrType
}
2022-08-27 02:17:33 +02:00
type MetaCmdFnType = func ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error )
type MetaCmdEntryType struct {
IsAlias bool
Fn MetaCmdFnType
}
2022-08-17 21:24:09 +02:00
2022-08-27 02:17:33 +02:00
var MetaCmdFnMap = make ( map [ string ] MetaCmdEntryType )
func init ( ) {
registerCmdFn ( "run" , RunCommand )
registerCmdFn ( "eval" , EvalCommand )
registerCmdFn ( "comment" , CommentCommand )
2022-10-27 09:33:50 +02:00
// registerCmdFn("cd", CdCommand)
2022-08-27 02:17:33 +02:00
registerCmdFn ( "cr" , CrCommand )
registerCmdFn ( "compgen" , CompGenCommand )
2022-08-27 07:01:29 +02:00
registerCmdFn ( "clear" , ClearCommand )
2022-10-25 06:29:11 +02:00
registerCmdFn ( "reset" , ResetCommand )
2022-08-27 02:17:33 +02:00
registerCmdFn ( "session" , SessionCommand )
registerCmdFn ( "session:open" , SessionOpenCommand )
registerCmdAlias ( "session:new" , SessionOpenCommand )
registerCmdFn ( "session:set" , SessionSetCommand )
2022-09-13 21:06:12 +02:00
registerCmdFn ( "session:delete" , SessionDeleteCommand )
2022-08-27 02:17:33 +02:00
registerCmdFn ( "screen" , ScreenCommand )
registerCmdFn ( "screen:close" , ScreenCloseCommand )
registerCmdFn ( "screen:open" , ScreenOpenCommand )
registerCmdAlias ( "screen:new" , ScreenOpenCommand )
2022-08-27 02:51:28 +02:00
registerCmdFn ( "screen:set" , ScreenSetCommand )
2022-08-27 02:17:33 +02:00
registerCmdAlias ( "remote" , RemoteCommand )
registerCmdFn ( "remote:show" , RemoteShowCommand )
2022-08-28 23:24:05 +02:00
registerCmdFn ( "remote:showall" , RemoteShowAllCommand )
2022-09-01 08:12:26 +02:00
registerCmdFn ( "remote:new" , RemoteNewCommand )
2022-09-13 21:06:12 +02:00
registerCmdFn ( "remote:archive" , RemoteArchiveCommand )
registerCmdFn ( "remote:set" , RemoteSetCommand )
2022-10-03 03:52:55 +02:00
registerCmdAlias ( "remote:edit" , RemoteSetCommand )
2022-09-01 21:47:10 +02:00
registerCmdFn ( "remote:disconnect" , RemoteDisconnectCommand )
registerCmdFn ( "remote:connect" , RemoteConnectCommand )
2022-09-27 06:09:43 +02:00
registerCmdFn ( "remote:install" , RemoteInstallCommand )
2022-09-27 08:23:04 +02:00
registerCmdFn ( "remote:installcancel" , RemoteInstallCancelCommand )
2022-08-28 23:24:05 +02:00
2022-10-07 03:33:54 +02:00
registerCmdFn ( "sw:set" , SwSetCommand )
2022-10-22 23:46:39 +02:00
registerCmdFn ( "sw:resize" , SwResizeCommand )
2022-10-07 03:33:54 +02:00
2022-10-22 23:46:39 +02:00
// sw:resize
registerCmdFn ( "window:resize" , SwResizeCommand )
2022-09-06 05:08:59 +02:00
2022-09-13 21:06:12 +02:00
registerCmdFn ( "line" , LineCommand )
registerCmdFn ( "line:show" , LineShowCommand )
2022-08-28 23:24:05 +02:00
registerCmdFn ( "history" , HistoryCommand )
2022-08-27 02:17:33 +02:00
}
2022-08-11 03:33:32 +02:00
2022-08-27 02:17:33 +02:00
func getValidCommands ( ) [ ] string {
var rtn [ ] string
for key , val := range MetaCmdFnMap {
if val . IsAlias {
continue
}
2022-09-27 08:23:04 +02:00
rtn = append ( rtn , "/" + key )
2022-08-27 02:17:33 +02:00
}
return rtn
}
2022-08-21 21:31:29 +02:00
2022-08-27 02:17:33 +02:00
func registerCmdFn ( cmdName string , fn MetaCmdFnType ) {
MetaCmdFnMap [ cmdName ] = MetaCmdEntryType { Fn : fn }
}
2022-08-23 01:00:25 +02:00
2022-08-27 02:17:33 +02:00
func registerCmdAlias ( cmdName string , fn MetaCmdFnType ) {
MetaCmdFnMap [ cmdName ] = MetaCmdEntryType { IsAlias : true , Fn : fn }
}
2022-08-23 23:01:52 +02:00
2022-08-27 02:17:33 +02:00
func HandleCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
metaCmd := SubMetaCmd ( pk . MetaCmd )
var cmdName string
if pk . MetaSubCmd == "" {
cmdName = metaCmd
} else {
cmdName = fmt . Sprintf ( "%s:%s" , pk . MetaCmd , pk . MetaSubCmd )
}
entry := MetaCmdFnMap [ cmdName ]
if entry . Fn == nil {
if MetaCmdFnMap [ metaCmd ] . Fn != nil {
return nil , fmt . Errorf ( "invalid /%s subcommand '%s'" , metaCmd , pk . MetaSubCmd )
}
return nil , fmt . Errorf ( "invalid command '/%s', no handler" , cmdName )
2022-08-11 03:33:32 +02:00
}
2022-08-27 02:17:33 +02:00
return entry . Fn ( ctx , pk )
2022-08-11 03:33:32 +02:00
}
2022-07-16 02:37:32 +02:00
func firstArg ( pk * scpacket . FeCommandPacketType ) string {
if len ( pk . Args ) == 0 {
return ""
}
return pk . Args [ 0 ]
}
2022-08-09 23:24:57 +02:00
func argN ( pk * scpacket . FeCommandPacketType , n int ) string {
if len ( pk . Args ) <= n {
return ""
}
return pk . Args [ n ]
}
2022-07-16 02:37:32 +02:00
func resolveBool ( arg string , def bool ) bool {
if arg == "" {
return def
}
if arg == "0" || arg == "false" {
return false
}
return true
}
2022-10-01 22:23:36 +02:00
func resolveFile ( arg string ) ( string , error ) {
if arg == "" {
return "" , nil
}
fileName := base . ExpandHomeDir ( arg )
if ! strings . HasPrefix ( fileName , "/" ) {
return "" , fmt . Errorf ( "must be absolute, cannot be a relative path" )
}
fd , err := os . Open ( fileName )
if fd != nil {
fd . Close ( )
}
if err != nil {
return "" , fmt . Errorf ( "cannot open file: %v" , err )
}
return fileName , nil
}
func resolvePosInt ( arg string , def int ) ( int , error ) {
2022-08-28 23:24:05 +02:00
if arg == "" {
return def , nil
}
ival , err := strconv . Atoi ( arg )
if err != nil {
return 0 , err
}
2022-10-01 22:23:36 +02:00
if ival <= 0 {
return 0 , fmt . Errorf ( "must be greater than 0" )
}
2022-08-28 23:24:05 +02:00
return ival , nil
}
2022-10-07 03:33:54 +02:00
func resolveNonNegInt ( arg string , def int ) ( int , error ) {
if arg == "" {
return def , nil
}
ival , err := strconv . Atoi ( arg )
if err != nil {
return 0 , err
}
if ival < 0 {
return 0 , fmt . Errorf ( "cannot be negative" )
}
return ival , nil
}
2022-10-22 23:46:39 +02:00
func getUITermOpts ( uiContext * scpacket . UIContextType ) * packet . TermOpts {
termOpts := & packet . TermOpts { Rows : shexec . DefaultTermRows , Cols : shexec . DefaultTermCols , Term : remote . DefaultTerm , MaxPtySize : shexec . DefaultMaxPtySize }
if uiContext != nil && uiContext . TermOpts != nil {
pkOpts := uiContext . TermOpts
if pkOpts . Cols > 0 {
termOpts . Cols = base . BoundInt ( pkOpts . Cols , shexec . MinTermCols , shexec . MaxTermCols )
}
if pkOpts . MaxPtySize > 0 {
termOpts . MaxPtySize = base . BoundInt64 ( pkOpts . MaxPtySize , shexec . MinMaxPtySize , shexec . MaxMaxPtySize )
}
}
return termOpts
}
2022-07-16 02:37:32 +02:00
func RunCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-10-11 02:30:48 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window | R_RemoteConnected )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/run error: %w" , err )
2022-07-16 02:37:32 +02:00
}
2022-09-21 02:37:49 +02:00
cmdId := scbase . GenSCUUID ( )
2022-07-16 02:37:32 +02:00
cmdStr := firstArg ( pk )
2022-10-27 09:33:50 +02:00
isRtnStateCmd := IsReturnStateCommand ( cmdStr )
2022-07-16 02:37:32 +02:00
runPacket := packet . MakeRunPacket ( )
runPacket . ReqId = uuid . New ( ) . String ( )
runPacket . CK = base . MakeCommandKey ( ids . SessionId , cmdId )
2022-10-17 08:51:04 +02:00
runPacket . State = ids . Remote . RemoteState
runPacket . StateComplete = true
2022-07-16 02:37:32 +02:00
runPacket . UsePty = true
2022-10-22 23:46:39 +02:00
runPacket . TermOpts = getUITermOpts ( pk . UIContext )
2022-07-16 02:37:32 +02:00
runPacket . Command = strings . TrimSpace ( cmdStr )
2022-10-27 09:33:50 +02:00
runPacket . ReturnState = resolveBool ( pk . Kwargs [ "rtnstate" ] , isRtnStateCmd )
2022-09-05 23:49:23 +02:00
cmd , callback , err := remote . RunCommand ( ctx , cmdId , ids . Remote . RemotePtr , ids . Remote . RemoteState , runPacket )
if callback != nil {
defer callback ( )
}
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
2022-10-19 03:03:02 +02:00
update , err := addLineForCmd ( ctx , "/run" , true , ids , cmd )
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
2022-10-19 03:03:02 +02:00
update . Interactive = pk . Interactive
2022-09-05 23:54:17 +02:00
sstore . MainBus . SendUpdate ( ids . SessionId , update )
return nil , nil
2022-07-16 02:37:32 +02:00
}
2022-09-13 21:06:12 +02:00
func addToHistory ( ctx context . Context , pk * scpacket . FeCommandPacketType , historyContext historyContextType , isMetaCmd bool , hadError bool ) error {
2022-08-12 08:45:15 +02:00
cmdStr := firstArg ( pk )
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
2022-08-12 08:45:15 +02:00
if err != nil {
return err
}
hitem := & sstore . HistoryItemType {
2022-09-21 02:37:49 +02:00
HistoryId : scbase . GenSCUUID ( ) ,
2022-08-12 08:45:15 +02:00
Ts : time . Now ( ) . UnixMilli ( ) ,
UserId : DefaultUserId ,
SessionId : ids . SessionId ,
ScreenId : ids . ScreenId ,
WindowId : ids . WindowId ,
2022-09-13 21:06:12 +02:00
LineId : historyContext . LineId ,
2022-08-12 08:45:15 +02:00
HadError : hadError ,
2022-09-13 21:06:12 +02:00
CmdId : historyContext . CmdId ,
2022-08-12 08:45:15 +02:00
CmdStr : cmdStr ,
2022-08-28 23:24:05 +02:00
IsMetaCmd : isMetaCmd ,
}
2022-09-13 21:06:12 +02:00
if ! isMetaCmd && historyContext . RemotePtr != nil {
hitem . Remote = * historyContext . RemotePtr
2022-08-12 08:45:15 +02:00
}
err = sstore . InsertHistoryItem ( ctx , hitem )
if err != nil {
return err
}
2022-08-11 21:07:41 +02:00
return nil
}
2022-07-16 02:37:32 +02:00
func EvalCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
if len ( pk . Args ) == 0 {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "usage: /eval [command], no command passed to eval" )
2022-07-16 02:37:32 +02:00
}
2022-09-13 21:06:12 +02:00
var historyContext historyContextType
ctxWithHistory := context . WithValue ( ctx , historyContextKey , & historyContext )
2022-08-31 09:01:42 +02:00
var update sstore . UpdatePacket
2022-09-13 21:06:12 +02:00
newPk , rtnErr := EvalMetaCommand ( ctxWithHistory , pk )
2022-08-31 09:01:42 +02:00
if rtnErr == nil {
2022-09-13 21:06:12 +02:00
update , rtnErr = HandleCommand ( ctxWithHistory , newPk )
2022-07-16 02:37:32 +02:00
}
2022-08-11 21:07:41 +02:00
if ! resolveBool ( pk . Kwargs [ "nohist" ] , false ) {
2022-09-13 21:06:12 +02:00
err := addToHistory ( ctx , pk , historyContext , ( newPk . MetaCmd != "run" ) , ( rtnErr != nil ) )
2022-08-12 08:45:15 +02:00
if err != nil {
2022-08-23 03:38:52 +02:00
fmt . Printf ( "[error] adding to history: %v\n" , err )
2022-08-12 08:45:15 +02:00
// continue...
}
2022-08-11 21:07:41 +02:00
}
2022-08-31 09:01:42 +02:00
return update , rtnErr
2022-08-12 08:45:15 +02:00
}
2022-08-27 02:17:33 +02:00
func ScreenCloseCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , fmt . Errorf ( "/screen:close cannot close screen: %w" , err )
2022-07-16 02:37:32 +02:00
}
2022-08-27 02:17:33 +02:00
update , err := sstore . DeleteScreen ( ctx , ids . SessionId , ids . ScreenId )
if err != nil {
return nil , err
}
return update , nil
}
func ScreenOpenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , fmt . Errorf ( "/screen:open cannot open screen: %w" , err )
}
activate := resolveBool ( pk . Kwargs [ "activate" ] , true )
newName := pk . Kwargs [ "name" ]
if newName != "" {
err := validateName ( newName , "screen" )
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
}
2022-08-27 02:17:33 +02:00
update , err := sstore . InsertScreen ( ctx , ids . SessionId , newName , activate )
if err != nil {
return nil , err
2022-07-16 02:37:32 +02:00
}
2022-08-27 02:17:33 +02:00
return update , nil
}
2022-08-27 02:51:28 +02:00
func ScreenSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen )
2022-08-27 02:51:28 +02:00
if err != nil {
return nil , err
}
var varsUpdated [ ] string
if pk . Kwargs [ "name" ] != "" {
newName := pk . Kwargs [ "name" ]
err = validateName ( newName , "screen" )
if err != nil {
return nil , err
}
err = sstore . SetScreenName ( ctx , ids . SessionId , ids . ScreenId , newName )
if err != nil {
return nil , fmt . Errorf ( "setting screen name: %v" , err )
}
varsUpdated = append ( varsUpdated , "name" )
}
2022-08-27 06:44:18 +02:00
if pk . Kwargs [ "tabcolor" ] != "" {
color := pk . Kwargs [ "tabcolor" ]
err = validateColor ( color , "screen tabcolor" )
if err != nil {
return nil , err
}
screenObj , err := sstore . GetScreenById ( ctx , ids . SessionId , ids . ScreenId )
if err != nil {
return nil , err
}
opts := screenObj . ScreenOpts
if opts == nil {
opts = & sstore . ScreenOptsType { }
}
opts . TabColor = color
err = sstore . SetScreenOpts ( ctx , ids . SessionId , ids . ScreenId , opts )
if err != nil {
return nil , fmt . Errorf ( "setting screen opts: %v" , err )
}
varsUpdated = append ( varsUpdated , "tabcolor" )
}
2022-08-27 02:51:28 +02:00
if len ( varsUpdated ) == 0 {
2022-08-27 06:44:18 +02:00
return nil , fmt . Errorf ( "/screen:set no updates, can set %s" , formatStrs ( [ ] string { "name" , "pos" , "tabcolor" } , "or" , false ) )
2022-08-27 02:51:28 +02:00
}
screenObj , err := sstore . GetScreenById ( ctx , ids . SessionId , ids . ScreenId )
if err != nil {
return nil , err
}
update , session := sstore . MakeSingleSessionUpdate ( ids . SessionId )
session . Screens = append ( session . Screens , screenObj )
update . Info = & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "screen updated %s" , formatStrs ( varsUpdated , "and" , false ) ) ,
TimeoutMs : 2000 ,
}
return update , nil
}
2022-08-27 02:17:33 +02:00
func ScreenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/screen cannot switch to screen: %w" , err )
2022-07-16 02:37:32 +02:00
}
firstArg := firstArg ( pk )
if firstArg == "" {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "usage /screen [screen-name|screen-index|screen-id], no param specified" )
2022-07-16 02:37:32 +02:00
}
2022-09-13 21:06:12 +02:00
ritem , err := resolveSessionScreen ( ctx , ids . SessionId , firstArg , ids . ScreenId )
2022-07-16 02:37:32 +02:00
if err != nil {
return nil , err
}
2022-08-27 02:29:32 +02:00
update , err := sstore . SwitchScreenById ( ctx , ids . SessionId , ritem . Id )
2022-07-16 02:53:23 +02:00
if err != nil {
return nil , err
}
return update , nil
2022-07-16 02:37:32 +02:00
}
2022-10-11 22:23:38 +02:00
var swAnchorRe = regexp . MustCompile ( "^(\\d+)(?::(-?\\d+))?$" )
2022-10-11 10:11:04 +02:00
2022-10-07 03:33:54 +02:00
func SwSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , fmt . Errorf ( "/sw:set cannot resolve current screen-window: %w" , err )
}
var setNonST bool // scrolltop does not receive an update
updateMap := make ( map [ string ] interface { } )
2022-10-11 10:11:04 +02:00
if pk . Kwargs [ "anchor" ] != "" {
m := swAnchorRe . FindStringSubmatch ( pk . Kwargs [ "anchor" ] )
if m == nil {
return nil , fmt . Errorf ( "/sw:set invalid anchor argument (must be [line] or [line]:[offset])" )
}
anchorLine , _ := strconv . Atoi ( m [ 1 ] )
updateMap [ sstore . SWField_AnchorLine ] = anchorLine
if m [ 2 ] != "" {
anchorOffset , _ := strconv . Atoi ( m [ 2 ] )
updateMap [ sstore . SWField_AnchorOffset ] = anchorOffset
2022-10-07 03:33:54 +02:00
}
2022-10-11 10:11:04 +02:00
}
if pk . Kwargs [ "focus" ] != "" {
focusVal := pk . Kwargs [ "focus" ]
if focusVal != sstore . SWFocusInput && focusVal != sstore . SWFocusCmd && focusVal != sstore . SWFocusCmdFg {
return nil , fmt . Errorf ( "/sw:set invalid focus argument %q, must be %s" , focusVal , formatStrs ( [ ] string { sstore . SWFocusInput , sstore . SWFocusCmd , sstore . SWFocusCmdFg } , "or" , false ) )
}
updateMap [ sstore . SWField_Focus ] = focusVal
setNonST = true
2022-10-07 03:33:54 +02:00
}
2022-10-07 10:08:03 +02:00
if pk . Kwargs [ "line" ] != "" {
sw , err := sstore . GetScreenWindowByIds ( ctx , ids . SessionId , ids . ScreenId , ids . WindowId )
if err != nil {
return nil , fmt . Errorf ( "/sw:set cannot get screen-window: %v" , err )
}
var selectedLineStr string
if sw . SelectedLine > 0 {
selectedLineStr = strconv . Itoa ( sw . SelectedLine )
}
ritem , err := resolveLine ( ctx , ids . SessionId , ids . WindowId , pk . Kwargs [ "line" ] , selectedLineStr )
if err != nil {
return nil , fmt . Errorf ( "/sw:set error resolving line: %v" , err )
}
if ritem == nil {
return nil , fmt . Errorf ( "/sw:set could not resolve line %q" , pk . Kwargs [ "line" ] )
}
setNonST = true
updateMap [ sstore . SWField_SelectedLine ] = ritem . Num
}
2022-10-07 03:33:54 +02:00
if len ( updateMap ) == 0 {
2022-10-11 10:11:04 +02:00
return nil , fmt . Errorf ( "/sw:set no updates, can set %s" , formatStrs ( [ ] string { "line" , "scrolltop" , "focus" } , "or" , false ) )
2022-10-07 03:33:54 +02:00
}
sw , err := sstore . UpdateScreenWindow ( ctx , ids . SessionId , ids . ScreenId , ids . WindowId , updateMap )
if err != nil {
return nil , fmt . Errorf ( "/sw:set failed to update: %v" , err )
}
if ! setNonST {
return nil , nil
}
2022-10-11 10:11:04 +02:00
return sstore . ModelUpdate { ScreenWindows : [ ] * sstore . ScreenWindowType { sw } } , nil
2022-10-07 03:33:54 +02:00
}
2022-09-27 06:09:43 +02:00
func RemoteInstallCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-09-27 08:23:04 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
if err != nil {
return nil , err
}
mshell := ids . Remote . MShell
go mshell . RunInstall ( )
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
2022-09-27 06:09:43 +02:00
}
2022-09-27 08:23:04 +02:00
func RemoteInstallCancelCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-09-01 21:47:10 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
if err != nil {
return nil , err
}
2022-09-27 08:23:04 +02:00
mshell := ids . Remote . MShell
go mshell . CancelInstall ( )
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
}
func RemoteConnectCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
if err != nil {
return nil , err
2022-09-16 21:28:09 +02:00
}
2022-09-01 21:47:10 +02:00
go ids . Remote . MShell . Launch ( )
2022-09-27 08:23:04 +02:00
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
2022-09-01 21:47:10 +02:00
}
func RemoteDisconnectCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
if err != nil {
return nil , err
}
force := resolveBool ( pk . Kwargs [ "force" ] , false )
2022-09-27 08:23:04 +02:00
go ids . Remote . MShell . Disconnect ( force )
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
PtyRemoteId : ids . Remote . RemotePtr . RemoteId ,
} ,
} , nil
2022-09-01 21:47:10 +02:00
}
2022-10-04 04:04:48 +02:00
func makeRemoteEditUpdate_new ( err error ) sstore . UpdatePacket {
redit := & sstore . RemoteEditType {
RemoteEdit : true ,
}
2022-09-30 23:46:51 +02:00
if err != nil {
2022-10-04 04:04:48 +02:00
redit . ErrorStr = err . Error ( )
2022-09-30 23:46:51 +02:00
}
update := sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
2022-10-04 04:04:48 +02:00
RemoteEdit : redit ,
2022-09-30 23:46:51 +02:00
} ,
}
2022-10-04 04:04:48 +02:00
return update
2022-09-30 23:46:51 +02:00
}
2022-10-04 04:04:48 +02:00
func makeRemoteEditErrorReturn_new ( visual bool , err error ) ( sstore . UpdatePacket , error ) {
2022-09-30 23:46:51 +02:00
if visual {
2022-10-04 04:04:48 +02:00
return makeRemoteEditUpdate_new ( err ) , nil
}
return nil , err
}
func makeRemoteEditUpdate_edit ( ids resolvedIds , err error ) sstore . UpdatePacket {
redit := & sstore . RemoteEditType {
RemoteEdit : true ,
}
redit . RemoteId = ids . Remote . RemotePtr . RemoteId
if ids . Remote . RemoteCopy . SSHOpts != nil {
redit . KeyStr = ids . Remote . RemoteCopy . SSHOpts . SSHIdentity
redit . HasPassword = ( ids . Remote . RemoteCopy . SSHOpts . SSHPassword != "" )
}
if err != nil {
redit . ErrorStr = err . Error ( )
}
update := sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
RemoteEdit : redit ,
} ,
}
return update
}
func makeRemoteEditErrorReturn_edit ( ids resolvedIds , visual bool , err error ) ( sstore . UpdatePacket , error ) {
if visual {
return makeRemoteEditUpdate_edit ( ids , err ) , nil
2022-09-30 23:46:51 +02:00
}
return nil , err
}
2022-10-01 22:23:36 +02:00
type RemoteEditArgs struct {
2022-10-03 21:25:43 +02:00
CanonicalName string
SSHOpts * sstore . SSHOpts
Sudo bool
ConnectMode string
Alias string
AutoInstall bool
SSHPassword string
SSHKeyFile string
Color string
EditMap map [ string ] interface { }
2022-10-01 22:23:36 +02:00
}
func parseRemoteEditArgs ( isNew bool , pk * scpacket . FeCommandPacketType ) ( * RemoteEditArgs , error ) {
2022-10-03 21:25:43 +02:00
var canonicalName string
2022-10-01 22:23:36 +02:00
var sshOpts * sstore . SSHOpts
var isSudo bool
if isNew {
2022-10-04 04:04:48 +02:00
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/remote:new must specify user@host argument (set visual=1 to edit in UI)" )
}
2022-10-03 21:25:43 +02:00
userHost := pk . Args [ 0 ]
2022-10-01 22:23:36 +02:00
m := userHostRe . FindStringSubmatch ( userHost )
if m == nil {
return nil , fmt . Errorf ( "invalid format of user@host argument" )
}
2022-10-03 21:25:43 +02:00
sudoStr , remoteUser , remoteHost , remotePortStr := m [ 1 ] , m [ 2 ] , m [ 3 ] , m [ 4 ]
var uhPort int
if remotePortStr != "" {
var err error
uhPort , err = strconv . Atoi ( remotePortStr )
if err != nil {
return nil , fmt . Errorf ( "invalid port specified on user@host argument" )
}
}
2022-10-01 22:23:36 +02:00
if sudoStr != "" {
isSudo = true
}
if pk . Kwargs [ "sudo" ] != "" {
sudoArg := resolveBool ( pk . Kwargs [ "sudo" ] , false )
if isSudo && ! sudoArg {
2022-10-03 21:25:43 +02:00
return nil , fmt . Errorf ( "invalid 'sudo' argument, with sudo kw arg set to false" )
2022-10-01 22:23:36 +02:00
}
if ! isSudo && sudoArg {
isSudo = true
}
}
sshOpts = & sstore . SSHOpts {
Local : false ,
SSHHost : remoteHost ,
SSHUser : remoteUser ,
}
portVal , err := resolvePosInt ( pk . Kwargs [ "port" ] , 0 )
if err != nil {
return nil , fmt . Errorf ( "invalid port %q: %v" , pk . Kwargs [ "port" ] , err )
}
2022-10-03 21:25:43 +02:00
if portVal != 0 && uhPort != 0 && portVal != uhPort {
return nil , fmt . Errorf ( "invalid port argument, does not match port specified in 'user@host:port' argument" )
}
if portVal == 0 && uhPort != 0 {
portVal = uhPort
}
2022-10-01 22:23:36 +02:00
sshOpts . SSHPort = portVal
2022-10-03 21:25:43 +02:00
canonicalName = remoteUser + "@" + remoteHost
if isSudo {
canonicalName = "sudo@" + canonicalName
}
2022-10-01 22:23:36 +02:00
} else {
if pk . Kwargs [ "sudo" ] != "" {
return nil , fmt . Errorf ( "cannot update 'sudo' value" )
}
if pk . Kwargs [ "port" ] != "" {
return nil , fmt . Errorf ( "cannot update 'port' value" )
}
2022-09-01 21:47:10 +02:00
}
alias := pk . Kwargs [ "alias" ]
if alias != "" {
if len ( alias ) > MaxRemoteAliasLen {
2022-10-01 22:23:36 +02:00
return nil , fmt . Errorf ( "alias too long, max length = %d" , MaxRemoteAliasLen )
2022-09-01 21:47:10 +02:00
}
if ! remoteAliasRe . MatchString ( alias ) {
2022-10-01 22:23:36 +02:00
return nil , fmt . Errorf ( "invalid alias format" )
2022-09-01 21:47:10 +02:00
}
}
2022-10-03 03:52:55 +02:00
var connectMode string
if isNew {
connectMode = sstore . ConnectModeAuto
}
2022-09-01 21:47:10 +02:00
if pk . Kwargs [ "connectmode" ] != "" {
connectMode = pk . Kwargs [ "connectmode" ]
}
2022-10-03 03:52:55 +02:00
if connectMode != "" && ! sstore . IsValidConnectMode ( connectMode ) {
2022-10-01 22:23:36 +02:00
err := fmt . Errorf ( "invalid connectmode %q: valid modes are %s" , connectMode , formatStrs ( [ ] string { sstore . ConnectModeStartup , sstore . ConnectModeAuto , sstore . ConnectModeManual } , "or" , false ) )
return nil , err
2022-09-01 21:47:10 +02:00
}
2022-09-25 07:42:52 +02:00
autoInstall := resolveBool ( pk . Kwargs [ "autoinstall" ] , true )
2022-10-01 22:23:36 +02:00
keyFile , err := resolveFile ( pk . Kwargs [ "key" ] )
if err != nil {
return nil , fmt . Errorf ( "invalid ssh keyfile %q: %v" , pk . Kwargs [ "key" ] , err )
2022-10-01 01:23:40 +02:00
}
2022-10-01 22:23:36 +02:00
color := pk . Kwargs [ "color" ]
if color != "" {
2022-09-01 21:47:10 +02:00
err := validateRemoteColor ( color , "remote color" )
if err != nil {
2022-10-01 22:23:36 +02:00
return nil , err
2022-09-01 21:47:10 +02:00
}
}
2022-10-01 22:23:36 +02:00
sshPassword := pk . Kwargs [ "password" ]
if sshOpts != nil {
sshOpts . SSHIdentity = keyFile
sshOpts . SSHPassword = sshPassword
}
2022-10-03 03:52:55 +02:00
// set up editmap
editMap := make ( map [ string ] interface { } )
if _ , found := pk . Kwargs [ sstore . RemoteField_Alias ] ; found {
editMap [ sstore . RemoteField_Alias ] = alias
}
if connectMode != "" {
editMap [ sstore . RemoteField_ConnectMode ] = connectMode
}
if _ , found := pk . Kwargs [ sstore . RemoteField_AutoInstall ] ; found {
editMap [ sstore . RemoteField_AutoInstall ] = autoInstall
}
if _ , found := pk . Kwargs [ "key" ] ; found {
editMap [ sstore . RemoteField_SSHKey ] = keyFile
}
if _ , found := pk . Kwargs [ sstore . RemoteField_Color ] ; found {
editMap [ sstore . RemoteField_Color ] = color
}
2022-10-04 04:04:48 +02:00
if _ , found := pk . Kwargs [ "password" ] ; found && pk . Kwargs [ "password" ] != PasswordUnchangedSentinel {
2022-10-03 03:52:55 +02:00
editMap [ sstore . RemoteField_SSHPassword ] = sshPassword
}
2022-10-01 22:23:36 +02:00
return & RemoteEditArgs {
2022-10-03 21:25:43 +02:00
SSHOpts : sshOpts ,
Sudo : isSudo ,
ConnectMode : connectMode ,
Alias : alias ,
AutoInstall : autoInstall ,
CanonicalName : canonicalName ,
SSHKeyFile : keyFile ,
SSHPassword : sshPassword ,
Color : color ,
EditMap : editMap ,
2022-10-01 22:23:36 +02:00
} , nil
}
func RemoteNewCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
visualEdit := resolveBool ( pk . Kwargs [ "visual" ] , false )
isSubmitted := resolveBool ( pk . Kwargs [ "submit" ] , false )
2022-10-04 04:04:48 +02:00
if visualEdit && ! isSubmitted && len ( pk . Args ) == 0 {
return makeRemoteEditUpdate_new ( nil ) , nil
2022-10-01 22:23:36 +02:00
}
editArgs , err := parseRemoteEditArgs ( true , pk )
if err != nil {
2022-10-04 04:04:48 +02:00
return makeRemoteEditErrorReturn_new ( visualEdit , fmt . Errorf ( "/remote:new %v" , err ) )
2022-10-01 02:22:28 +02:00
}
2022-09-01 21:47:10 +02:00
r := & sstore . RemoteType {
2022-09-21 02:37:49 +02:00
RemoteId : scbase . GenSCUUID ( ) ,
2022-09-01 21:47:10 +02:00
PhysicalId : "" ,
RemoteType : sstore . RemoteTypeSsh ,
2022-10-01 22:23:36 +02:00
RemoteAlias : editArgs . Alias ,
2022-10-03 21:25:43 +02:00
RemoteCanonicalName : editArgs . CanonicalName ,
2022-10-01 22:23:36 +02:00
RemoteSudo : editArgs . Sudo ,
RemoteUser : editArgs . SSHOpts . SSHUser ,
RemoteHost : editArgs . SSHOpts . SSHHost ,
ConnectMode : editArgs . ConnectMode ,
AutoInstall : editArgs . AutoInstall ,
SSHOpts : editArgs . SSHOpts ,
}
if editArgs . Color != "" {
r . RemoteOpts = & sstore . RemoteOptsType { Color : editArgs . Color }
}
err = remote . AddRemote ( ctx , r )
2022-09-01 21:47:10 +02:00
if err != nil {
2022-10-04 04:04:48 +02:00
return makeRemoteEditErrorReturn_new ( visualEdit , fmt . Errorf ( "cannot create remote %q: %v" , r . RemoteCanonicalName , err ) )
2022-09-01 21:47:10 +02:00
}
2022-09-30 23:46:51 +02:00
// SUCCESS
update := sstore . ModelUpdate {
2022-09-01 21:47:10 +02:00
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "remote %q created" , r . RemoteCanonicalName ) ,
TimeoutMs : 2000 ,
} ,
}
return update , nil
2022-09-01 08:12:26 +02:00
}
2022-09-13 21:06:12 +02:00
func RemoteSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-10-03 03:52:55 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
2022-09-13 21:06:12 +02:00
if err != nil {
return nil , err
}
2022-10-03 03:52:55 +02:00
visualEdit := resolveBool ( pk . Kwargs [ "visual" ] , false )
isSubmitted := resolveBool ( pk . Kwargs [ "submit" ] , false )
editArgs , err := parseRemoteEditArgs ( false , pk )
if err != nil {
2022-10-04 04:04:48 +02:00
return makeRemoteEditErrorReturn_edit ( ids , visualEdit , fmt . Errorf ( "/remote:new %v" , err ) )
2022-10-03 03:52:55 +02:00
}
if visualEdit && ! isSubmitted && len ( editArgs . EditMap ) == 0 {
2022-10-04 04:04:48 +02:00
return makeRemoteEditUpdate_edit ( ids , nil ) , nil
}
if ! visualEdit && len ( editArgs . EditMap ) == 0 {
return nil , fmt . Errorf ( "/remote:set no updates, can set %s. (set visual=1 to edit in UI)" , formatStrs ( RemoteSetArgs , "or" , false ) )
2022-10-03 03:52:55 +02:00
}
err = ids . Remote . MShell . UpdateRemote ( ctx , editArgs . EditMap )
if err != nil {
2022-10-04 04:04:48 +02:00
return makeRemoteEditErrorReturn_edit ( ids , visualEdit , fmt . Errorf ( "/remote:new error updating remote: %v" , err ) )
2022-10-03 03:52:55 +02:00
}
update := sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "remote %q updated" , ids . Remote . DisplayName ) ,
TimeoutMs : 2000 ,
} ,
}
return update , nil
2022-09-13 21:06:12 +02:00
}
2022-08-27 02:17:33 +02:00
func RemoteShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , err
2022-08-23 23:01:52 +02:00
}
2022-08-30 01:31:06 +02:00
state := ids . Remote . RState
2022-08-27 02:17:33 +02:00
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
2022-09-15 09:17:23 +02:00
PtyRemoteId : state . RemoteId ,
2022-08-28 23:24:05 +02:00
} ,
} , nil
}
func RemoteShowAllCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
stateArr := remote . GetAllRemoteRuntimeState ( )
2022-08-28 23:24:05 +02:00
var buf bytes . Buffer
for _ , rstate := range stateArr {
var name string
if rstate . RemoteAlias == "" {
name = rstate . RemoteCanonicalName
} else {
name = fmt . Sprintf ( "%s (%s)" , rstate . RemoteCanonicalName , rstate . RemoteAlias )
}
buf . WriteString ( fmt . Sprintf ( "%-12s %-5s %8s %s\n" , rstate . Status , rstate . RemoteType , rstate . RemoteId [ 0 : 8 ] , name ) )
}
return sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
2022-09-25 04:54:06 +02:00
RemoteShowAll : true ,
2022-08-27 02:17:33 +02:00
} ,
} , nil
2022-08-23 23:01:52 +02:00
}
2022-09-13 21:06:12 +02:00
func RemoteArchiveCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_Remote )
if err != nil {
return nil , err
}
2022-09-14 02:11:36 +02:00
err = remote . ArchiveRemote ( ctx , ids . Remote . RemotePtr . RemoteId )
if err != nil {
return nil , fmt . Errorf ( "archiving remote: %v" , err )
}
2022-09-13 21:06:12 +02:00
update := sstore . InfoMsgUpdate ( "remote [%s] archived" , ids . Remote . DisplayName )
2022-10-04 20:45:24 +02:00
localRemote := remote . GetLocalRemote ( )
if localRemote != nil {
update . Window = & sstore . WindowType {
SessionId : ids . SessionId ,
WindowId : ids . WindowId ,
CurRemote : sstore . RemotePtrType { RemoteId : localRemote . GetRemoteId ( ) } ,
}
}
2022-09-13 21:06:12 +02:00
return update , nil
}
2022-08-27 02:17:33 +02:00
func RemoteCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
return nil , fmt . Errorf ( "/remote requires a subcommand: %s" , formatStrs ( [ ] string { "show" } , "or" , false ) )
2022-08-23 01:00:25 +02:00
}
2022-08-17 21:24:09 +02:00
func CrCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window )
2022-08-17 21:24:09 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/cr error: %w" , err )
2022-08-17 21:24:09 +02:00
}
newRemote := firstArg ( pk )
if newRemote == "" {
return nil , nil
}
2022-10-01 02:22:28 +02:00
remoteName , rptr , _ , rstate , err := resolveRemote ( ctx , newRemote , ids . SessionId , ids . WindowId )
2022-08-17 21:24:09 +02:00
if err != nil {
return nil , err
}
2022-08-24 11:14:16 +02:00
if rptr == nil {
2022-10-01 02:22:28 +02:00
return nil , fmt . Errorf ( "/cr error: remote %q not found" , newRemote )
}
if rstate . Archived {
return nil , fmt . Errorf ( "/cr error: remote %q cannot switch to archived remote" , newRemote )
2022-08-17 21:24:09 +02:00
}
2022-08-24 11:14:16 +02:00
err = sstore . UpdateCurRemote ( ctx , ids . SessionId , ids . WindowId , * rptr )
2022-08-17 21:24:09 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/cr error: cannot update curremote: %w" , err )
2022-08-17 21:24:09 +02:00
}
2022-08-24 11:14:16 +02:00
update := sstore . ModelUpdate {
2022-08-25 03:56:50 +02:00
Window : & sstore . WindowType {
2022-08-17 21:24:09 +02:00
SessionId : ids . SessionId ,
WindowId : ids . WindowId ,
2022-08-24 11:14:16 +02:00
CurRemote : * rptr ,
2022-08-17 21:24:09 +02:00
} ,
Info : & sstore . InfoMsgType {
2022-10-01 02:22:28 +02:00
InfoMsg : fmt . Sprintf ( "current remote = %q" , remoteName ) ,
2022-08-17 21:24:09 +02:00
TimeoutMs : 2000 ,
} ,
}
return update , nil
}
2022-07-16 02:37:32 +02:00
func CdCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_RemoteConnected )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/cd error: %w" , err )
2022-07-16 02:37:32 +02:00
}
newDir := firstArg ( pk )
2022-08-09 23:24:57 +02:00
if newDir == "" {
2022-08-24 11:14:16 +02:00
return sstore . ModelUpdate {
2022-08-17 21:24:09 +02:00
Info : & sstore . InfoMsgType {
2022-08-30 01:31:06 +02:00
InfoMsg : fmt . Sprintf ( "[%s] current directory = %s" , ids . Remote . DisplayName , ids . Remote . RemoteState . Cwd ) ,
2022-08-17 21:24:09 +02:00
} ,
} , nil
}
2022-08-30 01:31:06 +02:00
newDir , err = ids . Remote . RState . ExpandHomeDir ( newDir )
2022-08-17 21:24:09 +02:00
if err != nil {
return nil , err
2022-08-09 23:24:57 +02:00
}
if ! strings . HasPrefix ( newDir , "/" ) {
2022-08-30 01:31:06 +02:00
if ids . Remote . RemoteState == nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/cd error: cannot get current remote directory (can only cd with absolute path)" )
2022-08-09 23:24:57 +02:00
}
2022-08-30 01:31:06 +02:00
newDir = path . Join ( ids . Remote . RemoteState . Cwd , newDir )
2022-08-09 23:24:57 +02:00
newDir , err = filepath . Abs ( newDir )
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/cd error: error canonicalizing new directory: %w" , err )
2022-08-09 23:24:57 +02:00
}
}
2022-07-16 02:37:32 +02:00
cdPacket := packet . MakeCdPacket ( )
cdPacket . ReqId = uuid . New ( ) . String ( )
cdPacket . Dir = newDir
2022-08-30 01:31:06 +02:00
resp , err := ids . Remote . MShell . PacketRpc ( ctx , cdPacket )
2022-08-09 23:24:57 +02:00
if err != nil {
return nil , err
}
2022-08-11 03:33:32 +02:00
if err = resp . Err ( ) ; err != nil {
return nil , err
}
2022-08-30 01:31:06 +02:00
state := * ids . Remote . RemoteState
2022-08-23 03:38:52 +02:00
state . Cwd = newDir
2022-08-30 01:31:06 +02:00
remoteInst , err := sstore . UpdateRemoteState ( ctx , ids . SessionId , ids . WindowId , ids . Remote . RemotePtr , state )
2022-08-09 23:24:57 +02:00
if err != nil {
return nil , err
}
2022-10-21 01:14:14 +02:00
var cmdOutput bytes . Buffer
displayStateUpdate ( & cmdOutput , * ids . Remote . RemoteState , remoteInst . State )
cmd , err := makeStaticCmd ( ctx , "cd" , ids , pk . GetRawStr ( ) , cmdOutput . Bytes ( ) )
2022-10-19 03:03:02 +02:00
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
update , err := addLineForCmd ( ctx , "/cd" , false , ids , cmd )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
2022-08-09 23:24:57 +02:00
}
2022-10-19 03:03:02 +02:00
update . Interactive = pk . Interactive
update . Sessions = sstore . MakeSessionsUpdateForRemote ( ids . SessionId , remoteInst )
//update.Info = &sstore.InfoMsgType{
// InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.Remote.DisplayName, newDir),
// TimeoutMs: 2000,
//}
2022-08-09 23:24:57 +02:00
return update , nil
}
2022-10-19 03:03:02 +02:00
func makeStaticCmd ( ctx context . Context , metaCmd string , ids resolvedIds , cmdStr string , cmdOutput [ ] byte ) ( * sstore . CmdType , error ) {
cmd := & sstore . CmdType {
SessionId : ids . SessionId ,
CmdId : scbase . GenSCUUID ( ) ,
CmdStr : cmdStr ,
Remote : ids . Remote . RemotePtr ,
TermOpts : sstore . TermOpts { Rows : shexec . DefaultTermRows , Cols : shexec . DefaultTermCols , FlexRows : true , MaxPtySize : remote . DefaultMaxPtySize } ,
Status : sstore . CmdStatusDone ,
StartPk : nil ,
DonePk : nil ,
RunOut : nil ,
}
if ids . Remote . RemoteState != nil {
cmd . RemoteState = * ids . Remote . RemoteState
}
err := sstore . CreateCmdPtyFile ( ctx , cmd . SessionId , cmd . CmdId , cmd . TermOpts . MaxPtySize )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , fmt . Errorf ( "cannot create local ptyout file for %s command: %w" , metaCmd , err )
}
// can ignore ptyupdate
_ , err = sstore . AppendToCmdPtyBlob ( ctx , cmd . SessionId , cmd . CmdId , cmdOutput , 0 )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , fmt . Errorf ( "cannot append to local ptyout file for %s command: %v" , metaCmd , err )
}
return cmd , nil
}
func addLineForCmd ( ctx context . Context , metaCmd string , shouldFocus bool , ids resolvedIds , cmd * sstore . CmdType ) ( * sstore . ModelUpdate , error ) {
rtnLine , err := sstore . AddCmdLine ( ctx , ids . SessionId , ids . WindowId , DefaultUserId , cmd )
if err != nil {
return nil , err
}
sw , err := sstore . GetScreenWindowByIds ( ctx , ids . SessionId , ids . ScreenId , ids . WindowId )
if err != nil {
// ignore error here, because the command has already run (nothing to do)
fmt . Printf ( "%s error getting screen-window: %v\n" , metaCmd , err )
}
if sw != nil {
updateMap := make ( map [ string ] interface { } )
updateMap [ sstore . SWField_SelectedLine ] = rtnLine . LineNum
if shouldFocus {
updateMap [ sstore . SWField_Focus ] = sstore . SWFocusCmdFg
}
sw , err = sstore . UpdateScreenWindow ( ctx , ids . SessionId , ids . ScreenId , ids . WindowId , updateMap )
if err != nil {
// ignore error again (nothing to do)
fmt . Printf ( "%s error updating screen-window selected line: %v\n" , metaCmd , err )
}
}
update := & sstore . ModelUpdate {
Line : rtnLine ,
Cmd : cmd ,
ScreenWindows : [ ] * sstore . ScreenWindowType { sw } ,
}
updateHistoryContext ( ctx , rtnLine , cmd )
return update , nil
}
func updateHistoryContext ( ctx context . Context , line * sstore . LineType , cmd * sstore . CmdType ) {
ctxVal := ctx . Value ( historyContextKey )
if ctxVal == nil {
return
}
hctx := ctxVal . ( * historyContextType )
if line != nil {
hctx . LineId = line . LineId
}
if cmd != nil {
hctx . CmdId = cmd . CmdId
hctx . RemotePtr = & cmd . Remote
}
}
2022-08-09 23:24:57 +02:00
func getStrArr ( v interface { } , field string ) [ ] string {
if v == nil {
return nil
}
m , ok := v . ( map [ string ] interface { } )
if ! ok {
return nil
}
fieldVal := m [ field ]
if fieldVal == nil {
return nil
}
iarr , ok := fieldVal . ( [ ] interface { } )
if ! ok {
return nil
}
var sarr [ ] string
for _ , iv := range iarr {
if sv , ok := iv . ( string ) ; ok {
sarr = append ( sarr , sv )
}
}
return sarr
}
2022-08-11 03:33:32 +02:00
func getBool ( v interface { } , field string ) bool {
if v == nil {
return false
}
m , ok := v . ( map [ string ] interface { } )
if ! ok {
return false
}
fieldVal := m [ field ]
if fieldVal == nil {
return false
}
bval , ok := fieldVal . ( bool )
if ! ok {
return false
}
return bval
}
func makeInfoFromComps ( compType string , comps [ ] string , hasMore bool ) sstore . UpdatePacket {
2022-08-24 02:26:42 +02:00
sort . Slice ( comps , func ( i int , j int ) bool {
c1 := comps [ i ]
c2 := comps [ j ]
c1mc := strings . HasPrefix ( c1 , "^" )
c2mc := strings . HasPrefix ( c2 , "^" )
if c1mc && ! c2mc {
return true
}
if ! c1mc && c2mc {
return false
}
return c1 < c2
} )
2022-08-23 22:37:08 +02:00
if len ( comps ) == 0 {
comps = [ ] string { "(no completions)" }
}
2022-08-24 11:14:16 +02:00
update := sstore . ModelUpdate {
2022-08-11 03:33:32 +02:00
Info : & sstore . InfoMsgType {
2022-08-11 19:21:45 +02:00
InfoTitle : fmt . Sprintf ( "%s completions" , compType ) ,
InfoComps : comps ,
InfoCompsMore : hasMore ,
2022-08-11 03:33:32 +02:00
} ,
}
return update
}
func makeInsertUpdateFromComps ( pos int64 , prefix string , comps [ ] string , hasMore bool ) sstore . UpdatePacket {
if hasMore {
return nil
}
lcp := longestPrefix ( prefix , comps )
if lcp == prefix || len ( lcp ) < len ( prefix ) || ! strings . HasPrefix ( lcp , prefix ) {
return nil
}
insertChars := lcp [ len ( prefix ) : ]
clu := & sstore . CmdLineType { InsertChars : insertChars , InsertPos : pos }
2022-08-24 11:14:16 +02:00
return sstore . ModelUpdate { CmdLine : clu }
2022-08-11 03:33:32 +02:00
}
func longestPrefix ( root string , comps [ ] string ) string {
if len ( comps ) == 0 {
return root
}
if len ( comps ) == 1 {
comp := comps [ 0 ]
if len ( comp ) >= len ( root ) && strings . HasPrefix ( comp , root ) {
2022-08-11 19:21:45 +02:00
if strings . HasSuffix ( comp , "/" ) {
return comps [ 0 ]
}
2022-08-11 03:33:32 +02:00
return comps [ 0 ] + " "
}
}
lcp := comps [ 0 ]
for i := 1 ; i < len ( comps ) ; i ++ {
s := comps [ i ]
for j := 0 ; j < len ( lcp ) ; j ++ {
if j >= len ( s ) || lcp [ j ] != s [ j ] {
lcp = lcp [ 0 : j ]
break
}
}
}
if len ( lcp ) < len ( root ) || ! strings . HasPrefix ( lcp , root ) {
return root
}
return lcp
}
2022-08-30 01:31:06 +02:00
func doMetaCompGen ( ctx context . Context , pk * scpacket . FeCommandPacketType , prefix string , forDisplay bool ) ( [ ] string , bool , error ) {
ids , err := resolveUiIds ( ctx , pk , 0 ) // best effort
var comps [ ] string
var hasMore bool
if ids . Remote != nil && ids . Remote . RState . IsConnected ( ) {
comps , hasMore , err = doCompGen ( ctx , pk , prefix , "file" , forDisplay )
if err != nil {
return nil , false , err
}
2022-08-23 22:37:08 +02:00
}
2022-08-27 02:17:33 +02:00
validCommands := getValidCommands ( )
for _ , cmd := range validCommands {
2022-08-11 03:33:32 +02:00
if strings . HasPrefix ( cmd , prefix ) {
2022-08-24 02:26:42 +02:00
if forDisplay {
comps = append ( comps , "^" + cmd )
} else {
comps = append ( comps , cmd )
}
2022-08-11 03:33:32 +02:00
}
}
2022-08-23 22:37:08 +02:00
return comps , hasMore , nil
2022-08-11 03:33:32 +02:00
}
2022-08-30 01:31:06 +02:00
func doCompGen ( ctx context . Context , pk * scpacket . FeCommandPacketType , prefix string , compType string , forDisplay bool ) ( [ ] string , bool , error ) {
2022-08-11 03:33:32 +02:00
if compType == "metacommand" {
2022-08-30 01:31:06 +02:00
return doMetaCompGen ( ctx , pk , prefix , forDisplay )
2022-08-09 23:24:57 +02:00
}
if ! packet . IsValidCompGenType ( compType ) {
2022-08-24 02:26:42 +02:00
return nil , false , fmt . Errorf ( "/compgen invalid type '%s'" , compType )
2022-08-09 23:24:57 +02:00
}
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window | R_RemoteConnected )
if err != nil {
return nil , false , fmt . Errorf ( "compgen error: %w" , err )
}
2022-08-09 23:24:57 +02:00
cgPacket := packet . MakeCompGenPacket ( )
cgPacket . ReqId = uuid . New ( ) . String ( )
cgPacket . CompType = compType
cgPacket . Prefix = prefix
2022-08-30 01:31:06 +02:00
cgPacket . Cwd = ids . Remote . RemoteState . Cwd
resp , err := ids . Remote . MShell . PacketRpc ( ctx , cgPacket )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-11 03:33:32 +02:00
return nil , false , err
}
if err = resp . Err ( ) ; err != nil {
return nil , false , err
2022-07-16 02:37:32 +02:00
}
2022-08-09 23:24:57 +02:00
comps := getStrArr ( resp . Data , "comps" )
2022-08-11 03:33:32 +02:00
hasMore := getBool ( resp . Data , "hasmore" )
return comps , hasMore , nil
}
func CompGenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
cmdLine := firstArg ( pk )
pos := len ( cmdLine )
if pk . Kwargs [ "comppos" ] != "" {
posArg , err := strconv . Atoi ( pk . Kwargs [ "comppos" ] )
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/compgen invalid comppos '%s': %w" , pk . Kwargs [ "comppos" ] , err )
2022-08-11 03:33:32 +02:00
}
pos = posArg
}
if pos < 0 {
pos = 0
}
if pos > len ( cmdLine ) {
pos = len ( cmdLine )
}
showComps := resolveBool ( pk . Kwargs [ "compshow" ] , false )
prefix := cmdLine [ : pos ]
parts := strings . Split ( prefix , " " )
compType := "file"
2022-08-24 02:26:42 +02:00
if len ( parts ) > 0 && len ( parts ) < 2 && strings . HasPrefix ( parts [ 0 ] , "/" ) {
2022-08-11 03:33:32 +02:00
compType = "metacommand"
2022-08-24 02:26:42 +02:00
} else if len ( parts ) == 2 && ( parts [ 0 ] == "cd" || parts [ 0 ] == "/cd" ) {
2022-08-11 03:33:32 +02:00
compType = "directory"
} else if len ( parts ) <= 1 {
compType = "command"
}
lastPart := ""
if len ( parts ) > 0 {
lastPart = parts [ len ( parts ) - 1 ]
}
2022-08-30 01:31:06 +02:00
comps , hasMore , err := doCompGen ( ctx , pk , lastPart , compType , showComps )
2022-08-11 03:33:32 +02:00
if err != nil {
return nil , err
}
if showComps {
return makeInfoFromComps ( compType , comps , hasMore ) , nil
}
return makeInsertUpdateFromComps ( int64 ( pos ) , lastPart , comps , hasMore ) , nil
2022-07-16 02:37:32 +02:00
}
func CommentCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Window )
2022-07-16 02:37:32 +02:00
if err != nil {
2022-08-24 02:26:42 +02:00
return nil , fmt . Errorf ( "/comment error: %w" , err )
2022-07-16 02:37:32 +02:00
}
text := firstArg ( pk )
if strings . TrimSpace ( text ) == "" {
return nil , fmt . Errorf ( "cannot post empty comment" )
}
rtnLine , err := sstore . AddCommentLine ( ctx , ids . SessionId , ids . WindowId , DefaultUserId , text )
if err != nil {
return nil , err
}
2022-10-11 02:30:48 +02:00
updateMap := make ( map [ string ] interface { } )
updateMap [ sstore . SWField_SelectedLine ] = rtnLine . LineNum
2022-10-11 10:11:04 +02:00
updateMap [ sstore . SWField_Focus ] = sstore . SWFocusInput
2022-10-11 02:30:48 +02:00
sw , err := sstore . UpdateScreenWindow ( ctx , ids . SessionId , ids . ScreenId , ids . WindowId , updateMap )
if err != nil {
// ignore error again (nothing to do)
fmt . Printf ( "/comment error updating screen-window selected line: %v\n" , err )
}
2022-10-11 10:11:04 +02:00
update := sstore . ModelUpdate { Line : rtnLine , ScreenWindows : [ ] * sstore . ScreenWindowType { sw } }
2022-10-11 02:30:48 +02:00
return update , nil
2022-07-16 02:37:32 +02:00
}
2022-07-16 02:53:23 +02:00
2022-08-27 01:21:19 +02:00
func maybeQuote ( s string , quote bool ) string {
if quote {
return fmt . Sprintf ( "%q" , s )
}
return s
}
2022-08-27 02:17:33 +02:00
func mapToStrs ( m map [ string ] bool ) [ ] string {
var rtn [ ] string
for key , val := range m {
if val {
rtn = append ( rtn , key )
}
}
return rtn
}
2022-08-27 01:21:19 +02:00
func formatStrs ( strs [ ] string , conj string , quote bool ) string {
if len ( strs ) == 0 {
return "(none)"
}
if len ( strs ) == 1 {
return maybeQuote ( strs [ 0 ] , quote )
}
if len ( strs ) == 2 {
return fmt . Sprintf ( "%s %s %s" , maybeQuote ( strs [ 0 ] , quote ) , conj , maybeQuote ( strs [ 1 ] , quote ) )
}
var buf bytes . Buffer
for idx := 0 ; idx < len ( strs ) - 1 ; idx ++ {
buf . WriteString ( maybeQuote ( strs [ idx ] , quote ) )
buf . WriteString ( ", " )
}
buf . WriteString ( conj )
buf . WriteString ( " " )
buf . WriteString ( maybeQuote ( strs [ len ( strs ) - 1 ] , quote ) )
return buf . String ( )
}
func validateName ( name string , typeStr string ) error {
if len ( name ) > MaxNameLen {
return fmt . Errorf ( "%s name too long, max length is %d" , typeStr , MaxNameLen )
}
if ! genericNameRe . MatchString ( name ) {
return fmt . Errorf ( "invalid %s name" , typeStr )
}
return nil
}
2022-08-27 06:44:18 +02:00
func validateColor ( color string , typeStr string ) error {
for _ , c := range ColorNames {
if color == c {
return nil
}
}
return fmt . Errorf ( "invalid %s, valid colors are: %s" , typeStr , formatStrs ( ColorNames , "or" , false ) )
}
2022-09-01 21:47:10 +02:00
func validateRemoteColor ( color string , typeStr string ) error {
for _ , c := range RemoteColorNames {
if color == c {
return nil
}
}
return fmt . Errorf ( "invalid %s, valid colors are: %s" , typeStr , formatStrs ( RemoteColorNames , "or" , false ) )
}
2022-08-27 02:17:33 +02:00
func SessionOpenCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
activate := resolveBool ( pk . Kwargs [ "activate" ] , true )
newName := pk . Kwargs [ "name" ]
if newName != "" {
err := validateName ( newName , "session" )
2022-08-09 01:21:46 +02:00
if err != nil {
return nil , err
}
}
2022-08-27 02:17:33 +02:00
update , err := sstore . InsertSessionWithName ( ctx , newName , activate )
if err != nil {
return nil , err
}
return update , nil
}
2022-09-13 21:06:12 +02:00
func SessionDeleteCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session )
if err != nil {
return nil , err
}
err = sstore . DeleteSession ( ctx , ids . SessionId )
if err != nil {
return nil , fmt . Errorf ( "cannot delete session: %v" , err )
}
sessionIds , _ := sstore . GetAllSessionIds ( ctx ) // ignore error, session is already deleted so that's the main return value
delSession := & sstore . SessionType { SessionId : ids . SessionId , Remove : true }
update := sstore . ModelUpdate {
Sessions : [ ] * sstore . SessionType { delSession } ,
}
if len ( sessionIds ) > 0 {
update . ActiveSessionId = sessionIds [ 0 ]
}
return update , nil
}
2022-08-27 02:17:33 +02:00
func SessionSetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session )
2022-08-27 02:17:33 +02:00
if err != nil {
return nil , err
}
var varsUpdated [ ] string
if pk . Kwargs [ "name" ] != "" {
newName := pk . Kwargs [ "name" ]
err = validateName ( newName , "session" )
2022-08-26 22:12:17 +02:00
if err != nil {
return nil , err
}
2022-08-27 02:17:33 +02:00
err = sstore . SetSessionName ( ctx , ids . SessionId , newName )
2022-08-26 22:12:17 +02:00
if err != nil {
2022-08-27 02:17:33 +02:00
return nil , fmt . Errorf ( "setting session name: %v" , err )
2022-08-26 22:12:17 +02:00
}
2022-08-27 02:17:33 +02:00
varsUpdated = append ( varsUpdated , "name" )
}
if pk . Kwargs [ "pos" ] != "" {
2022-08-27 01:21:19 +02:00
2022-08-26 22:12:17 +02:00
}
2022-08-27 02:17:33 +02:00
if len ( varsUpdated ) == 0 {
return nil , fmt . Errorf ( "/session:set no updates, can set %s" , formatStrs ( [ ] string { "name" , "pos" } , "or" , false ) )
}
2022-08-27 02:51:28 +02:00
bareSession , err := sstore . GetBareSessionById ( ctx , ids . SessionId )
2022-08-27 02:17:33 +02:00
update := sstore . ModelUpdate {
Sessions : [ ] * sstore . SessionType { bareSession } ,
Info : & sstore . InfoMsgType {
2022-08-27 02:51:28 +02:00
InfoMsg : fmt . Sprintf ( "session updated %s" , formatStrs ( varsUpdated , "and" , false ) ) ,
2022-08-27 02:17:33 +02:00
TimeoutMs : 2000 ,
} ,
2022-08-09 01:21:46 +02:00
}
2022-08-27 02:17:33 +02:00
return update , nil
}
func SessionCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-09-13 21:06:12 +02:00
ids , err := resolveUiIds ( ctx , pk , 0 )
if err != nil {
return nil , err
}
2022-08-09 01:21:46 +02:00
firstArg := firstArg ( pk )
if firstArg == "" {
2022-08-27 01:21:19 +02:00
return nil , fmt . Errorf ( "usage /session [name|id|pos], no param specified" )
2022-08-09 01:21:46 +02:00
}
2022-08-27 02:17:33 +02:00
bareSessions , err := sstore . GetBareSessions ( ctx )
2022-08-09 01:21:46 +02:00
if err != nil {
return nil , err
}
2022-08-27 02:17:33 +02:00
ritems := sessionsToResolveItems ( bareSessions )
2022-10-07 08:58:38 +02:00
ritem , err := genericResolve ( firstArg , ids . SessionId , ritems , false , "session" )
2022-08-27 01:21:19 +02:00
if err != nil {
2022-08-27 02:17:33 +02:00
return nil , err
2022-08-27 01:21:19 +02:00
}
2022-08-30 01:31:06 +02:00
err = sstore . SetActiveSessionId ( ctx , ritem . Id )
if err != nil {
return nil , err
}
2022-08-27 01:21:19 +02:00
update := sstore . ModelUpdate {
2022-08-27 02:17:33 +02:00
ActiveSessionId : ritem . Id ,
2022-08-27 01:21:19 +02:00
Info : & sstore . InfoMsgType {
2022-08-27 02:17:33 +02:00
InfoMsg : fmt . Sprintf ( "switched to session %q" , ritem . Name ) ,
2022-08-27 01:21:19 +02:00
TimeoutMs : 2000 ,
} ,
}
return update , nil
2022-07-16 02:53:23 +02:00
}
2022-08-24 02:26:42 +02:00
2022-10-25 06:29:11 +02:00
func ResetCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-10-28 02:10:36 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , err
}
initPk , err := ids . Remote . MShell . ReInit ( ctx )
if err != nil {
return nil , err
}
if initPk == nil || initPk . State == nil {
return nil , fmt . Errorf ( "invalid initpk received from remote (no remote state)" )
}
remoteInst , err := sstore . UpdateRemoteState ( ctx , ids . SessionId , ids . WindowId , ids . Remote . RemotePtr , * initPk . State )
if err != nil {
return nil , err
}
outputStr := "reset remote state"
cmd , err := makeStaticCmd ( ctx , "reset" , ids , pk . GetRawStr ( ) , [ ] byte ( outputStr ) )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
update , err := addLineForCmd ( ctx , "/cd" , false , ids , cmd )
if err != nil {
// TODO tricky error since the command was a success, but we can't show the output
return nil , err
}
update . Interactive = pk . Interactive
update . Sessions = sstore . MakeSessionsUpdateForRemote ( ids . SessionId , remoteInst )
return update , nil
2022-10-25 06:29:11 +02:00
}
2022-08-27 07:01:29 +02:00
func ClearCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
2022-08-27 07:01:29 +02:00
if err != nil {
return nil , err
}
update , err := sstore . ClearWindow ( ctx , ids . SessionId , ids . WindowId )
if err != nil {
return nil , fmt . Errorf ( "clearing window: %v" , err )
}
update . Info = & sstore . InfoMsgType {
InfoMsg : fmt . Sprintf ( "window cleared" ) ,
TimeoutMs : 2000 ,
}
return update , nil
}
2022-08-28 23:24:05 +02:00
const DefaultMaxHistoryItems = 10000
func HistoryCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-08-30 01:31:06 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window | R_Remote )
2022-08-28 23:24:05 +02:00
if err != nil {
return nil , err
}
2022-10-01 22:23:36 +02:00
maxItems , err := resolvePosInt ( pk . Kwargs [ "maxitems" ] , DefaultMaxHistoryItems )
2022-08-28 23:24:05 +02:00
if err != nil {
return nil , fmt . Errorf ( "invalid maxitems value '%s' (must be a number): %v" , pk . Kwargs [ "maxitems" ] , err )
}
if maxItems < 0 {
2022-10-21 01:14:14 +02:00
return nil , fmt . Errorf ( "invalid maxitems value '%d' (cannot be negative)" , maxItems )
2022-08-28 23:24:05 +02:00
}
if maxItems == 0 {
maxItems = DefaultMaxHistoryItems
}
2022-08-31 22:28:52 +02:00
htype := HistoryTypeWindow
hSessionId := ids . SessionId
hWindowId := ids . WindowId
if pk . Kwargs [ "type" ] != "" {
htype = pk . Kwargs [ "type" ]
if htype != HistoryTypeWindow && htype != HistoryTypeSession && htype != HistoryTypeGlobal {
return nil , fmt . Errorf ( "invalid history type '%s', valid types: %s" , htype , formatStrs ( [ ] string { HistoryTypeWindow , HistoryTypeSession , HistoryTypeGlobal } , "or" , false ) )
}
}
if htype == HistoryTypeGlobal {
hSessionId = ""
hWindowId = ""
} else if htype == HistoryTypeSession {
hWindowId = ""
}
hitems , err := sstore . GetHistoryItems ( ctx , hSessionId , hWindowId , sstore . HistoryQueryOpts { MaxItems : maxItems } )
2022-08-28 23:24:05 +02:00
if err != nil {
return nil , err
}
2022-08-31 08:11:06 +02:00
show := ! resolveBool ( pk . Kwargs [ "noshow" ] , false )
2022-09-30 23:46:51 +02:00
update := sstore . ModelUpdate { }
2022-08-30 01:31:06 +02:00
update . History = & sstore . HistoryInfoType {
2022-08-31 22:28:52 +02:00
HistoryType : htype ,
SessionId : ids . SessionId ,
WindowId : ids . WindowId ,
Items : hitems ,
Show : show ,
2022-08-30 01:31:06 +02:00
}
2022-08-28 23:24:05 +02:00
return update , nil
}
2022-08-24 02:26:42 +02:00
func splitLinesForInfo ( str string ) [ ] string {
rtn := strings . Split ( str , "\n" )
if rtn [ len ( rtn ) - 1 ] == "" {
return rtn [ : len ( rtn ) - 1 ]
}
return rtn
}
2022-09-06 05:08:59 +02:00
func resizeRunningCommand ( ctx context . Context , cmd * sstore . CmdType , newCols int ) error {
fmt . Printf ( "resize running cmd %s/%s %d => %d\n" , cmd . SessionId , cmd . CmdId , cmd . TermOpts . Cols , newCols )
siPk := packet . MakeSpecialInputPacket ( )
siPk . CK = base . MakeCommandKey ( cmd . SessionId , cmd . CmdId )
siPk . WinSize = & packet . WinSize { Rows : int ( cmd . TermOpts . Rows ) , Cols : newCols }
msh := remote . GetRemoteById ( cmd . Remote . RemoteId )
if msh == nil {
return fmt . Errorf ( "cannot resize, cmd remote not found" )
}
err := msh . SendSpecialInput ( siPk )
if err != nil {
return err
}
newTermOpts := cmd . TermOpts
newTermOpts . Cols = int64 ( newCols )
err = sstore . UpdateCmdTermOpts ( ctx , cmd . SessionId , cmd . CmdId , newTermOpts )
if err != nil {
return err
}
return nil
}
2022-10-22 23:46:39 +02:00
func SwResizeCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
2022-09-06 05:08:59 +02:00
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , err
}
colsStr := pk . Kwargs [ "cols" ]
if colsStr == "" {
2022-10-22 23:46:39 +02:00
return nil , fmt . Errorf ( "/sw:resize requires a numeric 'cols' argument" )
2022-09-06 05:08:59 +02:00
}
cols , err := strconv . Atoi ( colsStr )
if err != nil {
2022-10-22 23:46:39 +02:00
return nil , fmt . Errorf ( "/sw:resize requires a numeric 'cols' argument: %v" , err )
2022-09-06 05:08:59 +02:00
}
if cols <= 0 {
2022-10-22 23:46:39 +02:00
return nil , fmt . Errorf ( "/sw:resize invalid zero/negative 'cols' argument" )
2022-09-06 05:08:59 +02:00
}
cols = base . BoundInt ( cols , shexec . MinTermCols , shexec . MaxTermCols )
runningCmds , err := sstore . GetRunningWindowCmds ( ctx , ids . SessionId , ids . WindowId )
if err != nil {
2022-10-22 23:46:39 +02:00
return nil , fmt . Errorf ( "/sw:resize cannot get running commands: %v" , err )
2022-09-06 05:08:59 +02:00
}
if len ( runningCmds ) == 0 {
return nil , nil
}
for _ , cmd := range runningCmds {
if int ( cmd . TermOpts . Cols ) != cols {
resizeRunningCommand ( ctx , cmd , cols )
}
}
return nil , nil
}
2022-09-13 21:06:12 +02:00
func LineCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
return nil , fmt . Errorf ( "/line requires a subcommand: %s" , formatStrs ( [ ] string { "show" } , "or" , false ) )
}
func LineShowCommand ( ctx context . Context , pk * scpacket . FeCommandPacketType ) ( sstore . UpdatePacket , error ) {
ids , err := resolveUiIds ( ctx , pk , R_Session | R_Screen | R_Window )
if err != nil {
return nil , err
}
2022-09-21 06:50:36 +02:00
if len ( pk . Args ) == 0 {
return nil , fmt . Errorf ( "/line:show requires an argument (line number or id)" )
}
lineArg := pk . Args [ 0 ]
lineId , err := sstore . FindLineIdByArg ( ctx , ids . SessionId , ids . WindowId , lineArg )
if err != nil {
return nil , fmt . Errorf ( "error looking up lineid: %v" , err )
}
if lineId == "" {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
line , cmd , err := sstore . GetLineCmd ( ctx , ids . SessionId , ids . WindowId , lineId )
if err != nil {
return nil , fmt . Errorf ( "error getting line: %v" , err )
}
if line == nil {
return nil , fmt . Errorf ( "line %q not found" , lineArg )
}
var buf bytes . Buffer
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "lineid" , line . LineId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "type" , line . LineType ) )
lineNumStr := strconv . FormatInt ( line . LineNum , 10 )
if line . LineNumTemp {
lineNumStr = "~" + lineNumStr
}
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "linenum" , lineNumStr ) )
ts := time . UnixMilli ( line . Ts )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "ts" , ts . Format ( "2006-01-02 15:04:05" ) ) )
if line . Ephemeral {
2022-10-21 01:14:14 +02:00
buf . WriteString ( fmt . Sprintf ( " %-15s %v\n" , "ephemeral" , true ) )
2022-09-21 06:50:36 +02:00
}
if cmd != nil {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "cmdid" , cmd . CmdId ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "remote" , cmd . Remote . MakeFullRemoteRef ( ) ) )
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "status" , cmd . Status ) )
if cmd . RemoteState . Cwd != "" {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "cwd" , cmd . RemoteState . Cwd ) )
}
2022-09-21 21:39:55 +02:00
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "termopts" , formatTermOpts ( cmd . TermOpts ) ) )
2022-09-22 07:02:38 +02:00
if cmd . TermOpts != cmd . OrigTermOpts {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "orig-termopts" , formatTermOpts ( cmd . OrigTermOpts ) ) )
}
2022-10-28 02:10:36 +02:00
if cmd . RtnState {
buf . WriteString ( fmt . Sprintf ( " %-15s %s\n" , "rtnstate" , "true" ) )
}
2022-09-21 06:50:36 +02:00
}
update := sstore . ModelUpdate {
Info : & sstore . InfoMsgType {
InfoTitle : fmt . Sprintf ( "line %d info" , line . LineNum ) ,
InfoLines : splitLinesForInfo ( buf . String ( ) ) ,
} ,
}
return update , nil
2022-09-13 21:06:12 +02:00
}
2022-09-21 21:39:55 +02:00
func formatTermOpts ( termOpts sstore . TermOpts ) string {
if termOpts . Cols == 0 {
return "???"
}
rtnStr := fmt . Sprintf ( "%dx%d" , termOpts . Rows , termOpts . Cols )
if termOpts . FlexRows {
rtnStr += " flexrows"
}
if termOpts . MaxPtySize > 0 {
rtnStr += " maxbuf=" + scbase . NumFormatB2 ( termOpts . MaxPtySize )
}
return rtnStr
}
2022-09-25 04:54:06 +02:00
type ColMeta struct {
Title string
MinCols int
MaxCols int
}
func toInterfaceArr ( sarr [ ] string ) [ ] interface { } {
rtn := make ( [ ] interface { } , len ( sarr ) )
for idx , s := range sarr {
rtn [ idx ] = s
}
return rtn
}
func formatTextTable ( totalCols int , data [ ] [ ] string , colMeta [ ] ColMeta ) [ ] string {
numCols := len ( colMeta )
maxColLen := make ( [ ] int , len ( colMeta ) )
for i , cm := range colMeta {
maxColLen [ i ] = cm . MinCols
}
for _ , row := range data {
for i := 0 ; i < numCols && i < len ( row ) ; i ++ {
dlen := len ( row [ i ] )
if dlen > maxColLen [ i ] {
maxColLen [ i ] = dlen
}
}
}
fmtStr := ""
for idx , clen := range maxColLen {
if idx != 0 {
fmtStr += " "
}
fmtStr += fmt . Sprintf ( "%%%ds" , clen )
}
var rtn [ ] string
for _ , row := range data {
sval := fmt . Sprintf ( fmtStr , toInterfaceArr ( row ) ... )
rtn = append ( rtn , sval )
}
return rtn
}
2022-10-21 01:14:14 +02:00
func displayStateUpdate ( buf * bytes . Buffer , oldState packet . ShellState , newState packet . ShellState ) {
if newState . Cwd != oldState . Cwd {
2022-10-27 09:33:50 +02:00
buf . WriteString ( fmt . Sprintf ( "cwd %s\n" , newState . Cwd ) )
2022-10-21 01:14:14 +02:00
}
2022-10-25 21:31:29 +02:00
if ! bytes . Equal ( newState . ShellVars , oldState . ShellVars ) {
newEnvMap := shexec . DeclMapFromState ( & newState )
oldEnvMap := shexec . DeclMapFromState ( & oldState )
2022-10-21 01:14:14 +02:00
for key , newVal := range newEnvMap {
oldVal , found := oldEnvMap [ key ]
2022-10-27 09:33:50 +02:00
if ! found || ( ( oldVal . Value != newVal . Value ) || ( oldVal . IsExport ( ) != newVal . IsExport ( ) ) ) {
var exportStr string
if newVal . IsExport ( ) {
exportStr = "export "
}
buf . WriteString ( fmt . Sprintf ( "%s%s=%s\n" , exportStr , key , ShellQuote ( newVal . Value , false , 50 ) ) )
2022-10-21 01:14:14 +02:00
}
}
for key , _ := range oldEnvMap {
_ , found := newEnvMap [ key ]
if ! found {
2022-10-27 09:33:50 +02:00
buf . WriteString ( fmt . Sprintf ( "unset %s\n" , key ) )
2022-10-21 01:14:14 +02:00
}
}
}
if newState . Aliases != oldState . Aliases {
newAliasMap , _ := ParseAliases ( newState . Aliases )
oldAliasMap , _ := ParseAliases ( oldState . Aliases )
for aliasName , newAliasVal := range newAliasMap {
oldAliasVal , found := oldAliasMap [ aliasName ]
if ! found || newAliasVal != oldAliasVal {
buf . WriteString ( fmt . Sprintf ( "alias %s\n" , shellescape . Quote ( aliasName ) ) )
}
}
for aliasName , _ := range oldAliasMap {
_ , found := newAliasMap [ aliasName ]
if ! found {
2022-10-27 09:33:50 +02:00
buf . WriteString ( fmt . Sprintf ( "unalias %s\n" , shellescape . Quote ( aliasName ) ) )
2022-10-21 01:14:14 +02:00
}
}
}
if newState . Funcs != oldState . Funcs {
newFuncMap , _ := ParseFuncs ( newState . Funcs )
oldFuncMap , _ := ParseFuncs ( oldState . Funcs )
for funcName , newFuncVal := range newFuncMap {
oldFuncVal , found := oldFuncMap [ funcName ]
if ! found || newFuncVal != oldFuncVal {
buf . WriteString ( fmt . Sprintf ( "function %s\n" , shellescape . Quote ( funcName ) ) )
}
}
for funcName , _ := range oldFuncMap {
_ , found := newFuncMap [ funcName ]
if ! found {
2022-10-27 09:33:50 +02:00
buf . WriteString ( fmt . Sprintf ( "unset -f %s\n" , shellescape . Quote ( funcName ) ) )
2022-10-21 01:14:14 +02:00
}
}
}
}
2022-10-27 09:33:50 +02:00
func GetRtnStateDiff ( ctx context . Context , sessionId string , cmdId string ) ( [ ] byte , error ) {
cmd , err := sstore . GetCmdById ( ctx , sessionId , cmdId )
if err != nil {
return nil , err
}
if cmd == nil {
return nil , nil
}
if ! cmd . RtnState {
return nil , nil
}
if cmd . DonePk == nil || cmd . DonePk . FinalState == nil {
return nil , nil
}
var outputBytes bytes . Buffer
displayStateUpdate ( & outputBytes , cmd . RemoteState , * cmd . DonePk . FinalState )
return outputBytes . Bytes ( ) , nil
}