diff options
Diffstat (limited to 'internal/system/op.go')
| -rw-r--r-- | internal/system/op.go | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/internal/system/op.go b/internal/system/op.go new file mode 100644 index 00000000..c3f93cdb --- /dev/null +++ b/internal/system/op.go @@ -0,0 +1,126 @@ +package system + +import ( + "errors" + "fmt" + "sync" + + "git.ophivana.moe/cat/fortify/internal/state" +) + +const ( + // Process type is unconditionally reverted on exit. + Process = state.EnableLength + 1 + // User type is reverted at final launcher exit. + User = state.EnableLength +) + +type Criteria struct { + *state.Enablements +} + +func (ec *Criteria) hasType(o Op) bool { + // nil criteria: revert everything except User + if ec.Enablements == nil { + return o.Type() != User + } + + return ec.Has(o.Type()) +} + +// Op is a reversible system operation. +type Op interface { + // Type returns Op's enablement type. + Type() state.Enablement + + // apply the Op + apply(sys *I) error + // revert reverses the Op if criteria is met + revert(sys *I, ec *Criteria) error + + Is(o Op) bool + Path() string + String() string +} + +func TypeString(e state.Enablement) string { + switch e { + case User: + return "User" + case Process: + return "Process" + default: + return e.String() + } +} + +type I struct { + uid int + ops []Op + + state [2]bool + lock sync.Mutex +} + +func (sys *I) UID() int { + return sys.uid +} + +func (sys *I) Commit() error { + sys.lock.Lock() + defer sys.lock.Unlock() + + if sys.state[0] { + panic("sys instance committed twice") + } + sys.state[0] = true + + sp := New(sys.uid) + sp.ops = make([]Op, 0, len(sys.ops)) // prevent copies during commits + defer func() { + // sp is set to nil when all ops are applied + if sp != nil { + // rollback partial commit + if err := sp.Revert(&Criteria{nil}); err != nil { + fmt.Println("fortify: errors returned reverting partial commit:", err) + } + } + }() + + for _, o := range sys.ops { + if err := o.apply(sys); err != nil { + return err + } else { + // register partial commit + sp.ops = append(sp.ops, o) + } + } + + // disarm partial commit rollback + sp = nil + return nil +} + +func (sys *I) Revert(ec *Criteria) error { + sys.lock.Lock() + defer sys.lock.Unlock() + + if sys.state[1] { + panic("sys instance reverted twice") + } + sys.state[1] = true + + // collect errors + errs := make([]error, len(sys.ops)) + + for i := range sys.ops { + errs[i] = sys.ops[len(sys.ops)-i-1].revert(sys, ec) + } + + // errors.Join filters nils + return errors.Join(errs...) +} + +func New(uid int) *I { + return &I{uid: uid} +} |
