aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2018-02-23 17:40:32 +0100
committerSebastien Douheret <sebastien.douheret@iot.bzh>2018-02-23 17:40:32 +0100
commitabbf89a5589f2c92f786bb45c5cd613a318a9e24 (patch)
treed5b6f28e0b96570ee1486061057ac89de8f1f0d4
parent60e342228ba8017e1cacc71a64280143832e7719 (diff)
Add PtsMode support to eows libv0.2.0
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
-rw-r--r--golib/eows/eows-in.go3
-rw-r--r--golib/eows/eows-out.go26
-rw-r--r--golib/eows/eows.go169
3 files changed, 150 insertions, 48 deletions
diff --git a/golib/eows/eows-in.go b/golib/eows/eows-in.go
index 1ecd2a1..89ca891 100644
--- a/golib/eows/eows-in.go
+++ b/golib/eows/eows-in.go
@@ -7,6 +7,7 @@ import (
"time"
)
+// DoneChan Channel used to propagate status+error on command exit
type DoneChan struct {
status int
err error
@@ -43,6 +44,8 @@ func (e *ExecOverWS) cmdPumpStdin(inw *os.File) {
s := sts.Sys().(syscall.WaitStatus)
status = s.ExitStatus()
}
+ e.procExited = true
+
done <- DoneChan{status, err}
}()
diff --git a/golib/eows/eows-out.go b/golib/eows/eows-out.go
index b70e70c..2d9bdb0 100644
--- a/golib/eows/eows-out.go
+++ b/golib/eows/eows-out.go
@@ -16,7 +16,7 @@ func scanChars(data []byte, atEOF bool) (advance int, token []byte, err error) {
}
// _pumper is in charge to collect
-func (e *ExecOverWS) _pumper(sc *bufio.Scanner, fctOut func(s string)) {
+func (e *ExecOverWS) _pumper(sc *bufio.Scanner, fctCB func(s string)) {
// Select split function (default sc.ScanLines)
if e.OutSplit == SplitChar || e.OutSplit == SplitLineTime || e.OutSplit == SplitTime {
@@ -31,20 +31,26 @@ func (e *ExecOverWS) _pumper(sc *bufio.Scanner, fctOut func(s string)) {
buf += sc.Text()
if time.Since(t0).Nanoseconds() > e.LineTimeSpan ||
(e.OutSplit == SplitLineTime && strings.Contains(buf, "\n")) {
- fctOut(buf)
+ fctCB(buf)
buf = ""
t0 = time.Now()
}
+ if e.procExited {
+ break
+ }
}
// Send remaining characters
if len(buf) > 0 {
- e.OutputCB(e, "", buf)
+ fctCB(buf)
}
} else {
for sc.Scan() {
- e.OutputCB(e, sc.Text(), "")
+ fctCB(sc.Text())
+ if e.procExited {
+ break
+ }
}
}
@@ -58,10 +64,12 @@ func (e *ExecOverWS) cmdPumpStdout(r io.Reader, done chan struct{}) {
sc := bufio.NewScanner(r)
- e._pumper(sc, func(bufOut string) {
- e.OutputCB(e, bufOut, "")
+ e._pumper(sc, func(b string) {
+ e.OutputCB(e, b, "")
})
+ e.logDebug("STDOUT pump exit")
+
if sc.Err() != nil && !strings.Contains(sc.Err().Error(), "file already closed") {
e.logError("stdout scan: %v", sc.Err())
}
@@ -77,10 +85,12 @@ func (e *ExecOverWS) cmdPumpStderr(r io.Reader) {
sc := bufio.NewScanner(r)
- e._pumper(sc, func(bufErr string) {
- e.OutputCB(e, "", bufErr)
+ e._pumper(sc, func(b string) {
+ e.OutputCB(e, "", b)
})
+ e.logDebug("STDERR pump exit")
+
if sc.Err() != nil && !strings.Contains(sc.Err().Error(), "file already closed") {
e.logError("stderr scan: %v", sc.Err())
}
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...)
}
}