From b2dc52e1665dddcc6142a6344f10d59d817f12ab Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 28 Nov 2022 00:13:00 -0800 Subject: [PATCH] checkpoint --- db/migrations/000001_init.up.sql | 20 +++- db/schema.sql | 21 +++- pkg/cmdrunner/cmdrunner.go | 188 +++++++++++++++-------------- pkg/cmdrunner/resolver.go | 21 ++-- pkg/cmdrunner/shparse.go | 2 +- pkg/remote/remote.go | 151 ++++++++++++++++++------ pkg/sstore/dbops.go | 196 ++++++++++++++++++++++++++++--- pkg/sstore/quick.go | 23 ++++ pkg/sstore/sstore.go | 124 +++++++++++++------ pkg/utilfn/utilfn.go | 9 ++ 10 files changed, 560 insertions(+), 195 deletions(-) diff --git a/db/migrations/000001_init.up.sql b/db/migrations/000001_init.up.sql index bc54d74d..7f05513f 100644 --- a/db/migrations/000001_init.up.sql +++ b/db/migrations/000001_init.up.sql @@ -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, diff --git a/db/schema.sql b/db/schema.sql index c0d09ed5..34d8bbcc 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -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 ( diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index 705ce378..124b52c3 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -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 +} diff --git a/pkg/cmdrunner/resolver.go b/pkg/cmdrunner/resolver.go index 2ab26915..34fff2f9 100644 --- a/pkg/cmdrunner/resolver.go +++ b/pkg/cmdrunner/resolver.go @@ -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 } diff --git a/pkg/cmdrunner/shparse.go b/pkg/cmdrunner/shparse.go index e6504e58..3aa9a08c 100644 --- a/pkg/cmdrunner/shparse.go +++ b/pkg/cmdrunner/shparse.go @@ -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 { diff --git a/pkg/remote/remote.go b/pkg/remote/remote.go index eb151f8a..e993b29f 100644 --- a/pkg/remote/remote.go +++ b/pkg/remote/remote.go @@ -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 + } +} diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 1544af63..f18dbc3a 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -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 +} diff --git a/pkg/sstore/quick.go b/pkg/sstore/quick.go index df1882b7..e92c81f5 100644 --- a/pkg/sstore/quick.go +++ b/pkg/sstore/quick.go @@ -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 { diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index 772d7d59..a994795e 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -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") diff --git a/pkg/utilfn/utilfn.go b/pkg/utilfn/utilfn.go index e5b245f2..d74da80b 100644 --- a/pkg/utilfn/utilfn.go +++ b/pkg/utilfn/utilfn.go @@ -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 +}