internal/hlog: remove error wrapping
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Sandbox (push) Successful in 2m29s
Test / Hakurei (push) Successful in 4m6s
Test / Hpkg (push) Successful in 4m45s
Test / Sandbox (race detector) (push) Successful in 4m48s
Test / Hakurei (race detector) (push) Successful in 6m4s
Test / Flake checks (push) Successful in 1m26s
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Sandbox (push) Successful in 2m29s
Test / Hakurei (push) Successful in 4m6s
Test / Hpkg (push) Successful in 4m45s
Test / Sandbox (race detector) (push) Successful in 4m48s
Test / Hakurei (race detector) (push) Successful in 6m4s
Test / Flake checks (push) Successful in 1m26s
This was a stopgap solution that lasted for way too long. This finally removes it and prepares internal/app for some major changes. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
6265aea73a
commit
f876043844
@ -82,8 +82,7 @@ func buildCommand(out io.Writer) command.Command {
|
||||
passwdFunc = func() {
|
||||
var us string
|
||||
if uid, err := std.Uid(aid); err != nil {
|
||||
hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:")
|
||||
os.Exit(1)
|
||||
fatal("cannot obtain uid from setuid wrapper:", err)
|
||||
} else {
|
||||
us = strconv.Itoa(uid)
|
||||
}
|
||||
@ -260,11 +259,33 @@ func runApp(config *hst.Config) {
|
||||
|
||||
rs := new(app.RunState)
|
||||
if sa, err := a.Seal(config); err != nil {
|
||||
hlog.PrintBaseError(err, "cannot seal app:")
|
||||
internal.Exit(1)
|
||||
hlog.BeforeExit()
|
||||
fatal("cannot seal app:", err)
|
||||
} else {
|
||||
internal.Exit(app.PrintRunStateErr(rs, sa.Run(rs)))
|
||||
hlog.BeforeExit()
|
||||
os.Exit(app.PrintRunStateErr(rs, sa.Run(rs)))
|
||||
}
|
||||
|
||||
*(*int)(nil) = 0 // not reached
|
||||
}
|
||||
|
||||
// fatal prints the error message according to [container.GetErrorMessage], or fallback
|
||||
// prepended to err if an error message is not available, followed by a call to [os.Exit](1).
|
||||
func fatal(fallback string, err error) {
|
||||
m, ok := container.GetErrorMessage(err)
|
||||
if !ok {
|
||||
log.Fatal(fallback, err)
|
||||
return
|
||||
}
|
||||
|
||||
// this indicates the error message has already reached stderr, outside the current process's control;
|
||||
// this is only reached when hsu fails for any reason, as we do not want a second error message following hsu
|
||||
// TODO(ophestra): handle the hsu error here instead of relying on a magic string
|
||||
if m == "\x00" {
|
||||
hlog.Verbose("*"+fallback, err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
log.Fatal(m)
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -14,7 +13,6 @@ import (
|
||||
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
|
||||
@ -26,8 +24,7 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
|
||||
|
||||
// get hid by querying uid of identity 0
|
||||
if uid, err := std.Uid(0); err != nil {
|
||||
hlog.PrintBaseError(err, "cannot obtain uid from setuid wrapper:")
|
||||
os.Exit(1)
|
||||
fatal("cannot obtain uid from setuid wrapper:", err)
|
||||
} else {
|
||||
info.User = (uid / 10000) - 100
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"hakurei.app/internal/sys"
|
||||
)
|
||||
|
||||
// New returns the address of a newly initialised [App] struct.
|
||||
func New(ctx context.Context, os sys.State) (*App, error) {
|
||||
a := new(App)
|
||||
a.sys = os
|
||||
@ -24,6 +25,7 @@ func New(ctx context.Context, os sys.State) (*App, error) {
|
||||
return a, err
|
||||
}
|
||||
|
||||
// MustNew calls [New] and panics if an error is returned.
|
||||
func MustNew(ctx context.Context, os sys.State) *App {
|
||||
a, err := New(ctx, os)
|
||||
if err != nil {
|
||||
@ -32,6 +34,7 @@ func MustNew(ctx context.Context, os sys.State) *App {
|
||||
return a
|
||||
}
|
||||
|
||||
// An App keeps track of the hakurei container lifecycle.
|
||||
type App struct {
|
||||
outcome *Outcome
|
||||
|
||||
@ -46,7 +49,7 @@ func (a *App) ID() state.ID { a.mu.RLock(); defer a.mu.RUnlock(); return a.id.un
|
||||
|
||||
func (a *App) String() string {
|
||||
if a == nil {
|
||||
return "(invalid app)"
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
a.mu.RLock()
|
||||
@ -54,12 +57,12 @@ func (a *App) String() string {
|
||||
|
||||
if a.outcome != nil {
|
||||
if a.outcome.user.uid == nil {
|
||||
return fmt.Sprintf("(sealed app %s with invalid uid)", a.id)
|
||||
return "<invalid>"
|
||||
}
|
||||
return fmt.Sprintf("(sealed app %s as uid %s)", a.id, a.outcome.user.uid)
|
||||
return fmt.Sprintf("sealed app %s as uid %s", a.id, a.outcome.user.uid)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("(unsealed app %s)", a.id)
|
||||
return fmt.Sprintf("unsealed app %s", a.id)
|
||||
}
|
||||
|
||||
// Seal determines the [Outcome] of [hst.Config].
|
||||
@ -69,7 +72,7 @@ func (a *App) Seal(config *hst.Config) (*Outcome, error) {
|
||||
defer a.mu.Unlock()
|
||||
|
||||
if a.outcome != nil {
|
||||
panic("app sealed twice")
|
||||
panic("attempting to seal app twice")
|
||||
}
|
||||
|
||||
seal := new(Outcome)
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/app"
|
||||
"hakurei.app/internal/app/state"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/internal/sys"
|
||||
"hakurei.app/system"
|
||||
)
|
||||
@ -37,8 +36,11 @@ func TestApp(t *testing.T) {
|
||||
)
|
||||
if !t.Run("seal", func(t *testing.T) {
|
||||
if sa, err := a.Seal(tc.config); err != nil {
|
||||
hlog.PrintBaseError(err, "got generic error:")
|
||||
if s, ok := container.GetErrorMessage(err); !ok {
|
||||
t.Errorf("Seal: error = %v", err)
|
||||
} else {
|
||||
t.Errorf("Seal: %s", s)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
gotSys, gotContainer = app.AppIParams(a, sa)
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/hlog"
|
||||
"hakurei.app/internal/sys"
|
||||
"hakurei.app/system/dbus"
|
||||
)
|
||||
@ -23,7 +22,7 @@ const preallocateOpsCount = 1 << 5
|
||||
// Note that remaining container setup must be queued by the caller.
|
||||
func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid *int) (*container.Params, map[string]string, error) {
|
||||
if s == nil {
|
||||
return nil, nil, hlog.WrapErr(syscall.EBADE, "invalid container configuration")
|
||||
return nil, nil, newWithMessage("invalid container configuration")
|
||||
}
|
||||
|
||||
params := &container.Params{
|
||||
|
@ -4,48 +4,33 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/hlog"
|
||||
)
|
||||
|
||||
// PrintRunStateErr prints an error message via [log] if runErr is not nil, and returns an appropriate exit code.
|
||||
//
|
||||
// TODO(ophestra): remove this function once RunState has been replaced
|
||||
func PrintRunStateErr(rs *RunState, runErr error) (code int) {
|
||||
code = rs.ExitStatus()
|
||||
|
||||
if runErr != nil {
|
||||
if rs.Time == nil {
|
||||
hlog.PrintBaseError(runErr, "cannot start app:")
|
||||
// no process has been created
|
||||
printMessageError("cannot start app:", runErr)
|
||||
} else {
|
||||
var e *hlog.BaseError
|
||||
if !hlog.AsBaseError(runErr, &e) {
|
||||
log.Println("wait failed:", runErr)
|
||||
if m, ok := container.GetErrorMessage(runErr); !ok {
|
||||
// catch-all for unexpected errors
|
||||
log.Println("run returned error:", runErr)
|
||||
} else {
|
||||
// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError
|
||||
var se *StateStoreError
|
||||
if !errors.As(runErr, &se) {
|
||||
// does not need special handling
|
||||
log.Print(e.Message())
|
||||
// this could only be returned from a shim setup failure path
|
||||
log.Print(m)
|
||||
} else {
|
||||
// inner error are either unwrapped store errors
|
||||
// or joined errors returned by *appSealTx revert
|
||||
// wrapped in *app.BaseError
|
||||
var ej RevertCompoundError
|
||||
if !errors.As(se.InnerErr, &ej) {
|
||||
// does not require special handling
|
||||
log.Print(e.Message())
|
||||
} else {
|
||||
errs := ej.Unwrap()
|
||||
|
||||
// every error here is wrapped in *app.BaseError
|
||||
for _, ei := range errs {
|
||||
var eb *hlog.BaseError
|
||||
if !errors.As(ei, &eb) {
|
||||
// unreachable
|
||||
log.Println("invalid error type returned by revert:", ei)
|
||||
} else {
|
||||
// print inner *app.BaseError message
|
||||
log.Print(eb.Message())
|
||||
}
|
||||
}
|
||||
}
|
||||
// InnerErr is returned by c.Save(&sd, seal.ct), and are always unwrapped
|
||||
printMessageError("error returned during revert:",
|
||||
&FinaliseError{Step: "save process state", Err: se.InnerErr})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -58,43 +43,45 @@ func PrintRunStateErr(rs *RunState, runErr error) (code int) {
|
||||
if rs.RevertErr != nil {
|
||||
var stateStoreError *StateStoreError
|
||||
if !errors.As(rs.RevertErr, &stateStoreError) || stateStoreError == nil {
|
||||
hlog.PrintBaseError(rs.RevertErr, "generic fault during cleanup:")
|
||||
printMessageError("cannot clean up:", rs.RevertErr)
|
||||
goto out
|
||||
}
|
||||
|
||||
if stateStoreError.Err != nil {
|
||||
if len(stateStoreError.Err) == 2 {
|
||||
if stateStoreError.Err[0] != nil {
|
||||
if joinedErrs, ok := stateStoreError.Err[0].(interface{ Unwrap() []error }); !ok {
|
||||
hlog.PrintBaseError(stateStoreError.Err[0], "generic fault during revert:")
|
||||
if stateStoreError.Errs != nil {
|
||||
if len(stateStoreError.Errs) == 2 { // storeErr.save(revertErr, store.Close())
|
||||
if stateStoreError.Errs[0] != nil { // revertErr is MessageError joined by errors.Join
|
||||
var joinedErrors interface {
|
||||
Unwrap() []error
|
||||
error
|
||||
}
|
||||
if !errors.As(stateStoreError.Errs[0], &joinedErrors) {
|
||||
printMessageError("cannot revert:", stateStoreError.Errs[0])
|
||||
} else {
|
||||
for _, err := range joinedErrs.Unwrap() {
|
||||
for _, err := range joinedErrors.Unwrap() {
|
||||
if err != nil {
|
||||
hlog.PrintBaseError(err, "fault during revert:")
|
||||
printMessageError("cannot revert:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if stateStoreError.Err[1] != nil {
|
||||
log.Printf("cannot close store: %v", stateStoreError.Err[1])
|
||||
if stateStoreError.Errs[1] != nil { // store.Close() is joined by errors.Join
|
||||
log.Printf("cannot close store: %v", stateStoreError.Errs[1])
|
||||
}
|
||||
} else {
|
||||
log.Printf("fault during cleanup: %v",
|
||||
errors.Join(stateStoreError.Err...))
|
||||
log.Printf("fault during cleanup: %v", errors.Join(stateStoreError.Errs...))
|
||||
}
|
||||
}
|
||||
|
||||
if stateStoreError.OpErr != nil {
|
||||
log.Printf("blind revert due to store fault: %v",
|
||||
stateStoreError.OpErr)
|
||||
log.Printf("blind revert due to store fault: %v", stateStoreError.OpErr)
|
||||
}
|
||||
|
||||
if stateStoreError.DoErr != nil {
|
||||
hlog.PrintBaseError(stateStoreError.DoErr, "state store operation unsuccessful:")
|
||||
printMessageError("state store operation unsuccessful:", stateStoreError.DoErr)
|
||||
}
|
||||
|
||||
if stateStoreError.Inner && stateStoreError.InnerErr != nil {
|
||||
hlog.PrintBaseError(stateStoreError.InnerErr, "cannot destroy state entry:")
|
||||
printMessageError("cannot destroy state entry:", stateStoreError.InnerErr)
|
||||
}
|
||||
|
||||
out:
|
||||
@ -108,7 +95,18 @@ func PrintRunStateErr(rs *RunState, runErr error) (code int) {
|
||||
return
|
||||
}
|
||||
|
||||
// StateStoreError is returned for a failed state save
|
||||
// TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal
|
||||
func printMessageError(fallback string, err error) {
|
||||
if m, ok := container.GetErrorMessage(err); ok {
|
||||
if m != "\x00" {
|
||||
log.Print(m)
|
||||
}
|
||||
} else {
|
||||
log.Println(fallback, err)
|
||||
}
|
||||
}
|
||||
|
||||
// StateStoreError is returned for a failed state save.
|
||||
type StateStoreError struct {
|
||||
// whether inner function was called
|
||||
Inner bool
|
||||
@ -119,22 +117,23 @@ type StateStoreError struct {
|
||||
// stores an arbitrary store operation error
|
||||
OpErr error
|
||||
// stores arbitrary errors
|
||||
Err []error
|
||||
Errs []error
|
||||
}
|
||||
|
||||
// save saves arbitrary errors in [StateStoreError] once.
|
||||
// save saves arbitrary errors in [StateStoreError.Errs] once.
|
||||
func (e *StateStoreError) save(errs ...error) {
|
||||
if len(errs) == 0 || e.Err != nil {
|
||||
if len(errs) == 0 || e.Errs != nil {
|
||||
panic("invalid call to save")
|
||||
}
|
||||
e.Err = errs
|
||||
e.Errs = errs
|
||||
}
|
||||
|
||||
func (e *StateStoreError) equiv(a ...any) error {
|
||||
if e.Inner && e.InnerErr == nil && e.DoErr == nil && e.OpErr == nil && errors.Join(e.Err...) == nil {
|
||||
// equiv returns an error that [StateStoreError] is equivalent to, including nil.
|
||||
func (e *StateStoreError) equiv(step string) error {
|
||||
if e.Inner && e.InnerErr == nil && e.DoErr == nil && e.OpErr == nil && errors.Join(e.Errs...) == nil {
|
||||
return nil
|
||||
} else {
|
||||
return hlog.WrapErrSuffix(e, a...)
|
||||
return &FinaliseError{Step: step, Err: e}
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +147,7 @@ func (e *StateStoreError) Error() string {
|
||||
if e.OpErr != nil {
|
||||
return e.OpErr.Error()
|
||||
}
|
||||
if err := errors.Join(e.Err...); err != nil {
|
||||
if err := errors.Join(e.Errs...); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
@ -157,7 +156,7 @@ func (e *StateStoreError) Error() string {
|
||||
}
|
||||
|
||||
func (e *StateStoreError) Unwrap() (errs []error) {
|
||||
errs = make([]error, 0, 3)
|
||||
errs = make([]error, 0, 3+len(e.Errs))
|
||||
if e.InnerErr != nil {
|
||||
errs = append(errs, e.InnerErr)
|
||||
}
|
||||
@ -167,15 +166,10 @@ func (e *StateStoreError) Unwrap() (errs []error) {
|
||||
if e.OpErr != nil {
|
||||
errs = append(errs, e.OpErr)
|
||||
}
|
||||
if err := errors.Join(e.Err...); err != nil {
|
||||
for _, err := range e.Errs {
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// A RevertCompoundError encapsulates errors returned by
|
||||
// the Revert method of [system.I].
|
||||
type RevertCompoundError interface {
|
||||
Error() string
|
||||
Unwrap() []error
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ func (seal *Outcome) Run(rs *RunState) error {
|
||||
}()
|
||||
})
|
||||
storeErr.save(revertErr, store.Close())
|
||||
rs.RevertErr = storeErr.equiv("error during cleanup:")
|
||||
rs.RevertErr = storeErr.equiv("clean up")
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithCancel(seal.ctx)
|
||||
@ -119,8 +119,7 @@ func (seal *Outcome) Run(rs *RunState) error {
|
||||
|
||||
var e *gob.Encoder
|
||||
if fd, encoder, err := container.Setup(&cmd.ExtraFiles); err != nil {
|
||||
return hlog.WrapErrSuffix(err,
|
||||
"cannot create shim setup pipe:")
|
||||
return &FinaliseError{Step: "create shim setup pipe", Err: err}
|
||||
} else {
|
||||
e = encoder
|
||||
cmd.Env = []string{
|
||||
@ -140,8 +139,7 @@ func (seal *Outcome) Run(rs *RunState) error {
|
||||
hlog.Verbosef("setuid helper at %s", hsuPath)
|
||||
hlog.Suspend()
|
||||
if err := cmd.Start(); err != nil {
|
||||
return hlog.WrapErrSuffix(err,
|
||||
"cannot start setuid wrapper:")
|
||||
return &FinaliseError{Step: "start setuid wrapper", Err: err}
|
||||
}
|
||||
rs.setStart()
|
||||
|
||||
@ -161,14 +159,12 @@ func (seal *Outcome) Run(rs *RunState) error {
|
||||
case err := <-setupErr:
|
||||
if err != nil {
|
||||
hlog.Resume()
|
||||
return hlog.WrapErrSuffix(err,
|
||||
"cannot transmit shim config:")
|
||||
return &FinaliseError{Step: "transmit shim config", Err: err}
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
hlog.Resume()
|
||||
return hlog.WrapErr(syscall.ECANCELED,
|
||||
"shim setup canceled")
|
||||
return newWithMessageError("shim setup canceled", syscall.ECANCELED)
|
||||
}
|
||||
|
||||
// returned after blocking on waitErr
|
||||
@ -225,5 +221,5 @@ func (seal *Outcome) Run(rs *RunState) error {
|
||||
seal.dbusMsg()
|
||||
}
|
||||
|
||||
return earlyStoreErr.equiv("cannot save process state:")
|
||||
return earlyStoreErr.equiv("save process state")
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -28,35 +28,36 @@ import (
|
||||
"hakurei.app/system/wayland"
|
||||
)
|
||||
|
||||
const (
|
||||
home = "HOME"
|
||||
shell = "SHELL"
|
||||
// A FinaliseError is returned while finalising a [hst.Config] outcome.
|
||||
type FinaliseError struct {
|
||||
Step string
|
||||
Err error
|
||||
Msg string
|
||||
}
|
||||
|
||||
xdgConfigHome = "XDG_CONFIG_HOME"
|
||||
xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||
xdgSessionClass = "XDG_SESSION_CLASS"
|
||||
xdgSessionType = "XDG_SESSION_TYPE"
|
||||
func (e *FinaliseError) Error() string { return e.Err.Error() }
|
||||
func (e *FinaliseError) Unwrap() error { return e.Err }
|
||||
func (e *FinaliseError) Message() string {
|
||||
if e.Msg != "" {
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
term = "TERM"
|
||||
display = "DISPLAY"
|
||||
switch {
|
||||
case errors.As(e.Err, new(*os.PathError)),
|
||||
errors.As(e.Err, new(*os.LinkError)),
|
||||
errors.As(e.Err, new(*os.SyscallError)),
|
||||
errors.As(e.Err, new(*net.OpError)):
|
||||
return "cannot " + e.Error()
|
||||
|
||||
pulseServer = "PULSE_SERVER"
|
||||
pulseCookie = "PULSE_COOKIE"
|
||||
default:
|
||||
return "cannot " + e.Step + ": " + e.Error()
|
||||
}
|
||||
}
|
||||
|
||||
dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS"
|
||||
dbusSystemBusAddress = "DBUS_SYSTEM_BUS_ADDRESS"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIdent = errors.New("invalid identity")
|
||||
ErrName = errors.New("invalid username")
|
||||
|
||||
ErrXDisplay = errors.New(display + " unset")
|
||||
|
||||
ErrPulseCookie = errors.New("pulse cookie not present")
|
||||
ErrPulseSocket = errors.New("pulse socket not present")
|
||||
ErrPulseMode = errors.New("unexpected pulse socket mode")
|
||||
)
|
||||
func newWithMessage(msg string) error { return newWithMessageError(msg, os.ErrInvalid) }
|
||||
func newWithMessageError(msg string, err error) error {
|
||||
return &FinaliseError{Step: "finalise", Err: err, Msg: msg}
|
||||
}
|
||||
|
||||
// An Outcome is the runnable state of a hakurei container via [hst.Config].
|
||||
type Outcome struct {
|
||||
@ -146,35 +147,55 @@ type hsuUser struct {
|
||||
}
|
||||
|
||||
func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Config) error {
|
||||
const (
|
||||
home = "HOME"
|
||||
shell = "SHELL"
|
||||
|
||||
xdgConfigHome = "XDG_CONFIG_HOME"
|
||||
xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||
xdgSessionClass = "XDG_SESSION_CLASS"
|
||||
xdgSessionType = "XDG_SESSION_TYPE"
|
||||
|
||||
term = "TERM"
|
||||
display = "DISPLAY"
|
||||
|
||||
pulseServer = "PULSE_SERVER"
|
||||
pulseCookie = "PULSE_COOKIE"
|
||||
|
||||
dbusSessionBusAddress = "DBUS_SESSION_BUS_ADDRESS"
|
||||
dbusSystemBusAddress = "DBUS_SYSTEM_BUS_ADDRESS"
|
||||
)
|
||||
|
||||
if ctx == nil {
|
||||
// unreachable
|
||||
panic("invalid call to finalise")
|
||||
}
|
||||
if seal.ctx != nil {
|
||||
// unreachable
|
||||
panic("attempting to finalise twice")
|
||||
}
|
||||
seal.ctx = ctx
|
||||
|
||||
if config == nil {
|
||||
return hlog.WrapErr(syscall.EINVAL, syscall.EINVAL.Error())
|
||||
// unreachable
|
||||
return newWithMessage("invalid configuration")
|
||||
}
|
||||
if config.Home == nil {
|
||||
return hlog.WrapErr(os.ErrInvalid, "invalid path to home directory")
|
||||
return newWithMessage("invalid path to home directory")
|
||||
}
|
||||
|
||||
{
|
||||
// encode initial configuration for state tracking
|
||||
ct := new(bytes.Buffer)
|
||||
if err := gob.NewEncoder(ct).Encode(config); err != nil {
|
||||
return hlog.WrapErrSuffix(err,
|
||||
"cannot encode initial config:")
|
||||
return &FinaliseError{Step: "encode initial config", Err: err}
|
||||
}
|
||||
seal.ct = ct
|
||||
}
|
||||
|
||||
// allowed identity range 0 to 9999, this is checked again in hsu
|
||||
if config.Identity < 0 || config.Identity > 9999 {
|
||||
return hlog.WrapErr(ErrIdent,
|
||||
fmt.Sprintf("identity %d out of range", config.Identity))
|
||||
return newWithMessage(fmt.Sprintf("identity %d out of range", config.Identity))
|
||||
}
|
||||
|
||||
seal.user = hsuUser{
|
||||
@ -185,8 +206,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
if seal.user.username == "" {
|
||||
seal.user.username = "chronos"
|
||||
} else if !isValidUsername(seal.user.username) {
|
||||
return hlog.WrapErr(ErrName,
|
||||
fmt.Sprintf("invalid user name %q", seal.user.username))
|
||||
return newWithMessage(fmt.Sprintf("invalid user name %q", seal.user.username))
|
||||
}
|
||||
if u, err := sys.Uid(seal.user.identity.unwrap()); err != nil {
|
||||
return err
|
||||
@ -196,8 +216,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
seal.user.supp = make([]string, len(config.Groups))
|
||||
for i, name := range config.Groups {
|
||||
if g, err := sys.LookupGroup(name); err != nil {
|
||||
return hlog.WrapErr(err,
|
||||
fmt.Sprintf("unknown group %q", name))
|
||||
return newWithMessageError(fmt.Sprintf("unknown group %q", name), err)
|
||||
} else {
|
||||
seal.user.supp[i] = g.Gid
|
||||
}
|
||||
@ -219,9 +238,9 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
if config.Path == nil {
|
||||
if len(config.Args) > 0 {
|
||||
if p, err := sys.LookPath(config.Args[0]); err != nil {
|
||||
return hlog.WrapErr(err, err.Error())
|
||||
return &FinaliseError{Step: "look up executable file", Err: err}
|
||||
} else if config.Path, err = container.NewAbs(p); err != nil {
|
||||
return hlog.WrapErr(err, err.Error())
|
||||
return newWithMessageError(err.Error(), err)
|
||||
}
|
||||
} else {
|
||||
config.Path = config.Shell
|
||||
@ -272,10 +291,10 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
|
||||
// late nil checks for pd behaviour
|
||||
if config.Shell == nil {
|
||||
return hlog.WrapErr(syscall.EINVAL, "invalid shell path")
|
||||
return newWithMessage("invalid shell path")
|
||||
}
|
||||
if config.Path == nil {
|
||||
return hlog.WrapErr(syscall.EINVAL, "invalid program path")
|
||||
return newWithMessage("invalid program path")
|
||||
}
|
||||
|
||||
var mapuid, mapgid *stringPair[int]
|
||||
@ -285,8 +304,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
seal.container, seal.env, err = newContainer(config.Container, sys, seal.id.String(), &uid, &gid)
|
||||
seal.waitDelay = config.Container.WaitDelay
|
||||
if err != nil {
|
||||
return hlog.WrapErrSuffix(err,
|
||||
"cannot initialise container configuration:")
|
||||
return &FinaliseError{Step: "initialise container configuration", Err: err}
|
||||
}
|
||||
if len(config.Args) == 0 {
|
||||
config.Args = []string{config.Path.String()}
|
||||
@ -390,8 +408,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
|
||||
if config.Enablements.Unwrap()&system.EX11 != 0 {
|
||||
if d, ok := sys.LookupEnv(display); !ok {
|
||||
return hlog.WrapErr(ErrXDisplay,
|
||||
"DISPLAY is not set")
|
||||
return newWithMessage("DISPLAY is not set")
|
||||
} else {
|
||||
socketDir := container.AbsFHSTmp.Append(".X11-unix")
|
||||
|
||||
@ -410,8 +427,7 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
if socketPath != nil {
|
||||
if _, err := sys.Stat(socketPath.String()); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return hlog.WrapErrSuffix(err,
|
||||
fmt.Sprintf("cannot access X11 socket %q:", socketPath))
|
||||
return &FinaliseError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
|
||||
}
|
||||
} else {
|
||||
seal.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
|
||||
@ -435,24 +451,19 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
|
||||
if _, err := sys.Stat(pulseRuntimeDir.String()); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return hlog.WrapErrSuffix(err,
|
||||
fmt.Sprintf("cannot access PulseAudio directory %q:", pulseRuntimeDir))
|
||||
return &FinaliseError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
|
||||
}
|
||||
return hlog.WrapErr(ErrPulseSocket,
|
||||
fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
|
||||
return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
|
||||
}
|
||||
|
||||
if s, err := sys.Stat(pulseSocket.String()); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return hlog.WrapErrSuffix(err,
|
||||
fmt.Sprintf("cannot access PulseAudio socket %q:", pulseSocket))
|
||||
return &FinaliseError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
|
||||
}
|
||||
return hlog.WrapErr(ErrPulseSocket,
|
||||
fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir))
|
||||
return newWithMessage(fmt.Sprintf("PulseAudio directory %q found but socket does not exist", pulseRuntimeDir))
|
||||
} else {
|
||||
if m := s.Mode(); m&0o006 != 0o006 {
|
||||
return hlog.WrapErr(ErrPulseMode,
|
||||
fmt.Sprintf("unexpected permissions on %q:", pulseSocket), m)
|
||||
return newWithMessage(fmt.Sprintf("unexpected permissions on %q: %s", pulseSocket, m))
|
||||
}
|
||||
}
|
||||
|
||||
@ -464,15 +475,75 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
seal.env[pulseServer] = "unix:" + innerPulseSocket.String()
|
||||
|
||||
// publish current user's pulse cookie for target user
|
||||
if src, err := discoverPulseCookie(sys); err != nil {
|
||||
// not fatal
|
||||
hlog.Verbose(strings.TrimSpace(err.(*hlog.BaseError).Message()))
|
||||
var paCookiePath *container.Absolute
|
||||
{
|
||||
const paLocateStep = "locate PulseAudio cookie"
|
||||
|
||||
// from environment
|
||||
if p, ok := sys.LookupEnv(pulseCookie); ok {
|
||||
if a, err := container.NewAbs(p); err != nil {
|
||||
return &FinaliseError{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 := sys.LookupEnv(home); ok {
|
||||
if a, err := container.NewAbs(p); err != nil {
|
||||
return &FinaliseError{Step: paLocateStep, Err: err}
|
||||
} else {
|
||||
paCookiePath = a.Append(".pulse-cookie")
|
||||
}
|
||||
|
||||
if s, err := sys.Stat(paCookiePath.String()); err != nil {
|
||||
paCookiePath = nil
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return &FinaliseError{Step: "access PulseAudio cookie", Err: err}
|
||||
}
|
||||
// fallthrough
|
||||
} else if s.IsDir() {
|
||||
paCookiePath = nil
|
||||
} else {
|
||||
goto out
|
||||
}
|
||||
}
|
||||
|
||||
// $XDG_CONFIG_HOME/pulse/cookie
|
||||
if p, ok := sys.LookupEnv(xdgConfigHome); ok {
|
||||
if a, err := container.NewAbs(p); err != nil {
|
||||
return &FinaliseError{Step: paLocateStep, Err: err}
|
||||
} else {
|
||||
paCookiePath = a.Append("pulse", "cookie")
|
||||
}
|
||||
if s, err := sys.Stat(paCookiePath.String()); err != nil {
|
||||
paCookiePath = nil
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return &FinaliseError{Step: "access PulseAudio cookie", Err: err}
|
||||
}
|
||||
// fallthrough
|
||||
} else if s.IsDir() {
|
||||
paCookiePath = nil
|
||||
} else {
|
||||
goto out
|
||||
}
|
||||
}
|
||||
out:
|
||||
}
|
||||
|
||||
if paCookiePath != nil {
|
||||
innerDst := hst.AbsTmp.Append("/pulse-cookie")
|
||||
seal.env[pulseCookie] = innerDst.String()
|
||||
var payload *[]byte
|
||||
seal.container.PlaceP(innerDst, &payload)
|
||||
seal.sys.CopyFile(payload, src, 256, 256)
|
||||
seal.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
|
||||
} else {
|
||||
hlog.Verbose("cannot locate PulseAudio cookie (tried " +
|
||||
"$PULSE_COOKIE, " +
|
||||
"$XDG_CONFIG_HOME/pulse/cookie, " +
|
||||
"$HOME/.pulse-cookie)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -538,8 +609,8 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
seal.container.Env = make([]string, 0, len(seal.env))
|
||||
for k, v := range seal.env {
|
||||
if strings.IndexByte(k, '=') != -1 {
|
||||
return hlog.WrapErr(syscall.EINVAL,
|
||||
fmt.Sprintf("invalid environment variable %s", k))
|
||||
return &FinaliseError{Step: "flatten environment", Err: syscall.EINVAL,
|
||||
Msg: fmt.Sprintf("invalid environment variable %s", k)}
|
||||
}
|
||||
seal.container.Env = append(seal.container.Env, k+"="+v)
|
||||
}
|
||||
@ -552,42 +623,3 @@ func (seal *Outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// discoverPulseCookie attempts various standard methods to discover the current user's PulseAudio authentication cookie
|
||||
func discoverPulseCookie(sys sys.State) (string, error) {
|
||||
if p, ok := sys.LookupEnv(pulseCookie); ok {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// dotfile $HOME/.pulse-cookie
|
||||
if p, ok := sys.LookupEnv(home); ok {
|
||||
p = path.Join(p, ".pulse-cookie")
|
||||
if s, err := sys.Stat(p); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return p, hlog.WrapErrSuffix(err,
|
||||
fmt.Sprintf("cannot access PulseAudio cookie %q:", p))
|
||||
}
|
||||
// not found, try next method
|
||||
} else if !s.IsDir() {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
// $XDG_CONFIG_HOME/pulse/cookie
|
||||
if p, ok := sys.LookupEnv(xdgConfigHome); ok {
|
||||
p = path.Join(p, "pulse", "cookie")
|
||||
if s, err := sys.Stat(p); err != nil {
|
||||
if !errors.Is(err, fs.ErrNotExist) {
|
||||
return p, hlog.WrapErrSuffix(err,
|
||||
fmt.Sprintf("cannot access PulseAudio cookie %q:", p))
|
||||
}
|
||||
// not found, try next method
|
||||
} else if !s.IsDir() {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", hlog.WrapErr(ErrPulseCookie,
|
||||
fmt.Sprintf("cannot locate PulseAudio cookie (tried $%s, $%s/pulse/cookie, $%s/.pulse-cookie)",
|
||||
pulseCookie, xdgConfigHome, home))
|
||||
}
|
||||
|
@ -155,11 +155,11 @@ func ShimMain() {
|
||||
}
|
||||
|
||||
if err := z.Start(); err != nil {
|
||||
hlog.PrintBaseError(err, "cannot start container:")
|
||||
printMessageError("cannot start container:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := z.Serve(); err != nil {
|
||||
hlog.PrintBaseError(err, "cannot configure container:")
|
||||
printMessageError("cannot configure container:", err)
|
||||
}
|
||||
|
||||
if err := seccomp.Load(
|
||||
|
@ -1,81 +0,0 @@
|
||||
package hlog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// baseError implements a basic error container
|
||||
type baseError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *baseError) Error() string { return e.Err.Error() }
|
||||
func (e *baseError) Unwrap() error { return e.Err }
|
||||
|
||||
// BaseError implements an error container with a user-facing message
|
||||
type BaseError struct {
|
||||
message string
|
||||
baseError
|
||||
}
|
||||
|
||||
// Message returns a user-facing error message
|
||||
func (e *BaseError) Message() string { return e.message }
|
||||
|
||||
// WrapErr wraps an error with a corresponding message.
|
||||
func WrapErr(err error, a ...any) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return wrapErr(err, fmt.Sprintln(a...))
|
||||
}
|
||||
|
||||
// WrapErrSuffix wraps an error with a corresponding message with err at the end of the message.
|
||||
func WrapErrSuffix(err error, a ...any) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return wrapErr(err, fmt.Sprintln(append(a, err)...))
|
||||
}
|
||||
|
||||
// WrapErrFunc wraps an error with a corresponding message returned by f.
|
||||
func WrapErrFunc(err error, f func(err error) string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return wrapErr(err, f(err))
|
||||
}
|
||||
|
||||
func wrapErr(err error, message string) *BaseError {
|
||||
return &BaseError{message, baseError{err}}
|
||||
}
|
||||
|
||||
var (
|
||||
baseErrorType = reflect.TypeFor[*BaseError]()
|
||||
)
|
||||
|
||||
func AsBaseError(err error, target **BaseError) bool {
|
||||
v := reflect.ValueOf(err)
|
||||
if !v.CanConvert(baseErrorType) {
|
||||
return false
|
||||
}
|
||||
|
||||
*target = v.Convert(baseErrorType).Interface().(*BaseError)
|
||||
return true
|
||||
}
|
||||
|
||||
func PrintBaseError(err error, fallback string) {
|
||||
var e *BaseError
|
||||
|
||||
if AsBaseError(err, &e) {
|
||||
if msg := e.Message(); strings.TrimSpace(msg) != "" {
|
||||
log.Print(msg)
|
||||
return
|
||||
}
|
||||
Verbose("*"+fallback, err)
|
||||
return
|
||||
}
|
||||
log.Println(fallback, err)
|
||||
}
|
@ -5,8 +5,6 @@ type Output struct{}
|
||||
func (Output) IsVerbose() bool { return Load() }
|
||||
func (Output) Verbose(v ...any) { Verbose(v...) }
|
||||
func (Output) Verbosef(format string, v ...any) { Verbosef(format, v...) }
|
||||
func (Output) WrapErr(err error, a ...any) error { return WrapErr(err, a...) }
|
||||
func (Output) PrintBaseErr(err error, fallback string) { PrintBaseError(err, fallback) }
|
||||
func (Output) Suspend() { Suspend() }
|
||||
func (Output) Resume() bool { return Resume() }
|
||||
func (Output) BeforeExit() { BeforeExit() }
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
@ -51,7 +52,14 @@ const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||
func (s *Std) Paths() hst.Paths {
|
||||
s.pathsOnce.Do(func() {
|
||||
if userid, err := GetUserID(s); err != nil {
|
||||
hlog.PrintBaseError(err, "cannot obtain user id from hsu:")
|
||||
// TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal
|
||||
if m, ok := container.GetErrorMessage(err); ok {
|
||||
if m != "\x00" {
|
||||
log.Print(m)
|
||||
}
|
||||
} else {
|
||||
log.Println("cannot obtain user id from hsu:", err)
|
||||
}
|
||||
hlog.BeforeExit()
|
||||
s.Exit(1)
|
||||
} else {
|
||||
@ -61,6 +69,16 @@ func (s *Std) Paths() hst.Paths {
|
||||
return s.paths
|
||||
}
|
||||
|
||||
// this is a temporary placeholder until this package is removed
|
||||
type wrappedError struct {
|
||||
Err error
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *wrappedError) Error() string { return e.Err.Error() }
|
||||
func (e *wrappedError) Unwrap() error { return e.Err }
|
||||
func (e *wrappedError) Message() string { return e.Msg }
|
||||
|
||||
func (s *Std) Uid(identity int) (int, error) {
|
||||
s.uidOnce.Do(func() {
|
||||
s.uidCopy = make(map[int]struct {
|
||||
@ -103,12 +121,13 @@ func (s *Std) Uid(identity int) (int, error) {
|
||||
if p, u.err = cmd.Output(); u.err == nil {
|
||||
u.uid, u.err = strconv.Atoi(string(p))
|
||||
if u.err != nil {
|
||||
u.err = hlog.WrapErr(u.err, "invalid uid string from hsu")
|
||||
u.err = &wrappedError{u.err, "invalid uid string from hsu"}
|
||||
}
|
||||
} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
||||
u.err = hlog.WrapErr(syscall.EACCES, "") // hsu prints to stderr in this case
|
||||
// hsu prints an error message in this case
|
||||
u.err = &wrappedError{syscall.EACCES, "\x00"} // this drops the message, handled in cmd/hakurei/command.go
|
||||
} else if os.IsNotExist(u.err) {
|
||||
u.err = hlog.WrapErr(os.ErrNotExist, fmt.Sprintf("the setuid helper is missing: %s", hsuPath))
|
||||
u.err = &wrappedError{os.ErrNotExist, fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
|
||||
}
|
||||
return u.uid, u.err
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user