aboutsummaryrefslogtreecommitdiffhomepage
path: root/helper
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-11-12 23:12:38 +0900
committerOphestra <cat@gensokyo.uk>2025-11-12 23:16:13 +0900
commitf347d44c2281f507e3786aa498f634bfa4c92d84 (patch)
treefe85b41d5f6a5e308b87585f46efd64f90221e2b /helper
parentb5630f6883c874d458d9ae3c666cfb7ae5e3121d (diff)
internal/helper: relocate from helper
This package is ugly and is pending removal only kept alive by xdg-dbus-proxy. Its exported symbols are made available until v0.4.0 where it will be removed for #24. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'helper')
-rw-r--r--helper/args.go59
-rw-r--r--helper/args_test.go41
-rw-r--r--helper/cmd.go84
-rw-r--r--helper/cmd_test.go40
-rw-r--r--helper/container.go79
-rw-r--r--helper/container_test.go45
-rw-r--r--helper/deprecated.go73
-rw-r--r--helper/helper.go83
-rw-r--r--helper/helper_test.go139
-rw-r--r--helper/proc/deprecated.go63
-rw-r--r--helper/proc/files.go162
-rw-r--r--helper/proc/pipe.go110
-rw-r--r--helper/stub.go102
-rw-r--r--helper/stub_test.go11
14 files changed, 136 insertions, 955 deletions
diff --git a/helper/args.go b/helper/args.go
deleted file mode 100644
index 80cd3024..00000000
--- a/helper/args.go
+++ /dev/null
@@ -1,59 +0,0 @@
-package helper
-
-import (
- "bytes"
- "io"
- "syscall"
-)
-
-type argsWt [][]byte
-
-func (a argsWt) WriteTo(w io.Writer) (int64, error) {
- nt := 0
- for _, arg := range a {
- n, err := w.Write(arg)
- nt += n
-
- if err != nil {
- return int64(nt), err
- }
- }
-
- return int64(nt), nil
-}
-
-func (a argsWt) String() string {
- return string(
- bytes.TrimSuffix(
- bytes.ReplaceAll(
- bytes.Join(a, nil),
- []byte{0}, []byte{' '},
- ),
- []byte{' '},
- ),
- )
-}
-
-// NewCheckedArgs returns a checked null-terminated argument writer for a copy of args.
-func NewCheckedArgs(args ...string) (wt io.WriterTo, err error) {
- a := make(argsWt, len(args))
- for i, arg := range args {
- a[i], err = syscall.ByteSliceFromString(arg)
- if err != nil {
- return
- }
- }
- wt = a
- return
-}
-
-// MustNewCheckedArgs returns a checked null-terminated argument writer for a copy of args.
-// If s contains a NUL byte this function panics instead of returning an error.
-func MustNewCheckedArgs(args ...string) io.WriterTo {
- a, err := NewCheckedArgs(args...)
- if err != nil {
- panic(err.Error())
- }
-
- return a
-}
diff --git a/helper/args_test.go b/helper/args_test.go
deleted file mode 100644
index d6225c3e..00000000
--- a/helper/args_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package helper_test
-
-import (
- "errors"
- "fmt"
- "strings"
- "syscall"
- "testing"
-
- "hakurei.app/helper"
-)
-
-func TestArgsString(t *testing.T) {
- t.Parallel()
- wantString := strings.Join(wantArgs, " ")
- if got := argsWt.(fmt.Stringer).String(); got != wantString {
- t.Errorf("String: %q, want %q", got, wantString)
- }
-}
-
-func TestNewCheckedArgs(t *testing.T) {
- t.Parallel()
-
- args := []string{"\x00"}
- if _, err := helper.NewCheckedArgs(args...); !errors.Is(err, syscall.EINVAL) {
- t.Errorf("NewCheckedArgs: error = %v, wantErr %v", err, syscall.EINVAL)
- }
-
- t.Run("must panic", func(t *testing.T) {
- t.Parallel()
-
- badPayload := []string{"\x00"}
- defer func() {
- wantPanic := "invalid argument"
- if r := recover(); r != wantPanic {
- t.Errorf("MustNewCheckedArgs: panic = %v, wantPanic %v", r, wantPanic)
- }
- }()
- helper.MustNewCheckedArgs(badPayload...)
- })
-}
diff --git a/helper/cmd.go b/helper/cmd.go
deleted file mode 100644
index 9a5bc3ab..00000000
--- a/helper/cmd.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package helper
-
-import (
- "context"
- "errors"
- "io"
- "os"
- "os/exec"
- "slices"
- "sync"
- "syscall"
-
- "hakurei.app/helper/proc"
-)
-
-// NewDirect initialises a new direct Helper instance with wt as the null-terminated argument writer.
-// Function argF returns an array of arguments passed directly to the child process.
-func NewDirect(
- ctx context.Context,
- name string,
- wt io.WriterTo,
- stat bool,
- argF func(argsFd, statFd int) []string,
- cmdF func(cmd *exec.Cmd),
- extraFiles []*os.File,
-) Helper {
- d, args := newHelperCmd(ctx, name, wt, stat, argF, extraFiles)
- d.Args = append(d.Args, args...)
- if cmdF != nil {
- cmdF(d.Cmd)
- }
- return d
-}
-
-func newHelperCmd(
- ctx context.Context,
- name string,
- wt io.WriterTo,
- stat bool,
- argF func(argsFd, statFd int) []string,
- extraFiles []*os.File,
-) (cmd *helperCmd, args []string) {
- cmd = new(helperCmd)
- cmd.helperFiles, args = newHelperFiles(ctx, wt, stat, argF, extraFiles)
- cmd.Cmd = exec.CommandContext(ctx, name)
- cmd.Cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGTERM) }
- cmd.WaitDelay = WaitDelay
- return
-}
-
-// helperCmd provides a [exec.Cmd] wrapper around helper ipc.
-type helperCmd struct {
- mu sync.RWMutex
- *helperFiles
- *exec.Cmd
-}
-
-func (h *helperCmd) Start() error {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- // Check for doubled Start calls before we defer failure cleanup. If the prior
- // call to Start succeeded, we don't want to spuriously close its pipes.
- if h.Cmd != nil && h.Cmd.Process != nil {
- return errors.New("helper: already started")
- }
-
- h.Env = slices.Grow(h.Env, 2)
- if h.useArgsFd {
- h.Env = append(h.Env, HakureiHelper+"=1")
- } else {
- h.Env = append(h.Env, HakureiHelper+"=0")
- }
- if h.useStatFd {
- h.Env = append(h.Env, HakureiStatus+"=1")
-
- // stat is populated on fulfill
- h.Cancel = func() error { return h.stat.Close() }
- } else {
- h.Env = append(h.Env, HakureiStatus+"=0")
- }
-
- return proc.Fulfill(h.helperFiles.ctx, &h.ExtraFiles, h.Cmd.Start, h.files, h.extraFiles)
-}
diff --git a/helper/cmd_test.go b/helper/cmd_test.go
deleted file mode 100644
index 64ac643f..00000000
--- a/helper/cmd_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package helper_test
-
-import (
- "context"
- "errors"
- "io"
- "os"
- "os/exec"
- "testing"
-
- "hakurei.app/container"
- "hakurei.app/helper"
-)
-
-func TestCmd(t *testing.T) {
- t.Run("start non-existent helper path", func(t *testing.T) {
- h := helper.NewDirect(t.Context(), container.Nonexistent, argsWt, false, argF, nil, nil)
-
- if err := h.Start(); !errors.Is(err, os.ErrNotExist) {
- t.Errorf("Start: error = %v, wantErr %v",
- err, os.ErrNotExist)
- }
- })
-
- t.Run("valid new helper nil check", func(t *testing.T) {
- if got := helper.NewDirect(t.Context(), "hakurei", argsWt, false, argF, nil, nil); got == nil {
- t.Errorf("NewDirect(%q, %q) got nil",
- argsWt, "hakurei")
- return
- }
- })
-
- t.Run("implementation compliance", func(t *testing.T) {
- testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper {
- return helper.NewDirect(ctx, os.Args[0], argsWt, stat, argF, func(cmd *exec.Cmd) {
- setOutput(&cmd.Stdout, &cmd.Stderr)
- }, nil)
- })
- })
-}
diff --git a/helper/container.go b/helper/container.go
deleted file mode 100644
index b85f5859..00000000
--- a/helper/container.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package helper
-
-import (
- "context"
- "errors"
- "io"
- "os"
- "os/exec"
- "slices"
- "sync"
-
- "hakurei.app/container"
- "hakurei.app/container/check"
- "hakurei.app/helper/proc"
- "hakurei.app/message"
-)
-
-// New initialises a Helper instance with wt as the null-terminated argument writer.
-func New(
- ctx context.Context,
- msg message.Msg,
- pathname *check.Absolute, name string,
- wt io.WriterTo,
- stat bool,
- argF func(argsFd, statFd int) []string,
- cmdF func(z *container.Container),
- extraFiles []*os.File,
-) Helper {
- var args []string
- h := new(helperContainer)
- h.helperFiles, args = newHelperFiles(ctx, wt, stat, argF, extraFiles)
- h.Container = container.NewCommand(ctx, msg, pathname, name, args...)
- h.WaitDelay = WaitDelay
- if cmdF != nil {
- cmdF(h.Container)
- }
- return h
-}
-
-// helperContainer provides a [sandbox.Container] wrapper around helper ipc.
-type helperContainer struct {
- started bool
-
- mu sync.Mutex
- *helperFiles
- *container.Container
-}
-
-func (h *helperContainer) Start() error {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- if h.started {
- return errors.New("helper: already started")
- }
- h.started = true
-
- h.Env = slices.Grow(h.Env, 2)
- if h.useArgsFd {
- h.Env = append(h.Env, HakureiHelper+"=1")
- } else {
- h.Env = append(h.Env, HakureiHelper+"=0")
- }
- if h.useStatFd {
- h.Env = append(h.Env, HakureiStatus+"=1")
-
- // stat is populated on fulfill
- h.Cancel = func(*exec.Cmd) error { return h.stat.Close() }
- } else {
- h.Env = append(h.Env, HakureiStatus+"=0")
- }
-
- return proc.Fulfill(h.helperFiles.ctx, &h.ExtraFiles, func() error {
- if err := h.Container.Start(); err != nil {
- return err
- }
- return h.Container.Serve()
- }, h.files, h.extraFiles)
-}
diff --git a/helper/container_test.go b/helper/container_test.go
deleted file mode 100644
index 922d8253..00000000
--- a/helper/container_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package helper_test
-
-import (
- "context"
- "io"
- "os"
- "testing"
-
- "hakurei.app/container"
- "hakurei.app/container/check"
- "hakurei.app/container/fhs"
- "hakurei.app/helper"
-)
-
-func TestContainer(t *testing.T) {
- t.Run("start invalid container", func(t *testing.T) {
- h := helper.New(t.Context(), nil, check.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil)
-
- wantErr := "container: starting an invalid container"
- if err := h.Start(); err == nil || err.Error() != wantErr {
- t.Errorf("Start: error = %v, wantErr %q",
- err, wantErr)
- }
- })
-
- t.Run("valid new helper nil check", func(t *testing.T) {
- if got := helper.New(t.Context(), nil, check.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil); got == nil {
- t.Errorf("New(%q, %q) got nil",
- argsWt, "hakurei")
- return
- }
- })
-
- t.Run("implementation compliance", func(t *testing.T) {
- testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper {
- return helper.New(ctx, nil, check.MustAbs(os.Args[0]), "helper", argsWt, stat, argF, func(z *container.Container) {
- setOutput(&z.Stdout, &z.Stderr)
- z.
- Bind(fhs.AbsRoot, fhs.AbsRoot, 0).
- Proc(fhs.AbsProc).
- Dev(fhs.AbsDev, true)
- }, nil)
- })
- })
-}
diff --git a/helper/deprecated.go b/helper/deprecated.go
new file mode 100644
index 00000000..f09b0c84
--- /dev/null
+++ b/helper/deprecated.go
@@ -0,0 +1,73 @@
+// Package helper exposes the internal/helper package.
+//
+// Deprecated: This package will be removed in 0.4.
+package helper
+
+import (
+ "context"
+ "io"
+ "os"
+ "os/exec"
+ "time"
+ _ "unsafe" // for go:linkname
+
+ "hakurei.app/container"
+ "hakurei.app/container/check"
+ "hakurei.app/internal/helper"
+ "hakurei.app/message"
+)
+
+//go:linkname WaitDelay hakurei.app/internal/helper.WaitDelay
+var WaitDelay time.Duration
+
+const (
+ // HakureiHelper is set to 1 when args fd is enabled and 0 otherwise.
+ HakureiHelper = helper.HakureiHelper
+ // HakureiStatus is set to 1 when stat fd is enabled and 0 otherwise.
+ HakureiStatus = helper.HakureiStatus
+)
+
+type Helper = helper.Helper
+
+// NewCheckedArgs returns a checked null-terminated argument writer for a copy of args.
+//
+//go:linkname NewCheckedArgs hakurei.app/internal/helper.NewCheckedArgs
+func NewCheckedArgs(args ...string) (wt io.WriterTo, err error)
+
+// MustNewCheckedArgs returns a checked null-terminated argument writer for a copy of args.
+// If s contains a NUL byte this function panics instead of returning an error.
+//
+//go:linkname MustNewCheckedArgs hakurei.app/internal/helper.MustNewCheckedArgs
+func MustNewCheckedArgs(args ...string) io.WriterTo
+
+// NewDirect initialises a new direct Helper instance with wt as the null-terminated argument writer.
+// Function argF returns an array of arguments passed directly to the child process.
+//
+//go:linkname NewDirect hakurei.app/internal/helper.NewDirect
+func NewDirect(
+ ctx context.Context,
+ name string,
+ wt io.WriterTo,
+ stat bool,
+ argF func(argsFd, statFd int) []string,
+ cmdF func(cmd *exec.Cmd),
+ extraFiles []*os.File,
+) Helper
+
+// New initialises a Helper instance with wt as the null-terminated argument writer.
+//
+//go:linkname New hakurei.app/internal/helper.New
+func New(
+ ctx context.Context,
+ msg message.Msg,
+ pathname *check.Absolute, name string,
+ wt io.WriterTo,
+ stat bool,
+ argF func(argsFd, statFd int) []string,
+ cmdF func(z *container.Container),
+ extraFiles []*os.File,
+) Helper
+
+// InternalHelperStub is an internal function but exported because it is cross-package;
+// it is part of the implementation of the helper stub.
+func InternalHelperStub() { helper.InternalHelperStub() }
diff --git a/helper/helper.go b/helper/helper.go
deleted file mode 100644
index 60bd7615..00000000
--- a/helper/helper.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Package helper runs external helpers with optional sandboxing.
-package helper
-
-import (
- "context"
- "fmt"
- "io"
- "os"
- "time"
-
- "hakurei.app/helper/proc"
-)
-
-var WaitDelay = 2 * time.Second
-
-const (
- // HakureiHelper is set to 1 when args fd is enabled and 0 otherwise.
- HakureiHelper = "HAKUREI_HELPER"
- // HakureiStatus is set to 1 when stat fd is enabled and 0 otherwise.
- HakureiStatus = "HAKUREI_STATUS"
-)
-
-type Helper interface {
- // Start starts the helper process.
- Start() error
- // Wait blocks until Helper exits.
- Wait() error
-
- fmt.Stringer
-}
-
-func newHelperFiles(
- ctx context.Context,
- wt io.WriterTo,
- stat bool,
- argF func(argsFd, statFd int) []string,
- extraFiles []*os.File,
-) (hl *helperFiles, args []string) {
- hl = new(helperFiles)
- hl.ctx = ctx
- hl.useArgsFd = wt != nil
- hl.useStatFd = stat
-
- hl.extraFiles = new(proc.ExtraFilesPre)
- for _, f := range extraFiles {
- _, v := hl.extraFiles.Append()
- *v = f
- }
-
- argsFd := -1
- if hl.useArgsFd {
- f := proc.NewWriterTo(wt)
- argsFd = int(proc.InitFile(f, hl.extraFiles))
- hl.files = append(hl.files, f)
- }
-
- statFd := -1
- if hl.useStatFd {
- f := proc.NewStat(&hl.stat)
- statFd = int(proc.InitFile(f, hl.extraFiles))
- hl.files = append(hl.files, f)
- }
-
- args = argF(argsFd, statFd)
- return
-}
-
-// helperFiles provides a generic wrapper around helper ipc.
-type helperFiles struct {
- // whether argsFd is present
- useArgsFd bool
- // whether statFd is present
- useStatFd bool
-
- // closes statFd
- stat io.Closer
- // deferred extraFiles fulfillment
- files []proc.File
- // passed through to [proc.Fulfill] and [proc.InitFile]
- extraFiles *proc.ExtraFilesPre
-
- ctx context.Context
-}
diff --git a/helper/helper_test.go b/helper/helper_test.go
deleted file mode 100644
index 4cc36d34..00000000
--- a/helper/helper_test.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package helper_test
-
-import (
- "context"
- "errors"
- "fmt"
- "io"
- "os"
- "reflect"
- "strconv"
- "strings"
- "syscall"
- "testing"
- "time"
-
- "hakurei.app/helper"
-)
-
-var (
- wantArgs = []string{
- "unix:path=/run/dbus/system_bus_socket",
- "/tmp/hakurei.1971/12622d846cc3fe7b4c10359d01f0eb47/system_bus_socket",
- "--filter",
- "--talk=org.bluez",
- "--talk=org.freedesktop.Avahi",
- "--talk=org.freedesktop.UPower",
- }
-
- wantPayload = strings.Join(wantArgs, "\x00") + "\x00"
- argsWt = helper.MustNewCheckedArgs(wantArgs...)
-)
-
-func argF(argsFd, statFd int) []string {
- if argsFd == -1 {
- panic("invalid args fd")
- }
-
- return argFChecked(argsFd, statFd)
-}
-
-func argFChecked(argsFd, statFd int) (args []string) {
- args = make([]string, 0, 6)
- if argsFd > -1 {
- args = append(args, "--args", strconv.Itoa(argsFd))
- }
- if statFd > -1 {
- args = append(args, "--fd", strconv.Itoa(statFd))
- }
- return
-}
-
-const (
- containerTimeout = 30 * time.Second
-)
-
-// this function tests an implementation of the helper.Helper interface
-func testHelper(t *testing.T, createHelper func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper) {
- oldWaitDelay := helper.WaitDelay
- helper.WaitDelay = 16 * time.Second
- t.Cleanup(func() { helper.WaitDelay = oldWaitDelay })
-
- t.Run("start helper with status channel and wait", func(t *testing.T) {
- ctx, cancel := context.WithTimeout(t.Context(), containerTimeout)
- stdout := new(strings.Builder)
- h := createHelper(ctx, func(stdoutP, stderrP *io.Writer) { *stdoutP, *stderrP = stdout, os.Stderr }, true)
-
- t.Run("wait not yet started helper", func(t *testing.T) {
- if err := h.Wait(); !reflect.DeepEqual(err, syscall.EINVAL) &&
- !reflect.DeepEqual(err, errors.New("exec: not started")) {
- t.Errorf("Wait: error = %v", err)
- }
- })
-
- t.Log("starting helper stub")
- if err := h.Start(); err != nil {
- t.Errorf("Start: error = %v", err)
- cancel()
- return
- }
- t.Log("cancelling context")
- cancel()
-
- t.Run("start already started helper", func(t *testing.T) {
- wantErr := "helper: already started"
- if err := h.Start(); err != nil && err.Error() != wantErr {
- t.Errorf("Start: error = %v, wantErr %v",
- err, wantErr)
- return
- }
- })
-
- t.Log("waiting on helper")
- if err := h.Wait(); !errors.Is(err, context.Canceled) {
- t.Errorf("Wait: error = %v",
- err)
- }
-
- t.Run("wait already finalised helper", func(t *testing.T) {
- wantErr := "exec: Wait was already called"
- if err := h.Wait(); err != nil && err.Error() != wantErr {
- t.Errorf("Wait: error = %v, wantErr %v",
- err, wantErr)
- return
- }
- })
-
- if got := trimStdout(stdout); got != wantPayload {
- t.Errorf("Start: stdout = %q, want %q",
- got, wantPayload)
- }
- })
-
- t.Run("start helper and wait", func(t *testing.T) {
- ctx, cancel := context.WithTimeout(t.Context(), containerTimeout)
- defer cancel()
- stdout := new(strings.Builder)
- h := createHelper(ctx, func(stdoutP, stderrP *io.Writer) { *stdoutP, *stderrP = stdout, os.Stderr }, false)
-
- if err := h.Start(); err != nil {
- t.Errorf("Start: error = %v",
- err)
- return
- }
-
- if err := h.Wait(); err != nil {
- t.Errorf("Wait: error = %v stdout = %q",
- err, stdout)
- }
-
- if got := trimStdout(stdout); got != wantPayload {
- t.Errorf("Start: stdout = %q, want %q",
- got, wantPayload)
- }
- })
-}
-
-func trimStdout(stdout fmt.Stringer) string {
- return strings.TrimPrefix(stdout.String(), "=== RUN TestHelperInit\n")
-}
diff --git a/helper/proc/deprecated.go b/helper/proc/deprecated.go
new file mode 100644
index 00000000..91897526
--- /dev/null
+++ b/helper/proc/deprecated.go
@@ -0,0 +1,63 @@
+// Deprecated: This package will be removed in 0.4.
+package proc
+
+import (
+ "context"
+ "io"
+ "os"
+ "os/exec"
+ "time"
+ _ "unsafe" // for go:linkname
+
+ "hakurei.app/internal/helper/proc"
+)
+
+//go:linkname FulfillmentTimeout hakurei.app/internal/helper/proc.FulfillmentTimeout
+var FulfillmentTimeout time.Duration
+
+// A File is an extra file with deferred initialisation.
+type File = proc.File
+
+// ExtraFilesPre is a linked list storing addresses of [os.File].
+type ExtraFilesPre = proc.ExtraFilesPre
+
+// Fulfill calls the [File.Fulfill] method on all files, starts cmd and blocks until all fulfillment completes.
+//
+//go:linkname Fulfill hakurei.app/internal/helper/proc.Fulfill
+func Fulfill(ctx context.Context,
+ v *[]*os.File, start func() error,
+ files []File, extraFiles *ExtraFilesPre,
+) (err error)
+
+// InitFile initialises f as part of the slice extraFiles points to,
+// and returns its final fd value.
+//
+//go:linkname InitFile hakurei.app/internal/helper/proc.InitFile
+func InitFile(f File, extraFiles *ExtraFilesPre) (fd uintptr)
+
+// BaseFile implements the Init method of the File interface and provides indirect access to extra file state.
+type BaseFile = proc.BaseFile
+
+//go:linkname ExtraFile hakurei.app/internal/helper/proc.ExtraFile
+func ExtraFile(cmd *exec.Cmd, f *os.File) (fd uintptr)
+
+//go:linkname ExtraFileSlice hakurei.app/internal/helper/proc.ExtraFileSlice
+func ExtraFileSlice(extraFiles *[]*os.File, f *os.File) (fd uintptr)
+
+// NewWriterTo returns a [File] that receives content from wt on fulfillment.
+//
+//go:linkname NewWriterTo hakurei.app/internal/helper/proc.NewWriterTo
+func NewWriterTo(wt io.WriterTo) File
+
+// NewStat returns a [File] implementing the behaviour
+// of the receiving end of xdg-dbus-proxy stat fd.
+//
+//go:linkname NewStat hakurei.app/internal/helper/proc.NewStat
+func NewStat(s *io.Closer) File
+
+var (
+ //go:linkname ErrStatFault hakurei.app/internal/helper/proc.ErrStatFault
+ ErrStatFault error
+ //go:linkname ErrStatRead hakurei.app/internal/helper/proc.ErrStatRead
+ ErrStatRead error
+)
diff --git a/helper/proc/files.go b/helper/proc/files.go
deleted file mode 100644
index 0612718e..00000000
--- a/helper/proc/files.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package proc
-
-import (
- "context"
- "os"
- "os/exec"
- "sync/atomic"
- "syscall"
- "testing"
- "time"
-)
-
-var FulfillmentTimeout = 2 * time.Second
-
-func init() {
- if testing.Testing() {
- FulfillmentTimeout *= 10
- }
-}
-
-// A File is an extra file with deferred initialisation.
-type File interface {
- // Init initialises File state. Init must not be called more than once.
- Init(fd uintptr, v **os.File) uintptr
- // Fd returns the fd value set on initialisation.
- Fd() uintptr
- // ErrCount returns count of error values emitted during fulfillment.
- ErrCount() int
- // Fulfill is called prior to process creation and must populate its corresponding file address.
- // Calls to dispatchErr must match the return value of ErrCount.
- // Fulfill must not be called more than once.
- Fulfill(ctx context.Context, dispatchErr func(error)) error
-}
-
-// ExtraFilesPre is a linked list storing addresses of [os.File].
-type ExtraFilesPre struct {
- n *ExtraFilesPre
- v *os.File
-}
-
-// Append grows the list by one entry and returns an address of the address of [os.File] stored in the new entry.
-func (f *ExtraFilesPre) Append() (uintptr, **os.File) { return f.append(3) }
-
-// Files returns a slice pointing to a continuous segment of memory containing all addresses stored in f in order.
-func (f *ExtraFilesPre) Files() []*os.File { return f.copy(make([]*os.File, 0, f.len())) }
-
-func (f *ExtraFilesPre) append(i uintptr) (uintptr, **os.File) {
- if f.n == nil {
- f.n = new(ExtraFilesPre)
- return i, &f.v
- }
- return f.n.append(i + 1)
-}
-func (f *ExtraFilesPre) len() uintptr {
- if f == nil {
- return 0
- }
- return f.n.len() + 1
-}
-func (f *ExtraFilesPre) copy(e []*os.File) []*os.File {
- if f == nil {
- // the public methods ensure the first call is never nil;
- // the last element is unused, slice it off here
- return e[:len(e)-1]
- }
- return f.n.copy(append(e, f.v))
-}
-
-// Fulfill calls the [File.Fulfill] method on all files, starts cmd and blocks until all fulfillment completes.
-func Fulfill(ctx context.Context,
- v *[]*os.File, start func() error,
- files []File, extraFiles *ExtraFilesPre,
-) (err error) {
- var ecs int
- for _, o := range files {
- ecs += o.ErrCount()
- }
- ec := make(chan error, ecs)
-
- c, cancel := context.WithTimeout(ctx, FulfillmentTimeout)
- defer cancel()
-
- for _, f := range files {
- err = f.Fulfill(c, makeDispatchErr(f, ec))
- if err != nil {
- return
- }
- }
-
- *v = extraFiles.Files()
- if err = start(); err != nil {
- return
- }
-
- for ecs > 0 {
- select {
- case err = <-ec:
- ecs--
- if err != nil {
- break
- }
- case <-ctx.Done():
- err = syscall.ECANCELED
- break
- }
- }
- return
-}
-
-// InitFile initialises f as part of the slice extraFiles points to,
-// and returns its final fd value.
-func InitFile(f File, extraFiles *ExtraFilesPre) (fd uintptr) { return f.Init(extraFiles.Append()) }
-
-// BaseFile implements the Init method of the File interface and provides indirect access to extra file state.
-type BaseFile struct {
- fd uintptr
- v **os.File
-}
-
-func (f *BaseFile) Init(fd uintptr, v **os.File) uintptr {
- if v == nil || fd < 3 {
- panic("invalid extra file initial state")
- }
- if f.v != nil {
- panic("extra file initialised twice")
- }
- f.fd, f.v = fd, v
- return fd
-}
-
-func (f *BaseFile) Fd() uintptr {
- if f.v == nil {
- panic("use of uninitialised extra file")
- }
- return f.fd
-}
-
-func (f *BaseFile) Set(v *os.File) {
- *f.v = v // runtime guards against use before init
-}
-
-func makeDispatchErr(f File, ec chan<- error) func(error) {
- c := new(atomic.Int32)
- c.Store(int32(f.ErrCount()))
- return func(err error) {
- if c.Add(-1) < 0 {
- panic("unexpected error dispatches")
- }
- ec <- err
- }
-}
-
-func ExtraFile(cmd *exec.Cmd, f *os.File) (fd uintptr) {
- return ExtraFileSlice(&cmd.ExtraFiles, f)
-}
-
-func ExtraFileSlice(extraFiles *[]*os.File, f *os.File) (fd uintptr) {
- // ExtraFiles: If non-nil, entry i becomes file descriptor 3+i.
- fd = uintptr(3 + len(*extraFiles))
- *extraFiles = append(*extraFiles, f)
- return
-}
diff --git a/helper/proc/pipe.go b/helper/proc/pipe.go
deleted file mode 100644
index 838c4ae4..00000000
--- a/helper/proc/pipe.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package proc
-
-import (
- "context"
- "errors"
- "io"
- "os"
- "runtime"
-)
-
-// NewWriterTo returns a [File] that receives content from wt on fulfillment.
-func NewWriterTo(wt io.WriterTo) File { return &writeToFile{wt: wt} }
-
-// writeToFile exports the read end of a pipe with data written by an [io.WriterTo].
-type writeToFile struct {
- wt io.WriterTo
- BaseFile
-}
-
-func (f *writeToFile) ErrCount() int { return 3 }
-func (f *writeToFile) Fulfill(ctx context.Context, dispatchErr func(error)) error {
- r, w, err := os.Pipe()
- if err != nil {
- return err
- }
- f.Set(r)
-
- done := make(chan struct{})
- go func() {
- _, err = f.wt.WriteTo(w)
- dispatchErr(err)
- dispatchErr(w.Close())
- close(done)
- runtime.KeepAlive(r)
- }()
- go func() {
- select {
- case <-done:
- dispatchErr(nil)
- case <-ctx.Done():
- dispatchErr(w.Close()) // this aborts WriteTo with file already closed
- runtime.KeepAlive(r)
- }
- }()
-
- return nil
-}
-
-// NewStat returns a [File] implementing the behaviour
-// of the receiving end of xdg-dbus-proxy stat fd.
-func NewStat(s *io.Closer) File { return &statFile{s: s} }
-
-var (
- ErrStatFault = errors.New("generic stat fd fault")
- ErrStatRead = errors.New("unexpected stat behaviour")
-)
-
-// statFile implements xdg-dbus-proxy stat fd behaviour.
-type statFile struct {
- s *io.Closer
- BaseFile
-}
-
-func (f *statFile) ErrCount() int { return 2 }
-func (f *statFile) Fulfill(ctx context.Context, dispatchErr func(error)) error {
- r, w, err := os.Pipe()
- if err != nil {
- return err
- }
- f.Set(w)
-
- done := make(chan struct{})
- go func() {
- defer close(done)
- var n int
-
- n, err = r.Read(make([]byte, 1))
- switch n {
- case -1:
- if err == nil {
- err = ErrStatFault
- }
- dispatchErr(err)
- case 0:
- if err == nil {
- err = ErrStatRead
- }
- dispatchErr(err)
- case 1:
- dispatchErr(err)
- default:
- panic("unreachable")
- }
- runtime.KeepAlive(w)
- }()
-
- go func() {
- select {
- case <-done:
- dispatchErr(nil)
- case <-ctx.Done():
- dispatchErr(r.Close()) // this aborts Read with file already closed
- runtime.KeepAlive(w)
- }
- }()
-
- // this gets closed by the caller
- *f.s = r
- return nil
-}
diff --git a/helper/stub.go b/helper/stub.go
deleted file mode 100644
index c1f97e63..00000000
--- a/helper/stub.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package helper
-
-import (
- "flag"
- "fmt"
- "io"
- "os"
- "strconv"
- "syscall"
-)
-
-// InternalHelperStub is an internal function but exported because it is cross-package;
-// it is part of the implementation of the helper stub.
-func InternalHelperStub() {
- // this test mocks the helper process
- var ap, sp string
- if v, ok := os.LookupEnv(HakureiHelper); !ok {
- return
- } else {
- ap = v
- }
- if v, ok := os.LookupEnv(HakureiStatus); !ok {
- panic(HakureiStatus)
- } else {
- sp = v
- }
-
- genericStub(flagRestoreFiles(1, ap, sp))
-
- os.Exit(0)
-}
-
-func newFile(fd int, name, p string) *os.File {
- present := false
- switch p {
- case "0":
- case "1":
- present = true
- default:
- panic(fmt.Sprintf("%s fd has unexpected presence value %q", name, p))
- }
-
- f := os.NewFile(uintptr(fd), name)
- if !present && f != nil {
- panic(fmt.Sprintf("%s fd set but not present", name))
- }
- if present && f == nil {
- panic(fmt.Sprintf("%s fd preset but unset", name))
- }
-
- return f
-}
-
-func flagRestoreFiles(offset int, ap, sp string) (argsFile, statFile *os.File) {
- argsFd := flag.Int("args", -1, "")
- statFd := flag.Int("fd", -1, "")
- _ = flag.CommandLine.Parse(os.Args[offset:])
- argsFile = newFile(*argsFd, "args", ap)
- statFile = newFile(*statFd, "stat", sp)
- return
-}
-
-func genericStub(argsFile, statFile *os.File) {
- if argsFile != nil {
- // this output is checked by parent
- if _, err := io.Copy(os.Stdout, argsFile); err != nil {
- panic("cannot read args: " + err.Error())
- }
- }
-
- if statFile != nil {
- // simulate status pipe behaviour
- var epoll int
- if fd, err := syscall.EpollCreate1(0); err != nil {
- panic("cannot open epoll fd: " + err.Error())
- } else {
- defer func() {
- if err = syscall.Close(fd); err != nil {
- panic("cannot close epoll fd: " + err.Error())
- }
- }()
- epoll = fd
- }
- if err := syscall.EpollCtl(epoll, syscall.EPOLL_CTL_ADD, int(statFile.Fd()), &syscall.EpollEvent{}); err != nil {
- panic("cannot add status pipe to epoll: " + err.Error())
- }
-
- if _, err := statFile.Write([]byte{'x'}); err != nil {
- panic("cannot write to status pipe: " + err.Error())
- }
-
- // wait for status pipe close
- events := make([]syscall.EpollEvent, 1)
- if _, err := syscall.EpollWait(epoll, events, -1); err != nil {
- panic("cannot poll status pipe: " + err.Error())
- }
- if events[0].Events != syscall.EPOLLERR {
- panic(strconv.Itoa(int(events[0].Events)))
-
- }
- }
-}
diff --git a/helper/stub_test.go b/helper/stub_test.go
deleted file mode 100644
index 58a25383..00000000
--- a/helper/stub_test.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package helper_test
-
-import (
- "os"
- "testing"
-
- "hakurei.app/container"
- "hakurei.app/helper"
-)
-
-func TestMain(m *testing.M) { container.TryArgv0(nil); helper.InternalHelperStub(); os.Exit(m.Run()) }