Set "exec" command from client side.
[src/xds/xds-gdb.git] / gdb-xds.go
1 package main
2
3 import (
4         "encoding/json"
5         "fmt"
6         "net/http"
7         "os"
8         "strings"
9         "syscall"
10
11         "github.com/Sirupsen/logrus"
12         common "github.com/iotbzh/xds-common/golib"
13         "github.com/iotbzh/xds-server/lib/apiv1"
14         "github.com/iotbzh/xds-server/lib/crosssdk"
15         "github.com/iotbzh/xds-server/lib/xdsconfig"
16         sio_client "github.com/zhouhui8915/go-socket.io-client"
17 )
18
19 // GdbXds -
20 type GdbXds struct {
21         log     *logrus.Logger
22         ccmd    string
23         aargs   []string
24         eenv    []string
25         uri     string
26         prjID   string
27         sdkID   string
28         rPath   string
29         listPrj bool
30         cmdID   string
31
32         httpCli *common.HTTPClient
33         ioSock  *sio_client.Client
34
35         // callbacks
36         cbOnError      func(error)
37         cbOnDisconnect func(error)
38         cbRead         func(timestamp, stdout, stderr string)
39         cbInferiorRead func(timestamp, stdout, stderr string)
40         cbOnExit       func(code int, err error)
41 }
42
43 // NewGdbXds creates a new instance of GdbXds
44 func NewGdbXds(log *logrus.Logger, args []string, env []string) *GdbXds {
45         return &GdbXds{
46                 log:     log,
47                 ccmd:    "exec $GDB", // var set by environment-setup-xxx script
48                 aargs:   args,
49                 eenv:    env,
50                 httpCli: nil,
51                 ioSock:  nil,
52         }
53 }
54
55 // SetConfig set additional config fields
56 func (g *GdbXds) SetConfig(name string, value interface{}) error {
57         switch name {
58         case "uri":
59                 g.uri = value.(string)
60         case "prjID":
61                 g.prjID = value.(string)
62         case "sdkID":
63                 g.sdkID = value.(string)
64         case "rPath":
65                 g.rPath = value.(string)
66         case "listProject":
67                 g.listPrj = value.(bool)
68         default:
69                 return fmt.Errorf("Unknown %s field", name)
70         }
71         return nil
72 }
73
74 // Init initializes gdb XDS
75 func (g *GdbXds) Init() (int, error) {
76
77         // Define HTTP and WS url
78         baseURL := g.uri
79         if !strings.HasPrefix(g.uri, "http://") {
80                 baseURL = "http://" + g.uri
81         }
82
83         // Create HTTP client
84         g.log.Infoln("Connect HTTP client on ", baseURL)
85         conf := common.HTTPClientConfig{
86                 URLPrefix:           "/api/v1",
87                 HeaderClientKeyName: "XDS-SID",
88                 CsrfDisable:         true,
89         }
90         c, err := common.HTTPNewClient(baseURL, conf)
91         if err != nil {
92                 return int(syscallEBADE), err
93         }
94         g.httpCli = c
95
96         // First call to check that xds-server is alive
97         var data []byte
98         if err := c.HTTPGet("/folders", &data); err != nil {
99                 return int(syscallEBADE), err
100         }
101         g.log.Infof("Result of /folders: %v", string(data[:]))
102
103         // Check mandatory args
104         if g.prjID == "" || g.listPrj {
105                 return getProjectsList(c, g.log, data)
106         }
107
108         // Create io Websocket client
109         g.log.Infoln("Connecting IO.socket client on ", baseURL)
110
111         opts := &sio_client.Options{
112                 Transport: "websocket",
113                 Header:    make(map[string][]string),
114         }
115         opts.Header["XDS-SID"] = []string{c.GetClientID()}
116
117         iosk, err := sio_client.NewClient(baseURL, opts)
118         if err != nil {
119                 e := fmt.Sprintf("IO.socket connection error: " + err.Error())
120                 return int(syscall.ECONNABORTED), fmt.Errorf(e)
121         }
122         g.ioSock = iosk
123
124         iosk.On("error", func(err error) {
125                 if g.cbOnError != nil {
126                         g.cbOnError(err)
127                 }
128         })
129
130         iosk.On("disconnection", func(err error) {
131                 if g.cbOnDisconnect != nil {
132                         g.cbOnDisconnect(err)
133                 }
134         })
135
136         iosk.On(apiv1.ExecOutEvent, func(ev apiv1.ExecOutMsg) {
137                 if g.cbRead != nil {
138                         g.cbRead(ev.Timestamp, ev.Stdout, ev.Stderr)
139                 }
140         })
141
142         iosk.On(apiv1.ExecInferiorOutEvent, func(ev apiv1.ExecOutMsg) {
143                 if g.cbInferiorRead != nil {
144                         g.cbInferiorRead(ev.Timestamp, ev.Stdout, ev.Stderr)
145                 }
146         })
147
148         iosk.On(apiv1.ExecExitEvent, func(ev apiv1.ExecExitMsg) {
149                 if g.cbOnExit != nil {
150                         g.cbOnExit(ev.Code, ev.Error)
151                 }
152         })
153
154         return 0, nil
155 }
156
157 func (g *GdbXds) Close() error {
158         g.cbOnDisconnect = nil
159         g.cbOnError = nil
160         g.cbOnExit = nil
161         g.cbRead = nil
162         g.cbInferiorRead = nil
163
164         return nil
165 }
166
167 // Start sends a request to start remotely gdb within xds-server
168 func (g *GdbXds) Start(inferiorTTY bool) (int, error) {
169         var body []byte
170         var err error
171
172         // Enable workaround about inferior output with gdbserver connection
173         // except if XDS_GDBSERVER_OUTPUT_NOFIX is defined
174         _, gdbserverNoFix := os.LookupEnv("XDS_GDBSERVER_OUTPUT_NOFIX")
175
176         args := apiv1.ExecArgs{
177                 ID:              g.prjID,
178                 SdkID:           g.sdkID,
179                 Cmd:             g.ccmd,
180                 Args:            g.aargs,
181                 Env:             g.eenv,
182                 RPath:           g.rPath,
183                 TTY:             inferiorTTY,
184                 TTYGdbserverFix: !gdbserverNoFix,
185                 CmdTimeout:      -1, // no timeout, end when stdin close or command exited normally
186         }
187         body, err = json.Marshal(args)
188         if err != nil {
189                 return int(syscallEBADE), err
190         }
191
192         g.log.Infof("POST %s/exec %v", g.uri, string(body))
193         var res *http.Response
194         var found bool
195         res, err = g.httpCli.HTTPPostWithRes("/exec", string(body))
196         if err != nil {
197                 return int(syscall.EAGAIN), err
198         }
199         dRes := make(map[string]interface{})
200         json.Unmarshal(g.httpCli.ResponseToBArray(res), &dRes)
201         if _, found = dRes["cmdID"]; !found {
202                 return int(syscallEBADE), err
203         }
204         g.cmdID = dRes["cmdID"].(string)
205
206         return 0, nil
207 }
208
209 // Cmd returns the command name
210 func (g *GdbXds) Cmd() string {
211         return g.ccmd
212 }
213
214 // Args returns the list of arguments
215 func (g *GdbXds) Args() []string {
216         return g.aargs
217 }
218
219 // Env returns the list of environment variables
220 func (g *GdbXds) Env() []string {
221         return g.eenv
222 }
223
224 // OnError is called on a WebSocket error
225 func (g *GdbXds) OnError(f func(error)) {
226         g.cbOnError = f
227 }
228
229 // OnDisconnect is called when WebSocket disconnection
230 func (g *GdbXds) OnDisconnect(f func(error)) {
231         g.cbOnDisconnect = f
232 }
233
234 // OnExit calls when exit event is received
235 func (g *GdbXds) OnExit(f func(code int, err error)) {
236         g.cbOnExit = f
237 }
238
239 // Read calls when a message/string event is received on stdout or stderr
240 func (g *GdbXds) Read(f func(timestamp, stdout, stderr string)) {
241         g.cbRead = f
242 }
243
244 // InferiorRead calls when a message/string event is received on stdout or stderr of the debugged program (IOW inferior)
245 func (g *GdbXds) InferiorRead(f func(timestamp, stdout, stderr string)) {
246         g.cbInferiorRead = f
247 }
248
249 // Write writes message/string into gdb stdin
250 func (g *GdbXds) Write(args ...interface{}) error {
251         return g.ioSock.Emit(apiv1.ExecInEvent, args...)
252 }
253
254 // SendSignal is used to send a signal to remote process/gdb
255 func (g *GdbXds) SendSignal(sig os.Signal) error {
256         var body []byte
257         body, err := json.Marshal(apiv1.ExecSignalArgs{
258                 CmdID:  g.cmdID,
259                 Signal: sig.String(),
260         })
261         if err != nil {
262                 g.log.Errorf(err.Error())
263         }
264         g.log.Debugf("POST /signal %s", string(body))
265         return g.httpCli.HTTPPost("/signal", string(body))
266 }
267
268 //***** Private functions *****
269
270 func getProjectsList(c *common.HTTPClient, log *logrus.Logger, prjData []byte) (int, error) {
271
272         folders := xdsconfig.FoldersConfig{}
273         errMar := json.Unmarshal(prjData, &folders)
274         msg := ""
275         if errMar == nil {
276                 msg += "List of existing projects (use: export XDS_PROJECT_ID=<< ID >>): \n"
277                 msg += "  ID\t\t\t\t | Label"
278                 for _, f := range folders {
279                         msg += fmt.Sprintf("\n  %s\t | %s", f.ID, f.Label)
280                         if f.DefaultSdk != "" {
281                                 msg += fmt.Sprintf("\t(default SDK: %s)", f.DefaultSdk)
282                         }
283                 }
284                 msg += "\n"
285         }
286
287         var data []byte
288         if err := c.HTTPGet("/sdks", &data); err != nil {
289                 return int(syscallEBADE), err
290         }
291         log.Infof("Result of /sdks: %v", string(data[:]))
292
293         sdks := []crosssdk.SDK{}
294         errMar = json.Unmarshal(data, &sdks)
295         if errMar == nil {
296                 msg += "\nList of installed cross SDKs (use: export XDS_SDK_ID=<< ID >>): \n"
297                 msg += "  ID\t\t\t\t\t | NAME\n"
298                 for _, s := range sdks {
299                         msg += fmt.Sprintf("  %s\t | %s\n", s.ID, s.Name)
300                 }
301         }
302
303         if len(folders) > 0 && len(sdks) > 0 {
304                 msg += fmt.Sprintf("\n")
305                 msg += fmt.Sprintf("For example: \n")
306                 msg += fmt.Sprintf("  XDS_PROJECT_ID=%q XDS_SDK_ID=%q  %s -x myGdbConf.ini\n",
307                         folders[0].ID, sdks[0].ID, AppName)
308         }
309
310         return 0, fmt.Errorf(msg)
311 }