Files
hakurei/internal/system/pipewire.go
Ophestra 093e30c788
All checks were successful
Test / Create distribution (push) Successful in 29s
Test / Sandbox (race detector) (push) Successful in 42s
Test / Sandbox (push) Successful in 43s
Test / Hakurei (push) Successful in 47s
Test / Hakurei (race detector) (push) Successful in 46s
Test / Hpkg (push) Successful in 43s
Test / Flake checks (push) Successful in 1m32s
internal/system: integrate PipeWire SecurityContext
Tests for this Op happens to be the best out of everything due to the robust infrastructure offered by internal/pipewire.

This is now ready to use in internal/outcome for implementing #26.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-12-07 17:39:34 +09:00

100 lines
2.8 KiB
Go

package system
import (
"errors"
"fmt"
"io"
"hakurei.app/container/check"
"hakurei.app/hst"
"hakurei.app/internal/acl"
"hakurei.app/internal/pipewire"
)
// PipeWire maintains a pipewire socket with SecurityContext attached via [pipewire].
// The socket stops accepting connections once the pipe referred to by sync is closed.
// The socket is pathname only and is destroyed on revert.
func (sys *I) PipeWire(dst *check.Absolute) *I {
sys.ops = append(sys.ops, &pipewireOp{nil, dst})
return sys
}
// pipewireOp implements [I.PipeWire].
type pipewireOp struct {
scc io.Closer
dst *check.Absolute
}
func (p *pipewireOp) Type() hst.Enablement { return Process }
func (p *pipewireOp) apply(sys *I) (err error) {
var ctx *pipewire.Context
if ctx, err = sys.pipewireConnect(); err != nil {
return newOpError("pipewire", err, false)
}
defer func() {
if closeErr := ctx.Close(); closeErr != nil && err == nil {
err = newOpError("pipewire", closeErr, false)
}
}()
sys.msg.Verbosef("pipewire pathname socket on %q", p.dst)
var registry *pipewire.Registry
if registry, err = ctx.GetRegistry(); err != nil {
return newOpError("pipewire", err, false)
} else if err = ctx.GetCore().Sync(); err != nil {
return newOpError("pipewire", err, false)
}
var securityContext *pipewire.SecurityContext
if securityContext, err = registry.GetSecurityContext(); err != nil {
return newOpError("pipewire", err, false)
} else if err = ctx.Roundtrip(); err != nil {
return newOpError("pipewire", err, false)
}
if p.scc, err = securityContext.BindAndCreate(p.dst.String(), pipewire.SPADict{
{Key: pipewire.PW_KEY_SEC_ENGINE, Value: "app.hakurei"},
{Key: pipewire.PW_KEY_ACCESS, Value: "restricted"},
}); err != nil {
return newOpError("pipewire", err, false)
} else if err = ctx.GetCore().Sync(); err != nil {
_ = p.scc.Close()
return newOpError("pipewire", err, false)
}
if err = sys.chmod(p.dst.String(), 0); err != nil {
if closeErr := p.scc.Close(); closeErr != nil {
return newOpError("pipewire", errors.Join(err, closeErr), false)
}
return newOpError("pipewire", err, false)
}
if err = sys.aclUpdate(p.dst.String(), sys.uid, acl.Read, acl.Write, acl.Execute); err != nil {
if closeErr := p.scc.Close(); closeErr != nil {
return newOpError("pipewire", errors.Join(err, closeErr), false)
}
return newOpError("pipewire", err, false)
}
return nil
}
func (p *pipewireOp) revert(sys *I, _ *Criteria) error {
if p.scc != nil {
sys.msg.Verbosef("hanging up pipewire socket on %q", p.dst)
return newOpError("pipewire", p.scc.Close(), true)
}
return nil
}
func (p *pipewireOp) Is(o Op) bool {
target, ok := o.(*pipewireOp)
return ok && p != nil && target != nil &&
p.dst.Is(target.dst)
}
func (p *pipewireOp) Path() string { return p.dst.String() }
func (p *pipewireOp) String() string { return fmt.Sprintf("pipewire socket at %q", p.dst) }