mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
checkpoint
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user