mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
updates for getting pty updates sent again
This commit is contained in:
+12
-166
@@ -16,11 +16,11 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/scripthaus-dev/mshell/pkg/cmdtail"
|
|
||||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/scws"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/wsshell"
|
"github.com/scripthaus-dev/sh2-server/pkg/wsshell"
|
||||||
)
|
)
|
||||||
@@ -35,18 +35,16 @@ const MainServerAddr = "localhost:8080"
|
|||||||
const WSStateReconnectTime = 30 * time.Second
|
const WSStateReconnectTime = 30 * time.Second
|
||||||
const WSStatePacketChSize = 20
|
const WSStatePacketChSize = 20
|
||||||
|
|
||||||
const MaxInputDataSize = 1000
|
|
||||||
|
|
||||||
var GlobalLock = &sync.Mutex{}
|
var GlobalLock = &sync.Mutex{}
|
||||||
var WSStateMap = make(map[string]*WSState) // clientid -> WsState
|
var WSStateMap = make(map[string]*scws.WSState) // clientid -> WsState
|
||||||
|
|
||||||
func setWSState(state *WSState) {
|
func setWSState(state *scws.WSState) {
|
||||||
GlobalLock.Lock()
|
GlobalLock.Lock()
|
||||||
defer GlobalLock.Unlock()
|
defer GlobalLock.Unlock()
|
||||||
WSStateMap[state.ClientId] = state
|
WSStateMap[state.ClientId] = state
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWSState(clientId string) *WSState {
|
func getWSState(clientId string) *scws.WSState {
|
||||||
GlobalLock.Lock()
|
GlobalLock.Lock()
|
||||||
defer GlobalLock.Unlock()
|
defer GlobalLock.Unlock()
|
||||||
return WSStateMap[clientId]
|
return WSStateMap[clientId]
|
||||||
@@ -62,103 +60,10 @@ func removeWSStateAfterTimeout(clientId string, connectTime time.Time, waitDurat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(WSStateMap, clientId)
|
delete(WSStateMap, clientId)
|
||||||
err := state.CloseTailer()
|
state.UnWatchScreen()
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[error] closing tailer on ws %v\n", err)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
type WSState struct {
|
|
||||||
Lock *sync.Mutex
|
|
||||||
ClientId string
|
|
||||||
ConnectTime time.Time
|
|
||||||
Shell *wsshell.WSShell
|
|
||||||
Tailer *cmdtail.Tailer
|
|
||||||
PacketCh chan packet.PacketType
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeWSState(clientId string) (*WSState, error) {
|
|
||||||
var err error
|
|
||||||
rtn := &WSState{}
|
|
||||||
rtn.Lock = &sync.Mutex{}
|
|
||||||
rtn.ClientId = clientId
|
|
||||||
rtn.ConnectTime = time.Now()
|
|
||||||
rtn.PacketCh = make(chan packet.PacketType, WSStatePacketChSize)
|
|
||||||
chSender := packet.MakeChannelPacketSender(rtn.PacketCh)
|
|
||||||
gen := scbase.ScFileNameGenerator{ScHome: scbase.GetScHomeDir()}
|
|
||||||
rtn.Tailer, err = cmdtail.MakeTailer(chSender, gen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
defer close(rtn.PacketCh)
|
|
||||||
rtn.Tailer.Run()
|
|
||||||
}()
|
|
||||||
go rtn.runTailerToWS()
|
|
||||||
return rtn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *WSState) CloseTailer() error {
|
|
||||||
return ws.Tailer.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *WSState) getShell() *wsshell.WSShell {
|
|
||||||
ws.Lock.Lock()
|
|
||||||
defer ws.Lock.Unlock()
|
|
||||||
return ws.Shell
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *WSState) runTailerToWS() {
|
|
||||||
for pk := range ws.PacketCh {
|
|
||||||
if pk.GetType() == "cmddata" {
|
|
||||||
dataPacket := pk.(*packet.CmdDataPacketType)
|
|
||||||
err := ws.writePacket(dataPacket)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[error] writing packet to ws: %v\n", err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Printf("tailer-to-ws, bad packet %v\n", pk.GetType())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *WSState) writePacket(pk packet.PacketType) error {
|
|
||||||
shell := ws.getShell()
|
|
||||||
if shell == nil || shell.IsClosed() {
|
|
||||||
return fmt.Errorf("cannot write packet, empty or closed wsshell")
|
|
||||||
}
|
|
||||||
err := shell.WriteJson(pk)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *WSState) getConnectTime() time.Time {
|
|
||||||
ws.Lock.Lock()
|
|
||||||
defer ws.Lock.Unlock()
|
|
||||||
return ws.ConnectTime
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *WSState) updateConnectTime() {
|
|
||||||
ws.Lock.Lock()
|
|
||||||
defer ws.Lock.Unlock()
|
|
||||||
ws.ConnectTime = time.Now()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ws *WSState) replaceExistingShell(shell *wsshell.WSShell) {
|
|
||||||
ws.Lock.Lock()
|
|
||||||
defer ws.Lock.Unlock()
|
|
||||||
if ws.Shell == nil {
|
|
||||||
ws.Shell = shell
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ws.Shell.Conn.Close()
|
|
||||||
ws.Shell = shell
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleWs(w http.ResponseWriter, r *http.Request) {
|
func HandleWs(w http.ResponseWriter, r *http.Request) {
|
||||||
shell, err := wsshell.StartWS(w, r)
|
shell, err := wsshell.StartWS(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -173,57 +78,20 @@ func HandleWs(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
state := getWSState(clientId)
|
state := getWSState(clientId)
|
||||||
if state == nil {
|
if state == nil {
|
||||||
state, err = MakeWSState(clientId)
|
state = scws.MakeWSState(clientId)
|
||||||
if err != nil {
|
state.ReplaceShell(shell)
|
||||||
fmt.Printf("cannot make wsstate: %v\n", err)
|
|
||||||
close(shell.WriteChan)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
state.replaceExistingShell(shell)
|
|
||||||
setWSState(state)
|
setWSState(state)
|
||||||
} else {
|
} else {
|
||||||
state.updateConnectTime()
|
state.UpdateConnectTime()
|
||||||
state.replaceExistingShell(shell)
|
state.ReplaceShell(shell)
|
||||||
}
|
}
|
||||||
stateConnectTime := state.getConnectTime()
|
stateConnectTime := state.GetConnectTime()
|
||||||
defer func() {
|
defer func() {
|
||||||
removeWSStateAfterTimeout(clientId, stateConnectTime, WSStateReconnectTime)
|
removeWSStateAfterTimeout(clientId, stateConnectTime, WSStateReconnectTime)
|
||||||
}()
|
}()
|
||||||
shell.WriteJson(map[string]interface{}{"type": "hello"}) // let client know we accepted this connection, ignore error
|
shell.WriteJson(map[string]interface{}{"type": "hello"}) // let client know we accepted this connection, ignore error
|
||||||
fmt.Printf("WebSocket opened %s %s\n", shell.RemoteAddr, state.ClientId)
|
fmt.Printf("WebSocket opened %s %s\n", state.ClientId, shell.RemoteAddr)
|
||||||
for msgBytes := range shell.ReadChan {
|
state.RunWSRead()
|
||||||
pk, err := packet.ParseJsonPacket(msgBytes)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error unmarshalling ws message: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if pk.GetType() == "getcmd" {
|
|
||||||
getPk := pk.(*packet.GetCmdPacketType)
|
|
||||||
done, err := state.Tailer.AddWatch(getPk)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: send responseerror
|
|
||||||
respPk := packet.MakeErrorResponsePacket(getPk.ReqId, err)
|
|
||||||
fmt.Printf("[error] adding watch to tailer: %v\n", err)
|
|
||||||
fmt.Printf("%v\n", respPk)
|
|
||||||
}
|
|
||||||
if done {
|
|
||||||
respPk := packet.MakeResponsePacket(getPk.ReqId, true)
|
|
||||||
fmt.Printf("%v\n", respPk)
|
|
||||||
// TODO: send response
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if pk.GetType() == "input" {
|
|
||||||
go func() {
|
|
||||||
err = sendCmdInput(pk.(*packet.InputPacketType))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("[error] sending command input: %v\n", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Printf("got ws bad message: %v\n", pk.GetType())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: sync multiple writes to the same fifoName into a single go-routine and do liveness checking on fifo
|
// todo: sync multiple writes to the same fifoName into a single go-routine and do liveness checking on fifo
|
||||||
@@ -248,28 +116,6 @@ func writeToFifo(fifoName string, data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendCmdInput(pk *packet.InputPacketType) error {
|
|
||||||
err := pk.CK.Validate("input packet")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pk.RemoteId == "" {
|
|
||||||
return fmt.Errorf("input must set remoteid")
|
|
||||||
}
|
|
||||||
if len(pk.InputData64) == 0 && pk.SigNum == 0 {
|
|
||||||
return fmt.Errorf("empty input packet")
|
|
||||||
}
|
|
||||||
inputLen := packet.B64DecodedLen(pk.InputData64)
|
|
||||||
if inputLen > MaxInputDataSize {
|
|
||||||
return fmt.Errorf("input data size too large, len=%d (max=%d)", inputLen, MaxInputDataSize)
|
|
||||||
}
|
|
||||||
msh := remote.GetRemoteById(pk.RemoteId)
|
|
||||||
if msh == nil {
|
|
||||||
return fmt.Errorf("cannot connect to remote")
|
|
||||||
}
|
|
||||||
return msh.SendInput(pk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// params: sessionid
|
// params: sessionid
|
||||||
func HandleGetSession(w http.ResponseWriter, r *http.Request) {
|
func HandleGetSession(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, cmdId str
|
|||||||
DonePk: nil,
|
DonePk: nil,
|
||||||
RunOut: nil,
|
RunOut: nil,
|
||||||
}
|
}
|
||||||
err = sstore.AppendToCmdPtyBlob(ctx, cmd.SessionId, cmd.CmdId, nil)
|
err = sstore.AppendToCmdPtyBlob(ctx, cmd.SessionId, cmd.CmdId, nil, sstore.PosAppend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -358,7 +358,7 @@ func (runner *MShellProc) ProcessPackets() {
|
|||||||
}
|
}
|
||||||
var ack *packet.DataAckPacketType
|
var ack *packet.DataAckPacketType
|
||||||
if len(realData) > 0 {
|
if len(realData) > 0 {
|
||||||
err = sstore.AppendToCmdPtyBlob(context.Background(), dataPk.CK.GetSessionId(), dataPk.CK.GetCmdId(), realData)
|
err = sstore.AppendToCmdPtyBlob(context.Background(), dataPk.CK.GetSessionId(), dataPk.CK.GetCmdId(), realData, sstore.PosAppend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ack = makeDataAckPacket(dataPk.CK, dataPk.FdNum, 0, err)
|
ack = makeDataAckPacket(dataPk.CK, dataPk.FdNum, 0, err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const FeCommandPacketStr = "fecmd"
|
const FeCommandPacketStr = "fecmd"
|
||||||
|
const WatchScreenPacketStr = "watchscreen"
|
||||||
|
|
||||||
type RemoteState struct {
|
type RemoteState struct {
|
||||||
RemoteId string `json:"remoteid"`
|
RemoteId string `json:"remoteid"`
|
||||||
@@ -24,7 +25,8 @@ type FeCommandPacketType struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
packet.RegisterPacketType(FeCommandPacketStr, reflect.TypeOf(&FeCommandPacketType{}))
|
packet.RegisterPacketType(FeCommandPacketStr, reflect.TypeOf(FeCommandPacketType{}))
|
||||||
|
packet.RegisterPacketType(WatchScreenPacketStr, reflect.TypeOf(WatchScreenPacketType{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*FeCommandPacketType) GetType() string {
|
func (*FeCommandPacketType) GetType() string {
|
||||||
@@ -34,3 +36,17 @@ func (*FeCommandPacketType) GetType() string {
|
|||||||
func MakeFeCommandPacket() *FeCommandPacketType {
|
func MakeFeCommandPacket() *FeCommandPacketType {
|
||||||
return &FeCommandPacketType{Type: FeCommandPacketStr}
|
return &FeCommandPacketType{Type: FeCommandPacketStr}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WatchScreenPacketType struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
SessionId string `json:"sessionid"`
|
||||||
|
ScreenId string `json:"screenid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*WatchScreenPacketType) GetType() string {
|
||||||
|
return WatchScreenPacketStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeWatchScreenPacket() *WatchScreenPacketType {
|
||||||
|
return &WatchScreenPacketType{Type: WatchScreenPacketStr}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package scws
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"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"
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/wsshell"
|
||||||
|
)
|
||||||
|
|
||||||
|
const WSStatePacketChSize = 20
|
||||||
|
const MaxInputDataSize = 1000
|
||||||
|
|
||||||
|
type WSState struct {
|
||||||
|
Lock *sync.Mutex
|
||||||
|
ClientId string
|
||||||
|
ConnectTime time.Time
|
||||||
|
Shell *wsshell.WSShell
|
||||||
|
UpdateCh chan interface{}
|
||||||
|
UpdateQueue []interface{}
|
||||||
|
|
||||||
|
SessionId string
|
||||||
|
ScreenId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeWSState(clientId string) *WSState {
|
||||||
|
rtn := &WSState{}
|
||||||
|
rtn.Lock = &sync.Mutex{}
|
||||||
|
rtn.ClientId = clientId
|
||||||
|
rtn.ConnectTime = time.Now()
|
||||||
|
return rtn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WSState) GetShell() *wsshell.WSShell {
|
||||||
|
ws.Lock.Lock()
|
||||||
|
defer ws.Lock.Unlock()
|
||||||
|
return ws.Shell
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WSState) WriteUpdate(update interface{}) error {
|
||||||
|
shell := ws.GetShell()
|
||||||
|
if shell == nil {
|
||||||
|
return fmt.Errorf("cannot write update, empty shell")
|
||||||
|
}
|
||||||
|
err := shell.WriteJson(update)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WSState) UpdateConnectTime() {
|
||||||
|
ws.Lock.Lock()
|
||||||
|
defer ws.Lock.Unlock()
|
||||||
|
ws.ConnectTime = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WSState) GetConnectTime() time.Time {
|
||||||
|
ws.Lock.Lock()
|
||||||
|
defer ws.Lock.Unlock()
|
||||||
|
return ws.ConnectTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WSState) WatchScreen(sessionId string, screenId string) {
|
||||||
|
ws.Lock.Lock()
|
||||||
|
defer ws.Lock.Unlock()
|
||||||
|
if ws.SessionId == sessionId && ws.ScreenId == screenId {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ws.SessionId = sessionId
|
||||||
|
ws.ScreenId = screenId
|
||||||
|
ws.UpdateCh = sstore.MainBus.RegisterChannel(ws.ClientId, ws.SessionId)
|
||||||
|
go ws.RunUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WSState) UnWatchScreen() {
|
||||||
|
ws.Lock.Lock()
|
||||||
|
defer ws.Lock.Unlock()
|
||||||
|
sstore.MainBus.UnregisterChannel(ws.ClientId)
|
||||||
|
ws.SessionId = ""
|
||||||
|
ws.ScreenId = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WSState) getUpdateCh() chan interface{} {
|
||||||
|
ws.Lock.Lock()
|
||||||
|
defer ws.Lock.Unlock()
|
||||||
|
return ws.UpdateCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WSState) RunUpdates() {
|
||||||
|
updateCh := ws.getUpdateCh()
|
||||||
|
if updateCh == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for update := range updateCh {
|
||||||
|
shell := ws.GetShell()
|
||||||
|
if shell != nil {
|
||||||
|
shell.WriteJson(update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WSState) ReplaceShell(shell *wsshell.WSShell) {
|
||||||
|
ws.Lock.Lock()
|
||||||
|
defer ws.Lock.Unlock()
|
||||||
|
if ws.Shell == nil {
|
||||||
|
ws.Shell = shell
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ws.Shell.Conn.Close()
|
||||||
|
ws.Shell = shell
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WSState) RunWSRead() {
|
||||||
|
shell := ws.GetShell()
|
||||||
|
if shell == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for msgBytes := range shell.ReadChan {
|
||||||
|
pk, err := packet.ParseJsonPacket(msgBytes)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error unmarshalling ws message: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pk.GetType() == "input" {
|
||||||
|
go func() {
|
||||||
|
err = sendCmdInput(pk.(*packet.InputPacketType))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[error] sending command input: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pk.GetType() == "watchscreen" {
|
||||||
|
wsPk := pk.(*scpacket.WatchScreenPacketType)
|
||||||
|
if _, err := uuid.Parse(wsPk.SessionId); err != nil {
|
||||||
|
fmt.Printf("[error] invalid watchscreen sessionid: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := uuid.Parse(wsPk.ScreenId); err != nil {
|
||||||
|
fmt.Printf("[error] invalid watchscreen screenid: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ws.WatchScreen(wsPk.SessionId, wsPk.ScreenId)
|
||||||
|
fmt.Printf("[ws] watch screen clientid=%s %s/%s\n", ws.ClientId, wsPk.SessionId, wsPk.ScreenId)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("got ws bad message: %v\n", pk.GetType())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendCmdInput(pk *packet.InputPacketType) error {
|
||||||
|
err := pk.CK.Validate("input packet")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pk.RemoteId == "" {
|
||||||
|
return fmt.Errorf("input must set remoteid")
|
||||||
|
}
|
||||||
|
if len(pk.InputData64) == 0 && pk.SigNum == 0 {
|
||||||
|
return fmt.Errorf("empty input packet")
|
||||||
|
}
|
||||||
|
inputLen := packet.B64DecodedLen(pk.InputData64)
|
||||||
|
if inputLen > MaxInputDataSize {
|
||||||
|
return fmt.Errorf("input data size too large, len=%d (max=%d)", inputLen, MaxInputDataSize)
|
||||||
|
}
|
||||||
|
msh := remote.GetRemoteById(pk.RemoteId)
|
||||||
|
if msh == nil {
|
||||||
|
return fmt.Errorf("cannot connect to remote")
|
||||||
|
}
|
||||||
|
return msh.SendInput(pk)
|
||||||
|
}
|
||||||
+42
-4
@@ -2,19 +2,48 @@ package sstore
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AppendToCmdPtyBlob(ctx context.Context, sessionId string, cmdId string, data []byte) error {
|
const PosAppend = -1
|
||||||
|
|
||||||
|
// when calling with PosAppend, this is not multithread safe (since file could be modified).
|
||||||
|
// we need to know the real position of the write to send a proper pty update to the frontends
|
||||||
|
// in practice this is fine since we only use PosAppend in non-detached mode where
|
||||||
|
// we are reading/writing a stream in order with a single goroutine
|
||||||
|
func AppendToCmdPtyBlob(ctx context.Context, sessionId string, cmdId string, data []byte, pos int64) error {
|
||||||
ptyOutFileName, err := scbase.PtyOutFile(sessionId, cmdId)
|
ptyOutFileName, err := scbase.PtyOutFile(sessionId, cmdId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fd, err := os.OpenFile(ptyOutFileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
var fd *os.File
|
||||||
if err != nil {
|
var realPos int64
|
||||||
return err
|
if pos == PosAppend {
|
||||||
|
fd, err = os.OpenFile(ptyOutFileName, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
finfo, err := fd.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
realPos = finfo.Size()
|
||||||
|
} else {
|
||||||
|
fd, err = os.OpenFile(ptyOutFileName, os.O_WRONLY|os.O_CREATE, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
realPos, err = fd.Seek(pos, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if realPos != pos {
|
||||||
|
return fmt.Errorf("could not seek to pos:%d (realpos=%d)", pos, realPos)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
@@ -24,5 +53,14 @@ func AppendToCmdPtyBlob(ctx context.Context, sessionId string, cmdId string, dat
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
data64 := base64.StdEncoding.EncodeToString(data)
|
||||||
|
update := &PtyDataUpdate{
|
||||||
|
SessionId: sessionId,
|
||||||
|
CmdId: cmdId,
|
||||||
|
PtyPos: realPos,
|
||||||
|
PtyData64: data64,
|
||||||
|
PtyDataLen: int64(len(data)),
|
||||||
|
}
|
||||||
|
MainBus.SendUpdate(sessionId, update)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -58,14 +58,14 @@ func quickSetJson(ptr interface{}, m map[string]interface{}, name string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if str == "" {
|
if str == "" {
|
||||||
return
|
str = "{}"
|
||||||
}
|
}
|
||||||
json.Unmarshal([]byte(str), ptr)
|
json.Unmarshal([]byte(str), ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func quickJson(v interface{}) string {
|
func quickJson(v interface{}) string {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return ""
|
return "{}"
|
||||||
}
|
}
|
||||||
barr, _ := json.Marshal(v)
|
barr, _ := json.Marshal(v)
|
||||||
return string(barr)
|
return string(barr)
|
||||||
@@ -81,14 +81,14 @@ func quickScanJson(ptr interface{}, val interface{}) error {
|
|||||||
barrVal = []byte(strVal)
|
barrVal = []byte(strVal)
|
||||||
}
|
}
|
||||||
if len(barrVal) == 0 {
|
if len(barrVal) == 0 {
|
||||||
return nil
|
barrVal = []byte("{}")
|
||||||
}
|
}
|
||||||
return json.Unmarshal(barrVal, ptr)
|
return json.Unmarshal(barrVal, ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func quickValueJson(v interface{}) (driver.Value, error) {
|
func quickValueJson(v interface{}) (driver.Value, error) {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return "", nil
|
return "{}", nil
|
||||||
}
|
}
|
||||||
barr, err := json.Marshal(v)
|
barr, err := json.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ type ScreenWindowType struct {
|
|||||||
|
|
||||||
type HistoryItemType struct {
|
type HistoryItemType struct {
|
||||||
CmdStr string `json:"cmdstr"`
|
CmdStr string `json:"cmdstr"`
|
||||||
|
Remove bool `json:"remove"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoteState struct {
|
type RemoteState struct {
|
||||||
@@ -194,6 +195,7 @@ type LineType struct {
|
|||||||
LineType string `json:"linetype"`
|
LineType string `json:"linetype"`
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitempty"`
|
||||||
CmdId string `json:"cmdid,omitempty"`
|
CmdId string `json:"cmdid,omitempty"`
|
||||||
|
Remove bool `json:"remove,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSHOpts struct {
|
type SSHOpts struct {
|
||||||
@@ -239,6 +241,7 @@ type CmdType struct {
|
|||||||
DonePk *packet.CmdDonePacketType `json:"donepk"`
|
DonePk *packet.CmdDonePacketType `json:"donepk"`
|
||||||
UsedRows int64 `json:"usedrows"`
|
UsedRows int64 `json:"usedrows"`
|
||||||
RunOut []packet.PacketType `json:"runout"`
|
RunOut []packet.PacketType `json:"runout"`
|
||||||
|
Remove bool `json:"remove"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RemoteType) ToMap() map[string]interface{} {
|
func (r *RemoteType) ToMap() map[string]interface{} {
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package sstore
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
var MainBus *UpdateBus = MakeUpdateBus()
|
||||||
|
|
||||||
|
type UpdateCmd struct {
|
||||||
|
CmdId string
|
||||||
|
Status string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PtyDataUpdate struct {
|
||||||
|
SessionId string `json:"sessionid"`
|
||||||
|
CmdId string `json:"cmdid"`
|
||||||
|
PtyPos int64 `json:"ptypos"`
|
||||||
|
PtyData64 string `json:"ptydata64"`
|
||||||
|
PtyDataLen int64 `json:"ptydatalen"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WindowUpdate struct {
|
||||||
|
Window WindowType `json:"window"`
|
||||||
|
Remove bool `json:"remove,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionUpdate struct {
|
||||||
|
Session SessionType `json:"session"`
|
||||||
|
Remove bool `json:"remove,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CmdUpdate struct {
|
||||||
|
Cmd CmdType `json:"cmd"`
|
||||||
|
Remove bool `json:"remove,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScreenUpdate struct {
|
||||||
|
Screen CmdType `json:"screen"`
|
||||||
|
Remove bool `json:"remove,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateChannel struct {
|
||||||
|
SessionId string
|
||||||
|
ClientId string
|
||||||
|
Ch chan interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uch UpdateChannel) Match(sessionId string) bool {
|
||||||
|
if sessionId == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return sessionId == uch.SessionId
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateBus struct {
|
||||||
|
Lock *sync.Mutex
|
||||||
|
Channels map[string]UpdateChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeUpdateBus() *UpdateBus {
|
||||||
|
return &UpdateBus{
|
||||||
|
Lock: &sync.Mutex{},
|
||||||
|
Channels: make(map[string]UpdateChannel),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bus *UpdateBus) RegisterChannel(clientId string, sessionId string) chan interface{} {
|
||||||
|
bus.Lock.Lock()
|
||||||
|
defer bus.Lock.Unlock()
|
||||||
|
uch, found := bus.Channels[clientId]
|
||||||
|
if found {
|
||||||
|
close(uch.Ch)
|
||||||
|
uch.SessionId = sessionId
|
||||||
|
uch.Ch = make(chan interface{})
|
||||||
|
} else {
|
||||||
|
uch = UpdateChannel{
|
||||||
|
ClientId: clientId,
|
||||||
|
SessionId: sessionId,
|
||||||
|
Ch: make(chan interface{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bus.Channels[clientId] = uch
|
||||||
|
return uch.Ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bus *UpdateBus) UnregisterChannel(clientId string) {
|
||||||
|
bus.Lock.Lock()
|
||||||
|
defer bus.Lock.Unlock()
|
||||||
|
uch, found := bus.Channels[clientId]
|
||||||
|
if found {
|
||||||
|
close(uch.Ch)
|
||||||
|
delete(bus.Channels, clientId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bus *UpdateBus) SendUpdate(sessionId string, update interface{}) {
|
||||||
|
bus.Lock.Lock()
|
||||||
|
defer bus.Lock.Unlock()
|
||||||
|
for _, uch := range bus.Channels {
|
||||||
|
if uch.Match(sessionId) {
|
||||||
|
uch.Ch <- update
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package wsshell
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -65,6 +66,9 @@ func (ws *WSShell) WritePing() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ws *WSShell) WriteJson(val interface{}) error {
|
func (ws *WSShell) WriteJson(val interface{}) error {
|
||||||
|
if ws.IsClosed() {
|
||||||
|
return fmt.Errorf("cannot write packet, empty or closed wsshell")
|
||||||
|
}
|
||||||
barr, err := json.Marshal(val)
|
barr, err := json.Marshal(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user