aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-08-29 01:37:13 +0900
committerOphestra <cat@gensokyo.uk>2025-08-29 01:37:13 +0900
commitf24dd4ab8c3f1440654f9c5c18e50f03fdf85e27 (patch)
treec59d04453fa1859f6f1c4fec2110f53fcea339dc
parenta462341a0a3e1132f3fcdb1efb0f7a56e851a9e1 (diff)
container/init: handle unwrapped errors
This is much cleaner from both the return statement and the error handling. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--container/errors.go43
-rw-r--r--container/errors_test.go46
-rw-r--r--container/init.go24
3 files changed, 105 insertions, 8 deletions
diff --git a/container/errors.go b/container/errors.go
index 12cb3b95..1a828e0f 100644
--- a/container/errors.go
+++ b/container/errors.go
@@ -6,6 +6,49 @@ import (
"syscall"
)
+// messageFromError returns a printable error message for a supported concrete type.
+func messageFromError(err error) (string, bool) {
+ if m, ok := messagePrefixP[MountError, *MountError]("cannot ", err); ok {
+ return m, ok
+ }
+ if m, ok := messagePrefixP[os.PathError, *os.PathError]("cannot ", err); ok {
+ return m, ok
+ }
+ if m, ok := messagePrefixP[AbsoluteError, *AbsoluteError]("", err); ok {
+ return m, ok
+ }
+ if m, ok := messagePrefix[OpRepeatError]("", err); ok {
+ return m, ok
+ }
+ if m, ok := messagePrefix[OpStateError]("", err); ok {
+ return m, ok
+ }
+
+ return zeroString, false
+}
+
+// messagePrefix checks and prefixes the error message of a non-pointer error.
+// While this is usable for pointer errors, such use should be avoided as nil check is omitted.
+func messagePrefix[T error](prefix string, err error) (string, bool) {
+ var targetError T
+ if errors.As(err, &targetError) {
+ return prefix + targetError.Error(), true
+ }
+ return zeroString, false
+}
+
+// messagePrefixP checks and prefixes the error message of a pointer error.
+func messagePrefixP[V any, T interface {
+ *V
+ error
+}](prefix string, err error) (string, bool) {
+ var targetError T
+ if errors.As(err, &targetError) && targetError != nil {
+ return prefix + targetError.Error(), true
+ }
+ return zeroString, false
+}
+
type MountError struct {
Source, Target, Fstype string
diff --git a/container/errors_test.go b/container/errors_test.go
index 3d327430..eae5ee7d 100644
--- a/container/errors_test.go
+++ b/container/errors_test.go
@@ -8,6 +8,52 @@ import (
"testing"
)
+func TestMessageFromError(t *testing.T) {
+ testCases := []struct {
+ name string
+ err error
+ want string
+ wantOk bool
+ }{
+ {"mount", &MountError{
+ Source: SourceTmpfsEphemeral,
+ Target: "/sysroot/tmp",
+ Fstype: FstypeTmpfs,
+ Flags: syscall.MS_NOSUID | syscall.MS_NODEV,
+ Data: zeroString,
+ Errno: syscall.EINVAL,
+ }, "cannot mount tmpfs on /sysroot/tmp: invalid argument", true},
+
+ {"path", &os.PathError{
+ Op: "mount",
+ Path: "/sysroot",
+ Err: errUnique,
+ }, "cannot mount /sysroot: unique error injected by the test suite", true},
+
+ {"absolute", &AbsoluteError{"etc/mtab"},
+ `path "etc/mtab" is not absolute`, true},
+
+ {"repeat", OpRepeatError("autoetc"),
+ "autoetc is not repeatable", true},
+
+ {"state", OpStateError("overlay"),
+ "impossible overlay state reached", true},
+
+ {"unsupported", errUnique, zeroString, false},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ got, ok := messageFromError(tc.err)
+ if got != tc.want {
+ t.Errorf("messageFromError: %q, want %q", got, tc.want)
+ }
+ if ok != tc.wantOk {
+ t.Errorf("messageFromError: ok = %v, want %v", ok, tc.wantOk)
+ }
+ })
+ }
+}
+
func TestMountError(t *testing.T) {
testCases := []struct {
name string
diff --git a/container/init.go b/container/init.go
index 8b0a7a26..e56b4cf0 100644
--- a/container/init.go
+++ b/container/init.go
@@ -184,10 +184,14 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
}
if err := op.early(state, k); err != nil {
- k.printBaseErr(err,
- fmt.Sprintf("cannot prepare op at index %d:", i))
- k.beforeExit()
- k.exit(1)
+ if m, ok := messageFromError(err); ok {
+ k.fatal(m)
+ } else {
+ k.printBaseErr(err,
+ fmt.Sprintf("cannot prepare op at index %d:", i))
+ k.beforeExit()
+ k.exit(1)
+ }
}
}
@@ -224,10 +228,14 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
// ops already checked during early setup
k.verbosef("%s %s", op.prefix(), op)
if err := op.apply(state, k); err != nil {
- k.printBaseErr(err,
- fmt.Sprintf("cannot apply op at index %d:", i))
- k.beforeExit()
- k.exit(1)
+ if m, ok := messageFromError(err); ok {
+ k.fatal(m)
+ } else {
+ k.printBaseErr(err,
+ fmt.Sprintf("cannot apply op at index %d:", i))
+ k.beforeExit()
+ k.exit(1)
+ }
}
}