func pipePty(cmd *exec.Cmd, handler *interactive.ShellHandler) error { // Start the shell as TTY f, err := pty.Start(cmd) if err == nil { // Connect pipes (close stderr as tty only has two streams) go ioext.CopyAndClose(f, handler.StdinPipe()) go ioext.CopyAndClose(handler.StdoutPipe(), f) go handler.StderrPipe().Close() // Start communication handler.Communicate(func(cols, rows uint16) error { return setTTYSize(f, cols, rows) }, func() error { if cmd.Process != nil { return cmd.Process.Kill() } return nil }) } // If pty wasn't supported for some reason we fall back to normal execution if err == pty.ErrUnsupported { return pipeCommand(cmd, handler) } if err != nil { handler.Communicate(nil, func() error { // If cmd.Start() failed, then we don't have a process, but we start // the communication flow anyways. if cmd.Process != nil { return cmd.Process.Kill() } return nil }) } return err }
func (cmd) Execute(arguments map[string]interface{}) bool { URL := arguments["<URL>"].(string) command := arguments["<command>"].([]string) tty := isatty.IsTerminal(os.Stdout.Fd()) // Parse URL u, err := url.Parse(URL) if err != nil { fmt.Println("Failed to parse URL, error: ", err) return false } qs := u.Query() // Set the command, if we have one if len(command) > 0 { qs["command"] = command } // Set tty=true if we're in a tty if tty { qs.Set("tty", "true") } // Update query string u.RawQuery = qs.Encode() // Connect to remove websocket ws, res, err := dialer.Dial(u.String(), nil) if err == websocket.ErrBadHandshake { fmt.Println("Failed to connect, status: ", res.StatusCode) return false } if err != nil { fmt.Println("Failed to connect, error: ", err) return false } // Create shell client shell := shellclient.New(ws) // Switch terminal to raw mode cleanup := func() {} if tty { cleanup = SetupRawTerminal(shell.SetSize) } // Connect pipes go ioext.CopyAndClose(shell.StdinPipe(), os.Stdin) go io.Copy(os.Stdout, shell.StdoutPipe()) go io.Copy(os.Stderr, shell.StderrPipe()) // Wait for shell to be done success, _ := shell.Wait() // If we were in a tty we let's restore state cleanup() return success }
func (s *ShellServer) handleShell(ws *websocket.Conn, shell engines.Shell) { done := make(chan struct{}) // Create a shell handler s.updateRefCount(1) handler := NewShellHandler(ws, s.log.WithField("shell-instance-id", s.nextID())) // Connect pipes go ioext.CopyAndClose(shell.StdinPipe(), handler.StdinPipe()) go ioext.CopyAndClose(handler.StdoutPipe(), shell.StdoutPipe()) go ioext.CopyAndClose(handler.StderrPipe(), shell.StderrPipe()) // Start streaming handler.Communicate(shell.SetSize, shell.Abort) // Wait for call to abort all shells go func() { select { case <-s.done: shell.Abort() case <-done: } }() // Wait for the shell to terminate success, _ := shell.Wait() handler.Terminated(success) s.updateRefCount(-1) // Close done so we stop waiting for abort on all shells select { case <-done: default: close(done) } }
// StartProcess starts a new process with given arguments, environment variables, // and current working folder, running as given user. // // Returns an human readable error explaining why the sub-process couldn't start // if not successful. func StartProcess(options ProcessOptions) (*Process, error) { // Default arguments to system shell if len(options.Arguments) == 0 { options.Arguments = []string{defaultShell} } // If WorkingFolder isn't set find home folder of options.Owner (if set) // or current user if options.WorkingFolder == "" { if options.Owner != nil { options.WorkingFolder = options.Owner.homeFolder } else { u, err := user.Current() if err != nil { panic(fmt.Sprintf("Failed to lookup current user, error: %s", err)) } options.WorkingFolder = u.HomeDir } } // Default stdout to os.DevNul if options.Stdout == nil { options.Stdout = ioext.WriteNopCloser(ioutil.Discard) } // Default stderr to stdout if options.Stderr == nil { options.Stderr = options.Stdout } // Create process and command p := &Process{} p.cmd = exec.Command(options.Arguments[0], options.Arguments[1:]...) p.cmd.Env = formatEnv(options.Environment) p.cmd.Dir = options.WorkingFolder // Set owner for the process if options.Owner != nil { p.cmd.SysProcAttr = &syscall.SysProcAttr{ Credential: &syscall.Credential{ Uid: options.Owner.uid, Gid: options.Owner.gid, }, } } // Start the process var err error if !options.TTY { p.cmd.Stdin = options.Stdin p.cmd.Stdout = options.Stdout p.cmd.Stderr = options.Stderr p.stdin = options.Stdin p.stdout = options.Stdout p.stderr = options.Stderr err = p.cmd.Start() } else { p.pty, err = pty.Start(p.cmd) if options.Stdin != nil { p.sockets.Add(1) go func() { io.Copy(p.pty, options.Stdin) p.sockets.Done() // Kill process when stdin ends (if running as TTY) p.Kill() }() } p.sockets.Add(1) go func() { ioext.CopyAndClose(options.Stdout, p.pty) p.sockets.Done() }() } if err != nil { return nil, fmt.Errorf("Unable to execute binary, error: %s", err) } // Go wait for result go p.waitForResult() return p, nil }