Files
cmg-exec1/main.go
Super User 347d3c994a Add all
2025-02-13 17:53:24 +03:00

229 lines
4.2 KiB
Go

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
}