staros/process.go
starainrt d93a851d1b
feat: 完善 staros 系统能力并更新 wincmd 发布版依赖
- 重构 sysconf 为文档模型 INI Parser 与 Config Framework
- 强化 hosts 解析、插入校验、写回与异常输入处理
- 完善 StarCmd 生命周期、等待 API、流式输出与 IO 重定向
- 扩展跨平台文件时间、文件锁、内存、进程与网络能力
- 将 Windows 进程适配更新到 b612.me/wincmd v0.1.0
- 移除本地 wincmd/win32api replace,改用发布版依赖
- 将最低 Go 版本提升到 1.18
- 补充 hosts、sysconf、FileLock、StarCmd 与平台适配回归测试
2026-06-09 18:10:19 +08:00

929 lines
22 KiB
Go

package staros
import (
"bytes"
"context"
"errors"
"io"
"os"
"os/exec"
"sync"
"sync/atomic"
"time"
)
var errNilCommand = errors.New("nil command")
var errCommandStdinUnavailable = errors.New("command stdin is not available")
var errCommandProcessNotStarted = errors.New("command process is not started")
var errCommandAlreadyStarted = errors.New("command already started")
var errCommandAlreadyReleased = errors.New("command already released")
var errCommandStdinClosed = errors.New("command stdin is closed")
var errCommandAlreadyDetached = errors.New("command already detached")
var errCommandDetached = errors.New("command already detached")
var errCommandRedirectNil = errors.New("command redirect target is nil")
const starCmdUnknownExitCode = -999
const starCmdStreamBuffer = 64
type starCmdStream int
const (
starCmdStdout starCmdStream = iota
starCmdStderr
)
// StarCmdOutputStream identifies which process stream produced a chunk.
type StarCmdOutputStream int
const (
StarCmdOutputStdout StarCmdOutputStream = iota
StarCmdOutputStderr
)
// StarCmdOutput is a streamed stdout/stderr chunk.
type StarCmdOutput struct {
Stream StarCmdOutputStream
Data []byte
}
type starCmdWriter struct {
cmd *StarCmd
stream starCmdStream
}
func (writer starCmdWriter) Write(data []byte) (int, error) {
if writer.cmd == nil {
return 0, errNilCommand
}
writer.cmd.lock.Lock()
writer.cmd.ensureBuffers()
var redirect io.Writer
switch writer.stream {
case starCmdStdout:
if _, err := writer.cmd.stdoutBuf.Write(data); err != nil {
writer.cmd.lock.Unlock()
return 0, err
}
writer.cmd.stdout = append(writer.cmd.stdout, data...)
writer.cmd.publishStreamLocked(starCmdStdout, data)
redirect = writer.cmd.stdoutRedirect
case starCmdStderr:
if _, err := writer.cmd.stderrBuf.Write(data); err != nil {
writer.cmd.lock.Unlock()
return 0, err
}
writer.cmd.errout = append(writer.cmd.errout, data...)
writer.cmd.publishStreamLocked(starCmdStderr, data)
redirect = writer.cmd.stderrRedirect
default:
writer.cmd.lock.Unlock()
return 0, errors.New("unknown command stream")
}
writer.cmd.lock.Unlock()
if redirect != nil {
writer.cmd.redirectLock.Lock()
n, err := redirect.Write(data)
writer.cmd.redirectLock.Unlock()
if err != nil {
return n, err
}
if n != len(data) {
return n, io.ErrShortWrite
}
}
return len(data), nil
}
//StarCmd Is Here
type StarCmd struct {
CMD *exec.Cmd
infile io.WriteCloser
inclosed bool
running int32
started int32
released int32
detached int32
//Store AlL of the Standed Outputs
stdout []byte
//Store All of the Standed Errors
errout []byte
runerr error
exitcode int
stdoutBuf *bytes.Buffer
stderrBuf *bytes.Buffer
lock sync.Mutex
prewrite []string
prewritetime time.Duration
stopctxfunc context.CancelFunc
stopctx context.Context
doneOnce sync.Once
done chan struct{}
resultOnce sync.Once
resultDone chan struct{}
stdoutStream []chan []byte
stderrStream []chan []byte
outputStream []chan StarCmdOutput
streamClosed bool
stdoutRedirect io.Writer
stderrRedirect io.Writer
redirectLock sync.Mutex
closeAfter []io.Closer
}
func (starcli *StarCmd) ensureBuffers() {
if starcli.stdoutBuf == nil {
starcli.stdoutBuf = bytes.NewBuffer(make([]byte, 0))
}
if starcli.stderrBuf == nil {
starcli.stderrBuf = bytes.NewBuffer(make([]byte, 0))
}
}
func (starcli *StarCmd) ensureStopContext() {
if starcli.stopctx == nil || starcli.stopctxfunc == nil {
starcli.stopctx, starcli.stopctxfunc = context.WithCancel(context.Background())
}
if starcli.done == nil {
starcli.done = make(chan struct{})
}
if starcli.resultDone == nil {
starcli.resultDone = make(chan struct{})
}
}
func (starcli *StarCmd) ensureResultDone() <-chan struct{} {
if starcli == nil {
closed := make(chan struct{})
close(closed)
return closed
}
starcli.lock.Lock()
starcli.ensureStopContext()
done := starcli.resultDone
starcli.lock.Unlock()
return done
}
func (starcli *StarCmd) signalResultDone() {
if starcli == nil {
return
}
starcli.resultOnce.Do(func() {
starcli.lock.Lock()
starcli.ensureStopContext()
done := starcli.resultDone
starcli.lock.Unlock()
close(done)
})
}
func (starcli *StarCmd) finish() {
if starcli == nil {
return
}
starcli.setRunning(false)
starcli.signalResultDone()
if starcli.stopctxfunc != nil {
starcli.stopctxfunc()
}
starcli.doneOnce.Do(func() {
starcli.lock.Lock()
done := starcli.done
stdoutStream := starcli.stdoutStream
stderrStream := starcli.stderrStream
outputStream := starcli.outputStream
infile := starcli.infile
closeAfter := starcli.closeAfter
starcli.stdoutStream = nil
starcli.stderrStream = nil
starcli.outputStream = nil
starcli.closeAfter = nil
starcli.streamClosed = true
if infile != nil && !starcli.inclosed {
starcli.inclosed = true
} else {
infile = nil
}
starcli.lock.Unlock()
for _, stream := range stdoutStream {
close(stream)
}
for _, stream := range stderrStream {
close(stream)
}
for _, stream := range outputStream {
close(stream)
}
if infile != nil {
_ = infile.Close()
}
for _, closer := range closeAfter {
_ = closer.Close()
}
if done != nil {
close(done)
}
})
}
func (starcli *StarCmd) publishStreamLocked(stream starCmdStream, data []byte) {
if starcli == nil || starcli.streamClosed {
return
}
switch stream {
case starCmdStdout:
for _, receiver := range starcli.stdoutStream {
select {
case receiver <- append([]byte(nil), data...):
default:
}
}
for _, receiver := range starcli.outputStream {
select {
case receiver <- StarCmdOutput{Stream: StarCmdOutputStdout, Data: append([]byte(nil), data...)}:
default:
}
}
case starCmdStderr:
for _, receiver := range starcli.stderrStream {
select {
case receiver <- append([]byte(nil), data...):
default:
}
}
for _, receiver := range starcli.outputStream {
select {
case receiver <- StarCmdOutput{Stream: StarCmdOutputStderr, Data: append([]byte(nil), data...)}:
default:
}
}
}
}
func (starcli *StarCmd) registerByteStream(selectStream starCmdStream) <-chan []byte {
stream := make(chan []byte, starCmdStreamBuffer)
if starcli == nil {
close(stream)
return stream
}
starcli.lock.Lock()
if starcli.streamClosed {
close(stream)
} else {
switch selectStream {
case starCmdStdout:
starcli.stdoutStream = append(starcli.stdoutStream, stream)
case starCmdStderr:
starcli.stderrStream = append(starcli.stderrStream, stream)
default:
close(stream)
}
}
starcli.lock.Unlock()
return stream
}
func (starcli *StarCmd) ensureConfigurable() error {
if starcli == nil || starcli.CMD == nil {
return errNilCommand
}
if atomic.LoadInt32(&starcli.started) != 0 {
return errCommandAlreadyStarted
}
if atomic.LoadInt32(&starcli.detached) != 0 {
return errCommandDetached
}
return nil
}
func Command(command string, args ...string) (*StarCmd, error) {
return newStarCmd(exec.Command(command, args...))
}
func CommandContext(ctx context.Context, command string, args ...string) (*StarCmd, error) {
return newStarCmd(exec.CommandContext(ctx, command, args...))
}
func newStarCmd(cmd *exec.Cmd) (*StarCmd, error) {
var err error
shell := &StarCmd{
CMD: cmd,
prewritetime: time.Millisecond * 200,
stdoutBuf: bytes.NewBuffer(make([]byte, 0)),
stderrBuf: bytes.NewBuffer(make([]byte, 0)),
done: make(chan struct{}),
resultDone: make(chan struct{}),
exitcode: starCmdUnknownExitCode,
}
shell.stopctx, shell.stopctxfunc = context.WithCancel(context.Background())
shell.infile, err = shell.CMD.StdinPipe()
if err != nil {
return shell, err
}
shell.CMD.Stdout = starCmdWriter{cmd: shell, stream: starCmdStdout}
shell.CMD.Stderr = starCmdWriter{cmd: shell, stream: starCmdStderr}
return shell, nil
}
func (starcli *StarCmd) NowLineOutput() (string, error) {
if starcli == nil {
return "", errNilCommand
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
starcli.ensureBuffers()
buf, _ := starcli.stdoutBuf.ReadBytes('\n')
buferr, _ := starcli.stderrBuf.ReadBytes(byte('\n'))
if len(buferr) != 0 {
return string(buf), errors.New(string(buferr))
}
return string(buf), nil
}
func (starcli *StarCmd) NowLineStdOut() string {
if starcli == nil {
return ""
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
starcli.ensureBuffers()
buf, _ := starcli.stdoutBuf.ReadBytes('\n')
return string(buf)
}
func (starcli *StarCmd) NowLineStdErr() error {
if starcli == nil {
return errNilCommand
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
starcli.ensureBuffers()
buferr, _ := starcli.stderrBuf.ReadBytes(byte('\n'))
if len(buferr) != 0 {
return errors.New(string(buferr))
}
return nil
}
func (starcli *StarCmd) NowAllOutput() (string, error) {
if starcli == nil {
return "", errNilCommand
}
var outstr string
starcli.lock.Lock()
defer starcli.lock.Unlock()
starcli.ensureBuffers()
buf := make([]byte, starcli.stdoutBuf.Len())
n, _ := starcli.stdoutBuf.Read(buf)
runerr := starcli.runerr
if n != 0 {
outstr = string(buf[:n])
}
if runerr != nil {
return outstr, runerr
}
buf = make([]byte, starcli.stderrBuf.Len())
n, _ = starcli.stderrBuf.Read(buf)
if n != 0 {
return outstr, errors.New(string(buf[:n]))
}
return outstr, nil
}
func (starcli *StarCmd) NowStdOut() string {
if starcli == nil {
return ""
}
var outstr string
starcli.lock.Lock()
defer starcli.lock.Unlock()
starcli.ensureBuffers()
buf := make([]byte, starcli.stdoutBuf.Len())
n, _ := starcli.stdoutBuf.Read(buf)
if n != 0 {
outstr = string(buf[:n])
}
return outstr
}
func (starcli *StarCmd) NowStdErr() error {
if starcli == nil {
return errNilCommand
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
starcli.ensureBuffers()
buf := make([]byte, starcli.stderrBuf.Len())
n, _ := starcli.stderrBuf.Read(buf)
if n != 0 {
return errors.New(string(buf[:n]))
}
return nil
}
func (starcli *StarCmd) AllOutPut() (string, error) {
if starcli == nil {
return "", errNilCommand
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
err := starcli.runerr
if err == nil && len(starcli.errout) != 0 {
err = errors.New(string(starcli.errout))
}
return string(starcli.stdout), err
}
func (starcli *StarCmd) AllStdOut() string {
if starcli == nil {
return ""
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
return string(starcli.stdout)
}
func (starcli *StarCmd) AllStdErr() error {
if starcli == nil {
return errNilCommand
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
err := starcli.runerr
if err == nil && len(starcli.errout) != 0 {
err = errors.New(string(starcli.errout))
}
return err
}
func (starcli *StarCmd) setRunning(alive bool) {
if starcli == nil {
return
}
if alive {
atomic.StoreInt32(&starcli.running, 1)
return
}
atomic.StoreInt32(&starcli.running, 0)
}
func (starcli *StarCmd) Start() error {
if starcli == nil || starcli.CMD == nil {
return errNilCommand
}
if atomic.LoadInt32(&starcli.detached) != 0 {
return errCommandDetached
}
starcli.lock.Lock()
starcli.ensureBuffers()
starcli.ensureStopContext()
starcli.lock.Unlock()
if !atomic.CompareAndSwapInt32(&starcli.started, 0, 1) {
return errCommandAlreadyStarted
}
if err := starcli.CMD.Start(); err != nil {
starcli.lock.Lock()
starcli.runerr = err
starcli.exitcode = -1
starcli.lock.Unlock()
starcli.signalResultDone()
starcli.finish()
return err
}
starcli.setRunning(true)
go func() {
err := starcli.CMD.Wait()
if err != nil {
starcli.lock.Lock()
starcli.runerr = err
starcli.lock.Unlock()
}
if starcli.CMD.ProcessState != nil {
starcli.lock.Lock()
starcli.exitcode = starcli.CMD.ProcessState.ExitCode()
starcli.lock.Unlock()
}
starcli.signalResultDone()
starcli.finish()
}()
go func(ctx context.Context) {
starcli.lock.Lock()
prewrite := append([]string(nil), starcli.prewrite...)
prewritetime := starcli.prewritetime
starcli.lock.Unlock()
for _, v := range prewrite {
select {
case <-ctx.Done():
return
default:
}
_ = starcli.WriteCmdE(v)
time.Sleep(prewritetime)
}
}(starcli.stopctx)
return nil
}
func (starcli *StarCmd) IsRunning() bool {
if starcli == nil {
return false
}
return 0 != atomic.LoadInt32(&starcli.running)
}
func (starcli *StarCmd) runError() error {
if starcli == nil {
return errNilCommand
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
return starcli.runerr
}
func (starcli *StarCmd) ensureWaitable() error {
if starcli == nil || starcli.CMD == nil {
return errNilCommand
}
if atomic.LoadInt32(&starcli.started) == 0 {
return errCommandProcessNotStarted
}
return nil
}
// Stopped returns a channel that is closed after the command reaches its final state.
func (starcli *StarCmd) Stopped() <-chan struct{} {
if starcli == nil {
closed := make(chan struct{})
close(closed)
return closed
}
starcli.lock.Lock()
starcli.ensureStopContext()
done := starcli.done
starcli.lock.Unlock()
return done
}
// Stoped returns a channel that is closed after the command reaches its final state.
//
// Deprecated: use Stopped.
func (starcli *StarCmd) Stoped() <-chan struct{} {
return starcli.Stopped()
}
// Wait blocks until the command reaches its final state and returns the process wait error.
func (starcli *StarCmd) Wait() error {
if err := starcli.ensureWaitable(); err != nil {
return err
}
<-starcli.ensureResultDone()
return starcli.runError()
}
// WaitContext blocks until the command reaches its final state or ctx is done.
func (starcli *StarCmd) WaitContext(ctx context.Context) error {
if err := starcli.ensureWaitable(); err != nil {
return err
}
if ctx == nil {
return starcli.Wait()
}
resultDone := starcli.ensureResultDone()
select {
case <-resultDone:
return starcli.runError()
default:
}
select {
case <-resultDone:
return starcli.runError()
case <-ctx.Done():
select {
case <-resultDone:
return starcli.runError()
default:
}
return ctx.Err()
}
}
// WaitTimeout blocks until the command reaches its final state or tm elapses.
func (starcli *StarCmd) WaitTimeout(tm time.Duration) error {
if err := starcli.ensureWaitable(); err != nil {
return err
}
if tm <= 0 {
select {
case <-starcli.ensureResultDone():
return starcli.runError()
default:
return ERR_TIMEOUT
}
}
timer := time.NewTimer(tm)
defer timer.Stop()
resultDone := starcli.ensureResultDone()
select {
case <-resultDone:
return starcli.runError()
case <-timer.C:
select {
case <-resultDone:
return starcli.runError()
default:
return ERR_TIMEOUT
}
}
}
// StdoutChan returns a channel that receives future stdout chunks until Stopped closes.
func (starcli *StarCmd) StdoutChan() <-chan []byte {
return starcli.registerByteStream(starCmdStdout)
}
// StderrChan returns a channel that receives future stderr chunks until Stopped closes.
func (starcli *StarCmd) StderrChan() <-chan []byte {
return starcli.registerByteStream(starCmdStderr)
}
// OutputChan returns a channel that receives future stdout and stderr chunks until Stopped closes.
func (starcli *StarCmd) OutputChan() <-chan StarCmdOutput {
stream := make(chan StarCmdOutput, starCmdStreamBuffer)
if starcli == nil {
close(stream)
return stream
}
starcli.lock.Lock()
if starcli.streamClosed {
close(stream)
} else {
starcli.outputStream = append(starcli.outputStream, stream)
}
starcli.lock.Unlock()
return stream
}
// RedirectStdout mirrors stdout into writer while keeping StarCmd output capture enabled.
func (starcli *StarCmd) RedirectStdout(writer io.Writer) error {
if writer == nil {
return errCommandRedirectNil
}
if err := starcli.ensureConfigurable(); err != nil {
return err
}
starcli.lock.Lock()
starcli.stdoutRedirect = writer
starcli.lock.Unlock()
return nil
}
// RedirectStderr mirrors stderr into writer while keeping StarCmd error capture enabled.
func (starcli *StarCmd) RedirectStderr(writer io.Writer) error {
if writer == nil {
return errCommandRedirectNil
}
if err := starcli.ensureConfigurable(); err != nil {
return err
}
starcli.lock.Lock()
starcli.stderrRedirect = writer
starcli.lock.Unlock()
return nil
}
// RedirectOutput mirrors stdout and stderr into writer while keeping StarCmd capture enabled.
func (starcli *StarCmd) RedirectOutput(writer io.Writer) error {
if writer == nil {
return errCommandRedirectNil
}
if err := starcli.ensureConfigurable(); err != nil {
return err
}
starcli.lock.Lock()
starcli.stdoutRedirect = writer
starcli.stderrRedirect = writer
starcli.lock.Unlock()
return nil
}
// RedirectStdin replaces the managed stdin pipe with reader.
func (starcli *StarCmd) RedirectStdin(reader io.Reader) error {
if reader == nil {
return errCommandRedirectNil
}
if err := starcli.ensureConfigurable(); err != nil {
return err
}
starcli.lock.Lock()
if starcli.infile != nil && !starcli.inclosed {
if err := starcli.infile.Close(); err != nil {
starcli.lock.Unlock()
return err
}
}
starcli.CMD.Stdin = reader
starcli.infile = nil
starcli.inclosed = true
starcli.lock.Unlock()
return nil
}
func (starcli *StarCmd) addCloseAfter(closer io.Closer) {
if starcli == nil || closer == nil {
return
}
starcli.lock.Lock()
starcli.closeAfter = append(starcli.closeAfter, closer)
starcli.lock.Unlock()
}
// RedirectStdoutFile mirrors stdout into path while keeping StarCmd output capture enabled.
func (starcli *StarCmd) RedirectStdoutFile(path string) error {
if err := starcli.ensureConfigurable(); err != nil {
return err
}
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
if err := starcli.RedirectStdout(file); err != nil {
_ = file.Close()
return err
}
starcli.addCloseAfter(file)
return nil
}
// RedirectStderrFile mirrors stderr into path while keeping StarCmd error capture enabled.
func (starcli *StarCmd) RedirectStderrFile(path string) error {
if err := starcli.ensureConfigurable(); err != nil {
return err
}
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
if err := starcli.RedirectStderr(file); err != nil {
_ = file.Close()
return err
}
starcli.addCloseAfter(file)
return nil
}
// RedirectOutputFile mirrors stdout and stderr into path while keeping StarCmd capture enabled.
func (starcli *StarCmd) RedirectOutputFile(path string) error {
if err := starcli.ensureConfigurable(); err != nil {
return err
}
file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
if err := starcli.RedirectOutput(file); err != nil {
_ = file.Close()
return err
}
starcli.addCloseAfter(file)
return nil
}
// RedirectStdinFile replaces the managed stdin pipe with path opened for reading.
func (starcli *StarCmd) RedirectStdinFile(path string) error {
if err := starcli.ensureConfigurable(); err != nil {
return err
}
file, err := os.Open(path)
if err != nil {
return err
}
if err := starcli.RedirectStdin(file); err != nil {
_ = file.Close()
return err
}
starcli.addCloseAfter(file)
return nil
}
func (starcli *StarCmd) Exec(cmd string, wait int) (string, error) {
if err := starcli.WriteCmdE(cmd); err != nil {
return "", err
}
time.Sleep(time.Millisecond * time.Duration(wait))
return starcli.NowAllOutput()
}
func (starcli *StarCmd) WriteCmd(cmdstr string) {
_ = starcli.WriteCmdE(cmdstr)
}
// WriteStdinE writes raw bytes to stdin without appending a newline.
func (starcli *StarCmd) WriteStdinE(data []byte) error {
if starcli == nil {
return errNilCommand
}
starcli.lock.Lock()
infile := starcli.infile
inclosed := starcli.inclosed
starcli.lock.Unlock()
if infile == nil {
return errCommandStdinUnavailable
}
if inclosed {
return errCommandStdinClosed
}
_, err := infile.Write(data)
return err
}
// WriteStdinStringE writes raw text to stdin without appending a newline.
func (starcli *StarCmd) WriteStdinStringE(data string) error {
return starcli.WriteStdinE([]byte(data))
}
// WriteStdinLineE writes text to stdin and appends one newline.
func (starcli *StarCmd) WriteStdinLineE(data string) error {
return starcli.WriteStdinStringE(data + "\n")
}
func (starcli *StarCmd) WriteCmdE(cmdstr string) error {
return starcli.WriteStdinLineE(cmdstr)
}
func (starcli *StarCmd) CloseStdin() {
_ = starcli.CloseStdinE()
}
func (starcli *StarCmd) CloseStdinE() error {
if starcli == nil {
return errNilCommand
}
starcli.lock.Lock()
infile := starcli.infile
if infile == nil {
starcli.lock.Unlock()
return errCommandStdinUnavailable
}
if starcli.inclosed {
starcli.lock.Unlock()
return errCommandStdinClosed
}
starcli.inclosed = true
starcli.lock.Unlock()
return infile.Close()
}
func (starcli *StarCmd) PreWrite(cmd ...string) {
if starcli == nil {
return
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
for _, v := range cmd {
starcli.prewrite = append(starcli.prewrite, v)
}
}
func (starcli *StarCmd) PreWriteInterval(dt time.Duration) {
if starcli == nil {
return
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
starcli.prewritetime = dt
}
func (starcli *StarCmd) ExitCode() int {
if starcli == nil {
return starCmdUnknownExitCode
}
starcli.lock.Lock()
defer starcli.lock.Unlock()
return starcli.exitcode
}
func (starcli *StarCmd) Kill() error {
if starcli == nil || starcli.CMD == nil || starcli.CMD.Process == nil {
return errCommandProcessNotStarted
}
err := starcli.CMD.Process.Kill()
if err != nil {
return err
}
return nil
}
func (starcli *StarCmd) GetPid() int {
if starcli == nil || starcli.CMD == nil || starcli.CMD.Process == nil {
return -1
}
return starcli.CMD.Process.Pid
}
func (starcli *StarCmd) Signal(sig os.Signal) error {
if starcli == nil || starcli.CMD == nil || starcli.CMD.Process == nil {
return errCommandProcessNotStarted
}
return starcli.CMD.Process.Signal(sig)
}