system: isolate app/system into generic implementation
This improves maintainability and extensibility of system operations, makes writing tests for them possible, and operations now apply and revert in order, instead of being bunched up into their own categories. Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
parent
0fd63e85e7
commit
430f1a5b4e
|
@ -0,0 +1,78 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/acl"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/fmsg"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdatePerm appends an ephemeral acl update Op.
|
||||||
|
func (sys *I) UpdatePerm(path string, perms ...acl.Perm) {
|
||||||
|
sys.UpdatePermType(Process, path, perms...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdatePermType appends an acl update Op.
|
||||||
|
func (sys *I) UpdatePermType(et state.Enablement, path string, perms ...acl.Perm) {
|
||||||
|
sys.lock.Lock()
|
||||||
|
defer sys.lock.Unlock()
|
||||||
|
|
||||||
|
sys.ops = append(sys.ops, &ACL{et, path, perms})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ACL struct {
|
||||||
|
et state.Enablement
|
||||||
|
path string
|
||||||
|
perms []acl.Perm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACL) Type() state.Enablement {
|
||||||
|
return a.et
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACL) apply(sys *I) error {
|
||||||
|
verbose.Println("applying ACL", a, "uid:", sys.uid, "type:", TypeString(a.et), "path:", a.path)
|
||||||
|
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid, a.perms...),
|
||||||
|
fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
||||||
|
if ec.hasType(a) {
|
||||||
|
verbose.Println("stripping ACL", a, "uid:", sys.uid, "type:", TypeString(a.et), "path:", a.path)
|
||||||
|
return fmsg.WrapErrorSuffix(acl.UpdatePerm(a.path, sys.uid),
|
||||||
|
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
|
||||||
|
} else {
|
||||||
|
verbose.Println("skipping ACL", a, "uid:", sys.uid, "tag:", TypeString(a.et), "path:", a.path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACL) Is(o Op) bool {
|
||||||
|
a0, ok := o.(*ACL)
|
||||||
|
return ok && a0 != nil &&
|
||||||
|
a.et == a0.et &&
|
||||||
|
a.path == a0.path &&
|
||||||
|
slices.Equal(a.perms, a0.perms)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACL) Path() string {
|
||||||
|
return a.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ACL) String() string {
|
||||||
|
var s = []byte("---")
|
||||||
|
for _, p := range a.perms {
|
||||||
|
switch p {
|
||||||
|
case acl.Read:
|
||||||
|
s[0] = 'r'
|
||||||
|
case acl.Write:
|
||||||
|
s[1] = 'w'
|
||||||
|
case acl.Execute:
|
||||||
|
s[2] = 'x'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(s)
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/dbus"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/fmsg"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDBusConfig = errors.New("dbus config not supplied")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath string) error {
|
||||||
|
d := new(DBus)
|
||||||
|
|
||||||
|
// used by waiting goroutine to notify process exit
|
||||||
|
d.done = make(chan struct{})
|
||||||
|
|
||||||
|
// session bus is mandatory
|
||||||
|
if session == nil {
|
||||||
|
return fmsg.WrapError(ErrDBusConfig,
|
||||||
|
"attempted to seal message bus proxy without session bus config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// system bus is optional
|
||||||
|
d.system = system == nil
|
||||||
|
|
||||||
|
// upstream address, downstream socket path
|
||||||
|
var sessionBus, systemBus [2]string
|
||||||
|
|
||||||
|
// resolve upstream bus addresses
|
||||||
|
sessionBus[0], systemBus[0] = dbus.Address()
|
||||||
|
|
||||||
|
// set paths from caller
|
||||||
|
sessionBus[1], systemBus[1] = sessionPath, systemPath
|
||||||
|
|
||||||
|
// create proxy instance
|
||||||
|
d.proxy = dbus.New(sessionBus, systemBus)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if verbose.Get() && d.proxy.Sealed() {
|
||||||
|
verbose.Println("sealed session proxy", session.Args(sessionBus))
|
||||||
|
if system != nil {
|
||||||
|
verbose.Println("sealed system proxy", system.Args(systemBus))
|
||||||
|
}
|
||||||
|
verbose.Println("message bus proxy final args:", d.proxy)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// queue operation
|
||||||
|
sys.ops = append(sys.ops, d)
|
||||||
|
|
||||||
|
// seal dbus proxy
|
||||||
|
return fmsg.WrapErrorSuffix(d.proxy.Seal(session, system),
|
||||||
|
"cannot seal message bus proxy:")
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBus struct {
|
||||||
|
proxy *dbus.Proxy
|
||||||
|
|
||||||
|
// whether system bus proxy is enabled
|
||||||
|
system bool
|
||||||
|
// notification from goroutine waiting for dbus.Proxy
|
||||||
|
done chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DBus) Type() state.Enablement {
|
||||||
|
return Process
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DBus) apply(_ *I) error {
|
||||||
|
verbose.Printf("session bus proxy on %q for upstream %q\n", d.proxy.Session()[1], d.proxy.Session()[0])
|
||||||
|
if d.system {
|
||||||
|
verbose.Printf("system bus proxy on %q for upstream %q\n", d.proxy.System()[1], d.proxy.System()[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ready channel passed to dbus package
|
||||||
|
ready := make(chan error, 1)
|
||||||
|
|
||||||
|
// background dbus proxy start
|
||||||
|
if err := d.proxy.Start(ready, os.Stderr, true); err != nil {
|
||||||
|
return fmsg.WrapErrorSuffix(err,
|
||||||
|
"cannot start message bus proxy:")
|
||||||
|
}
|
||||||
|
verbose.Println("starting message bus proxy:", d.proxy)
|
||||||
|
if verbose.Get() { // save the extra bwrap arg build when verbose logging is off
|
||||||
|
verbose.Println("message bus proxy bwrap args:", d.proxy.Bwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// background wait for proxy instance and notify completion
|
||||||
|
go func() {
|
||||||
|
if err := d.proxy.Wait(); err != nil {
|
||||||
|
fmt.Println("fortify: message bus proxy exited with error:", err)
|
||||||
|
go func() { ready <- err }()
|
||||||
|
} else {
|
||||||
|
verbose.Println("message bus proxy exit")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure socket removal so ephemeral directory is empty at revert
|
||||||
|
if err := os.Remove(d.proxy.Session()[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
fmt.Println("fortify: cannot remove dangling session bus socket:", err)
|
||||||
|
}
|
||||||
|
if d.system {
|
||||||
|
if err := os.Remove(d.proxy.System()[1]); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
fmt.Println("fortify: cannot remove dangling system bus socket:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// notify proxy completion
|
||||||
|
close(d.done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// ready is not nil if the proxy process faulted
|
||||||
|
if err := <-ready; err != nil {
|
||||||
|
// note that err here is either an I/O error or a predetermined unexpected behaviour error
|
||||||
|
return fmsg.WrapErrorSuffix(err,
|
||||||
|
"message bus proxy fault after start:")
|
||||||
|
}
|
||||||
|
verbose.Println("message bus proxy ready")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DBus) revert(_ *I, _ *Criteria) error {
|
||||||
|
// criteria ignored here since dbus is always process-scoped
|
||||||
|
verbose.Println("terminating message bus proxy")
|
||||||
|
|
||||||
|
if err := d.proxy.Close(); err != nil {
|
||||||
|
if errors.Is(err, os.ErrClosed) {
|
||||||
|
return fmsg.WrapError(err,
|
||||||
|
"message bus proxy already closed")
|
||||||
|
} else {
|
||||||
|
return fmsg.WrapErrorSuffix(err,
|
||||||
|
"cannot stop message bus proxy:")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// block until proxy wait returns
|
||||||
|
<-d.done
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DBus) Is(o Op) bool {
|
||||||
|
d0, ok := o.(*DBus)
|
||||||
|
return ok && d0 != nil && *d == *d0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DBus) Path() string {
|
||||||
|
return "(dbus proxy)"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DBus) String() string {
|
||||||
|
return d.proxy.String()
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/fmsg"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure the existence and mode of a directory.
|
||||||
|
func (sys *I) Ensure(name string, perm os.FileMode) {
|
||||||
|
sys.lock.Lock()
|
||||||
|
defer sys.lock.Unlock()
|
||||||
|
|
||||||
|
sys.ops = append(sys.ops, &Mkdir{User, name, perm, false})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ephemeral ensures the temporary existence and mode of a directory through the life of et.
|
||||||
|
func (sys *I) Ephemeral(et state.Enablement, name string, perm os.FileMode) {
|
||||||
|
sys.lock.Lock()
|
||||||
|
defer sys.lock.Unlock()
|
||||||
|
|
||||||
|
sys.ops = append(sys.ops, &Mkdir{et, name, perm, true})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mkdir struct {
|
||||||
|
et state.Enablement
|
||||||
|
path string
|
||||||
|
perm os.FileMode
|
||||||
|
ephemeral bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mkdir) Type() state.Enablement {
|
||||||
|
return m.et
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mkdir) apply(_ *I) error {
|
||||||
|
verbose.Println("ensuring directory", m)
|
||||||
|
|
||||||
|
// create directory
|
||||||
|
err := os.Mkdir(m.path, m.perm)
|
||||||
|
if !errors.Is(err, os.ErrExist) {
|
||||||
|
return fmsg.WrapErrorSuffix(err,
|
||||||
|
fmt.Sprintf("cannot create directory %q:", m.path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// directory exists, ensure mode
|
||||||
|
return fmsg.WrapErrorSuffix(os.Chmod(m.path, m.perm),
|
||||||
|
fmt.Sprintf("cannot change mode of %q to %s:", m.path, m.perm))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mkdir) revert(_ *I, ec *Criteria) error {
|
||||||
|
if !m.ephemeral {
|
||||||
|
// skip non-ephemeral dir and do not log anything
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ec.hasType(m) {
|
||||||
|
verbose.Println("destroying ephemeral directory", m)
|
||||||
|
return fmsg.WrapErrorSuffix(os.Remove(m.path),
|
||||||
|
fmt.Sprintf("cannot remove ephemeral directory %q:", m.path))
|
||||||
|
} else {
|
||||||
|
verbose.Println("skipping ephemeral directory", m)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mkdir) Is(o Op) bool {
|
||||||
|
m0, ok := o.(*Mkdir)
|
||||||
|
return ok && m0 != nil && *m == *m0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mkdir) Path() string {
|
||||||
|
return m.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mkdir) String() string {
|
||||||
|
return fmt.Sprintf("mode: %s path: %q", m.perm.String(), m.path)
|
||||||
|
}
|
|
@ -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}
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/acl"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/fmsg"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CopyFile registers an Op that copies path dst from src.
|
||||||
|
func (sys *I) CopyFile(dst, src string) {
|
||||||
|
sys.CopyFileType(Process, dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFileType registers a file copying Op labelled with type et.
|
||||||
|
func (sys *I) CopyFileType(et state.Enablement, dst, src string) {
|
||||||
|
sys.lock.Lock()
|
||||||
|
sys.ops = append(sys.ops, &Tmpfile{et, tmpfileCopy, dst, src})
|
||||||
|
sys.lock.Unlock()
|
||||||
|
|
||||||
|
sys.UpdatePermType(et, dst, acl.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link registers an Op that links dst to src.
|
||||||
|
func (sys *I) Link(oldname, newname string) {
|
||||||
|
sys.LinkFileType(Process, oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinkFileType registers a file linking Op labelled with type et.
|
||||||
|
func (sys *I) LinkFileType(et state.Enablement, oldname, newname string) {
|
||||||
|
sys.lock.Lock()
|
||||||
|
defer sys.lock.Unlock()
|
||||||
|
|
||||||
|
sys.ops = append(sys.ops, &Tmpfile{et, tmpfileLink, newname, oldname})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write registers an Op that writes dst with the contents of src.
|
||||||
|
func (sys *I) Write(dst, src string) {
|
||||||
|
sys.WriteType(Process, dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteType registers a file writing Op labelled with type et.
|
||||||
|
func (sys *I) WriteType(et state.Enablement, dst, src string) {
|
||||||
|
sys.lock.Lock()
|
||||||
|
sys.ops = append(sys.ops, &Tmpfile{et, tmpfileWrite, dst, src})
|
||||||
|
sys.lock.Unlock()
|
||||||
|
|
||||||
|
sys.UpdatePermType(et, dst, acl.Read)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
tmpfileCopy uint8 = iota
|
||||||
|
tmpfileLink
|
||||||
|
tmpfileWrite
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tmpfile struct {
|
||||||
|
et state.Enablement
|
||||||
|
method uint8
|
||||||
|
dst, src string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tmpfile) Type() state.Enablement {
|
||||||
|
return t.et
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tmpfile) apply(_ *I) error {
|
||||||
|
switch t.method {
|
||||||
|
case tmpfileCopy:
|
||||||
|
verbose.Printf("publishing tmpfile %s\n", t)
|
||||||
|
return fmsg.WrapErrorSuffix(copyFile(t.dst, t.src),
|
||||||
|
fmt.Sprintf("cannot copy tmpfile %q:", t.dst))
|
||||||
|
case tmpfileLink:
|
||||||
|
verbose.Printf("linking tmpfile %s\n", t)
|
||||||
|
return fmsg.WrapErrorSuffix(os.Link(t.src, t.dst),
|
||||||
|
fmt.Sprintf("cannot link tmpfile %q:", t.dst))
|
||||||
|
case tmpfileWrite:
|
||||||
|
verbose.Printf("writing %s\n", t)
|
||||||
|
return fmsg.WrapErrorSuffix(os.WriteFile(t.dst, []byte(t.src), 0600),
|
||||||
|
fmt.Sprintf("cannot write tmpfile %q:", t.dst))
|
||||||
|
default:
|
||||||
|
panic("invalid tmpfile method " + strconv.Itoa(int(t.method)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tmpfile) revert(_ *I, ec *Criteria) error {
|
||||||
|
if ec.hasType(t) {
|
||||||
|
verbose.Printf("removing tmpfile %q\n", t.dst)
|
||||||
|
return fmsg.WrapErrorSuffix(os.Remove(t.dst),
|
||||||
|
fmt.Sprintf("cannot remove tmpfile %q:", t.dst))
|
||||||
|
} else {
|
||||||
|
verbose.Printf("skipping tmpfile %q\n", t.dst)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tmpfile) Is(o Op) bool {
|
||||||
|
t0, ok := o.(*Tmpfile)
|
||||||
|
return ok && t0 != nil && *t == *t0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tmpfile) Path() string {
|
||||||
|
if t.method == tmpfileWrite {
|
||||||
|
return fmt.Sprintf("(%d bytes of data)", len(t.src))
|
||||||
|
}
|
||||||
|
return t.src
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tmpfile) String() string {
|
||||||
|
switch t.method {
|
||||||
|
case tmpfileCopy:
|
||||||
|
return fmt.Sprintf("%q from %q", t.dst, t.src)
|
||||||
|
case tmpfileLink:
|
||||||
|
return fmt.Sprintf("%q from %q", t.dst, t.src)
|
||||||
|
case tmpfileWrite:
|
||||||
|
return fmt.Sprintf("%d bytes of data to %q", len(t.src), t.dst)
|
||||||
|
default:
|
||||||
|
panic("invalid tmpfile method " + strconv.Itoa(int(t.method)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(dst, src string) error {
|
||||||
|
dstD, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srcD, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Join(err, dstD.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(dstD, srcD)
|
||||||
|
return errors.Join(err, dstD.Close(), srcD.Close())
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/fmsg"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/state"
|
||||||
|
"git.ophivana.moe/cat/fortify/internal/verbose"
|
||||||
|
"git.ophivana.moe/cat/fortify/xcb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChangeHosts appends an X11 ChangeHosts command Op.
|
||||||
|
func (sys *I) ChangeHosts(username string) {
|
||||||
|
sys.lock.Lock()
|
||||||
|
defer sys.lock.Unlock()
|
||||||
|
|
||||||
|
sys.ops = append(sys.ops, XHost(username))
|
||||||
|
}
|
||||||
|
|
||||||
|
type XHost string
|
||||||
|
|
||||||
|
func (x XHost) Type() state.Enablement {
|
||||||
|
return state.EnableX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x XHost) apply(_ *I) error {
|
||||||
|
verbose.Printf("inserting entry %s to X11\n", x)
|
||||||
|
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
||||||
|
fmt.Sprintf("cannot insert entry %s to X11:", x))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x XHost) revert(_ *I, ec *Criteria) error {
|
||||||
|
if ec.hasType(x) {
|
||||||
|
verbose.Printf("deleting entry %s from X11\n", x)
|
||||||
|
return fmsg.WrapErrorSuffix(xcb.ChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)),
|
||||||
|
fmt.Sprintf("cannot delete entry %s from X11:", x))
|
||||||
|
} else {
|
||||||
|
verbose.Printf("skipping entry %s in X11\n", x)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x XHost) Is(o Op) bool {
|
||||||
|
x0, ok := o.(XHost)
|
||||||
|
return ok && x == x0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x XHost) Path() string {
|
||||||
|
return string(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x XHost) String() string {
|
||||||
|
return string("SI:localuser:" + x)
|
||||||
|
}
|
Loading…
Reference in New Issue