commit 347d3c994a42bdf7d53e871d489075d0aa5c5bc5 Author: Super User Date: Thu Feb 13 17:53:24 2025 +0300 Add all diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d37c7ff --- /dev/null +++ b/go.mod @@ -0,0 +1,4 @@ +module gitea.cmp167.cloudns.ph/cmp167/cmg-exec1 + +go 1.20.0 + diff --git a/main.go b/main.go new file mode 100644 index 0000000..88a029d --- /dev/null +++ b/main.go @@ -0,0 +1,228 @@ + + 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 + } diff --git a/testGround.go b/testGround.go new file mode 100644 index 0000000..58bcb47 --- /dev/null +++ b/testGround.go @@ -0,0 +1,135 @@ + + package CmpExec1 + + import ( + "log" + ) + + func Exec1GroundTestStdOutFunc(s string) { + if s == "" { + return + } + + log.Println("StdOut", s) + } + + func Exec1GroundTestStdErrFunc(s string) { + if s == "" { + return + } + + log.Println("StdErr", s) + } + + func Exec1GroundTest(opt *CmpExec1GroundStruct) { + ret := Exec1Ground(opt) + + Exec1Print(ret) + + if ret.Error != nil { + return + } + + if opt.Background { + log.Println("Wait channel") + <- ret.C + + Exec1Print(ret) + } + } + + func Exec1GroundTestErrPath(bg bool) { + opt := &CmpExec1GroundStruct{ + Background: bg, + LimitTime: 3000, + StdOutFunc: Exec1GroundTestStdOutFunc, + StdErrFunc: Exec1GroundTestStdErrFunc, + CommandArg: []string{ + "./test.sh", + "-a", + }, + } + + Exec1GroundTest(opt) + } + + func Exec1GroundTestErrParm(bg bool) { + opt := &CmpExec1GroundStruct{ + Background: bg, + LimitTime: 3000, + StdOutFunc: Exec1GroundTestStdOutFunc, + StdErrFunc: Exec1GroundTestStdErrFunc, + CommandArg: []string{ + "sleep", + "v", + }, + } + + Exec1GroundTest(opt) + } + + func Exec1GroundTestTimeout(bg bool) { + opt := &CmpExec1GroundStruct{ + Background: bg, + LimitTime: 2, + StdOutFunc: Exec1GroundTestStdOutFunc, + StdErrFunc: Exec1GroundTestStdErrFunc, + CommandArg: []string{ + "sleep", + "4", + }, + } + + Exec1GroundTest(opt) + } + + func Exec1GroundTestSuccess(bg bool) { + opt := &CmpExec1GroundStruct{ + Background: bg, + LimitTime: 3000, + StdOutFunc: Exec1GroundTestStdOutFunc, + StdErrFunc: Exec1GroundTestStdErrFunc, + CommandArg: []string{ + "bash", + "-c", + "sleep 1 ; echo ok ; sleep 1; echo ok", + }, + } + + Exec1GroundTest(opt) + } + + func Exec1GroundTestAll() { + log.Println("====", "Exec1GroundTestErrPath") + + Exec1GroundTestErrPath(true) + + log.Println("====", "Exec1GroundTestErrParm") + + Exec1GroundTestErrParm(true) + + log.Println("====", "Exec1GroundTestTimeout") + + Exec1GroundTestTimeout(true) + + log.Println("====", "Exec1GroundTestSuccess") + + Exec1GroundTestSuccess(true) + + + log.Println("====", "Exec1GroundTestErrPath") + + Exec1GroundTestErrPath(false) + + log.Println("====", "Exec1GroundTestErrParm") + + Exec1GroundTestErrParm(false) + + log.Println("====", "Exec1GroundTestTimeout") + + Exec1GroundTestTimeout(false) + + log.Println("====", "Exec1GroundTestSuccess") + + Exec1GroundTestSuccess(false) + } diff --git a/testSimple.go b/testSimple.go new file mode 100644 index 0000000..f10eb06 --- /dev/null +++ b/testSimple.go @@ -0,0 +1,64 @@ + + package CmpExec1 + + import ( + "log" + ) + + func Exec1Print(ret *CmpExec1Struct) { + log.Println("Path:" , ret.Path ) + log.Println("Args:" , ret.Args ) + log.Println("Error:" , ret.Error ) + log.Println("ExitCode:" , ret.ExitCode ) + log.Println("StdOut:" , ret.StdOut ) + log.Println("StdErr:" , ret.StdErr ) + } + + + func Exec1SimpleTestErrParm() { + ret := Exec1Simple( + "sleep", + "v", + ) + Exec1Print(ret) + } + + func Exec1SimpleTestErrExec() { + ret := Exec1Simple( + "data", + ) + Exec1Print(ret) + } + + func Exec1SimpleTestShotSuccess() { + ret := Exec1Simple( + "date", + ) + Exec1Print(ret) + } + + func Exec1SimpleTestLongSuccess() { + ret := Exec1Simple( + "sleep", + "15", + ) + Exec1Print(ret) + } + + func Exec1SimpleTestAll() { + log.Println("====", "Exec1SimpleTestErrParm") + + Exec1SimpleTestErrParm() + + log.Println("====", "Exec1SimpleTestErrExec") + + Exec1SimpleTestErrExec() + + log.Println("====", "Exec1SimpleTestShotSuccess") + + Exec1SimpleTestShotSuccess() + + log.Println("====", "Exec1SimpleTestLongSuccess") + + Exec1SimpleTestLongSuccess() + }