151 lines
2.9 KiB
Go
151 lines
2.9 KiB
Go
package helper
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
)
|
|
|
|
type pipes struct {
|
|
args io.WriterTo
|
|
|
|
statP [2]*os.File
|
|
argsP [2]*os.File
|
|
|
|
ready chan error
|
|
|
|
cmd *exec.Cmd
|
|
}
|
|
|
|
func (p *pipes) pipe() error {
|
|
if p.statP[0] != nil || p.statP[1] != nil ||
|
|
p.argsP[0] != nil || p.argsP[1] != nil {
|
|
panic("attempted to pipe twice")
|
|
}
|
|
if p.args == nil {
|
|
panic("attempted to pipe without args")
|
|
}
|
|
|
|
// create pipes
|
|
if pr, pw, err := os.Pipe(); err != nil {
|
|
return err
|
|
} else {
|
|
p.argsP[0], p.argsP[1] = pr, pw
|
|
}
|
|
|
|
// create status pipes if ready signal is requested
|
|
if p.ready != nil {
|
|
if pr, pw, err := os.Pipe(); err != nil {
|
|
return err
|
|
} else {
|
|
p.statP[0], p.statP[1] = pr, pw
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// calls pipe to create pipes and sets them up as ExtraFiles, returning their fd
|
|
func (p *pipes) prepareCmd(cmd *exec.Cmd) (int, int, error) {
|
|
if err := p.pipe(); err != nil {
|
|
return -1, -1, err
|
|
}
|
|
|
|
// save a reference of cmd for future use
|
|
p.cmd = cmd
|
|
|
|
// ExtraFiles: If non-nil, entry i becomes file descriptor 3+i.
|
|
argsFd := 3 + len(cmd.ExtraFiles)
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, p.argsP[0])
|
|
|
|
if p.ready != nil {
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, p.statP[1])
|
|
return argsFd, argsFd + 1, nil
|
|
} else {
|
|
return argsFd, -1, nil
|
|
}
|
|
}
|
|
|
|
func (p *pipes) readyWriteArgs() error {
|
|
statsP, argsP := p.statP[0], p.argsP[1]
|
|
|
|
// write arguments and close args pipe
|
|
if _, err := p.args.WriteTo(argsP); err != nil {
|
|
if err1 := p.cmd.Process.Kill(); err1 != nil {
|
|
// should be unreachable
|
|
panic(err1.Error())
|
|
}
|
|
return err
|
|
} else {
|
|
if err = argsP.Close(); err != nil {
|
|
if err1 := p.cmd.Process.Kill(); err1 != nil {
|
|
// should be unreachable
|
|
panic(err1.Error())
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
|
|
if p.ready != nil {
|
|
// monitor stat pipe
|
|
go func() {
|
|
n, err := statsP.Read(make([]byte, 1))
|
|
switch n {
|
|
case -1:
|
|
if err1 := p.cmd.Process.Kill(); err1 != nil {
|
|
// should be unreachable
|
|
panic(err1.Error())
|
|
}
|
|
// ensure error is not nil
|
|
if err == nil {
|
|
err = ErrStatusFault
|
|
}
|
|
p.ready <- err
|
|
case 0:
|
|
// ensure error is not nil
|
|
if err == nil {
|
|
err = ErrStatusRead
|
|
}
|
|
p.ready <- err
|
|
case 1:
|
|
p.ready <- nil
|
|
default:
|
|
panic("unreachable") // unexpected read count
|
|
}
|
|
}()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *pipes) mustClosePipes() {
|
|
if err := p.argsP[0].Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
|
// unreachable
|
|
panic(err.Error())
|
|
}
|
|
if err := p.argsP[1].Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
|
// unreachable
|
|
panic(err.Error())
|
|
}
|
|
|
|
if p.ready != nil {
|
|
if err := p.statP[0].Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
|
// unreachable
|
|
panic(err.Error())
|
|
}
|
|
if err := p.statP[1].Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
|
// unreachable
|
|
panic(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *pipes) closeStatus() error {
|
|
if p.ready == nil {
|
|
panic("attempted to close helper with no status pipe")
|
|
}
|
|
|
|
return p.statP[0].Close()
|
|
}
|