package CmpExec1 import ( "io" "sync" "os/exec" "log" "time" "bytes" "syscall" "strings" "context" ) type CmpExec1Struct struct { C chan bool Path string Args []string Error error ExitCode int StdOut []string StdErr []string } type CmpExec1GroundStruct struct { Background bool LimitTime time.Duration StdOutFunc func (string) StdErrFunc func (string) CommandArg []string CtxCancel context.CancelFunc } func Exec1GetExitCode(cmd *exec.Cmd, ret *CmpExec1Struct) (*CmpExec1Struct) { if ret.Error == nil { ws := cmd.ProcessState.Sys().(syscall.WaitStatus) ret.ExitCode = ws.ExitStatus() return ret } exitError, ok := ret.Error.(*exec.ExitError) if ok { ws := exitError.Sys().(syscall.WaitStatus) ret.ExitCode = ws.ExitStatus() return ret } // This will happen (in OSX) if `name` is not available in $PATH, // in this situation, exit code could not be get, and stderr will be // empty string very likely, so we use the default fail code, and format err // to string and set to stderr // Wrap errror !!! log.Println("Could not get exit code for failed program", ret.Path) ret.ExitCode = 255 return ret } func Exec1Simple(cma ...string) (*CmpExec1Struct) { ret := &CmpExec1Struct{} ret.Path, ret.Error = exec.LookPath(cma[0]) if ret.Error != nil { ret.ExitCode = 127 return ret } ret.Args = cma[1:] ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second) defer cancel() cmd := exec.CommandContext(ctx, ret.Path, ret.Args...) var bbStdOut, bbStdErr bytes.Buffer cmd.Stdout = &bbStdOut cmd.Stderr = &bbStdErr ret.Error = cmd.Run() ret.StdOut = strings.Split(bbStdOut.String(), "\n") ret.StdErr = strings.Split(bbStdErr.String(), "\n") return Exec1GetExitCode(cmd, ret) } func Exec1OutCatch(SubjIn *CmpExec1GroundStruct, SubjOut *CmpExec1Struct, StreamNum int, r io.Reader) { buff := make([]byte, 1024, 1024) var tail string for { n, err := r.Read(buff[:]) if n > 0 { d := buff[:n] strList := strings.Split(string(d), "\n") strCnt := len(strList) prcLen := 0 for i := 0; i < strCnt - 1; i++ { strItem := strList[i] if i == 0 && len(tail) > 0 { strItem = tail + strList[i] tail = "" } if StreamNum == 1 { SubjIn.StdOutFunc(strItem) } else if StreamNum == 2 { SubjIn.StdErrFunc(strItem) } prcLen += len(strList[i]) + 1 } var strLast string if strCnt > 0 { strLast = strList[strCnt - 1] } else { strLast = strList[strCnt] } prcLenZ := prcLen + len(strLast) if prcLenZ != 0 { if string(buff[prcLenZ-1:prcLenZ]) != "\n" { tail = string(buff[prcLen:n]) } } } if err != nil { // Read returns io.EOF at the end of file, which is not an error for us if err == io.EOF { err = nil } SubjOut.Error = err return } } } func Exec1Wait(cmd *exec.Cmd, stdoutIn io.ReadCloser, stderrIn io.ReadCloser, opt *CmpExec1GroundStruct, ret *CmpExec1Struct) { if opt.Background { defer opt.CtxCancel() } var wg sync.WaitGroup wg.Add(2) go func() { Exec1OutCatch(opt, ret, 1, stdoutIn) wg.Done() }() go func() { Exec1OutCatch(opt, ret, 2, stderrIn) wg.Done() }() wg.Wait() ret.Error = cmd.Wait() Exec1GetExitCode(cmd, ret) if ret.C != nil { ret.C <- true } } func Exec1Ground(opt *CmpExec1GroundStruct) (*CmpExec1Struct) { ret := &CmpExec1Struct{} ret.Path, ret.Error = exec.LookPath(opt.CommandArg[0]) if ret.Error != nil { ret.ExitCode = 127 if ret.C != nil { ret.C <- true } return ret } ret.Args = opt.CommandArg[1:] if opt.Background { ret.C = make(chan bool) } var cmd *exec.Cmd if opt.LimitTime != 0 { var ctx context.Context ctx, opt.CtxCancel = context.WithTimeout(context.Background(), opt.LimitTime * time.Second) cmd = exec.CommandContext(ctx, ret.Path, ret.Args...) } else { cmd = exec.Command(ret.Path, ret.Args...) } stdoutIn, _ := cmd.StdoutPipe() stderrIn, _ := cmd.StderrPipe() ret.Error = cmd.Start() if opt.Background { go Exec1Wait(cmd, stdoutIn, stderrIn, opt, ret) return ret } Exec1Wait(cmd, stdoutIn, stderrIn, opt, ret) return ret }