diff --git a/internal/outcome/dispatcher.go b/internal/outcome/dispatcher.go index 0813f81..93adfbb 100644 --- a/internal/outcome/dispatcher.go +++ b/internal/outcome/dispatcher.go @@ -66,6 +66,8 @@ type syscallDispatcher interface { // lookupGroupId calls [user.LookupGroup] and returns the Gid field of the resulting [user.Group] struct. lookupGroupId(name string) (string, error) + // lookPath provides exec.LookPath. + lookPath(file string) (string, error) // cmdOutput provides the Output method of [exec.Cmd]. cmdOutput(cmd *exec.Cmd) ([]byte, error) @@ -140,6 +142,7 @@ func (direct) lookupGroupId(name string) (gid string, err error) { return } +func (direct) lookPath(file string) (string, error) { return exec.LookPath(file) } func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() } func (direct) notifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc) { diff --git a/internal/outcome/dispatcher_test.go b/internal/outcome/dispatcher_test.go index 4cfa0fa..84f06c7 100644 --- a/internal/outcome/dispatcher_test.go +++ b/internal/outcome/dispatcher_test.go @@ -707,6 +707,7 @@ func (panicDispatcher) exit(int) { pa func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") } func (panicDispatcher) prctl(uintptr, uintptr, uintptr) error { panic("unreachable") } func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") } +func (panicDispatcher) lookPath(string) (string, error) { panic("unreachable") } func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") } func (panicDispatcher) overflowUid(message.Msg) int { panic("unreachable") } func (panicDispatcher) overflowGid(message.Msg) int { panic("unreachable") } diff --git a/internal/outcome/outcome.go b/internal/outcome/outcome.go index ea01099..45ef443 100644 --- a/internal/outcome/outcome.go +++ b/internal/outcome/outcome.go @@ -70,7 +70,7 @@ type outcomeState struct { // Copied from their respective exported values. mapuid, mapgid *stringPair[int] - // Copied from [EnvPaths] per-process. + // Copied from [env.Paths] per-process. sc hst.Paths *env.Paths @@ -172,6 +172,8 @@ type outcomeStateSys struct { // Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only. directWayland bool + // Copied from [hst.Config]. Safe for read by spPipeWireOp.toSystem only. + directPipeWire bool // Copied from [hst.Config]. Safe for read by spPulseOp.toSystem only. directPulse bool // Copied header from [hst.Config]. Safe for read by spFilesystemOp.toSystem only. @@ -187,9 +189,8 @@ type outcomeStateSys struct { func (s *outcomeState) newSys(config *hst.Config, sys *system.I) *outcomeStateSys { return &outcomeStateSys{ appId: config.ID, et: config.Enablements.Unwrap(), - directWayland: config.DirectWayland, directPulse: config.DirectPulse, - extraPerms: config.ExtraPerms, - sessionBus: config.SessionBus, systemBus: config.SystemBus, + directWayland: config.DirectWayland, directPipeWire: config.DirectPipeWire, directPulse: config.DirectPulse, + extraPerms: config.ExtraPerms, sessionBus: config.SessionBus, systemBus: config.SystemBus, sys: sys, outcomeState: s, } } @@ -295,7 +296,7 @@ func (state *outcomeStateSys) toSystem() error { // optional via enablements &spWaylandOp{}, &spX11Op{}, - spPipeWireOp{}, + &spPipeWireOp{}, &spPulseOp{}, &spDBusOp{}, diff --git a/internal/outcome/run_test.go b/internal/outcome/run_test.go index 01d487a..db18e02 100644 --- a/internal/outcome/run_test.go +++ b/internal/outcome/run_test.go @@ -883,6 +883,16 @@ func (k *stubNixOS) lookupGroupId(name string) (string, error) { } } +func (k *stubNixOS) lookPath(file string) (string, error) { + switch file { + case "pipewire-pulse": + return "/run/current-system/sw/bin/pipewire-pulse", nil + + default: + panic(fmt.Sprintf("unexpected file %q", file)) + } +} + func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) { switch cmd.Path { case "/proc/nonexistent/hsu": diff --git a/internal/outcome/sppipewire.go b/internal/outcome/sppipewire.go index b9359f7..fd99d51 100644 --- a/internal/outcome/sppipewire.go +++ b/internal/outcome/sppipewire.go @@ -3,20 +3,33 @@ package outcome import ( "encoding/gob" + "hakurei.app/container/check" "hakurei.app/hst" "hakurei.app/internal/pipewire" ) -func init() { gob.Register(spPipeWireOp{}) } +const pipewirePulseName = "pipewire-pulse" + +func init() { gob.Register(new(spPipeWireOp)) } // spPipeWireOp exports the PipeWire server to the container via SecurityContext. // Runs after spRuntimeOp. -type spPipeWireOp struct{} +type spPipeWireOp struct { + // Path to pipewire-pulse server. Populated during toSystem if DirectPipeWire is false. + CompatServerPath *check.Absolute +} -func (s spPipeWireOp) toSystem(state *outcomeStateSys) error { +func (s *spPipeWireOp) toSystem(state *outcomeStateSys) error { if state.et&hst.EPipeWire == 0 { return errNotEnabled } + if !state.directPipeWire { + if n, err := state.k.lookPath(pipewirePulseName); err != nil { + return &hst.AppError{Step: "look up " + pipewirePulseName, Err: err} + } else if s.CompatServerPath, err = check.NewAbs(n); err != nil { + return err + } + } appId := state.appId if appId == "" { @@ -27,10 +40,10 @@ func (s spPipeWireOp) toSystem(state *outcomeStateSys) error { return nil } -func (s spPipeWireOp) toContainer(state *outcomeStateParams) error { - innerPath := state.runtimeDir.Append(pipewire.PW_DEFAULT_REMOTE) - state.env[pipewire.Remote] = innerPath.String() - state.params.Bind(state.instancePath().Append("pipewire"), innerPath, 0) +func (s *spPipeWireOp) toContainer(state *outcomeStateParams) error { + innerPath := state.runtimeDir.Append(pipewire.PW_DEFAULT_REMOTE) + state.env[pipewire.Remote] = innerPath.String() + state.params.Bind(state.instancePath().Append("pipewire"), innerPath, 0) return nil } diff --git a/internal/outcome/sppipewire_test.go b/internal/outcome/sppipewire_test.go index 35ad8e5..3259915 100644 --- a/internal/outcome/sppipewire_test.go +++ b/internal/outcome/sppipewire_test.go @@ -16,7 +16,7 @@ func TestSpPipeWireOp(t *testing.T) { checkOpBehaviour(t, []opBehaviourTestCase{ {"not enabled", func(bool, bool) outcomeOp { - return spPipeWireOp{} + return new(spPipeWireOp) }, func() *hst.Config { c := hst.Template() *c.Enablements = 0 @@ -24,8 +24,12 @@ func TestSpPipeWireOp(t *testing.T) { }, nil, nil, nil, nil, errNotEnabled, nil, nil, nil, nil, nil}, {"success", func(bool, bool) outcomeOp { - return spPipeWireOp{} - }, hst.Template, nil, []stub.Call{}, newI(). + return new(spPipeWireOp) + }, func() *hst.Config { + c := hst.Template() + c.DirectPipeWire = true + return c + }, nil, []stub.Call{}, newI(). // state.instance Ephemeral(system.Process, m(wantInstancePrefix), 0711). // toSystem