mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
checkpoint got stdout/stderr data packets working with new remote handler
This commit is contained in:
+101
-15
@@ -11,6 +11,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -21,8 +22,10 @@ import (
|
|||||||
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
||||||
)
|
)
|
||||||
|
|
||||||
// in single run mode, we don't want the runner to die from signals
|
const MShellVersion = "0.1.0"
|
||||||
// since we want the single runner to persist even if session / main runner
|
|
||||||
|
// in single run mode, we don't want mshell to die from signals
|
||||||
|
// since we want the single mshell to persist even if session / main mshell
|
||||||
// is terminated.
|
// is terminated.
|
||||||
func setupSingleSignals(cmd *shexec.ShExecType) {
|
func setupSingleSignals(cmd *shexec.ShExecType) {
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
@@ -46,7 +49,7 @@ func doSingle(cmdId string) {
|
|||||||
runPacket, _ = pk.(*packet.RunPacketType)
|
runPacket, _ = pk.(*packet.RunPacketType)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
sender.SendErrorPacket(fmt.Sprintf("invalid packet '%s' sent to runner", pk.GetType()))
|
sender.SendErrorPacket(fmt.Sprintf("invalid packet '%s' sent to mshell", pk.GetType()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if runPacket == nil {
|
if runPacket == nil {
|
||||||
@@ -66,13 +69,9 @@ func doSingle(cmdId string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
setupSingleSignals(cmd)
|
setupSingleSignals(cmd)
|
||||||
startPacket := packet.MakeCmdStartPacket()
|
startPacket := cmd.MakeCmdStartPacket()
|
||||||
startPacket.Ts = time.Now().UnixMilli()
|
|
||||||
startPacket.CmdId = runPacket.CmdId
|
|
||||||
startPacket.Pid = cmd.Cmd.Process.Pid
|
|
||||||
startPacket.RunnerPid = os.Getpid()
|
|
||||||
sender.SendPacket(startPacket)
|
sender.SendPacket(startPacket)
|
||||||
donePacket := cmd.WaitForCommand(runPacket.CmdId)
|
donePacket := cmd.WaitForCommand()
|
||||||
sender.SendPacket(donePacket)
|
sender.SendPacket(donePacket)
|
||||||
sender.CloseSendCh()
|
sender.CloseSendCh()
|
||||||
sender.WaitForDone()
|
sender.WaitForDone()
|
||||||
@@ -94,7 +93,7 @@ func doMainRun(pk *packet.RunPacketType, sender *packet.PacketSender) {
|
|||||||
}
|
}
|
||||||
cmd, err := shexec.MakeRunnerExec(pk.CmdId)
|
cmd, err := shexec.MakeRunnerExec(pk.CmdId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sender.SendPacket(packet.MakeIdErrorPacket(pk.CmdId, fmt.Sprintf("cannot make runner command: %v", err)))
|
sender.SendPacket(packet.MakeIdErrorPacket(pk.CmdId, fmt.Sprintf("cannot make mshell command: %v", err)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cmdStdin, err := cmd.StdinPipe()
|
cmdStdin, err := cmd.StdinPipe()
|
||||||
@@ -155,7 +154,7 @@ func doMain() {
|
|||||||
packet.SendErrorPacket(os.Stdout, fmt.Sprintf("cannot change directory to $HOME '%s': %v", homeDir, err))
|
packet.SendErrorPacket(os.Stdout, fmt.Sprintf("cannot change directory to $HOME '%s': %v", homeDir, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = base.EnsureMShellPath()
|
_, err = base.GetMShellPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
packet.SendErrorPacket(os.Stdout, err.Error())
|
packet.SendErrorPacket(os.Stdout, err.Error())
|
||||||
return
|
return
|
||||||
@@ -168,7 +167,7 @@ func doMain() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
go tailer.Run()
|
go tailer.Run()
|
||||||
initPacket := packet.MakeRunnerInitPacket()
|
initPacket := packet.MakeInitPacket()
|
||||||
initPacket.Env = os.Environ()
|
initPacket.Env = os.Environ()
|
||||||
initPacket.HomeDir = homeDir
|
initPacket.HomeDir = homeDir
|
||||||
initPacket.ScHomeDir = scHomeDir
|
initPacket.ScHomeDir = scHomeDir
|
||||||
@@ -207,19 +206,106 @@ func doMain() {
|
|||||||
}
|
}
|
||||||
if pk.GetType() == packet.ErrorPacketStr {
|
if pk.GetType() == packet.ErrorPacketStr {
|
||||||
errPk := pk.(*packet.ErrorPacketType)
|
errPk := pk.(*packet.ErrorPacketType)
|
||||||
errPk.Error = "invalid packet sent to runner: " + errPk.Error
|
errPk.Error = "invalid packet sent to mshell: " + errPk.Error
|
||||||
sender.SendPacket(errPk)
|
sender.SendPacket(errPk)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sender.SendErrorPacket(fmt.Sprintf("invalid packet '%s' sent to runner", pk.GetType()))
|
sender.SendErrorPacket(fmt.Sprintf("invalid packet '%s' sent to mshell", pk.GetType()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleRemote() {
|
||||||
|
packetCh := packet.PacketParser(os.Stdin)
|
||||||
|
sender := packet.MakePacketSender(os.Stdout)
|
||||||
|
defer func() {
|
||||||
|
// wait for sender to complete
|
||||||
|
close(sender.SendCh)
|
||||||
|
<-sender.DoneCh
|
||||||
|
}()
|
||||||
|
initPacket := packet.MakeInitPacket()
|
||||||
|
initPacket.Version = MShellVersion
|
||||||
|
sender.SendPacket(initPacket)
|
||||||
|
var runPacket *packet.RunPacketType
|
||||||
|
for pk := range packetCh {
|
||||||
|
if pk.GetType() == packet.PingPacketStr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pk.GetType() == packet.RunPacketStr {
|
||||||
|
runPacket, _ = pk.(*packet.RunPacketType)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sender.SendErrorPacket(fmt.Sprintf("invalid packet '%s' sent to mshell", pk.GetType()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cmd, err := shexec.RunCommand(runPacket, sender)
|
||||||
|
if err != nil {
|
||||||
|
sender.SendErrorPacket(fmt.Sprintf("error running command: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer cmd.Close()
|
||||||
|
startPacket := cmd.MakeCmdStartPacket()
|
||||||
|
sender.SendPacket(startPacket)
|
||||||
|
cmd.RunIOAndWait(sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleServer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleClient() {
|
||||||
|
fmt.Printf("mshell client\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUsage(extended bool) {
|
||||||
|
usage := `
|
||||||
|
Client Usage: mshell [mshell-opts] [ssh-opts] user@host [command]
|
||||||
|
|
||||||
|
mshell multiplexes input and output streams to a remote command over ssh.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--env 'X=Y,A=B' - set remote environment variables for command, comma or newline separated
|
||||||
|
--env-file [file] - load environment variables from [file] (.env format)
|
||||||
|
--env-copy [glob] - copy local environment variables to remote using [glob] pattern
|
||||||
|
--cwd [dir] - execute remote command in [dir]
|
||||||
|
--no-auto-fds - do not auto-detect additional fds
|
||||||
|
--fds [fdspec] - open fds based off [fdspec], comma separated (implies --no-auto-fds)
|
||||||
|
<[num] opens for reading
|
||||||
|
>[num] opens for writing
|
||||||
|
<>[num] opens for read/write
|
||||||
|
e.g. --fds '<5,>6,<>7'
|
||||||
|
|
||||||
|
mshell is licensed under the MPLv2
|
||||||
|
Please see https://github.com/scripthaus-dev/mshell for extended usage modes, source code, bugs, and feature requests
|
||||||
|
`
|
||||||
|
fmt.Printf("%s\n\n", strings.TrimSpace(usage))
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if len(os.Args) == 1 {
|
||||||
|
handleUsage(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
firstArg := os.Args[1]
|
||||||
|
if firstArg == "--help" {
|
||||||
|
handleUsage(true)
|
||||||
|
return
|
||||||
|
} else if firstArg == "--version" {
|
||||||
|
fmt.Printf("mshell v%s\n", MShellVersion)
|
||||||
|
return
|
||||||
|
} else if firstArg == "--remote" {
|
||||||
|
handleRemote()
|
||||||
|
return
|
||||||
|
} else if firstArg == "--server" {
|
||||||
|
handleServer()
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
handleClient()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if len(os.Args) >= 2 {
|
if len(os.Args) >= 2 {
|
||||||
cmdId, err := uuid.Parse(os.Args[1])
|
cmdId, err := uuid.Parse(os.Args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
packet.SendErrorPacket(os.Stdout, fmt.Sprintf("invalid non-cmdid passed to runner", err))
|
packet.SendErrorPacket(os.Stdout, fmt.Sprintf("invalid non-cmdid passed to mshell", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
doSingle(cmdId.String())
|
doSingle(cmdId.String())
|
||||||
|
|||||||
+7
-10
@@ -17,6 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const DefaultMShellPath = "mshell"
|
const DefaultMShellPath = "mshell"
|
||||||
|
const DefaultUserMShellPath = ".mshell/mshell"
|
||||||
const MShellPathVarName = "MSHELL_PATH"
|
const MShellPathVarName = "MSHELL_PATH"
|
||||||
const SSHCommandVarName = "SSH_COMMAND"
|
const SSHCommandVarName = "SSH_COMMAND"
|
||||||
const ScHomeVarName = "SCRIPTHAUS_HOME"
|
const ScHomeVarName = "SCRIPTHAUS_HOME"
|
||||||
@@ -128,21 +129,17 @@ func EnsureSessionDir(sessionId string) (string, error) {
|
|||||||
return sdir, nil
|
return sdir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMShellPath() string {
|
func GetMShellPath() (string, error) {
|
||||||
msPath := os.Getenv(MShellPathVarName)
|
msPath := os.Getenv(MShellPathVarName)
|
||||||
if msPath != "" {
|
if msPath != "" {
|
||||||
return msPath
|
return exec.LookPath(msPath)
|
||||||
}
|
}
|
||||||
return DefaultMShellPath
|
userMShellPath := path.Join(GetHomeDir(), DefaultUserMShellPath)
|
||||||
}
|
msPath, err := exec.LookPath(userMShellPath)
|
||||||
|
|
||||||
func EnsureMShellPath() error {
|
|
||||||
msPath := GetMShellPath()
|
|
||||||
_, err := exec.LookPath(msPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return msPath, nil
|
||||||
}
|
}
|
||||||
return nil
|
return exec.LookPath(DefaultMShellPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetScSessionsDir() (string, error) {
|
func GetScSessionsDir() (string, error) {
|
||||||
|
|||||||
+41
-36
@@ -18,28 +18,28 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// remote: runnerinit, run, ping, data, cmdstart, cmddone
|
// remote: init, run, ping, data, cmdstart, cmddone
|
||||||
// remote(detached): runnerinit, run, cmdstart
|
// remote(detached): init, run, cmdstart
|
||||||
// server: runnerinit, run, ping, cmdstart, cmddone, cd, resp, getcmd, untailcmd, cmddata, input, data, [comp]
|
// server: init, run, ping, cmdstart, cmddone, cd, resp, getcmd, untailcmd, cmddata, input, data, [comp]
|
||||||
// all: error, message
|
// all: error, message
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RunPacketStr = "run"
|
RunPacketStr = "run"
|
||||||
PingPacketStr = "ping"
|
PingPacketStr = "ping"
|
||||||
RunnerInitPacketStr = "runnerinit"
|
InitPacketStr = "init"
|
||||||
DataPacketStr = "data"
|
DataPacketStr = "data"
|
||||||
CmdStartPacketStr = "cmdstart"
|
CmdStartPacketStr = "cmdstart"
|
||||||
CmdDonePacketStr = "cmddone"
|
CmdDonePacketStr = "cmddone"
|
||||||
ResponsePacketStr = "resp"
|
ResponsePacketStr = "resp"
|
||||||
DonePacketStr = "done"
|
DonePacketStr = "done"
|
||||||
ErrorPacketStr = "error"
|
ErrorPacketStr = "error"
|
||||||
MessagePacketStr = "message"
|
MessagePacketStr = "message"
|
||||||
GetCmdPacketStr = "getcmd"
|
GetCmdPacketStr = "getcmd"
|
||||||
UntailCmdPacketStr = "untailcmd"
|
UntailCmdPacketStr = "untailcmd"
|
||||||
CdPacketStr = "cd"
|
CdPacketStr = "cd"
|
||||||
CmdDataPacketStr = "cmddata"
|
CmdDataPacketStr = "cmddata"
|
||||||
RawPacketStr = "raw"
|
RawPacketStr = "raw"
|
||||||
InputPacketStr = "input"
|
InputPacketStr = "input"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TypeStrToFactory map[string]reflect.Type
|
var TypeStrToFactory map[string]reflect.Type
|
||||||
@@ -56,7 +56,7 @@ func init() {
|
|||||||
TypeStrToFactory[CmdDonePacketStr] = reflect.TypeOf(CmdDonePacketType{})
|
TypeStrToFactory[CmdDonePacketStr] = reflect.TypeOf(CmdDonePacketType{})
|
||||||
TypeStrToFactory[GetCmdPacketStr] = reflect.TypeOf(GetCmdPacketType{})
|
TypeStrToFactory[GetCmdPacketStr] = reflect.TypeOf(GetCmdPacketType{})
|
||||||
TypeStrToFactory[UntailCmdPacketStr] = reflect.TypeOf(UntailCmdPacketType{})
|
TypeStrToFactory[UntailCmdPacketStr] = reflect.TypeOf(UntailCmdPacketType{})
|
||||||
TypeStrToFactory[RunnerInitPacketStr] = reflect.TypeOf(RunnerInitPacketType{})
|
TypeStrToFactory[InitPacketStr] = reflect.TypeOf(InitPacketType{})
|
||||||
TypeStrToFactory[CdPacketStr] = reflect.TypeOf(CdPacketType{})
|
TypeStrToFactory[CdPacketStr] = reflect.TypeOf(CdPacketType{})
|
||||||
TypeStrToFactory[CmdDataPacketStr] = reflect.TypeOf(CmdDataPacketType{})
|
TypeStrToFactory[CmdDataPacketStr] = reflect.TypeOf(CmdDataPacketType{})
|
||||||
TypeStrToFactory[RawPacketStr] = reflect.TypeOf(RawPacketType{})
|
TypeStrToFactory[RawPacketStr] = reflect.TypeOf(RawPacketType{})
|
||||||
@@ -112,18 +112,20 @@ func MakePingPacket() *PingPacketType {
|
|||||||
|
|
||||||
type DataPacketType struct {
|
type DataPacketType struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
SessionId string `json:"sessionid"`
|
SessionId string `json:"sessionid,omitempty"`
|
||||||
CmdId string `json:"cmdid"`
|
CmdId string `json:"cmdid,omitempty"`
|
||||||
FdNum int `json:"fdnum"`
|
FdNum int `json:"fdnum"`
|
||||||
Data string `json:"data"`
|
Data string `json:"data"`
|
||||||
|
Eof bool `json:"eof,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*DataPacketType) GetType() string {
|
func (*DataPacketType) GetType() string {
|
||||||
return DataPacketStr
|
return DataPacketStr
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeDataPacket(fdNum int, data string) *DataPacketType {
|
func MakeDataPacket() *DataPacketType {
|
||||||
return &DataPacketType{Type: DataPacketStr, FdNum: fdNum, Data: data}
|
return &DataPacketType{Type: DataPacketStr}
|
||||||
}
|
}
|
||||||
|
|
||||||
// InputData gets written to PTY directly
|
// InputData gets written to PTY directly
|
||||||
@@ -249,7 +251,7 @@ func FmtMessagePacket(fmtStr string, args ...interface{}) *MessagePacketType {
|
|||||||
return &MessagePacketType{Type: MessagePacketStr, Message: message}
|
return &MessagePacketType{Type: MessagePacketStr, Message: message}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunnerInitPacketType struct {
|
type InitPacketType struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
ScHomeDir string `json:"schomedir,omitempty"`
|
ScHomeDir string `json:"schomedir,omitempty"`
|
||||||
@@ -258,12 +260,12 @@ type RunnerInitPacketType struct {
|
|||||||
User string `json:"user,omitempty"`
|
User string `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*RunnerInitPacketType) GetType() string {
|
func (*InitPacketType) GetType() string {
|
||||||
return RunnerInitPacketStr
|
return InitPacketStr
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeRunnerInitPacket() *RunnerInitPacketType {
|
func MakeInitPacket() *InitPacketType {
|
||||||
return &RunnerInitPacketType{Type: RunnerInitPacketStr}
|
return &InitPacketType{Type: InitPacketStr}
|
||||||
}
|
}
|
||||||
|
|
||||||
type DonePacketType struct {
|
type DonePacketType struct {
|
||||||
@@ -281,7 +283,8 @@ func MakeDonePacket() *DonePacketType {
|
|||||||
type CmdDonePacketType struct {
|
type CmdDonePacketType struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Ts int64 `json:"ts"`
|
Ts int64 `json:"ts"`
|
||||||
CmdId string `json:"cmdid"`
|
SessionId string `json:"sessionid,omitempty"`
|
||||||
|
CmdId string `json:"cmdid,omitempty"`
|
||||||
ExitCode int `json:"exitcode"`
|
ExitCode int `json:"exitcode"`
|
||||||
DurationMs int64 `json:"durationms"`
|
DurationMs int64 `json:"durationms"`
|
||||||
}
|
}
|
||||||
@@ -297,9 +300,10 @@ func MakeCmdDonePacket() *CmdDonePacketType {
|
|||||||
type CmdStartPacketType struct {
|
type CmdStartPacketType struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Ts int64 `json:"ts"`
|
Ts int64 `json:"ts"`
|
||||||
CmdId string `json:"cmdid"`
|
SessionId string `json:"sessionid,omitempty"`
|
||||||
|
CmdId string `json:"cmdid,omitempty"`
|
||||||
Pid int `json:"pid"`
|
Pid int `json:"pid"`
|
||||||
RunnerPid int `json:"runnerpid"`
|
MShellPid int `json:"mshellpid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*CmdStartPacketType) GetType() string {
|
func (*CmdStartPacketType) GetType() string {
|
||||||
@@ -323,13 +327,14 @@ type RemoteFd struct {
|
|||||||
|
|
||||||
type RunPacketType struct {
|
type RunPacketType struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
SessionId string `json:"sessionid"`
|
SessionId string `json:"sessionid,omitempty"`
|
||||||
CmdId string `json:"cmdid"`
|
CmdId string `json:"cmdid,omitempty"`
|
||||||
Command string `json:"command"`
|
Command string `json:"command"`
|
||||||
Cwd string `json:"cwd,omitempty"`
|
Cwd string `json:"cwd,omitempty"`
|
||||||
Env map[string]string `json:"env,omitempty"`
|
Env map[string]string `json:"env,omitempty"`
|
||||||
TermSize TermSize `json:"termsize"`
|
TermSize TermSize `json:"termsize,omitempty"`
|
||||||
Fds []RemoteFd `json:"fds"`
|
Fds []RemoteFd `json:"fds,omitempty"`
|
||||||
|
Detached bool `json:"detached,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*RunPacketType) GetType() string {
|
func (*RunPacketType) GetType() string {
|
||||||
|
|||||||
+180
-32
@@ -12,6 +12,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -27,14 +28,48 @@ const MaxRows = 1024
|
|||||||
const MaxCols = 1024
|
const MaxCols = 1024
|
||||||
|
|
||||||
type ShExecType struct {
|
type ShExecType struct {
|
||||||
FileNames *base.CommandFileNames
|
StartTs time.Time
|
||||||
Cmd *exec.Cmd
|
RunPacket *packet.RunPacketType
|
||||||
CmdPty *os.File
|
FileNames *base.CommandFileNames
|
||||||
StartTs time.Time
|
Cmd *exec.Cmd
|
||||||
|
CmdPty *os.File
|
||||||
|
FdReaders map[int]*os.File
|
||||||
|
FdWriters map[int]*os.File
|
||||||
|
CloseAfterStart []*os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeShExec(pk *packet.RunPacketType) *ShExecType {
|
||||||
|
return &ShExecType{
|
||||||
|
StartTs: time.Now(),
|
||||||
|
RunPacket: pk,
|
||||||
|
FdReaders: make(map[int]*os.File),
|
||||||
|
FdWriters: make(map[int]*os.File),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ShExecType) Close() {
|
func (c *ShExecType) Close() {
|
||||||
c.CmdPty.Close()
|
if c.CmdPty != nil {
|
||||||
|
c.CmdPty.Close()
|
||||||
|
}
|
||||||
|
for _, fd := range c.FdReaders {
|
||||||
|
fd.Close()
|
||||||
|
}
|
||||||
|
for _, fd := range c.FdWriters {
|
||||||
|
fd.Close()
|
||||||
|
}
|
||||||
|
for _, fd := range c.CloseAfterStart {
|
||||||
|
fd.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ShExecType) MakeCmdStartPacket() *packet.CmdStartPacketType {
|
||||||
|
startPacket := packet.MakeCmdStartPacket()
|
||||||
|
startPacket.Ts = time.Now().UnixMilli()
|
||||||
|
startPacket.SessionId = c.RunPacket.SessionId
|
||||||
|
startPacket.CmdId = c.RunPacket.CmdId
|
||||||
|
startPacket.Pid = c.Cmd.Process.Pid
|
||||||
|
startPacket.MShellPid = os.Getpid()
|
||||||
|
return startPacket
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEnvStrKey(envStr string) string {
|
func getEnvStrKey(envStr string) string {
|
||||||
@@ -93,7 +128,10 @@ func MakeExecCmd(pk *packet.RunPacketType, cmdTty *os.File) *exec.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MakeRunnerExec(cmdId string) (*exec.Cmd, error) {
|
func MakeRunnerExec(cmdId string) (*exec.Cmd, error) {
|
||||||
msPath := base.GetMShellPath()
|
msPath, err := base.GetMShellPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
ecmd := exec.Command(msPath, cmdId)
|
ecmd := exec.Command(msPath, cmdId)
|
||||||
return ecmd, nil
|
return ecmd, nil
|
||||||
}
|
}
|
||||||
@@ -124,19 +162,21 @@ func ValidateRunPacket(pk *packet.RunPacketType) error {
|
|||||||
if pk.Type != packet.RunPacketStr {
|
if pk.Type != packet.RunPacketStr {
|
||||||
return fmt.Errorf("run packet has wrong type: %s", pk.Type)
|
return fmt.Errorf("run packet has wrong type: %s", pk.Type)
|
||||||
}
|
}
|
||||||
if pk.SessionId == "" {
|
if pk.Detached {
|
||||||
return fmt.Errorf("run packet does not have sessionid")
|
if pk.SessionId == "" {
|
||||||
}
|
return fmt.Errorf("run packet does not have sessionid")
|
||||||
_, err := uuid.Parse(pk.SessionId)
|
}
|
||||||
if err != nil {
|
_, err := uuid.Parse(pk.SessionId)
|
||||||
return fmt.Errorf("invalid sessionid '%s' for command", pk.SessionId)
|
if err != nil {
|
||||||
}
|
return fmt.Errorf("invalid sessionid '%s' for command", pk.SessionId)
|
||||||
if pk.CmdId == "" {
|
}
|
||||||
return fmt.Errorf("run packet does not have cmdid")
|
if pk.CmdId == "" {
|
||||||
}
|
return fmt.Errorf("run packet does not have cmdid")
|
||||||
_, err = uuid.Parse(pk.CmdId)
|
}
|
||||||
if err != nil {
|
_, err = uuid.Parse(pk.CmdId)
|
||||||
return fmt.Errorf("invalid cmdid '%s' for command", pk.CmdId)
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid cmdid '%s' for command", pk.CmdId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if pk.Cwd != "" {
|
if pk.Cwd != "" {
|
||||||
dirInfo, err := os.Stat(pk.Cwd)
|
dirInfo, err := os.Stat(pk.Cwd)
|
||||||
@@ -164,13 +204,120 @@ func GetWinsize(p *packet.RunPacketType) *pty.Winsize {
|
|||||||
|
|
||||||
// when err is nil, the command will have already been started
|
// when err is nil, the command will have already been started
|
||||||
func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
||||||
if pk.CmdId == "" {
|
|
||||||
pk.CmdId = uuid.New().String()
|
|
||||||
}
|
|
||||||
err := ValidateRunPacket(pk)
|
err := ValidateRunPacket(pk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if !pk.Detached {
|
||||||
|
return runCommandSimple(pk, sender)
|
||||||
|
} else {
|
||||||
|
return runCommandDetached(pk, sender)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the *writer* to connect to process, reader is put in FdReaders
|
||||||
|
func (cmd *ShExecType) makeReaderPipe(fdNum int) (*os.File, error) {
|
||||||
|
pr, pw, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.FdReaders[fdNum] = pr
|
||||||
|
cmd.CloseAfterStart = append(cmd.CloseAfterStart, pw)
|
||||||
|
return pw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the *reader* to connect to process, writer is put in FdWriters
|
||||||
|
func (cmd *ShExecType) makeWriterPipe(fdNum int) (*os.File, error) {
|
||||||
|
pr, pw, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.FdWriters[fdNum] = pw
|
||||||
|
cmd.CloseAfterStart = append(cmd.CloseAfterStart, pr)
|
||||||
|
return pr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *ShExecType) MakeDataPacket(fdNum int, data []byte) *packet.DataPacketType {
|
||||||
|
pk := packet.MakeDataPacket()
|
||||||
|
pk.SessionId = cmd.RunPacket.SessionId
|
||||||
|
pk.CmdId = cmd.RunPacket.CmdId
|
||||||
|
pk.FdNum = fdNum
|
||||||
|
pk.Data = string(data)
|
||||||
|
return pk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *ShExecType) runReadLoop(wg *sync.WaitGroup, fdNum int, fd *os.File, sender *packet.PacketSender) {
|
||||||
|
go func() {
|
||||||
|
defer fd.Close()
|
||||||
|
defer wg.Done()
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
for {
|
||||||
|
nr, err := fd.Read(buf)
|
||||||
|
pk := cmd.MakeDataPacket(fdNum, buf[0:nr])
|
||||||
|
if err == io.EOF {
|
||||||
|
pk.Eof = true
|
||||||
|
sender.SendPacket(pk)
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
pk.Error = err.Error()
|
||||||
|
sender.SendPacket(pk)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
sender.SendPacket(pk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd *ShExecType) RunIOAndWait(sender *packet.PacketSender) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(cmd.FdReaders))
|
||||||
|
go func() {
|
||||||
|
for fdNum, fd := range cmd.FdReaders {
|
||||||
|
cmd.runReadLoop(&wg, fdNum, fd, sender)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
donePacket := cmd.WaitForCommand()
|
||||||
|
wg.Wait()
|
||||||
|
sender.SendPacket(donePacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCommandSimple(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
||||||
|
cmd := MakeShExec(pk)
|
||||||
|
cmd.Cmd = exec.Command("bash", "-c", pk.Command)
|
||||||
|
UpdateCmdEnv(cmd.Cmd, pk.Env)
|
||||||
|
if pk.Cwd != "" {
|
||||||
|
cmd.Cmd.Dir = pk.Cwd
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
cmd.Cmd.Stdin, err = cmd.makeWriterPipe(0)
|
||||||
|
if err != nil {
|
||||||
|
cmd.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.Cmd.Stdout, err = cmd.makeReaderPipe(1)
|
||||||
|
if err != nil {
|
||||||
|
cmd.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd.Cmd.Stderr, err = cmd.makeReaderPipe(2)
|
||||||
|
if err != nil {
|
||||||
|
cmd.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = cmd.Cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
cmd.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, fd := range cmd.CloseAfterStart {
|
||||||
|
fd.Close()
|
||||||
|
}
|
||||||
|
cmd.CloseAfterStart = nil
|
||||||
|
return cmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCommandDetached(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecType, error) {
|
||||||
fileNames, err := base.GetCommandFileNames(pk.SessionId, pk.CmdId)
|
fileNames, err := base.GetCommandFileNames(pk.SessionId, pk.CmdId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -190,7 +337,7 @@ func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecT
|
|||||||
defer func() {
|
defer func() {
|
||||||
cmdTty.Close()
|
cmdTty.Close()
|
||||||
}()
|
}()
|
||||||
startTs := time.Now()
|
rtn := MakeShExec(pk)
|
||||||
ecmd := MakeExecCmd(pk, cmdTty)
|
ecmd := MakeExecCmd(pk, cmdTty)
|
||||||
err = ecmd.Start()
|
err = ecmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -214,12 +361,10 @@ func RunCommand(pk *packet.RunPacketType, sender *packet.PacketSender) (*ShExecT
|
|||||||
sender.SendErrorPacket(fmt.Sprintf("reading from stdin fifo: %v", copyFifoErr))
|
sender.SendErrorPacket(fmt.Sprintf("reading from stdin fifo: %v", copyFifoErr))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return &ShExecType{
|
rtn.FileNames = fileNames
|
||||||
FileNames: fileNames,
|
rtn.Cmd = ecmd
|
||||||
Cmd: ecmd,
|
rtn.CmdPty = cmdPty
|
||||||
CmdPty: cmdPty,
|
return rtn, nil
|
||||||
StartTs: startTs,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetExitCode(err error) int {
|
func GetExitCode(err error) int {
|
||||||
@@ -233,16 +378,19 @@ func GetExitCode(err error) int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ShExecType) WaitForCommand(cmdId string) *packet.CmdDonePacketType {
|
func (c *ShExecType) WaitForCommand() *packet.CmdDonePacketType {
|
||||||
exitErr := c.Cmd.Wait()
|
exitErr := c.Cmd.Wait()
|
||||||
endTs := time.Now()
|
endTs := time.Now()
|
||||||
cmdDuration := endTs.Sub(c.StartTs)
|
cmdDuration := endTs.Sub(c.StartTs)
|
||||||
exitCode := GetExitCode(exitErr)
|
exitCode := GetExitCode(exitErr)
|
||||||
donePacket := packet.MakeCmdDonePacket()
|
donePacket := packet.MakeCmdDonePacket()
|
||||||
donePacket.Ts = endTs.UnixMilli()
|
donePacket.Ts = endTs.UnixMilli()
|
||||||
donePacket.CmdId = cmdId
|
donePacket.SessionId = c.RunPacket.SessionId
|
||||||
|
donePacket.CmdId = c.RunPacket.CmdId
|
||||||
donePacket.ExitCode = exitCode
|
donePacket.ExitCode = exitCode
|
||||||
donePacket.DurationMs = int64(cmdDuration / time.Millisecond)
|
donePacket.DurationMs = int64(cmdDuration / time.Millisecond)
|
||||||
os.Remove(c.FileNames.StdinFifo) // best effort (no need to check error)
|
if c.FileNames != nil {
|
||||||
|
os.Remove(c.FileNames.StdinFifo) // best effort (no need to check error)
|
||||||
|
}
|
||||||
return donePacket
|
return donePacket
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user