internal/app/sppulse: relocate hard to test code
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m15s
Test / Hakurei (push) Successful in 3m1s
Test / Sandbox (race detector) (push) Successful in 3m59s
Test / Hpkg (push) Successful in 4m8s
Test / Hakurei (race detector) (push) Successful in 4m48s
Test / Flake checks (push) Successful in 1m19s
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m15s
Test / Hakurei (push) Successful in 3m1s
Test / Sandbox (race detector) (push) Successful in 3m59s
Test / Hpkg (push) Successful in 4m8s
Test / Hakurei (race detector) (push) Successful in 4m48s
Test / Flake checks (push) Successful in 1m19s
These are better tested separately instead of creating many op test cases. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
6cf58ca1b3
commit
c922c3f80e
@ -7,10 +7,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"hakurei.app/container/check"
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
const pulseCookieSizeMax = 1 << 8
|
const pulseCookieSizeMax = 1 << 8
|
||||||
@ -48,101 +50,17 @@ func (s *spPulseOp) toSystem(state *outcomeStateSys) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hard link pulse socket into target-executable share
|
// 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"))
|
state.sys.Link(pulseSocket, state.runtime().Append("pulse"))
|
||||||
|
|
||||||
// publish current user's pulse cookie for target user
|
// load up to pulseCookieSizeMax bytes of pulse cookie for transmission to shim
|
||||||
var paCookiePath *check.Absolute
|
if a, err := discoverPulseCookie(state.k); err != nil {
|
||||||
{
|
return err
|
||||||
const paLocateStep = "locate PulseAudio cookie"
|
} else if a != nil {
|
||||||
|
|
||||||
// from environment
|
|
||||||
if p, ok := state.k.lookupEnv("PULSE_COOKIE"); ok {
|
|
||||||
if a, err := check.NewAbs(p); err != nil {
|
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
|
||||||
} else {
|
|
||||||
// this takes precedence, do not verify whether the file is accessible
|
|
||||||
paCookiePath = a
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// $HOME/.pulse-cookie
|
|
||||||
if p, ok := state.k.lookupEnv("HOME"); ok {
|
|
||||||
if a, err := check.NewAbs(p); err != nil {
|
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
|
||||||
} else {
|
|
||||||
paCookiePath = a.Append(".pulse-cookie")
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi, err := state.k.stat(paCookiePath.String()); err != nil {
|
|
||||||
paCookiePath = nil
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
} else if fi.IsDir() {
|
|
||||||
paCookiePath = nil
|
|
||||||
} else {
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// $XDG_CONFIG_HOME/pulse/cookie
|
|
||||||
if p, ok := state.k.lookupEnv("XDG_CONFIG_HOME"); ok {
|
|
||||||
if a, err := check.NewAbs(p); err != nil {
|
|
||||||
return &hst.AppError{Step: paLocateStep, Err: err}
|
|
||||||
} else {
|
|
||||||
paCookiePath = a.Append("pulse", "cookie")
|
|
||||||
}
|
|
||||||
if fi, err := state.k.stat(paCookiePath.String()); err != nil {
|
|
||||||
paCookiePath = nil
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
|
||||||
}
|
|
||||||
// fallthrough
|
|
||||||
} else if fi.IsDir() {
|
|
||||||
paCookiePath = nil
|
|
||||||
} else {
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out:
|
|
||||||
}
|
|
||||||
|
|
||||||
if paCookiePath != nil {
|
|
||||||
if b, err := state.k.stat(paCookiePath.String()); err != nil {
|
|
||||||
return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
|
|
||||||
} else {
|
|
||||||
if b.IsDir() {
|
|
||||||
return &hst.AppError{Step: "read PulseAudio cookie", Err: &os.PathError{Op: "stat", Path: paCookiePath.String(), Err: syscall.EISDIR}}
|
|
||||||
}
|
|
||||||
if b.Size() > pulseCookieSizeMax {
|
|
||||||
return newWithMessageError(
|
|
||||||
fmt.Sprintf("PulseAudio cookie at %q exceeds maximum expected size", paCookiePath),
|
|
||||||
&os.PathError{Op: "stat", Path: paCookiePath.String(), Err: syscall.ENOMEM},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var r io.ReadCloser
|
|
||||||
if f, err := state.k.open(paCookiePath.String()); err != nil {
|
|
||||||
return &hst.AppError{Step: "open PulseAudio cookie", Err: err}
|
|
||||||
} else {
|
|
||||||
r = f
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Cookie = new([pulseCookieSizeMax]byte)
|
s.Cookie = new([pulseCookieSizeMax]byte)
|
||||||
if n, err := r.Read(s.Cookie[:]); err != nil {
|
if err = loadFile(state.msg, state.k, "PulseAudio cookie", a.String(), s.Cookie[:]); err != nil {
|
||||||
if !errors.Is(err, io.EOF) {
|
return err
|
||||||
_ = r.Close()
|
|
||||||
return &hst.AppError{Step: "read PulseAudio cookie", Err: err}
|
|
||||||
}
|
|
||||||
state.msg.Verbosef("copied %d bytes from %q", n, paCookiePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.Close(); err != nil {
|
|
||||||
return &hst.AppError{Step: "close PulseAudio cookie", Err: err}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.msg.Verbose("cannot locate PulseAudio cookie (tried " +
|
state.msg.Verbose("cannot locate PulseAudio cookie (tried " +
|
||||||
@ -175,3 +93,111 @@ func (s *spPulseOp) commonPaths(state *outcomeState) (pulseRuntimeDir, pulseSock
|
|||||||
pulseSocket = pulseRuntimeDir.Append("native")
|
pulseSocket = pulseRuntimeDir.Append("native")
|
||||||
return
|
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,
|
||||||
|
) error {
|
||||||
|
n := len(buf)
|
||||||
|
if n == 0 {
|
||||||
|
return errors.New("invalid buffer")
|
||||||
|
}
|
||||||
|
msg.Verbosef("loading up to %d bytes from %q", n, pathname)
|
||||||
|
|
||||||
|
if fi, err := k.stat(pathname); err != nil {
|
||||||
|
return &hst.AppError{Step: "access " + description, Err: err}
|
||||||
|
} else {
|
||||||
|
if fi.IsDir() {
|
||||||
|
return &hst.AppError{Step: "read " + description,
|
||||||
|
Err: &os.PathError{Op: "stat", Path: pathname, Err: syscall.EISDIR}}
|
||||||
|
}
|
||||||
|
if s := fi.Size(); s > int64(n) {
|
||||||
|
return newWithMessageError(
|
||||||
|
description+" at "+strconv.Quote(pathname)+" exceeds maximum expected size",
|
||||||
|
&os.PathError{Op: "stat", Path: pathname, Err: syscall.ENOMEM},
|
||||||
|
)
|
||||||
|
} else if s < int64(n) {
|
||||||
|
msg.Verbosef("%s at %q is %d bytes longer than expected", description, pathname, int64(n)-s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f, err := k.open(pathname); err != nil {
|
||||||
|
return &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 &hst.AppError{Step: "read " + description, Err: err}
|
||||||
|
}
|
||||||
|
msg.Verbosef("copied %d bytes from %q", n, pathname)
|
||||||
|
} // nil error indicates a partial read, which is handled after stat
|
||||||
|
|
||||||
|
if err = f.Close(); err != nil {
|
||||||
|
return &hst.AppError{Step: "close " + description, Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user