aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-10-31 22:34:38 +0900
committerOphestra <cat@gensokyo.uk>2025-10-31 22:39:02 +0900
commit68ff0a2ba624fc9c87a6feb331a01f64cdef4209 (patch)
tree6ba673174995b8c4c3d35460625cc90af4b2d85d
parent6a0ecced9021482f1f2f05d6ec63c1e4cd33c8a0 (diff)
container/params: expose pipe
This increases flexibility of how caller wants to handle the I/O. Also makes it no longer rely on finalizer. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--container/container.go31
-rw-r--r--container/params.go4
-rw-r--r--container/params_test.go9
-rw-r--r--internal/outcome/process.go38
4 files changed, 44 insertions, 38 deletions
diff --git a/container/container.go b/container/container.go
index 55e4c441..3ba87dad 100644
--- a/container/container.go
+++ b/container/container.go
@@ -25,6 +25,9 @@ const (
// CancelSignal is the signal expected by container init on context cancel.
// A custom [Container.Cancel] function must eventually deliver this signal.
CancelSignal = SIGUSR2
+
+ // Timeout for writing initParams to Container.setup.
+ initSetupTimeout = 5 * time.Second
)
type (
@@ -37,8 +40,8 @@ type (
// with behaviour identical to its [exec.Cmd] counterpart.
ExtraFiles []*os.File
- // param encoder for shim and init
- setup *gob.Encoder
+ // param pipe for shim and init
+ setup *os.File
// cancels cmd
cancel context.CancelFunc
// closed after Wait returns
@@ -228,10 +231,10 @@ func (p *Container) Start() error {
}
// place setup pipe before user supplied extra files, this is later restored by init
- if fd, e, err := Setup(&p.cmd.ExtraFiles); err != nil {
+ if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil {
return &StartError{true, "set up params stream", err, false, false}
} else {
- p.setup = e
+ p.setup = f
p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)}
}
p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, p.ExtraFiles...)
@@ -310,6 +313,9 @@ func (p *Container) Serve() error {
setup := p.setup
p.setup = nil
+ if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil {
+ return &StartError{true, "set init pipe deadline", err, false, true}
+ }
if p.Path == nil {
p.cancel()
@@ -324,15 +330,14 @@ func (p *Container) Serve() error {
p.SeccompRules = make([]seccomp.NativeRule, 0)
}
- err := setup.Encode(
- &initParams{
- p.Params,
- Getuid(),
- Getgid(),
- len(p.ExtraFiles),
- p.msg.IsVerbose(),
- },
- )
+ err := gob.NewEncoder(setup).Encode(&initParams{
+ p.Params,
+ Getuid(),
+ Getgid(),
+ len(p.ExtraFiles),
+ p.msg.IsVerbose(),
+ })
+ _ = setup.Close()
if err != nil {
p.cancel()
}
diff --git a/container/params.go b/container/params.go
index a48eabaf..46c3b9b2 100644
--- a/container/params.go
+++ b/container/params.go
@@ -9,13 +9,13 @@ import (
)
// Setup appends the read end of a pipe for setup params transmission and returns its fd.
-func Setup(extraFiles *[]*os.File) (int, *gob.Encoder, error) {
+func Setup(extraFiles *[]*os.File) (int, *os.File, error) {
if r, w, err := os.Pipe(); err != nil {
return -1, nil, err
} else {
fd := 3 + len(*extraFiles)
*extraFiles = append(*extraFiles, r)
- return fd, gob.NewEncoder(w), nil
+ return fd, w, nil
}
}
diff --git a/container/params_test.go b/container/params_test.go
index a65f686a..f74abf1c 100644
--- a/container/params_test.go
+++ b/container/params_test.go
@@ -1,6 +1,7 @@
package container_test
import (
+ "encoding/gob"
"errors"
"os"
"slices"
@@ -59,12 +60,16 @@ func TestSetupReceive(t *testing.T) {
encoderDone := make(chan error, 1)
extraFiles := make([]*os.File, 0, 1)
- if fd, encoder, err := container.Setup(&extraFiles); err != nil {
+ deadline, _ := t.Deadline()
+ if fd, f, err := container.Setup(&extraFiles); err != nil {
t.Fatalf("Setup: error = %v", err)
} else if fd != 3 {
t.Fatalf("Setup: fd = %d, want 3", fd)
} else {
- go func() { encoderDone <- encoder.Encode(payload) }()
+ if err = f.SetDeadline(deadline); err != nil {
+ t.Fatal(err.Error())
+ }
+ go func() { encoderDone <- gob.NewEncoder(f).Encode(payload) }()
}
if len(extraFiles) != 1 {
diff --git a/internal/outcome/process.go b/internal/outcome/process.go
index 4ef49b0c..64d83624 100644
--- a/internal/outcome/process.go
+++ b/internal/outcome/process.go
@@ -20,8 +20,12 @@ import (
"hakurei.app/system"
)
-// Duration to wait for shim to exit on top of container WaitDelay.
-const shimWaitTimeout = 5 * time.Second
+const (
+ // Duration to wait for shim to exit on top of container WaitDelay.
+ shimWaitTimeout = 5 * time.Second
+ // Timeout for writing outcomeState to the shim setup pipe.
+ shimSetupTimeout = 5 * time.Second
+)
// mainState holds persistent state bound to outcome.main.
type mainState struct {
@@ -214,7 +218,7 @@ func (k *outcome) main(msg message.Msg) {
hsuPath := internal.MustHsuPath()
// ms.beforeExit required beyond this point
- ms := &mainState{Msg: msg, k: k}
+ ms := mainState{Msg: msg, k: k}
if err := k.sys.Commit(); err != nil {
ms.fatal("cannot commit system setup:", err)
@@ -232,11 +236,12 @@ func (k *outcome) main(msg message.Msg) {
// shim runs in the same session as monitor; see shim.go for behaviour
ms.cmd.Cancel = func() error { return ms.cmd.Process.Signal(syscall.SIGCONT) }
- var e *gob.Encoder
- if fd, encoder, err := container.Setup(&ms.cmd.ExtraFiles); err != nil {
+ var shimPipe *os.File
+ if fd, w, err := container.Setup(&ms.cmd.ExtraFiles); err != nil {
ms.fatal("cannot create shim setup pipe:", err)
+ panic("unreachable")
} else {
- e = encoder
+ shimPipe = w
ms.cmd.Env = []string{
// passed through to shim by hsu
shimEnv + "=" + strconv.Itoa(fd),
@@ -262,23 +267,14 @@ func (k *outcome) main(msg message.Msg) {
go func() { ms.cmdWait <- ms.cmd.Wait(); cancel() }()
ms.Time = &startTime
- // unfortunately the I/O here cannot be directly canceled;
- // the cancellation path leads to fatal in this case so that is fine
- select {
- case err := <-func() (setupErr chan error) {
- setupErr = make(chan error, 1)
- go func() { setupErr <- e.Encode(k.state) }()
- return
- }():
- if err != nil {
- msg.Resume()
- ms.fatal("cannot transmit shim config:", err)
- }
-
- case <-ctx.Done():
+ if err := shimPipe.SetDeadline(time.Now().Add(shimSetupTimeout)); err != nil {
+ msg.Verbose(err.Error())
+ }
+ if err := gob.NewEncoder(shimPipe).Encode(k.state); err != nil {
msg.Resume()
- ms.fatal("shim context canceled:", newWithMessageError("shim setup canceled", ctx.Err()))
+ ms.fatal("cannot transmit shim config:", err)
}
+ _ = shimPipe.Close()
// shim accepted setup payload, create process state
if ok, err := ms.store.Do(k.state.identity.unwrap(), func(c store.Cursor) {