container: remove global msg
All checks were successful
Test / Create distribution (push) Successful in 1m10s
Test / Sandbox (push) Successful in 2m40s
Test / Hakurei (push) Successful in 3m58s
Test / Hpkg (push) Successful in 4m44s
Test / Sandbox (race detector) (push) Successful in 5m1s
Test / Hakurei (race detector) (push) Successful in 6m2s
Test / Flake checks (push) Successful in 1m47s

This frees all container instances of side effects.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2025-09-29 02:33:10 +09:00
parent ad1bc6794f
commit 46cd3a28c8
69 changed files with 987 additions and 838 deletions

View File

@@ -31,22 +31,22 @@ type aclUpdateOp struct {
func (a *aclUpdateOp) Type() Enablement { return a.et }
func (a *aclUpdateOp) apply(sys *I) error {
sys.verbose("applying ACL", a)
sys.msg.Verbose("applying ACL", a)
return newOpError("acl", sys.aclUpdate(a.path, sys.uid, a.perms...), false)
}
func (a *aclUpdateOp) revert(sys *I, ec *Criteria) error {
if ec.hasType(a.Type()) {
sys.verbose("stripping ACL", a)
sys.msg.Verbose("stripping ACL", a)
err := sys.aclUpdate(a.path, sys.uid)
if errors.Is(err, os.ErrNotExist) {
// the ACL is effectively stripped if the file no longer exists
sys.verbosef("target of ACL %s no longer exists", a)
sys.msg.Verbosef("target of ACL %s no longer exists", a)
err = nil
}
return newOpError("acl", err, true)
} else {
sys.verbose("skipping ACL", a)
sys.msg.Verbose("skipping ACL", a)
return nil
}
}

View File

@@ -54,14 +54,14 @@ func (sys *I) ProxyDBus(session, system *dbus.Config, sessionPath, systemPath st
return nil, newOpErrorMessage("dbus", err,
fmt.Sprintf("cannot finalise message bus proxy: %v", err), false)
} else {
if sys.isVerbose() {
sys.verbose("session bus proxy:", session.Args(sessionBus))
if sys.msg.IsVerbose() {
sys.msg.Verbose("session bus proxy:", session.Args(sessionBus))
if system != nil {
sys.verbose("system bus proxy:", system.Args(systemBus))
sys.msg.Verbose("system bus proxy:", system.Args(systemBus))
}
// this calls the argsWt String method
sys.verbose("message bus proxy final args:", final.WriterTo)
sys.msg.Verbose("message bus proxy final args:", final.WriterTo)
}
d.final = final
@@ -84,28 +84,28 @@ type dbusProxyOp struct {
func (d *dbusProxyOp) Type() Enablement { return Process }
func (d *dbusProxyOp) apply(sys *I) error {
sys.verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0])
sys.msg.Verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0])
if d.system {
sys.verbosef("system bus proxy on %q for upstream %q", d.final.System[1], d.final.System[0])
sys.msg.Verbosef("system bus proxy on %q for upstream %q", d.final.System[1], d.final.System[0])
}
d.proxy = dbus.New(sys.ctx, d.final, d.out)
d.proxy = dbus.New(sys.ctx, sys.msg, d.final, d.out)
if err := sys.dbusProxyStart(d.proxy); err != nil {
d.out.Dump()
return newOpErrorMessage("dbus", err,
fmt.Sprintf("cannot start message bus proxy: %v", err), false)
}
sys.verbose("starting message bus proxy", d.proxy)
sys.msg.Verbose("starting message bus proxy", d.proxy)
return nil
}
func (d *dbusProxyOp) revert(sys *I, _ *Criteria) error {
// criteria ignored here since dbus is always process-scoped
sys.verbose("terminating message bus proxy")
sys.msg.Verbose("terminating message bus proxy")
sys.dbusProxyClose(d.proxy)
exitMessage := "message bus proxy exit"
defer func() { sys.verbose(exitMessage) }()
defer func() { sys.msg.Verbose(exitMessage) }()
err := sys.dbusProxyWait(d.proxy)
if errors.Is(err, context.Canceled) {

View File

@@ -11,6 +11,7 @@ import (
"testing"
"time"
"hakurei.app/container"
"hakurei.app/helper"
"hakurei.app/system/dbus"
)
@@ -92,9 +93,9 @@ func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
t.Run("invalid start", func(t *testing.T) {
if !useSandbox {
p = dbus.NewDirect(t.Context(), nil, nil)
p = dbus.NewDirect(t.Context(), container.NewMsg(nil), nil, nil)
} else {
p = dbus.New(t.Context(), nil, nil)
p = dbus.New(t.Context(), container.NewMsg(nil), nil, nil)
}
if err := p.Start(); !errors.Is(err, syscall.ENOTRECOVERABLE) {
@@ -127,9 +128,9 @@ func testProxyFinaliseStartWaitCloseString(t *testing.T, useSandbox bool) {
defer cancel()
output := new(strings.Builder)
if !useSandbox {
p = dbus.NewDirect(ctx, final, output)
p = dbus.NewDirect(ctx, container.NewMsg(nil), final, output)
} else {
p = dbus.New(ctx, final, output)
p = dbus.New(ctx, container.NewMsg(nil), final, output)
}
t.Run("invalid wait", func(t *testing.T) {

View File

@@ -3,11 +3,13 @@ package dbus
import (
"context"
"io"
"hakurei.app/container"
)
// NewDirect returns a new instance of [Proxy] with its sandbox disabled.
func NewDirect(ctx context.Context, final *Final, output io.Writer) *Proxy {
p := New(ctx, final, output)
func NewDirect(ctx context.Context, msg container.Msg, final *Final, output io.Writer) *Proxy {
p := New(ctx, msg, final, output)
p.useSandbox = false
return p
}

View File

@@ -52,14 +52,14 @@ func (p *Proxy) Start() error {
}
var libPaths []*container.Absolute
if entries, err := ldd.Exec(ctx, toolPath.String()); err != nil {
if entries, err := ldd.Exec(ctx, p.msg, toolPath.String()); err != nil {
return err
} else {
libPaths = ldd.Path(entries)
}
p.helper = helper.New(
ctx, toolPath, "xdg-dbus-proxy",
ctx, p.msg, toolPath, "xdg-dbus-proxy",
p.final, true,
argF, func(z *container.Container) {
z.SeccompFlags |= seccomp.AllowMultiarch

View File

@@ -6,12 +6,10 @@ import (
"hakurei.app/container"
"hakurei.app/helper"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
)
func TestMain(m *testing.M) {
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)
container.TryArgv0(nil)
helper.InternalHelperStub()
os.Exit(m.Run())
}

View File

@@ -7,6 +7,7 @@ import (
"sync"
"syscall"
"hakurei.app/container"
"hakurei.app/helper"
)
@@ -27,6 +28,7 @@ func (e *BadInterfaceError) Error() string {
type Proxy struct {
helper helper.Helper
ctx context.Context
msg container.Msg
cancel context.CancelCauseFunc
cause func() error
@@ -107,6 +109,6 @@ func Finalise(sessionBus, systemBus ProxyPair, session, system *Config) (final *
}
// New returns a new instance of [Proxy].
func New(ctx context.Context, final *Final, output io.Writer) *Proxy {
return &Proxy{name: ProxyName, ctx: ctx, final: final, output: output, useSandbox: true}
func New(ctx context.Context, msg container.Msg, final *Final, output io.Writer) *Proxy {
return &Proxy{name: ProxyName, ctx: ctx, msg: msg, final: final, output: output, useSandbox: true}
}

View File

@@ -57,10 +57,6 @@ type syscallDispatcher interface {
dbusProxyClose(proxy *dbus.Proxy)
// dbusProxyWait provides the Wait method of [dbus.Proxy].
dbusProxyWait(proxy *dbus.Proxy) error
isVerbose() bool
verbose(v ...any)
verbosef(format string, v ...any)
}
// direct implements syscallDispatcher on the current kernel.
@@ -96,7 +92,3 @@ func (k direct) dbusFinalise(sessionBus, systemBus dbus.ProxyPair, session, syst
func (k direct) dbusProxyStart(proxy *dbus.Proxy) error { return proxy.Start() }
func (k direct) dbusProxyClose(proxy *dbus.Proxy) { proxy.Close() }
func (k direct) dbusProxyWait(proxy *dbus.Proxy) error { return proxy.Wait() }
func (k direct) isVerbose() bool { return msg.IsVerbose() }
func (direct) verbose(v ...any) { msg.Verbose(v...) }
func (direct) verbosef(format string, v ...any) { msg.Verbosef(format, v...) }

View File

@@ -3,6 +3,7 @@ package system
import (
"io"
"io/fs"
"log"
"os"
"reflect"
"slices"
@@ -214,10 +215,10 @@ func (r *readerOsFile) Close() error {
// InternalNew initialises [I] with a stub syscallDispatcher.
func InternalNew(t *testing.T, want stub.Expect, uid int) (*I, *stub.Stub[syscallDispatcher]) {
k := stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{s} }, want)
sys := New(t.Context(), uid)
sys.syscallDispatcher = &kstub{k}
return sys, k
k := &kstub{stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{s} }, want)}
sys := New(t.Context(), k, uid)
sys.syscallDispatcher = k
return sys, k.Stub
}
type kstub struct{ *stub.Stub[syscallDispatcher] }
@@ -357,12 +358,23 @@ func (k *kstub) dbusProxySCW(expect *stub.Call, proxy *dbus.Proxy) error {
return expect.Err
}
func (k *kstub) isVerbose() bool { k.Helper(); return k.Expects("isVerbose").Ret.(bool) }
func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }
func (k *kstub) IsVerbose() bool { k.Helper(); return k.Expects("isVerbose").Ret.(bool) }
func (k *kstub) SwapVerbose(verbose bool) bool {
k.Helper()
expect := k.Expects("swapVerbose")
if expect.Error(
stub.CheckArg(k.Stub, "verbose", verbose, 0)) != nil {
k.FailNow()
}
return expect.Ret.(bool)
}
// ignoreValue marks a value to be ignored by the test suite.
type ignoreValue struct{}
func (k *kstub) verbose(v ...any) {
func (k *kstub) Verbose(v ...any) {
k.Helper()
expect := k.Expects("verbose")
@@ -381,7 +393,7 @@ func (k *kstub) verbose(v ...any) {
}
}
func (k *kstub) verbosef(format string, v ...any) {
func (k *kstub) Verbosef(format string, v ...any) {
k.Helper()
if k.Expects("verbosef").Error(
stub.CheckArg(k.Stub, "format", format, 0),
@@ -389,3 +401,7 @@ func (k *kstub) verbosef(format string, v ...any) {
k.FailNow()
}
}
func (k *kstub) Suspend() bool { k.Helper(); return k.Expects("suspend").Ret.(bool) }
func (k *kstub) Resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
func (k *kstub) BeforeExit() { k.Helper(); k.Expects("beforeExit") }

View File

@@ -22,16 +22,16 @@ type hardlinkOp struct {
func (l *hardlinkOp) Type() Enablement { return l.et }
func (l *hardlinkOp) apply(sys *I) error {
sys.verbose("linking", l)
sys.msg.Verbose("linking", l)
return newOpError("hardlink", sys.link(l.src, l.dst), false)
}
func (l *hardlinkOp) revert(sys *I, ec *Criteria) error {
if ec.hasType(l.Type()) {
sys.verbosef("removing hard link %q", l.dst)
sys.msg.Verbosef("removing hard link %q", l.dst)
return newOpError("hardlink", sys.remove(l.dst), true)
} else {
sys.verbosef("skipping hard link %q", l.dst)
sys.msg.Verbosef("skipping hard link %q", l.dst)
return nil
}
}

View File

@@ -29,7 +29,7 @@ type mkdirOp struct {
func (m *mkdirOp) Type() Enablement { return m.et }
func (m *mkdirOp) apply(sys *I) error {
sys.verbose("ensuring directory", m)
sys.msg.Verbose("ensuring directory", m)
if err := sys.mkdir(m.path, m.perm); err != nil {
if !errors.Is(err, os.ErrExist) {
@@ -49,10 +49,10 @@ func (m *mkdirOp) revert(sys *I, ec *Criteria) error {
}
if ec.hasType(m.Type()) {
sys.verbose("destroying ephemeral directory", m)
sys.msg.Verbose("destroying ephemeral directory", m)
return newOpError("mkdir", sys.remove(m.path), true)
} else {
sys.verbose("skipping ephemeral directory", m)
sys.msg.Verbose("skipping ephemeral directory", m)
return nil
}
}

View File

@@ -8,16 +8,6 @@ import (
"hakurei.app/container"
)
var msg container.Msg = new(container.DefaultMsg)
func SetOutput(v container.Msg) {
if v == nil {
msg = new(container.DefaultMsg)
} else {
msg = v
}
}
// OpError is returned by [I.Commit] and [I.Revert].
type OpError struct {
Op string

View File

@@ -9,7 +9,6 @@ import (
"testing"
"hakurei.app/container"
"hakurei.app/internal/hlog"
)
func TestOpError(t *testing.T) {
@@ -87,33 +86,6 @@ func TestOpError(t *testing.T) {
})
}
func TestSetOutput(t *testing.T) {
oldmsg := msg
t.Cleanup(func() { msg = oldmsg })
msg = nil
t.Run("nil", func(t *testing.T) {
SetOutput(nil)
if _, ok := msg.(*container.DefaultMsg); !ok {
t.Errorf("SetOutput: %#v", msg)
}
})
t.Run("hlog", func(t *testing.T) {
SetOutput(hlog.Output{})
if _, ok := msg.(hlog.Output); !ok {
t.Errorf("SetOutput: %#v", msg)
}
})
t.Run("reset", func(t *testing.T) {
SetOutput(nil)
if _, ok := msg.(*container.DefaultMsg); !ok {
t.Errorf("SetOutput: %#v", msg)
}
})
}
func TestPrintJoinedError(t *testing.T) {
testCases := []struct {
name string

View File

@@ -5,6 +5,8 @@ import (
"context"
"errors"
"strings"
"hakurei.app/container"
)
const (
@@ -65,11 +67,11 @@ func TypeString(e Enablement) string {
}
// New returns the address of a new [I] targeting uid.
func New(ctx context.Context, uid int) (sys *I) {
if ctx == nil || uid < 0 {
func New(ctx context.Context, msg container.Msg, uid int) (sys *I) {
if ctx == nil || msg == nil || uid < 0 {
panic("invalid call to New")
}
return &I{ctx: ctx, uid: uid, syscallDispatcher: direct{}}
return &I{ctx: ctx, msg: msg, uid: uid, syscallDispatcher: direct{}}
}
// An I provides deferred operating system interaction. [I] must not be copied.
@@ -86,6 +88,7 @@ type I struct {
// the behaviour of Revert is only defined for up to one call
reverted bool
msg container.Msg
syscallDispatcher
}
@@ -114,14 +117,14 @@ func (sys *I) Commit() error {
}
sys.committed = true
sp := New(sys.ctx, sys.uid)
sp := New(sys.ctx, sys.msg, sys.uid)
sp.syscallDispatcher = sys.syscallDispatcher
sp.ops = make([]Op, 0, len(sys.ops)) // prevent copies during commits
defer func() {
// sp is set to nil when all ops are applied
if sp != nil {
// rollback partial commit
sys.verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
sys.msg.Verbosef("commit faulted after %d ops, rolling back partial commit", len(sp.ops))
if err := sp.Revert(nil); err != nil {
printJoinedError(sys.println, "cannot revert partial commit:", err)
}

View File

@@ -8,6 +8,7 @@ import (
"strconv"
"testing"
"hakurei.app/container"
"hakurei.app/container/stub"
"hakurei.app/system/internal/xcb"
)
@@ -71,7 +72,17 @@ func TestNew(t *testing.T) {
t.Errorf("recover: %v, want %v", r, want)
}
}()
New(nil, 0)
New(nil, container.NewMsg(nil), 0)
})
t.Run("msg", func(t *testing.T) {
defer func() {
want := "invalid call to New"
if r := recover(); r != want {
t.Errorf("recover: %v, want %v", r, want)
}
}()
New(t.Context(), nil, 0)
})
t.Run("uid", func(t *testing.T) {
@@ -81,11 +92,11 @@ func TestNew(t *testing.T) {
t.Errorf("recover: %v, want %v", r, want)
}
}()
New(t.Context(), -1)
New(t.Context(), container.NewMsg(nil), -1)
})
})
sys := New(t.Context(), 0xdeadbeef)
sys := New(t.Context(), container.NewMsg(nil), 0xdeadbeef)
if sys.ctx == nil {
t.Error("New: ctx = nil")
}
@@ -102,51 +113,51 @@ func TestEqual(t *testing.T) {
want bool
}{
{"simple UID",
New(t.Context(), 150),
New(t.Context(), 150),
New(t.Context(), container.NewMsg(nil), 150),
New(t.Context(), container.NewMsg(nil), 150),
true},
{"simple UID differ",
New(t.Context(), 150),
New(t.Context(), 151),
New(t.Context(), container.NewMsg(nil), 150),
New(t.Context(), container.NewMsg(nil), 151),
false},
{"simple UID nil",
New(t.Context(), 150),
New(t.Context(), container.NewMsg(nil), 150),
nil,
false},
{"op length mismatch",
New(t.Context(), 150).
New(t.Context(), container.NewMsg(nil), 150).
ChangeHosts("chronos"),
New(t.Context(), 150).
New(t.Context(), container.NewMsg(nil), 150).
ChangeHosts("chronos").
Ensure("/run", 0755),
false},
{"op value mismatch",
New(t.Context(), 150).
New(t.Context(), container.NewMsg(nil), 150).
ChangeHosts("chronos").
Ensure("/run", 0644),
New(t.Context(), 150).
New(t.Context(), container.NewMsg(nil), 150).
ChangeHosts("chronos").
Ensure("/run", 0755),
false},
{"op type mismatch",
New(t.Context(), 150).
New(t.Context(), container.NewMsg(nil), 150).
ChangeHosts("chronos").
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 0, 256),
New(t.Context(), 150).
New(t.Context(), container.NewMsg(nil), 150).
ChangeHosts("chronos").
Ensure("/run", 0755),
false},
{"op equals",
New(t.Context(), 150).
New(t.Context(), container.NewMsg(nil), 150).
ChangeHosts("chronos").
Ensure("/run", 0755),
New(t.Context(), 150).
New(t.Context(), container.NewMsg(nil), 150).
ChangeHosts("chronos").
Ensure("/run", 0755),
true},

View File

@@ -34,7 +34,7 @@ func (t *tmpfileOp) apply(sys *I) error {
return errors.New("invalid payload")
}
sys.verbose("copying", t)
sys.msg.Verbose("copying", t)
if b, err := sys.stat(t.src); err != nil {
return newOpError("tmpfile", err, false)
@@ -58,7 +58,7 @@ func (t *tmpfileOp) apply(sys *I) error {
_ = r.Close()
return newOpError("tmpfile", err, false)
}
sys.verbosef("copied %d bytes from %q", n, t.src)
sys.msg.Verbosef("copied %d bytes from %q", n, t.src)
}
if err := r.Close(); err != nil {
return newOpError("tmpfile", err, false)

View File

@@ -43,14 +43,14 @@ func (w *waylandOp) apply(sys *I) error {
if err := w.conn.Attach(w.src); err != nil {
return newOpError("wayland", err, false)
} else {
sys.verbosef("wayland attached on %q", w.src)
sys.msg.Verbosef("wayland attached on %q", w.src)
}
if sp, err := w.conn.Bind(w.dst, w.appID, w.instanceID); err != nil {
return newOpError("wayland", err, false)
} else {
*w.sync = sp
sys.verbosef("wayland listening on %q", w.dst)
sys.msg.Verbosef("wayland listening on %q", w.dst)
if err = sys.chmod(w.dst, 0); err != nil {
return newOpError("wayland", err, false)
}
@@ -59,12 +59,12 @@ func (w *waylandOp) apply(sys *I) error {
}
func (w *waylandOp) revert(sys *I, _ *Criteria) error {
sys.verbosef("removing wayland socket on %q", w.dst)
sys.msg.Verbosef("removing wayland socket on %q", w.dst)
if err := sys.remove(w.dst); err != nil && !errors.Is(err, os.ErrNotExist) {
return newOpError("wayland", err, true)
}
sys.verbosef("detaching from wayland on %q", w.src)
sys.msg.Verbosef("detaching from wayland on %q", w.src)
return newOpError("wayland", w.conn.Close(), true)
}

View File

@@ -16,18 +16,18 @@ type xhostOp string
func (x xhostOp) Type() Enablement { return EX11 }
func (x xhostOp) apply(sys *I) error {
sys.verbosef("inserting entry %s to X11", x)
sys.msg.Verbosef("inserting entry %s to X11", x)
return newOpError("xhost",
sys.xcbChangeHosts(xcb.HostModeInsert, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)), false)
}
func (x xhostOp) revert(sys *I, ec *Criteria) error {
if ec.hasType(x.Type()) {
sys.verbosef("deleting entry %s from X11", x)
sys.msg.Verbosef("deleting entry %s from X11", x)
return newOpError("xhost",
sys.xcbChangeHosts(xcb.HostModeDelete, xcb.FamilyServerInterpreted, "localuser\x00"+string(x)), true)
} else {
sys.verbosef("skipping entry %s in X11", x)
sys.msg.Verbosef("skipping entry %s in X11", x)
return nil
}
}