X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?p=src%2Fxds%2Fxds-cli.git;a=blobdiff_plain;f=cmd-target.go;h=6140755edd9d6aa0cfe5be607b91803bc71bc14f;hp=688aef6e237de431632cc3d1b47e800ea040134a;hb=3b5e82b55433fd49cfe0cd0349756e0c2e9a9c12;hpb=2231e3eae86c5e3ae05e30f667d331f5875c7884 diff --git a/cmd-target.go b/cmd-target.go index 688aef6..6140755 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" ) @@ -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") + 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 + } } } }() @@ -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 { @@ -471,50 +507,58 @@ func TerminalSendSignal(tgt *xaapiv1.TargetConfig, term *xaapiv1.TerminalConfig, // 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 + 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 } }