func newExecShell(command []string, tty bool) (engines.Shell, error) { if len(command) == 0 { command = []string{defaultShell} } s := &execShell{ cmd: exec.Command(command[0], command[1:]...), } // Start is wrapped in pty, if shell is supposed to emulate a TTY var err error if tty && pty.Supported { s.pty, err = pty.Start(s.cmd) if err != nil { // if there was a start error we set empty streams s.stdin = ioext.WriteNopCloser(ioutil.Discard) s.stdout = ioutil.NopCloser(bytes.NewReader(nil)) s.stderr = ioutil.NopCloser(bytes.NewReader(nil)) } else { s.stdin = s.pty s.stdout = s.pty s.stderr = ioutil.NopCloser(bytes.NewReader(nil)) } } else { s.cmd.Stdin, s.stdin = io.Pipe() s.stdout, s.cmd.Stdout = io.Pipe() s.stderr, s.cmd.Stderr = io.Pipe() err = s.cmd.Start() } // if there was an error starting, then we just resolve as is... Hence, it'll // be empty stdio and false result. if err != nil { s.resolve.Do(func() { s.stdin.Close() s.stdout.Close() s.stderr.Close() s.result = false s.abortErr = engines.ErrShellTerminated }) } else { // otherwise wait for the result, and resolve when shell terminates go s.waitForResult() } return s, nil }
// 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 }