aboutsummaryrefslogtreecommitdiffhomepage
path: root/helper/helper.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-02-13 23:15:34 +0900
committerOphestra <cat@gensokyo.uk>2025-02-13 23:34:15 +0900
commitfe7d208cf76fa6f24bb9d12ba29b5ed61d837ce3 (patch)
treecac02af50a13b2078739a8f5a74d219f3b60833d /helper/helper.go
parent60c287375048b21eab2bd82f1e7d43e36dcfb3a2 (diff)
helper: use generic extra files interface
This replaces the pipes object and integrates context into helper process lifecycle. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'helper/helper.go')
-rw-r--r--helper/helper.go130
1 files changed, 114 insertions, 16 deletions
diff --git a/helper/helper.go b/helper/helper.go
index d3115c59..2ef1afaf 100644
--- a/helper/helper.go
+++ b/helper/helper.go
@@ -2,35 +2,133 @@
package helper
import (
- "errors"
+ "context"
+ "fmt"
+ "io"
+ "os"
"os/exec"
+ "slices"
+ "syscall"
+ "time"
+
+ "git.gensokyo.uk/security/fortify/helper/proc"
)
var (
- ErrStatusFault = errors.New("generic status pipe fault")
- ErrStatusRead = errors.New("unexpected status response")
+ WaitDelay = 2 * time.Second
)
const (
- // FortifyHelper is set for the process launched by Helper.
+ // FortifyHelper is set to 1 when args fd is enabled and 0 otherwise.
FortifyHelper = "FORTIFY_HELPER"
- // FortifyStatus is 1 when sync fd is enabled and 0 otherwise.
+ // FortifyStatus is set to 1 when stat fd is enabled and 0 otherwise.
FortifyStatus = "FORTIFY_STATUS"
)
type Helper interface {
- // StartNotify starts the helper process.
- // A status pipe is passed to the helper if ready is not nil.
- StartNotify(ready chan error) error
+ // Stdin sets the standard input of Helper.
+ Stdin(r io.Reader) Helper
+ // Stdout sets the standard output of Helper.
+ Stdout(w io.Writer) Helper
+ // Stderr sets the standard error of Helper.
+ Stderr(w io.Writer) Helper
+ // SetEnv sets the environment of Helper.
+ SetEnv(env []string) Helper
+
// Start starts the helper process.
- Start() error
- // Close closes the status pipe.
- // If helper is started without the status pipe, Close panics.
- Close() error
- // Wait calls wait on the child process and cleans up pipes.
+ // A status pipe is passed to the helper if stat is true.
+ Start(ctx context.Context, stat bool) error
+ // Wait blocks until Helper exits and releases all its resources.
Wait() error
- // Unwrap returns the underlying exec.Cmd instance.
- Unwrap() *exec.Cmd
+
+ fmt.Stringer
+}
+
+func newHelperCmd(
+ h Helper, name string,
+ wt io.WriterTo, argF func(argsFd, statFd int) []string,
+ extraFiles []*os.File,
+) (cmd *helperCmd) {
+ cmd = new(helperCmd)
+
+ cmd.r = h
+ cmd.name = name
+
+ cmd.extraFiles = new(proc.ExtraFilesPre)
+ for _, f := range extraFiles {
+ _, v := cmd.extraFiles.Append()
+ *v = f
+ }
+
+ argsFd := -1
+ if wt != nil {
+ f := proc.NewWriterTo(wt)
+ argsFd = int(proc.InitFile(f, cmd.extraFiles))
+ cmd.files = append(cmd.files, f)
+ cmd.hasArgsFd = true
+ }
+ cmd.argF = func(statFd int) []string { return argF(argsFd, statFd) }
+
+ return
+}
+
+// helperCmd wraps Cmd and implements methods shared across all Helper implementations.
+type helperCmd struct {
+ // ref to parent
+ r Helper
+
+ // returns an array of arguments passed directly
+ // to the helper process
+ argF func(statFd int) []string
+ // whether argsFd is present
+ hasArgsFd bool
+
+ // closes statFd
+ stat io.Closer
+ // deferred extraFiles fulfillment
+ files []proc.File
+ // passed through to [proc.Fulfill] and [proc.InitFile]
+ extraFiles *proc.ExtraFilesPre
+
+ name string
+ stdin io.Reader
+ stdout, stderr io.Writer
+ env []string
+ *exec.Cmd
+}
+
+func (h *helperCmd) Stdin(r io.Reader) Helper { h.stdin = r; return h.r }
+func (h *helperCmd) Stdout(w io.Writer) Helper { h.stdout = w; return h.r }
+func (h *helperCmd) Stderr(w io.Writer) Helper { h.stderr = w; return h.r }
+func (h *helperCmd) SetEnv(env []string) Helper { h.env = env; return h.r }
+
+// finalise initialises the underlying [exec.Cmd] object.
+func (h *helperCmd) finalise(ctx context.Context, stat bool) (args []string) {
+ h.Cmd = commandContext(ctx, h.name)
+ h.Cmd.Stdin, h.Cmd.Stdout, h.Cmd.Stderr = h.stdin, h.stdout, h.stderr
+ h.Cmd.Env = slices.Grow(h.env, 2)
+ if h.hasArgsFd {
+ h.Cmd.Env = append(h.Cmd.Env, FortifyHelper+"=1")
+ } else {
+ h.Cmd.Env = append(h.Cmd.Env, FortifyHelper+"=0")
+ }
+
+ h.Cmd.Cancel = func() error { return h.Cmd.Process.Signal(syscall.SIGTERM) }
+ h.Cmd.WaitDelay = WaitDelay
+
+ statFd := -1
+ if stat {
+ f := proc.NewStat(&h.stat)
+ statFd = int(proc.InitFile(f, h.extraFiles))
+ h.files = append(h.files, f)
+ h.Cmd.Env = append(h.Cmd.Env, FortifyStatus+"=1")
+
+ // stat is populated on fulfill
+ h.Cmd.Cancel = func() error { return h.stat.Close() }
+ } else {
+ h.Cmd.Env = append(h.Cmd.Env, FortifyStatus+"=0")
+ }
+ return h.argF(statFd)
}
-var execCommand = exec.Command
+var commandContext = exec.CommandContext