aboutsummaryrefslogtreecommitdiffhomepage
path: root/hst
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-09-15 01:44:43 +0900
committerOphestra <cat@gensokyo.uk>2025-09-15 01:44:43 +0900
commit8690419c2d577a27efe3cfcafc39c05b7646926b (patch)
treec792c397d9026c01cecb3a7610d9ad9f7149f1fe /hst
parent1cdc6b4246ac82adb0c9ed7620d3f7225f1a3e7f (diff)
hst: replace internal/app error
This turns out to still be quite useful across internal/app and its relatives. Perhaps a cleaner replacement for baseError. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'hst')
-rw-r--r--hst/hst.go30
-rw-r--r--hst/hst_test.go82
2 files changed, 112 insertions, 0 deletions
diff --git a/hst/hst.go b/hst/hst.go
index d9c1168b..c9a23d7a 100644
--- a/hst/hst.go
+++ b/hst/hst.go
@@ -2,12 +2,42 @@
package hst
import (
+ "errors"
+ "net"
+ "os"
+
"hakurei.app/container"
"hakurei.app/container/seccomp"
"hakurei.app/system"
"hakurei.app/system/dbus"
)
+// An AppError is returned while starting an app according to [hst.Config].
+type AppError struct {
+ Step string
+ Err error
+ Msg string
+}
+
+func (e *AppError) Error() string { return e.Err.Error() }
+func (e *AppError) Unwrap() error { return e.Err }
+func (e *AppError) Message() string {
+ if e.Msg != "" {
+ return e.Msg
+ }
+
+ switch {
+ case errors.As(e.Err, new(*os.PathError)),
+ errors.As(e.Err, new(*os.LinkError)),
+ errors.As(e.Err, new(*os.SyscallError)),
+ errors.As(e.Err, new(*net.OpError)):
+ return "cannot " + e.Error()
+
+ default:
+ return "cannot " + e.Step + ": " + e.Error()
+ }
+}
+
// Paths contains environment-dependent paths used by hakurei.
type Paths struct {
// temporary directory returned by [os.TempDir] (usually `/tmp`)
diff --git a/hst/hst_test.go b/hst/hst_test.go
index 04057212..70f7e531 100644
--- a/hst/hst_test.go
+++ b/hst/hst_test.go
@@ -2,11 +2,93 @@ package hst_test
import (
"encoding/json"
+ "errors"
+ "net"
+ "os"
+ "syscall"
"testing"
+ "hakurei.app/container"
+ "hakurei.app/container/stub"
"hakurei.app/hst"
)
+func TestAppError(t *testing.T) {
+ testCases := []struct {
+ name string
+ err error
+ s string
+ message string
+ is, isF error
+ }{
+ {"message", &hst.AppError{Step: "obtain uid from hsu", Err: stub.UniqueError(0),
+ Msg: "the setuid helper is missing: /run/wrappers/bin/hsu"},
+ "unique error 0 injected by the test suite",
+ "the setuid helper is missing: /run/wrappers/bin/hsu",
+ stub.UniqueError(0), os.ErrNotExist},
+
+ {"os.PathError", &hst.AppError{Step: "passthrough os.PathError",
+ Err: &os.PathError{Op: "stat", Path: "/proc/nonexistent", Err: os.ErrNotExist}},
+ "stat /proc/nonexistent: file does not exist",
+ "cannot stat /proc/nonexistent: file does not exist",
+ os.ErrNotExist, stub.UniqueError(0xdeadbeef)},
+
+ {"os.LinkError", &hst.AppError{Step: "passthrough os.LinkError",
+ Err: &os.LinkError{Op: "link", Old: "/proc/self", New: "/proc/nonexistent", Err: os.ErrNotExist}},
+ "link /proc/self /proc/nonexistent: file does not exist",
+ "cannot link /proc/self /proc/nonexistent: file does not exist",
+ os.ErrNotExist, stub.UniqueError(0xdeadbeef)},
+
+ {"os.SyscallError", &hst.AppError{Step: "passthrough os.SyscallError",
+ Err: &os.SyscallError{Syscall: "meow", Err: syscall.ENOSYS}},
+ "meow: function not implemented",
+ "cannot meow: function not implemented",
+ syscall.ENOSYS, syscall.ENOTRECOVERABLE},
+
+ {"net.OpError", &hst.AppError{Step: "passthrough net.OpError",
+ Err: &net.OpError{Op: "dial", Net: "cat", Err: net.UnknownNetworkError("cat")}},
+ "dial cat: unknown network cat",
+ "cannot dial cat: unknown network cat",
+ net.UnknownNetworkError("cat"), syscall.ENOTRECOVERABLE},
+
+ {"default", &hst.AppError{Step: "initialise container configuration", Err: stub.UniqueError(1)},
+ "unique error 1 injected by the test suite",
+ "cannot initialise container configuration: unique error 1 injected by the test suite",
+ stub.UniqueError(1), os.ErrInvalid},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Run("error", func(t *testing.T) {
+ if got := tc.err.Error(); got != tc.s {
+ t.Errorf("Error: %s, want %s", got, tc.s)
+ }
+ })
+
+ t.Run("message", func(t *testing.T) {
+ gotMessage, gotMessageOk := container.GetErrorMessage(tc.err)
+ if want := tc.message != "\x00"; gotMessageOk != want {
+ t.Errorf("GetErrorMessage: ok = %v, want %v", gotMessage, want)
+ }
+
+ if gotMessageOk {
+ if gotMessage != tc.message {
+ t.Errorf("GetErrorMessage: %s, want %s", gotMessage, tc.message)
+ }
+ }
+ })
+
+ t.Run("is", func(t *testing.T) {
+ if !errors.Is(tc.err, tc.is) {
+ t.Errorf("Is: unexpected false for %v", tc.is)
+ }
+ if errors.Is(tc.err, tc.isF) {
+ t.Errorf("Is: unexpected true for %v", tc.isF)
+ }
+ })
+ })
+ }
+}
+
func TestTemplate(t *testing.T) {
const want = `{
"id": "org.chromium.Chromium",