2019-06-05 03:24:16 +00:00
|
|
|
package data
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"github.com/kr/pty"
|
|
|
|
"github.com/lunixbochs/vtclean"
|
|
|
|
"io"
|
|
|
|
"os/exec"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2019-06-23 04:08:24 +00:00
|
|
|
startupTimeout = 200 * time.Millisecond
|
2019-06-05 03:24:16 +00:00
|
|
|
minAwaitTimeout = 100 * time.Millisecond
|
|
|
|
maxAwaitTimeout = 1 * time.Second
|
|
|
|
)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Experimental
|
|
|
|
*/
|
|
|
|
type PtyInteractiveShell struct {
|
|
|
|
item *Item
|
|
|
|
variables []string
|
|
|
|
cmd *exec.Cmd
|
2019-06-09 14:37:05 +00:00
|
|
|
file io.WriteCloser
|
2019-06-05 03:24:16 +00:00
|
|
|
ch chan string
|
|
|
|
errCount int
|
2019-06-21 04:32:02 +00:00
|
|
|
timeout time.Duration
|
2019-06-05 03:24:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PtyInteractiveShell) init() error {
|
|
|
|
|
2019-06-23 04:08:24 +00:00
|
|
|
cmd := exec.Command("sh", "-c", s.item.initScripts[0])
|
2019-06-05 03:24:16 +00:00
|
|
|
enrichEnvVariables(cmd, s.variables)
|
|
|
|
|
|
|
|
file, err := pty.Start(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
channel := make(chan string)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for scanner.Scan() {
|
|
|
|
channel <- scanner.Text()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
s.cmd = cmd
|
2019-06-09 14:37:05 +00:00
|
|
|
s.file = file
|
2019-06-05 03:24:16 +00:00
|
|
|
s.ch = channel
|
|
|
|
|
|
|
|
_, err = file.Read(make([]byte, 4096))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(startupTimeout)
|
|
|
|
|
2019-06-23 04:08:24 +00:00
|
|
|
for i := 1; i < len(s.item.initScripts); i++ {
|
|
|
|
_, err = io.WriteString(s.file, fmt.Sprintf(" %s\n", s.item.initScripts[i]))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
time.Sleep(startupTimeout) // TODO wait until cmd complete
|
|
|
|
}
|
|
|
|
|
2019-06-05 03:24:16 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PtyInteractiveShell) execute() (string, error) {
|
|
|
|
|
2019-06-09 14:37:05 +00:00
|
|
|
_, err := io.WriteString(s.file, fmt.Sprintf(" %s\n", s.item.sampleScript))
|
2019-06-05 03:24:16 +00:00
|
|
|
if err != nil {
|
|
|
|
s.errCount++
|
|
|
|
if s.errCount > errorThreshold {
|
2019-06-09 14:37:05 +00:00
|
|
|
_ = s.cmd.Wait()
|
|
|
|
_ = s.file.Close()
|
2019-06-05 03:24:16 +00:00
|
|
|
s.item.ptyShell = nil // restart session
|
|
|
|
}
|
|
|
|
return "", errors.New(fmt.Sprintf("Failed to execute command: %s", err))
|
|
|
|
}
|
|
|
|
|
|
|
|
softTimeout := make(chan bool, 1)
|
|
|
|
hardTimeout := make(chan bool, 1)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
time.Sleep(s.getAwaitTimeout() / 2)
|
|
|
|
softTimeout <- true
|
|
|
|
time.Sleep(s.getAwaitTimeout() * 100)
|
|
|
|
hardTimeout <- true
|
|
|
|
}()
|
|
|
|
|
|
|
|
var builder strings.Builder
|
|
|
|
softTimeoutElapsed := false
|
|
|
|
|
|
|
|
await:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case out := <-s.ch:
|
|
|
|
cout := vtclean.Clean(out, false)
|
|
|
|
if len(cout) > 0 && !strings.Contains(cout, s.item.sampleScript) {
|
|
|
|
builder.WriteString(cout)
|
|
|
|
builder.WriteString("\n")
|
|
|
|
if softTimeoutElapsed {
|
|
|
|
break await
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case <-softTimeout:
|
|
|
|
if builder.Len() > 0 {
|
|
|
|
break await
|
|
|
|
} else {
|
|
|
|
softTimeoutElapsed = true
|
|
|
|
}
|
|
|
|
case <-hardTimeout:
|
|
|
|
break await
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sample := strings.TrimSpace(builder.String())
|
|
|
|
|
|
|
|
return s.item.transform(sample)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *PtyInteractiveShell) getAwaitTimeout() time.Duration {
|
|
|
|
|
2019-06-21 04:32:02 +00:00
|
|
|
if s.timeout > maxAwaitTimeout {
|
2019-06-05 03:24:16 +00:00
|
|
|
return maxAwaitTimeout
|
2019-06-21 04:32:02 +00:00
|
|
|
} else if s.timeout < minAwaitTimeout {
|
2019-06-05 03:24:16 +00:00
|
|
|
return minAwaitTimeout
|
|
|
|
}
|
|
|
|
|
2019-06-21 04:32:02 +00:00
|
|
|
return s.timeout
|
2019-06-05 03:24:16 +00:00
|
|
|
}
|