diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-09-29 02:33:10 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-09-29 06:11:47 +0900 |
| commit | 46cd3a28c83e93b5d66c52d06a8366c11910d5c0 (patch) | |
| tree | 6e25c5078b5cd9de78b7a6c9b253f1132358812b /container | |
| parent | ad1bc6794f0053c3e63eab408a0d530d53e8b51c (diff) | |
container: remove global msg
This frees all container instances of side effects.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'container')
| -rw-r--r-- | container/autoroot.go | 4 | ||||
| -rw-r--r-- | container/autoroot_test.go | 23 | ||||
| -rw-r--r-- | container/container.go | 29 | ||||
| -rw-r--r-- | container/container_test.go | 14 | ||||
| -rw-r--r-- | container/dispatcher.go | 50 | ||||
| -rw-r--r-- | container/dispatcher_test.go | 62 | ||||
| -rw-r--r-- | container/executable.go | 9 | ||||
| -rw-r--r-- | container/executable_test.go | 2 | ||||
| -rw-r--r-- | container/init.go | 155 | ||||
| -rw-r--r-- | container/init_test.go | 161 | ||||
| -rw-r--r-- | container/initbind.go | 8 | ||||
| -rw-r--r-- | container/initdev.go | 4 | ||||
| -rw-r--r-- | container/initplace.go | 1 | ||||
| -rw-r--r-- | container/initremount.go | 4 | ||||
| -rw-r--r-- | container/mount.go | 17 | ||||
| -rw-r--r-- | container/mount_test.go | 82 | ||||
| -rw-r--r-- | container/msg.go | 89 | ||||
| -rw-r--r-- | container/msg_test.go | 239 | ||||
| -rw-r--r-- | container/output_test.go | 52 | ||||
| -rw-r--r-- | container/sysctl.go | 41 |
20 files changed, 584 insertions, 462 deletions
diff --git a/container/autoroot.go b/container/autoroot.go index c37e49bc..ee8bcbd3 100644 --- a/container/autoroot.go +++ b/container/autoroot.go @@ -34,7 +34,7 @@ func (r *AutoRootOp) early(state *setupState, k syscallDispatcher) error { r.resolved = make([]*BindMountOp, 0, len(d)) for _, ent := range d { name := ent.Name() - if IsAutoRootBindable(name) { + if IsAutoRootBindable(state, name) { // careful: the Valid method is skipped, make sure this is always valid op := &BindMountOp{ Source: r.Host.Append(name), @@ -78,7 +78,7 @@ func (r *AutoRootOp) String() string { } // IsAutoRootBindable returns whether a dir entry name is selected for AutoRoot. -func IsAutoRootBindable(name string) bool { +func IsAutoRootBindable(msg Msg, name string) bool { switch name { case "proc", "dev", "tmp", "mnt", "etc": diff --git a/container/autoroot_test.go b/container/autoroot_test.go index 3c904312..fe662226 100644 --- a/container/autoroot_test.go +++ b/container/autoroot_test.go @@ -180,19 +180,26 @@ func TestIsAutoRootBindable(t *testing.T) { testCases := []struct { name string want bool + log bool }{ - {"proc", false}, - {"dev", false}, - {"tmp", false}, - {"mnt", false}, - {"etc", false}, - {"", false}, + {"proc", false, false}, + {"dev", false, false}, + {"tmp", false, false}, + {"mnt", false, false}, + {"etc", false, false}, + {"", false, true}, - {"var", true}, + {"var", true, false}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - if got := IsAutoRootBindable(tc.name); got != tc.want { + var msg Msg + if tc.log { + 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), + }})} + } + if got := IsAutoRootBindable(msg, tc.name); got != tc.want { t.Errorf("IsAutoRootBindable: %v, want %v", got, tc.want) } }) diff --git a/container/container.go b/container/container.go index f49faa89..c19c2e7f 100644 --- a/container/container.go +++ b/container/container.go @@ -49,6 +49,7 @@ type ( cmd *exec.Cmd ctx context.Context + msg Msg Params } @@ -162,10 +163,10 @@ func (p *Container) Start() error { // map to overflow id to work around ownership checks if p.Uid < 1 { - p.Uid = OverflowUid() + p.Uid = OverflowUid(p.msg) } if p.Gid < 1 { - p.Gid = OverflowGid() + p.Gid = OverflowGid(p.msg) } if !p.RetainSession { @@ -263,19 +264,19 @@ func (p *Container) Start() error { } return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false} } else { - msg.Verbosef("landlock abi version %d", abi) + p.msg.Verbosef("landlock abi version %d", abi) } if rulesetFd, err := rulesetAttr.Create(0); err != nil { return &StartError{true, "create landlock ruleset", err, false, false} } else { - msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr) + p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr) if err = LandlockRestrictSelf(rulesetFd, 0); err != nil { _ = Close(rulesetFd) return &StartError{true, "enforce landlock ruleset", err, false, false} } if err = Close(rulesetFd); err != nil { - msg.Verbosef("cannot close landlock ruleset: %v", err) + p.msg.Verbosef("cannot close landlock ruleset: %v", err) // not fatal } } @@ -283,7 +284,7 @@ func (p *Container) Start() error { landlockOut: } - msg.Verbose("starting container init") + p.msg.Verbose("starting container init") if err := p.cmd.Start(); err != nil { return &StartError{false, "start container init", err, false, true} } @@ -325,7 +326,7 @@ func (p *Container) Serve() error { Getuid(), Getgid(), len(p.ExtraFiles), - msg.IsVerbose(), + p.msg.IsVerbose(), }, ) if err != nil { @@ -392,17 +393,21 @@ func (p *Container) ProcessState() *os.ProcessState { } // New returns the address to a new instance of [Container] that requires further initialisation before use. -func New(ctx context.Context) *Container { - p := &Container{ctx: ctx, Params: Params{Ops: new(Ops)}} +func New(ctx context.Context, msg Msg) *Container { + if msg == nil { + msg = NewMsg(nil) + } + + p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}} c, cancel := context.WithCancel(ctx) p.cancel = cancel - p.cmd = exec.CommandContext(c, MustExecutable()) + p.cmd = exec.CommandContext(c, MustExecutable(msg)) return p } // NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields. -func NewCommand(ctx context.Context, pathname *Absolute, name string, args ...string) *Container { - z := New(ctx) +func NewCommand(ctx context.Context, msg Msg, pathname *Absolute, name string, args ...string) *Container { + z := New(ctx, msg) z.Path = pathname z.Args = append([]string{name}, args...) return z diff --git a/container/container_test.go b/container/container_test.go index 0b6cdf51..22356484 100644 --- a/container/container_test.go +++ b/container/container_test.go @@ -23,8 +23,6 @@ import ( "hakurei.app/container/seccomp" "hakurei.app/container/vfs" "hakurei.app/hst" - "hakurei.app/internal" - "hakurei.app/internal/hlog" "hakurei.app/ldd" ) @@ -351,8 +349,6 @@ var containerTestCases = []struct { } func TestContainer(t *testing.T) { - replaceOutput(t) - t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) { wantErr := context.Canceled wantExitCode := 0 @@ -547,7 +543,8 @@ func testContainerCancel( } func TestContainerString(t *testing.T) { - c := container.NewCommand(t.Context(), container.MustAbs("/run/current-system/sw/bin/ldd"), "ldd", "/usr/bin/env") + msg := container.NewMsg(nil) + c := container.NewCommand(t.Context(), msg, container.MustAbs("/run/current-system/sw/bin/ldd"), "ldd", "/usr/bin/env") c.SeccompFlags |= seccomp.AllowMultiarch c.SeccompRules = seccomp.Preset( seccomp.PresetExt|seccomp.PresetDenyNS|seccomp.PresetDenyTTY, @@ -689,7 +686,7 @@ var ( var helperCommands []func(c command.Command) func TestMain(m *testing.M) { - container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput) + container.TryArgv0(nil) if os.Getenv(envDoCheck) == "1" { c := command.New(os.Stderr, log.Printf, "helper", func(args []string) error { @@ -712,12 +709,13 @@ func TestMain(m *testing.M) { } func helperNewContainerLibPaths(ctx context.Context, libPaths *[]*container.Absolute, args ...string) (c *container.Container) { - c = container.NewCommand(ctx, absHelperInnerPath, "helper", args...) + msg := container.NewMsg(nil) + c = container.NewCommand(ctx, msg, absHelperInnerPath, "helper", args...) c.Env = append(c.Env, envDoCheck+"=1") c.Bind(container.MustAbs(os.Args[0]), absHelperInnerPath, 0) // in case test has cgo enabled - if entries, err := ldd.Exec(ctx, os.Args[0]); err != nil { + if entries, err := ldd.Exec(ctx, msg, os.Args[0]); err != nil { log.Fatalf("ldd: %v", err) } else { *libPaths = ldd.Path(entries) diff --git a/container/dispatcher.go b/container/dispatcher.go index 929421bd..65881cc7 100644 --- a/container/dispatcher.go +++ b/container/dispatcher.go @@ -3,7 +3,6 @@ package container import ( "io" "io/fs" - "log" "os" "os/exec" "os/signal" @@ -38,7 +37,7 @@ type syscallDispatcher interface { setNoNewPrivs() error // lastcap provides [LastCap]. - lastcap() uintptr + lastcap(msg Msg) uintptr // capset provides capset. capset(hdrp *capHeader, datap *[2]capData) error // capBoundingSetDrop provides capBoundingSetDrop. @@ -53,9 +52,9 @@ type syscallDispatcher interface { receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error) // bindMount provides procPaths.bindMount. - bindMount(source, target string, flags uintptr) error + bindMount(msg Msg, source, target string, flags uintptr) error // remount provides procPaths.remount. - remount(target string, flags uintptr) error + remount(msg Msg, target string, flags uintptr) error // mountTmpfs provides mountTmpfs. mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error // ensureFile provides ensureFile. @@ -122,22 +121,12 @@ type syscallDispatcher interface { // wait4 provides syscall.Wait4 wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error) - // printf provides [log.Printf]. - printf(format string, v ...any) - // fatal provides [log.Fatal] - fatal(v ...any) - // fatalf provides [log.Fatalf] - fatalf(format string, v ...any) - // verbose provides [Msg.Verbose]. - verbose(v ...any) - // verbosef provides [Msg.Verbosef]. - verbosef(format string, v ...any) - // suspend provides [Msg.Suspend]. - suspend() - // resume provides [Msg.Resume]. - resume() bool - // beforeExit provides [Msg.BeforeExit]. - beforeExit() + // printf provides the Printf method of [log.Logger]. + printf(msg Msg, format string, v ...any) + // fatal provides the Fatal method of [log.Logger] + fatal(msg Msg, v ...any) + // fatalf provides the Fatalf method of [log.Logger] + fatalf(msg Msg, format string, v ...any) } // direct implements syscallDispatcher on the current kernel. @@ -151,7 +140,7 @@ func (direct) setPtracer(pid uintptr) error { return SetPtracer(pid) } func (direct) setDumpable(dumpable uintptr) error { return SetDumpable(dumpable) } func (direct) setNoNewPrivs() error { return SetNoNewPrivs() } -func (direct) lastcap() uintptr { return LastCap() } +func (direct) lastcap(msg Msg) uintptr { return LastCap(msg) } func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) } func (direct) capBoundingSetDrop(cap uintptr) error { return capBoundingSetDrop(cap) } func (direct) capAmbientClearAll() error { return capAmbientClearAll() } @@ -161,11 +150,11 @@ func (direct) receive(key string, e any, fdp *uintptr) (func() error, error) { return Receive(key, e, fdp) } -func (direct) bindMount(source, target string, flags uintptr) error { - return hostProc.bindMount(source, target, flags) +func (direct) bindMount(msg Msg, source, target string, flags uintptr) error { + return hostProc.bindMount(msg, source, target, flags) } -func (direct) remount(target string, flags uintptr) error { - return hostProc.remount(target, flags) +func (direct) remount(msg Msg, target string, flags uintptr) error { + return hostProc.remount(msg, target, flags) } func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error { return mountTmpfs(k, fsname, target, flags, size, perm) @@ -232,11 +221,6 @@ func (direct) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *s return syscall.Wait4(pid, wstatus, options, rusage) } -func (direct) printf(format string, v ...any) { log.Printf(format, v...) } -func (direct) fatal(v ...any) { log.Fatal(v...) } -func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) } -func (direct) verbose(v ...any) { msg.Verbose(v...) } -func (direct) verbosef(format string, v ...any) { msg.Verbosef(format, v...) } -func (direct) suspend() { msg.Suspend() } -func (direct) resume() bool { return msg.Resume() } -func (direct) beforeExit() { msg.BeforeExit() } +func (direct) printf(msg Msg, format string, v ...any) { msg.GetLogger().Printf(format, v...) } +func (direct) fatal(msg Msg, v ...any) { msg.GetLogger().Fatal(v...) } +func (direct) fatalf(msg Msg, format string, v ...any) { msg.GetLogger().Fatalf(format, v...) } diff --git a/container/dispatcher_test.go b/container/dispatcher_test.go index 7e141304..2a2685fd 100644 --- a/container/dispatcher_test.go +++ b/container/dispatcher_test.go @@ -2,8 +2,10 @@ package container import ( "bytes" + "fmt" "io" "io/fs" + "log" "os" "os/exec" "reflect" @@ -136,7 +138,7 @@ func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call { type simpleTestCase struct { name string - f func(k syscallDispatcher) error + f func(k *kstub) error want stub.Expect wantErr error } @@ -185,11 +187,11 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) { t.Run(tc.name, func(t *testing.T) { t.Helper() - state := &setupState{Params: tc.params} k := &kstub{nil, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{nil, s} }, stub.Expect{Calls: slices.Concat(tc.early, []stub.Call{{Name: stub.CallSeparator}}, tc.apply)}, )} + state := &setupState{Params: tc.params, Msg: k} defer stub.HandleExit(t) errEarly := tc.op.early(state, k) k.Expects(stub.CallSeparator) @@ -327,7 +329,11 @@ func (k *kstub) setDumpable(dumpable uintptr) error { } func (k *kstub) setNoNewPrivs() error { k.Helper(); return k.Expects("setNoNewPrivs").Err } -func (k *kstub) lastcap() uintptr { k.Helper(); return k.Expects("lastcap").Ret.(uintptr) } +func (k *kstub) lastcap(msg Msg) uintptr { + k.Helper() + k.checkMsg(msg) + return k.Expects("lastcap").Ret.(uintptr) +} func (k *kstub) capset(hdrp *capHeader, datap *[2]capData) error { k.Helper() @@ -403,16 +409,18 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error return } -func (k *kstub) bindMount(source, target string, flags uintptr) error { +func (k *kstub) bindMount(msg Msg, source, target string, flags uintptr) error { k.Helper() + k.checkMsg(msg) return k.Expects("bindMount").Error( stub.CheckArg(k.Stub, "source", source, 0), stub.CheckArg(k.Stub, "target", target, 1), stub.CheckArg(k.Stub, "flags", flags, 2)) } -func (k *kstub) remount(target string, flags uintptr) error { +func (k *kstub) remount(msg Msg, target string, flags uintptr) error { k.Helper() + k.checkMsg(msg) return k.Expects("remount").Error( stub.CheckArg(k.Stub, "target", target, 0), stub.CheckArg(k.Stub, "flags", flags, 1)) @@ -694,7 +702,7 @@ func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage return } -func (k *kstub) printf(format string, v ...any) { +func (k *kstub) printf(_ Msg, format string, v ...any) { k.Helper() if k.Expects("printf").Error( stub.CheckArg(k.Stub, "format", format, 0), @@ -703,7 +711,7 @@ func (k *kstub) printf(format string, v ...any) { } } -func (k *kstub) fatal(v ...any) { +func (k *kstub) fatal(_ Msg, v ...any) { k.Helper() if k.Expects("fatal").Error( stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil { @@ -712,7 +720,7 @@ func (k *kstub) fatal(v ...any) { panic(stub.PanicExit) } -func (k *kstub) fatalf(format string, v ...any) { +func (k *kstub) fatalf(_ Msg, format string, v ...any) { k.Helper() if k.Expects("fatalf").Error( stub.CheckArg(k.Stub, "format", format, 0), @@ -722,7 +730,35 @@ func (k *kstub) fatalf(format string, v ...any) { panic(stub.PanicExit) } -func (k *kstub) verbose(v ...any) { +func (k *kstub) checkMsg(msg Msg) { + k.Helper() + var target *kstub + + if state, ok := msg.(*setupState); ok { + target = state.Msg.(*kstub) + } else { + target = msg.(*kstub) + } + + if k != target { + panic(fmt.Sprintf("unexpected Msg: %#v", msg)) + } +} + +func (k *kstub) GetLogger() *log.Logger { panic("unreachable") } +func (k *kstub) IsVerbose() bool { panic("unreachable") } + +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) +} + +func (k *kstub) Verbose(v ...any) { k.Helper() if k.Expects("verbose").Error( stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil { @@ -730,7 +766,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), @@ -739,6 +775,6 @@ func (k *kstub) verbosef(format string, v ...any) { } } -func (k *kstub) suspend() { k.Helper(); k.Expects("suspend") } -func (k *kstub) resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) } -func (k *kstub) beforeExit() { k.Helper(); k.Expects("beforeExit") } +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") } diff --git a/container/executable.go b/container/executable.go index d19d56f5..9b6d4f66 100644 --- a/container/executable.go +++ b/container/executable.go @@ -1,7 +1,6 @@ package container import ( - "log" "os" "sync" ) @@ -11,16 +10,16 @@ var ( executableOnce sync.Once ) -func copyExecutable() { +func copyExecutable(msg Msg) { if name, err := os.Executable(); err != nil { msg.BeforeExit() - log.Fatalf("cannot read executable path: %v", err) + msg.GetLogger().Fatalf("cannot read executable path: %v", err) } else { executable = name } } -func MustExecutable() string { - executableOnce.Do(copyExecutable) +func MustExecutable(msg Msg) string { + executableOnce.Do(func() { copyExecutable(msg) }) return executable } diff --git a/container/executable_test.go b/container/executable_test.go index 2a44017a..5608a8ae 100644 --- a/container/executable_test.go +++ b/container/executable_test.go @@ -9,7 +9,7 @@ import ( func TestExecutable(t *testing.T) { for i := 0; i < 16; i++ { - if got := container.MustExecutable(); got != os.Args[0] { + if got := container.MustExecutable(container.NewMsg(nil)); got != os.Args[0] { t.Errorf("MustExecutable: %q, want %q", got, os.Args[0]) } diff --git a/container/init.go b/container/init.go index 73478b5a..e65472e7 100644 --- a/container/init.go +++ b/container/init.go @@ -3,6 +3,7 @@ package container import ( "errors" "fmt" + "log" "os" "os/exec" "path" @@ -59,6 +60,7 @@ type ( setupState struct { nonrepeatable uintptr *Params + Msg } ) @@ -91,20 +93,23 @@ type initParams struct { Verbose bool } -func Init(prepareLogger func(prefix string), setVerbose func(verbose bool)) { - initEntrypoint(direct{}, prepareLogger, setVerbose) +// Init is called by [TryArgv0] if the current process is the container init. +func Init(msg Msg) { + if msg == nil { + panic("attempting to call initEntrypoint with nil msg") + } + initEntrypoint(direct{}, msg) } -func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setVerbose func(verbose bool)) { +func initEntrypoint(k syscallDispatcher, msg Msg) { k.lockOSThread() - prepareLogger("init") if k.getpid() != 1 { - k.fatal("this process must run as pid 1") + k.fatal(msg, "this process must run as pid 1") } if err := k.setPtracer(0); err != nil { - k.verbosef("cannot enable ptrace protection via Yama LSM: %v", err) + msg.Verbosef("cannot enable ptrace protection via Yama LSM: %v", err) // not fatal: this program has no additional privileges at initial program start } @@ -116,65 +121,65 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV ) if f, err := k.receive(setupEnv, ¶ms, &setupFd); err != nil { if errors.Is(err, EBADF) { - k.fatal("invalid setup descriptor") + k.fatal(msg, "invalid setup descriptor") } if errors.Is(err, ErrReceiveEnv) { - k.fatal("HAKUREI_SETUP not set") + k.fatal(msg, "HAKUREI_SETUP not set") } - k.fatalf("cannot decode init setup payload: %v", err) + k.fatalf(msg, "cannot decode init setup payload: %v", err) } else { if params.Ops == nil { - k.fatal("invalid setup parameters") + k.fatal(msg, "invalid setup parameters") } if params.ParentPerm == 0 { params.ParentPerm = 0755 } - setVerbose(params.Verbose) - k.verbose("received setup parameters") + msg.SwapVerbose(params.Verbose) + msg.Verbose("received setup parameters") closeSetup = f offsetSetup = int(setupFd + 1) } // write uid/gid map here so parent does not need to set dumpable if err := k.setDumpable(SUID_DUMP_USER); err != nil { - k.fatalf("cannot set SUID_DUMP_USER: %v", err) + k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err) } if err := k.writeFile(FHSProc+"self/uid_map", append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...), 0); err != nil { - k.fatalf("%v", err) + k.fatalf(msg, "%v", err) } if err := k.writeFile(FHSProc+"self/setgroups", []byte("deny\n"), 0); err != nil && !os.IsNotExist(err) { - k.fatalf("%v", err) + k.fatalf(msg, "%v", err) } if err := k.writeFile(FHSProc+"self/gid_map", append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...), 0); err != nil { - k.fatalf("%v", err) + k.fatalf(msg, "%v", err) } if err := k.setDumpable(SUID_DUMP_DISABLE); err != nil { - k.fatalf("cannot set SUID_DUMP_DISABLE: %v", err) + k.fatalf(msg, "cannot set SUID_DUMP_DISABLE: %v", err) } oldmask := k.umask(0) if params.Hostname != "" { if err := k.sethostname([]byte(params.Hostname)); err != nil { - k.fatalf("cannot set hostname: %v", err) + k.fatalf(msg, "cannot set hostname: %v", err) } } // cache sysctl before pivot_root - lastcap := k.lastcap() + lastcap := k.lastcap(msg) if err := k.mount(zeroString, FHSRoot, zeroString, MS_SILENT|MS_SLAVE|MS_REC, zeroString); err != nil { - k.fatalf("cannot make / rslave: %v", err) + k.fatalf(msg, "cannot make / rslave: %v", err) } - state := &setupState{Params: ¶ms.Params} + state := &setupState{Params: ¶ms.Params, Msg: msg} /* early is called right before pivot_root into intermediate root; this step is mostly for gathering information that would otherwise be difficult to obtain @@ -182,41 +187,41 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV the state of the mount namespace */ for i, op := range *params.Ops { if op == nil || !op.Valid() { - k.fatalf("invalid op at index %d", i) + k.fatalf(msg, "invalid op at index %d", i) } if err := op.early(state, k); err != nil { if m, ok := messageFromError(err); ok { - k.fatal(m) + k.fatal(msg, m) } else { - k.fatalf("cannot prepare op at index %d: %v", i, err) + k.fatalf(msg, "cannot prepare op at index %d: %v", i, err) } } } if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil { - k.fatalf("cannot mount intermediate root: %v", err) + k.fatalf(msg, "cannot mount intermediate root: %v", err) } if err := k.chdir(intermediateHostPath); err != nil { - k.fatalf("cannot enter intermediate host path: %v", err) + k.fatalf(msg, "cannot enter intermediate host path: %v", err) } if err := k.mkdir(sysrootDir, 0755); err != nil { - k.fatalf("%v", err) + k.fatalf(msg, "%v", err) } if err := k.mount(sysrootDir, sysrootDir, zeroString, MS_SILENT|MS_BIND|MS_REC, zeroString); err != nil { - k.fatalf("cannot bind sysroot: %v", err) + k.fatalf(msg, "cannot bind sysroot: %v", err) } if err := k.mkdir(hostDir, 0755); err != nil { - k.fatalf("%v", err) + k.fatalf(msg, "%v", err) } // pivot_root uncovers intermediateHostPath in hostDir if err := k.pivotRoot(intermediateHostPath, hostDir); err != nil { - k.fatalf("cannot pivot into intermediate root: %v", err) + k.fatalf(msg, "cannot pivot into intermediate root: %v", err) } if err := k.chdir(FHSRoot); err != nil { - k.fatalf("cannot enter intermediate root: %v", err) + k.fatalf(msg, "cannot enter intermediate root: %v", err) } /* apply is called right after pivot_root and entering the new root; @@ -226,23 +231,23 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV for i, op := range *params.Ops { // ops already checked during early setup if prefix, ok := op.prefix(); ok { - k.verbosef("%s %s", prefix, op) + msg.Verbosef("%s %s", prefix, op) } if err := op.apply(state, k); err != nil { if m, ok := messageFromError(err); ok { - k.fatal(m) + k.fatal(msg, m) } else { - k.fatalf("cannot apply op at index %d: %v", i, err) + k.fatalf(msg, "cannot apply op at index %d: %v", i, err) } } } // setup requiring host root complete at this point if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil { - k.fatalf("cannot make host root rprivate: %v", err) + k.fatalf(msg, "cannot make host root rprivate: %v", err) } if err := k.unmount(hostDir, MNT_DETACH); err != nil { - k.fatalf("cannot unmount host root: %v", err) + k.fatalf(msg, "cannot unmount host root: %v", err) } { @@ -251,39 +256,39 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV fd, err = k.open(FHSRoot, O_DIRECTORY|O_RDONLY, 0) return }); err != nil { - k.fatalf("cannot open intermediate root: %v", err) + k.fatalf(msg, "cannot open intermediate root: %v", err) } if err := k.chdir(sysrootPath); err != nil { - k.fatalf("cannot enter sysroot: %v", err) + k.fatalf(msg, "cannot enter sysroot: %v", err) } if err := k.pivotRoot(".", "."); err != nil { - k.fatalf("cannot pivot into sysroot: %v", err) + k.fatalf(msg, "cannot pivot into sysroot: %v", err) } if err := k.fchdir(fd); err != nil { - k.fatalf("cannot re-enter intermediate root: %v", err) + k.fatalf(msg, "cannot re-enter intermediate root: %v", err) } if err := k.unmount(".", MNT_DETACH); err != nil { - k.fatalf("cannot unmount intermediate root: %v", err) + k.fatalf(msg, "cannot unmount intermediate root: %v", err) } if err := k.chdir(FHSRoot); err != nil { - k.fatalf("cannot enter root: %v", err) + k.fatalf(msg, "cannot enter root: %v", err) } if err := k.close(fd); err != nil { - k.fatalf("cannot close intermediate root: %v", err) + k.fatalf(msg, "cannot close intermediate root: %v", err) } } if err := k.capAmbientClearAll(); err != nil { - k.fatalf("cannot clear the ambient capability set: %v", err) + k.fatalf(msg, "cannot clear the ambient capability set: %v", err) } for i := uintptr(0); i <= lastcap; i++ { if params.Privileged && i == CAP_SYS_ADMIN { continue } if err := k.capBoundingSetDrop(i); err != nil { - k.fatalf("cannot drop capability from bounding set: %v", err) + k.fatalf(msg, "cannot drop capability from bounding set: %v", err) } } @@ -292,29 +297,29 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN) if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil { - k.fatalf("cannot raise CAP_SYS_ADMIN: %v", err) + k.fatalf(msg, "cannot raise CAP_SYS_ADMIN: %v", err) } } if err := k.capset( &capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}}, ); err != nil { - k.fatalf("cannot capset: %v", err) + k.fatalf(msg, "cannot capset: %v", err) } if !params.SeccompDisable { rules := params.SeccompRules if len(rules) == 0 { // non-empty rules slice always overrides presets - k.verbosef("resolving presets %#x", params.SeccompPresets) + msg.Verbosef("resolving presets %#x", params.SeccompPresets) rules = seccomp.Preset(params.SeccompPresets, params.SeccompFlags) } if err := k.seccompLoad(rules, params.SeccompFlags); err != nil { // this also indirectly asserts PR_SET_NO_NEW_PRIVS - k.fatalf("cannot load syscall filter: %v", err) + k.fatalf(msg, "cannot load syscall filter: %v", err) } - k.verbosef("%d filter rules loaded", len(rules)) + msg.Verbosef("%d filter rules loaded", len(rules)) } else { - k.verbose("syscall filter not configured") + msg.Verbose("syscall filter not configured") } extraFiles := make([]*os.File, params.Count) @@ -331,14 +336,14 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV cmd.ExtraFiles = extraFiles cmd.Dir = params.Dir.String() - k.verbosef("starting initial program %s", params.Path) + msg.Verbosef("starting initial program %s", params.Path) if err := k.start(cmd); err != nil { - k.fatalf("%v", err) + k.fatalf(msg, "%v", err) } - k.suspend() + msg.Suspend() if err := closeSetup(); err != nil { - k.printf("cannot close setup pipe: %v", err) + k.printf(msg, "cannot close setup pipe: %v", err) // not fatal } @@ -372,7 +377,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV } } if !errors.Is(err, ECHILD) { - k.printf("unexpected wait4 response: %v", err) + k.printf(msg, "unexpected wait4 response: %v", err) } close(done) @@ -389,50 +394,50 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV for { select { case s := <-sig: - if k.resume() { - k.verbosef("%s after process start", s.String()) + if msg.Resume() { + msg.Verbosef("%s after process start", s.String()) } else { - k.verbosef("got %s", s.String()) + msg.Verbosef("got %s", s.String()) } if s == CancelSignal && params.ForwardCancel && cmd.Process != nil { - k.verbose("forwarding context cancellation") + msg.Verbose("forwarding context cancellation") if err := k.signal(cmd, os.Interrupt); err != nil { - k.printf("cannot forward cancellation: %v", err) + k.printf(msg, "cannot forward cancellation: %v", err) } continue } - k.beforeExit() + msg.BeforeExit() k.exit(0) case w := <-info: if w.wpid == cmd.Process.Pid { // initial process exited, output is most likely available again - k.resume() + msg.Resume() switch { case w.wstatus.Exited(): r = w.wstatus.ExitStatus() - k.verbosef("initial process exited with code %d", w.wstatus.ExitStatus()) + msg.Verbosef("initial process exited with code %d", w.wstatus.ExitStatus()) case w.wstatus.Signaled(): r = 128 + int(w.wstatus.Signal()) - k.verbosef("initial process exited with signal %s", w.wstatus.Signal()) + msg.Verbosef("initial process exited with signal %s", w.wstatus.Signal()) default: r = 255 - k.verbosef("initial process exited with status %#x", w.wstatus) + msg.Verbosef("initial process exited with status %#x", w.wstatus) } go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }() } case <-done: - k.beforeExit() + msg.BeforeExit() k.exit(r) case <-timeout: - k.printf("timeout exceeded waiting for lingering processes") - k.beforeExit() + k.printf(msg, "timeout exceeded waiting for lingering processes") + msg.BeforeExit() k.exit(r) } } @@ -441,10 +446,16 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV const initName = "init" // TryArgv0 calls [Init] if the last element of argv0 is "init". -func TryArgv0(v Msg, prepare func(prefix string), setVerbose func(verbose bool)) { +// If a nil msg is passed, the system logger is used instead. +func TryArgv0(msg Msg) { + if msg == nil { + log.SetPrefix(initName + ": ") + log.SetFlags(0) + msg = NewMsg(log.Default()) + } + if len(os.Args) > 0 && path.Base(os.Args[0]) == initName { - msg = v - Init(prepare, setVerbose) + Init(msg) msg.BeforeExit() os.Exit(0) } diff --git a/container/init_test.go b/container/init_test.go index 7dd92c09..162afad7 100644 --- a/container/init_test.go +++ b/container/init_test.go @@ -11,25 +11,9 @@ import ( ) func TestInitEntrypoint(t *testing.T) { - assertPrefix := func(prefix string) { - if prefix != "init" { - t.Fatalf("prepareLogger: prefix = %q", prefix) - } - } - - assertVerboseFalse := func(verbose bool) { - if verbose { - t.Fatal("setVerbose: verbose = true, want false") - } - } - assertVerboseTrue := func(verbose bool) { - if !verbose { - t.Fatal("setVerbose: verbose = false, want true") - } - } checkSimple(t, "initEntrypoint", []simpleTestCase{ - {"getpid", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"getpid", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1<<10, nil), @@ -37,7 +21,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"receive bad fd", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"receive bad fd", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -47,7 +31,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"receive not set", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"receive not set", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -57,7 +41,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"receive payload decode", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"receive payload decode", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -67,7 +51,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"receive invalid params", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"receive invalid params", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -91,7 +75,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"setDumpable user", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"setDumpable user", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -112,13 +96,14 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(78), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, stub.UniqueError(77)), call("fatalf", stub.ExpectArgs{"cannot set SUID_DUMP_USER: %v", []any{stub.UniqueError(77)}}, nil, nil), }, }, nil}, - {"writeFile uid_map", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"writeFile uid_map", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -139,6 +124,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(76), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, stub.UniqueError(75)), @@ -146,7 +132,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"writeFile setgroups", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"writeFile setgroups", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -167,6 +153,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(74), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -175,7 +162,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"writeFile gid_map", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"writeFile gid_map", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -196,6 +183,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(72), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -205,7 +193,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"setDumpable disable", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"setDumpable disable", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -226,6 +214,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(70), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -236,7 +225,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"sethostname", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"sethostname", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -257,6 +246,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(68), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -269,7 +259,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"mount rslave root", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"mount rslave root", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -290,6 +280,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(66), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -304,7 +295,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"nil op", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"nil op", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -325,6 +316,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(64), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -341,7 +333,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"invalid op", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"invalid op", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -362,6 +354,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(63), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -378,7 +371,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"early unhandled error", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"early unhandled error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -399,6 +392,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(62), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -416,7 +410,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"early", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"early", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -437,6 +431,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(60), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -454,7 +449,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"mount ih", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"mount ih", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -475,6 +470,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(59), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -493,7 +489,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"chdir ih", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"chdir ih", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -514,6 +510,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(57), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -533,7 +530,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"mkdir sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"mkdir sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -554,6 +551,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(55), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -574,7 +572,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"mount bind sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"mount bind sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -595,6 +593,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(53), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -616,7 +615,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"mkdir host", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"mkdir host", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -637,6 +636,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(51), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -659,7 +659,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"pivotRoot ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"pivotRoot ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -680,6 +680,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(49), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -703,7 +704,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"chdir ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"chdir ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -724,6 +725,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(47), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -748,7 +750,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"apply unhandled error", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"apply unhandled error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -769,6 +771,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(45), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -802,7 +805,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"apply", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"apply", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -823,6 +826,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(43), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -856,7 +860,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"mount rprivate host", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"mount rprivate host", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -877,6 +881,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(42), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -911,7 +916,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"unmount host", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"unmount host", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -932,6 +937,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(40), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -967,7 +973,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"open ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"open ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -988,6 +994,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(38), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1025,7 +1032,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"chdir sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"chdir sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1046,6 +1053,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(36), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1084,7 +1092,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"pivotRoot sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"pivotRoot sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1105,6 +1113,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(34), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1144,7 +1153,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"fchdir ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"fchdir ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1165,6 +1174,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(32), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1205,7 +1215,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"unmount ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"unmount ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1226,6 +1236,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(30), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1267,7 +1278,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"chdir ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"chdir ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1288,6 +1299,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(28), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1330,7 +1342,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"close ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"close ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1351,6 +1363,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(26), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1394,7 +1407,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"capAmbientClearAll", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"capAmbientClearAll", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1415,6 +1428,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(24), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1459,7 +1473,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"capBoundingSetDrop", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"capBoundingSetDrop", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1480,6 +1494,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(22), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1532,7 +1547,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"capAmbientRaise", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"capAmbientRaise", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1553,6 +1568,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(20), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1638,7 +1654,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"capset", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"capset", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1659,6 +1675,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(18), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1745,7 +1762,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"seccompLoad", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"seccompLoad", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1766,6 +1783,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(16), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -1854,7 +1872,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"start", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{ + {"start", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("getpid", stub.ExpectArgs{}, 1, nil), @@ -1874,6 +1892,7 @@ func TestInitEntrypoint(t *testing.T) { SeccompDisable: true, ParentPerm: 0750, }, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(13), nil), + call("swapVerbose", stub.ExpectArgs{false}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil), @@ -1966,7 +1985,7 @@ func TestInitEntrypoint(t *testing.T) { }, }, nil}, - {"lowlastcap signaled cancel forward error", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{ + {"lowlastcap signaled cancel forward error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ /* entrypoint */ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), @@ -1987,6 +2006,7 @@ func TestInitEntrypoint(t *testing.T) { SeccompDisable: true, ParentPerm: 0750, }, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(10), nil), + call("swapVerbose", stub.ExpectArgs{false}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil), @@ -2039,7 +2059,7 @@ func TestInitEntrypoint(t *testing.T) { call("umask", stub.ExpectArgs{022}, 0, nil), call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil), call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil), - call("suspend", stub.ExpectArgs{}, nil, nil), + call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(10)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- CancelSignal }, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), @@ -2065,7 +2085,7 @@ func TestInitEntrypoint(t *testing.T) { }}}, }, nil}, - {"lowlastcap signaled cancel", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{ + {"lowlastcap signaled cancel", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ /* entrypoint */ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), @@ -2086,6 +2106,7 @@ func TestInitEntrypoint(t *testing.T) { SeccompDisable: true, ParentPerm: 0750, }, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(7), nil), + call("swapVerbose", stub.ExpectArgs{false}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil), @@ -2138,7 +2159,7 @@ func TestInitEntrypoint(t *testing.T) { call("umask", stub.ExpectArgs{022}, 0, nil), call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil), call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil), - call("suspend", stub.ExpectArgs{}, nil, nil), + call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), @@ -2155,7 +2176,7 @@ func TestInitEntrypoint(t *testing.T) { }}}, }, nil}, - {"lowlastcap signaled timeout", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{ + {"lowlastcap signaled timeout", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ /* entrypoint */ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), @@ -2176,6 +2197,7 @@ func TestInitEntrypoint(t *testing.T) { SeccompDisable: true, ParentPerm: 0750, }, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(5), nil), + call("swapVerbose", stub.ExpectArgs{false}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil), @@ -2228,7 +2250,7 @@ func TestInitEntrypoint(t *testing.T) { call("umask", stub.ExpectArgs{022}, 0, nil), call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil), call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil), - call("suspend", stub.ExpectArgs{}, nil, nil), + call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(5)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), @@ -2247,7 +2269,7 @@ func TestInitEntrypoint(t *testing.T) { }}}, }, nil}, - {"lowlastcap signaled", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{ + {"lowlastcap signaled", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ /* entrypoint */ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), @@ -2268,6 +2290,7 @@ func TestInitEntrypoint(t *testing.T) { SeccompDisable: true, ParentPerm: 0750, }, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(3), nil), + call("swapVerbose", stub.ExpectArgs{false}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil), @@ -2320,7 +2343,7 @@ func TestInitEntrypoint(t *testing.T) { call("umask", stub.ExpectArgs{022}, 0, nil), call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil), call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil), - call("suspend", stub.ExpectArgs{}, nil, nil), + call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(3)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), @@ -2346,7 +2369,7 @@ func TestInitEntrypoint(t *testing.T) { }}}, }, nil}, - {"strangewait nopriv notty noseccomp yamafault", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{ + {"strangewait nopriv notty noseccomp yamafault", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ /* entrypoint */ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), @@ -2367,6 +2390,7 @@ func TestInitEntrypoint(t *testing.T) { SeccompDisable: true, ParentPerm: 0750, }, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(1), nil), + call("swapVerbose", stub.ExpectArgs{false}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil), @@ -2455,7 +2479,7 @@ func TestInitEntrypoint(t *testing.T) { call("umask", stub.ExpectArgs{022}, 0, nil), call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil), call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil), - call("suspend", stub.ExpectArgs{}, nil, nil), + call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(1)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), @@ -2481,7 +2505,7 @@ func TestInitEntrypoint(t *testing.T) { }}}, }, nil}, - {"success", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{ + {"success", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ /* entrypoint */ Calls: []stub.Call{ call("lockOSThread", stub.ExpectArgs{}, nil, nil), @@ -2503,6 +2527,7 @@ func TestInitEntrypoint(t *testing.T) { RetainSession: true, Privileged: true, }, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(0), nil), + call("swapVerbose", stub.ExpectArgs{true}, false, nil), call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil), @@ -2594,7 +2619,7 @@ func TestInitEntrypoint(t *testing.T) { call("umask", stub.ExpectArgs{022}, 0, nil), call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/bin/zsh")}}, nil, nil), call("start", stub.ExpectArgs{"/bin/zsh", []string{"zsh", "-c", "exec vim"}, []string{"DISPLAY=:0"}, "/.hakurei"}, &os.Process{Pid: 0xcafe}, nil), - call("suspend", stub.ExpectArgs{}, nil, nil), + call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(0)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), diff --git a/container/initbind.go b/container/initbind.go index ad8bfaf5..43c8310d 100644 --- a/container/initbind.go +++ b/container/initbind.go @@ -59,7 +59,7 @@ func (b *BindMountOp) early(_ *setupState, k syscallDispatcher) error { } } -func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error { +func (b *BindMountOp) apply(state *setupState, k syscallDispatcher) error { if b.sourceFinal == nil { if b.Flags&BindOptional == 0 { // unreachable @@ -92,11 +92,11 @@ func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error { } if b.sourceFinal.String() == b.Target.String() { - k.verbosef("mounting %q flags %#x", target, flags) + state.Verbosef("mounting %q flags %#x", target, flags) } else { - k.verbosef("mounting %q on %q flags %#x", source, target, flags) + state.Verbosef("mounting %q on %q flags %#x", source, target, flags) } - return k.bindMount(source, target, flags) + return k.bindMount(state, source, target, flags) } func (b *BindMountOp) Is(op Op) bool { diff --git a/container/initdev.go b/container/initdev.go index cfb4b8b7..335cac06 100644 --- a/container/initdev.go +++ b/container/initdev.go @@ -46,6 +46,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error { return err } if err := k.bindMount( + state, toHost(FHSDev+name), targetPath, 0, @@ -93,6 +94,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error { if name, err := k.readlink(hostProc.stdout()); err != nil { return err } else if err = k.bindMount( + state, toHost(name), consolePath, 0, @@ -116,7 +118,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error { return nil } - if err := k.remount(target, MS_RDONLY); err != nil { + if err := k.remount(state, target, MS_RDONLY); err != nil { return err } return k.mountTmpfs(SourceTmpfs, devShmPath, MS_NOSUID|MS_NODEV, 0, 01777) diff --git a/container/initplace.go b/container/initplace.go index 5775ad7f..5975bba2 100644 --- a/container/initplace.go +++ b/container/initplace.go @@ -52,6 +52,7 @@ func (t *TmpfileOp) apply(state *setupState, k syscallDispatcher) error { if err := k.ensureFile(target, 0444, state.ParentPerm); err != nil { return err } else if err = k.bindMount( + state, tmpPath, target, syscall.MS_RDONLY|syscall.MS_NODEV, diff --git a/container/initremount.go b/container/initremount.go index 09f141db..b89ed543 100644 --- a/container/initremount.go +++ b/container/initremount.go @@ -21,8 +21,8 @@ type RemountOp struct { func (r *RemountOp) Valid() bool { return r != nil && r.Target != nil } func (*RemountOp) early(*setupState, syscallDispatcher) error { return nil } -func (r *RemountOp) apply(_ *setupState, k syscallDispatcher) error { - return k.remount(toSysroot(r.Target.String()), r.Flags) +func (r *RemountOp) apply(state *setupState, k syscallDispatcher) error { + return k.remount(state, toSysroot(r.Target.String()), r.Flags) } func (r *RemountOp) Is(op Op) bool { diff --git a/container/mount.go b/container/mount.go index c5a82aaa..dbd39cb9 100644 --- a/container/mount.go +++ b/container/mount.go @@ -96,18 +96,17 @@ const ( ) // bindMount mounts source on target and recursively applies flags if MS_REC is set. -func (p *procPaths) bindMount(source, target string, flags uintptr) error { +func (p *procPaths) bindMount(msg Msg, source, target string, flags uintptr) error { // 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 { return err } - - return p.k.remount(target, flags) + return p.k.remount(msg, target, flags) } // remount applies flags on target, recursively if MS_REC is set. -func (p *procPaths) remount(target string, flags uintptr) error { +func (p *procPaths) remount(msg Msg, target string, flags uintptr) error { // syscallDispatcher methods bindMount, remount must not be called from this function var targetFinal string @@ -116,7 +115,7 @@ func (p *procPaths) remount(target string, flags uintptr) error { } else { targetFinal = v if targetFinal != target { - p.k.verbosef("target resolves to %q", targetFinal) + msg.Verbosef("target resolves to %q", targetFinal) } } @@ -146,7 +145,7 @@ func (p *procPaths) remount(target string, flags uintptr) error { return err } - if err = remountWithFlags(p.k, n, mf); err != nil { + if err = remountWithFlags(p.k, msg, n, mf); err != nil { return err } if flags&MS_REC == 0 { @@ -159,7 +158,7 @@ func (p *procPaths) remount(target string, flags uintptr) error { continue } - if err = remountWithFlags(p.k, cur, mf); err != nil && !errors.Is(err, EACCES) { + if err = remountWithFlags(p.k, msg, cur, mf); err != nil && !errors.Is(err, EACCES) { return err } } @@ -169,12 +168,12 @@ func (p *procPaths) remount(target string, flags uintptr) error { } // remountWithFlags remounts mount point described by [vfs.MountInfoNode]. -func remountWithFlags(k syscallDispatcher, n *vfs.MountInfoNode, mf uintptr) error { +func remountWithFlags(k syscallDispatcher, msg Msg, n *vfs.MountInfoNode, mf uintptr) error { // syscallDispatcher methods bindMount, remount must not be called from this function kf, unmatched := n.Flags() if len(unmatched) != 0 { - k.verbosef("unmatched vfs options: %q", unmatched) + msg.Verbosef("unmatched vfs options: %q", unmatched) } if kf&mf != mf { diff --git a/container/mount_test.go b/container/mount_test.go index 5d274e26..5391623c 100644 --- a/container/mount_test.go +++ b/container/mount_test.go @@ -11,21 +11,21 @@ import ( func TestBindMount(t *testing.T) { checkSimple(t, "bindMount", []simpleTestCase{ - {"mount", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY) + {"mount", func(k *kstub) error { + return newProcPaths(k, hostPath).bindMount(nil, "/host/nix", "/sysroot/nix", syscall.MS_RDONLY) }, stub.Expect{Calls: []stub.Call{ call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, stub.UniqueError(0xbad)), }}, stub.UniqueError(0xbad)}, - {"success ne", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY) + {"success ne", func(k *kstub) error { + return newProcPaths(k, hostPath).bindMount(k, "/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY) }, stub.Expect{Calls: []stub.Call{ call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/.host-nix", "", uintptr(0x9000), ""}, nil, nil), call("remount", stub.ExpectArgs{"/sysroot/.host-nix", uintptr(1)}, nil, nil), }}, nil}, - {"success", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY) + {"success", func(k *kstub) error { + return newProcPaths(k, hostPath).bindMount(k, "/host/nix", "/sysroot/nix", syscall.MS_RDONLY) }, stub.Expect{Calls: []stub.Call{ call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, nil), call("remount", stub.ExpectArgs{"/sysroot/nix", uintptr(1)}, nil, nil), @@ -77,29 +77,29 @@ func TestRemount(t *testing.T) { 416 415 0:30 / /sysroot/nix/store ro,relatime master:5 - overlay overlay rw,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work` checkSimple(t, "remount", []simpleTestCase{ - {"evalSymlinks", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"evalSymlinks", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", stub.UniqueError(6)), }}, stub.UniqueError(6)}, - {"open", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"open", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, stub.UniqueError(5)), }}, &os.PathError{Op: "open", Path: "/sysroot/nix", Err: stub.UniqueError(5)}}, - {"readlink", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"readlink", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil), call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", stub.UniqueError(4)), }}, stub.UniqueError(4)}, - {"close", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"close", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil), @@ -107,8 +107,8 @@ func TestRemount(t *testing.T) { call("close", stub.ExpectArgs{0xdeadbeef}, nil, stub.UniqueError(3)), }}, &os.PathError{Op: "close", Path: "/sysroot/nix", Err: stub.UniqueError(3)}}, - {"mountinfo no match", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"mountinfo no match", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(k, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/.hakurei", nil), call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/.hakurei"}}, nil, nil), @@ -118,8 +118,8 @@ func TestRemount(t *testing.T) { call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil), }}, &vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/sysroot/.hakurei")}}, - {"mountinfo", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"mountinfo", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil), @@ -128,8 +128,8 @@ func TestRemount(t *testing.T) { call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile("\x00"), nil), }}, &vfs.DecoderError{Op: "parse", Line: 0, Err: vfs.ErrMountInfoFields}}, - {"mount", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"mount", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil), @@ -139,8 +139,8 @@ func TestRemount(t *testing.T) { call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, stub.UniqueError(2)), }}, stub.UniqueError(2)}, - {"mount propagate", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"mount propagate", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil), @@ -151,8 +151,8 @@ func TestRemount(t *testing.T) { call("mount", stub.ExpectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, stub.UniqueError(1)), }}, stub.UniqueError(1)}, - {"success toplevel", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/bin", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"success toplevel", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/bin", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/bin"}, "/sysroot/bin", nil), call("open", stub.ExpectArgs{"/sysroot/bin", 0x280000, uint32(0)}, 0xbabe, nil), @@ -162,8 +162,8 @@ func TestRemount(t *testing.T) { call("mount", stub.ExpectArgs{"none", "/sysroot/bin", "", uintptr(0x209027), ""}, nil, nil), }}, nil}, - {"success EACCES", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"success EACCES", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil), @@ -175,8 +175,8 @@ func TestRemount(t *testing.T) { call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil), }}, nil}, - {"success no propagate", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_RDONLY|syscall.MS_NODEV) + {"success no propagate", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil), @@ -186,8 +186,8 @@ func TestRemount(t *testing.T) { call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil), }}, nil}, - {"success case sensitive", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"success case sensitive", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, nil), @@ -199,8 +199,8 @@ func TestRemount(t *testing.T) { call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil), }}, nil}, - {"success", func(k syscallDispatcher) error { - return newProcPaths(k, hostPath).remount("/sysroot/.nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) + {"success", func(k *kstub) error { + return newProcPaths(k, hostPath).remount(k, "/sysroot/.nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) }, stub.Expect{Calls: []stub.Call{ call("evalSymlinks", stub.ExpectArgs{"/sysroot/.nix"}, "/sysroot/NIX", nil), call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/NIX"}}, nil, nil), @@ -217,18 +217,18 @@ func TestRemount(t *testing.T) { func TestRemountWithFlags(t *testing.T) { checkSimple(t, "remountWithFlags", []simpleTestCase{ - {"noop unmatched", func(k syscallDispatcher) error { - return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime,cat"}}, 0) + {"noop unmatched", func(k *kstub) error { + return remountWithFlags(k, k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime,cat"}}, 0) }, stub.Expect{Calls: []stub.Call{ call("verbosef", stub.ExpectArgs{"unmatched vfs options: %q", []any{[]string{"cat"}}}, nil, nil), }}, nil}, - {"noop", func(k syscallDispatcher) error { - return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, 0) + {"noop", func(k *kstub) error { + return remountWithFlags(k, nil, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, 0) }, stub.Expect{}, nil}, - {"success", func(k syscallDispatcher) error { - return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, syscall.MS_RDONLY) + {"success", func(k *kstub) error { + return remountWithFlags(k, nil, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, syscall.MS_RDONLY) }, stub.Expect{Calls: []stub.Call{ call("mount", stub.ExpectArgs{"none", "", "", uintptr(0x209021), ""}, nil, nil), }}, nil}, @@ -237,20 +237,20 @@ func TestRemountWithFlags(t *testing.T) { func TestMountTmpfs(t *testing.T) { checkSimple(t, "mountTmpfs", []simpleTestCase{ - {"mkdirAll", func(k syscallDispatcher) error { + {"mkdirAll", func(k *kstub) error { return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 1<<10, 0700) }, stub.Expect{Calls: []stub.Call{ call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, stub.UniqueError(0)), }}, stub.UniqueError(0)}, - {"success no size", func(k syscallDispatcher) error { + {"success no size", func(k *kstub) error { return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 0, 0710) }, stub.Expect{Calls: []stub.Call{ call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0750)}, nil, nil), call("mount", stub.ExpectArgs{"ephemeral", "/sysroot/run/user/1000", "tmpfs", uintptr(0), "mode=0710"}, nil, nil), }}, nil}, - {"success", func(k syscallDispatcher) error { + {"success", func(k *kstub) error { return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 1<<10, 0700) }, stub.Expect{Calls: []stub.Call{ call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, nil), diff --git a/container/msg.go b/container/msg.go index 1512a583..38c38ecd 100644 --- a/container/msg.go +++ b/container/msg.go @@ -23,45 +23,88 @@ func GetErrorMessage(err error) (string, bool) { return e.Message(), true } +// Msg is used for package-wide verbose logging. type Msg interface { + // GetLogger returns the address of the underlying [log.Logger]. + GetLogger() *log.Logger + + // IsVerbose atomically loads and returns whether [Msg] has verbose logging enabled. IsVerbose() bool + // SwapVerbose atomically stores a new verbose state and returns the previous value held by [Msg]. + SwapVerbose(verbose bool) bool + // Verbose passes its argument to the Println method of the underlying [log.Logger] if IsVerbose returns true. Verbose(v ...any) + // Verbosef passes its argument to the Printf method of the underlying [log.Logger] if IsVerbose returns true. Verbosef(format string, v ...any) - Suspend() + // Suspend causes the embedded [Suspendable] to withhold writes to its downstream [io.Writer]. + // Suspend returns false and is a noop if called between calls to Suspend and Resume. + Suspend() bool + + // Resume dumps the entire buffer held by the embedded [Suspendable] and stops withholding future writes. + // Resume returns false and is a noop if a call to Suspend does not precede it. Resume() bool + + // BeforeExit runs implementation-specific cleanup code, and optionally prints warnings. + // BeforeExit is called before [os.Exit]. BeforeExit() } -type DefaultMsg struct{ inactive atomic.Bool } +// defaultMsg is the default implementation of the [Msg] interface. +// The zero value is not safe for use. Callers should use the [NewMsg] function instead. +type defaultMsg struct { + verbose atomic.Bool -func (msg *DefaultMsg) IsVerbose() bool { return true } -func (msg *DefaultMsg) Verbose(v ...any) { - if !msg.inactive.Load() { - log.Println(v...) - } + logger *log.Logger + Suspendable } -func (msg *DefaultMsg) Verbosef(format string, v ...any) { - if !msg.inactive.Load() { - log.Printf(format, v...) + +// NewMsg initialises a downstream [log.Logger] for a new [Msg]. +// The [log.Logger] should no longer be configured after NewMsg returns. +// If downstream is nil, a new logger is initialised in its place. +func NewMsg(downstream *log.Logger) Msg { + if downstream == nil { + downstream = log.New(log.Writer(), "container: ", 0) } + + m := defaultMsg{logger: downstream} + m.Suspendable.Downstream = downstream.Writer() + downstream.SetOutput(&m.Suspendable) + return &m } -func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) } -func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) } -func (msg *DefaultMsg) BeforeExit() {} +func (msg *defaultMsg) GetLogger() *log.Logger { return msg.logger } -// msg is the [Msg] implemented used by all exported [container] functions. -var msg Msg = new(DefaultMsg) +func (msg *defaultMsg) IsVerbose() bool { return msg.verbose.Load() } +func (msg *defaultMsg) SwapVerbose(verbose bool) bool { return msg.verbose.Swap(verbose) } +func (msg *defaultMsg) Verbose(v ...any) { + if msg.verbose.Load() { + msg.logger.Println(v...) + } +} +func (msg *defaultMsg) Verbosef(format string, v ...any) { + if msg.verbose.Load() { + msg.logger.Printf(format, v...) + } +} -// GetOutput returns the current active [Msg] implementation. -func GetOutput() Msg { return msg } +// Resume calls [Suspendable.Resume] and prints a message if buffer was filled +// between calls to [Suspendable.Suspend] and Resume. +func (msg *defaultMsg) Resume() bool { + resumed, dropped, _, err := msg.Suspendable.Resume() + if err != nil { + // probably going to result in an error as well, so this message is as good as unreachable + msg.logger.Printf("cannot dump buffer on resume: %v", err) + } + if resumed && dropped > 0 { + msg.logger.Printf("dropped %d bytes while output is suspended", dropped) + } + return resumed +} -// SetOutput replaces the current active [Msg] implementation. -func SetOutput(v Msg) { - if v == nil { - msg = new(DefaultMsg) - } else { - msg = v +// BeforeExit prints a message if called between calls to [Suspendable.Suspend] and Resume. +func (msg *defaultMsg) BeforeExit() { + if msg.Resume() { + msg.logger.Printf("beforeExit reached on suspended output") } } diff --git a/container/msg_test.go b/container/msg_test.go index 1b36a1dd..ed5c0c59 100644 --- a/container/msg_test.go +++ b/container/msg_test.go @@ -1,14 +1,16 @@ package container_test import ( + "bytes" "errors" + "io" "log" "strings" - "sync/atomic" "syscall" "testing" "hakurei.app/container" + "hakurei.app/container/stub" ) func TestMessageError(t *testing.T) { @@ -39,146 +41,137 @@ func TestMessageError(t *testing.T) { } func TestDefaultMsg(t *testing.T) { - { - w := log.Writer() - f := log.Flags() - t.Cleanup(func() { log.SetOutput(w); log.SetFlags(f) }) - } - msg := new(container.DefaultMsg) + // copied from output.go + const suspendBufMax = 1 << 24 - t.Run("is verbose", func(t *testing.T) { - if !msg.IsVerbose() { - t.Error("IsVerbose unexpected outcome") - } - }) + t.Run("logger", func(t *testing.T) { + t.Run("nil", func(t *testing.T) { + got := container.NewMsg(nil).GetLogger() - t.Run("verbose", func(t *testing.T) { - log.SetOutput(panicWriter{}) - msg.Suspend() - msg.Verbose() - msg.Verbosef("\x00") - msg.Resume() + if out := got.Writer().(*container.Suspendable).Downstream; out != log.Writer() { + t.Errorf("GetLogger: Downstream = %#v", out) + } - buf := new(strings.Builder) - log.SetOutput(buf) - log.SetFlags(0) - msg.Verbose() - msg.Verbosef("\x00") + if prefix := got.Prefix(); prefix != "container: " { + t.Errorf("GetLogger: prefix = %q", prefix) + } + }) - want := "\n\x00\n" - if buf.String() != want { - t.Errorf("Verbose: %q, want %q", buf.String(), want) - } - }) + t.Run("takeover", func(t *testing.T) { + l := log.New(io.Discard, "\x00", 0xdeadbeef) + got := container.NewMsg(l) - t.Run("inactive", func(t *testing.T) { - { - inactive := msg.Resume() - if inactive { - t.Cleanup(func() { msg.Suspend() }) + if logger := got.GetLogger(); logger != l { + t.Errorf("GetLogger: %#v, want %#v", logger, l) } - } - - if msg.Resume() { - t.Error("Resume unexpected outcome") - } - msg.Suspend() - if !msg.Resume() { - t.Error("Resume unexpected outcome") - } + if ds := l.Writer().(*container.Suspendable).Downstream; ds != io.Discard { + t.Errorf("GetLogger: Downstream = %#v", ds) + } + }) }) - // the function is a noop - t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() }) -} - -type panicWriter struct{} - -func (panicWriter) Write([]byte) (int, error) { panic("unreachable") } - -func saveRestoreOutput(t *testing.T) { - out := container.GetOutput() - t.Cleanup(func() { container.SetOutput(out) }) -} - -func replaceOutput(t *testing.T) { - saveRestoreOutput(t) - container.SetOutput(&testOutput{t: t}) -} + dw := expectWriter{t: t} -type testOutput struct { - t *testing.T - suspended atomic.Bool -} + steps := []struct { + name string + pt, next []byte + err error -func (out *testOutput) IsVerbose() bool { return testing.Verbose() } + f func(t *testing.T, msg container.Msg) + }{ + {"zero verbose", nil, nil, nil, func(t *testing.T, msg container.Msg) { + if msg.IsVerbose() { + t.Error("IsVerbose unexpected true") + } + }}, -func (out *testOutput) Verbose(v ...any) { - if !out.IsVerbose() { - return - } - out.t.Log(v...) -} + {"swap false", nil, nil, nil, func(t *testing.T, msg container.Msg) { + if msg.SwapVerbose(false) { + t.Error("SwapVerbose unexpected true") + } + }}, + {"write discard", nil, nil, nil, func(_ *testing.T, msg container.Msg) { + msg.Verbose("\x00") + msg.Verbosef("\x00") + }}, + {"verbose false", nil, nil, nil, func(t *testing.T, msg container.Msg) { + if msg.IsVerbose() { + t.Error("IsVerbose unexpected true") + } + }}, -func (out *testOutput) Verbosef(format string, v ...any) { - if !out.IsVerbose() { - return - } - out.t.Logf(format, v...) -} + {"swap true", nil, nil, nil, func(t *testing.T, msg container.Msg) { + if msg.SwapVerbose(true) { + t.Error("SwapVerbose unexpected true") + } + }}, + {"write verbose", []byte("test: \x00\n"), nil, nil, func(_ *testing.T, msg container.Msg) { + msg.Verbose("\x00") + }}, + {"write verbosef", []byte(`test: "\x00"` + "\n"), nil, nil, func(_ *testing.T, msg container.Msg) { + msg.Verbosef("%q", "\x00") + }}, + {"verbose true", nil, nil, nil, func(t *testing.T, msg container.Msg) { + if !msg.IsVerbose() { + t.Error("IsVerbose unexpected false") + } + }}, -func (out *testOutput) Suspend() { - if out.suspended.CompareAndSwap(false, true) { - out.Verbose("suspend called") - return - } - out.Verbose("suspend called on suspended output") -} + {"resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) { + if msg.Resume() { + t.Error("Resume unexpected success") + } + }}, + {"beforeExit noop", nil, nil, nil, func(_ *testing.T, msg container.Msg) { + msg.BeforeExit() + }}, -func (out *testOutput) Resume() bool { - if out.suspended.CompareAndSwap(true, false) { - out.Verbose("resume called") - return true - } - out.Verbose("resume called on unsuspended output") - return false -} + {"beforeExit suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) { + msg.Suspend() + }}, + {"beforeExit message", []byte("test: beforeExit reached on suspended output\n"), nil, nil, func(_ *testing.T, msg container.Msg) { + msg.BeforeExit() + }}, + {"post beforeExit resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) { + if msg.Resume() { + t.Error("Resume unexpected success") + } + }}, -func (out *testOutput) BeforeExit() { out.Verbose("beforeExit called") } + {"suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) { + msg.Suspend() + }}, + {"suspend write", nil, nil, nil, func(_ *testing.T, msg container.Msg) { + 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) { + if !msg.Resume() { + t.Error("Resume unexpected failure") + } + }}, -func TestGetSetOutput(t *testing.T) { - { - out := container.GetOutput() - t.Cleanup(func() { container.SetOutput(out) }) + {"suspend drop", nil, nil, nil, func(_ *testing.T, msg container.Msg) { + msg.Suspend() + }}, + {"suspend write fill", nil, nil, nil, func(_ *testing.T, msg container.Msg) { + 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) { + if !msg.Resume() { + t.Error("Resume unexpected failure") + } + }}, } - t.Run("default", func(t *testing.T) { - container.SetOutput(new(stubOutput)) - if v, ok := container.GetOutput().(*container.DefaultMsg); ok { - t.Fatalf("SetOutput: got unexpected output %#v", v) - } - container.SetOutput(nil) - if _, ok := container.GetOutput().(*container.DefaultMsg); !ok { - t.Fatalf("SetOutput: got unexpected output %#v", container.GetOutput()) - } - }) - - t.Run("stub", func(t *testing.T) { - container.SetOutput(new(stubOutput)) - if _, ok := container.GetOutput().(*stubOutput); !ok { - t.Fatalf("SetOutput: got unexpected output %#v", container.GetOutput()) + msg := container.NewMsg(log.New(&dw, "test: ", 0)) + for _, step := range steps { + // these share the same writer, so cannot be subtests + t.Logf("running step %q", step.name) + dw.expect, dw.next, dw.err = step.pt, step.next, step.err + step.f(t, msg) + if dw.expect != nil { + t.Errorf("expect: %q", string(dw.expect)) } - }) -} - -type stubOutput struct { - wrapF func(error, ...any) error + } } - -func (*stubOutput) IsVerbose() bool { panic("unreachable") } -func (*stubOutput) Verbose(...any) { panic("unreachable") } -func (*stubOutput) Verbosef(string, ...any) { panic("unreachable") } -func (*stubOutput) Suspend() { panic("unreachable") } -func (*stubOutput) Resume() bool { panic("unreachable") } -func (*stubOutput) BeforeExit() { panic("unreachable") } diff --git a/container/output_test.go b/container/output_test.go index cfa8e0de..b6dedf16 100644 --- a/container/output_test.go +++ b/container/output_test.go @@ -28,7 +28,7 @@ func TestSuspendable(t *testing.T) { ) // shares the same writer - testCases := []struct { + steps := []struct { name string w, pt []byte err error @@ -75,25 +75,25 @@ func TestSuspendable(t *testing.T) { var dw expectWriter w := container.Suspendable{Downstream: &dw} - for _, tc := range testCases { + for _, step := range steps { // these share the same writer, so cannot be subtests - t.Logf("writing step %q", tc.name) - dw.expect, dw.err = tc.pt, tc.err + t.Logf("writing step %q", step.name) + dw.expect, dw.err = step.pt, step.err var ( gotN int gotErr error ) - wantN := tc.n + wantN := step.n switch wantN { case nSpecialPtEquiv: - wantN = len(tc.pt) - gotN, gotErr = w.Write(tc.w) + wantN = len(step.pt) + gotN, gotErr = w.Write(step.w) case nSpecialWEquiv: - wantN = len(tc.w) - gotN, gotErr = w.Write(tc.w) + wantN = len(step.w) + gotN, gotErr = w.Write(step.w) case nSpecialSuspend: s := w.IsSuspended() @@ -101,8 +101,8 @@ func TestSuspendable(t *testing.T) { t.Fatal("Suspend: unexpected success") } - wantN = len(tc.w) - gotN, gotErr = w.Write(tc.w) + wantN = len(step.w) + gotN, gotErr = w.Write(step.w) default: if wantN <= nSpecialDump { @@ -118,10 +118,10 @@ func TestSuspendable(t *testing.T) { t.Errorf("Resume: dropped = %d, want %d", dropped, wantDropped) } - wantN = len(tc.pt) + wantN = len(step.pt) gotN, gotErr = int(n), err } else { - gotN, gotErr = w.Write(tc.w) + gotN, gotErr = w.Write(step.w) } } @@ -129,9 +129,13 @@ func TestSuspendable(t *testing.T) { t.Errorf("Write: n = %d, want %d", gotN, wantN) } - if !reflect.DeepEqual(gotErr, tc.wantErr) { + if !reflect.DeepEqual(gotErr, step.wantErr) { t.Errorf("Write: %v", gotErr) } + + if dw.expect != nil { + t.Errorf("expect: %q", string(dw.expect)) + } } } @@ -139,17 +143,31 @@ func TestSuspendable(t *testing.T) { type expectWriter struct { expect []byte err error + + // optional consecutive write + next []byte + + // optional, calls Error on failure if not nil + t *testing.T } func (w *expectWriter) Write(p []byte) (n int, err error) { - defer func() { w.expect = nil }() + defer func() { w.expect = w.next; w.next = nil }() n, err = len(p), w.err if w.expect == nil { - return 0, errors.New("unexpected call to Write: " + strconv.Quote(string(p))) + n, err = 0, errors.New("unexpected call to Write: "+strconv.Quote(string(p))) + if w.t != nil { + w.t.Error(err.Error()) + } + return } if string(p) != string(w.expect) { - return 0, errors.New("p = " + strconv.Quote(string(p)) + ", want " + strconv.Quote(string(w.expect))) + n, err = 0, errors.New("p = "+strconv.Quote(string(p))+", want "+strconv.Quote(string(w.expect))) + if w.t != nil { + w.t.Error(err.Error()) + } + return } return } diff --git a/container/sysctl.go b/container/sysctl.go index bfcf2788..327872b5 100644 --- a/container/sysctl.go +++ b/container/sysctl.go @@ -2,7 +2,6 @@ package container import ( "bytes" - "log" "os" "strconv" "sync" @@ -22,26 +21,28 @@ const ( kernelCapLastCapPath = FHSProcSys + "kernel/cap_last_cap" ) -func mustReadSysctl() { - if v, err := os.ReadFile(kernelOverflowuidPath); err != nil { - log.Fatalf("cannot read %q: %v", kernelOverflowuidPath, err) - } else if kernelOverflowuid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { - log.Fatalf("cannot interpret %q: %v", kernelOverflowuidPath, err) - } +func mustReadSysctl(msg Msg) { + sysctlOnce.Do(func() { + if v, err := os.ReadFile(kernelOverflowuidPath); err != nil { + msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowuidPath, err) + } else if kernelOverflowuid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { + msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelOverflowuidPath, err) + } - if v, err := os.ReadFile(kernelOverflowgidPath); err != nil { - log.Fatalf("cannot read %q: %v", kernelOverflowgidPath, err) - } else if kernelOverflowgid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { - log.Fatalf("cannot interpret %q: %v", kernelOverflowgidPath, err) - } + if v, err := os.ReadFile(kernelOverflowgidPath); err != nil { + msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowgidPath, err) + } else if kernelOverflowgid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { + msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelOverflowgidPath, err) + } - if v, err := os.ReadFile(kernelCapLastCapPath); err != nil { - log.Fatalf("cannot read %q: %v", kernelCapLastCapPath, err) - } else if kernelCapLastCap, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { - log.Fatalf("cannot interpret %q: %v", kernelCapLastCapPath, err) - } + if v, err := os.ReadFile(kernelCapLastCapPath); err != nil { + msg.GetLogger().Fatalf("cannot read %q: %v", kernelCapLastCapPath, err) + } else if kernelCapLastCap, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { + msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelCapLastCapPath, err) + } + }) } -func OverflowUid() int { sysctlOnce.Do(mustReadSysctl); return kernelOverflowuid } -func OverflowGid() int { sysctlOnce.Do(mustReadSysctl); return kernelOverflowgid } -func LastCap() uintptr { sysctlOnce.Do(mustReadSysctl); return uintptr(kernelCapLastCap) } +func OverflowUid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowuid } +func OverflowGid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowgid } +func LastCap(msg Msg) uintptr { mustReadSysctl(msg); return uintptr(kernelCapLastCap) } |
