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) }