2 * Copyright (C) 2017-2018 "IoT.bzh"
3 * Author Sebastien Douheret <sebastien@iot.bzh>
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
29 common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git"
30 "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/eows"
31 "gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/lib/xsapiv1"
32 "github.com/gin-gonic/gin"
38 // ExecCmd executes remotely a command
39 func (s *APIService) execCmd(c *gin.Context) {
40 var gdbPty, gdbTty *os.File
42 var args xsapiv1.ExecArgs
43 if c.BindJSON(&args) != nil {
44 common.APIError(c, "Invalid arguments")
48 // TODO: add permission ?
50 // Retrieve session info
51 sess := s.sessions.Get(c)
53 common.APIError(c, "Unknown sessions")
58 common.APIError(c, "Websocket not established")
62 // Allow to pass id in url (/exec/:id) or as JSON argument
63 idArg := c.Param("id")
68 common.APIError(c, "Invalid id")
71 id, err := s.mfolders.ResolveID(idArg)
73 common.APIError(c, err.Error())
76 f := s.mfolders.Get(id)
78 common.APIError(c, "Unknown id")
82 prj := fld.GetConfig()
87 // Reset by default LD_LIBRARY_PATH
88 // With new AGL SDK, New environment-setup-*-agl-linux file checks if
89 // LD_LIBRARY_PATH is set or not and not empty LD_LIBRARY_PATH is considered
90 // as misconfigured and prevent to use SDK
91 if !args.LdLibPathNoReset {
92 cmd = append(cmd, "unset LD_LIBRARY_PATH; ")
95 // Setup env var regarding Sdk ID (used for example to setup cross toolchain)
96 if envCmd := s.sdks.GetEnvCmd(args.SdkID, prj.DefaultSdk); len(envCmd) > 0 {
97 cmd = append(cmd, envCmd...)
98 cmd = append(cmd, "&&")
100 // It's an error if no envcmd found while a sdkid has been provided
101 if args.SdkID != "" {
102 common.APIError(c, "Unknown sdkid")
107 cmd = append(cmd, "cd", "\""+fld.GetFullPath(args.RPath)+"\"")
108 // FIXME - add 'exec' prevents to use syntax:
109 // xds-exec -l debug -c xds-config.env -- "cd build && cmake .."
110 // but exec is mandatory to allow to pass correctly signals
111 // As workaround, exec is set for now on client side (eg. in xds-gdb)
112 //cmd = append(cmd, "&&", "exec", args.Cmd)
113 cmd = append(cmd, "&&", args.Cmd)
115 // Process command arguments
116 cmdArgs := make([]string, len(args.Args)+1)
118 // Copy and Translate path from client to server
119 for _, aa := range args.Args {
120 if strings.Contains(aa, prj.ClientPath) {
121 cmdArgs = append(cmdArgs, fld.ConvPathCli2Svr(aa))
123 cmdArgs = append(cmdArgs, aa)
127 // Allocate pts if tty if used
129 gdbPty, gdbTty, err = pty.Open()
131 common.APIError(c, err.Error())
135 s.Log.Debugf("Client command tty: %v %v\n", gdbTty.Name(), gdbTty.Name())
136 cmdArgs = append(cmdArgs, "--tty="+gdbTty.Name())
139 // Unique ID for each commands
140 if args.CmdID == "" {
141 args.CmdID = s.Config.ServerUID[:18] + "_" + strconv.Itoa(execCommandID)
145 // Create new execution over WS context
146 execWS := eows.New(strings.Join(cmd, " "), cmdArgs, sop, sess.ID, args.CmdID)
148 execWS.OutSplit = eows.SplitChar
150 // Append client project dir to environment
151 execWS.Env = append(args.Env, "CLIENT_PROJECT_DIR="+prj.ClientPath)
153 // Set command execution timeout
154 if args.CmdTimeout == 0 {
155 // 0 : default timeout
156 // TODO get default timeout from server-config.json file
157 execWS.CmdExecTimeout = 24 * 60 * 60 // 1 day
159 execWS.CmdExecTimeout = args.CmdTimeout
162 // Define callback for input (stdin)
163 execWS.InputEvent = xsapiv1.ExecInEvent
164 execWS.InputCB = func(e *eows.ExecOverWS, bStdin []byte) ([]byte, error) {
166 stdin := string(bStdin)
168 s.Log.Debugf("STDIN <<%v>>", strings.Replace(stdin, "\n", "\\n", -1))
171 if len(stdin) == 1 && stdin == "\x04" {
173 errMsg := fmt.Errorf("close stdin: %v", stdin)
174 return []byte{}, errMsg
179 prjID := (*data)["ID"].(string)
180 f := s.mfolders.Get(prjID)
182 s.Log.Errorf("InputCB: Cannot get folder ID %s", prjID)
184 // Translate paths from client to server
185 stdin = (*f).ConvPathCli2Svr(stdin)
187 return []byte(stdin), nil
190 // Define callback for output (stdout+stderr)
191 execWS.OutputCB = func(e *eows.ExecOverWS, bStdout, bStderr []byte) {
193 stdout := string(bStdout)
194 stderr := string(bStderr)
196 // IO socket can be nil when disconnected
197 so := s.sessions.IOSocketGet(e.Sid)
199 s.Log.Infof("%s not emitted: WS closed (sid:%s, CmdID:%s)", xsapiv1.ExecOutEvent, e.Sid, e.CmdID)
203 // Retrieve project ID and RootPath
205 prjID := (*data)["ID"].(string)
206 gdbServerTTY := (*data)["gdbServerTTY"].(string)
208 f := s.mfolders.Get(prjID)
210 s.Log.Errorf("OutputCB: Cannot get folder ID %s", prjID)
212 // Translate paths from server to client
213 stdout = (*f).ConvPathSvr2Cli(stdout)
214 stderr = (*f).ConvPathSvr2Cli(stderr)
217 s.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - prjID:%s", xsapiv1.ExecOutEvent, e.Sid[4:], e.CmdID, prjID)
219 s.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
222 s.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
225 // FIXME replace by .BroadcastTo a room
226 err := (*so).Emit(xsapiv1.ExecOutEvent, xsapiv1.ExecOutMsg{
228 Timestamp: time.Now().String(),
233 s.Log.Errorf("WS Emit : %v", err)
236 // XXX - Workaround due to gdbserver bug that doesn't redirect
237 // inferior output (https://bugs.eclipse.org/bugs/show_bug.cgi?id=437532#c13)
238 if gdbServerTTY == "workaround" && len(stdout) > 1 && stdout[0] == '&' {
240 // Extract and cleanup string like &"bla bla\n"
241 re := regexp.MustCompile("&\"(.*)\"")
242 rer := re.FindAllStringSubmatch(stdout, -1)
244 if rer != nil && len(rer) > 0 {
245 for _, o := range rer {
247 out = strings.Replace(o[1], "\\n", "\n", -1)
248 out = strings.Replace(out, "\\r", "\r", -1)
249 out = strings.Replace(out, "\\t", "\t", -1)
251 s.Log.Debugf("STDOUT INFERIOR: <<%v>>", out)
252 err := (*so).Emit(xsapiv1.ExecInferiorOutEvent, xsapiv1.ExecOutMsg{
254 Timestamp: time.Now().String(),
259 s.Log.Errorf("WS Emit : %v", err)
264 s.Log.Errorf("INFERIOR out parsing error: stdout=<%v>", stdout)
269 // Define callback for output
270 execWS.ExitCB = func(e *eows.ExecOverWS, code int, err error) {
271 s.Log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", e.CmdID, code, err)
273 defer LockXdsUpdateCounter(s.Context, false)
284 // IO socket can be nil when disconnected
285 so := s.sessions.IOSocketGet(e.Sid)
287 s.Log.Infof("%s not emitted - WS closed (id:%s)", xsapiv1.ExecExitEvent, e.CmdID)
291 // Retrieve project ID and RootPath
293 prjID := (*data)["ID"].(string)
294 exitImm := (*data)["ExitImmediate"].(bool)
296 // XXX - workaround to be sure that Syncthing detected all changes
297 if err := s.mfolders.ForceSync(prjID); err != nil {
298 s.Log.Errorf("Error while syncing folder %s: %v", prjID, err)
301 // Wait end of file sync
302 // FIXME pass as argument
304 for t := tmo; t > 0; t-- {
305 s.Log.Debugf("Wait file in-sync for %s (%d/%d)", prjID, t, tmo)
306 if sync, err := s.mfolders.IsFolderInSync(prjID); sync || err != nil {
308 s.Log.Errorf("ERROR IsFolderInSync (%s): %v", prjID, err)
312 time.Sleep(time.Second)
314 s.Log.Debugf("OK file are synchronized.")
317 // FIXME replace by .BroadcastTo a room
318 errSoEmit := (*so).Emit(xsapiv1.ExecExitEvent, xsapiv1.ExecExitMsg{
320 Timestamp: time.Now().String(),
324 if errSoEmit != nil {
325 s.Log.Errorf("WS Emit : %v", errSoEmit)
329 // User data (used within callbacks)
330 data := make(map[string]interface{})
332 data["ExitImmediate"] = args.ExitImmediate
333 if args.TTY && args.TTYGdbserverFix {
334 data["gdbServerTTY"] = "workaround"
336 data["gdbServerTTY"] = ""
338 execWS.UserData = &data
340 // Start command execution
341 s.Log.Infof("Execute [Cmd ID %s]: %v %v", execWS.CmdID, execWS.Cmd, execWS.Args)
343 LockXdsUpdateCounter(s.Context, true)
346 LockXdsUpdateCounter(s.Context, false)
347 common.APIError(c, err.Error())
351 c.JSON(http.StatusOK, xsapiv1.ExecResult{Status: "OK", CmdID: execWS.CmdID})
354 // ExecCmd executes remotely a command
355 func (s *APIService) execSignalCmd(c *gin.Context) {
356 var args xsapiv1.ExecSignalArgs
358 if c.BindJSON(&args) != nil {
359 common.APIError(c, "Invalid arguments")
363 s.Log.Debugf("Signal %s for command ID %s", args.Signal, args.CmdID)
365 e := eows.GetEows(args.CmdID)
367 common.APIError(c, "unknown cmdID")
371 err := e.Signal(args.Signal)
373 common.APIError(c, err.Error())
377 c.JSON(http.StatusOK, xsapiv1.ExecSigResult{Status: "OK", CmdID: args.CmdID})