message: relocate from container
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m22s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m28s
Test / Hakurei (race detector) (push) Successful in 5m21s
Test / Hakurei (push) Successful in 2m9s
Test / Flake checks (push) Successful in 1m29s

This package is quite useful. This change allows it to be imported without importing container.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-10-09 05:04:08 +09:00
parent df9b77b077
commit 87b5c30ef6
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
47 changed files with 210 additions and 185 deletions

View File

@ -15,17 +15,17 @@ import (
"time" "time"
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/internal/app" "hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/message"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
) )
func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningErrs, out io.Writer) command.Command { func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
var ( var (
flagVerbose bool flagVerbose bool
flagJSON bool flagJSON bool

View File

@ -7,7 +7,7 @@ import (
"testing" "testing"
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container" "hakurei.app/message"
) )
func TestHelp(t *testing.T) { func TestHelp(t *testing.T) {
@ -69,7 +69,7 @@ Flags:
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
out := new(bytes.Buffer) out := new(bytes.Buffer)
c := buildCommand(t.Context(), container.NewMsg(nil), new(earlyHardeningErrs), out) c := buildCommand(t.Context(), message.NewMsg(nil), new(earlyHardeningErrs), out)
if err := c.Parse(tc.args); !errors.Is(err, command.ErrHelp) && !errors.Is(err, flag.ErrHelp) { if err := c.Parse(tc.args); !errors.Is(err, command.ErrHelp) && !errors.Is(err, flag.ErrHelp) {
t.Errorf("Parse: error = %v; want %v", t.Errorf("Parse: error = %v; want %v",
err, command.ErrHelp) err, command.ErrHelp)

View File

@ -13,6 +13,7 @@ import (
"syscall" "syscall"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/message"
) )
var ( var (
@ -31,7 +32,7 @@ func main() {
log.SetPrefix("hakurei: ") log.SetPrefix("hakurei: ")
log.SetFlags(0) log.SetFlags(0)
msg := container.NewMsg(log.Default()) msg := message.NewMsg(log.Default())
early := earlyHardeningErrs{ early := earlyHardeningErrs{
yamaLSM: container.SetPtracer(0), yamaLSM: container.SetPtracer(0),

View File

@ -10,13 +10,13 @@ import (
"strings" "strings"
"syscall" "syscall"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app" "hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/message"
) )
func tryPath(msg container.Msg, name string) (config *hst.Config) { func tryPath(msg message.Msg, name string) (config *hst.Config) {
var r io.Reader var r io.Reader
config = new(hst.Config) config = new(hst.Config)
@ -49,7 +49,7 @@ func tryPath(msg container.Msg, name string) (config *hst.Config) {
return return
} }
func tryFd(msg container.Msg, name string) io.ReadCloser { func tryFd(msg message.Msg, name string) io.ReadCloser {
if v, err := strconv.Atoi(name); err != nil { if v, err := strconv.Atoi(name); err != nil {
if !errors.Is(err, strconv.ErrSyntax) { if !errors.Is(err, strconv.ErrSyntax) {
msg.Verbosef("name cannot be interpreted as int64: %v", err) msg.Verbosef("name cannot be interpreted as int64: %v", err)
@ -68,7 +68,7 @@ func tryFd(msg container.Msg, name string) io.ReadCloser {
} }
} }
func tryShort(msg container.Msg, name string) (config *hst.Config, entry *state.State) { func tryShort(msg message.Msg, name string) (config *hst.Config, entry *state.State) {
likePrefix := false likePrefix := false
if len(name) <= 32 { if len(name) <= 32 {
likePrefix = true likePrefix = true

View File

@ -11,10 +11,10 @@ import (
"text/tabwriter" "text/tabwriter"
"time" "time"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app" "hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/message"
) )
func printShowSystem(output io.Writer, short, flagJSON bool) { func printShowSystem(output io.Writer, short, flagJSON bool) {
@ -56,7 +56,7 @@ func printShowInstance(
if err := config.Validate(); err != nil { if err := config.Validate(); err != nil {
valid = false valid = false
if m, ok := container.GetErrorMessage(err); ok { if m, ok := message.GetMessage(err); ok {
mustPrint(output, "Error: "+m+"!\n\n") mustPrint(output, "Error: "+m+"!\n\n")
} }
} }

View File

@ -11,10 +11,10 @@ import (
"syscall" "syscall"
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
) )
var ( var (
@ -24,7 +24,7 @@ var (
func main() { func main() {
log.SetPrefix("hpkg: ") log.SetPrefix("hpkg: ")
log.SetFlags(0) log.SetFlags(0)
msg := container.NewMsg(log.Default()) msg := message.NewMsg(log.Default())
if err := os.Setenv("SHELL", pathShell.String()); err != nil { if err := os.Setenv("SHELL", pathShell.String()); err != nil {
log.Fatalf("cannot set $SHELL: %v", err) log.Fatalf("cannot set $SHELL: %v", err)

View File

@ -7,10 +7,10 @@ import (
"strconv" "strconv"
"sync/atomic" "sync/atomic"
"hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
) )
const bash = "bash" const bash = "bash"
@ -52,7 +52,7 @@ func lookPath(file string) string {
var beforeRunFail = new(atomic.Pointer[func()]) var beforeRunFail = new(atomic.Pointer[func()])
func mustRun(msg container.Msg, name string, arg ...string) { func mustRun(msg message.Msg, name string, arg ...string) {
msg.Verbosef("spawning process: %q %q", name, arg) msg.Verbosef("spawning process: %q %q", name, arg)
cmd := exec.Command(name, arg...) cmd := exec.Command(name, arg...)
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr

View File

@ -9,14 +9,14 @@ import (
"os" "os"
"os/exec" "os/exec"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/message"
) )
var hakureiPath = internal.MustHakureiPath() var hakureiPath = internal.MustHakureiPath()
func mustRunApp(ctx context.Context, msg container.Msg, config *hst.Config, beforeFail func()) { func mustRunApp(ctx context.Context, msg message.Msg, config *hst.Config, beforeFail func()) {
var ( var (
cmd *exec.Cmd cmd *exec.Cmd
st io.WriteCloser st io.WriteCloser

View File

@ -5,15 +5,15 @@ import (
"os" "os"
"strings" "strings"
"hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
) )
func withNixDaemon( func withNixDaemon(
ctx context.Context, ctx context.Context,
msg container.Msg, msg message.Msg,
action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config, action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config,
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(), app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
) { ) {
@ -64,7 +64,7 @@ func withNixDaemon(
func withCacheDir( func withCacheDir(
ctx context.Context, ctx context.Context,
msg container.Msg, msg message.Msg,
action string, command []string, workDir *check.Absolute, action string, command []string, workDir *check.Absolute,
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) { app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
mustRunAppDropShell(ctx, msg, &hst.Config{ mustRunAppDropShell(ctx, msg, &hst.Config{
@ -102,7 +102,7 @@ func withCacheDir(
}, dropShell, beforeFail) }, dropShell, beforeFail)
} }
func mustRunAppDropShell(ctx context.Context, msg container.Msg, config *hst.Config, dropShell bool, beforeFail func()) { func mustRunAppDropShell(ctx context.Context, msg message.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
if dropShell { if dropShell {
if config.Container != nil { if config.Container != nil {
config.Container.Args = []string{bash, "-l"} config.Container.Args = []string{bash, "-l"}

View File

@ -6,6 +6,7 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/message"
) )
func init() { gob.Register(new(AutoRootOp)) } func init() { gob.Register(new(AutoRootOp)) }
@ -81,7 +82,7 @@ func (r *AutoRootOp) String() string {
} }
// IsAutoRootBindable returns whether a dir entry name is selected for AutoRoot. // IsAutoRootBindable returns whether a dir entry name is selected for AutoRoot.
func IsAutoRootBindable(msg Msg, name string) bool { func IsAutoRootBindable(msg message.Msg, name string) bool {
switch name { switch name {
case "proc", "dev", "tmp", "mnt", "etc": case "proc", "dev", "tmp", "mnt", "etc":

View File

@ -8,6 +8,7 @@ import (
"hakurei.app/container/bits" "hakurei.app/container/bits"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/message"
) )
func TestAutoRootOp(t *testing.T) { func TestAutoRootOp(t *testing.T) {
@ -195,7 +196,7 @@ func TestIsAutoRootBindable(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
var msg Msg var msg message.Msg
if tc.log { if tc.log {
msg = &kstub{nil, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { panic("unreachable") }, stub.Expect{Calls: []stub.Call{ msg = &kstub{nil, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { panic("unreachable") }, stub.Expect{Calls: []stub.Call{
call("verbose", stub.ExpectArgs{[]any{"got unexpected root entry"}}, nil, nil), call("verbose", stub.ExpectArgs{[]any{"got unexpected root entry"}}, nil, nil),

View File

@ -18,6 +18,7 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/message"
) )
const ( const (
@ -52,7 +53,7 @@ type (
cmd *exec.Cmd cmd *exec.Cmd
ctx context.Context ctx context.Context
msg Msg msg message.Msg
Params Params
} }
@ -396,9 +397,9 @@ func (p *Container) ProcessState() *os.ProcessState {
} }
// New returns the address to a new instance of [Container] that requires further initialisation before use. // New returns the address to a new instance of [Container] that requires further initialisation before use.
func New(ctx context.Context, msg Msg) *Container { func New(ctx context.Context, msg message.Msg) *Container {
if msg == nil { if msg == nil {
msg = NewMsg(nil) msg = message.NewMsg(nil)
} }
p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}} p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}}
@ -409,7 +410,7 @@ func New(ctx context.Context, msg Msg) *Container {
} }
// NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields. // NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields.
func NewCommand(ctx context.Context, msg Msg, pathname *check.Absolute, name string, args ...string) *Container { func NewCommand(ctx context.Context, msg message.Msg, pathname *check.Absolute, name string, args ...string) *Container {
z := New(ctx, msg) z := New(ctx, msg)
z.Path = pathname z.Path = pathname
z.Args = append([]string{name}, args...) z.Args = append([]string{name}, args...)

View File

@ -26,6 +26,7 @@ import (
"hakurei.app/container/vfs" "hakurei.app/container/vfs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/ldd" "hakurei.app/ldd"
"hakurei.app/message"
) )
func TestStartError(t *testing.T) { func TestStartError(t *testing.T) {
@ -152,13 +153,13 @@ func TestStartError(t *testing.T) {
}) })
t.Run("msg", func(t *testing.T) { t.Run("msg", func(t *testing.T) {
if got, ok := container.GetErrorMessage(tc.err); !ok { if got, ok := message.GetMessage(tc.err); !ok {
if tc.msg != "" { if tc.msg != "" {
t.Errorf("GetErrorMessage: err does not implement MessageError") t.Errorf("GetMessage: err does not implement MessageError")
} }
return return
} else if got != tc.msg { } else if got != tc.msg {
t.Errorf("GetErrorMessage: %q, want %q", got, tc.msg) t.Errorf("GetMessage: %q, want %q", got, tc.msg)
} }
}) })
}) })
@ -545,7 +546,7 @@ func testContainerCancel(
} }
func TestContainerString(t *testing.T) { func TestContainerString(t *testing.T) {
msg := container.NewMsg(nil) msg := message.NewMsg(nil)
c := container.NewCommand(t.Context(), msg, check.MustAbs("/run/current-system/sw/bin/ldd"), "ldd", "/usr/bin/env") c := container.NewCommand(t.Context(), msg, check.MustAbs("/run/current-system/sw/bin/ldd"), "ldd", "/usr/bin/env")
c.SeccompFlags |= seccomp.AllowMultiarch c.SeccompFlags |= seccomp.AllowMultiarch
c.SeccompRules = seccomp.Preset( c.SeccompRules = seccomp.Preset(
@ -711,7 +712,7 @@ func TestMain(m *testing.M) {
} }
func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) { func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*check.Absolute, args ...string) (c *container.Container) {
msg := container.NewMsg(nil) msg := message.NewMsg(nil)
c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...) c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...)
c.Env = append(c.Env, envDoCheck+"=1") c.Env = append(c.Env, envDoCheck+"=1")
c.Bind(check.MustAbs(os.Args[0]), absHelperInnerPath, 0) c.Bind(check.MustAbs(os.Args[0]), absHelperInnerPath, 0)

View File

@ -11,6 +11,7 @@ import (
"syscall" "syscall"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/message"
) )
type osFile interface { type osFile interface {
@ -37,7 +38,7 @@ type syscallDispatcher interface {
setNoNewPrivs() error setNoNewPrivs() error
// lastcap provides [LastCap]. // lastcap provides [LastCap].
lastcap(msg Msg) uintptr lastcap(msg message.Msg) uintptr
// capset provides capset. // capset provides capset.
capset(hdrp *capHeader, datap *[2]capData) error capset(hdrp *capHeader, datap *[2]capData) error
// capBoundingSetDrop provides capBoundingSetDrop. // capBoundingSetDrop provides capBoundingSetDrop.
@ -52,9 +53,9 @@ type syscallDispatcher interface {
receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error) receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error)
// bindMount provides procPaths.bindMount. // bindMount provides procPaths.bindMount.
bindMount(msg Msg, source, target string, flags uintptr) error bindMount(msg message.Msg, source, target string, flags uintptr) error
// remount provides procPaths.remount. // remount provides procPaths.remount.
remount(msg Msg, target string, flags uintptr) error remount(msg message.Msg, target string, flags uintptr) error
// mountTmpfs provides mountTmpfs. // mountTmpfs provides mountTmpfs.
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
// ensureFile provides ensureFile. // ensureFile provides ensureFile.
@ -122,11 +123,11 @@ type syscallDispatcher interface {
wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error)
// printf provides the Printf method of [log.Logger]. // printf provides the Printf method of [log.Logger].
printf(msg Msg, format string, v ...any) printf(msg message.Msg, format string, v ...any)
// fatal provides the Fatal method of [log.Logger] // fatal provides the Fatal method of [log.Logger]
fatal(msg Msg, v ...any) fatal(msg message.Msg, v ...any)
// fatalf provides the Fatalf method of [log.Logger] // fatalf provides the Fatalf method of [log.Logger]
fatalf(msg Msg, format string, v ...any) fatalf(msg message.Msg, format string, v ...any)
} }
// direct implements syscallDispatcher on the current kernel. // direct implements syscallDispatcher on the current kernel.
@ -140,7 +141,7 @@ func (direct) setPtracer(pid uintptr) error { return SetPtracer(pid) }
func (direct) setDumpable(dumpable uintptr) error { return SetDumpable(dumpable) } func (direct) setDumpable(dumpable uintptr) error { return SetDumpable(dumpable) }
func (direct) setNoNewPrivs() error { return SetNoNewPrivs() } func (direct) setNoNewPrivs() error { return SetNoNewPrivs() }
func (direct) lastcap(msg Msg) uintptr { return LastCap(msg) } func (direct) lastcap(msg message.Msg) uintptr { return LastCap(msg) }
func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) } func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) }
func (direct) capBoundingSetDrop(cap uintptr) error { return capBoundingSetDrop(cap) } func (direct) capBoundingSetDrop(cap uintptr) error { return capBoundingSetDrop(cap) }
func (direct) capAmbientClearAll() error { return capAmbientClearAll() } func (direct) capAmbientClearAll() error { return capAmbientClearAll() }
@ -150,10 +151,10 @@ func (direct) receive(key string, e any, fdp *uintptr) (func() error, error) {
return Receive(key, e, fdp) return Receive(key, e, fdp)
} }
func (direct) bindMount(msg Msg, source, target string, flags uintptr) error { func (direct) bindMount(msg message.Msg, source, target string, flags uintptr) error {
return hostProc.bindMount(msg, source, target, flags) return hostProc.bindMount(msg, source, target, flags)
} }
func (direct) remount(msg Msg, target string, flags uintptr) error { func (direct) remount(msg message.Msg, target string, flags uintptr) error {
return hostProc.remount(msg, target, flags) return hostProc.remount(msg, target, flags)
} }
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error { func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
@ -221,6 +222,6 @@ func (direct) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *s
return syscall.Wait4(pid, wstatus, options, rusage) return syscall.Wait4(pid, wstatus, options, rusage)
} }
func (direct) printf(msg Msg, format string, v ...any) { msg.GetLogger().Printf(format, v...) } func (direct) printf(msg message.Msg, format string, v ...any) { msg.GetLogger().Printf(format, v...) }
func (direct) fatal(msg Msg, v ...any) { msg.GetLogger().Fatal(v...) } func (direct) fatal(msg message.Msg, v ...any) { msg.GetLogger().Fatal(v...) }
func (direct) fatalf(msg Msg, format string, v ...any) { msg.GetLogger().Fatalf(format, v...) } func (direct) fatalf(msg message.Msg, format string, v ...any) { msg.GetLogger().Fatalf(format, v...) }

View File

@ -18,6 +18,7 @@ import (
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/message"
) )
type opValidTestCase struct { type opValidTestCase struct {
@ -329,7 +330,7 @@ func (k *kstub) setDumpable(dumpable uintptr) error {
} }
func (k *kstub) setNoNewPrivs() error { k.Helper(); return k.Expects("setNoNewPrivs").Err } func (k *kstub) setNoNewPrivs() error { k.Helper(); return k.Expects("setNoNewPrivs").Err }
func (k *kstub) lastcap(msg Msg) uintptr { func (k *kstub) lastcap(msg message.Msg) uintptr {
k.Helper() k.Helper()
k.checkMsg(msg) k.checkMsg(msg)
return k.Expects("lastcap").Ret.(uintptr) return k.Expects("lastcap").Ret.(uintptr)
@ -409,7 +410,7 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
return return
} }
func (k *kstub) bindMount(msg Msg, source, target string, flags uintptr) error { func (k *kstub) bindMount(msg message.Msg, source, target string, flags uintptr) error {
k.Helper() k.Helper()
k.checkMsg(msg) k.checkMsg(msg)
return k.Expects("bindMount").Error( return k.Expects("bindMount").Error(
@ -418,7 +419,7 @@ func (k *kstub) bindMount(msg Msg, source, target string, flags uintptr) error {
stub.CheckArg(k.Stub, "flags", flags, 2)) stub.CheckArg(k.Stub, "flags", flags, 2))
} }
func (k *kstub) remount(msg Msg, target string, flags uintptr) error { func (k *kstub) remount(msg message.Msg, target string, flags uintptr) error {
k.Helper() k.Helper()
k.checkMsg(msg) k.checkMsg(msg)
return k.Expects("remount").Error( return k.Expects("remount").Error(
@ -702,7 +703,7 @@ func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage
return return
} }
func (k *kstub) printf(_ Msg, format string, v ...any) { func (k *kstub) printf(_ message.Msg, format string, v ...any) {
k.Helper() k.Helper()
if k.Expects("printf").Error( if k.Expects("printf").Error(
stub.CheckArg(k.Stub, "format", format, 0), stub.CheckArg(k.Stub, "format", format, 0),
@ -711,7 +712,7 @@ func (k *kstub) printf(_ Msg, format string, v ...any) {
} }
} }
func (k *kstub) fatal(_ Msg, v ...any) { func (k *kstub) fatal(_ message.Msg, v ...any) {
k.Helper() k.Helper()
if k.Expects("fatal").Error( if k.Expects("fatal").Error(
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil { stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
@ -720,7 +721,7 @@ func (k *kstub) fatal(_ Msg, v ...any) {
panic(stub.PanicExit) panic(stub.PanicExit)
} }
func (k *kstub) fatalf(_ Msg, format string, v ...any) { func (k *kstub) fatalf(_ message.Msg, format string, v ...any) {
k.Helper() k.Helper()
if k.Expects("fatalf").Error( if k.Expects("fatalf").Error(
stub.CheckArg(k.Stub, "format", format, 0), stub.CheckArg(k.Stub, "format", format, 0),
@ -730,7 +731,7 @@ func (k *kstub) fatalf(_ Msg, format string, v ...any) {
panic(stub.PanicExit) panic(stub.PanicExit)
} }
func (k *kstub) checkMsg(msg Msg) { func (k *kstub) checkMsg(msg message.Msg) {
k.Helper() k.Helper()
var target *kstub var target *kstub

View File

@ -3,6 +3,8 @@ package container
import ( import (
"os" "os"
"sync" "sync"
"hakurei.app/message"
) )
var ( var (
@ -10,7 +12,7 @@ var (
executableOnce sync.Once executableOnce sync.Once
) )
func copyExecutable(msg Msg) { func copyExecutable(msg message.Msg) {
if name, err := os.Executable(); err != nil { if name, err := os.Executable(); err != nil {
msg.BeforeExit() msg.BeforeExit()
msg.GetLogger().Fatalf("cannot read executable path: %v", err) msg.GetLogger().Fatalf("cannot read executable path: %v", err)
@ -19,7 +21,7 @@ func copyExecutable(msg Msg) {
} }
} }
func MustExecutable(msg Msg) string { func MustExecutable(msg message.Msg) string {
executableOnce.Do(func() { copyExecutable(msg) }) executableOnce.Do(func() { copyExecutable(msg) })
return executable return executable
} }

View File

@ -5,11 +5,12 @@ import (
"testing" "testing"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/message"
) )
func TestExecutable(t *testing.T) { func TestExecutable(t *testing.T) {
for i := 0; i < 16; i++ { for i := 0; i < 16; i++ {
if got := container.MustExecutable(container.NewMsg(nil)); got != os.Args[0] { if got := container.MustExecutable(message.NewMsg(nil)); got != os.Args[0] {
t.Errorf("MustExecutable: %q, want %q", t.Errorf("MustExecutable: %q, want %q",
got, os.Args[0]) got, os.Args[0])
} }

View File

@ -14,6 +14,7 @@ import (
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/message"
) )
const ( const (
@ -61,7 +62,7 @@ type (
setupState struct { setupState struct {
nonrepeatable uintptr nonrepeatable uintptr
*Params *Params
Msg message.Msg
} }
) )
@ -95,14 +96,14 @@ type initParams struct {
} }
// Init is called by [TryArgv0] if the current process is the container init. // Init is called by [TryArgv0] if the current process is the container init.
func Init(msg Msg) { func Init(msg message.Msg) {
if msg == nil { if msg == nil {
panic("attempting to call initEntrypoint with nil msg") panic("attempting to call initEntrypoint with nil msg")
} }
initEntrypoint(direct{}, msg) initEntrypoint(direct{}, msg)
} }
func initEntrypoint(k syscallDispatcher, msg Msg) { func initEntrypoint(k syscallDispatcher, msg message.Msg) {
k.lockOSThread() k.lockOSThread()
if k.getpid() != 1 { if k.getpid() != 1 {
@ -125,7 +126,7 @@ func initEntrypoint(k syscallDispatcher, msg Msg) {
k.fatal(msg, "invalid setup descriptor") k.fatal(msg, "invalid setup descriptor")
} }
if errors.Is(err, ErrReceiveEnv) { if errors.Is(err, ErrReceiveEnv) {
k.fatal(msg, "HAKUREI_SETUP not set") k.fatal(msg, setupEnv+" not set")
} }
k.fatalf(msg, "cannot decode init setup payload: %v", err) k.fatalf(msg, "cannot decode init setup payload: %v", err)
@ -448,11 +449,11 @@ const initName = "init"
// TryArgv0 calls [Init] if the last element of argv0 is "init". // TryArgv0 calls [Init] if the last element of argv0 is "init".
// If a nil msg is passed, the system logger is used instead. // If a nil msg is passed, the system logger is used instead.
func TryArgv0(msg Msg) { func TryArgv0(msg message.Msg) {
if msg == nil { if msg == nil {
log.SetPrefix(initName + ": ") log.SetPrefix(initName + ": ")
log.SetFlags(0) log.SetFlags(0)
msg = NewMsg(log.Default()) msg = message.NewMsg(log.Default())
} }
if len(os.Args) > 0 && path.Base(os.Args[0]) == initName { if len(os.Args) > 0 && path.Base(os.Args[0]) == initName {

View File

@ -7,6 +7,7 @@ import (
. "syscall" . "syscall"
"hakurei.app/container/vfs" "hakurei.app/container/vfs"
"hakurei.app/message"
) )
/* /*
@ -87,7 +88,7 @@ const (
) )
// bindMount mounts source on target and recursively applies flags if MS_REC is set. // bindMount mounts source on target and recursively applies flags if MS_REC is set.
func (p *procPaths) bindMount(msg Msg, source, target string, flags uintptr) error { func (p *procPaths) bindMount(msg message.Msg, source, target string, flags uintptr) error {
// syscallDispatcher.bindMount and procPaths.remount must not be called from this function // syscallDispatcher.bindMount and procPaths.remount must not be called from this function
if err := p.k.mount(source, target, FstypeNULL, MS_SILENT|MS_BIND|flags&MS_REC, zeroString); err != nil { if err := p.k.mount(source, target, FstypeNULL, MS_SILENT|MS_BIND|flags&MS_REC, zeroString); err != nil {
@ -97,7 +98,7 @@ func (p *procPaths) bindMount(msg Msg, source, target string, flags uintptr) err
} }
// remount applies flags on target, recursively if MS_REC is set. // remount applies flags on target, recursively if MS_REC is set.
func (p *procPaths) remount(msg Msg, target string, flags uintptr) error { func (p *procPaths) remount(msg message.Msg, target string, flags uintptr) error {
// syscallDispatcher methods bindMount, remount must not be called from this function // syscallDispatcher methods bindMount, remount must not be called from this function
var targetFinal string var targetFinal string
@ -159,7 +160,7 @@ func (p *procPaths) remount(msg Msg, target string, flags uintptr) error {
} }
// remountWithFlags remounts mount point described by [vfs.MountInfoNode]. // remountWithFlags remounts mount point described by [vfs.MountInfoNode].
func remountWithFlags(k syscallDispatcher, msg Msg, n *vfs.MountInfoNode, mf uintptr) error { func remountWithFlags(k syscallDispatcher, msg message.Msg, n *vfs.MountInfoNode, mf uintptr) error {
// syscallDispatcher methods bindMount, remount must not be called from this function // syscallDispatcher methods bindMount, remount must not be called from this function
kf, unmatched := n.Flags() kf, unmatched := n.Flags()

View File

@ -7,6 +7,7 @@ import (
"sync" "sync"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/message"
) )
var ( var (
@ -23,7 +24,7 @@ const (
kernelCapLastCapPath = fhs.ProcSys + "kernel/cap_last_cap" kernelCapLastCapPath = fhs.ProcSys + "kernel/cap_last_cap"
) )
func mustReadSysctl(msg Msg) { func mustReadSysctl(msg message.Msg) {
sysctlOnce.Do(func() { sysctlOnce.Do(func() {
if v, err := os.ReadFile(kernelOverflowuidPath); err != nil { if v, err := os.ReadFile(kernelOverflowuidPath); err != nil {
msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowuidPath, err) msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowuidPath, err)
@ -45,6 +46,6 @@ func mustReadSysctl(msg Msg) {
}) })
} }
func OverflowUid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowuid } func OverflowUid(msg message.Msg) int { mustReadSysctl(msg); return kernelOverflowuid }
func OverflowGid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowgid } func OverflowGid(msg message.Msg) int { mustReadSysctl(msg); return kernelOverflowgid }
func LastCap(msg Msg) uintptr { mustReadSysctl(msg); return uintptr(kernelCapLastCap) } func LastCap(msg message.Msg) uintptr { mustReadSysctl(msg); return uintptr(kernelCapLastCap) }

View File

@ -12,12 +12,13 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/helper/proc" "hakurei.app/helper/proc"
"hakurei.app/message"
) )
// New initialises a Helper instance with wt as the null-terminated argument writer. // New initialises a Helper instance with wt as the null-terminated argument writer.
func New( func New(
ctx context.Context, ctx context.Context,
msg container.Msg, msg message.Msg,
pathname *check.Absolute, name string, pathname *check.Absolute, name string,
wt io.WriterTo, wt io.WriterTo,
stat bool, stat bool,

View File

@ -5,8 +5,8 @@ import (
"slices" "slices"
"testing" "testing"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
) )
func TestBadInterfaceError(t *testing.T) { func TestBadInterfaceError(t *testing.T) {
@ -26,10 +26,10 @@ func TestBadInterfaceError(t *testing.T) {
if gotError := tc.err.Error(); gotError != tc.want { if gotError := tc.err.Error(); gotError != tc.want {
t.Errorf("Error: %s, want %s", gotError, tc.want) t.Errorf("Error: %s, want %s", gotError, tc.want)
} }
if gotMessage, ok := container.GetErrorMessage(tc.err); !ok { if gotMessage, ok := message.GetMessage(tc.err); !ok {
t.Error("GetErrorMessage: ok = false") t.Error("GetMessage: ok = false")
} else if gotMessage != tc.want { } else if gotMessage != tc.want {
t.Errorf("GetErrorMessage: %s, want %s", gotMessage, tc.want) t.Errorf("GetMessage: %s, want %s", gotMessage, tc.want)
} }
}) })
} }

View File

@ -8,9 +8,9 @@ import (
"syscall" "syscall"
"testing" "testing"
"hakurei.app/container"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
) )
func TestAppError(t *testing.T) { func TestAppError(t *testing.T) {
@ -65,14 +65,14 @@ func TestAppError(t *testing.T) {
}) })
t.Run("message", func(t *testing.T) { t.Run("message", func(t *testing.T) {
gotMessage, gotMessageOk := container.GetErrorMessage(tc.err) gotMessage, gotMessageOk := message.GetMessage(tc.err)
if want := tc.message != "\x00"; gotMessageOk != want { if want := tc.message != "\x00"; gotMessageOk != want {
t.Errorf("GetErrorMessage: ok = %v, want %v", gotMessage, want) t.Errorf("GetMessage: ok = %v, want %v", gotMessage, want)
} }
if gotMessageOk { if gotMessageOk {
if gotMessage != tc.message { if gotMessage != tc.message {
t.Errorf("GetErrorMessage: %s, want %s", gotMessage, tc.message) t.Errorf("GetMessage: %s, want %s", gotMessage, tc.message)
} }
} }
}) })

View File

@ -6,13 +6,13 @@ import (
"log" "log"
"os" "os"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/message"
) )
// Main runs an app according to [hst.Config] and terminates. Main does not return. // Main runs an app according to [hst.Config] and terminates. Main does not return.
func Main(ctx context.Context, msg container.Msg, config *hst.Config) { func Main(ctx context.Context, msg message.Msg, config *hst.Config) {
var id state.ID var id state.ID
if err := state.NewAppID(&id); err != nil { if err := state.NewAppID(&id); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -23,12 +23,13 @@ import (
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/message"
"hakurei.app/system" "hakurei.app/system"
"hakurei.app/system/acl" "hakurei.app/system/acl"
) )
func TestApp(t *testing.T) { func TestApp(t *testing.T) {
msg := container.NewMsg(nil) msg := message.NewMsg(nil)
msg.SwapVerbose(testing.Verbose()) msg.SwapVerbose(testing.Verbose())
testCases := []struct { testCases := []struct {
@ -744,8 +745,8 @@ func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
} }
} }
func (k *stubNixOS) overflowUid(container.Msg) int { return 65534 } func (k *stubNixOS) overflowUid(message.Msg) int { return 65534 }
func (k *stubNixOS) overflowGid(container.Msg) int { return 65534 } func (k *stubNixOS) overflowGid(message.Msg) int { return 65534 }
func (k *stubNixOS) mustHsuPath() *check.Absolute { return m("/proc/nonexistent/hsu") } func (k *stubNixOS) mustHsuPath() *check.Absolute { return m("/proc/nonexistent/hsu") }

View File

@ -12,6 +12,7 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/message"
) )
// osFile represents [os.File]. // osFile represents [os.File].
@ -53,9 +54,9 @@ type syscallDispatcher interface {
cmdOutput(cmd *exec.Cmd) ([]byte, error) cmdOutput(cmd *exec.Cmd) ([]byte, error)
// overflowUid provides [container.OverflowUid]. // overflowUid provides [container.OverflowUid].
overflowUid(msg container.Msg) int overflowUid(msg message.Msg) int
// overflowGid provides [container.OverflowGid]. // overflowGid provides [container.OverflowGid].
overflowGid(msg container.Msg) int overflowGid(msg message.Msg) int
// mustHsuPath provides [internal.MustHsuPath]. // mustHsuPath provides [internal.MustHsuPath].
mustHsuPath() *check.Absolute mustHsuPath() *check.Absolute
@ -90,8 +91,8 @@ func (direct) lookupGroupId(name string) (gid string, err error) {
func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() } func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() }
func (direct) overflowUid(msg container.Msg) int { return container.OverflowUid(msg) } func (direct) overflowUid(msg message.Msg) int { return container.OverflowUid(msg) }
func (direct) overflowGid(msg container.Msg) int { return container.OverflowGid(msg) } func (direct) overflowGid(msg message.Msg) int { return container.OverflowGid(msg) }
func (direct) mustHsuPath() *check.Absolute { return internal.MustHsuPath() } func (direct) mustHsuPath() *check.Absolute { return internal.MustHsuPath() }

View File

@ -4,8 +4,8 @@ import (
"os" "os"
"os/exec" "os/exec"
"hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/message"
) )
type panicDispatcher struct{} type panicDispatcher struct{}
@ -21,7 +21,7 @@ func (panicDispatcher) tempdir() string { panic("unreachab
func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") } func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") }
func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") } func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") }
func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") } func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") }
func (panicDispatcher) overflowUid(container.Msg) int { panic("unreachable") } func (panicDispatcher) overflowUid(message.Msg) int { panic("unreachable") }
func (panicDispatcher) overflowGid(container.Msg) int { panic("unreachable") } func (panicDispatcher) overflowGid(message.Msg) int { panic("unreachable") }
func (panicDispatcher) mustHsuPath() *check.Absolute { panic("unreachable") } func (panicDispatcher) mustHsuPath() *check.Absolute { panic("unreachable") }
func (panicDispatcher) fatalf(string, ...any) { panic("unreachable") } func (panicDispatcher) fatalf(string, ...any) { panic("unreachable") }

View File

@ -8,9 +8,9 @@ import (
"os/user" "os/user"
"sync/atomic" "sync/atomic"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/message"
"hakurei.app/system" "hakurei.app/system"
) )
@ -37,7 +37,7 @@ type outcome struct {
syscallDispatcher syscallDispatcher
} }
func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, config *hst.Config) error { func (k *outcome) finalise(ctx context.Context, msg message.Msg, id *state.ID, config *hst.Config) error {
if ctx == nil || id == nil { if ctx == nil || id == nil {
// unreachable // unreachable
panic("invalid call to finalise") panic("invalid call to finalise")

View File

@ -9,9 +9,9 @@ import (
"strconv" "strconv"
"sync" "sync"
"hakurei.app/container"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
) )
// Hsu caches responses from cmd/hsu. // Hsu caches responses from cmd/hsu.
@ -74,7 +74,7 @@ func (h *Hsu) ID() (int, error) {
func (h *Hsu) MustID() int { return h.MustIDMsg(nil) } func (h *Hsu) MustID() int { return h.MustIDMsg(nil) }
// MustIDMsg implements MustID with a custom [container.Msg]. // MustIDMsg implements MustID with a custom [container.Msg].
func (h *Hsu) MustIDMsg(msg container.Msg) int { func (h *Hsu) MustIDMsg(msg message.Msg) int {
id, err := h.ID() id, err := h.ID()
if err == nil { if err == nil {
return id return id
@ -87,7 +87,7 @@ func (h *Hsu) MustIDMsg(msg container.Msg) int {
} }
os.Exit(1) os.Exit(1)
return -0xdeadbeef return -0xdeadbeef
} else if m, ok := container.GetErrorMessage(err); ok { } else if m, ok := message.GetMessage(err); ok {
log.Fatal(m) log.Fatal(m)
return -0xdeadbeef return -0xdeadbeef
} else { } else {

View File

@ -8,6 +8,7 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/message"
"hakurei.app/system" "hakurei.app/system"
"hakurei.app/system/acl" "hakurei.app/system/acl"
) )
@ -61,7 +62,7 @@ type outcomeState struct {
// Copied via populateLocal. // Copied via populateLocal.
k syscallDispatcher k syscallDispatcher
// Copied via populateLocal. // Copied via populateLocal.
msg container.Msg msg message.Msg
} }
// valid checks outcomeState to be safe for use with outcomeOp. // valid checks outcomeState to be safe for use with outcomeOp.
@ -75,7 +76,7 @@ func (s *outcomeState) valid() bool {
// populateEarly populates exported fields via syscallDispatcher. // populateEarly populates exported fields via syscallDispatcher.
// This must only be called from the priv side. // This must only be called from the priv side.
func (s *outcomeState) populateEarly(k syscallDispatcher, msg container.Msg, config *hst.Config) { func (s *outcomeState) populateEarly(k syscallDispatcher, msg message.Msg, config *hst.Config) {
s.Shim = &shimParams{PrivPID: os.Getpid(), Verbose: msg.IsVerbose(), Ops: fromConfig(config)} s.Shim = &shimParams{PrivPID: os.Getpid(), Verbose: msg.IsVerbose(), Ops: fromConfig(config)}
// enforce bounds and default early // enforce bounds and default early
@ -98,7 +99,7 @@ func (s *outcomeState) populateEarly(k syscallDispatcher, msg container.Msg, con
// populateLocal populates unexported fields from transmitted exported fields. // populateLocal populates unexported fields from transmitted exported fields.
// These fields are cheaper to recompute per-process. // These fields are cheaper to recompute per-process.
func (s *outcomeState) populateLocal(k syscallDispatcher, msg container.Msg) error { func (s *outcomeState) populateLocal(k syscallDispatcher, msg message.Msg) error {
if !s.valid() || k == nil || msg == nil { if !s.valid() || k == nil || msg == nil {
return newWithMessage("impossible outcome state reached") return newWithMessage("impossible outcome state reached")
} }

View File

@ -17,6 +17,7 @@ import (
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/message"
"hakurei.app/system" "hakurei.app/system"
) )
@ -40,7 +41,7 @@ type mainState struct {
cmdWait chan error cmdWait chan error
k *outcome k *outcome
container.Msg message.Msg
uintptr uintptr
} }
@ -207,7 +208,7 @@ func (ms mainState) fatal(fallback string, ferr error) {
} }
// main carries out outcome and terminates. main does not return. // main carries out outcome and terminates. main does not return.
func (k *outcome) main(msg container.Msg) { func (k *outcome) main(msg message.Msg) {
if !k.active.CompareAndSwap(false, true) { if !k.active.CompareAndSwap(false, true) {
panic("outcome: attempted to run twice") panic("outcome: attempted to run twice")
} }
@ -312,10 +313,10 @@ func (k *outcome) main(msg container.Msg) {
os.Exit(0) os.Exit(0)
} }
// printMessageError prints the error message according to [container.GetErrorMessage], // printMessageError prints the error message according to [message.GetMessage],
// or fallback prepended to err if an error message is not available. // or fallback prepended to err if an error message is not available.
func printMessageError(fallback string, err error) { func printMessageError(fallback string, err error) {
m, ok := container.GetErrorMessage(err) m, ok := message.GetMessage(err)
if !ok { if !ok {
log.Println(fallback, err) log.Println(fallback, err)
return return

View File

@ -18,6 +18,7 @@ import (
"hakurei.app/container/bits" "hakurei.app/container/bits"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
) )
//#include "shim-signal.h" //#include "shim-signal.h"
@ -57,7 +58,7 @@ func (p *shimParams) valid() bool {
func ShimMain() { func ShimMain() {
log.SetPrefix("shim: ") log.SetPrefix("shim: ")
log.SetFlags(0) log.SetFlags(0)
msg := container.NewMsg(log.Default()) msg := message.NewMsg(log.Default())
if err := container.SetDumpable(container.SUID_DUMP_DISABLE); err != nil { if err := container.SetDumpable(container.SUID_DUMP_DISABLE); err != nil {
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
@ -81,7 +82,7 @@ func ShimMain() {
closeSetup = f closeSetup = f
if err = state.populateLocal(direct{}, msg); err != nil { if err = state.populateLocal(direct{}, msg); err != nil {
if m, ok := container.GetErrorMessage(err); ok { if m, ok := message.GetMessage(err); ok {
log.Fatal(m) log.Fatal(m)
} else { } else {
log.Fatalf("cannot populate local state: %v", err) log.Fatalf("cannot populate local state: %v", err)
@ -114,7 +115,7 @@ func ShimMain() {
} }
for _, op := range state.Shim.Ops { for _, op := range state.Shim.Ops {
if err := op.toContainer(&stateParams); err != nil { if err := op.toContainer(&stateParams); err != nil {
if m, ok := container.GetErrorMessage(err); ok { if m, ok := message.GetMessage(err); ok {
log.Fatal(m) log.Fatal(m)
} else { } else {
log.Fatalf("cannot create container state: %v", err) log.Fatalf("cannot create container state: %v", err)

View File

@ -15,6 +15,7 @@ import (
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
) )
@ -299,7 +300,7 @@ func resolveRoot(c *hst.ContainerConfig) (rootfs hst.FilesystemConfig, filesyste
} }
// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist]. // evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
func evalSymlinks(msg container.Msg, k syscallDispatcher, v *string) error { func evalSymlinks(msg message.Msg, k syscallDispatcher, v *string) error {
if p, err := k.evalSymlinks(*v); err != nil { if p, err := k.evalSymlinks(*v); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return err return err

View File

@ -11,8 +11,8 @@ import (
"sync" "sync"
"syscall" "syscall"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
) )
// fine-grained locking and access // fine-grained locking and access
@ -22,7 +22,7 @@ type multiStore struct {
// initialised backends // initialised backends
backends *sync.Map backends *sync.Map
msg container.Msg msg message.Msg
mu sync.RWMutex mu sync.RWMutex
} }
@ -280,7 +280,7 @@ func (b *multiBackend) close() error {
} }
// NewMulti returns an instance of the multi-file store. // NewMulti returns an instance of the multi-file store.
func NewMulti(msg container.Msg, runDir string) Store { func NewMulti(msg message.Msg, runDir string) Store {
return &multiStore{ return &multiStore{
msg: msg, msg: msg,
base: path.Join(runDir, "state"), base: path.Join(runDir, "state"),

View File

@ -4,10 +4,10 @@ import (
"log" "log"
"testing" "testing"
"hakurei.app/container"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/message"
) )
func TestMulti(t *testing.T) { func TestMulti(t *testing.T) {
testStore(t, state.NewMulti(container.NewMsg(log.New(log.Writer(), "multi: ", 0)), t.TempDir())) testStore(t, state.NewMulti(message.NewMsg(log.New(log.Writer(), "multi: ", 0)), t.TempDir()))
} }

View File

@ -13,6 +13,7 @@ import (
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/fhs" "hakurei.app/container/fhs"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/message"
) )
const ( const (
@ -25,7 +26,7 @@ var (
msgStaticGlibc = []byte("not a dynamic executable") msgStaticGlibc = []byte("not a dynamic executable")
) )
func Exec(ctx context.Context, msg container.Msg, p string) ([]*Entry, error) { func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
c, cancel := context.WithTimeout(ctx, lddTimeout) c, cancel := context.WithTimeout(ctx, lddTimeout)
defer cancel() defer cancel()

View File

@ -1,4 +1,5 @@
package container // Package message provides interfaces and a base implementation for extended reporting on top of [log.Logger]
package message
import ( import (
"errors" "errors"
@ -6,19 +7,19 @@ import (
"sync/atomic" "sync/atomic"
) )
// MessageError is an error with a user-facing message. // Error is an error with a user-facing message.
type MessageError interface { type Error interface {
// Message returns a user-facing error message. // Message returns a user-facing error message.
Message() string Message() string
error error
} }
// GetErrorMessage returns whether an error implements [MessageError], and the message if it does. // GetMessage returns whether an error implements [Error], and the message if it does.
func GetErrorMessage(err error) (string, bool) { func GetMessage(err error) (string, bool) {
var e MessageError var e Error
if !errors.As(err, &e) || e == nil { if !errors.As(err, &e) || e == nil {
return zeroString, false return "", false
} }
return e.Message(), true return e.Message(), true
} }

View File

@ -1,4 +1,4 @@
package container_test package message_test
import ( import (
"bytes" "bytes"
@ -11,6 +11,7 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/message"
) )
func TestMessageError(t *testing.T) { func TestMessageError(t *testing.T) {
@ -29,12 +30,12 @@ func TestMessageError(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
got, ok := container.GetErrorMessage(tc.err) got, ok := message.GetMessage(tc.err)
if got != tc.want { if got != tc.want {
t.Errorf("GetErrorMessage: %q, want %q", got, tc.want) t.Errorf("GetMessage: %q, want %q", got, tc.want)
} }
if ok != tc.wantOk { if ok != tc.wantOk {
t.Errorf("GetErrorMessage: ok = %v, want %v", ok, tc.wantOk) t.Errorf("GetMessage: ok = %v, want %v", ok, tc.wantOk)
} }
}) })
} }
@ -46,9 +47,9 @@ func TestDefaultMsg(t *testing.T) {
t.Run("logger", func(t *testing.T) { t.Run("logger", func(t *testing.T) {
t.Run("nil", func(t *testing.T) { t.Run("nil", func(t *testing.T) {
got := container.NewMsg(nil).GetLogger() got := message.NewMsg(nil).GetLogger()
if out := got.Writer().(*container.Suspendable).Downstream; out != log.Writer() { if out := got.Writer().(*message.Suspendable).Downstream; out != log.Writer() {
t.Errorf("GetLogger: Downstream = %#v", out) t.Errorf("GetLogger: Downstream = %#v", out)
} }
@ -59,13 +60,13 @@ func TestDefaultMsg(t *testing.T) {
t.Run("takeover", func(t *testing.T) { t.Run("takeover", func(t *testing.T) {
l := log.New(io.Discard, "\x00", 0xdeadbeef) l := log.New(io.Discard, "\x00", 0xdeadbeef)
got := container.NewMsg(l) got := message.NewMsg(l)
if logger := got.GetLogger(); logger != l { if logger := got.GetLogger(); logger != l {
t.Errorf("GetLogger: %#v, want %#v", logger, l) t.Errorf("GetLogger: %#v, want %#v", logger, l)
} }
if ds := l.Writer().(*container.Suspendable).Downstream; ds != io.Discard { if ds := l.Writer().(*message.Suspendable).Downstream; ds != io.Discard {
t.Errorf("GetLogger: Downstream = %#v", ds) t.Errorf("GetLogger: Downstream = %#v", ds)
} }
}) })
@ -78,93 +79,93 @@ func TestDefaultMsg(t *testing.T) {
pt, next []byte pt, next []byte
err error err error
f func(t *testing.T, msg container.Msg) f func(t *testing.T, msg message.Msg)
}{ }{
{"zero verbose", nil, nil, nil, func(t *testing.T, msg container.Msg) { {"zero verbose", nil, nil, nil, func(t *testing.T, msg message.Msg) {
if msg.IsVerbose() { if msg.IsVerbose() {
t.Error("IsVerbose unexpected true") t.Error("IsVerbose unexpected true")
} }
}}, }},
{"swap false", nil, nil, nil, func(t *testing.T, msg container.Msg) { {"swap false", nil, nil, nil, func(t *testing.T, msg message.Msg) {
if msg.SwapVerbose(false) { if msg.SwapVerbose(false) {
t.Error("SwapVerbose unexpected true") t.Error("SwapVerbose unexpected true")
} }
}}, }},
{"write discard", nil, nil, nil, func(_ *testing.T, msg container.Msg) { {"write discard", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
msg.Verbose("\x00") msg.Verbose("\x00")
msg.Verbosef("\x00") msg.Verbosef("\x00")
}}, }},
{"verbose false", nil, nil, nil, func(t *testing.T, msg container.Msg) { {"verbose false", nil, nil, nil, func(t *testing.T, msg message.Msg) {
if msg.IsVerbose() { if msg.IsVerbose() {
t.Error("IsVerbose unexpected true") t.Error("IsVerbose unexpected true")
} }
}}, }},
{"swap true", nil, nil, nil, func(t *testing.T, msg container.Msg) { {"swap true", nil, nil, nil, func(t *testing.T, msg message.Msg) {
if msg.SwapVerbose(true) { if msg.SwapVerbose(true) {
t.Error("SwapVerbose unexpected true") t.Error("SwapVerbose unexpected true")
} }
}}, }},
{"write verbose", []byte("test: \x00\n"), nil, nil, func(_ *testing.T, msg container.Msg) { {"write verbose", []byte("test: \x00\n"), nil, nil, func(_ *testing.T, msg message.Msg) {
msg.Verbose("\x00") msg.Verbose("\x00")
}}, }},
{"write verbosef", []byte(`test: "\x00"` + "\n"), nil, nil, func(_ *testing.T, msg container.Msg) { {"write verbosef", []byte(`test: "\x00"` + "\n"), nil, nil, func(_ *testing.T, msg message.Msg) {
msg.Verbosef("%q", "\x00") msg.Verbosef("%q", "\x00")
}}, }},
{"verbose true", nil, nil, nil, func(t *testing.T, msg container.Msg) { {"verbose true", nil, nil, nil, func(t *testing.T, msg message.Msg) {
if !msg.IsVerbose() { if !msg.IsVerbose() {
t.Error("IsVerbose unexpected false") t.Error("IsVerbose unexpected false")
} }
}}, }},
{"resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) { {"resume noop", nil, nil, nil, func(t *testing.T, msg message.Msg) {
if msg.Resume() { if msg.Resume() {
t.Error("Resume unexpected success") t.Error("Resume unexpected success")
} }
}}, }},
{"beforeExit noop", nil, nil, nil, func(_ *testing.T, msg container.Msg) { {"beforeExit noop", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
msg.BeforeExit() msg.BeforeExit()
}}, }},
{"beforeExit suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) { {"beforeExit suspend", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
msg.Suspend() msg.Suspend()
}}, }},
{"beforeExit message", []byte("test: beforeExit reached on suspended output\n"), nil, nil, func(_ *testing.T, msg container.Msg) { {"beforeExit message", []byte("test: beforeExit reached on suspended output\n"), nil, nil, func(_ *testing.T, msg message.Msg) {
msg.BeforeExit() msg.BeforeExit()
}}, }},
{"post beforeExit resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) { {"post beforeExit resume noop", nil, nil, nil, func(t *testing.T, msg message.Msg) {
if msg.Resume() { if msg.Resume() {
t.Error("Resume unexpected success") t.Error("Resume unexpected success")
} }
}}, }},
{"suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) { {"suspend", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
msg.Suspend() msg.Suspend()
}}, }},
{"suspend write", nil, nil, nil, func(_ *testing.T, msg container.Msg) { {"suspend write", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
msg.GetLogger().Print("\x00") msg.GetLogger().Print("\x00")
}}, }},
{"resume error", []byte("test: \x00\n"), []byte("test: cannot dump buffer on resume: unique error 0 injected by the test suite\n"), stub.UniqueError(0), func(t *testing.T, msg container.Msg) { {"resume error", []byte("test: \x00\n"), []byte("test: cannot dump buffer on resume: unique error 0 injected by the test suite\n"), stub.UniqueError(0), func(t *testing.T, msg message.Msg) {
if !msg.Resume() { if !msg.Resume() {
t.Error("Resume unexpected failure") t.Error("Resume unexpected failure")
} }
}}, }},
{"suspend drop", nil, nil, nil, func(_ *testing.T, msg container.Msg) { {"suspend drop", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
msg.Suspend() msg.Suspend()
}}, }},
{"suspend write fill", nil, nil, nil, func(_ *testing.T, msg container.Msg) { {"suspend write fill", nil, nil, nil, func(_ *testing.T, msg message.Msg) {
msg.GetLogger().Print(strings.Repeat("\x00", suspendBufMax)) msg.GetLogger().Print(strings.Repeat("\x00", suspendBufMax))
}}, }},
{"resume dropped", append([]byte("test: "), bytes.Repeat([]byte{0}, suspendBufMax-6)...), []byte("test: dropped 7 bytes while output is suspended\n"), nil, func(t *testing.T, msg container.Msg) { {"resume dropped", append([]byte("test: "), bytes.Repeat([]byte{0}, suspendBufMax-6)...), []byte("test: dropped 7 bytes while output is suspended\n"), nil, func(t *testing.T, msg message.Msg) {
if !msg.Resume() { if !msg.Resume() {
t.Error("Resume unexpected failure") t.Error("Resume unexpected failure")
} }
}}, }},
} }
msg := container.NewMsg(log.New(&dw, "test: ", 0)) msg := message.NewMsg(log.New(&dw, "test: ", 0))
for _, step := range steps { for _, step := range steps {
// these share the same writer, so cannot be subtests // these share the same writer, so cannot be subtests
t.Logf("running step %q", step.name) t.Logf("running step %q", step.name)

View File

@ -1,4 +1,4 @@
package container package message
import ( import (
"bytes" "bytes"

View File

@ -1,4 +1,4 @@
package container_test package message_test
import ( import (
"bytes" "bytes"
@ -8,8 +8,8 @@ import (
"syscall" "syscall"
"testing" "testing"
"hakurei.app/container"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/message"
) )
func TestSuspendable(t *testing.T) { func TestSuspendable(t *testing.T) {
@ -74,7 +74,7 @@ func TestSuspendable(t *testing.T) {
var dw expectWriter var dw expectWriter
w := container.Suspendable{Downstream: &dw} w := message.Suspendable{Downstream: &dw}
for _, step := range steps { for _, step := range steps {
// these share the same writer, so cannot be subtests // these share the same writer, so cannot be subtests
t.Logf("writing step %q", step.name) t.Logf("writing step %q", step.name)

View File

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

View File

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

View File

@ -6,9 +6,9 @@ import (
"sync" "sync"
"syscall" "syscall"
"hakurei.app/container"
"hakurei.app/helper" "hakurei.app/helper"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
) )
// ProxyName is the file name or path to the proxy program. // ProxyName is the file name or path to the proxy program.
@ -19,7 +19,7 @@ var ProxyName = "xdg-dbus-proxy"
type Proxy struct { type Proxy struct {
helper helper.Helper helper helper.Helper
ctx context.Context ctx context.Context
msg container.Msg msg message.Msg
cancel context.CancelCauseFunc cancel context.CancelCauseFunc
cause func() error cause func() error
@ -100,6 +100,6 @@ func Finalise(sessionBus, systemBus ProxyPair, session, system *hst.BusConfig) (
} }
// New returns a new instance of [Proxy]. // New returns a new instance of [Proxy].
func New(ctx context.Context, msg container.Msg, final *Final, output io.Writer) *Proxy { func New(ctx context.Context, msg message.Msg, final *Final, output io.Writer) *Proxy {
return &Proxy{name: ProxyName, ctx: ctx, msg: msg, final: final, output: output, useSandbox: true} return &Proxy{name: ProxyName, ctx: ctx, msg: msg, final: final, output: output, useSandbox: true}
} }

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/message"
) )
// OpError is returned by [I.Commit] and [I.Revert]. // OpError is returned by [I.Commit] and [I.Revert].
@ -70,14 +71,14 @@ func printJoinedError(println func(v ...any), fallback string, err error) {
error error
} }
if !errors.As(err, &joinErr) { if !errors.As(err, &joinErr) {
if m, ok := container.GetErrorMessage(err); ok { if m, ok := message.GetMessage(err); ok {
println(m) println(m)
} else { } else {
println(fallback, err) println(fallback, err)
} }
} else { } else {
for _, err = range joinErr.Unwrap() { for _, err = range joinErr.Unwrap() {
if m, ok := container.GetErrorMessage(err); ok { if m, ok := message.GetMessage(err); ok {
println(m) println(m)
} else { } else {
println(err.Error()) println(err.Error())

View File

@ -9,6 +9,7 @@ import (
"testing" "testing"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/message"
) )
func TestOpError(t *testing.T) { func TestOpError(t *testing.T) {
@ -64,13 +65,13 @@ func TestOpError(t *testing.T) {
}) })
t.Run("msg", func(t *testing.T) { t.Run("msg", func(t *testing.T) {
if got, ok := container.GetErrorMessage(tc.err); !ok { if got, ok := message.GetMessage(tc.err); !ok {
if tc.msg != "" { if tc.msg != "" {
t.Errorf("GetErrorMessage: err does not implement MessageError") t.Errorf("GetMessage: err does not implement MessageError")
} }
return return
} else if got != tc.msg { } else if got != tc.msg {
t.Errorf("GetErrorMessage: %q, want %q", got, tc.msg) t.Errorf("GetMessage: %q, want %q", got, tc.msg)
} }
}) })
}) })

View File

@ -6,8 +6,8 @@ import (
"errors" "errors"
"strings" "strings"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
) )
const ( const (
@ -68,7 +68,7 @@ func TypeString(e hst.Enablement) string {
} }
// New returns the address of a new [I] targeting uid. // New returns the address of a new [I] targeting uid.
func New(ctx context.Context, msg container.Msg, uid int) (sys *I) { func New(ctx context.Context, msg message.Msg, uid int) (sys *I) {
if ctx == nil || msg == nil || uid < 0 { if ctx == nil || msg == nil || uid < 0 {
panic("invalid call to New") panic("invalid call to New")
} }
@ -89,7 +89,7 @@ type I struct {
// the behaviour of Revert is only defined for up to one call // the behaviour of Revert is only defined for up to one call
reverted bool reverted bool
msg container.Msg msg message.Msg
syscallDispatcher syscallDispatcher
} }

View File

@ -8,10 +8,10 @@ import (
"strconv" "strconv"
"testing" "testing"
"hakurei.app/container"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/message"
"hakurei.app/system/internal/xcb" "hakurei.app/system/internal/xcb"
) )
@ -74,7 +74,7 @@ func TestNew(t *testing.T) {
t.Errorf("recover: %v, want %v", r, want) t.Errorf("recover: %v, want %v", r, want)
} }
}() }()
New(nil, container.NewMsg(nil), 0) New(nil, message.NewMsg(nil), 0)
}) })
t.Run("msg", func(t *testing.T) { t.Run("msg", func(t *testing.T) {
@ -94,11 +94,11 @@ func TestNew(t *testing.T) {
t.Errorf("recover: %v, want %v", r, want) t.Errorf("recover: %v, want %v", r, want)
} }
}() }()
New(t.Context(), container.NewMsg(nil), -1) New(t.Context(), message.NewMsg(nil), -1)
}) })
}) })
sys := New(t.Context(), container.NewMsg(nil), 0xdeadbeef) sys := New(t.Context(), message.NewMsg(nil), 0xdeadbeef)
if sys.ctx == nil { if sys.ctx == nil {
t.Error("New: ctx = nil") t.Error("New: ctx = nil")
} }
@ -115,51 +115,51 @@ func TestEqual(t *testing.T) {
want bool want bool
}{ }{
{"simple UID", {"simple UID",
New(t.Context(), container.NewMsg(nil), 150), New(t.Context(), message.NewMsg(nil), 150),
New(t.Context(), container.NewMsg(nil), 150), New(t.Context(), message.NewMsg(nil), 150),
true}, true},
{"simple UID differ", {"simple UID differ",
New(t.Context(), container.NewMsg(nil), 150), New(t.Context(), message.NewMsg(nil), 150),
New(t.Context(), container.NewMsg(nil), 151), New(t.Context(), message.NewMsg(nil), 151),
false}, false},
{"simple UID nil", {"simple UID nil",
New(t.Context(), container.NewMsg(nil), 150), New(t.Context(), message.NewMsg(nil), 150),
nil, nil,
false}, false},
{"op length mismatch", {"op length mismatch",
New(t.Context(), container.NewMsg(nil), 150). New(t.Context(), message.NewMsg(nil), 150).
ChangeHosts("chronos"), ChangeHosts("chronos"),
New(t.Context(), container.NewMsg(nil), 150). New(t.Context(), message.NewMsg(nil), 150).
ChangeHosts("chronos"). ChangeHosts("chronos").
Ensure(m("/run"), 0755), Ensure(m("/run"), 0755),
false}, false},
{"op value mismatch", {"op value mismatch",
New(t.Context(), container.NewMsg(nil), 150). New(t.Context(), message.NewMsg(nil), 150).
ChangeHosts("chronos"). ChangeHosts("chronos").
Ensure(m("/run"), 0644), Ensure(m("/run"), 0644),
New(t.Context(), container.NewMsg(nil), 150). New(t.Context(), message.NewMsg(nil), 150).
ChangeHosts("chronos"). ChangeHosts("chronos").
Ensure(m("/run"), 0755), Ensure(m("/run"), 0755),
false}, false},
{"op type mismatch", {"op type mismatch",
New(t.Context(), container.NewMsg(nil), 150). New(t.Context(), message.NewMsg(nil), 150).
ChangeHosts("chronos"). ChangeHosts("chronos").
CopyFile(new([]byte), m("/home/ophestra/xdg/config/pulse/cookie"), 0, 256), CopyFile(new([]byte), m("/home/ophestra/xdg/config/pulse/cookie"), 0, 256),
New(t.Context(), container.NewMsg(nil), 150). New(t.Context(), message.NewMsg(nil), 150).
ChangeHosts("chronos"). ChangeHosts("chronos").
Ensure(m("/run"), 0755), Ensure(m("/run"), 0755),
false}, false},
{"op equals", {"op equals",
New(t.Context(), container.NewMsg(nil), 150). New(t.Context(), message.NewMsg(nil), 150).
ChangeHosts("chronos"). ChangeHosts("chronos").
Ensure(m("/run"), 0755), Ensure(m("/run"), 0755),
New(t.Context(), container.NewMsg(nil), 150). New(t.Context(), message.NewMsg(nil), 150).
ChangeHosts("chronos"). ChangeHosts("chronos").
Ensure(m("/run"), 0755), Ensure(m("/run"), 0755),
true}, true},