fortify/system/op.go
Ophestra 9a1f8e129f
All checks were successful
Test / Create distribution (push) Successful in 24s
Test / Fortify (push) Successful in 2m27s
Test / Fpkg (push) Successful in 3m36s
Test / Data race detector (push) Successful in 4m16s
Test / Flake checks (push) Successful in 55s
sandbox: wrap fmsg interface
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-03-17 02:44:07 +09:00

153 lines
3.1 KiB
Go

// Package system provides tools for safely interacting with the operating system.
package system
import (
"context"
"errors"
"log"
"sync"
)
const (
// User type is reverted at final launcher exit.
User = Enablement(ELen)
// Process type is unconditionally reverted on exit.
Process = Enablement(ELen + 1)
)
// Criteria specifies types of Op to revert.
type Criteria struct {
*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() 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
}
// TypeString returns the string representation of a type stored as an [Enablement].
func TypeString(e Enablement) string {
switch e {
case User:
return "User"
case Process:
return "Process"
default:
return e.String()
}
}
// New initialises sys with no-op verbose functions.
func New(uid int) (sys *I) {
sys = new(I)
sys.uid = uid
return
}
// An I provides indirect bulk operating system interaction. I must not be copied.
type I struct {
uid int
ops []Op
ctx context.Context
// whether sys has been reverted
state bool
lock sync.Mutex
}
func (sys *I) UID() int { return sys.uid }
// Equal returns whether all [Op] instances held by v is identical to that of sys.
func (sys *I) Equal(v *I) bool {
if v == nil || sys.uid != v.uid || len(sys.ops) != len(v.ops) {
return false
}
for i, o := range sys.ops {
if !o.Is(v.ops[i]) {
return false
}
}
return true
}
// Commit applies all [Op] held by [I] and reverts successful [Op] on first error encountered.
// Commit must not be called more than once.
func (sys *I) Commit(ctx context.Context) error {
sys.lock.Lock()
defer sys.lock.Unlock()
if sys.ctx != nil {
panic("sys instance committed twice")
}
sys.ctx = ctx
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
msg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
if err := sp.Revert(&Criteria{nil}); err != nil {
log.Println("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
}
// Revert reverts all [Op] meeting [Criteria] held by [I].
func (sys *I) Revert(ec *Criteria) error {
sys.lock.Lock()
defer sys.lock.Unlock()
if sys.state {
panic("sys instance reverted twice")
}
sys.state = 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...)
}