diff --git a/cmdext/cmdrunner.go b/cmdext/cmdrunner.go new file mode 100644 index 0000000..c00ff6d --- /dev/null +++ b/cmdext/cmdrunner.go @@ -0,0 +1,142 @@ +package cmdext + +import ( + "bufio" + "os/exec" + "time" +) + +type CommandResult struct { + StdOut string + StdErr string + StdCombined string + ExitCode int + CommandTimedOut bool +} + +func RunCommand(program string, args []string, timeout *time.Duration) (CommandResult, error) { + + cmd := exec.Command(program, args...) + + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + return CommandResult{}, err + } + + stderrPipe, err := cmd.StderrPipe() + if err != nil { + return CommandResult{}, err + } + + err = cmd.Start() + if err != nil { + return CommandResult{}, err + } + + errch := make(chan error, 1) + go func() { errch <- cmd.Wait() }() + + combch := make(chan string, 32) + stopCombch := make(chan bool) + + stdout := "" + go func() { + scanner := bufio.NewScanner(stdoutPipe) + for scanner.Scan() { + txt := scanner.Text() + stdout += txt + combch <- txt + } + }() + + stderr := "" + go func() { + scanner := bufio.NewScanner(stderrPipe) + for scanner.Scan() { + txt := scanner.Text() + stderr += txt + combch <- txt + } + }() + + defer func() { + stopCombch <- true + }() + + stdcombined := "" + go func() { + for { + select { + case txt := <-combch: + stdcombined += txt + case <-stopCombch: + return + } + } + }() + + if timeout != nil { + + select { + + case <-time.After(*timeout): + _ = cmd.Process.Kill() + return CommandResult{ + StdOut: stdout, + StdErr: stderr, + StdCombined: stdcombined, + ExitCode: -1, + CommandTimedOut: true, + }, nil + + case err := <-errch: + if exiterr, ok := err.(*exec.ExitError); ok { + return CommandResult{ + StdOut: stdout, + StdErr: stderr, + StdCombined: stdcombined, + ExitCode: exiterr.ExitCode(), + CommandTimedOut: false, + }, nil + } else if err != nil { + return CommandResult{}, err + } else { + return CommandResult{ + StdOut: stdout, + StdErr: stderr, + StdCombined: stdcombined, + ExitCode: 0, + CommandTimedOut: false, + }, nil + } + + } + + } else { + + select { + + case err := <-errch: + if exiterr, ok := err.(*exec.ExitError); ok { + return CommandResult{ + StdOut: stdout, + StdErr: stderr, + StdCombined: stdcombined, + ExitCode: exiterr.ExitCode(), + CommandTimedOut: false, + }, nil + } else if err != nil { + return CommandResult{}, err + } else { + return CommandResult{ + StdOut: stdout, + StdErr: stderr, + StdCombined: stdcombined, + ExitCode: 0, + CommandTimedOut: false, + }, nil + } + + } + } +}