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 }