package exec import ( "context" "io" "os/exec" "syscall" "time" ) const ( DefaultRunCmdTimeout = time.Second * 3 ) // RunCmd executes the given command and arguments and stdin and ensures the // command takes no longer than the timeout before the command is terminated. func RunCmd(timeout time.Duration, env []string, command string, stdin io.Reader, args ...string) (string, error) { var ( ctx context.Context cancel context.CancelFunc ) if timeout > 0 { ctx, cancel = context.WithTimeout(context.Background(), timeout) } else { ctx, cancel = context.WithCancel(context.Background()) } defer cancel() cmd := exec.CommandContext(ctx, command, args...) cmd.Env = append(cmd.Env, env...) cmd.Stdin = stdin out, err := cmd.CombinedOutput() if err != nil { if exitError, ok := err.(*exec.ExitError); ok { if ws, ok := exitError.Sys().(syscall.WaitStatus); ok && ws.Signal() == syscall.SIGKILL { err = &ErrCommandKilled{Err: err, Signal: ws.Signal()} } else { err = &ErrCommandFailed{Err: err, Status: exitError.ExitCode()} } } return string(out), err } return string(out), nil }