304 lines
7.2 KiB
Go
304 lines
7.2 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) *Shell {
|
|
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 *Shell) Run(cmdline string) string {
|
|
cmd.InitProcess(cmdline)
|
|
if cmd.Error != nil {
|
|
return ""
|
|
}
|
|
cmd.Exec(cmdline)
|
|
return Chomp(cmd.Buffer)
|
|
}
|
|
|
|
func (cmd *Shell) 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 *Shell) 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 *Shell) 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 *Shell) 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 *Shell) 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")
|
|
return err, false, string(output)
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
tmp := string(output)
|
|
tmp = strings.TrimSpace(tmp)
|
|
|
|
// Print the output
|
|
return nil, true, tmp
|
|
}
|