diff options
author | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2018-03-09 17:33:18 +0100 |
---|---|---|
committer | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2018-03-09 17:33:18 +0100 |
commit | 00b5b83dcff4904aeb18760caa193fa3393241e0 (patch) | |
tree | 447d4d5249e659de8c0a062559bb58f739220322 /cmd-target.go | |
parent | 04040c928142db92d2ef2d4b43ad4701392e5ceb (diff) |
Fixed terminal output (support escape and control characters)
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
Diffstat (limited to 'cmd-target.go')
-rw-r--r-- | cmd-target.go | 120 |
1 files changed, 80 insertions, 40 deletions
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 } } |