shell/run.go

346 lines
8.3 KiB
Go

package shell
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"strings"
"syscall"
"time"
"github.com/svent/go-nbreader"
"go.wit.com/log"
)
var msecDelay int = 20 // check every 20 milliseconds
// TODO: look at https://github.com/go-cmd/cmd/issues/20
// use go-cmd instead here?
// exiterr.Sys().(syscall.WaitStatus)
// run command and return it's output
func RunCapture(cmdline string) string {
test := New()
test.Exec(cmdline)
return Chomp(test.Buffer)
}
func RunWait(args []string) *OldShell {
test := New()
cmdline := strings.Join(args, " ")
test.Exec(cmdline)
return test
}
// var newfile *shell.File
func RunString(args string) bool {
// return false
parts := strings.Split(args, " ")
return Run(parts)
}
func Run(args []string) bool {
dir, err := os.Getwd()
if err != nil {
println("Failed to get current directory:", err)
return false
}
return RunPath(dir, args)
}
// run, but set the working path
func RunPath(path string, args []string) bool {
if len(args) == 0 {
log.Warn("command line was empty")
return false
}
if args[0] == "" {
log.Warn("command line was empty")
return false
}
thing := args[0]
parts := args[1:]
cmd := exec.Command(thing, parts...)
cmd.Dir = path
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Info("path =", path, "cmd =", strings.Join(args, " "))
if err := cmd.Run(); err != nil {
// Handle error if the command execution fails
log.Info("RunPath() failed")
// log.Info("cmd.Enviorn =", cmd.Environ())
out, outerr := cmd.Output()
log.Info("cmd.output =", out)
log.Info("cmd.output err=", outerr)
log.Info("path =", path)
log.Info("args =", args)
log.Info("err =", err.Error())
return false
}
return true
}
func (cmd *OldShell) Run(cmdline string) string {
cmd.InitProcess(cmdline)
if cmd.Error != nil {
return ""
}
cmd.Exec(cmdline)
return Chomp(cmd.Buffer)
}
func (cmd *OldShell) InitProcess(cmdline string) {
log.Log(RUN, "shell.InitProcess() START "+cmdline)
cmd.Cmdline = Chomp(cmdline) // this is like 'chomp' in perl
cmdArgs := strings.Fields(cmd.Cmdline)
if len(cmdArgs) == 0 {
cmd.Error = fmt.Errorf("cmdline == ''")
cmd.Done = true
return
}
if cmdArgs[0] == "cd" {
if len(cmdArgs) > 1 {
log.Log(RUN, "os.Chdir()", cmd)
os.Chdir(cmdArgs[1])
}
handleError(nil, 0)
cmd.Done = true
return
}
cmd.Process = exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...)
}
func (cmd *OldShell) FileCreate(out string) {
var newfile File
var iof io.ReadCloser
if out == "STDOUT" {
iof, _ = cmd.Process.StdoutPipe()
} else {
iof, _ = cmd.Process.StderrPipe()
}
newfile.Fio = iof
newfile.Fbufio = bufio.NewReader(iof)
newfile.Fnbreader = nbreader.NewNBReader(newfile.Fbufio, 1024)
if out == "STDOUT" {
cmd.STDOUT = &newfile
} else {
cmd.STDERR = &newfile
}
}
// NOTE: this might cause problems:
// always remove the newlines at the end ?
func (cmd *OldShell) Exec(cmdline string) {
log.Log(RUN, "shell.Run() START "+cmdline)
cmd.InitProcess(cmdline)
if cmd.Error != nil {
return
}
cmd.FileCreate("STDOUT")
cmd.FileCreate("STDERR")
cmd.Process.Start()
// TODO; 'goroutine' both of these
// and make your own wait that will make sure
// the process is then done and run process.Wait()
go cmd.Capture(cmd.STDERR)
cmd.Capture(cmd.STDOUT)
// wait until the process exists
// https://golang.org/pkg/os/exec/#Cmd.Wait
// What should happen here, before calling Wait()
// is checks to make sure the READERS() on STDOUT and STDERR are done
err := cmd.Process.Wait()
// time.Sleep(2 * time.Second) // putting this here doesn't help STDOUT flush()
if err != nil {
cmd.Fail = true
cmd.Error = err
log.Log(RUN, "process.Wait() END err =", err.Error())
} else {
log.Log(RUN, "process.Wait() END")
}
return
}
// nonblocking read until file errors
func (cmd *OldShell) Capture(f *File) {
log.Log(RUN, "nbrREADER() START")
if cmd.Buffer == nil {
cmd.Buffer = new(bytes.Buffer)
}
if cmd.Buffer == nil {
f.Dead = false
cmd.Error = fmt.Errorf("could not make buffer")
log.Error(cmd.Error, "f.Buffer == nil")
log.Error(cmd.Error, "SHOULD DIE HERE")
cmd.Done = true
}
f.Dead = false
// loop that keeps trying to read from f
for f.Dead == false {
time.Sleep(time.Duration(msecDelay) * time.Millisecond) // only check the buffer 500 times a second
// set to false so it keeps retrying reads
f.Empty = false
// tight loop that reads 1024 bytes at a time until buffer is empty
// 1024 is set in f.BufferSize
for f.Empty == false {
f.Empty = cmd.ReadToBuffer(f)
}
}
}
// returns true if filehandle buffer is empty
func (cmd *OldShell) ReadToBuffer(f *File) bool {
log.Log(RUN, "ReadToBuffer() START")
nbr := f.Fnbreader
oneByte := make([]byte, 1024)
if nbr == nil {
// log.Debugln("ReadToBuffer() ERROR nbr is nil")
f.Dead = true
return true
}
count, err := nbr.Read(oneByte)
f.TotalCount += count
if err != nil {
// log.Debugln("ReadToBuffer() file has closed with", err)
// log.Debugln("ReadToBuffer() count = ", count, "err = ", err)
f.Dead = true
return true
}
if count == 0 {
// log.Debugln("ReadToBuffer() START count == 0 return true")
return true
}
// log.Debugln("ReadToBuffer() count = ", count)
// tmp := Chomp(oneByte)
// log.Debugln("ReadToBuffer() tmp = ", tmp)
io.WriteString(cmd.Buffer, strings.Trim(string(oneByte), "\x00"))
return false
}
// send the path and the command
func RunCmd(workingpath string, parts []string) (error, bool, string) {
if len(parts) == 0 {
log.Warn("command line was empty")
return errors.New("empty"), false, ""
}
if parts[0] == "" {
log.Warn("command line was empty")
return errors.New("empty"), false, ""
}
thing := parts[0]
parts = parts[1:]
log.Log(INFO, "working path =", workingpath, "thing =", thing, "cmdline =", parts)
// Create the command
cmd := exec.Command(thing, parts...)
// Set the working directory
cmd.Dir = workingpath
// Execute the command
output, err := cmd.CombinedOutput()
if err != nil {
if thing == "git" {
log.Log(INFO, "git ERROR. maybe okay", workingpath, "thing =", thing, "cmdline =", parts)
log.Log(INFO, "git ERROR. maybe okay err =", err)
if err.Error() == "exit status 1" {
log.Log(INFO, "git ERROR. normal exit status 1")
if parts[0] == "diff-index" {
log.Log(INFO, "git normal diff-index when repo dirty")
return nil, false, "git diff-index exit status 1"
}
}
}
log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
log.Error(err)
log.Warn("output was", string(output))
log.Warn("cmd exited with error", err)
// panic("fucknuts")
// The command failed (non-zero exit status)
if exitErr, ok := err.(*exec.ExitError); ok {
// Assert that it is an exec.ExitError and get the exit code
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
log.Warn("Exit Status: %d\n", status.ExitStatus())
}
} else {
log.Warn("cmd.Run() failed with %s\n", err)
}
return err, false, string(output)
}
tmp := string(output)
tmp = strings.TrimSpace(tmp)
// Print the output
return nil, true, tmp
}
// send the path and the command
func RunCmdRun(workingpath string, parts []string) error {
if len(parts) == 0 {
log.Warn("command line was empty")
return errors.New("empty")
}
if parts[0] == "" {
log.Warn("command line was empty")
return errors.New("empty")
}
thing := parts[0]
parts = parts[1:]
log.Log(INFO, "working path =", workingpath, "thing =", thing, "cmdline =", parts)
// Create the command
cmd := exec.Command(thing, parts...)
// Set the working directory
cmd.Dir = workingpath
// Execute the command
err := cmd.Run()
if err != nil {
log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
log.Error(err)
log.Warn("cmd exited with error", err)
// panic("fucknuts")
// The command failed (non-zero exit status)
if exitErr, ok := err.(*exec.ExitError); ok {
// Assert that it is an exec.ExitError and get the exit code
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
log.Warn("Exit Status: %d\n", status.ExitStatus())
}
} else {
log.Warn("cmd.Run() failed with %s\n", err)
}
return err
}
return nil
}