diff options
Diffstat (limited to 'helper/pipe.go')
| -rw-r--r-- | helper/pipe.go | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/helper/pipe.go b/helper/pipe.go new file mode 100644 index 00000000..e6013283 --- /dev/null +++ b/helper/pipe.go @@ -0,0 +1,150 @@ +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() +} |
