aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-09-30 00:21:26 +0900
committerOphestra <cat@gensokyo.uk>2025-09-30 00:21:26 +0900
commite58181a930064385c881b7ee1cac4914dd435121 (patch)
treef9ee955ebf054e8a1dcb8d8a3e623a47f0d5ab77 /internal
parent71e70b7b5feb94aab5d99d9a3c2bea54537632c4 (diff)
internal/app/paths: defer extra formatting
This reduces payload size for params to shim. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal')
-rw-r--r--internal/app/dispatcher_test.go26
-rw-r--r--internal/app/finalise.go2
-rw-r--r--internal/app/paths.go53
-rw-r--r--internal/app/paths_test.go129
4 files changed, 195 insertions, 15 deletions
diff --git a/internal/app/dispatcher_test.go b/internal/app/dispatcher_test.go
new file mode 100644
index 00000000..2c7d926f
--- /dev/null
+++ b/internal/app/dispatcher_test.go
@@ -0,0 +1,26 @@
+package app
+
+import (
+ "os"
+ "os/exec"
+
+ "hakurei.app/container"
+)
+
+type panicDispatcher struct{}
+
+func (panicDispatcher) new(func(k syscallDispatcher)) { panic("unreachable") }
+func (panicDispatcher) getuid() int { panic("unreachable") }
+func (panicDispatcher) getgid() int { panic("unreachable") }
+func (panicDispatcher) lookupEnv(string) (string, bool) { panic("unreachable") }
+func (panicDispatcher) stat(string) (os.FileInfo, error) { panic("unreachable") }
+func (panicDispatcher) readdir(string) ([]os.DirEntry, error) { panic("unreachable") }
+func (panicDispatcher) tempdir() string { panic("unreachable") }
+func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") }
+func (panicDispatcher) lookPath(string) (string, error) { panic("unreachable") }
+func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") }
+func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") }
+func (panicDispatcher) overflowUid(container.Msg) int { panic("unreachable") }
+func (panicDispatcher) overflowGid(container.Msg) int { panic("unreachable") }
+func (panicDispatcher) mustHsuPath() *container.Absolute { panic("unreachable") }
+func (panicDispatcher) fatalf(string, ...any) { panic("unreachable") }
diff --git a/internal/app/finalise.go b/internal/app/finalise.go
index 708df056..fbe4bb38 100644
--- a/internal/app/finalise.go
+++ b/internal/app/finalise.go
@@ -275,7 +275,7 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.C
// TODO(ophestra): revert this after params to shim
share := &shareHost{seal: k}
- copyPaths(k.syscallDispatcher, &share.sc, hsu.MustIDMsg(msg))
+ copyPaths(k.syscallDispatcher).Copy(&share.sc, hsu.MustIDMsg(msg))
msg.Verbosef("process share directory at %q, runtime directory at %q", share.sc.SharePath, share.sc.RunDirPath)
var mapuid, mapgid *stringPair[int]
diff --git a/internal/app/paths.go b/internal/app/paths.go
index 267d0eab..4305c665 100644
--- a/internal/app/paths.go
+++ b/internal/app/paths.go
@@ -7,28 +7,53 @@ import (
"hakurei.app/hst"
)
-// CopyPaths populates a [hst.Paths] struct.
-func CopyPaths(v *hst.Paths, userid int) { copyPaths(direct{}, v, userid) }
+// EnvPaths holds paths copied from the environment and is used to create [hst.Paths].
+type EnvPaths struct {
+ // TempDir is returned by [os.TempDir].
+ TempDir *container.Absolute
+ // RuntimePath is copied from $XDG_RUNTIME_DIR.
+ RuntimePath *container.Absolute
+}
+
+// Copy expands [EnvPaths] into [hst.Paths].
+func (env *EnvPaths) Copy(v *hst.Paths, userid int) {
+ if env == nil || env.TempDir == nil || v == nil {
+ panic("attempting to use an invalid EnvPaths")
+ }
-// copyPaths populates a [hst.Paths] struct.
-func copyPaths(k syscallDispatcher, v *hst.Paths, userid int) {
+ v.TempDir = env.TempDir
+ v.SharePath = env.TempDir.Append("hakurei." + strconv.Itoa(userid))
+
+ if env.RuntimePath == nil {
+ // fall back to path in share since hakurei has no hard XDG dependency
+ v.RunDirPath = v.SharePath.Append("run")
+ v.RuntimePath = v.RunDirPath.Append("compat")
+ } else {
+ v.RuntimePath = env.RuntimePath
+ v.RunDirPath = env.RuntimePath.Append("hakurei")
+ }
+}
+
+// CopyPaths returns a populated [EnvPaths].
+func CopyPaths() *EnvPaths { return copyPaths(direct{}) }
+
+// copyPaths returns a populated [EnvPaths].
+func copyPaths(k syscallDispatcher) *EnvPaths {
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
+ var env EnvPaths
+
if tempDir, err := container.NewAbs(k.tempdir()); err != nil {
k.fatalf("invalid TMPDIR: %v", err)
+ panic("unreachable")
} else {
- v.TempDir = tempDir
+ env.TempDir = tempDir
}
- v.SharePath = v.TempDir.Append("hakurei." + strconv.Itoa(userid))
-
r, _ := k.lookupEnv(xdgRuntimeDir)
- if a, err := container.NewAbs(r); err != nil {
- // fall back to path in share since hakurei has no hard XDG dependency
- v.RunDirPath = v.SharePath.Append("run")
- v.RuntimePath = v.RunDirPath.Append("compat")
- } else {
- v.RuntimePath = a
- v.RunDirPath = v.RuntimePath.Append("hakurei")
+ if a, err := container.NewAbs(r); err == nil {
+ env.RuntimePath = a
}
+
+ return &env
}
diff --git a/internal/app/paths_test.go b/internal/app/paths_test.go
new file mode 100644
index 00000000..04e21344
--- /dev/null
+++ b/internal/app/paths_test.go
@@ -0,0 +1,129 @@
+package app
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ "hakurei.app/container"
+ "hakurei.app/container/stub"
+ "hakurei.app/hst"
+)
+
+func TestEnvPaths(t *testing.T) {
+ testCases := []struct {
+ name string
+ env *EnvPaths
+ want hst.Paths
+
+ wantPanic string
+ }{
+ {"nil", nil, hst.Paths{}, "attempting to use an invalid EnvPaths"},
+ {"zero", new(EnvPaths), hst.Paths{}, "attempting to use an invalid EnvPaths"},
+
+ {"nil tempdir", &EnvPaths{
+ RuntimePath: container.AbsFHSTmp,
+ }, hst.Paths{}, "attempting to use an invalid EnvPaths"},
+
+ {"nil runtime", &EnvPaths{
+ TempDir: container.AbsFHSTmp,
+ }, hst.Paths{
+ TempDir: container.AbsFHSTmp,
+ SharePath: container.AbsFHSTmp.Append("hakurei.3735928559"),
+ RuntimePath: container.AbsFHSTmp.Append("hakurei.3735928559/run/compat"),
+ RunDirPath: container.AbsFHSTmp.Append("hakurei.3735928559/run"),
+ }, ""},
+
+ {"full", &EnvPaths{
+ TempDir: container.AbsFHSTmp,
+ RuntimePath: container.AbsFHSRunUser.Append("1000"),
+ }, hst.Paths{
+ TempDir: container.AbsFHSTmp,
+ SharePath: container.AbsFHSTmp.Append("hakurei.3735928559"),
+ RuntimePath: container.AbsFHSRunUser.Append("1000"),
+ RunDirPath: container.AbsFHSRunUser.Append("1000/hakurei"),
+ }, ""},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ if tc.wantPanic != "" {
+ defer func() {
+ if r := recover(); r != tc.wantPanic {
+ t.Errorf("Copy: panic = %#v, want %q", r, tc.wantPanic)
+ }
+ }()
+ }
+
+ var sc hst.Paths
+ tc.env.Copy(&sc, 0xdeadbeef)
+ if !reflect.DeepEqual(&sc, &tc.want) {
+ t.Errorf("Copy: %#v, want %#v", sc, tc.want)
+ }
+ })
+ }
+}
+
+func TestCopyPaths(t *testing.T) {
+ testCases := []struct {
+ name string
+ env map[string]string
+ tmp string
+ fatal string
+ want EnvPaths
+ }{
+ {"invalid tempdir", nil, "\x00",
+ "invalid TMPDIR: path \"\\x00\" is not absolute", EnvPaths{}},
+ {"empty environment", make(map[string]string), container.Nonexistent,
+ "", EnvPaths{TempDir: container.MustAbs(container.Nonexistent)}},
+ {"invalid XDG_RUNTIME_DIR", map[string]string{"XDG_RUNTIME_DIR": "\x00"}, container.Nonexistent,
+ "", EnvPaths{TempDir: container.MustAbs(container.Nonexistent)}},
+ {"full", map[string]string{"XDG_RUNTIME_DIR": "/\x00"}, container.Nonexistent,
+ "", EnvPaths{TempDir: container.MustAbs(container.Nonexistent), RuntimePath: container.MustAbs("/\x00")}},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ if tc.fatal != "" {
+ defer stub.HandleExit(t)
+ }
+
+ k := copyPathsDispatcher{t: t, env: tc.env, tmp: tc.tmp, expectsFatal: tc.fatal}
+ got := copyPaths(k)
+
+ if tc.fatal != "" {
+ t.Fatalf("copyPaths: expected fatal %q", tc.fatal)
+ }
+
+ if !reflect.DeepEqual(got, &tc.want) {
+ t.Errorf("copyPaths: %#v, want %#v", got, &tc.want)
+ }
+ })
+ }
+}
+
+// copyPathsDispatcher implements enough of syscallDispatcher for all copyPaths code paths.
+type copyPathsDispatcher struct {
+ env map[string]string
+ tmp string
+
+ // must be checked at the conclusion of the test
+ expectsFatal string
+
+ t *testing.T
+ panicDispatcher
+}
+
+func (k copyPathsDispatcher) tempdir() string { return k.tmp }
+func (k copyPathsDispatcher) lookupEnv(key string) (value string, ok bool) {
+ value, ok = k.env[key]
+ return
+}
+func (k copyPathsDispatcher) fatalf(format string, v ...any) {
+ if k.expectsFatal == "" {
+ k.t.Fatalf("unexpected call to fatalf: format = %q, v = %#v", format, v)
+ }
+
+ if got := fmt.Sprintf(format, v...); got != k.expectsFatal {
+ k.t.Fatalf("fatalf: %q, want %q", got, k.expectsFatal)
+ }
+ panic(stub.PanicExit)
+}