fortify/internal/app/errors.go
Ophestra 840ceb615a
All checks were successful
Test / Create distribution (push) Successful in 25s
Test / Fortify (push) Successful in 2m27s
Test / Data race detector (push) Successful in 3m24s
Test / Fpkg (push) Successful in 3m30s
Test / Flake checks (push) Successful in 52s
app: handle RunState errors
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-02-26 17:36:14 +09:00

180 lines
4.4 KiB
Go

package app
import (
"errors"
"log"
"git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/internal/fmsg"
)
func PrintRunStateErr(rs *fst.RunState, runErr error) {
if runErr != nil {
if rs.Time == nil {
fmsg.PrintBaseError(runErr, "cannot start app:")
} else {
var e *fmsg.BaseError
if !fmsg.AsBaseError(runErr, &e) {
log.Println("wait failed:", runErr)
} else {
// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError
var se *StateStoreError
if !errors.As(runErr, &se) {
// does not need special handling
log.Print(e.Message())
} else {
// inner error are either unwrapped store errors
// or joined errors returned by *appSealTx revert
// wrapped in *app.BaseError
var ej RevertCompoundError
if !errors.As(se.InnerErr, &ej) {
// does not require special handling
log.Print(e.Message())
} else {
errs := ej.Unwrap()
// every error here is wrapped in *app.BaseError
for _, ei := range errs {
var eb *fmsg.BaseError
if !errors.As(ei, &eb) {
// unreachable
log.Println("invalid error type returned by revert:", ei)
} else {
// print inner *app.BaseError message
log.Print(eb.Message())
}
}
}
}
}
}
if rs.ExitCode == 0 {
rs.ExitCode = 126
}
}
if rs.RevertErr != nil {
var stateStoreError *StateStoreError
if !errors.As(rs.RevertErr, &stateStoreError) || stateStoreError == nil {
fmsg.PrintBaseError(rs.RevertErr, "generic fault during cleanup:")
goto out
}
if stateStoreError.Err != nil {
if len(stateStoreError.Err) == 2 {
if stateStoreError.Err[0] != nil {
if joinedErrs, ok := stateStoreError.Err[0].(interface{ Unwrap() []error }); !ok {
fmsg.PrintBaseError(stateStoreError.Err[0], "generic fault during revert:")
} else {
for _, err := range joinedErrs.Unwrap() {
if err != nil {
fmsg.PrintBaseError(err, "fault during revert:")
}
}
}
}
if stateStoreError.Err[1] != nil {
log.Printf("cannot close store: %v", stateStoreError.Err[1])
}
} else {
log.Printf("fault during cleanup: %v",
errors.Join(stateStoreError.Err...))
}
}
if stateStoreError.OpErr != nil {
log.Printf("blind revert due to store fault: %v",
stateStoreError.OpErr)
}
if stateStoreError.DoErr != nil {
fmsg.PrintBaseError(stateStoreError.DoErr, "state store operation unsuccessful:")
}
if stateStoreError.Inner && stateStoreError.InnerErr != nil {
fmsg.PrintBaseError(stateStoreError.InnerErr, "cannot destroy state entry:")
}
out:
if rs.ExitCode == 0 {
rs.ExitCode = 128
}
}
if rs.WaitErr != nil {
log.Println("inner wait failed:", rs.WaitErr)
}
}
// StateStoreError is returned for a failed state save
type StateStoreError struct {
// whether inner function was called
Inner bool
// returned by the Save/Destroy method of [state.Cursor]
InnerErr error
// returned by the Do method of [state.Store]
DoErr error
// stores an arbitrary store operation error
OpErr error
// stores arbitrary errors
Err []error
}
// save saves arbitrary errors in [StateStoreError] once.
func (e *StateStoreError) save(errs []error) {
if len(errs) == 0 || e.Err != nil {
panic("invalid call to save")
}
e.Err = errs
}
func (e *StateStoreError) equiv(a ...any) error {
if e.Inner && e.InnerErr == nil && e.DoErr == nil && e.OpErr == nil && errors.Join(e.Err...) == nil {
return nil
} else {
return fmsg.WrapErrorSuffix(e, a...)
}
}
func (e *StateStoreError) Error() string {
if e.Inner && e.InnerErr != nil {
return e.InnerErr.Error()
}
if e.DoErr != nil {
return e.DoErr.Error()
}
if e.OpErr != nil {
return e.OpErr.Error()
}
if err := errors.Join(e.Err...); err != nil {
return err.Error()
}
// equiv nullifies e for values where this is reached
panic("unreachable")
}
func (e *StateStoreError) Unwrap() (errs []error) {
errs = make([]error, 0, 3)
if e.InnerErr != nil {
errs = append(errs, e.InnerErr)
}
if e.DoErr != nil {
errs = append(errs, e.DoErr)
}
if e.OpErr != nil {
errs = append(errs, e.OpErr)
}
if err := errors.Join(e.Err...); err != nil {
errs = append(errs, err)
}
return
}
// A RevertCompoundError encapsulates errors returned by
// the Revert method of [system.I].
type RevertCompoundError interface {
Error() string
Unwrap() []error
}