summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastien Douheret <sebastien.douheret@iot.bzh>2018-03-09 17:33:18 +0100
committerSebastien Douheret <sebastien.douheret@iot.bzh>2018-03-09 17:33:18 +0100
commit00b5b83dcff4904aeb18760caa193fa3393241e0 (patch)
tree447d4d5249e659de8c0a062559bb58f739220322
parent04040c928142db92d2ef2d4b43ad4701392e5ceb (diff)
Fixed terminal output (support escape and control characters)
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
-rw-r--r--.vscode/settings.json6
-rw-r--r--cmd-exec.go8
-rw-r--r--cmd-sdks.go14
-rw-r--r--cmd-target.go120
-rw-r--r--glide.yaml7
-rw-r--r--iosocket-client.go80
-rw-r--r--main.go27
7 files changed, 195 insertions, 67 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json
index fc15f8b..9f7f5b2 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -39,6 +39,10 @@
"gerrit",
"EVTSDK",
"tgts",
- "sigs"
+ "sigs",
+ "rdfs",
+ "goselect",
+ "creack",
+ "Sillyf"
]
}
diff --git a/cmd-exec.go b/cmd-exec.go
index 3f1ee97..c988f95 100644
--- a/cmd-exec.go
+++ b/cmd-exec.go
@@ -78,7 +78,7 @@ func execCmd(ctx *cli.Context) error {
}
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}
})
@@ -96,15 +96,15 @@ func execCmd(ctx *cli.Context) error {
}
}
- IOsk.On(xaapiv1.ExecOutEvent, func(ev xaapiv1.ExecOutMsg) {
+ IOSkClient.On(xaapiv1.ExecOutEvent, func(ev xaapiv1.ExecOutMsg) {
outFunc(ev.Timestamp, ev.Stdout, ev.Stderr)
})
- IOsk.On(xaapiv1.ExecExitEvent, func(ev xaapiv1.ExecExitMsg) {
+ IOSkClient.On(xaapiv1.ExecExitEvent, func(ev xaapiv1.ExecExitMsg) {
exitChan <- exitResult{ev.Error, ev.Code}
})
- IOsk.On(xaapiv1.EVTProjectChange, func(ev xaapiv1.EventMsg) {
+ IOSkClient.On(xaapiv1.EVTProjectChange, func(ev xaapiv1.EventMsg) {
prj, _ := ev.DecodeProjectConfig()
Log.Infof("Event %v (%v): %v", ev.Type, ev.Time, prj)
})
diff --git a/cmd-sdks.go b/cmd-sdks.go
index 3fc596f..eeebffa 100644
--- a/cmd-sdks.go
+++ b/cmd-sdks.go
@@ -242,7 +242,7 @@ func sdksInstall(ctx *cli.Context) error {
}
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)
errMsg := ""
if err != nil {
@@ -251,7 +251,7 @@ func sdksInstall(ctx *cli.Context) error {
exitChan <- exitResult{errMsg, 2}
})
- IOsk.On(xaapiv1.EVTSDKManagement, func(ev xaapiv1.EventMsg) {
+ IOSkClient.On(xaapiv1.EVTSDKManagement, func(ev xaapiv1.EventMsg) {
sdkEvt, _ := ev.DecodeSDKMgtMsg()
if sdkEvt.Action != xaapiv1.SdkMgtActionInstall {
@@ -259,11 +259,11 @@ func sdksInstall(ctx *cli.Context) error {
return
}
- if !shortOut && sdkEvt.Stdout != "" {
- fmt.Printf("%s", sdkEvt.Stdout)
+ if !shortOut && len(sdkEvt.Stdout) > 0 {
+ os.Stdout.Write([]byte(sdkEvt.Stdout))
}
- if !shortOut && sdkEvt.Stderr != "" {
- fmt.Fprintf(os.Stderr, "%s", sdkEvt.Stderr)
+ if !shortOut && len(sdkEvt.Stderr) > 0 {
+ os.Stderr.Write([]byte(sdkEvt.Stderr))
}
if sdkEvt.Exited {
@@ -271,7 +271,7 @@ func sdksInstall(ctx *cli.Context) error {
}
})
- IOsk.On(xaapiv1.EVTSDKStateChange, func(ev xaapiv1.EventMsg) {
+ IOSkClient.On(xaapiv1.EVTSDKStateChange, func(ev xaapiv1.EventMsg) {
sdk, _ := ev.DecodeSDKEvent()
Log.Debugf("EVTSDKStateChange: %v", sdk)
})
diff --git a/cmd-target.go b/cmd-target.go
index db97e91..cd96ed4 100644
--- a/cmd-target.go
+++ b/cmd-target.go
@@ -19,18 +19,17 @@
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"
)
@@ -198,6 +197,7 @@ func _displayTargets(tgts []xaapiv1.TargetConfig, verbose bool) {
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"
@@ -324,60 +324,88 @@ func terminalOpen(ctx *cli.Context) error {
}
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")
- }
- if sc.Err() != nil {
- exitChan <- exitResult{sc.Err(), 3}
+ 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 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
+ }
+ }
- // 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")
-
- // 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
+ }
}
}
}()
@@ -388,7 +416,7 @@ func terminalOpen(ctx *cli.Context) error {
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)
}
@@ -404,6 +432,9 @@ func terminalOpen(ctx *cli.Context) error {
return cli.NewExitError(err.Error(), 1)
}
+ // Send init size
+ TerminalResize(tgt, term)
+
// Wait exit - blocking
select {
case res := <-exitChan:
@@ -452,7 +483,8 @@ func TerminalResize(tgt *xaapiv1.TargetConfig, term *xaapiv1.TerminalConfig) {
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 {
@@ -514,6 +546,14 @@ func GetTargetAndTerminalIDs(ctx *cli.Context, useFirstFree bool) (*xaapiv1.Targ
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
}
}
diff --git a/glide.yaml b/glide.yaml
index a0e7826..faa57f2 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -16,7 +16,7 @@ import:
subpackages:
- lib/xaapiv1
- package: gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git
- version: ~0.2.0
+ version: ~0.3.0
subpackages:
- golib/common
- package: github.com/joho/godotenv
@@ -25,4 +25,7 @@ import:
- cmd/godotenv
- package: github.com/franciscocpg/reflectme
version: ^0.1.9
-- package: github.com/golang/crypto/ssh/terminal
+- package: github.com/golang/crypto
+ subpackages:
+ - ssh/terminal
+- package: github.com/creack/goselect
diff --git a/iosocket-client.go b/iosocket-client.go
new file mode 100644
index 0000000..9115b10
--- /dev/null
+++ b/iosocket-client.go
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 "IoT.bzh"
+ * Author Sebastien Douheret <sebastien@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package main
+
+import (
+ "fmt"
+ "sync"
+
+ socketio_client "github.com/sebd71/go-socket.io-client"
+)
+
+// IOSockClient .
+type IOSockClient struct {
+ URL string
+ Conn *socketio_client.Client
+ Options *socketio_client.Options
+ EmitMutex *sync.Mutex
+ Connected bool
+ EscapeKeys []byte
+}
+
+// NewIoSocketClient Create a new IOSockClient
+func NewIoSocketClient(url, clientID string) (*IOSockClient, error) {
+
+ var err error
+
+ sCli := &IOSockClient{
+ URL: url,
+ EmitMutex: &sync.Mutex{},
+ Options: &socketio_client.Options{
+ Transport: "websocket",
+ Header: make(map[string][]string),
+ },
+ }
+ sCli.Options.Header["XDS-AGENT-SID"] = []string{clientID}
+
+ sCli.Conn, err = socketio_client.NewClient(url, sCli.Options)
+ if err != nil {
+ return nil, fmt.Errorf("IO.socket connection error: " + err.Error())
+ }
+
+ sCli.Conn.On("connection", func() {
+ sCli.Connected = true
+ })
+
+ sCli.Conn.On("disconnection", func(err error) {
+ Log.Debugf("WS disconnection event with err: %v\n", err)
+ sCli.Connected = false
+ })
+
+ return sCli, nil
+}
+
+// On record a callback on a specific message
+func (c *IOSockClient) On(message string, f interface{}) (err error) {
+ return c.Conn.On(message, f)
+}
+
+// Emit send a message
+func (c *IOSockClient) Emit(message string, args ...interface{}) (err error) {
+ c.EmitMutex.Lock()
+ defer c.EmitMutex.Unlock()
+ return c.Conn.Emit(message, args...)
+}
diff --git a/main.go b/main.go
index a6f8104..2dfd056 100644
--- a/main.go
+++ b/main.go
@@ -33,7 +33,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/joho/godotenv"
- socketio_client "github.com/sebd71/go-socket.io-client"
"github.com/urfave/cli"
)
@@ -71,8 +70,8 @@ var EnvConfFileMap map[string]string
// HTTPCli Global variable that hold HTTP Client
var HTTPCli *common.HTTPClient
-// IOsk Global variable that hold SocketIo client
-var IOsk *socketio_client.Client
+// IOSkClient Global variable that hold SocketIo client
+var IOSkClient *IOSockClient
// exitError exists this program with the specified error
func exitError(code int, f string, a ...interface{}) {
@@ -96,6 +95,14 @@ func earlyDisplay() {
earlyDebug = []string{}
}
+// LogSillyf Logging helper used for silly logging (printed on log.debug)
+func LogSillyf(format string, args ...interface{}) {
+ sillyVal, sillyLog := os.LookupEnv("XDS_LOG_SILLY")
+ if sillyLog && sillyVal == "1" {
+ Log.Debugf("SILLY: "+format, args...)
+ }
+}
+
// main
func main() {
@@ -382,23 +389,17 @@ func XdsConnInit(ctx *cli.Context) error {
// Create io Websocket client
Log.Debugln("Connecting IO.socket client on ", agentURL)
- opts := &socketio_client.Options{
- Transport: "websocket",
- Header: make(map[string][]string),
- }
- opts.Header["XDS-AGENT-SID"] = []string{HTTPCli.GetClientID()}
-
- IOsk, err = socketio_client.NewClient(agentURL, opts)
+ IOSkClient, err = NewIoSocketClient(agentURL, HTTPCli.GetClientID())
if err != nil {
- return cli.NewExitError("IO.socket connection error: "+err.Error(), 1)
+ return cli.NewExitError(err.Error(), 1)
}
- IOsk.On("error", func(err error) {
+ IOSkClient.On("error", func(err error) {
fmt.Println("ERROR Websocket: ", err.Error())
})
ctx.App.Metadata["httpCli"] = HTTPCli
- ctx.App.Metadata["ioskCli"] = IOsk
+ ctx.App.Metadata["ioskCli"] = IOSkClient
// Display version in logs (debug helpers)
ver := xaapiv1.XDSVersion{}