From a5f0aa3f3077cb7bf3cbf7ac9e707ba165aceb4a Mon Sep 17 00:00:00 2001 From: Ophestra Date: Fri, 3 Oct 2025 16:59:29 +0900 Subject: internal/app: declutter and merge small files This should make internal/app easier to work with for the upcoming params to shim. Signed-off-by: Ophestra --- internal/app/env.go | 59 +++++++++++++++++++++ internal/app/env_test.go | 129 +++++++++++++++++++++++++++++++++++++++++++++ internal/app/finalise.go | 11 ++++ internal/app/path.go | 6 ++- internal/app/paths.go | 59 --------------------- internal/app/paths_test.go | 129 --------------------------------------------- internal/app/strings.go | 16 ------ 7 files changed, 204 insertions(+), 205 deletions(-) create mode 100644 internal/app/env.go create mode 100644 internal/app/env_test.go delete mode 100644 internal/app/paths.go delete mode 100644 internal/app/paths_test.go delete mode 100644 internal/app/strings.go (limited to 'internal/app') diff --git a/internal/app/env.go b/internal/app/env.go new file mode 100644 index 00000000..4305c665 --- /dev/null +++ b/internal/app/env.go @@ -0,0 +1,59 @@ +package app + +import ( + "strconv" + + "hakurei.app/container" + "hakurei.app/hst" +) + +// 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") + } + + 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 { + env.TempDir = tempDir + } + + r, _ := k.lookupEnv(xdgRuntimeDir) + if a, err := container.NewAbs(r); err == nil { + env.RuntimePath = a + } + + return &env +} diff --git a/internal/app/env_test.go b/internal/app/env_test.go new file mode 100644 index 00000000..04e21344 --- /dev/null +++ b/internal/app/env_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) +} diff --git a/internal/app/finalise.go b/internal/app/finalise.go index a33bcc91..d2459421 100644 --- a/internal/app/finalise.go +++ b/internal/app/finalise.go @@ -26,6 +26,17 @@ import ( "hakurei.app/system/wayland" ) +func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} } + +// stringPair stores a value and its string representation. +type stringPair[T comparable] struct { + v T + s string +} + +func (s *stringPair[T]) unwrap() T { return s.v } +func (s *stringPair[T]) String() string { return s.s } + func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) } func newWithMessageError(msg string, err error) error { return &hst.AppError{Step: "finalise", Err: err, Msg: msg} diff --git a/internal/app/path.go b/internal/app/path.go index 56e373d9..493dfe39 100644 --- a/internal/app/path.go +++ b/internal/app/path.go @@ -6,6 +6,10 @@ import ( ) func deepContainsH(basepath, targpath string) (bool, error) { + const upper = ".." + string(filepath.Separator) + rel, err := filepath.Rel(basepath, targpath) - return err == nil && rel != ".." && !strings.HasPrefix(rel, string([]byte{'.', '.', filepath.Separator})), err + return err == nil && + rel != ".." && + !strings.HasPrefix(rel, upper), err } diff --git a/internal/app/paths.go b/internal/app/paths.go deleted file mode 100644 index 4305c665..00000000 --- a/internal/app/paths.go +++ /dev/null @@ -1,59 +0,0 @@ -package app - -import ( - "strconv" - - "hakurei.app/container" - "hakurei.app/hst" -) - -// 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") - } - - 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 { - env.TempDir = tempDir - } - - r, _ := k.lookupEnv(xdgRuntimeDir) - 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 deleted file mode 100644 index 04e21344..00000000 --- a/internal/app/paths_test.go +++ /dev/null @@ -1,129 +0,0 @@ -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) -} diff --git a/internal/app/strings.go b/internal/app/strings.go deleted file mode 100644 index a7fdcd46..00000000 --- a/internal/app/strings.go +++ /dev/null @@ -1,16 +0,0 @@ -package app - -import ( - "strconv" -) - -func newInt(v int) *stringPair[int] { return &stringPair[int]{v, strconv.Itoa(v)} } - -// stringPair stores a value and its string representation. -type stringPair[T comparable] struct { - v T - s string -} - -func (s *stringPair[T]) unwrap() T { return s.v } -func (s *stringPair[T]) String() string { return s.s } -- cgit v1.3.1