aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/app/sppulse.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-10-29 04:32:43 +0900
committerOphestra <cat@gensokyo.uk>2025-10-29 04:33:13 +0900
commita0b4e47acc228e48165966d8e139277b8f6f450e (patch)
treed363a6781536ce946aa445271ca1d3df587e6031 /internal/app/sppulse.go
parenta52f7038e5a607dd0901244abb791c96deaf9c2d (diff)
internal/outcome: rename from app
This is less ambiguous, and more accurately describes the purpose of the package. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/app/sppulse.go')
-rw-r--r--internal/app/sppulse.go210
1 files changed, 0 insertions, 210 deletions
diff --git a/internal/app/sppulse.go b/internal/app/sppulse.go
deleted file mode 100644
index fe63f366..00000000
--- a/internal/app/sppulse.go
+++ /dev/null
@@ -1,210 +0,0 @@
-package app
-
-import (
- "encoding/gob"
- "errors"
- "fmt"
- "io"
- "io/fs"
- "os"
- "strconv"
- "syscall"
-
- "hakurei.app/container/check"
- "hakurei.app/hst"
- "hakurei.app/message"
-)
-
-const pulseCookieSizeMax = 1 << 8
-
-func init() { gob.Register(new(spPulseOp)) }
-
-// spPulseOp exports the PulseAudio server to the container.
-// Runs after spRuntimeOp.
-type spPulseOp struct {
- // PulseAudio cookie data, populated during toSystem if a cookie is present.
- Cookie *[pulseCookieSizeMax]byte
- // PulseAudio cookie size, populated during toSystem if a cookie is present.
- CookieSize int
-}
-
-func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
- if state.et&hst.EPulse == 0 {
- return errNotEnabled
- }
-
- pulseRuntimeDir, pulseSocket := s.commonPaths(state.outcomeState)
-
- if _, err := state.k.stat(pulseRuntimeDir.String()); err != nil {
- if !errors.Is(err, fs.ErrNotExist) {
- return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
- }
- return newWithMessageError(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir), err)
- }
-
- if fi, err := state.k.stat(pulseSocket.String()); err != nil {
- if !errors.Is(err, fs.ErrNotExist) {
- return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
- }
- return newWithMessageError(fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir), err)
- } else {
- if m := fi.Mode(); m&0o006 != 0o006 {
- return newWithMessage(fmt.Sprintf("unexpected permissions on %q: %s", pulseSocket, m))
- }
- }
-
- // pulse socket is world writable and its parent directory DAC permissions prevents access;
- // hard link to target-executable share directory to grant access
- state.sys.Link(pulseSocket, state.runtime().Append("pulse"))
-
- // load up to pulseCookieSizeMax bytes of pulse cookie for transmission to shim
- if a, err := discoverPulseCookie(state.k); err != nil {
- return err
- } else if a != nil {
- s.Cookie = new([pulseCookieSizeMax]byte)
- if s.CookieSize, err = loadFile(state.msg, state.k, "PulseAudio cookie", a.String(), s.Cookie[:]); err != nil {
- return err
- }
- } else {
- state.msg.Verbose("cannot locate PulseAudio cookie (tried " +
- "$PULSE_COOKIE, " +
- "$XDG_CONFIG_HOME/pulse/cookie, " +
- "$HOME/.pulse-cookie)")
- }
-
- return nil
-}
-
-func (s *spPulseOp) toContainer(state *outcomeStateParams) error {
- innerPulseSocket := state.runtimeDir.Append("pulse", "native")
- state.params.Bind(state.runtimePath().Append("pulse"), innerPulseSocket, 0)
- state.env["PULSE_SERVER"] = "unix:" + innerPulseSocket.String()
-
- if s.Cookie != nil {
- innerDst := hst.AbsPrivateTmp.Append("/pulse-cookie")
-
- if s.CookieSize < 0 || s.CookieSize > pulseCookieSizeMax {
- return newWithMessage("unexpected PulseAudio cookie size")
- }
- state.env["PULSE_COOKIE"] = innerDst.String()
- state.params.Place(innerDst, s.Cookie[:s.CookieSize])
- }
-
- return nil
-}
-
-func (s *spPulseOp) commonPaths(state *outcomeState) (pulseRuntimeDir, pulseSocket *check.Absolute) {
- // PulseAudio runtime directory (usually `/run/user/%d/pulse`)
- pulseRuntimeDir = state.sc.RuntimePath.Append("pulse")
- // PulseAudio socket (usually `/run/user/%d/pulse/native`)
- pulseSocket = pulseRuntimeDir.Append("native")
- return
-}
-
-// discoverPulseCookie attempts to discover the pathname of the PulseAudio cookie of the current user.
-// If both returned pathname and error are nil, the cookie is likely unavailable and can be silently skipped.
-func discoverPulseCookie(k syscallDispatcher) (*check.Absolute, error) {
- const paLocateStep = "locate PulseAudio cookie"
-
- // from environment
- if p, ok := k.lookupEnv("PULSE_COOKIE"); ok {
- if a, err := check.NewAbs(p); err != nil {
- return nil, &hst.AppError{Step: paLocateStep, Err: err}
- } else {
- // this takes precedence, do not verify whether the file is accessible
- return a, nil
- }
- }
-
- // $HOME/.pulse-cookie
- if p, ok := k.lookupEnv("HOME"); ok {
- var pulseCookiePath *check.Absolute
- if a, err := check.NewAbs(p); err != nil {
- return nil, &hst.AppError{Step: paLocateStep, Err: err}
- } else {
- pulseCookiePath = a.Append(".pulse-cookie")
- }
-
- if fi, err := k.stat(pulseCookiePath.String()); err != nil {
- if !errors.Is(err, fs.ErrNotExist) {
- return nil, &hst.AppError{Step: "access PulseAudio cookie", Err: err}
- }
- // fallthrough
- } else if fi.IsDir() {
- // fallthrough
- } else {
- return pulseCookiePath, nil
- }
- }
-
- // $XDG_CONFIG_HOME/pulse/cookie
- if p, ok := k.lookupEnv("XDG_CONFIG_HOME"); ok {
- var pulseCookiePath *check.Absolute
- if a, err := check.NewAbs(p); err != nil {
- return nil, &hst.AppError{Step: paLocateStep, Err: err}
- } else {
- pulseCookiePath = a.Append("pulse", "cookie")
- }
-
- if fi, err := k.stat(pulseCookiePath.String()); err != nil {
- if !errors.Is(err, fs.ErrNotExist) {
- return nil, &hst.AppError{Step: "access PulseAudio cookie", Err: err}
- }
- // fallthrough
- } else if fi.IsDir() {
- // fallthrough
- } else {
- return pulseCookiePath, nil
- }
- }
-
- // cookie not present
- // not fatal: authentication is disabled
- return nil, nil
-}
-
-// loadFile reads up to len(buf) bytes from the file at pathname.
-func loadFile(
- msg message.Msg, k syscallDispatcher,
- description, pathname string, buf []byte,
-) (int, error) {
- n := len(buf)
- if n == 0 {
- return -1, errors.New("invalid buffer")
- }
-
- if fi, err := k.stat(pathname); err != nil {
- return -1, &hst.AppError{Step: "access " + description, Err: err}
- } else {
- if fi.IsDir() {
- return -1, &hst.AppError{Step: "read " + description,
- Err: &os.PathError{Op: "stat", Path: pathname, Err: syscall.EISDIR}}
- }
- if s := fi.Size(); s > int64(n) {
- return -1, newWithMessageError(
- description+" at "+strconv.Quote(pathname)+" exceeds expected size",
- &os.PathError{Op: "stat", Path: pathname, Err: syscall.ENOMEM},
- )
- } else if s < int64(n) {
- msg.Verbosef("%s at %q is %d bytes shorter than expected", description, pathname, int64(n)-s)
- } else {
- msg.Verbosef("loading %d bytes from %q", n, pathname)
- }
- }
-
- if f, err := k.open(pathname); err != nil {
- return -1, &hst.AppError{Step: "open " + description, Err: err}
- } else {
- if n, err = f.Read(buf); err != nil {
- if !errors.Is(err, io.EOF) {
- _ = f.Close()
- return n, &hst.AppError{Step: "read " + description, Err: err}
- }
- }
-
- if err = f.Close(); err != nil {
- return n, &hst.AppError{Step: "close " + description, Err: err}
- }
- return n, nil
- }
-}