From f1e97cdbcc13318cb3de39d9e67bc0241614dfcc Mon Sep 17 00:00:00 2001 From: Sebastien Douheret Date: Fri, 9 Mar 2018 15:57:36 +0100 Subject: Improved PtyMode (eows lib). - renamed PtsMode to PtyMode - used byte array instead of string - allowed terminal echo on/off (PtyMode only) - fixed support escaped and control characters (PtyMode only) Signed-off-by: Sebastien Douheret --- golib/eows/eows-in.go | 10 +++++----- golib/eows/eows-out.go | 49 +++++++++++++++++++++++++++++++++++++------------ golib/eows/eows.go | 36 ++++++++++++++++++++---------------- 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/golib/eows/eows-in.go b/golib/eows/eows-in.go index 89ca891..5e74c76 100644 --- a/golib/eows/eows-in.go +++ b/golib/eows/eows-in.go @@ -13,21 +13,21 @@ type DoneChan struct { err error } -// cmdPumpStdin is in charge of receive characters and send them to stdin -func (e *ExecOverWS) cmdPumpStdin(inw *os.File) { +// pumpStdin is in charge of receive characters and send them to stdin +func (e *ExecOverWS) pumpStdin(inw *os.File) { done := make(chan DoneChan, 1) if e.InputEvent != "" && e.InputCB != nil { - err := (*e.SocketIO).On(e.InputEvent, func(stdin string) { - in, err := e.InputCB(e, string(stdin)) + err := (*e.SocketIO).On(e.InputEvent, func(stdin []byte) { + in, err := e.InputCB(e, stdin) if err != nil { e.logDebug("Error stdin: %s", err.Error()) inw.Close() return } - if _, err := inw.Write([]byte(in)); err != nil { + if _, err := inw.Write(in); err != nil { e.logError("Error while writing to stdin: %s", err.Error()) } }) diff --git a/golib/eows/eows-out.go b/golib/eows/eows-out.go index 2d9bdb0..3163b2f 100644 --- a/golib/eows/eows-out.go +++ b/golib/eows/eows-out.go @@ -56,16 +56,15 @@ func (e *ExecOverWS) _pumper(sc *bufio.Scanner, fctCB func(s string)) { } -// cmdPumpStdout is in charge to forward stdout in websocket -func (e *ExecOverWS) cmdPumpStdout(r io.Reader, done chan struct{}) { - - defer func() { - }() +// pipePumpStdout is in charge to forward stdout in websocket +func (e *ExecOverWS) pipePumpStdout(r io.Reader, done chan struct{}) { sc := bufio.NewScanner(r) e._pumper(sc, func(b string) { - e.OutputCB(e, b, "") + if e.OutputCB != nil { + e.OutputCB(e, []byte(b), []byte{}) + } }) e.logDebug("STDOUT pump exit") @@ -77,16 +76,15 @@ func (e *ExecOverWS) cmdPumpStdout(r io.Reader, done chan struct{}) { close(done) } -// cmdPumpStderr is in charge to forward stderr in websocket -func (e *ExecOverWS) cmdPumpStderr(r io.Reader) { - - defer func() { - }() +// pipePumpStderr is in charge to forward stderr in websocket +func (e *ExecOverWS) pipePumpStderr(r io.Reader) { sc := bufio.NewScanner(r) e._pumper(sc, func(b string) { - e.OutputCB(e, "", b) + if e.OutputCB != nil { + e.OutputCB(e, []byte{}, []byte(b)) + } }) e.logDebug("STDERR pump exit") @@ -95,3 +93,30 @@ func (e *ExecOverWS) cmdPumpStderr(r io.Reader) { e.logError("stderr scan: %v", sc.Err()) } } + +// ptsPumpStdout is in charge to forward stdout in websocket +// (only used when PtyMode is set) +func (e *ExecOverWS) ptsPumpStdout(r io.Reader, done chan struct{}) { + + buffer := make([]byte, 1024) + for { + n, err := r.Read(buffer) + if err != nil { + if err != io.EOF && + !strings.Contains(err.Error(), "file already closed") { + e.logError("Error stdout read: %v", err) + } + break + } + if n == 0 { + continue + } + if e.OutputCB != nil { + e.OutputCB(e, buffer[:n], []byte{}) + } + } + + close(done) + + e.logDebug("Eows stdout pump exited") +} diff --git a/golib/eows/eows.go b/golib/eows/eows.go index 283d673..9d0b520 100644 --- a/golib/eows/eows.go +++ b/golib/eows/eows.go @@ -16,10 +16,10 @@ import ( ) // OnInputCB is the function callback used to receive data -type OnInputCB func(e *ExecOverWS, stdin string) (string, error) +type OnInputCB func(e *ExecOverWS, stdin []byte) ([]byte, error) // EmitOutputCB is the function callback used to emit data -type EmitOutputCB func(e *ExecOverWS, stdout, stderr string) +type EmitOutputCB func(e *ExecOverWS, stdout, stderr []byte) // EmitExitCB is the function callback used to emit exit proc code type EmitExitCB func(e *ExecOverWS, code int, err error) @@ -60,7 +60,8 @@ type ExecOverWS struct { UserData *map[string]interface{} // user data passed to callbacks OutSplit SplitType // split method to tokenize stdout/stderr LineTimeSpan int64 // time span (only used with SplitTime or SplitLineTime) - PtsMode bool // Allocate a pseudo-terminal (allow to execute screen-based program) + PtyMode bool // Allocate a pseudo-terminal (allow to execute screen-based program) + PtyTermEcho bool // Turn on/off terminal echo // Private fields @@ -84,7 +85,8 @@ func New(cmd string, args []string, so *socketio.Socket, soID, cmdID string) *Ex CmdExecTimeout: -1, // default no timeout OutSplit: SplitLineTime, // default split by line with time LineTimeSpan: 500 * time.Millisecond.Nanoseconds(), - PtsMode: false, + PtyMode: false, + PtyTermEcho: true, } cmdIDMap[cmdID] = e @@ -114,7 +116,7 @@ func (e *ExecOverWS) Start() error { e.procExited = false - if e.PtsMode { + if e.PtyMode { e.command = exec.Command(bashArgs[0], bashArgs[1:]...) e.command.Env = append(os.Environ(), e.Env...) @@ -126,7 +128,9 @@ func (e *ExecOverWS) Start() error { e.proc = e.command.Process // Turn off terminal echo - e.terminalEcho(e.ptmx, false) + if !e.PtyTermEcho { + e.terminalEcho(e.ptmx, false) + } } else { @@ -162,15 +166,15 @@ func (e *ExecOverWS) Start() error { go func() { stdoutDone := make(chan struct{}) - if e.PtsMode { + if e.PtyMode { // Make sure to close the pty at the end. defer e.ptmx.Close() // Handle both stdout mixed with stderr - go e.cmdPumpStdout(e.ptmx, stdoutDone) + go e.ptsPumpStdout(e.ptmx, stdoutDone) // Blocking function that poll input or wait for end of process - e.cmdPumpStdin(e.ptmx) + e.pumpStdin(e.ptmx) } else { // Make sure to close all pipes @@ -182,11 +186,11 @@ func (e *ExecOverWS) Start() error { defer inw.Close() // Handle stdout + stderr - go e.cmdPumpStdout(outr, stdoutDone) - go e.cmdPumpStderr(errr) + go e.pipePumpStdout(outr, stdoutDone) + go e.pipePumpStderr(errr) // Blocking function that poll input or wait for end of process - e.cmdPumpStdin(inw) + e.pumpStdin(inw) } if status, err := e.proc.Wait(); err == nil { @@ -222,8 +226,8 @@ exitErr: // TerminalSetSize Set terminal size func (e *ExecOverWS) TerminalSetSize(rows, cols uint16) error { - if !e.PtsMode || e.ptmx == nil { - return fmt.Errorf("PtsMode not set") + if !e.PtyMode || e.ptmx == nil { + return fmt.Errorf("PtyMode not set") } w, err := pty.GetsizeFull(e.ptmx) if err != nil { @@ -234,8 +238,8 @@ func (e *ExecOverWS) TerminalSetSize(rows, cols uint16) error { // TerminalSetSizePos Set terminal size and position func (e *ExecOverWS) TerminalSetSizePos(rows, cols, x, y uint16) error { - if !e.PtsMode || e.ptmx == nil { - return fmt.Errorf("PtsMode not set") + if !e.PtyMode || e.ptmx == nil { + return fmt.Errorf("PtyMode not set") } winSz := pty.Winsize{Rows: rows, Cols: cols, X: x, Y: y} return pty.Setsize(e.ptmx, &winSz) -- cgit 1.2.3-korg