Update .gitreview file
[src/xds/xds-cli.git] / cmd-target.go
index db97e91..6140755 100644 (file)
 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"
 )
 
@@ -166,7 +165,7 @@ func targetsList(ctx *cli.Context) error {
 }
 
 func targetsGet(ctx *cli.Context) error {
-       id := GetID(ctx)
+       id := GetID(ctx, "XDS_TARGET_ID")
        if id == "" {
                return cli.NewExitError("id parameter or option must be set", 1)
        }
@@ -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"
@@ -254,7 +254,7 @@ func targetsAdd(ctx *cli.Context) error {
 
 func targetsRemove(ctx *cli.Context) error {
        var res xaapiv1.TargetConfig
-       id := GetID(ctx)
+       id := GetID(ctx, "XDS_TARGET_ID")
        if id == "" {
                return cli.NewExitError("id parameter or option must be set", 1)
        }
@@ -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,8 +432,15 @@ func terminalOpen(ctx *cli.Context) error {
                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 {
@@ -452,7 +487,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 {
@@ -507,13 +543,21 @@ func GetTargetAndTerminalIDs(ctx *cli.Context, useFirstFree bool) (*xaapiv1.Targ
        }
 
        // Allow to create a new terminal when only target id is set
-       idArg = GetIDName(ctx, "id")
+       idArg = GetIDName(ctx, "id", "XDS_TARGET_ID")
        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
                }
        }