aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-10-08 19:48:04 +0900
committerOphestra <cat@gensokyo.uk>2025-10-08 20:02:09 +0900
commite5baaf416f1404a4a9ae8581603524d868716325 (patch)
tree944ef5ffc3c1a094a6ba21ff075a26e16618cd76
parentee6c471fe6e183d78e396a23b2a92fd26bb31143 (diff)
internal/app: check transmitted ops
This simulates params to shim and this is the last step before params to shim is merged. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--internal/app/app_test.go91
-rw-r--r--internal/app/outcome.go2
-rw-r--r--internal/app/spaccount.go3
-rw-r--r--internal/app/spcontainer.go5
-rw-r--r--internal/app/spdbus.go4
-rw-r--r--internal/app/spfinal.go3
-rw-r--r--internal/app/sppulse.go3
-rw-r--r--internal/app/spruntime.go4
-rw-r--r--internal/app/sptmpdir.go4
-rw-r--r--internal/app/spwayland.go4
-rw-r--r--internal/app/spx11.go3
11 files changed, 120 insertions, 6 deletions
diff --git a/internal/app/app_test.go b/internal/app/app_test.go
index 90c45792..f40b0923 100644
--- a/internal/app/app_test.go
+++ b/internal/app/app_test.go
@@ -2,11 +2,14 @@ package app
import (
"bytes"
+ "encoding/gob"
"encoding/json"
+ "errors"
"fmt"
"io"
"io/fs"
"log"
+ "maps"
"os/exec"
"os/user"
"reflect"
@@ -447,21 +450,101 @@ func TestApp(t *testing.T) {
err := seal.finalise(t.Context(), msg, &tc.id, tc.config)
if err != nil {
if s, ok := container.GetErrorMessage(err); !ok {
- t.Fatalf("Seal: error = %v", err)
+ t.Fatalf("outcome: error = %v", err)
} else {
- t.Fatalf("Seal: %s", s)
+ t.Fatalf("outcome: %s", s)
}
}
t.Run("sys", func(t *testing.T) {
if !seal.sys.Equal(tc.wantSys) {
- t.Errorf("Seal: sys = %#v, want %#v", seal.sys, tc.wantSys)
+ t.Errorf("outcome: sys = %#v, want %#v", seal.sys, tc.wantSys)
}
})
t.Run("params", func(t *testing.T) {
if !reflect.DeepEqual(&seal.container, tc.wantParams) {
- t.Errorf("seal: container =\n%s\n, want\n%s", mustMarshal(&seal.container), mustMarshal(tc.wantParams))
+ t.Errorf("outcome: container =\n%s\n, want\n%s", mustMarshal(&seal.container), mustMarshal(tc.wantParams))
+ }
+ })
+ })
+
+ t.Run("ops", func(t *testing.T) {
+ // copied from shim
+ const envAllocSize = 1 << 6
+
+ gr, gw := io.Pipe()
+
+ var gotSys *system.I
+ {
+ sPriv := outcomeState{
+ ID: &tc.id,
+ Identity: tc.config.Identity,
+ UserID: (&Hsu{k: tc.k}).MustIDMsg(msg),
+ EnvPaths: copyPaths(tc.k),
+ Container: tc.config.Container,
+ }
+
+ sPriv.populateEarly(tc.k, msg)
+ if err := sPriv.populateLocal(tc.k, msg); err != nil {
+ t.Fatalf("populateLocal: error = %#v", err)
+ }
+
+ gotSys = system.New(t.Context(), msg, sPriv.uid.unwrap())
+ opsPriv := fromConfig(tc.config)
+ stateSys := outcomeStateSys{sys: gotSys, outcomeState: &sPriv}
+ for _, op := range opsPriv {
+ if err := op.toSystem(&stateSys, tc.config); err != nil {
+ t.Fatalf("toSystem: error = %#v", err)
+ }
+ }
+
+ go func() {
+ e := gob.NewEncoder(gw)
+ if err := errors.Join(e.Encode(&sPriv), e.Encode(&opsPriv)); err != nil {
+ t.Errorf("Encode: error = %v", err)
+ panic("unexpected encode fault")
+ }
+ }()
+ }
+
+ var gotParams container.Params
+ {
+ var (
+ sShim outcomeState
+ opsShim []outcomeOp
+ )
+
+ d := gob.NewDecoder(gr)
+ if err := errors.Join(d.Decode(&sShim), d.Decode(&opsShim)); err != nil {
+ t.Fatalf("Decode: error = %v", err)
+ }
+ if err := sShim.populateLocal(tc.k, msg); err != nil {
+ t.Fatalf("populateLocal: error = %#v", err)
+ }
+
+ stateParams := outcomeStateParams{params: &gotParams, outcomeState: &sShim}
+ if sShim.Container.Env == nil {
+ stateParams.env = make(map[string]string, envAllocSize)
+ } else {
+ stateParams.env = maps.Clone(sShim.Container.Env)
+ }
+ for _, op := range opsShim {
+ if err := op.toContainer(&stateParams); err != nil {
+ t.Fatalf("toContainer: error = %#v", err)
+ }
+ }
+ }
+
+ t.Run("sys", func(t *testing.T) {
+ if !gotSys.Equal(tc.wantSys) {
+ t.Errorf("toSystem: sys = %#v, want %#v", gotSys, tc.wantSys)
+ }
+ })
+
+ t.Run("params", func(t *testing.T) {
+ if !reflect.DeepEqual(&gotParams, tc.wantParams) {
+ t.Errorf("toContainer: params =\n%s\n, want\n%s", mustMarshal(&gotParams), mustMarshal(tc.wantParams))
}
})
})
diff --git a/internal/app/outcome.go b/internal/app/outcome.go
index 671d14c0..ef7c7807 100644
--- a/internal/app/outcome.go
+++ b/internal/app/outcome.go
@@ -194,8 +194,6 @@ type outcomeStateParams struct {
*outcomeState
}
-// TODO(ophestra): register outcomeOp implementations (params to shim)
-
// An outcomeOp inflicts an outcome on [system.I] and contains enough information to
// inflict it on [container.Params] in a separate process.
// An implementation of outcomeOp must store cross-process states in exported fields only.
diff --git a/internal/app/spaccount.go b/internal/app/spaccount.go
index 0927bcc3..6976e80a 100644
--- a/internal/app/spaccount.go
+++ b/internal/app/spaccount.go
@@ -1,6 +1,7 @@
package app
import (
+ "encoding/gob"
"fmt"
"syscall"
@@ -8,6 +9,8 @@ import (
"hakurei.app/hst"
)
+func init() { gob.Register(spAccountOp{}) }
+
// spAccountOp sets up user account emulation inside the container.
type spAccountOp struct{}
diff --git a/internal/app/spcontainer.go b/internal/app/spcontainer.go
index bce0e200..3827fa2e 100644
--- a/internal/app/spcontainer.go
+++ b/internal/app/spcontainer.go
@@ -1,6 +1,7 @@
package app
import (
+ "encoding/gob"
"errors"
"io/fs"
"os"
@@ -19,6 +20,8 @@ import (
const varRunNscd = fhs.Var + "run/nscd"
+func init() { gob.Register(new(spParamsOp)) }
+
// spParamsOp initialises unordered fields of [container.Params] and the optional root filesystem.
// This outcomeOp is hardcoded to always run first.
type spParamsOp struct {
@@ -113,6 +116,8 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error {
return nil
}
+func init() { gob.Register(spFilesystemOp{}) }
+
// spFilesystemOp applies configured filesystems to [container.Params], excluding the optional root filesystem.
type spFilesystemOp struct{}
diff --git a/internal/app/spdbus.go b/internal/app/spdbus.go
index 77c2dcdc..81e05957 100644
--- a/internal/app/spdbus.go
+++ b/internal/app/spdbus.go
@@ -1,12 +1,16 @@
package app
import (
+ "encoding/gob"
+
"hakurei.app/container/fhs"
"hakurei.app/hst"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
)
+func init() { gob.Register(new(spDBusOp)) }
+
// spDBusOp maintains an xdg-dbus-proxy instance for the container.
type spDBusOp struct {
// Whether to bind the system bus socket.
diff --git a/internal/app/spfinal.go b/internal/app/spfinal.go
index c8255080..b64d0806 100644
--- a/internal/app/spfinal.go
+++ b/internal/app/spfinal.go
@@ -1,6 +1,7 @@
package app
import (
+ "encoding/gob"
"fmt"
"slices"
"strings"
@@ -12,6 +13,8 @@ import (
"hakurei.app/system/acl"
)
+func init() { gob.Register(spFinal{}) }
+
// spFinal is a transitional op destined for removal after #3, #8, #9 has been resolved.
// It exists to avoid reordering the expected entries in test cases.
type spFinal struct{}
diff --git a/internal/app/sppulse.go b/internal/app/sppulse.go
index a6dba590..bf79ae51 100644
--- a/internal/app/sppulse.go
+++ b/internal/app/sppulse.go
@@ -1,6 +1,7 @@
package app
import (
+ "encoding/gob"
"errors"
"fmt"
"io"
@@ -14,6 +15,8 @@ import (
const pulseCookieSizeMax = 1 << 8
+func init() { gob.Register(new(spPulseOp)) }
+
// spPulseOp exports the PulseAudio server to the container.
type spPulseOp struct {
// PulseAudio cookie data, populated during toSystem if a cookie is present.
diff --git a/internal/app/spruntime.go b/internal/app/spruntime.go
index d327003f..87decd11 100644
--- a/internal/app/spruntime.go
+++ b/internal/app/spruntime.go
@@ -1,6 +1,8 @@
package app
import (
+ "encoding/gob"
+
"hakurei.app/container/bits"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
@@ -9,6 +11,8 @@ import (
"hakurei.app/system/acl"
)
+func init() { gob.Register(spRuntimeOp{}) }
+
// spRuntimeOp sets up XDG_RUNTIME_DIR inside the container.
type spRuntimeOp struct{}
diff --git a/internal/app/sptmpdir.go b/internal/app/sptmpdir.go
index 4cc0c7da..8f8dd85e 100644
--- a/internal/app/sptmpdir.go
+++ b/internal/app/sptmpdir.go
@@ -1,6 +1,8 @@
package app
import (
+ "encoding/gob"
+
"hakurei.app/container/bits"
"hakurei.app/container/check"
"hakurei.app/container/fhs"
@@ -9,6 +11,8 @@ import (
"hakurei.app/system/acl"
)
+func init() { gob.Register(spTmpdirOp{}) }
+
// spTmpdirOp sets up TMPDIR inside the container.
type spTmpdirOp struct{}
diff --git a/internal/app/spwayland.go b/internal/app/spwayland.go
index 91ce1ac3..efbd8afc 100644
--- a/internal/app/spwayland.go
+++ b/internal/app/spwayland.go
@@ -1,12 +1,16 @@
package app
import (
+ "encoding/gob"
+
"hakurei.app/container/check"
"hakurei.app/hst"
"hakurei.app/system/acl"
"hakurei.app/system/wayland"
)
+func init() { gob.Register(new(spWaylandOp)) }
+
// spWaylandOp exports the Wayland display server to the container.
type spWaylandOp struct {
// Path to host wayland socket. Populated during toSystem if DirectWayland is true.
diff --git a/internal/app/spx11.go b/internal/app/spx11.go
index 18890fd8..ba7a768a 100644
--- a/internal/app/spx11.go
+++ b/internal/app/spx11.go
@@ -1,6 +1,7 @@
package app
import (
+ "encoding/gob"
"errors"
"fmt"
"io/fs"
@@ -15,6 +16,8 @@ import (
var absX11SocketDir = fhs.AbsTmp.Append(".X11-unix")
+func init() { gob.Register(new(spX11Op)) }
+
// spX11Op exports the X11 display server to the container.
type spX11Op struct {
// Value of $DISPLAY, stored during toSystem