checkpoint

This commit is contained in:
sawka
2022-11-28 00:13:00 -08:00
parent d5ea9e0221
commit b2dc52e166
10 changed files with 560 additions and 195 deletions
+18 -2
View File
@@ -63,7 +63,24 @@ CREATE TABLE remote_instance (
windowid varchar(36) NOT NULL,
remoteownerid varchar(36) NOT NULL,
remoteid varchar(36) NOT NULL,
state json NOT NULL
festate json NOT NULL,
statebasehash varchar(36) NOT NULL,
statediffhasharr json NOT NULL
);
CREATE TABLE state_base (
basehash varchar(36) PRIMARY KEY,
ts bigint NOT NULL,
version varchar(200) NOT NULL,
data blob NOT NULL
);
CREATE TABLE state_diff (
diffhash varchar(36) PRIMARY KEY,
ts bigint NOT NULL,
basehash varchar(36) NOT NULL,
diffhasharr json NOT NULL,
data blob NOT NULL
);
CREATE TABLE line (
@@ -94,7 +111,6 @@ CREATE TABLE remote (
remotehost varchar(200) NOT NULL,
connectmode varchar(20) NOT NULL,
autoinstall boolean NOT NULL,
initpk json NOT NULL,
sshopts json NOT NULL,
remoteopts json NOT NULL,
lastconnectts bigint NOT NULL,
+18 -3
View File
@@ -60,7 +60,22 @@ CREATE TABLE remote_instance (
windowid varchar(36) NOT NULL,
remoteownerid varchar(36) NOT NULL,
remoteid varchar(36) NOT NULL,
state json NOT NULL
festate json NOT NULL,
statebasehash varchar(36) NOT NULL,
statediffhasharr json NOT NULL
);
CREATE TABLE state_base (
basehash varchar(36) PRIMARY KEY,
ts bigint NOT NULL,
version varchar(200) NOT NULL,
data blob NOT NULL
);
CREATE TABLE state_diff (
diffhash varchar(36) PRIMARY KEY,
ts bigint NOT NULL,
basehash varchar(36) NOT NULL,
diffhasharr json NOT NULL,
data blob NOT NULL
);
CREATE TABLE line (
sessionid varchar(36) NOT NULL,
@@ -75,6 +90,7 @@ CREATE TABLE line (
text text NOT NULL,
cmdid varchar(36) NOT NULL,
ephemeral boolean NOT NULL,
contentheight int NOT NULL,
PRIMARY KEY (sessionid, windowid, lineid)
);
CREATE TABLE remote (
@@ -88,7 +104,6 @@ CREATE TABLE remote (
remotehost varchar(200) NOT NULL,
connectmode varchar(20) NOT NULL,
autoinstall boolean NOT NULL,
initpk json NOT NULL,
sshopts json NOT NULL,
remoteopts json NOT NULL,
lastconnectts bigint NOT NULL,
@@ -110,7 +125,7 @@ CREATE TABLE cmd (
startpk json NOT NULL,
donepk json NOT NULL,
runout json NOT NULL,
usedrows int NOT NULL,
rtnstate bool NOT NULL,
PRIMARY KEY (sessionid, cmdid)
);
CREATE TABLE history (
+101 -87
View File
@@ -6,8 +6,6 @@ import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
@@ -43,6 +41,7 @@ const DefaultUserId = "sawka"
const MaxNameLen = 50
const MaxRemoteAliasLen = 50
const PasswordUnchangedSentinel = "--unchanged--"
const DefaultPTERM = "MxM"
var ColorNames = []string{"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"}
var RemoteColorNames = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"}
@@ -50,7 +49,27 @@ var RemoteSetArgs = []string{"alias", "connectmode", "key", "password", "autoins
var WindowCmds = []string{"run", "comment", "cd", "cr", "clear", "sw", "alias", "unalias", "function", "reset"}
var NoHistCmds = []string{"_compgen", "line", "history"}
var GlobalCmds = []string{"session", "screen", "remote", "killserver"}
var GlobalCmds = []string{"session", "screen", "remote", "killserver", "set"}
var SetVarNameMap map[string]string = map[string]string{
"tabcolor": "screen.tabcolor",
"pterm": "window.pterm",
"anchor": "sw.anchor",
"focus": "sw.focus",
"line": "sw.line",
}
var SetVarScopes = []SetVarScope{
SetVarScope{ScopeName: "global", VarNames: []string{}},
SetVarScope{ScopeName: "session", VarNames: []string{"name", "pos"}},
SetVarScope{ScopeName: "screen", VarNames: []string{"name", "tabcolor", "pos"}},
SetVarScope{ScopeName: "window", VarNames: []string{"pterm"}},
SetVarScope{ScopeName: "sw", VarNames: []string{"anchor", "focus", "line"}},
SetVarScope{ScopeName: "line", VarNames: []string{}},
// connection = remote, remote = remoteinstance
SetVarScope{ScopeName: "connection", VarNames: []string{"alias", "connectmode", "key", "password", "autoinstall", "color"}},
SetVarScope{ScopeName: "remote", VarNames: []string{}},
}
var hostNameRe = regexp.MustCompile("^[a-z][a-z0-9.-]*$")
var userHostRe = regexp.MustCompile("^(sudo@)?([a-z][a-z0-9-]*)@([a-z][a-z0-9.-]*)(?::([0-9]+))?$")
@@ -63,6 +82,11 @@ type contextType string
var historyContextKey = contextType("history")
type SetVarScope struct {
ScopeName string
VarNames []string
}
type historyContextType struct {
LineId string
CmdId string
@@ -81,7 +105,6 @@ func init() {
registerCmdFn("run", RunCommand)
registerCmdFn("eval", EvalCommand)
registerCmdFn("comment", CommentCommand)
// registerCmdFn("cd", CdCommand)
registerCmdFn("cr", CrCommand)
registerCmdFn("_compgen", CompGenCommand)
registerCmdFn("clear", ClearCommand)
@@ -122,6 +145,8 @@ func init() {
registerCmdFn("history", HistoryCommand)
registerCmdFn("killserver", KillServerCommand)
registerCmdFn("set", SetCommand)
}
func getValidCommands() []string {
@@ -185,6 +210,13 @@ func resolveBool(arg string, def bool) bool {
return true
}
func defaultStr(arg string, def string) string {
if arg == "" {
return def
}
return arg
}
func resolveFile(arg string) (string, error) {
if arg == "" {
return "", nil
@@ -231,20 +263,6 @@ func resolveNonNegInt(arg string, def int) (int, error) {
return ival, nil
}
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
}
func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window|R_RemoteConnected)
if err != nil {
@@ -252,12 +270,16 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.U
}
cmdStr := firstArg(pk)
isRtnStateCmd := IsReturnStateCommand(cmdStr)
// runPacket.State is set in remote.RunCommand()
runPacket := packet.MakeRunPacket()
runPacket.ReqId = uuid.New().String()
runPacket.CK = base.MakeCommandKey(ids.SessionId, scbase.GenSCUUID())
// runPacket.State is set in remote.RunCommand()
runPacket.UsePty = true
runPacket.TermOpts = getUITermOpts(pk.UIContext)
ptermVal := defaultStr(pk.Kwargs["pterm"], DefaultPTERM)
runPacket.TermOpts, err = GetUITermOpts(pk.UIContext.WinSize, ptermVal)
if err != nil {
return nil, fmt.Errorf("/run error, invalid 'pterm' value %q: %v", ptermVal, err)
}
runPacket.Command = strings.TrimSpace(cmdStr)
runPacket.ReturnState = resolveBool(pk.Kwargs["rtnstate"], isRtnStateCmd)
cmd, callback, err := remote.RunCommand(ctx, ids.SessionId, ids.WindowId, ids.Remote.RemotePtr, runPacket)
@@ -883,7 +905,7 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up
if newRemote == "" {
return nil, nil
}
remoteName, rptr, _, rstate, err := resolveRemote(ctx, newRemote, ids.SessionId, ids.WindowId)
remoteName, rptr, rstate, err := resolveRemote(ctx, newRemote, ids.SessionId, ids.WindowId)
if err != nil {
return nil, err
}
@@ -911,70 +933,6 @@ func CrCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.Up
return update, nil
}
func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_RemoteConnected)
if err != nil {
return nil, fmt.Errorf("/cd error: %w", err)
}
newDir := firstArg(pk)
if newDir == "" {
return sstore.ModelUpdate{
Info: &sstore.InfoMsgType{
InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.Remote.DisplayName, ids.Remote.RemoteState.Cwd),
},
}, nil
}
newDir, err = ids.Remote.RState.ExpandHomeDir(newDir)
if err != nil {
return nil, err
}
if !strings.HasPrefix(newDir, "/") {
if ids.Remote.RemoteState == nil {
return nil, fmt.Errorf("/cd error: cannot get current remote directory (can only cd with absolute path)")
}
newDir = path.Join(ids.Remote.RemoteState.Cwd, newDir)
newDir, err = filepath.Abs(newDir)
if err != nil {
return nil, fmt.Errorf("/cd error: error canonicalizing new directory: %w", err)
}
}
cdPacket := packet.MakeCdPacket()
cdPacket.ReqId = uuid.New().String()
cdPacket.Dir = newDir
resp, err := ids.Remote.MShell.PacketRpc(ctx, cdPacket)
if err != nil {
return nil, err
}
if err = resp.Err(); err != nil {
return nil, err
}
state := *ids.Remote.RemoteState
state.Cwd = newDir
remoteInst, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.Remote.RemotePtr, state)
if err != nil {
return nil, err
}
var cmdOutput bytes.Buffer
displayStateUpdateDiff(&cmdOutput, *ids.Remote.RemoteState, remoteInst.State)
cmd, err := makeStaticCmd(ctx, "cd", ids, pk.GetRawStr(), cmdOutput.Bytes())
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)
//update.Info = &sstore.InfoMsgType{
// InfoMsg: fmt.Sprintf("[%s] current directory = %s", ids.Remote.DisplayName, newDir),
// TimeoutMs: 2000,
//}
return update, nil
}
func makeStaticCmd(ctx context.Context, metaCmd string, ids resolvedIds, cmdStr string, cmdOutput []byte) (*sstore.CmdType, error) {
cmd := &sstore.CmdType{
SessionId: ids.SessionId,
@@ -1409,7 +1367,8 @@ func ResetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore
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)
feState := sstore.FeStateFromShellState(initPk.State)
remoteInst, err := sstore.UpdateRemoteState(ctx, ids.SessionId, ids.WindowId, ids.Remote.RemotePtr, *feState, initPk.State, nil)
if err != nil {
return nil, err
}
@@ -1618,6 +1577,33 @@ func LineShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst
return update, nil
}
func SetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
var setMap map[string]map[string]string
setMap = make(map[string]map[string]string)
_, err := resolveUiIds(ctx, pk, 0) // best effort
if err != nil {
return nil, err
}
for argIdx, rawArgVal := range pk.Args {
eqIdx := strings.Index(rawArgVal, "=")
if eqIdx == -1 {
return nil, fmt.Errorf("/set invalid argument %d, does not contain an '='", argIdx)
}
argName := rawArgVal[:eqIdx]
argVal := rawArgVal[eqIdx+1:]
ok, scopeName, varName := resolveSetArg(argName)
if !ok {
return nil, fmt.Errorf("/set invalid setvar %q", argName)
}
if _, ok := setMap[scopeName]; !ok {
setMap[scopeName] = make(map[string]string)
}
setMap[scopeName][varName] = argVal
}
fmt.Printf("setmap: %#v\n", setMap)
return nil, nil
}
func KillServerCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
go func() {
log.Printf("received /killserver, shutting down\n")
@@ -1693,7 +1679,7 @@ func displayStateUpdateDiff(buf *bytes.Buffer, oldState packet.ShellState, newSt
oldEnvMap := shexec.DeclMapFromState(&oldState)
for key, newVal := range newEnvMap {
oldVal, found := oldEnvMap[key]
if !found || !shexec.DeclsEqual(oldVal, newVal) {
if !found || !shexec.DeclsEqual(false, oldVal, newVal) {
var exportStr string
if newVal.IsExport() {
exportStr = "export "
@@ -1760,3 +1746,31 @@ func GetRtnStateDiff(ctx context.Context, sessionId string, cmdId string) ([]byt
displayStateUpdateDiff(&outputBytes, cmd.RemoteState, *cmd.DonePk.FinalState)
return outputBytes.Bytes(), nil
}
func isValidInScope(scopeName string, varName string) bool {
for _, varScope := range SetVarScopes {
if varScope.ScopeName == scopeName {
return utilfn.ContainsStr(varScope.VarNames, varName)
}
}
return false
}
// returns (is-valid, scope, name)
// TODO write a full resolver to allow for indexed arguments. e.g. session[1].screen[1].window.pterm="25x80"
func resolveSetArg(argName string) (bool, string, string) {
dotIdx := strings.Index(argName, ".")
if dotIdx == -1 {
argName = SetVarNameMap[argName]
dotIdx = strings.Index(argName, ".")
}
if argName == "" {
return false, "", ""
}
scopeName := argName[0:dotIdx]
varName := argName[dotIdx+1:]
if !isValidInScope(scopeName, varName) {
return false, "", ""
}
return true, scopeName, varName
}
+7 -14
View File
@@ -464,7 +464,7 @@ func resolveRemoteFromPtr(ctx context.Context, rptr *sstore.RemotePtrType, sessi
return nil, fmt.Errorf("cannot resolve remote state '%s': %w", displayName, err)
}
if state == nil {
state = rstate.DefaultState
state = msh.GetDefaultState()
}
rtn.RemoteState = state
}
@@ -472,26 +472,22 @@ func resolveRemoteFromPtr(ctx context.Context, rptr *sstore.RemotePtrType, sessi
}
// returns (remoteDisplayName, remoteptr, state, rstate, err)
func resolveRemote(ctx context.Context, fullRemoteRef string, sessionId string, windowId string) (string, *sstore.RemotePtrType, *packet.ShellState, *remote.RemoteRuntimeState, error) {
func resolveRemote(ctx context.Context, fullRemoteRef string, sessionId string, windowId string) (string, *sstore.RemotePtrType, *remote.RemoteRuntimeState, error) {
if fullRemoteRef == "" {
return "", nil, nil, nil, nil
return "", nil, nil, nil
}
userRef, remoteRef, remoteName, err := parseFullRemoteRef(fullRemoteRef)
if err != nil {
return "", nil, nil, nil, err
return "", nil, nil, err
}
if userRef != "" {
return "", nil, nil, nil, fmt.Errorf("invalid remote '%s', cannot resolve remote userid '%s'", fullRemoteRef, userRef)
return "", nil, nil, fmt.Errorf("invalid remote '%s', cannot resolve remote userid '%s'", fullRemoteRef, userRef)
}
rstate := remote.ResolveRemoteRef(remoteRef)
if rstate == nil {
return "", nil, nil, nil, fmt.Errorf("cannot resolve remote '%s': not found", fullRemoteRef)
return "", nil, nil, fmt.Errorf("cannot resolve remote '%s': not found", fullRemoteRef)
}
rptr := sstore.RemotePtrType{RemoteId: rstate.RemoteId, Name: remoteName}
state, err := sstore.GetRemoteState(ctx, sessionId, windowId, rptr)
if err != nil {
return "", nil, nil, nil, fmt.Errorf("cannot resolve remote state '%s': %w", fullRemoteRef, err)
}
rname := rstate.RemoteCanonicalName
if rstate.RemoteAlias != "" {
rname = rstate.RemoteAlias
@@ -499,8 +495,5 @@ func resolveRemote(ctx context.Context, fullRemoteRef string, sessionId string,
if rptr.Name != "" {
rname = fmt.Sprintf("%s:%s", rname, rptr.Name)
}
if state == nil {
return rname, &rptr, rstate.DefaultState, rstate, nil
}
return rname, &rptr, state, rstate, nil
return rname, &rptr, rstate, nil
}
+1 -1
View File
@@ -110,7 +110,7 @@ func parseMetaCmd(origCommandStr string) (string, string, string) {
}
func onlyPositionalArgs(metaCmd string, metaSubCmd string) bool {
return (metaCmd == "setenv" || metaCmd == "unset") && metaSubCmd == ""
return (metaCmd == "setenv" || metaCmd == "unset" || metaCmd == "set") && metaSubCmd == ""
}
func onlyRawArgs(metaCmd string, metaSubCmd string) bool {
+116 -35
View File
@@ -90,7 +90,8 @@ type MShellProc struct {
ControllingPty *os.File
PtyBuffer *circbuf.Buffer
MakeClientCancelFn context.CancelFunc
DefaultState *packet.ShellState
StateMap map[string]*packet.ShellState // sha1->state
CurrentState string // sha1
// install
InstallStatus string
@@ -111,26 +112,26 @@ type RunCmdType struct {
}
type RemoteRuntimeState struct {
RemoteType string `json:"remotetype"`
RemoteId string `json:"remoteid"`
PhysicalId string `json:"physicalremoteid"`
RemoteAlias string `json:"remotealias,omitempty"`
RemoteCanonicalName string `json:"remotecanonicalname"`
RemoteVars map[string]string `json:"remotevars"`
Status string `json:"status"`
ErrorStr string `json:"errorstr,omitempty"`
InstallStatus string `json:"installstatus"`
InstallErrorStr string `json:"installerrorstr,omitempty"`
NeedsMShellUpgrade bool `json:"needsmshellupgrade,omitempty"`
DefaultState *packet.ShellState `json:"defaultstate"`
ConnectMode string `json:"connectmode"`
AutoInstall bool `json:"autoinstall"`
Archived bool `json:"archived,omitempty"`
RemoteIdx int64 `json:"remoteidx"`
UName string `json:"uname"`
MShellVersion string `json:"mshellversion"`
WaitingForPassword bool `json:"waitingforpassword,omitempty"`
Local bool `json:"local,omitempty"`
RemoteType string `json:"remotetype"`
RemoteId string `json:"remoteid"`
PhysicalId string `json:"physicalremoteid"`
RemoteAlias string `json:"remotealias,omitempty"`
RemoteCanonicalName string `json:"remotecanonicalname"`
RemoteVars map[string]string `json:"remotevars"`
DefaultFeState *sstore.FeStateType `json:"defaultfestate"`
Status string `json:"status"`
ErrorStr string `json:"errorstr,omitempty"`
InstallStatus string `json:"installstatus"`
InstallErrorStr string `json:"installerrorstr,omitempty"`
NeedsMShellUpgrade bool `json:"needsmshellupgrade,omitempty"`
ConnectMode string `json:"connectmode"`
AutoInstall bool `json:"autoinstall"`
Archived bool `json:"archived,omitempty"`
RemoteIdx int64 `json:"remoteidx"`
UName string `json:"uname"`
MShellVersion string `json:"mshellversion"`
WaitingForPassword bool `json:"waitingforpassword,omitempty"`
Local bool `json:"local,omitempty"`
}
func (state RemoteRuntimeState) IsConnected() bool {
@@ -146,7 +147,13 @@ func (msh *MShellProc) GetStatus() string {
func (msh *MShellProc) GetDefaultState() *packet.ShellState {
msh.Lock.Lock()
defer msh.Lock.Unlock()
return msh.DefaultState
return msh.StateMap[msh.CurrentState]
}
func (msh *MShellProc) GetStateByHash(hval string) *packet.ShellState {
msh.Lock.Lock()
defer msh.Lock.Unlock()
return msh.StateMap[hval]
}
func (msh *MShellProc) GetRemoteId() string {
@@ -484,7 +491,6 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState {
vars["color"] = msh.Remote.RemoteOpts.Color
}
if msh.ServerProc != nil && msh.ServerProc.InitPk != nil {
state.DefaultState = msh.DefaultState
state.MShellVersion = msh.ServerProc.InitPk.Version
vars["home"] = msh.ServerProc.InitPk.HomeDir
vars["remoteuser"] = msh.ServerProc.InitPk.User
@@ -494,6 +500,11 @@ func (msh *MShellProc) GetRemoteRuntimeState() RemoteRuntimeState {
vars["besthost"] = vars["remotehost"]
vars["bestshorthost"] = vars["remoteshorthost"]
}
curState := msh.StateMap[msh.CurrentState]
if curState != nil {
state.DefaultFeState = sstore.FeStateFromShellState(curState)
vars["cwd"] = curState.Cwd
}
if msh.Remote.Local && msh.Remote.RemoteSudo {
vars["bestuser"] = "sudo"
} else if msh.Remote.RemoteSudo {
@@ -557,6 +568,7 @@ func MakeMShell(r *sstore.RemoteType) *MShellProc {
InstallStatus: StatusDisconnected,
RunningCmds: make(map[base.CommandKey]RunCmdType),
PendingStateCmds: make(map[string]base.CommandKey),
StateMap: make(map[string]*packet.ShellState),
}
rtn.WriteToPtyBuffer("console for remote [%s]\n", r.GetName())
return rtn
@@ -913,9 +925,10 @@ func (msh *MShellProc) ReInit(ctx context.Context) (*packet.InitPacketType, erro
if initPk.State == nil {
return nil, fmt.Errorf("invalid reinit response initpk does not contain remote state")
}
hval := initPk.State.GetHashVal(false)
msh.WithLock(func() {
msh.Remote.InitPk = initPk
msh.DefaultState = initPk.State
msh.CurrentState = hval
msh.StateMap[hval] = initPk.State
})
return initPk, nil
}
@@ -999,11 +1012,10 @@ func (msh *MShellProc) Launch() {
cproc, initPk, err := shexec.MakeClientProc(makeClientCtx, ecmd)
// TODO check if initPk.State is not nil
var mshellVersion string
var stateBaseHash string
msh.WithLock(func() {
msh.MakeClientCancelFn = nil
if initPk != nil {
msh.DefaultState = initPk.State
msh.Remote.InitPk = initPk
msh.UName = initPk.UName
mshellVersion = initPk.Version
if semver.Compare(mshellVersion, MShellVersion) < 0 {
@@ -1011,6 +1023,15 @@ func (msh *MShellProc) Launch() {
msh.NeedsMShellUpgrade = true
}
}
if initPk != nil && initPk.State != nil {
hval := initPk.State.GetHashVal(false)
msh.CurrentState = hval
msh.StateMap[hval] = initPk.State
sstore.StoreStateBase(context.Background(), initPk.State)
stateBaseHash = hval
} else {
msh.CurrentState = ""
}
// no notify here, because we'll call notify in either case below
})
if err == context.Canceled {
@@ -1029,7 +1050,7 @@ func (msh *MShellProc) Launch() {
msh.WriteToPtyBuffer("*error connecting to remote: %v\n", err)
return
}
msh.WriteToPtyBuffer("connected\n")
msh.WriteToPtyBuffer("connected state:%s\n", stateBaseHash)
msh.WithLock(func() {
msh.ServerProc = cproc
msh.Status = StatusConnected
@@ -1389,10 +1410,33 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) {
// fall-through (nothing to do)
}
update.ScreenWindows = sws
if donePk.FinalState != nil {
rct := msh.GetRunningCmd(donePk.CK)
if rct != nil {
remoteInst, err := sstore.UpdateRemoteState(context.Background(), rct.SessionId, rct.WindowId, rct.RemotePtr, *donePk.FinalState)
rct := msh.GetRunningCmd(donePk.CK)
if donePk.FinalState != nil && rct != nil {
fmt.Printf("** FINALSTATE!\n")
feState := sstore.FeStateFromShellState(donePk.FinalState)
remoteInst, err := sstore.UpdateRemoteState(context.Background(), rct.SessionId, rct.WindowId, rct.RemotePtr, *feState, donePk.FinalState, nil)
if err != nil {
msh.WriteToPtyBuffer("*error trying to update remotestate: %v\n", err)
// fall-through (nothing to do)
}
if remoteInst != nil {
update.Sessions = sstore.MakeSessionsUpdateForRemote(rct.SessionId, remoteInst)
}
} else if donePk.FinalStateDiff != nil && rct != nil {
fmt.Printf("** STATEDIFF! %#v\n", donePk.FinalStateDiff)
fullState, err := msh.getFullState(donePk.FinalStateDiff)
if err != nil {
fmt.Printf("**ERR: %v\n", err)
}
donePk.FinalStateDiff.Dump()
shexec.DumpVarMapFromState(fullState)
feState, err := msh.getFeStateFromDiff(donePk.FinalStateDiff)
if err != nil {
msh.WriteToPtyBuffer("*error trying to update remotestate: %v\n", err)
// fall-through (nothing to do)
} else {
fmt.Printf("** festate = %#v\n", feState)
remoteInst, err := sstore.UpdateRemoteState(context.Background(), rct.SessionId, rct.WindowId, rct.RemotePtr, *feState, nil, donePk.FinalStateDiff)
if err != nil {
msh.WriteToPtyBuffer("*error trying to update remotestate: %v\n", err)
// fall-through (nothing to do)
@@ -1402,9 +1446,6 @@ func (msh *MShellProc) handleCmdDonePacket(donePk *packet.CmdDonePacketType) {
}
}
}
if donePk.FinalStateDiff != nil {
fmt.Printf("** final state diff! %v\n", donePk.FinalStateDiff)
}
sstore.MainBus.SendUpdate(donePk.CK.GetSessionId(), update)
return
}
@@ -1692,3 +1733,43 @@ func evalPromptEsc(escCode string, vars map[string]string, state *packet.ShellSt
// we don't support date/time escapes (d, t, T, @), version escapes (v, V), cmd number (#, !), terminal device (l), jobs (j)
return "(" + escCode + ")"
}
func (msh *MShellProc) getFullState(stateDiff *packet.ShellStateDiff) (*packet.ShellState, error) {
baseState := msh.GetStateByHash(stateDiff.BaseHash)
if baseState != nil && len(stateDiff.DiffHashArr) == 0 {
newState, err := shexec.ApplyShellStateDiff(*baseState, *stateDiff)
if err != nil {
return nil, err
}
return &newState, nil
} else {
fullState, err := sstore.GetFullState(context.Background(), stateDiff.BaseHash, stateDiff.DiffHashArr)
if err != nil {
return nil, err
}
newState, err := shexec.ApplyShellStateDiff(*fullState, *stateDiff)
return &newState, nil
}
}
// internal func, first tries the StateMap, otherwise will fallback on sstore.GetFullState
func (msh *MShellProc) getFeStateFromDiff(stateDiff *packet.ShellStateDiff) (*sstore.FeStateType, error) {
baseState := msh.GetStateByHash(stateDiff.BaseHash)
if baseState != nil && len(stateDiff.DiffHashArr) == 0 {
newState, err := shexec.ApplyShellStateDiff(*baseState, *stateDiff)
if err != nil {
return nil, err
}
return sstore.FeStateFromShellState(&newState), nil
} else {
fullState, err := sstore.GetFullState(context.Background(), stateDiff.BaseHash, stateDiff.DiffHashArr)
if err != nil {
return nil, err
}
newState, err := shexec.ApplyShellStateDiff(*fullState, *stateDiff)
if err != nil {
return nil, err
}
return sstore.FeStateFromShellState(&newState), nil
}
}
+178 -18
View File
@@ -5,10 +5,12 @@ import (
"fmt"
"strconv"
"strings"
"time"
"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/scbase"
)
@@ -141,8 +143,8 @@ func UpsertRemote(ctx context.Context, r *RemoteType) error {
maxRemoteIdx := tx.GetInt(query)
r.RemoteIdx = int64(maxRemoteIdx + 1)
query = `INSERT INTO remote
( remoteid, physicalid, remotetype, remotealias, remotecanonicalname, remotesudo, remoteuser, remotehost, connectmode, autoinstall, initpk, sshopts, remoteopts, lastconnectts, archived, remoteidx, local) VALUES
(:remoteid,:physicalid,:remotetype,:remotealias,:remotecanonicalname,:remotesudo,:remoteuser,:remotehost,:connectmode,:autoinstall,:initpk,:sshopts,:remoteopts,:lastconnectts,:archived,:remoteidx,:local)`
( remoteid, physicalid, remotetype, remotealias, remotecanonicalname, remotesudo, remoteuser, remotehost, connectmode, autoinstall, sshopts, remoteopts, lastconnectts, archived, remoteidx, local) VALUES
(:remoteid,:physicalid,:remotetype,:remotealias,:remotecanonicalname,:remotesudo,:remoteuser,:remotehost,:connectmode,:autoinstall,:sshopts,:remoteopts,:lastconnectts,:archived,:remoteidx,:local)`
tx.NamedExecWrap(query, r.ToMap())
return nil
})
@@ -822,18 +824,25 @@ func DeleteScreen(ctx context.Context, sessionId string, screenId string) (Updat
}
func GetRemoteState(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType) (*packet.ShellState, error) {
var remoteState *packet.ShellState
var state *packet.ShellState
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND remoteownerid = ? AND remoteid = ? AND name = ?`
m := tx.GetMap(query, sessionId, windowId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name)
ri := RIFromMap(m)
if ri != nil {
remoteState = &ri.State
ri, err := GetRemoteInstance(tx.Context(), sessionId, windowId, remotePtr)
if err != nil {
return err
}
if ri == nil {
return nil
}
state, err = GetFullState(tx.Context(), ri.StateBaseHash, ri.StateDiffHashArr)
if err != nil {
return err
}
return nil
})
return remoteState, txErr
if txErr != nil {
return nil, txErr
}
return state, nil
}
func validateSessionWindow(tx *TxWrap, sessionId string, windowId string) error {
@@ -852,7 +861,50 @@ func validateSessionWindow(tx *TxWrap, sessionId string, windowId string) error
}
}
func UpdateRemoteState(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType, state packet.ShellState) (*RemoteInstance, error) {
func GetRemoteInstance(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType) (*RemoteInstance, error) {
if remotePtr.IsSessionScope() {
windowId = ""
}
var ri *RemoteInstance
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND remoteownerid = ? AND remoteid = ? AND name = ?`
m := tx.GetMap(query, sessionId, windowId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name)
ri = RIFromMap(m)
return nil
})
if txErr != nil {
return nil, txErr
}
return ri, nil
}
// internal function for UpdateRemoteState
func updateRIWithState(ctx context.Context, ri *RemoteInstance, stateBase *packet.ShellState, stateDiff *packet.ShellStateDiff) error {
if stateBase != nil {
ri.StateBaseHash = stateBase.GetHashVal(false)
err := StoreStateBase(ctx, stateBase)
if err != nil {
return err
}
} else if stateDiff != nil {
ri.StateBaseHash = stateDiff.BaseHash
ri.StateDiffHashArr = append(stateDiff.DiffHashArr, stateDiff.GetHashVal(false))
err := StoreStateDiff(ctx, stateDiff)
if err != nil {
return err
}
}
return nil
}
// TODO - statediff
func UpdateRemoteState(ctx context.Context, sessionId string, windowId string, remotePtr RemotePtrType, feState FeStateType, stateBase *packet.ShellState, stateDiff *packet.ShellStateDiff) (*RemoteInstance, error) {
if stateBase == nil && stateDiff == nil {
return nil, fmt.Errorf("UpdateRemoteState, must set state or diff")
}
if stateBase != nil && stateDiff != nil {
return nil, fmt.Errorf("UpdateRemoteState, cannot set state and diff")
}
if remotePtr.IsSessionScope() {
windowId = ""
}
@@ -860,7 +912,7 @@ func UpdateRemoteState(ctx context.Context, sessionId string, windowId string, r
txErr := WithTx(ctx, func(tx *TxWrap) error {
err := validateSessionWindow(tx, sessionId, windowId)
if err != nil {
return fmt.Errorf("cannot update remote instance cwd: %w", err)
return fmt.Errorf("cannot update remote instance state: %w", err)
}
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND remoteownerid = ? AND remoteid = ? AND name = ?`
m := tx.GetMap(query, sessionId, windowId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name)
@@ -873,17 +925,26 @@ func UpdateRemoteState(ctx context.Context, sessionId string, windowId string, r
WindowId: windowId,
RemoteOwnerId: remotePtr.OwnerId,
RemoteId: remotePtr.RemoteId,
State: state,
FeState: feState,
}
query = `INSERT INTO remote_instance ( riid, name, sessionid, windowid, remoteownerid, remoteid, state)
VALUES (:riid,:name,:sessionid,:windowid,:remoteownerid,:remoteid,:state)`
err = updateRIWithState(tx.Context(), ri, stateBase, stateDiff)
if err != nil {
return err
}
query = `INSERT INTO remote_instance ( riid, name, sessionid, windowid, remoteownerid, remoteid, festate, statebasehash, statediffhasharr)
VALUES (:riid,:name,:sessionid,:windowid,:remoteownerid,:remoteid,:festate,:statebasehash,:statediffhasharr)`
tx.NamedExecWrap(query, ri.ToMap())
return nil
} else {
query = `UPDATE remote_instance SET festate = ? WHERE riid = ?`
ri.FeState = feState
err = updateRIWithState(tx.Context(), ri, stateBase, stateDiff)
if err != nil {
return err
}
tx.ExecWrap(query, quickJson(ri.FeState), ri.RIId)
return nil
}
query = `UPDATE remote_instance SET state = ? WHERE riid = ?`
ri.State = state
tx.ExecWrap(query, quickJson(ri.State), ri.RIId)
return nil
})
return ri, txErr
}
@@ -1255,3 +1316,102 @@ func UpdateSWsWithCmdFg(ctx context.Context, sessionId string, cmdId string) ([]
}
return rtn, nil
}
func StoreStateBase(ctx context.Context, state *packet.ShellState) error {
stateBase := &StateBase{
Version: state.Version,
Ts: time.Now().UnixMilli(),
}
stateBase.BaseHash, stateBase.Data = state.EncodeAndHash()
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT basehash FROM state_base WHERE basehash = ?`
if tx.Exists(query, stateBase.BaseHash) {
return nil
}
query = `INSERT INTO state_base (basehash, ts, version, data) VALUES (:basehash,:ts,:version,:data)`
tx.NamedExecWrap(query, stateBase)
return nil
})
if txErr != nil {
return txErr
}
return nil
}
func StoreStateDiff(ctx context.Context, diff *packet.ShellStateDiff) error {
stateDiff := &StateDiff{
BaseHash: diff.BaseHash,
Ts: time.Now().UnixMilli(),
DiffHashArr: diff.DiffHashArr,
}
stateDiff.DiffHash, stateDiff.Data = diff.EncodeAndHash()
txErr := WithTx(ctx, func(tx *TxWrap) error {
query := `SELECT basehash FROM state_base WHERE basehash = ?`
if stateDiff.BaseHash == "" || !tx.Exists(query, stateDiff.BaseHash) {
return fmt.Errorf("cannot store statediff, basehash:%s does not exist", stateDiff.BaseHash)
}
query = `SELECT diffhash FROM state_diff WHERE diffhash = ?`
for idx, diffHash := range stateDiff.DiffHashArr {
if !tx.Exists(query, diffHash) {
return fmt.Errorf("cannot store statediff, diffhash[%d]:%s does not exist", idx, diffHash)
}
}
if tx.Exists(query, stateDiff.DiffHash) {
return nil
}
query = `INSERT INTO state_diff (diffhash, ts, basehash, diffhasharr, data) VALUES (:diffhash,:ts,:basehash,:diffhasharr,:data)`
tx.NamedExecWrap(query, stateDiff.ToMap())
return nil
})
if txErr != nil {
return txErr
}
return nil
}
// returns error when not found
func GetFullState(ctx context.Context, baseHash string, diffHashArr []string) (*packet.ShellState, error) {
var state *packet.ShellState
if baseHash == "" {
return nil, fmt.Errorf("invalid empty basehash")
}
txErr := WithTx(ctx, func(tx *TxWrap) error {
var stateBase StateBase
query := `SELECT * FROM state_base WHERE basehash = ?`
found := tx.GetWrap(&stateBase, query, baseHash)
if !found {
return fmt.Errorf("ShellState %s not found", baseHash)
}
state = &packet.ShellState{}
err := state.DecodeShellState(stateBase.Data)
if err != nil {
return err
}
for idx, diffHash := range diffHashArr {
query = `SELECT * FROM state_diff WHERE diffhash = ?`
m := tx.GetMap(query, diffHash)
stateDiff := StateDiffFromMap(m)
if stateDiff == nil {
return fmt.Errorf("ShellStateDiff %s not found", diffHash)
}
var ssDiff packet.ShellStateDiff
err = ssDiff.DecodeShellStateDiff(stateDiff.Data)
if err != nil {
return err
}
newState, err := shexec.ApplyShellStateDiff(*state, ssDiff)
if err != nil {
return fmt.Errorf("GetFullState, diff[%d]:%s: %v", idx, diffHash, err)
}
state = &newState
}
return nil
})
if txErr != nil {
return nil, txErr
}
if state == nil {
return nil, fmt.Errorf("ShellState not found")
}
return state, nil
}
+23
View File
@@ -80,6 +80,21 @@ func quickSetJson(ptr interface{}, m map[string]interface{}, name string) {
json.Unmarshal([]byte(str), ptr)
}
func quickSetJsonArr(ptr interface{}, m map[string]interface{}, name string) {
v, ok := m[name]
if !ok {
return
}
str, ok := v.(string)
if !ok {
return
}
if str == "" {
str = "[]"
}
json.Unmarshal([]byte(str), ptr)
}
func quickJson(v interface{}) string {
if v == nil {
return "{}"
@@ -88,6 +103,14 @@ func quickJson(v interface{}) string {
return string(barr)
}
func quickJsonArr(v interface{}) string {
if v == nil {
return "[]"
}
barr, _ := json.Marshal(v)
return string(barr)
}
func quickScanJson(ptr interface{}, val interface{}) error {
barrVal, ok := val.([]byte)
if !ok {
+89 -35
View File
@@ -170,6 +170,7 @@ type SessionStatsType struct {
}
type WindowOptsType struct {
PTerm string `json:"pterm,omitempty"`
}
func (opts *WindowOptsType) Scan(val interface{}) error {
@@ -458,30 +459,70 @@ func (opts TermOpts) Value() (driver.Value, error) {
}
type RemoteInstance struct {
RIId string `json:"riid"`
Name string `json:"name"`
SessionId string `json:"sessionid"`
WindowId string `json:"windowid"`
RemoteOwnerId string `json:"remoteownerid"`
RemoteId string `json:"remoteid"`
State packet.ShellState `json:"state"`
RIId string `json:"riid"`
Name string `json:"name"`
SessionId string `json:"sessionid"`
WindowId string `json:"windowid"`
RemoteOwnerId string `json:"remoteownerid"`
RemoteId string `json:"remoteid"`
FeState FeStateType `json:"festate"`
StateBaseHash string `json:"-"`
StateDiffHashArr []string `json:"-"`
// only for updates
Remove bool `json:"remove,omitempty"`
}
func (ri *RemoteInstance) ToMap() map[string]interface{} {
type StateBase struct {
BaseHash string
Version string
Ts int64
Data []byte
}
type StateDiff struct {
DiffHash string
Ts int64
BaseHash string
DiffHashArr []string
Data []byte
}
func StateDiffFromMap(m map[string]interface{}) *StateDiff {
if len(m) == 0 {
return nil
}
var sd StateDiff
quickSetStr(&sd.DiffHash, m, "diffhash")
quickSetInt64(&sd.Ts, m, "ts")
quickSetStr(&sd.BaseHash, m, "basehash")
quickSetJsonArr(&sd.DiffHashArr, m, "diffhasharr")
quickSetBytes(&sd.Data, m, "data")
return &sd
}
func (sd *StateDiff) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
rtn["riid"] = ri.RIId
rtn["name"] = ri.Name
rtn["sessionid"] = ri.SessionId
rtn["windowid"] = ri.WindowId
rtn["remoteownerid"] = ri.RemoteOwnerId
rtn["remoteid"] = ri.RemoteId
rtn["state"] = quickJson(ri.State)
rtn["diffhash"] = sd.DiffHash
rtn["ts"] = sd.Ts
rtn["basehash"] = sd.BaseHash
rtn["diffhasharr"] = quickJsonArr(sd.DiffHashArr)
rtn["data"] = sd.Data
return rtn
}
type FeStateType struct {
Cwd string `json:"cwd"`
// maybe later we can add some vars
}
func FeStateFromShellState(state *packet.ShellState) *FeStateType {
if state == nil {
return nil
}
return &FeStateType{Cwd: state.Cwd}
}
func RIFromMap(m map[string]interface{}) *RemoteInstance {
if len(m) == 0 {
return nil
@@ -493,10 +534,26 @@ func RIFromMap(m map[string]interface{}) *RemoteInstance {
quickSetStr(&ri.WindowId, m, "windowid")
quickSetStr(&ri.RemoteOwnerId, m, "remoteownerid")
quickSetStr(&ri.RemoteId, m, "remoteid")
quickSetJson(&ri.State, m, "state")
quickSetJson(&ri.FeState, m, "festate")
quickSetStr(&ri.StateBaseHash, m, "statebasehash")
quickSetJsonArr(&ri.StateDiffHashArr, m, "statediffhasharr")
return &ri
}
func (ri *RemoteInstance) ToMap() map[string]interface{} {
rtn := make(map[string]interface{})
rtn["riid"] = ri.RIId
rtn["name"] = ri.Name
rtn["sessionid"] = ri.SessionId
rtn["windowid"] = ri.WindowId
rtn["remoteownerid"] = ri.RemoteOwnerId
rtn["remoteid"] = ri.RemoteId
rtn["festate"] = quickJson(ri.FeState)
rtn["statebasehash"] = ri.StateBaseHash
rtn["statediffhasharr"] = quickJsonArr(ri.StateDiffHashArr)
return rtn
}
type LineType struct {
SessionId string `json:"sessionid"`
WindowId string `json:"windowid"`
@@ -543,23 +600,22 @@ func (opts RemoteOptsType) Value() (driver.Value, error) {
}
type RemoteType struct {
RemoteId string `json:"remoteid"`
PhysicalId string `json:"physicalid"`
RemoteType string `json:"remotetype"`
RemoteAlias string `json:"remotealias"`
RemoteCanonicalName string `json:"remotecanonicalname"`
RemoteSudo bool `json:"remotesudo"`
RemoteUser string `json:"remoteuser"`
RemoteHost string `json:"remotehost"`
ConnectMode string `json:"connectmode"`
AutoInstall bool `json:"autoinstall"`
InitPk *packet.InitPacketType `json:"inipk"`
SSHOpts *SSHOpts `json:"sshopts"`
RemoteOpts *RemoteOptsType `json:"remoteopts"`
LastConnectTs int64 `json:"lastconnectts"`
Archived bool `json:"archived"`
RemoteIdx int64 `json:"remoteidx"`
Local bool `json:"local"`
RemoteId string `json:"remoteid"`
PhysicalId string `json:"physicalid"`
RemoteType string `json:"remotetype"`
RemoteAlias string `json:"remotealias"`
RemoteCanonicalName string `json:"remotecanonicalname"`
RemoteSudo bool `json:"remotesudo"`
RemoteUser string `json:"remoteuser"`
RemoteHost string `json:"remotehost"`
ConnectMode string `json:"connectmode"`
AutoInstall bool `json:"autoinstall"`
SSHOpts *SSHOpts `json:"sshopts"`
RemoteOpts *RemoteOptsType `json:"remoteopts"`
LastConnectTs int64 `json:"lastconnectts"`
Archived bool `json:"archived"`
RemoteIdx int64 `json:"remoteidx"`
Local bool `json:"local"`
}
func (r *RemoteType) GetName() string {
@@ -597,7 +653,6 @@ func (r *RemoteType) ToMap() map[string]interface{} {
rtn["remotehost"] = r.RemoteHost
rtn["connectmode"] = r.ConnectMode
rtn["autoinstall"] = r.AutoInstall
rtn["initpk"] = quickJson(r.InitPk)
rtn["sshopts"] = quickJson(r.SSHOpts)
rtn["remoteopts"] = quickJson(r.RemoteOpts)
rtn["lastconnectts"] = r.LastConnectTs
@@ -622,7 +677,6 @@ func RemoteFromMap(m map[string]interface{}) *RemoteType {
quickSetStr(&r.RemoteHost, m, "remotehost")
quickSetStr(&r.ConnectMode, m, "connectmode")
quickSetBool(&r.AutoInstall, m, "autoinstall")
quickSetJson(&r.InitPk, m, "initpk")
quickSetJson(&r.SSHOpts, m, "sshopts")
quickSetJson(&r.RemoteOpts, m, "remoteopts")
quickSetInt64(&r.LastConnectTs, m, "lastconnectts")
+9
View File
@@ -1,6 +1,8 @@
package utilfn
import (
"crypto/sha1"
"encoding/base64"
"regexp"
"strings"
"unicode/utf8"
@@ -174,3 +176,10 @@ func (sp StrWithPos) Prepend(str string) StrWithPos {
func (sp StrWithPos) Append(str string) StrWithPos {
return StrWithPos{Str: sp.Str + str, Pos: sp.Pos}
}
// returns base64 hash of data
func Sha1Hash(data []byte) string {
hvalRaw := sha1.Sum(data)
hval := base64.StdEncoding.EncodeToString(hvalRaw[:])
return hval
}