From 6807547ff79adcc43cde1579392dc35e44440de4 Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 15 Jul 2022 17:37:32 -0700 Subject: [PATCH] updates, switch to new fecmd packet to run all UI commands through --- cmd/main-server.go | 192 +------------------ pkg/cmdrunner/cmdrunner.go | 367 +++++++++++++++++++++++++++++++++++++ pkg/remote/remote.go | 54 +++--- pkg/scpacket/scpacket.go | 12 +- pkg/sstore/dbops.go | 22 +++ pkg/sstore/updatebus.go | 33 +++- 6 files changed, 461 insertions(+), 219 deletions(-) create mode 100644 pkg/cmdrunner/cmdrunner.go diff --git a/cmd/main-server.go b/cmd/main-server.go index ca27f8f3..409730ac 100644 --- a/cmd/main-server.go +++ b/cmd/main-server.go @@ -10,7 +10,6 @@ import ( "net/http" "os" "runtime/debug" - "strconv" "strings" "sync" "time" @@ -18,7 +17,7 @@ import ( "github.com/google/uuid" "github.com/gorilla/mux" - "github.com/scripthaus-dev/mshell/pkg/packet" + "github.com/scripthaus-dev/sh2-server/pkg/cmdrunner" "github.com/scripthaus-dev/sh2-server/pkg/remote" "github.com/scripthaus-dev/sh2-server/pkg/scbase" "github.com/scripthaus-dev/sh2-server/pkg/scpacket" @@ -153,69 +152,6 @@ func HandleGetAllSessions(w http.ResponseWriter, r *http.Request) { return } -// params: name -func HandleCreateSession(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) - w.Header().Set("Access-Control-Allow-Credentials", "true") - w.Header().Set("Vary", "Origin") - w.Header().Set("Cache-Control", "no-cache") - qvals := r.URL.Query() - name := qvals.Get("name") - sessionId, err := sstore.InsertSessionWithName(r.Context(), name) - if err != nil { - WriteJsonError(w, fmt.Errorf("inserting session: %w", err)) - return - } - session, err := sstore.GetSessionById(r.Context(), sessionId) - if err != nil { - WriteJsonError(w, fmt.Errorf("getting new session: %w", err)) - return - } - WriteJsonSuccess(w, session) - return -} - -// params: sessionid, name -func HandleCreateWindow(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) - w.Header().Set("Access-Control-Allow-Credentials", "true") - w.Header().Set("Vary", "Origin") - w.Header().Set("Cache-Control", "no-cache") - qvals := r.URL.Query() - sessionId := qvals.Get("sessionid") - if _, err := uuid.Parse(sessionId); err != nil { - WriteJsonError(w, fmt.Errorf("invalid sessionid: %w", err)) - return - } - name := qvals.Get("name") - fmt.Printf("insert-window %s\n", name) - return -} - -// params: sessionid, name, activate -func HandleCreateScreen(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) - w.Header().Set("Access-Control-Allow-Credentials", "true") - w.Header().Set("Vary", "Origin") - w.Header().Set("Cache-Control", "no-cache") - qvals := r.URL.Query() - sessionId := qvals.Get("sessionid") - if _, err := uuid.Parse(sessionId); err != nil { - WriteJsonError(w, fmt.Errorf("invalid sessionid: %w", err)) - return - } - name := qvals.Get("name") - activateStr := qvals.Get("activate") - activate := activateStr != "" - screenId, err := sstore.InsertScreen(r.Context(), sessionId, name, activate) - if err != nil { - WriteJsonError(w, fmt.Errorf("inserting screen: %w", err)) - return - } - WriteJsonSuccess(w, screenId) - return -} - // params: [none] func HandleGetRemotes(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin")) @@ -304,7 +240,7 @@ func HandleGetPtyOut(w http.ResponseWriter, r *http.Request) { func WriteJsonError(w http.ResponseWriter, errVal error) { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(500) + w.WriteHeader(200) errMap := make(map[string]interface{}) errMap["error"] = errVal.Error() barr, _ := json.Marshal(errMap) @@ -329,11 +265,6 @@ func WriteJsonSuccess(w http.ResponseWriter, data interface{}) { return } -type runCommandResponse struct { - Line *sstore.LineType `json:"line"` - Cmd *sstore.CmdType `json:"cmd"` -} - func HandleRunCommand(w http.ResponseWriter, r *http.Request) { defer func() { r := recover() @@ -360,127 +291,15 @@ func HandleRunCommand(w http.ResponseWriter, r *http.Request) { WriteJsonError(w, fmt.Errorf("error decoding json: %w", err)) return } - if _, err = uuid.Parse(commandPk.SessionId); err != nil { - WriteJsonError(w, fmt.Errorf("invalid sessionid '%s': %w", commandPk.SessionId, err)) - return - } - resp, err := ProcessFeCommandPacket(r.Context(), &commandPk) + update, err := cmdrunner.HandleCommand(r.Context(), &commandPk) if err != nil { WriteJsonError(w, err) return } - WriteJsonSuccess(w, resp) + WriteJsonSuccess(w, update) return } -func ProcessFeCommandPacket(ctx context.Context, pk *scpacket.FeCommandPacketType) (*runCommandResponse, error) { - // parse metacmd - commandStr := strings.TrimSpace(pk.CmdStr) - if commandStr == "" { - return nil, fmt.Errorf("invalid emtpty command") - } - metaCmd := "" - metaSubCmd := "" - if commandStr == "cd" || strings.HasPrefix(commandStr, "cd ") { - metaCmd = "cd" - commandStr = strings.TrimSpace(commandStr[2:]) - } else if commandStr[0] == '/' { - spaceIdx := strings.Index(commandStr, " ") - if spaceIdx == -1 { - metaCmd = commandStr[1:] - commandStr = "" - } else { - metaCmd = commandStr[1:spaceIdx] - commandStr = strings.TrimSpace(commandStr[spaceIdx+1:]) - } - colonIdx := strings.Index(metaCmd, ":") - if colonIdx != -1 { - metaCmd, metaSubCmd = metaCmd[0:colonIdx], metaCmd[colonIdx+1:] - } - if metaCmd == "" { - return nil, fmt.Errorf("invalid command, got bare '/', with no command") - } - } - - // execute metacmd - if metaCmd == "comment" { - text := commandStr - rtnLine, err := sstore.AddCommentLine(ctx, pk.SessionId, pk.WindowId, pk.UserId, text) - if err != nil { - return nil, err - } - return &runCommandResponse{Line: rtnLine}, nil - } else if metaCmd == "cd" { - newDir := commandStr - cdPacket := packet.MakeCdPacket() - cdPacket.ReqId = uuid.New().String() - cdPacket.Dir = newDir - localRemote := remote.GetRemoteById(pk.RemoteState.RemoteId) - if localRemote == nil { - return nil, fmt.Errorf("invalid remote, cannot execute command") - } - resp, err := localRemote.PacketRpc(ctx, cdPacket) - if err != nil { - return nil, err - } - fmt.Printf("GOT cd RESP: %v\n", resp) - return nil, nil - } else if metaCmd == "s" || metaCmd == "screen" { - err := RunScreenCmd(ctx, pk.SessionId, pk.ScreenId, metaSubCmd, commandStr) - if err != nil { - return nil, err - } - return nil, nil - } else if metaCmd == "" { - cmdId := uuid.New().String() - cmd, err := remote.RunCommand(ctx, pk, cmdId) - if err != nil { - return nil, err - } - rtnLine, err := sstore.AddCmdLine(ctx, pk.SessionId, pk.WindowId, pk.UserId, cmd) - if err != nil { - return nil, err - } - return &runCommandResponse{Line: rtnLine, Cmd: cmd}, nil - } else { - fmt.Printf("INVALID metacmd '%s'\n", metaCmd) - return nil, fmt.Errorf("Invalid command type /%s", metaCmd) - } -} - -func RunScreenCmd(ctx context.Context, sessionId string, screenId string, subCmd string, commandStr string) error { - if subCmd == "close" { - err := sstore.DeleteScreen(ctx, sessionId, screenId) - if err != nil { - return err - } - return nil - } - if subCmd != "" { - return fmt.Errorf("invalid /screen subcommand '%s'", subCmd) - } - if commandStr == "" { - return fmt.Errorf("usage /screen [screen-name|screen-index|screen-id], no param specified") - } - screens, err := sstore.GetSessionScreens(ctx, sessionId) - if err != nil { - return fmt.Errorf("could not retreive screens for session=%s", sessionId) - } - screenNum, err := strconv.Atoi(commandStr) - if err == nil { - if screenNum < 1 || screenNum > len(screens) { - return fmt.Errorf("could not switch to screen #%d (out of range), valid screens 1-%d", screenNum, len(screens)) - } - return sstore.SwitchScreenById(ctx, sessionId, screens[screenNum-1].ScreenId) - } - for _, screen := range screens { - if screen.ScreenId == commandStr || screen.Name == commandStr { - return sstore.SwitchScreenById(ctx, sessionId, screen.ScreenId) - } - } - return fmt.Errorf("could not switch to screen '%s' (name/id not found)", commandStr) -} - // /api/start-session // returns: // * userid @@ -620,11 +439,8 @@ func main() { gr := mux.NewRouter() gr.HandleFunc("/api/ptyout", HandleGetPtyOut) gr.HandleFunc("/api/get-all-sessions", HandleGetAllSessions) - gr.HandleFunc("/api/create-session", HandleCreateSession) gr.HandleFunc("/api/get-window", HandleGetWindow) gr.HandleFunc("/api/get-remotes", HandleGetRemotes) - gr.HandleFunc("/api/create-window", HandleCreateWindow) - gr.HandleFunc("/api/create-screen", HandleCreateScreen) gr.HandleFunc("/api/run-command", HandleRunCommand).Methods("GET", "POST", "OPTIONS") server := &http.Server{ Addr: MainServerAddr, diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go new file mode 100644 index 00000000..7eae553b --- /dev/null +++ b/pkg/cmdrunner/cmdrunner.go @@ -0,0 +1,367 @@ +package cmdrunner + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/google/uuid" + "github.com/scripthaus-dev/mshell/pkg/base" + "github.com/scripthaus-dev/mshell/pkg/packet" + "github.com/scripthaus-dev/sh2-server/pkg/remote" + "github.com/scripthaus-dev/sh2-server/pkg/scpacket" + "github.com/scripthaus-dev/sh2-server/pkg/sstore" +) + +const DefaultUserId = "sawka" + +const ( + R_Session = 1 + R_Screen = 2 + R_Window = 4 + R_Remote = 8 + R_SessionOpt = 16 + R_ScreenOpt = 32 + R_WindowOpt = 64 + R_RemoteOpt = 128 +) + +type resolvedIds struct { + SessionId string + ScreenId string + WindowId string + RemoteId string + RemoteState *sstore.RemoteState +} + +func SubMetaCmd(cmd string) string { + switch cmd { + case "s": + return "screen" + case "w": + return "window" + case "r": + return "run" + case "c": + return "comment" + case "e": + return "eval" + default: + return cmd + } +} + +func firstArg(pk *scpacket.FeCommandPacketType) string { + if len(pk.Args) == 0 { + return "" + } + return pk.Args[0] +} + +func resolveBool(arg string, def bool) bool { + if arg == "" { + return def + } + if arg == "0" || arg == "false" { + return false + } + return true +} + +func resolveSessionScreen(ctx context.Context, sessionId string, screenArg string) (string, error) { + screens, err := sstore.GetSessionScreens(ctx, sessionId) + if err != nil { + return "", fmt.Errorf("could not retreive screens for session=%s", sessionId) + } + screenNum, err := strconv.Atoi(screenArg) + if err == nil { + if screenNum < 1 || screenNum > len(screens) { + return "", fmt.Errorf("could not resolve screen #%d (out of range), valid screens 1-%d", screenNum, len(screens)) + } + return screens[screenNum-1].ScreenId, nil + } + for _, screen := range screens { + if screen.ScreenId == screenArg || screen.Name == screenArg { + return screen.ScreenId, nil + } + } + return "", fmt.Errorf("could not resolve screen '%s' (name/id not found)", screenArg) +} + +func resolveSessionId(pk *scpacket.FeCommandPacketType) (string, error) { + sessionId := pk.Kwargs["session"] + if sessionId == "" { + return "", nil + } + if _, err := uuid.Parse(sessionId); err != nil { + return "", fmt.Errorf("invalid sessionid '%s'", sessionId) + } + return sessionId, nil +} + +func resolveWindowId(pk *scpacket.FeCommandPacketType, sessionId string) (string, error) { + windowId := pk.Kwargs["window"] + if windowId == "" { + return "", nil + } + if _, err := uuid.Parse(windowId); err != nil { + return "", fmt.Errorf("invalid windowid '%s'", windowId) + } + return windowId, nil +} + +func resolveScreenId(ctx context.Context, pk *scpacket.FeCommandPacketType, sessionId string) (string, error) { + screenArg := pk.Kwargs["screen"] + if screenArg == "" { + return "", nil + } + if _, err := uuid.Parse(screenArg); err == nil { + return screenArg, nil + } + if sessionId == "" { + return "", fmt.Errorf("cannot resolve screen without session") + } + return resolveSessionScreen(ctx, sessionId, screenArg) +} + +func resolveRemote(ctx context.Context, pk *scpacket.FeCommandPacketType, sessionId string, windowId string) (string, *sstore.RemoteState, error) { + remoteArg := pk.Kwargs["remote"] + if remoteArg == "" { + return "", nil, nil + } + remoteId, state, err := sstore.GetRemoteState(ctx, remoteArg, sessionId, windowId) + if err != nil { + return "", nil, fmt.Errorf("cannot resolve remote '%s': %w", remoteArg, err) + } + if state == nil { + state, err = remote.GetDefaultRemoteStateById(remoteId) + if err != nil { + return "", nil, fmt.Errorf("cannot resolve remote '%s': %w", remoteArg, err) + } + } + return remoteId, state, nil +} + +func resolveIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int) (resolvedIds, error) { + rtn := resolvedIds{} + if rtype == 0 { + return rtn, nil + } + var err error + if (rtype&R_Session)+(rtype&R_SessionOpt) > 0 { + rtn.SessionId, err = resolveSessionId(pk) + if err != nil { + return rtn, err + } + if rtn.SessionId == "" && (rtype&R_Session) > 0 { + return rtn, fmt.Errorf("no session") + } + } + if (rtype&R_Window)+(rtype&R_WindowOpt) > 0 { + rtn.WindowId, err = resolveWindowId(pk, rtn.SessionId) + if err != nil { + return rtn, err + } + if rtn.WindowId == "" && (rtype&R_Window) > 0 { + return rtn, fmt.Errorf("no window") + } + + } + if (rtype&R_Screen)+(rtype&R_ScreenOpt) > 0 { + rtn.ScreenId, err = resolveScreenId(ctx, pk, rtn.SessionId) + if err != nil { + return rtn, err + } + if rtn.ScreenId == "" && (rtype&R_Screen) > 0 { + return rtn, fmt.Errorf("no screen") + } + } + if (rtype&R_Remote)+(rtype&R_RemoteOpt) > 0 { + rtn.RemoteId, rtn.RemoteState, err = resolveRemote(ctx, pk, rtn.SessionId, rtn.WindowId) + if err != nil { + return rtn, err + } + if rtn.RemoteId == "" && (rtype&R_Remote) > 0 { + return rtn, fmt.Errorf("no remote") + } + } + return rtn, nil +} + +func HandleCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + switch SubMetaCmd(pk.MetaCmd) { + case "run": + return RunCommand(ctx, pk) + + case "eval": + return EvalCommand(ctx, pk) + + case "screen": + return ScreenCommand(ctx, pk) + + case "comment": + return CommentCommand(ctx, pk) + + case "cd": + return CdCommand(ctx, pk) + + default: + return nil, fmt.Errorf("invalid command '/%s', no handler", pk.MetaCmd) + } +} + +func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote) + if err != nil { + return nil, fmt.Errorf("/run error: %w", err) + } + cmdId := uuid.New().String() + cmdStr := firstArg(pk) + runPacket := packet.MakeRunPacket() + runPacket.ReqId = uuid.New().String() + runPacket.CK = base.MakeCommandKey(ids.SessionId, cmdId) + runPacket.Cwd = ids.RemoteState.Cwd + runPacket.Env = nil + runPacket.UsePty = true + runPacket.TermOpts = &packet.TermOpts{Rows: remote.DefaultTermRows, Cols: remote.DefaultTermCols, Term: remote.DefaultTerm} + runPacket.Command = strings.TrimSpace(cmdStr) + cmd, err := remote.RunCommand(ctx, cmdId, ids.RemoteId, ids.RemoteState, runPacket) + if err != nil { + return nil, err + } + rtnLine, err := sstore.AddCmdLine(ctx, ids.SessionId, ids.WindowId, DefaultUserId, cmd) + if err != nil { + return nil, err + } + return sstore.LineUpdate{Line: rtnLine, Cmd: cmd}, nil +} + +func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + if len(pk.Args) == 0 { + return nil, fmt.Errorf("usage: /eval [command], no command passed to eval") + } + // parse metacmd + commandStr := strings.TrimSpace(pk.Args[0]) + if commandStr == "" { + return nil, fmt.Errorf("/eval, invalid emtpty command") + } + metaCmd := "" + metaSubCmd := "" + if commandStr == "cd" || strings.HasPrefix(commandStr, "cd ") { + metaCmd = "cd" + commandStr = strings.TrimSpace(commandStr[2:]) + } else if commandStr[0] == '/' { + spaceIdx := strings.Index(commandStr, " ") + if spaceIdx == -1 { + metaCmd = commandStr[1:] + commandStr = "" + } else { + metaCmd = commandStr[1:spaceIdx] + commandStr = strings.TrimSpace(commandStr[spaceIdx+1:]) + } + colonIdx := strings.Index(metaCmd, ":") + if colonIdx != -1 { + metaCmd, metaSubCmd = metaCmd[0:colonIdx], metaCmd[colonIdx+1:] + } + if metaCmd == "" { + return nil, fmt.Errorf("invalid command, got bare '/', with no command") + } + } + if metaCmd == "" { + metaCmd = "run" + } + var args []string + if metaCmd == "run" || metaCmd == "comment" { + args = []string{commandStr} + } else { + args = strings.Fields(commandStr) + } + newPk := &scpacket.FeCommandPacketType{ + MetaCmd: metaCmd, + MetaSubCmd: metaSubCmd, + Args: args, + Kwargs: pk.Kwargs, + } + return HandleCommand(ctx, newPk) +} + +func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + if pk.MetaSubCmd == "close" { + ids, err := resolveIds(ctx, pk, R_Session|R_Screen) + if err != nil { + return nil, fmt.Errorf("/screen:close cannot close screen: %w", err) + } + err = sstore.DeleteScreen(ctx, ids.SessionId, ids.ScreenId) + if err != nil { + return nil, err + } + return nil, nil + } + if pk.MetaSubCmd == "open" || pk.MetaSubCmd == "new" { + ids, err := resolveIds(ctx, pk, R_Session) + if err != nil { + return nil, fmt.Errorf("/screen:open cannot open screen: %w", err) + } + activate := resolveBool(pk.Kwargs["activate"], true) + _, err = sstore.InsertScreen(ctx, ids.SessionId, pk.Kwargs["name"], activate) + if err != nil { + return nil, err + } + return nil, nil + } + if pk.MetaSubCmd != "" { + return nil, fmt.Errorf("invalid /screen subcommand '%s'", pk.MetaSubCmd) + } + ids, err := resolveIds(ctx, pk, R_Session) + if err != nil { + return nil, fmt.Errorf("/screen cannot switch to screen: %w", err) + } + firstArg := firstArg(pk) + if firstArg == "" { + return nil, fmt.Errorf("usage /screen [screen-name|screen-index|screen-id], no param specified") + } + screenIdArg, err := resolveSessionScreen(ctx, ids.SessionId, firstArg) + if err != nil { + return nil, err + } + sstore.SwitchScreenById(ctx, ids.SessionId, screenIdArg) + return nil, nil +} + +func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote) + if err != nil { + return nil, fmt.Errorf("/cd error: %w", err) + } + newDir := firstArg(pk) + cdPacket := packet.MakeCdPacket() + cdPacket.ReqId = uuid.New().String() + cdPacket.Dir = newDir + localRemote := remote.GetRemoteById(ids.RemoteId) + if localRemote == nil { + return nil, fmt.Errorf("invalid remote, cannot execute command") + } + resp, err := localRemote.PacketRpc(ctx, cdPacket) + if err != nil { + return nil, err + } + fmt.Printf("GOT cd RESP: %v\n", resp) + return nil, nil +} + +func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + ids, err := resolveIds(ctx, pk, R_Session|R_Window) + if err != nil { + return nil, fmt.Errorf("/comment error: %w", err) + } + 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 + } + return sstore.LineUpdate{Line: rtnLine}, nil +} diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index e53ae02b..faf0ab26 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -6,14 +6,11 @@ import ( "errors" "fmt" "os/exec" - "strings" "sync" - "github.com/google/uuid" "github.com/scripthaus-dev/mshell/pkg/base" "github.com/scripthaus-dev/mshell/pkg/packet" "github.com/scripthaus-dev/mshell/pkg/shexec" - "github.com/scripthaus-dev/sh2-server/pkg/scpacket" "github.com/scripthaus-dev/sh2-server/pkg/sstore" ) @@ -123,6 +120,21 @@ func GetAllRemoteState() []RemoteState { return rtn } +func GetDefaultRemoteStateById(remoteId string) (*sstore.RemoteState, error) { + remote := GetRemoteById(remoteId) + if remote == nil { + return nil, fmt.Errorf("remote not found") + } + if !remote.IsConnected() { + return nil, fmt.Errorf("remote not connected") + } + state := remote.GetDefaultState() + if state == nil { + return nil, fmt.Errorf("could not get default remote state") + } + return state, nil +} + func MakeMShell(r *sstore.RemoteType) *MShellProc { rtn := &MShellProc{Lock: &sync.Mutex{}, Remote: r, Status: StatusInit} return rtn @@ -168,6 +180,15 @@ func (msh *MShellProc) IsConnected() bool { return msh.Status == StatusConnected } +func (msh *MShellProc) GetDefaultState() *sstore.RemoteState { + msh.Lock.Lock() + defer msh.Lock.Unlock() + if msh.ServerProc == nil || msh.ServerProc.InitPk == nil { + return nil + } + return &sstore.RemoteState{Cwd: msh.ServerProc.InitPk.HomeDir} +} + func (msh *MShellProc) IsCmdRunning(ck base.CommandKey) bool { msh.Lock.Lock() defer msh.Lock.Unlock() @@ -193,30 +214,21 @@ func (msh *MShellProc) SendInput(pk *packet.InputPacketType) error { return msh.ServerProc.Input.SendPacket(dataPk) } -func convertRemoteState(rs scpacket.RemoteState) sstore.RemoteState { - return sstore.RemoteState{Cwd: rs.Cwd} -} - func makeTermOpts() sstore.TermOpts { return sstore.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, FlexRows: true} } -func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, cmdId string) (*sstore.CmdType, error) { - msh := GetRemoteById(pk.RemoteState.RemoteId) +func RunCommand(ctx context.Context, cmdId string, remoteId string, remoteState *sstore.RemoteState, runPacket *packet.RunPacketType) (*sstore.CmdType, error) { + msh := GetRemoteById(remoteId) if msh == nil { - return nil, fmt.Errorf("no remote id=%s found", pk.RemoteState.RemoteId) + return nil, fmt.Errorf("no remote id=%s found", remoteId) } if !msh.IsConnected() { - return nil, fmt.Errorf("remote '%s' is not connected", msh.Remote.RemoteName) + return nil, fmt.Errorf("remote '%s' is not connected", remoteId) + } + if remoteState == nil { + return nil, fmt.Errorf("no remote state passed to RunCommand") } - runPacket := packet.MakeRunPacket() - runPacket.ReqId = uuid.New().String() - runPacket.CK = base.MakeCommandKey(pk.SessionId, cmdId) - runPacket.Cwd = pk.RemoteState.Cwd - runPacket.Env = nil - runPacket.UsePty = true - runPacket.TermOpts = &packet.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, Term: DefaultTerm} - runPacket.Command = strings.TrimSpace(pk.CmdStr) fmt.Printf("RUN-CMD> %s reqid=%s (msh=%v)\n", runPacket.CK, runPacket.ReqId, msh.Remote) msh.ServerProc.Output.RegisterRpc(runPacket.ReqId) err := shexec.SendRunPacketAndRunData(ctx, msh.ServerProc.Input, runPacket) @@ -240,11 +252,11 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, cmdId str status = sstore.CmdStatusDetached } cmd := &sstore.CmdType{ - SessionId: pk.SessionId, + SessionId: runPacket.CK.GetSessionId(), CmdId: startPk.CK.GetCmdId(), CmdStr: runPacket.Command, RemoteId: msh.Remote.RemoteId, - RemoteState: convertRemoteState(pk.RemoteState), + RemoteState: *remoteState, TermOpts: makeTermOpts(), Status: status, StartPk: startPk, diff --git a/pkg/scpacket/scpacket.go b/pkg/scpacket/scpacket.go index 0c0b3459..b5e32e0a 100644 --- a/pkg/scpacket/scpacket.go +++ b/pkg/scpacket/scpacket.go @@ -16,13 +16,11 @@ type RemoteState struct { } type FeCommandPacketType struct { - Type string `json:"type"` - SessionId string `json:"sessionid"` - ScreenId string `json:"screenid"` - WindowId string `json:"windowid"` - UserId string `json:"userid"` - CmdStr string `json:"cmdstr"` - RemoteState RemoteState `json:"remotestate"` + Type string `json:"type"` + MetaCmd string `json:"metacmd"` + MetaSubCmd string `json:"metasubcmd,omitempty"` + Args []string `json:"args,omitempty"` + Kwargs map[string]string `json:"kwargs,omitempty"` } func init() { diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index d18350c2..1d475b53 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -468,3 +468,25 @@ func DeleteScreen(ctx context.Context, sessionId string, screenId string) error MainBus.SendUpdate("", update) return nil } + +func GetRemoteState(ctx context.Context, rname string, sessionId string, windowId string) (string, *RemoteState, error) { + var remoteId string + var remoteState *RemoteState + txErr := WithTx(ctx, func(tx *TxWrap) error { + var ri RemoteInstance + query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND name = ?` + found := tx.GetWrap(&ri, query, sessionId, windowId, rname) + if found { + remoteId = ri.RemoteId + remoteState = &ri.State + return nil + } + query = `SELECT remoteid FROM remote WHERE remotename = ?` + remoteId = tx.GetString(query, rname) + if remoteId == "" { + return fmt.Errorf("remote not found", rname) + } + return nil + }) + return remoteId, remoteState, txErr +} diff --git a/pkg/sstore/updatebus.go b/pkg/sstore/updatebus.go index b9e94298..f36b7626 100644 --- a/pkg/sstore/updatebus.go +++ b/pkg/sstore/updatebus.go @@ -4,6 +4,16 @@ import "sync" var MainBus *UpdateBus = MakeUpdateBus() +const PtyDataUpdateStr = "pty" +const SessionUpdateStr = "session" +const WindowUpdateStr = "window" +const CmdUpdateStr = "cmd" +const LineCmdUpdateStr = "line+cmd" + +type UpdatePacket interface { + UpdateType() string +} + type UpdateCmd struct { CmdId string Status string @@ -17,15 +27,27 @@ type PtyDataUpdate struct { PtyDataLen int64 `json:"ptydatalen"` } +func (PtyDataUpdate) UpdateType() string { + return PtyDataUpdateStr +} + type WindowUpdate struct { Window WindowType `json:"window"` Remove bool `json:"remove,omitempty"` } +func (WindowUpdate) WindowUpdate() string { + return WindowUpdateStr +} + type SessionUpdate struct { Sessions []*SessionType `json:"sessions"` } +func (SessionUpdate) UpdateType() string { + return SessionUpdateStr +} + func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) { session := &SessionType{ SessionId: sessionId, @@ -37,9 +59,14 @@ func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) { return update, session } -type CmdUpdate struct { - Cmd CmdType `json:"cmd"` - Remove bool `json:"remove,omitempty"` +type LineUpdate struct { + Line *LineType `json:"line"` + Cmd *CmdType `json:"cmd,omitempty"` + Remove bool `json:"remove,omitempty"` +} + +func (LineUpdate) UpdateType() string { + return LineCmdUpdateStr } type UpdateChannel struct {