From f24dd4ab8c3f1440654f9c5c18e50f03fdf85e27 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Fri, 29 Aug 2025 01:37:13 +0900 Subject: [PATCH] container/init: handle unwrapped errors This is much cleaner from both the return statement and the error handling. Signed-off-by: Ophestra --- container/errors.go | 43 +++++++++++++++++++++++++++++++++++++ container/errors_test.go | 46 ++++++++++++++++++++++++++++++++++++++++ container/init.go | 24 ++++++++++++++------- 3 files changed, 105 insertions(+), 8 deletions(-) diff --git a/container/errors.go b/container/errors.go index 12cb3b9..1a828e0 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 3d32743..eae5ee7 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 8b0a7a2..e56b4cf 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) + } } }