aboutsummaryrefslogtreecommitdiffstats
path: root/golib/eows/eows.go
diff options
context:
space:
mode:
Diffstat (limited to 'golib/eows/eows.go')
-rw-r--r--golib/eows/eows.go169
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...)
}
}