diff options
author | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2018-02-23 17:40:32 +0100 |
---|---|---|
committer | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2018-02-23 17:40:32 +0100 |
commit | abbf89a5589f2c92f786bb45c5cd613a318a9e24 (patch) | |
tree | d5b6f28e0b96570ee1486061057ac89de8f1f0d4 /golib/eows/eows.go | |
parent | 60e342228ba8017e1cacc71a64280143832e7719 (diff) |
Add PtsMode support to eows libv0.2.0
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
Diffstat (limited to 'golib/eows/eows.go')
-rw-r--r-- | golib/eows/eows.go | 169 |
1 files changed, 129 insertions, 40 deletions
diff --git a/golib/eows/eows.go b/golib/eows/eows.go index 192be3d..283d673 100644 --- a/golib/eows/eows.go +++ b/golib/eows/eows.go @@ -4,11 +4,15 @@ package eows import ( "fmt" "os" + "os/exec" "strings" + "syscall" "time" + "unsafe" "github.com/Sirupsen/logrus" "github.com/googollee/go-socket.io" + "github.com/kr/pty" ) // OnInputCB is the function callback used to receive data @@ -56,9 +60,14 @@ 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) // Private fields - proc *os.Process + + proc *os.Process + command *exec.Cmd + ptmx *os.File + procExited bool } var cmdIDMap = make(map[string]*ExecOverWS) @@ -75,6 +84,7 @@ 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, } cmdIDMap[cmdID] = e @@ -102,53 +112,82 @@ func (e *ExecOverWS) Start() error { e.CmdExecTimeout = 365 * 24 * 60 * 60 } - // Create pipes - outr, outw, err = os.Pipe() - if err != nil { - err = fmt.Errorf("Pipe stdout error: " + err.Error()) - goto exitErr - } + e.procExited = false - errr, errw, err = os.Pipe() - if err != nil { - err = fmt.Errorf("Pipe stderr error: " + err.Error()) - goto exitErr - } + if e.PtsMode { - inr, inw, err = os.Pipe() - if err != nil { - err = fmt.Errorf("Pipe stdin error: " + err.Error()) - goto exitErr - } + e.command = exec.Command(bashArgs[0], bashArgs[1:]...) + e.command.Env = append(os.Environ(), e.Env...) + e.ptmx, err = pty.Start(e.command) + if err != nil { + err = fmt.Errorf("Process start error: " + err.Error()) + goto exitErr + } + e.proc = e.command.Process - e.proc, err = os.StartProcess("/bin/bash", bashArgs, &os.ProcAttr{ - Files: []*os.File{inr, outw, errw}, - Env: append(os.Environ(), e.Env...), - }) - if err != nil { - err = fmt.Errorf("Process start error: " + err.Error()) - goto exitErr + // Turn off terminal echo + e.terminalEcho(e.ptmx, false) + + } else { + + // Create pipes + outr, outw, err = os.Pipe() + if err != nil { + err = fmt.Errorf("Pipe stdout error: " + err.Error()) + goto exitErr + } + + errr, errw, err = os.Pipe() + if err != nil { + err = fmt.Errorf("Pipe stderr error: " + err.Error()) + goto exitErr + } + + inr, inw, err = os.Pipe() + if err != nil { + err = fmt.Errorf("Pipe stdin error: " + err.Error()) + goto exitErr + } + + e.proc, err = os.StartProcess(bashArgs[0], bashArgs, &os.ProcAttr{ + Files: []*os.File{inr, outw, errw}, + Env: append(os.Environ(), e.Env...), + }) + if err != nil { + err = fmt.Errorf("Process start error: " + err.Error()) + goto exitErr + } } go func() { - defer outr.Close() - defer outw.Close() - defer errr.Close() - defer errw.Close() - defer inr.Close() - defer inw.Close() - stdoutDone := make(chan struct{}) - go e.cmdPumpStdout(outr, stdoutDone) - go e.cmdPumpStderr(errr) - // Blocking function that poll input or wait for end of process - e.cmdPumpStdin(inw) + if e.PtsMode { + // 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) + + // Blocking function that poll input or wait for end of process + e.cmdPumpStdin(e.ptmx) - // Some commands will exit when stdin is closed. - inw.Close() + } else { + // Make sure to close all pipes + defer outr.Close() + defer outw.Close() + defer errr.Close() + defer errw.Close() + defer inr.Close() + defer inw.Close() - defer outr.Close() + // Handle stdout + stderr + go e.cmdPumpStdout(outr, stdoutDone) + go e.cmdPumpStderr(errr) + + // Blocking function that poll input or wait for end of process + e.cmdPumpStdin(inw) + } if status, err := e.proc.Wait(); err == nil { // Other commands need a bonk on the head. @@ -181,14 +220,64 @@ exitErr: return err } +// TerminalSetSize Set terminal size +func (e *ExecOverWS) TerminalSetSize(rows, cols uint16) error { + if !e.PtsMode || e.ptmx == nil { + return fmt.Errorf("PtsMode not set") + } + w, err := pty.GetsizeFull(e.ptmx) + if err != nil { + return err + } + return e.TerminalSetSizePos(rows, cols, w.X, w.Y) +} + +// 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") + } + winSz := pty.Winsize{Rows: rows, Cols: cols, X: x, Y: y} + return pty.Setsize(e.ptmx, &winSz) +} + +/** + * Private functions + **/ + +// terminalEcho Enable or disable echoing terminal input. +// This is useful specifically for when users enter passwords. +func (e *ExecOverWS) terminalEcho(ff *os.File, show bool) { + var termios = &syscall.Termios{} + + fd := ff.Fd() + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, + syscall.TCGETS, uintptr(unsafe.Pointer(termios))); err != 0 { + return + } + + if show { + termios.Lflag |= syscall.ECHO + } else { + termios.Lflag &^= syscall.ECHO + } + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, + uintptr(syscall.TCSETS), + uintptr(unsafe.Pointer(termios))); err != 0 { + return + } +} + func (e *ExecOverWS) logDebug(format string, a ...interface{}) { if e.Log != nil { - e.Log.Debugf(format, a) + e.Log.Debugf(format, a...) } } func (e *ExecOverWS) logError(format string, a ...interface{}) { if e.Log != nil { - e.Log.Errorf(format, a) + e.Log.Errorf(format, a...) } } |