package main
import (
- "bufio"
"encoding/json"
"fmt"
+ "io"
"os"
"sort"
"strings"
- "syscall"
"time"
- "github.com/golang/crypto/ssh/terminal"
-
"gerrit.automotivelinux.org/gerrit/src/xds/xds-agent.git/lib/xaapiv1"
+ "github.com/creack/goselect"
+ "github.com/golang/crypto/ssh/terminal"
"github.com/urfave/cli"
)
for _, tt := range tgt.Terms {
tmNfo += "\t ID:\t" + tt.ID + "\n"
tmNfo += "\t Name:\t" + tt.Name + "\n"
+ tmNfo += "\t Type:\t" + string(tt.Type) + "\n"
tmNfo += "\t Status:\t" + tt.Status + "\n"
tmNfo += "\t User:\t" + tt.User + "\n"
tmNfo += "\t Options:\t" + strings.Join(tt.Options, " ") + "\n"
}
exitChan := make(chan exitResult, 1)
- IOsk.On("disconnection", func(err error) {
+ IOSkClient.On("disconnection", func(err error) {
Log.Debugf("WS disconnection event with err: %v\n", err)
exitChan <- exitResult{err, 2}
})
- IOsk.On(xaapiv1.TerminalOutEvent, func(ev xaapiv1.TerminalOutMsg) {
- if ev.Stdout != "" {
- fmt.Printf(ev.Stdout)
+ IOSkClient.On(xaapiv1.TerminalOutEvent, func(ev xaapiv1.TerminalOutMsg) {
+ if len(ev.Stdout) > 0 {
+ os.Stdout.Write(ev.Stdout)
}
- if ev.Stderr != "" {
- fmt.Fprintf(os.Stderr, ev.Stderr)
+ if len(ev.Stderr) > 0 {
+ os.Stderr.Write(ev.Stdout)
}
})
- IOsk.On(xaapiv1.TerminalExitEvent, func(ev xaapiv1.TerminalExitMsg) {
+ IOSkClient.On(xaapiv1.TerminalExitEvent, func(ev xaapiv1.TerminalExitMsg) {
exitChan <- exitResult{ev.Error, ev.Code}
})
- /* FIXME - use raw mode to support escape keys, arrows keys, control char...
- // import "github.com/golang/crypto/ssh/terminal"
-
+ // Setup terminal (raw mode to handle escape and control keys)
+ if !terminal.IsTerminal(0) || !terminal.IsTerminal(1) {
+ return cli.NewExitError("stdin/stdout should be terminal", 1)
+ }
oldState, err := terminal.MakeRaw(int(os.Stdin.Fd()))
- if err == nil {
- defer terminal.Restore(int(os.Stdin.Fd()), oldState)
+ if err != nil {
+ return cli.NewExitError(err.Error(), 1)
}
- */
+ defer terminal.Restore(int(os.Stdin.Fd()), oldState)
// Send stdin though WS
go func() {
- paranoia := 600
- reader := bufio.NewReader(os.Stdin)
+ type exposeFd interface {
+ Fd() uintptr
+ }
+ buff := make([]byte, 128)
+ rdfs := &goselect.FDSet{}
+ reader := io.ReadCloser(os.Stdin)
+ defer reader.Close()
+
for {
- sc := bufio.NewScanner(reader)
- for sc.Scan() {
- command := sc.Text()
- Log.Debugf("Terminal Send command <%v>", command)
- IOsk.Emit(xaapiv1.TerminalInEvent, command+"\n")
+ rdfs.Zero()
+ rdfs.Set(reader.(exposeFd).Fd())
+ err := goselect.Select(1, rdfs, nil, nil, 50*time.Millisecond)
+ if err != nil {
+ terminal.Restore(int(os.Stdin.Fd()), oldState)
+ exitChan <- exitResult{err, 3}
+ return
}
- if sc.Err() != nil {
- exitChan <- exitResult{sc.Err(), 3}
- }
-
- // CTRL-D exited scanner, so send it explicitly
- IOsk.Emit(xaapiv1.TerminalInEvent, "\x04\n")
- time.Sleep(time.Millisecond * 100)
-
- if paranoia--; paranoia <= 0 {
- msg := "Abnormal loop detected on stdin"
- Log.Errorf("Abnormal loop detected on stdin")
+ if rdfs.IsSet(reader.(exposeFd).Fd()) {
+ size, err := reader.Read(buff)
+
+ if err != nil {
+ Log.Debugf("Read error %v; err %v", size, err)
+ if err == io.EOF {
+ // CTRL-D exited scanner, so send it explicitly
+ err := IOSkClient.Emit(xaapiv1.TerminalInEvent, "\x04\n")
+
+ if err != nil {
+ terminal.Restore(int(os.Stdin.Fd()), oldState)
+ exitChan <- exitResult{err, 4}
+ return
+ }
+ time.Sleep(time.Millisecond * 100)
+ continue
+ } else {
+ terminal.Restore(int(os.Stdin.Fd()), oldState)
+ exitChan <- exitResult{err, 5}
+ return
+ }
+ }
- // Send signal to gently exit terminal session
- TerminalSendSignal(tgt, term, syscall.SIGTERM)
+ if size <= 0 {
+ continue
+ }
- exitChan <- exitResult{fmt.Errorf(msg), int(syscall.ELOOP)}
+ data := buff[:size]
+ LogSillyf("Terminal Send data <%v> (%s)", data, data)
+ err = IOSkClient.Emit(xaapiv1.TerminalInEvent, data)
+ if err != nil {
+ terminal.Restore(int(os.Stdin.Fd()), oldState)
+ exitChan <- exitResult{err, 6}
+ return
+ }
}
}
}()
if IsWinResizeSignal(sig) {
TerminalResize(tgt, term)
} else if IsInterruptSignal(sig) {
- IOsk.Emit(xaapiv1.TerminalInEvent, "\x03\n")
+ IOSkClient.Emit(xaapiv1.TerminalInEvent, "\x03\n")
} else {
TerminalSendSignal(tgt, term, sig)
}
return cli.NewExitError(err.Error(), 1)
}
+ // Send init size
+ TerminalResize(tgt, term)
+
// Wait exit - blocking
select {
+ case res := <-IOSkClient.ServerDiscoChan:
+ Log.Debugf("XDS Server disconnected %v", res)
+ return cli.NewExitError(res.error, res.code)
+
case res := <-exitChan:
errStr := ""
if res.code == 0 {
if err != nil {
Log.Errorf("Error cannot get terminal size: %v", err)
}
- Log.Debugf("Terminal resizing rows %v, cols %v", row, col)
+
+ LogSillyf("Terminal resizing rows %v, cols %v", row, col)
sz := xaapiv1.TerminalResizeArgs{Rows: uint16(row), Cols: uint16(col)}
url := XdsServerComputeURL("/targets/" + tgt.ID + "/terminals/" + term.ID + "/resize")
if err := HTTPCli.Post(url, &sz, nil); err != nil {
// GetTargetAndTerminalIDs Retrieve Target and Terminal definition from IDs
func GetTargetAndTerminalIDs(ctx *cli.Context, useFirstFree bool) (*xaapiv1.TargetConfig, *xaapiv1.TerminalConfig, error) {
- idArg := ctx.String("id")
- tidArg := GetIDName(ctx, "termId")
- if tidArg == "" {
- tidArg = GetIDName(ctx, "tid")
- }
- if idArg == "" && tidArg == "" {
- return nil, nil, fmt.Errorf("id or termId argument must be set")
- }
-
tgts := []xaapiv1.TargetConfig{}
if err := TargetsListGet(&tgts); err != nil {
return nil, nil, err
}
- matching := 0
- ti := 0
- tj := 0
- for ii, tt := range tgts {
- for jj, ttm := range tt.Terms {
- if idArg == "" && compareID(ttm.ID, tidArg) {
- ti = ii
- tj = jj
- matching++
- }
- if idArg != "" && compareID(tt.ID, idArg) && compareID(ttm.ID, tidArg) {
- ti = ii
- tj = jj
- matching++
+ idArg := ctx.String("id")
+ tidArg := ctx.String("termId")
+ if tidArg == "" {
+ tidArg = ctx.String("tid")
+ }
+ if idArg != "" || tidArg != "" {
+ matching := 0
+ ti := 0
+ tj := 0
+ for ii, tt := range tgts {
+ for jj, ttm := range tt.Terms {
+ if idArg == "" && compareID(ttm.ID, tidArg) {
+ ti = ii
+ tj = jj
+ matching++
+ }
+ if idArg != "" && compareID(tt.ID, idArg) && compareID(ttm.ID, tidArg) {
+ ti = ii
+ tj = jj
+ matching++
+ }
}
}
- }
- if matching > 1 {
- return nil, nil, fmt.Errorf("Multiple IDs found, please set -id and -tid with full ID notation")
- } else if matching == 1 {
- return &tgts[ti], &tgts[ti].Terms[tj], nil
+ if matching > 1 {
+ return nil, nil, fmt.Errorf("Multiple IDs found, please set -id and -tid with full ID notation")
+ } else if matching == 1 {
+ return &tgts[ti], &tgts[ti].Terms[tj], nil
+ }
}
// Allow to create a new terminal when only target id is set
idArg = GetIDName(ctx, "id")
- if idArg != "" {
- for _, tt := range tgts {
- if compareID(tt.ID, idArg) {
- return &tt, nil, nil
+ if idArg == "" {
+ return nil, nil, fmt.Errorf("id or termId argument must be set")
+ }
+
+ for _, tt := range tgts {
+ if compareID(tt.ID, idArg) {
+ if useFirstFree {
+ for _, ttm := range tt.Terms {
+ if ttm.Type == xaapiv1.TypeTermSSH &&
+ (ttm.Status == xaapiv1.StatusTermEnable || ttm.Status == xaapiv1.StatusTermClose) {
+ return &tt, &ttm, nil
+ }
+ }
}
+ return &tt, nil, nil
}
}