app: defer system.I revert
Just returning an error after a successful call of commit will leave garbage behind with no way for the caller to clean them. This change ensures revert is always called after successful commit with at least per-process state enabled. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
ef81828e0c
commit
3c80fd2b0f
14
fst/app.go
14
fst/app.go
@ -1,6 +1,9 @@
|
|||||||
package fst
|
package fst
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type App interface {
|
type App interface {
|
||||||
// ID returns a copy of App's unique ID.
|
// ID returns a copy of App's unique ID.
|
||||||
@ -14,10 +17,15 @@ type App interface {
|
|||||||
|
|
||||||
// RunState stores the outcome of a call to [App.Run].
|
// RunState stores the outcome of a call to [App.Run].
|
||||||
type RunState struct {
|
type RunState struct {
|
||||||
// Start is true if fsu is successfully started.
|
// Time is the exact point in time where the process was created.
|
||||||
Start bool
|
// Location must be set to UTC.
|
||||||
|
//
|
||||||
|
// Time is nil if no process was ever created.
|
||||||
|
Time *time.Time
|
||||||
// ExitCode is the value returned by shim.
|
// ExitCode is the value returned by shim.
|
||||||
ExitCode int
|
ExitCode int
|
||||||
|
// RevertErr is stored by the deferred revert call.
|
||||||
|
RevertErr error
|
||||||
// WaitErr is error returned by the underlying wait syscall.
|
// WaitErr is error returned by the underlying wait syscall.
|
||||||
WaitErr error
|
WaitErr error
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/internal/app/shim"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/sys"
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
||||||
)
|
)
|
||||||
@ -22,15 +21,11 @@ func New(os sys.State) (fst.App, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type app struct {
|
type app struct {
|
||||||
// application unique identifier
|
id *stringPair[fst.ID]
|
||||||
id *stringPair[fst.ID]
|
|
||||||
// operating system interface
|
|
||||||
sys sys.State
|
sys sys.State
|
||||||
// shim process manager
|
|
||||||
shim *shim.Shim
|
|
||||||
|
|
||||||
mu sync.RWMutex
|
|
||||||
*appSeal
|
*appSeal
|
||||||
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) ID() fst.ID { return a.id.unwrap() }
|
func (a *app) ID() fst.ID { return a.id.unwrap() }
|
||||||
@ -43,10 +38,6 @@ func (a *app) String() string {
|
|||||||
a.mu.RLock()
|
a.mu.RLock()
|
||||||
defer a.mu.RUnlock()
|
defer a.mu.RUnlock()
|
||||||
|
|
||||||
if a.shim != nil {
|
|
||||||
return a.shim.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.appSeal != nil {
|
if a.appSeal != nil {
|
||||||
if a.appSeal.user.uid == nil {
|
if a.appSeal.user.uid == nil {
|
||||||
return fmt.Sprintf("(sealed app %s with invalid uid)", a.id)
|
return fmt.Sprintf("(sealed app %s with invalid uid)", a.id)
|
||||||
|
@ -28,7 +28,10 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error {
|
|||||||
panic("attempted to pass nil state to run")
|
panic("attempted to pass nil state to run")
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve exec paths
|
/*
|
||||||
|
resolve exec paths
|
||||||
|
*/
|
||||||
|
|
||||||
shimExec := [2]string{helper.BubblewrapName}
|
shimExec := [2]string{helper.BubblewrapName}
|
||||||
if len(a.appSeal.command) > 0 {
|
if len(a.appSeal.command) > 0 {
|
||||||
shimExec[1] = a.appSeal.command[0]
|
shimExec[1] = a.appSeal.command[0]
|
||||||
@ -47,68 +50,128 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// startup will go ahead, commit system setup
|
/*
|
||||||
|
prepare/revert os state
|
||||||
|
*/
|
||||||
|
|
||||||
if err := a.appSeal.sys.Commit(ctx); err != nil {
|
if err := a.appSeal.sys.Commit(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
a.appSeal.needRevert = true
|
store := state.NewMulti(a.sys.Paths().RunDirPath)
|
||||||
|
deferredStoreFunc := func(c state.Cursor) error { return nil }
|
||||||
|
defer func() {
|
||||||
|
var revertErr error
|
||||||
|
storeErr := new(StateStoreError)
|
||||||
|
storeErr.Inner, storeErr.DoErr = store.Do(a.appSeal.user.aid.unwrap(), func(c state.Cursor) {
|
||||||
|
revertErr = func() error {
|
||||||
|
storeErr.InnerErr = deferredStoreFunc(c)
|
||||||
|
|
||||||
|
/*
|
||||||
|
revert app setup transaction
|
||||||
|
*/
|
||||||
|
|
||||||
|
rt, ec := new(system.Enablements), new(system.Criteria)
|
||||||
|
ec.Enablements = new(system.Enablements)
|
||||||
|
ec.Set(system.Process)
|
||||||
|
if states, err := c.Load(); err != nil {
|
||||||
|
// revert per-process state here to limit damage
|
||||||
|
return errors.Join(err, a.appSeal.sys.Revert(ec))
|
||||||
|
} else {
|
||||||
|
if l := len(states); l == 0 {
|
||||||
|
fmsg.Verbose("no other launchers active, will clean up globals")
|
||||||
|
ec.Set(system.User)
|
||||||
|
} else {
|
||||||
|
fmsg.Verbosef("found %d active launchers, cleaning up without globals", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// accumulate enablements of remaining launchers
|
||||||
|
for i, s := range states {
|
||||||
|
if s.Config != nil {
|
||||||
|
*rt |= s.Config.Confinement.Enablements
|
||||||
|
} else {
|
||||||
|
log.Printf("state entry %d does not contain config", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// invert accumulated enablements for cleanup
|
||||||
|
for i := system.Enablement(0); i < system.Enablement(system.ELen); i++ {
|
||||||
|
if !rt.Has(i) {
|
||||||
|
ec.Set(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fmsg.Load() {
|
||||||
|
labels := make([]string, 0, system.ELen+1)
|
||||||
|
for i := system.Enablement(0); i < system.Enablement(system.ELen+2); i++ {
|
||||||
|
if ec.Has(i) {
|
||||||
|
labels = append(labels, system.TypeString(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(labels) > 0 {
|
||||||
|
fmsg.Verbose("reverting operations type", strings.Join(labels, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := a.appSeal.sys.Revert(ec)
|
||||||
|
if err != nil {
|
||||||
|
err = err.(RevertCompoundError)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
storeErr.Err = errors.Join(revertErr, store.Close())
|
||||||
|
rs.RevertErr = storeErr.equiv("error returned during cleanup:")
|
||||||
|
}()
|
||||||
|
|
||||||
|
/*
|
||||||
|
shim process lifecycle
|
||||||
|
*/
|
||||||
|
|
||||||
// start shim via manager
|
|
||||||
a.shim = new(shim.Shim)
|
|
||||||
waitErr := make(chan error, 1)
|
waitErr := make(chan error, 1)
|
||||||
if startTime, err := a.shim.Start(
|
cmd := new(shim.Shim)
|
||||||
|
if startTime, err := cmd.Start(
|
||||||
a.appSeal.user.aid.String(),
|
a.appSeal.user.aid.String(),
|
||||||
a.appSeal.user.supp,
|
a.appSeal.user.supp,
|
||||||
a.appSeal.bwrapSync,
|
a.appSeal.bwrapSync,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
// shim process created
|
// whether/when the fsu process was created
|
||||||
rs.Start = true
|
rs.Time = startTime
|
||||||
|
|
||||||
shimSetupCtx, shimSetupCancel := context.WithDeadline(ctx, time.Now().Add(shimSetupTimeout))
|
|
||||||
defer shimSetupCancel()
|
|
||||||
|
|
||||||
// start waiting for shim
|
|
||||||
go func() {
|
|
||||||
waitErr <- a.shim.Unwrap().Wait()
|
|
||||||
// cancel shim setup in case shim died before receiving payload
|
|
||||||
shimSetupCancel()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// send payload
|
|
||||||
if err = a.shim.Serve(shimSetupCtx, &shim.Payload{
|
|
||||||
Argv: a.appSeal.command,
|
|
||||||
Exec: shimExec,
|
|
||||||
Bwrap: a.appSeal.container,
|
|
||||||
Home: a.appSeal.user.data,
|
|
||||||
|
|
||||||
Verbose: fmsg.Load(),
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// shim accepted setup payload, create process state
|
|
||||||
sd := state.State{
|
|
||||||
ID: a.id.unwrap(),
|
|
||||||
PID: a.shim.Unwrap().Process.Pid,
|
|
||||||
Time: *startTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
// register process state
|
|
||||||
var err0 = new(StateStoreError)
|
|
||||||
err0.Inner, err0.DoErr = a.appSeal.store.Do(a.appSeal.user.aid.unwrap(), func(c state.Cursor) {
|
|
||||||
err0.InnerErr = c.Save(&sd, a.appSeal.ct)
|
|
||||||
})
|
|
||||||
a.appSeal.stateInStore = true
|
|
||||||
if err = err0.equiv("cannot save process state:"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shimSetupCtx, shimSetupCancel := context.WithDeadline(ctx, time.Now().Add(shimSetupTimeout))
|
||||||
|
defer shimSetupCancel()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
waitErr <- cmd.Unwrap().Wait()
|
||||||
|
// cancel shim setup in case shim died before receiving payload
|
||||||
|
shimSetupCancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := cmd.Serve(shimSetupCtx, &shim.Payload{
|
||||||
|
Argv: a.appSeal.command,
|
||||||
|
Exec: shimExec,
|
||||||
|
Bwrap: a.appSeal.container,
|
||||||
|
Home: a.appSeal.user.data,
|
||||||
|
|
||||||
|
Verbose: fmsg.Load(),
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// shim accepted setup payload, create process state
|
||||||
|
sd := state.State{
|
||||||
|
ID: a.id.unwrap(),
|
||||||
|
PID: cmd.Unwrap().Process.Pid,
|
||||||
|
Time: *rs.Time,
|
||||||
|
}
|
||||||
|
var earlyStoreErr = new(StateStoreError) // returned after blocking on waitErr
|
||||||
|
earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(a.appSeal.user.aid.unwrap(), func(c state.Cursor) { earlyStoreErr.InnerErr = c.Save(&sd, a.appSeal.ct) })
|
||||||
|
// destroy defunct state entry
|
||||||
|
deferredStoreFunc = func(c state.Cursor) error { return c.Destroy(a.id.unwrap()) }
|
||||||
|
|
||||||
select {
|
select {
|
||||||
// wait for process and resolve exit code
|
case err := <-waitErr: // block until fsu/shim returns
|
||||||
case err := <-waitErr:
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var exitError *exec.ExitError
|
var exitError *exec.ExitError
|
||||||
if !errors.As(err, &exitError) {
|
if !errors.As(err, &exitError) {
|
||||||
@ -119,16 +182,16 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error {
|
|||||||
// store non-zero return code
|
// store non-zero return code
|
||||||
rs.ExitCode = exitError.ExitCode()
|
rs.ExitCode = exitError.ExitCode()
|
||||||
} else {
|
} else {
|
||||||
rs.ExitCode = a.shim.Unwrap().ProcessState.ExitCode()
|
rs.ExitCode = cmd.Unwrap().ProcessState.ExitCode()
|
||||||
}
|
}
|
||||||
if fmsg.Load() {
|
if fmsg.Load() {
|
||||||
fmsg.Verbosef("process %d exited with exit code %d", a.shim.Unwrap().Process.Pid, rs.ExitCode)
|
fmsg.Verbosef("process %d exited with exit code %d", cmd.Unwrap().Process.Pid, rs.ExitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is reached when a fault makes an already running shim impossible to continue execution
|
// this is reached when a fault makes an already running shim impossible to continue execution
|
||||||
// however a kill signal could not be delivered (should actually always happen like that since fsu)
|
// however a kill signal could not be delivered (should actually always happen like that since fsu)
|
||||||
// the effects of this is similar to the alternative exit path and ensures shim death
|
// the effects of this is similar to the alternative exit path and ensures shim death
|
||||||
case err := <-a.shim.WaitFallback():
|
case err := <-cmd.WaitFallback():
|
||||||
rs.ExitCode = 255
|
rs.ExitCode = 255
|
||||||
log.Printf("cannot terminate shim on faulted setup: %v", err)
|
log.Printf("cannot terminate shim on faulted setup: %v", err)
|
||||||
|
|
||||||
@ -137,93 +200,35 @@ func (a *app) Run(ctx context.Context, rs *fst.RunState) error {
|
|||||||
fmsg.Verbose("alternative exit path selected")
|
fmsg.Verbose("alternative exit path selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
// child process exited, resume output
|
|
||||||
fmsg.Resume()
|
fmsg.Resume()
|
||||||
|
|
||||||
// print queued up dbus messages
|
|
||||||
if a.appSeal.dbusMsg != nil {
|
if a.appSeal.dbusMsg != nil {
|
||||||
|
// dump dbus message buffer
|
||||||
a.appSeal.dbusMsg()
|
a.appSeal.dbusMsg()
|
||||||
}
|
}
|
||||||
|
|
||||||
// update store and revert app setup transaction
|
return earlyStoreErr.equiv("cannot save process state:")
|
||||||
e := new(StateStoreError)
|
|
||||||
e.Inner, e.DoErr = a.appSeal.store.Do(a.appSeal.user.aid.unwrap(), func(b state.Cursor) {
|
|
||||||
e.InnerErr = func() error {
|
|
||||||
// destroy defunct state entry
|
|
||||||
if cmd := a.shim.Unwrap(); cmd != nil && a.appSeal.stateInStore {
|
|
||||||
if err := b.Destroy(a.id.unwrap()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// enablements of remaining launchers
|
|
||||||
rt, ec := new(system.Enablements), new(system.Criteria)
|
|
||||||
ec.Enablements = new(system.Enablements)
|
|
||||||
ec.Set(system.Process)
|
|
||||||
if states, err := b.Load(); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
if l := len(states); l == 0 {
|
|
||||||
// cleanup globals as the final launcher
|
|
||||||
fmsg.Verbose("no other launchers active, will clean up globals")
|
|
||||||
ec.Set(system.User)
|
|
||||||
} else {
|
|
||||||
fmsg.Verbosef("found %d active launchers, cleaning up without globals", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// accumulate capabilities of other launchers
|
|
||||||
for i, s := range states {
|
|
||||||
if s.Config != nil {
|
|
||||||
*rt |= s.Config.Confinement.Enablements
|
|
||||||
} else {
|
|
||||||
log.Printf("state entry %d does not contain config", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// invert accumulated enablements for cleanup
|
|
||||||
for i := system.Enablement(0); i < system.Enablement(system.ELen); i++ {
|
|
||||||
if !rt.Has(i) {
|
|
||||||
ec.Set(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if fmsg.Load() {
|
|
||||||
labels := make([]string, 0, system.ELen+1)
|
|
||||||
for i := system.Enablement(0); i < system.Enablement(system.ELen+2); i++ {
|
|
||||||
if ec.Has(i) {
|
|
||||||
labels = append(labels, system.TypeString(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(labels) > 0 {
|
|
||||||
fmsg.Verbose("reverting operations labelled", strings.Join(labels, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if a.appSeal.needRevert {
|
|
||||||
if err := a.appSeal.sys.Revert(ec); err != nil {
|
|
||||||
return err.(RevertCompoundError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
|
|
||||||
e.Err = a.appSeal.store.Close()
|
|
||||||
return e.equiv("error returned during cleanup:", e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateStoreError is returned for a failed state save
|
// StateStoreError is returned for a failed state save
|
||||||
type StateStoreError struct {
|
type StateStoreError struct {
|
||||||
// whether inner function was called
|
// whether inner function was called
|
||||||
Inner bool
|
Inner bool
|
||||||
// error returned by state.Store Do method
|
// returned by the Do method of [state.Store]
|
||||||
DoErr error
|
DoErr error
|
||||||
// error returned by state.Backend Save method
|
// returned by the Save/Destroy method of [state.Cursor]
|
||||||
InnerErr error
|
InnerErr error
|
||||||
// any other errors needing to be tracked
|
// stores an arbitrary error
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save saves exactly one arbitrary error in [StateStoreError].
|
||||||
|
func (e *StateStoreError) save(err error) {
|
||||||
|
if err == nil || e.Err != nil {
|
||||||
|
panic("invalid call to save")
|
||||||
|
}
|
||||||
|
e.Err = err
|
||||||
|
}
|
||||||
|
|
||||||
func (e *StateStoreError) equiv(a ...any) error {
|
func (e *StateStoreError) equiv(a ...any) error {
|
||||||
if e.Inner && e.DoErr == nil && e.InnerErr == nil && e.Err == nil {
|
if e.Inner && e.DoErr == nil && e.InnerErr == nil && e.Err == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -245,7 +250,8 @@ func (e *StateStoreError) Error() string {
|
|||||||
return e.Err.Error()
|
return e.Err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
return "(nil)"
|
// equiv nullifies e for values where this is reached
|
||||||
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *StateStoreError) Unwrap() (errs []error) {
|
func (e *StateStoreError) Unwrap() (errs []error) {
|
||||||
@ -262,6 +268,8 @@ func (e *StateStoreError) Unwrap() (errs []error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A RevertCompoundError encapsulates errors returned by
|
||||||
|
// the Revert method of [system.I].
|
||||||
type RevertCompoundError interface {
|
type RevertCompoundError interface {
|
||||||
Error() string
|
Error() string
|
||||||
Unwrap() []error
|
Unwrap() []error
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/sys"
|
"git.gensokyo.uk/security/fortify/internal/sys"
|
||||||
"git.gensokyo.uk/security/fortify/system"
|
"git.gensokyo.uk/security/fortify/system"
|
||||||
"git.gensokyo.uk/security/fortify/wl"
|
"git.gensokyo.uk/security/fortify/wl"
|
||||||
@ -63,17 +62,11 @@ type appSeal struct {
|
|||||||
// passed through from [fst.Config]
|
// passed through from [fst.Config]
|
||||||
command []string
|
command []string
|
||||||
|
|
||||||
// state instance initialised during seal; used during process lifecycle events
|
|
||||||
store state.Store
|
|
||||||
// initial [fst.Config] gob stream for state data;
|
// initial [fst.Config] gob stream for state data;
|
||||||
// this is prepared ahead of time as config is mutated during seal creation
|
// this is prepared ahead of time as config is mutated during seal creation
|
||||||
ct io.WriterTo
|
ct io.WriterTo
|
||||||
// dump dbus proxy message buffer
|
// dump dbus proxy message buffer
|
||||||
dbusMsg func()
|
dbusMsg func()
|
||||||
// whether [system.I] was committed; used during process lifecycle events
|
|
||||||
needRevert bool
|
|
||||||
// whether state was inserted into [state.Store]; used during process lifecycle events
|
|
||||||
stateInStore bool
|
|
||||||
|
|
||||||
user appUser
|
user appUser
|
||||||
sys *system.I
|
sys *system.I
|
||||||
@ -230,7 +223,6 @@ func (seal *appSeal) finalise(sys sys.State, config *fst.Config, id string) erro
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
sc := sys.Paths()
|
sc := sys.Paths()
|
||||||
seal.store = state.NewMulti(sc.RunDirPath)
|
|
||||||
seal.sys = system.New(seal.user.uid.unwrap())
|
seal.sys = system.New(seal.user.uid.unwrap())
|
||||||
seal.sys.IsVerbose = fmsg.Load
|
seal.sys.IsVerbose = fmsg.Load
|
||||||
seal.sys.Verbose = fmsg.Verbose
|
seal.sys.Verbose = fmsg.Verbose
|
||||||
|
8
main.go
8
main.go
@ -334,7 +334,7 @@ func runApp(config *fst.Config) {
|
|||||||
fmsg.PrintBaseError(err, "cannot seal app:")
|
fmsg.PrintBaseError(err, "cannot seal app:")
|
||||||
internal.Exit(1)
|
internal.Exit(1)
|
||||||
} else if err = a.Run(ctx, rs); err != nil {
|
} else if err = a.Run(ctx, rs); err != nil {
|
||||||
if !rs.Start {
|
if rs.Time == nil {
|
||||||
fmsg.PrintBaseError(err, "cannot start app:")
|
fmsg.PrintBaseError(err, "cannot start app:")
|
||||||
} else {
|
} else {
|
||||||
logWaitError(err)
|
logWaitError(err)
|
||||||
@ -344,6 +344,12 @@ func runApp(config *fst.Config) {
|
|||||||
rs.ExitCode = 126
|
rs.ExitCode = 126
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rs.RevertErr != nil {
|
||||||
|
fmsg.PrintBaseError(rs.RevertErr, "generic error returned during cleanup:")
|
||||||
|
if rs.ExitCode == 0 {
|
||||||
|
rs.ExitCode = 128
|
||||||
|
}
|
||||||
|
}
|
||||||
if rs.WaitErr != nil {
|
if rs.WaitErr != nil {
|
||||||
log.Println("inner wait failed:", rs.WaitErr)
|
log.Println("inner wait failed:", rs.WaitErr)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user