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

This frees all container instances of side effects.

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

View File

@ -17,17 +17,30 @@ import (
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/internal/app" "hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
"hakurei.app/system" "hakurei.app/system"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
) )
func buildCommand(ctx context.Context, out io.Writer) command.Command { func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
var ( var (
flagVerbose bool flagVerbose bool
flagJSON bool flagJSON bool
) )
c := command.New(out, log.Printf, "hakurei", func([]string) error { internal.InstallOutput(flagVerbose); return nil }). c := command.New(out, log.Printf, "hakurei", func([]string) error {
msg.SwapVerbose(flagVerbose)
if early.yamaLSM != nil {
msg.Verbosef("cannot enable ptrace protection via Yama LSM: %v", early.yamaLSM)
// not fatal
}
if early.dumpable != nil {
log.Printf("cannot set SUID_DUMP_DISABLE: %s", early.dumpable)
// not fatal
}
return nil
}).
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity"). Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable") Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
@ -39,10 +52,10 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
} }
// config extraArgs... // config extraArgs...
config := tryPath(args[0]) config := tryPath(msg, args[0])
config.Args = append(config.Args, args[1:]...) config.Args = append(config.Args, args[1:]...)
app.Main(ctx, config) app.Main(ctx, msg, config)
panic("unreachable") panic("unreachable")
}) })
@ -78,9 +91,9 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
passwd *user.User passwd *user.User
passwdOnce sync.Once passwdOnce sync.Once
passwdFunc = func() { passwdFunc = func() {
us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustID(), flagIdentity)) us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustIDMsg(msg), flagIdentity))
if u, err := user.LookupId(us); err != nil { if u, err := user.LookupId(us); err != nil {
hlog.Verbosef("cannot look up uid %s", us) msg.Verbosef("cannot look up uid %s", us)
passwd = &user.User{ passwd = &user.User{
Uid: us, Uid: us,
Gid: us, Gid: us,
@ -162,7 +175,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
} }
} }
app.Main(ctx, config) app.Main(ctx, msg, config)
panic("unreachable") panic("unreachable")
}). }).
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"), Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
@ -198,13 +211,13 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
c.NewCommand("show", "Show live or local app configuration", func(args []string) error { c.NewCommand("show", "Show live or local app configuration", func(args []string) error {
switch len(args) { switch len(args) {
case 0: // system case 0: // system
printShowSystem(os.Stdout, flagShort, flagJSON) printShowSystem(msg, os.Stdout, flagShort, flagJSON)
case 1: // instance case 1: // instance
name := args[0] name := args[0]
config, entry := tryShort(name) config, entry := tryShort(msg, name)
if config == nil { if config == nil {
config = tryPath(name) config = tryPath(msg, name)
} }
printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON) printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON)
@ -219,8 +232,8 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
var flagShort bool var flagShort bool
c.NewCommand("ps", "List active instances", func(args []string) error { c.NewCommand("ps", "List active instances", func(args []string) error {
var sc hst.Paths var sc hst.Paths
app.CopyPaths(&sc, new(app.Hsu).MustID()) app.CopyPaths(msg, &sc, new(app.Hsu).MustID())
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sc.RunDirPath.String()), flagShort, flagJSON) printPs(os.Stdout, time.Now().UTC(), state.NewMulti(msg, sc.RunDirPath.String()), flagShort, flagJSON)
return errSuccess return errSuccess
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id") }).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
} }

View File

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

View File

@ -13,8 +13,6 @@ import (
"syscall" "syscall"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
) )
var ( var (
@ -24,20 +22,20 @@ var (
license string license string
) )
func init() { hlog.Prepare("hakurei") } // earlyHardeningErrs are errors collected while setting up early hardening feature.
type earlyHardeningErrs struct{ yamaLSM, dumpable error }
func main() { func main() {
// early init path, skips root check and duplicate PR_SET_DUMPABLE // early init path, skips root check and duplicate PR_SET_DUMPABLE
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput) container.TryArgv0(nil)
if err := container.SetPtracer(0); err != nil { log.SetPrefix("hakurei: ")
hlog.Verbosef("cannot enable ptrace protection via Yama LSM: %v", err) log.SetFlags(0)
// not fatal: this program runs as the privileged user msg := container.NewMsg(log.Default())
}
if err := container.SetDumpable(container.SUID_DUMP_DISABLE); err != nil { early := earlyHardeningErrs{
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err) yamaLSM: container.SetPtracer(0),
// not fatal: this program runs as the privileged user dumpable: container.SetDumpable(container.SUID_DUMP_DISABLE),
} }
if os.Geteuid() == 0 { if os.Geteuid() == 0 {
@ -48,10 +46,10 @@ func main() {
syscall.SIGINT, syscall.SIGTERM) syscall.SIGINT, syscall.SIGTERM)
defer stop() // unreachable defer stop() // unreachable
buildCommand(ctx, os.Stderr).MustParse(os.Args[1:], func(err error) { buildCommand(ctx, msg, &early, os.Stderr).MustParse(os.Args[1:], func(err error) {
hlog.Verbosef("command returned %v", err) msg.Verbosef("command returned %v", err)
if errors.Is(err, errSuccess) { if errors.Is(err, errSuccess) {
hlog.BeforeExit() msg.BeforeExit()
os.Exit(0) os.Exit(0)
} }
// this catches faulty command handlers that fail to return before this point // this catches faulty command handlers that fail to return before this point

View File

@ -10,20 +10,20 @@ import (
"strings" "strings"
"syscall" "syscall"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app" "hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
) )
func tryPath(name string) (config *hst.Config) { func tryPath(msg container.Msg, name string) (config *hst.Config) {
var r io.Reader var r io.Reader
config = new(hst.Config) config = new(hst.Config)
if name != "-" { if name != "-" {
r = tryFd(name) r = tryFd(msg, name)
if r == nil { if r == nil {
hlog.Verbose("load configuration from file") msg.Verbose("load configuration from file")
if f, err := os.Open(name); err != nil { if f, err := os.Open(name); err != nil {
log.Fatalf("cannot access configuration file %q: %s", name, err) log.Fatalf("cannot access configuration file %q: %s", name, err)
@ -49,14 +49,14 @@ func tryPath(name string) (config *hst.Config) {
return return
} }
func tryFd(name string) io.ReadCloser { func tryFd(msg container.Msg, name string) io.ReadCloser {
if v, err := strconv.Atoi(name); err != nil { if v, err := strconv.Atoi(name); err != nil {
if !errors.Is(err, strconv.ErrSyntax) { if !errors.Is(err, strconv.ErrSyntax) {
hlog.Verbosef("name cannot be interpreted as int64: %v", err) msg.Verbosef("name cannot be interpreted as int64: %v", err)
} }
return nil return nil
} else { } else {
hlog.Verbosef("trying config stream from %d", v) msg.Verbosef("trying config stream from %d", v)
fd := uintptr(v) fd := uintptr(v)
if _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_GETFD, 0); errno != 0 { if _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_GETFD, 0); errno != 0 {
if errors.Is(errno, syscall.EBADF) { if errors.Is(errno, syscall.EBADF) {
@ -68,7 +68,7 @@ func tryFd(name string) io.ReadCloser {
} }
} }
func tryShort(name string) (config *hst.Config, entry *state.State) { func tryShort(msg container.Msg, name string) (config *hst.Config, entry *state.State) {
likePrefix := false likePrefix := false
if len(name) <= 32 { if len(name) <= 32 {
likePrefix = true likePrefix = true
@ -86,11 +86,11 @@ func tryShort(name string) (config *hst.Config, entry *state.State) {
// try to match from state store // try to match from state store
if likePrefix && len(name) >= 8 { if likePrefix && len(name) >= 8 {
hlog.Verbose("argument looks like prefix") msg.Verbose("argument looks like prefix")
var sc hst.Paths var sc hst.Paths
app.CopyPaths(&sc, new(app.Hsu).MustID()) app.CopyPaths(msg, &sc, new(app.Hsu).MustID())
s := state.NewMulti(sc.RunDirPath.String()) s := state.NewMulti(msg, sc.RunDirPath.String())
if entries, err := state.Join(s); err != nil { if entries, err := state.Join(s); err != nil {
log.Printf("cannot join store: %v", err) log.Printf("cannot join store: %v", err)
// drop to fetch from file // drop to fetch from file
@ -104,7 +104,7 @@ func tryShort(name string) (config *hst.Config, entry *state.State) {
break break
} }
hlog.Verbosef("instance %s skipped", v) msg.Verbosef("instance %s skipped", v)
} }
} }
} }

View File

@ -11,18 +11,19 @@ import (
"text/tabwriter" "text/tabwriter"
"time" "time"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app" "hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
) )
func printShowSystem(output io.Writer, short, flagJSON bool) { func printShowSystem(msg container.Msg, output io.Writer, short, flagJSON bool) {
t := newPrinter(output) t := newPrinter(output)
defer t.MustFlush() defer t.MustFlush()
info := &hst.Info{User: new(app.Hsu).MustID()} info := &hst.Info{User: new(app.Hsu).MustID()}
app.CopyPaths(&info.Paths, info.User) app.CopyPaths(msg, &info.Paths, info.User)
if flagJSON { if flagJSON {
printJSON(output, short, info) printJSON(output, short, info)

View File

@ -13,22 +13,21 @@ import (
"hakurei.app/command" "hakurei.app/command"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
) )
var ( var (
errSuccess = errors.New("success") errSuccess = errors.New("success")
) )
func init() { func main() {
hlog.Prepare("hpkg") log.SetPrefix("hpkg: ")
log.SetFlags(0)
msg := container.NewMsg(log.Default())
if err := os.Setenv("SHELL", pathShell.String()); err != nil { if err := os.Setenv("SHELL", pathShell.String()); err != nil {
log.Fatalf("cannot set $SHELL: %v", err) log.Fatalf("cannot set $SHELL: %v", err)
} }
}
func main() {
if os.Geteuid() == 0 { if os.Geteuid() == 0 {
log.Fatal("this program must not run as root") log.Fatal("this program must not run as root")
} }
@ -41,7 +40,7 @@ func main() {
flagVerbose bool flagVerbose bool
flagDropShell bool flagDropShell bool
) )
c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { internal.InstallOutput(flagVerbose); return nil }). c := command.New(os.Stderr, log.Printf, "hpkg", func([]string) error { msg.SwapVerbose(flagVerbose); return nil }).
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console"). Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action") Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
@ -90,12 +89,12 @@ func main() {
} }
cleanup := func() { cleanup := func() {
// should be faster than a native implementation // should be faster than a native implementation
mustRun(chmod, "-R", "+w", workDir.String()) mustRun(msg, chmod, "-R", "+w", workDir.String())
mustRun(rm, "-rf", workDir.String()) mustRun(msg, rm, "-rf", workDir.String())
} }
beforeRunFail.Store(&cleanup) beforeRunFail.Store(&cleanup)
mustRun(tar, "-C", workDir.String(), "-xf", pkgPath) mustRun(msg, tar, "-C", workDir.String(), "-xf", pkgPath)
/* /*
Parse bundle and app metadata, do pre-install checks. Parse bundle and app metadata, do pre-install checks.
@ -148,10 +147,10 @@ func main() {
} }
// sec: should compare version string // sec: should compare version string
hlog.Verbosef("installing application %q version %q over local %q", msg.Verbosef("installing application %q version %q over local %q",
bundle.ID, bundle.Version, a.Version) bundle.ID, bundle.Version, a.Version)
} else { } else {
hlog.Verbosef("application %q clean installation", bundle.ID) msg.Verbosef("application %q clean installation", bundle.ID)
// sec: should install credentials // sec: should install credentials
} }
@ -159,7 +158,7 @@ func main() {
Setup steps for files owned by the target user. Setup steps for files owned by the target user.
*/ */
withCacheDir(ctx, "install", []string{ withCacheDir(ctx, msg, "install", []string{
// export inner bundle path in the environment // export inner bundle path in the environment
"export BUNDLE=" + hst.Tmp + "/bundle", "export BUNDLE=" + hst.Tmp + "/bundle",
// replace inner /etc // replace inner /etc
@ -181,7 +180,7 @@ func main() {
}, workDir, bundle, pathSet, flagDropShell, cleanup) }, workDir, bundle, pathSet, flagDropShell, cleanup)
if bundle.GPU { if bundle.GPU {
withCacheDir(ctx, "mesa-wrappers", []string{ withCacheDir(ctx, msg, "mesa-wrappers", []string{
// link nixGL mesa wrappers // link nixGL mesa wrappers
"mkdir -p nix/.nixGL", "mkdir -p nix/.nixGL",
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL", "ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
@ -193,7 +192,7 @@ func main() {
Activate home-manager generation. Activate home-manager generation.
*/ */
withNixDaemon(ctx, "activate", []string{ withNixDaemon(ctx, msg, "activate", []string{
// clean up broken links // clean up broken links
"mkdir -p .local/state/{nix,home-manager}", "mkdir -p .local/state/{nix,home-manager}",
"chmod -R +w .local/state/{nix,home-manager}", "chmod -R +w .local/state/{nix,home-manager}",
@ -261,7 +260,7 @@ func main() {
*/ */
if a.GPU && flagAutoDrivers { if a.GPU && flagAutoDrivers {
withNixDaemon(ctx, "nix-gl", []string{ withNixDaemon(ctx, msg, "nix-gl", []string{
"mkdir -p /nix/.nixGL/auto", "mkdir -p /nix/.nixGL/auto",
"rm -rf /nix/.nixGL/auto", "rm -rf /nix/.nixGL/auto",
"export NIXPKGS_ALLOW_UNFREE=1", "export NIXPKGS_ALLOW_UNFREE=1",
@ -316,7 +315,7 @@ func main() {
Spawn app. Spawn app.
*/ */
mustRunApp(ctx, config, func() {}) mustRunApp(ctx, msg, config, func() {})
return errSuccess return errSuccess
}). }).
Flag(&flagDropShellNixGL, "s", command.BoolFlag(false), "Drop to a shell on nixGL build"). Flag(&flagDropShellNixGL, "s", command.BoolFlag(false), "Drop to a shell on nixGL build").
@ -324,9 +323,9 @@ func main() {
} }
c.MustParse(os.Args[1:], func(err error) { c.MustParse(os.Args[1:], func(err error) {
hlog.Verbosef("command returned %v", err) msg.Verbosef("command returned %v", err)
if errors.Is(err, errSuccess) { if errors.Is(err, errSuccess) {
hlog.BeforeExit() msg.BeforeExit()
os.Exit(0) os.Exit(0)
} }
}) })

View File

@ -9,7 +9,6 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/hlog"
) )
const bash = "bash" const bash = "bash"
@ -51,8 +50,8 @@ func lookPath(file string) string {
var beforeRunFail = new(atomic.Pointer[func()]) var beforeRunFail = new(atomic.Pointer[func()])
func mustRun(name string, arg ...string) { func mustRun(msg container.Msg, name string, arg ...string) {
hlog.Verbosef("spawning process: %q %q", name, arg) msg.Verbosef("spawning process: %q %q", name, arg)
cmd := exec.Command(name, arg...) cmd := exec.Command(name, arg...)
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {

View File

@ -9,14 +9,14 @@ import (
"os" "os"
"os/exec" "os/exec"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/internal/hlog"
) )
var hakureiPath = internal.MustHakureiPath() var hakureiPath = internal.MustHakureiPath()
func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) { func mustRunApp(ctx context.Context, msg container.Msg, config *hst.Config, beforeFail func()) {
var ( var (
cmd *exec.Cmd cmd *exec.Cmd
st io.WriteCloser st io.WriteCloser
@ -26,10 +26,10 @@ func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
beforeFail() beforeFail()
log.Fatalf("cannot pipe: %v", err) log.Fatalf("cannot pipe: %v", err)
} else { } else {
if hlog.Load() { if msg.IsVerbose() {
cmd = exec.CommandContext(ctx, hakureiPath, "-v", "app", "3") cmd = exec.CommandContext(ctx, hakureiPath.String(), "-v", "app", "3")
} else { } else {
cmd = exec.CommandContext(ctx, hakureiPath, "app", "3") cmd = exec.CommandContext(ctx, hakureiPath.String(), "app", "3")
} }
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.ExtraFiles = []*os.File{r} cmd.ExtraFiles = []*os.File{r}
@ -51,7 +51,8 @@ func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) {
var exitError *exec.ExitError var exitError *exec.ExitError
if errors.As(err, &exitError) { if errors.As(err, &exitError) {
beforeFail() beforeFail()
internal.Exit(exitError.ExitCode()) msg.BeforeExit()
os.Exit(exitError.ExitCode())
} else { } else {
beforeFail() beforeFail()
log.Fatalf("cannot wait: %v", err) log.Fatalf("cannot wait: %v", err)

View File

@ -2,20 +2,21 @@ package main
import ( import (
"context" "context"
"os"
"strings" "strings"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal"
) )
func withNixDaemon( func withNixDaemon(
ctx context.Context, ctx context.Context,
msg container.Msg,
action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config, action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config,
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(), app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
) { ) {
mustRunAppDropShell(ctx, updateConfig(&hst.Config{ mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{
ID: app.ID, ID: app.ID,
Path: pathShell, Path: pathShell,
@ -61,9 +62,10 @@ func withNixDaemon(
func withCacheDir( func withCacheDir(
ctx context.Context, ctx context.Context,
msg container.Msg,
action string, command []string, workDir *container.Absolute, action string, command []string, workDir *container.Absolute,
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) { app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
mustRunAppDropShell(ctx, &hst.Config{ mustRunAppDropShell(ctx, msg, &hst.Config{
ID: app.ID, ID: app.ID,
Path: pathShell, Path: pathShell,
@ -97,12 +99,13 @@ func withCacheDir(
}, dropShell, beforeFail) }, dropShell, beforeFail)
} }
func mustRunAppDropShell(ctx context.Context, config *hst.Config, dropShell bool, beforeFail func()) { func mustRunAppDropShell(ctx context.Context, msg container.Msg, config *hst.Config, dropShell bool, beforeFail func()) {
if dropShell { if dropShell {
config.Args = []string{bash, "-l"} config.Args = []string{bash, "-l"}
mustRunApp(ctx, config, beforeFail) mustRunApp(ctx, msg, config, beforeFail)
beforeFail() beforeFail()
internal.Exit(0) msg.BeforeExit()
os.Exit(0)
} }
mustRunApp(ctx, config, beforeFail) mustRunApp(ctx, msg, config, beforeFail)
} }

View File

@ -34,7 +34,7 @@ func (r *AutoRootOp) early(state *setupState, k syscallDispatcher) error {
r.resolved = make([]*BindMountOp, 0, len(d)) r.resolved = make([]*BindMountOp, 0, len(d))
for _, ent := range d { for _, ent := range d {
name := ent.Name() name := ent.Name()
if IsAutoRootBindable(name) { if IsAutoRootBindable(state, name) {
// careful: the Valid method is skipped, make sure this is always valid // careful: the Valid method is skipped, make sure this is always valid
op := &BindMountOp{ op := &BindMountOp{
Source: r.Host.Append(name), 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. // 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 { switch name {
case "proc", "dev", "tmp", "mnt", "etc": case "proc", "dev", "tmp", "mnt", "etc":

View File

@ -180,19 +180,26 @@ func TestIsAutoRootBindable(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
want bool want bool
log bool
}{ }{
{"proc", false}, {"proc", false, false},
{"dev", false}, {"dev", false, false},
{"tmp", false}, {"tmp", false, false},
{"mnt", false}, {"mnt", false, false},
{"etc", false}, {"etc", false, false},
{"", false}, {"", false, true},
{"var", true}, {"var", true, false},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { 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) t.Errorf("IsAutoRootBindable: %v, want %v", got, tc.want)
} }
}) })

View File

@ -49,6 +49,7 @@ type (
cmd *exec.Cmd cmd *exec.Cmd
ctx context.Context ctx context.Context
msg Msg
Params Params
} }
@ -162,10 +163,10 @@ func (p *Container) Start() error {
// map to overflow id to work around ownership checks // map to overflow id to work around ownership checks
if p.Uid < 1 { if p.Uid < 1 {
p.Uid = OverflowUid() p.Uid = OverflowUid(p.msg)
} }
if p.Gid < 1 { if p.Gid < 1 {
p.Gid = OverflowGid() p.Gid = OverflowGid(p.msg)
} }
if !p.RetainSession { 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} return &StartError{false, "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET", ENOSYS, true, false}
} else { } else {
msg.Verbosef("landlock abi version %d", abi) p.msg.Verbosef("landlock abi version %d", abi)
} }
if rulesetFd, err := rulesetAttr.Create(0); err != nil { if rulesetFd, err := rulesetAttr.Create(0); err != nil {
return &StartError{true, "create landlock ruleset", err, false, false} return &StartError{true, "create landlock ruleset", err, false, false}
} else { } else {
msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr) p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil { if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
_ = Close(rulesetFd) _ = Close(rulesetFd)
return &StartError{true, "enforce landlock ruleset", err, false, false} return &StartError{true, "enforce landlock ruleset", err, false, false}
} }
if err = Close(rulesetFd); err != nil { 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 // not fatal
} }
} }
@ -283,7 +284,7 @@ func (p *Container) Start() error {
landlockOut: landlockOut:
} }
msg.Verbose("starting container init") p.msg.Verbose("starting container init")
if err := p.cmd.Start(); err != nil { if err := p.cmd.Start(); err != nil {
return &StartError{false, "start container init", err, false, true} return &StartError{false, "start container init", err, false, true}
} }
@ -325,7 +326,7 @@ func (p *Container) Serve() error {
Getuid(), Getuid(),
Getgid(), Getgid(),
len(p.ExtraFiles), len(p.ExtraFiles),
msg.IsVerbose(), p.msg.IsVerbose(),
}, },
) )
if err != nil { 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. // New returns the address to a new instance of [Container] that requires further initialisation before use.
func New(ctx context.Context) *Container { func New(ctx context.Context, msg Msg) *Container {
p := &Container{ctx: ctx, Params: Params{Ops: new(Ops)}} if msg == nil {
msg = NewMsg(nil)
}
p := &Container{ctx: ctx, msg: msg, Params: Params{Ops: new(Ops)}}
c, cancel := context.WithCancel(ctx) c, cancel := context.WithCancel(ctx)
p.cancel = cancel p.cancel = cancel
p.cmd = exec.CommandContext(c, MustExecutable()) p.cmd = exec.CommandContext(c, MustExecutable(msg))
return p return p
} }
// NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields. // NewCommand calls [New] and initialises the [Params.Path] and [Params.Args] fields.
func NewCommand(ctx context.Context, pathname *Absolute, name string, args ...string) *Container { func NewCommand(ctx context.Context, msg Msg, pathname *Absolute, name string, args ...string) *Container {
z := New(ctx) z := New(ctx, msg)
z.Path = pathname z.Path = pathname
z.Args = append([]string{name}, args...) z.Args = append([]string{name}, args...)
return z return z

View File

@ -23,8 +23,6 @@ import (
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/container/vfs" "hakurei.app/container/vfs"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
"hakurei.app/ldd" "hakurei.app/ldd"
) )
@ -351,8 +349,6 @@ var containerTestCases = []struct {
} }
func TestContainer(t *testing.T) { func TestContainer(t *testing.T) {
replaceOutput(t)
t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) { t.Run("cancel", testContainerCancel(nil, func(t *testing.T, c *container.Container) {
wantErr := context.Canceled wantErr := context.Canceled
wantExitCode := 0 wantExitCode := 0
@ -547,7 +543,8 @@ func testContainerCancel(
} }
func TestContainerString(t *testing.T) { 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.SeccompFlags |= seccomp.AllowMultiarch
c.SeccompRules = seccomp.Preset( c.SeccompRules = seccomp.Preset(
seccomp.PresetExt|seccomp.PresetDenyNS|seccomp.PresetDenyTTY, seccomp.PresetExt|seccomp.PresetDenyNS|seccomp.PresetDenyTTY,
@ -689,7 +686,7 @@ var (
var helperCommands []func(c command.Command) var helperCommands []func(c command.Command)
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput) container.TryArgv0(nil)
if os.Getenv(envDoCheck) == "1" { if os.Getenv(envDoCheck) == "1" {
c := command.New(os.Stderr, log.Printf, "helper", func(args []string) error { 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) { 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.Env = append(c.Env, envDoCheck+"=1")
c.Bind(container.MustAbs(os.Args[0]), absHelperInnerPath, 0) c.Bind(container.MustAbs(os.Args[0]), absHelperInnerPath, 0)
// in case test has cgo enabled // 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) log.Fatalf("ldd: %v", err)
} else { } else {
*libPaths = ldd.Path(entries) *libPaths = ldd.Path(entries)

View File

@ -3,7 +3,6 @@ package container
import ( import (
"io" "io"
"io/fs" "io/fs"
"log"
"os" "os"
"os/exec" "os/exec"
"os/signal" "os/signal"
@ -38,7 +37,7 @@ type syscallDispatcher interface {
setNoNewPrivs() error setNoNewPrivs() error
// lastcap provides [LastCap]. // lastcap provides [LastCap].
lastcap() uintptr lastcap(msg Msg) uintptr
// capset provides capset. // capset provides capset.
capset(hdrp *capHeader, datap *[2]capData) error capset(hdrp *capHeader, datap *[2]capData) error
// capBoundingSetDrop provides capBoundingSetDrop. // capBoundingSetDrop provides capBoundingSetDrop.
@ -53,9 +52,9 @@ type syscallDispatcher interface {
receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error) receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error)
// bindMount provides procPaths.bindMount. // bindMount provides procPaths.bindMount.
bindMount(source, target string, flags uintptr) error bindMount(msg Msg, source, target string, flags uintptr) error
// remount provides procPaths.remount. // remount provides procPaths.remount.
remount(target string, flags uintptr) error remount(msg Msg, target string, flags uintptr) error
// mountTmpfs provides mountTmpfs. // mountTmpfs provides mountTmpfs.
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
// ensureFile provides ensureFile. // ensureFile provides ensureFile.
@ -122,22 +121,12 @@ type syscallDispatcher interface {
// wait4 provides syscall.Wait4 // wait4 provides syscall.Wait4
wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error)
// printf provides [log.Printf]. // printf provides the Printf method of [log.Logger].
printf(format string, v ...any) printf(msg Msg, format string, v ...any)
// fatal provides [log.Fatal] // fatal provides the Fatal method of [log.Logger]
fatal(v ...any) fatal(msg Msg, v ...any)
// fatalf provides [log.Fatalf] // fatalf provides the Fatalf method of [log.Logger]
fatalf(format string, v ...any) fatalf(msg Msg, 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()
} }
// direct implements syscallDispatcher on the current kernel. // 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) setDumpable(dumpable uintptr) error { return SetDumpable(dumpable) }
func (direct) setNoNewPrivs() error { return SetNoNewPrivs() } 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) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) }
func (direct) capBoundingSetDrop(cap uintptr) error { return capBoundingSetDrop(cap) } func (direct) capBoundingSetDrop(cap uintptr) error { return capBoundingSetDrop(cap) }
func (direct) capAmbientClearAll() error { return capAmbientClearAll() } func (direct) capAmbientClearAll() error { return capAmbientClearAll() }
@ -161,11 +150,11 @@ func (direct) receive(key string, e any, fdp *uintptr) (func() error, error) {
return Receive(key, e, fdp) return Receive(key, e, fdp)
} }
func (direct) bindMount(source, target string, flags uintptr) error { func (direct) bindMount(msg Msg, source, target string, flags uintptr) error {
return hostProc.bindMount(source, target, flags) return hostProc.bindMount(msg, source, target, flags)
} }
func (direct) remount(target string, flags uintptr) error { func (direct) remount(msg Msg, target string, flags uintptr) error {
return hostProc.remount(target, flags) return hostProc.remount(msg, target, flags)
} }
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error { func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
return mountTmpfs(k, fsname, target, flags, size, perm) 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) return syscall.Wait4(pid, wstatus, options, rusage)
} }
func (direct) printf(format string, v ...any) { log.Printf(format, v...) } func (direct) printf(msg Msg, format string, v ...any) { msg.GetLogger().Printf(format, v...) }
func (direct) fatal(v ...any) { log.Fatal(v...) } func (direct) fatal(msg Msg, v ...any) { msg.GetLogger().Fatal(v...) }
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) } func (direct) fatalf(msg Msg, format string, v ...any) { msg.GetLogger().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() }

View File

@ -2,8 +2,10 @@ package container
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"io/fs" "io/fs"
"log"
"os" "os"
"os/exec" "os/exec"
"reflect" "reflect"
@ -136,7 +138,7 @@ func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
type simpleTestCase struct { type simpleTestCase struct {
name string name string
f func(k syscallDispatcher) error f func(k *kstub) error
want stub.Expect want stub.Expect
wantErr error wantErr error
} }
@ -185,11 +187,11 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Helper() t.Helper()
state := &setupState{Params: tc.params}
k := &kstub{nil, stub.New(t, k := &kstub{nil, stub.New(t,
func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{nil, s} }, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{nil, s} },
stub.Expect{Calls: slices.Concat(tc.early, []stub.Call{{Name: stub.CallSeparator}}, tc.apply)}, 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) defer stub.HandleExit(t)
errEarly := tc.op.early(state, k) errEarly := tc.op.early(state, k)
k.Expects(stub.CallSeparator) 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) 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 { func (k *kstub) capset(hdrp *capHeader, datap *[2]capData) error {
k.Helper() k.Helper()
@ -403,16 +409,18 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
return 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.Helper()
k.checkMsg(msg)
return k.Expects("bindMount").Error( return k.Expects("bindMount").Error(
stub.CheckArg(k.Stub, "source", source, 0), stub.CheckArg(k.Stub, "source", source, 0),
stub.CheckArg(k.Stub, "target", target, 1), stub.CheckArg(k.Stub, "target", target, 1),
stub.CheckArg(k.Stub, "flags", flags, 2)) 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.Helper()
k.checkMsg(msg)
return k.Expects("remount").Error( return k.Expects("remount").Error(
stub.CheckArg(k.Stub, "target", target, 0), stub.CheckArg(k.Stub, "target", target, 0),
stub.CheckArg(k.Stub, "flags", flags, 1)) stub.CheckArg(k.Stub, "flags", flags, 1))
@ -694,7 +702,7 @@ func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage
return return
} }
func (k *kstub) printf(format string, v ...any) { func (k *kstub) printf(_ Msg, format string, v ...any) {
k.Helper() k.Helper()
if k.Expects("printf").Error( if k.Expects("printf").Error(
stub.CheckArg(k.Stub, "format", format, 0), stub.CheckArg(k.Stub, "format", format, 0),
@ -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() k.Helper()
if k.Expects("fatal").Error( if k.Expects("fatal").Error(
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil { stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
@ -712,7 +720,7 @@ func (k *kstub) fatal(v ...any) {
panic(stub.PanicExit) panic(stub.PanicExit)
} }
func (k *kstub) fatalf(format string, v ...any) { func (k *kstub) fatalf(_ Msg, format string, v ...any) {
k.Helper() k.Helper()
if k.Expects("fatalf").Error( if k.Expects("fatalf").Error(
stub.CheckArg(k.Stub, "format", format, 0), stub.CheckArg(k.Stub, "format", format, 0),
@ -722,7 +730,35 @@ func (k *kstub) fatalf(format string, v ...any) {
panic(stub.PanicExit) 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() k.Helper()
if k.Expects("verbose").Error( if k.Expects("verbose").Error(
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil { 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() k.Helper()
if k.Expects("verbosef").Error( if k.Expects("verbosef").Error(
stub.CheckArg(k.Stub, "format", format, 0), 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) 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) Resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
func (k *kstub) beforeExit() { k.Helper(); k.Expects("beforeExit") } func (k *kstub) BeforeExit() { k.Helper(); k.Expects("beforeExit") }

View File

@ -1,7 +1,6 @@
package container package container
import ( import (
"log"
"os" "os"
"sync" "sync"
) )
@ -11,16 +10,16 @@ var (
executableOnce sync.Once executableOnce sync.Once
) )
func copyExecutable() { func copyExecutable(msg Msg) {
if name, err := os.Executable(); err != nil { if name, err := os.Executable(); err != nil {
msg.BeforeExit() msg.BeforeExit()
log.Fatalf("cannot read executable path: %v", err) msg.GetLogger().Fatalf("cannot read executable path: %v", err)
} else { } else {
executable = name executable = name
} }
} }
func MustExecutable() string { func MustExecutable(msg Msg) string {
executableOnce.Do(copyExecutable) executableOnce.Do(func() { copyExecutable(msg) })
return executable return executable
} }

View File

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

View File

@ -3,6 +3,7 @@ package container
import ( import (
"errors" "errors"
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@ -59,6 +60,7 @@ type (
setupState struct { setupState struct {
nonrepeatable uintptr nonrepeatable uintptr
*Params *Params
Msg
} }
) )
@ -91,20 +93,23 @@ type initParams struct {
Verbose bool Verbose bool
} }
func Init(prepareLogger func(prefix string), setVerbose func(verbose bool)) { // Init is called by [TryArgv0] if the current process is the container init.
initEntrypoint(direct{}, prepareLogger, setVerbose) 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() k.lockOSThread()
prepareLogger("init")
if k.getpid() != 1 { 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 { 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 // 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, &params, &setupFd); err != nil { if f, err := k.receive(setupEnv, &params, &setupFd); err != nil {
if errors.Is(err, EBADF) { if errors.Is(err, EBADF) {
k.fatal("invalid setup descriptor") k.fatal(msg, "invalid setup descriptor")
} }
if errors.Is(err, ErrReceiveEnv) { 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 { } else {
if params.Ops == nil { if params.Ops == nil {
k.fatal("invalid setup parameters") k.fatal(msg, "invalid setup parameters")
} }
if params.ParentPerm == 0 { if params.ParentPerm == 0 {
params.ParentPerm = 0755 params.ParentPerm = 0755
} }
setVerbose(params.Verbose) msg.SwapVerbose(params.Verbose)
k.verbose("received setup parameters") msg.Verbose("received setup parameters")
closeSetup = f closeSetup = f
offsetSetup = int(setupFd + 1) offsetSetup = int(setupFd + 1)
} }
// write uid/gid map here so parent does not need to set dumpable // write uid/gid map here so parent does not need to set dumpable
if err := k.setDumpable(SUID_DUMP_USER); err != nil { 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", if err := k.writeFile(FHSProc+"self/uid_map",
append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...), append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...),
0); err != nil { 0); err != nil {
k.fatalf("%v", err) k.fatalf(msg, "%v", err)
} }
if err := k.writeFile(FHSProc+"self/setgroups", if err := k.writeFile(FHSProc+"self/setgroups",
[]byte("deny\n"), []byte("deny\n"),
0); err != nil && !os.IsNotExist(err) { 0); err != nil && !os.IsNotExist(err) {
k.fatalf("%v", err) k.fatalf(msg, "%v", err)
} }
if err := k.writeFile(FHSProc+"self/gid_map", if err := k.writeFile(FHSProc+"self/gid_map",
append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...), append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...),
0); err != nil { 0); err != nil {
k.fatalf("%v", err) k.fatalf(msg, "%v", err)
} }
if err := k.setDumpable(SUID_DUMP_DISABLE); err != nil { 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) oldmask := k.umask(0)
if params.Hostname != "" { if params.Hostname != "" {
if err := k.sethostname([]byte(params.Hostname)); err != nil { 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 // 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 { 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: &params.Params} state := &setupState{Params: &params.Params, Msg: msg}
/* early is called right before pivot_root into intermediate root; /* early is called right before pivot_root into intermediate root;
this step is mostly for gathering information that would otherwise be difficult to obtain 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 */ the state of the mount namespace */
for i, op := range *params.Ops { for i, op := range *params.Ops {
if op == nil || !op.Valid() { 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 err := op.early(state, k); err != nil {
if m, ok := messageFromError(err); ok { if m, ok := messageFromError(err); ok {
k.fatal(m) k.fatal(msg, m)
} else { } 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 { 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 { 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 { 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 { 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 { if err := k.mkdir(hostDir, 0755); err != nil {
k.fatalf("%v", err) k.fatalf(msg, "%v", err)
} }
// pivot_root uncovers intermediateHostPath in hostDir // pivot_root uncovers intermediateHostPath in hostDir
if err := k.pivotRoot(intermediateHostPath, hostDir); err != nil { 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 { 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; /* 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 { for i, op := range *params.Ops {
// ops already checked during early setup // ops already checked during early setup
if prefix, ok := op.prefix(); ok { 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 err := op.apply(state, k); err != nil {
if m, ok := messageFromError(err); ok { if m, ok := messageFromError(err); ok {
k.fatal(m) k.fatal(msg, m)
} else { } 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 // setup requiring host root complete at this point
if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil { 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 { 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) fd, err = k.open(FHSRoot, O_DIRECTORY|O_RDONLY, 0)
return return
}); err != nil { }); 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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++ { for i := uintptr(0); i <= lastcap; i++ {
if params.Privileged && i == CAP_SYS_ADMIN { if params.Privileged && i == CAP_SYS_ADMIN {
continue continue
} }
if err := k.capBoundingSetDrop(i); err != nil { 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) keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN)
if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil { 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( if err := k.capset(
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, &capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
&[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}}, &[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}},
); err != nil { ); err != nil {
k.fatalf("cannot capset: %v", err) k.fatalf(msg, "cannot capset: %v", err)
} }
if !params.SeccompDisable { if !params.SeccompDisable {
rules := params.SeccompRules rules := params.SeccompRules
if len(rules) == 0 { // non-empty rules slice always overrides presets 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) rules = seccomp.Preset(params.SeccompPresets, params.SeccompFlags)
} }
if err := k.seccompLoad(rules, params.SeccompFlags); err != nil { if err := k.seccompLoad(rules, params.SeccompFlags); err != nil {
// this also indirectly asserts PR_SET_NO_NEW_PRIVS // 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 { } else {
k.verbose("syscall filter not configured") msg.Verbose("syscall filter not configured")
} }
extraFiles := make([]*os.File, params.Count) extraFiles := make([]*os.File, params.Count)
@ -331,14 +336,14 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
cmd.ExtraFiles = extraFiles cmd.ExtraFiles = extraFiles
cmd.Dir = params.Dir.String() 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 { 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 { if err := closeSetup(); err != nil {
k.printf("cannot close setup pipe: %v", err) k.printf(msg, "cannot close setup pipe: %v", err)
// not fatal // not fatal
} }
@ -372,7 +377,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
} }
} }
if !errors.Is(err, ECHILD) { if !errors.Is(err, ECHILD) {
k.printf("unexpected wait4 response: %v", err) k.printf(msg, "unexpected wait4 response: %v", err)
} }
close(done) close(done)
@ -389,50 +394,50 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
for { for {
select { select {
case s := <-sig: case s := <-sig:
if k.resume() { if msg.Resume() {
k.verbosef("%s after process start", s.String()) msg.Verbosef("%s after process start", s.String())
} else { } else {
k.verbosef("got %s", s.String()) msg.Verbosef("got %s", s.String())
} }
if s == CancelSignal && params.ForwardCancel && cmd.Process != nil { 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 { 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 continue
} }
k.beforeExit() msg.BeforeExit()
k.exit(0) k.exit(0)
case w := <-info: case w := <-info:
if w.wpid == cmd.Process.Pid { if w.wpid == cmd.Process.Pid {
// initial process exited, output is most likely available again // initial process exited, output is most likely available again
k.resume() msg.Resume()
switch { switch {
case w.wstatus.Exited(): case w.wstatus.Exited():
r = w.wstatus.ExitStatus() 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(): case w.wstatus.Signaled():
r = 128 + int(w.wstatus.Signal()) 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: default:
r = 255 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) }() go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
} }
case <-done: case <-done:
k.beforeExit() msg.BeforeExit()
k.exit(r) k.exit(r)
case <-timeout: case <-timeout:
k.printf("timeout exceeded waiting for lingering processes") k.printf(msg, "timeout exceeded waiting for lingering processes")
k.beforeExit() msg.BeforeExit()
k.exit(r) k.exit(r)
} }
} }
@ -441,10 +446,16 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
const initName = "init" const initName = "init"
// TryArgv0 calls [Init] if the last element of argv0 is "init". // TryArgv0 calls [Init] if the last element of argv0 is "init".
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 { if len(os.Args) > 0 && path.Base(os.Args[0]) == initName {
msg = v Init(msg)
Init(prepare, setVerbose)
msg.BeforeExit() msg.BeforeExit()
os.Exit(0) os.Exit(0)
} }

View File

@ -11,25 +11,9 @@ import (
) )
func TestInitEntrypoint(t *testing.T) { 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{ 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1<<10, nil), call("getpid", stub.ExpectArgs{}, 1<<10, nil),
@ -37,7 +21,7 @@ func TestInitEntrypoint(t *testing.T) {
}, },
}, nil}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -47,7 +31,7 @@ func TestInitEntrypoint(t *testing.T) {
}, },
}, nil}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -57,7 +41,7 @@ func TestInitEntrypoint(t *testing.T) {
}, },
}, nil}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -67,7 +51,7 @@ func TestInitEntrypoint(t *testing.T) {
}, },
}, nil}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -91,7 +75,7 @@ func TestInitEntrypoint(t *testing.T) {
}, },
}, nil}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -112,13 +96,14 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(78), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, stub.UniqueError(77)), 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), call("fatalf", stub.ExpectArgs{"cannot set SUID_DUMP_USER: %v", []any{stub.UniqueError(77)}}, nil, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -139,6 +124,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(76), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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)), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -167,6 +153,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(74), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -196,6 +183,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(72), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -226,6 +214,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(70), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -257,6 +246,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(68), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -290,6 +280,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(66), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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},
{"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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -325,6 +316,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(64), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -362,6 +354,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(63), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -399,6 +392,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(62), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -437,6 +431,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(60), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -475,6 +470,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(59), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -514,6 +510,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(57), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -554,6 +551,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(55), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -595,6 +593,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(53), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -637,6 +636,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(51), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -680,6 +680,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(49), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -724,6 +725,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(47), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -769,6 +771,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(45), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -823,6 +826,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(43), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -877,6 +881,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(42), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -932,6 +937,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(40), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -988,6 +994,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(38), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1046,6 +1053,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(36), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1105,6 +1113,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(34), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1165,6 +1174,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(32), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1226,6 +1236,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(30), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1288,6 +1299,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(28), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1351,6 +1363,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(26), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1415,6 +1428,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(24), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1480,6 +1494,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(22), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1553,6 +1568,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(20), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1659,6 +1675,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(18), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1766,6 +1783,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(16), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil), call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1874,6 +1892,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true, SeccompDisable: true,
ParentPerm: 0750, ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(13), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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}, }, 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 */ /* entrypoint */
Calls: []stub.Call{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -1987,6 +2006,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true, SeccompDisable: true,
ParentPerm: 0750, ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(10), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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("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("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("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("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(10)}}, nil, nil),
call("New", stub.ExpectArgs{}, 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), 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}, }, 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 */ /* entrypoint */
Calls: []stub.Call{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -2086,6 +2106,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true, SeccompDisable: true,
ParentPerm: 0750, ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(7), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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("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("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("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("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
call("New", stub.ExpectArgs{}, 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), 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}, }, 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 */ /* entrypoint */
Calls: []stub.Call{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -2176,6 +2197,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true, SeccompDisable: true,
ParentPerm: 0750, ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(5), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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("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("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("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("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(5)}}, nil, nil),
call("New", stub.ExpectArgs{}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil),
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, 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}, }, 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 */ /* entrypoint */
Calls: []stub.Call{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -2268,6 +2290,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true, SeccompDisable: true,
ParentPerm: 0750, ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(3), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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("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("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("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("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(3)}}, nil, nil),
call("New", stub.ExpectArgs{}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil),
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, 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}, }, 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 */ /* entrypoint */
Calls: []stub.Call{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -2367,6 +2390,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true, SeccompDisable: true,
ParentPerm: 0750, ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(1), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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("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("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("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("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(1)}}, nil, nil),
call("New", stub.ExpectArgs{}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil),
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, 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}, }, 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 */ /* entrypoint */
Calls: []stub.Call{ Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil), call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -2503,6 +2527,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true, RetainSession: true,
Privileged: true, Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(0), nil), }, 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("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, 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), 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("umask", stub.ExpectArgs{022}, 0, nil),
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/bin/zsh")}}, nil, 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("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("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(0)}}, nil, nil),
call("New", stub.ExpectArgs{}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil),
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),

View File

@ -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.sourceFinal == nil {
if b.Flags&BindOptional == 0 { if b.Flags&BindOptional == 0 {
// unreachable // unreachable
@ -92,11 +92,11 @@ func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error {
} }
if b.sourceFinal.String() == b.Target.String() { if b.sourceFinal.String() == b.Target.String() {
k.verbosef("mounting %q flags %#x", target, flags) state.Verbosef("mounting %q flags %#x", target, flags)
} else { } 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 { func (b *BindMountOp) Is(op Op) bool {

View File

@ -46,6 +46,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
return err return err
} }
if err := k.bindMount( if err := k.bindMount(
state,
toHost(FHSDev+name), toHost(FHSDev+name),
targetPath, targetPath,
0, 0,
@ -93,6 +94,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
if name, err := k.readlink(hostProc.stdout()); err != nil { if name, err := k.readlink(hostProc.stdout()); err != nil {
return err return err
} else if err = k.bindMount( } else if err = k.bindMount(
state,
toHost(name), toHost(name),
consolePath, consolePath,
0, 0,
@ -116,7 +118,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
return nil return nil
} }
if err := k.remount(target, MS_RDONLY); err != nil { if err := k.remount(state, target, MS_RDONLY); err != nil {
return err return err
} }
return k.mountTmpfs(SourceTmpfs, devShmPath, MS_NOSUID|MS_NODEV, 0, 01777) return k.mountTmpfs(SourceTmpfs, devShmPath, MS_NOSUID|MS_NODEV, 0, 01777)

View File

@ -52,6 +52,7 @@ func (t *TmpfileOp) apply(state *setupState, k syscallDispatcher) error {
if err := k.ensureFile(target, 0444, state.ParentPerm); err != nil { if err := k.ensureFile(target, 0444, state.ParentPerm); err != nil {
return err return err
} else if err = k.bindMount( } else if err = k.bindMount(
state,
tmpPath, tmpPath,
target, target,
syscall.MS_RDONLY|syscall.MS_NODEV, syscall.MS_RDONLY|syscall.MS_NODEV,

View File

@ -21,8 +21,8 @@ type RemountOp struct {
func (r *RemountOp) Valid() bool { return r != nil && r.Target != nil } func (r *RemountOp) Valid() bool { return r != nil && r.Target != nil }
func (*RemountOp) early(*setupState, syscallDispatcher) error { return nil } func (*RemountOp) early(*setupState, syscallDispatcher) error { return nil }
func (r *RemountOp) apply(_ *setupState, k syscallDispatcher) error { func (r *RemountOp) apply(state *setupState, k syscallDispatcher) error {
return k.remount(toSysroot(r.Target.String()), r.Flags) return k.remount(state, toSysroot(r.Target.String()), r.Flags)
} }
func (r *RemountOp) Is(op Op) bool { func (r *RemountOp) Is(op Op) bool {

View File

@ -96,18 +96,17 @@ const (
) )
// bindMount mounts source on target and recursively applies flags if MS_REC is set. // bindMount mounts source on target and recursively applies flags if MS_REC is set.
func (p *procPaths) bindMount(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 // syscallDispatcher.bindMount and procPaths.remount must not be called from this function
if err := p.k.mount(source, target, FstypeNULL, MS_SILENT|MS_BIND|flags&MS_REC, zeroString); err != nil { if err := p.k.mount(source, target, FstypeNULL, MS_SILENT|MS_BIND|flags&MS_REC, zeroString); err != nil {
return err return err
} }
return p.k.remount(msg, target, flags)
return p.k.remount(target, flags)
} }
// remount applies flags on target, recursively if MS_REC is set. // remount applies flags on target, recursively if MS_REC is set.
func (p *procPaths) remount(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 // syscallDispatcher methods bindMount, remount must not be called from this function
var targetFinal string var targetFinal string
@ -116,7 +115,7 @@ func (p *procPaths) remount(target string, flags uintptr) error {
} else { } else {
targetFinal = v targetFinal = v
if targetFinal != target { 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 return err
} }
if err = remountWithFlags(p.k, n, mf); err != nil { if err = remountWithFlags(p.k, msg, n, mf); err != nil {
return err return err
} }
if flags&MS_REC == 0 { if flags&MS_REC == 0 {
@ -159,7 +158,7 @@ func (p *procPaths) remount(target string, flags uintptr) error {
continue 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 return err
} }
} }
@ -169,12 +168,12 @@ func (p *procPaths) remount(target string, flags uintptr) error {
} }
// remountWithFlags remounts mount point described by [vfs.MountInfoNode]. // remountWithFlags remounts mount point described by [vfs.MountInfoNode].
func remountWithFlags(k syscallDispatcher, 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 // syscallDispatcher methods bindMount, remount must not be called from this function
kf, unmatched := n.Flags() kf, unmatched := n.Flags()
if len(unmatched) != 0 { if len(unmatched) != 0 {
k.verbosef("unmatched vfs options: %q", unmatched) msg.Verbosef("unmatched vfs options: %q", unmatched)
} }
if kf&mf != mf { if kf&mf != mf {

View File

@ -11,21 +11,21 @@ import (
func TestBindMount(t *testing.T) { func TestBindMount(t *testing.T) {
checkSimple(t, "bindMount", []simpleTestCase{ checkSimple(t, "bindMount", []simpleTestCase{
{"mount", func(k syscallDispatcher) error { {"mount", func(k *kstub) error {
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY) return newProcPaths(k, hostPath).bindMount(nil, "/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, stub.UniqueError(0xbad)), call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, stub.UniqueError(0xbad)),
}}, stub.UniqueError(0xbad)}, }}, stub.UniqueError(0xbad)},
{"success ne", func(k syscallDispatcher) error { {"success ne", func(k *kstub) error {
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY) return newProcPaths(k, hostPath).bindMount(k, "/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/.host-nix", "", uintptr(0x9000), ""}, nil, nil), call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/.host-nix", "", uintptr(0x9000), ""}, nil, nil),
call("remount", stub.ExpectArgs{"/sysroot/.host-nix", uintptr(1)}, nil, nil), call("remount", stub.ExpectArgs{"/sysroot/.host-nix", uintptr(1)}, nil, nil),
}}, nil}, }}, nil},
{"success", func(k syscallDispatcher) error { {"success", func(k *kstub) error {
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY) return newProcPaths(k, hostPath).bindMount(k, "/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, nil), call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, nil),
call("remount", stub.ExpectArgs{"/sysroot/nix", uintptr(1)}, 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` 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{ checkSimple(t, "remount", []simpleTestCase{
{"evalSymlinks", func(k syscallDispatcher) error { {"evalSymlinks", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", stub.UniqueError(6)), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", stub.UniqueError(6)),
}}, stub.UniqueError(6)}, }}, stub.UniqueError(6)},
{"open", func(k syscallDispatcher) error { {"open", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, stub.UniqueError(5)), call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, stub.UniqueError(5)),
}}, &os.PathError{Op: "open", Path: "/sysroot/nix", Err: stub.UniqueError(5)}}, }}, &os.PathError{Op: "open", Path: "/sysroot/nix", Err: stub.UniqueError(5)}},
{"readlink", func(k syscallDispatcher) error { {"readlink", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, 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)), call("readlink", stub.ExpectArgs{"/host/proc/self/fd/3735928559"}, "/sysroot/nix", stub.UniqueError(4)),
}}, stub.UniqueError(4)}, }}, stub.UniqueError(4)},
{"close", func(k syscallDispatcher) error { {"close", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, 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)), call("close", stub.ExpectArgs{0xdeadbeef}, nil, stub.UniqueError(3)),
}}, &os.PathError{Op: "close", Path: "/sysroot/nix", Err: stub.UniqueError(3)}}, }}, &os.PathError{Op: "close", Path: "/sysroot/nix", Err: stub.UniqueError(3)}},
{"mountinfo no match", func(k syscallDispatcher) error { {"mountinfo no match", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(k, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/.hakurei", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/.hakurei", nil),
call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/.hakurei"}}, nil, 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), call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile(sampleMountinfoNix), nil),
}}, &vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/sysroot/.hakurei")}}, }}, &vfs.DecoderError{Op: "unfold", Line: -1, Err: vfs.UnfoldTargetError("/sysroot/.hakurei")}},
{"mountinfo", func(k syscallDispatcher) error { {"mountinfo", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, 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), call("openNew", stub.ExpectArgs{"/host/proc/self/mountinfo"}, newConstFile("\x00"), nil),
}}, &vfs.DecoderError{Op: "parse", Line: 0, Err: vfs.ErrMountInfoFields}}, }}, &vfs.DecoderError{Op: "parse", Line: 0, Err: vfs.ErrMountInfoFields}},
{"mount", func(k syscallDispatcher) error { {"mount", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, 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)), call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, stub.UniqueError(2)),
}}, stub.UniqueError(2)}, }}, stub.UniqueError(2)},
{"mount propagate", func(k syscallDispatcher) error { {"mount propagate", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, 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)), call("mount", stub.ExpectArgs{"none", "/sysroot/nix/.ro-store", "", uintptr(0x209027), ""}, nil, stub.UniqueError(1)),
}}, stub.UniqueError(1)}, }}, stub.UniqueError(1)},
{"success toplevel", func(k syscallDispatcher) error { {"success toplevel", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/bin", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/bin", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/bin"}, "/sysroot/bin", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/bin"}, "/sysroot/bin", nil),
call("open", stub.ExpectArgs{"/sysroot/bin", 0x280000, uint32(0)}, 0xbabe, 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), call("mount", stub.ExpectArgs{"none", "/sysroot/bin", "", uintptr(0x209027), ""}, nil, nil),
}}, nil}, }}, nil},
{"success EACCES", func(k syscallDispatcher) error { {"success EACCES", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, 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), call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil),
}}, nil}, }}, nil},
{"success no propagate", func(k syscallDispatcher) error { {"success no propagate", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, 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), call("mount", stub.ExpectArgs{"none", "/sysroot/nix", "", uintptr(0x209027), ""}, nil, nil),
}}, nil}, }}, nil},
{"success case sensitive", func(k syscallDispatcher) error { {"success case sensitive", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(nil, "/sysroot/nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/nix"}, "/sysroot/nix", nil),
call("open", stub.ExpectArgs{"/sysroot/nix", 0x280000, uint32(0)}, 0xdeadbeef, 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), call("mount", stub.ExpectArgs{"none", "/sysroot/nix/store", "", uintptr(0x209027), ""}, nil, nil),
}}, nil}, }}, nil},
{"success", func(k syscallDispatcher) error { {"success", func(k *kstub) error {
return newProcPaths(k, hostPath).remount("/sysroot/.nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV) return newProcPaths(k, hostPath).remount(k, "/sysroot/.nix", syscall.MS_REC|syscall.MS_RDONLY|syscall.MS_NODEV)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/sysroot/.nix"}, "/sysroot/NIX", nil), call("evalSymlinks", stub.ExpectArgs{"/sysroot/.nix"}, "/sysroot/NIX", nil),
call("verbosef", stub.ExpectArgs{"target resolves to %q", []any{"/sysroot/NIX"}}, nil, 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) { func TestRemountWithFlags(t *testing.T) {
checkSimple(t, "remountWithFlags", []simpleTestCase{ checkSimple(t, "remountWithFlags", []simpleTestCase{
{"noop unmatched", func(k syscallDispatcher) error { {"noop unmatched", func(k *kstub) error {
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime,cat"}}, 0) return remountWithFlags(k, k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime,cat"}}, 0)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("verbosef", stub.ExpectArgs{"unmatched vfs options: %q", []any{[]string{"cat"}}}, nil, nil), call("verbosef", stub.ExpectArgs{"unmatched vfs options: %q", []any{[]string{"cat"}}}, nil, nil),
}}, nil}, }}, nil},
{"noop", func(k syscallDispatcher) error { {"noop", func(k *kstub) error {
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, 0) return remountWithFlags(k, nil, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, 0)
}, stub.Expect{}, nil}, }, stub.Expect{}, nil},
{"success", func(k syscallDispatcher) error { {"success", func(k *kstub) error {
return remountWithFlags(k, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, syscall.MS_RDONLY) return remountWithFlags(k, nil, &vfs.MountInfoNode{MountInfoEntry: &vfs.MountInfoEntry{VfsOptstr: "rw,relatime"}}, syscall.MS_RDONLY)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("mount", stub.ExpectArgs{"none", "", "", uintptr(0x209021), ""}, nil, nil), call("mount", stub.ExpectArgs{"none", "", "", uintptr(0x209021), ""}, nil, nil),
}}, nil}, }}, nil},
@ -237,20 +237,20 @@ func TestRemountWithFlags(t *testing.T) {
func TestMountTmpfs(t *testing.T) { func TestMountTmpfs(t *testing.T) {
checkSimple(t, "mountTmpfs", []simpleTestCase{ 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) return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 1<<10, 0700)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, stub.UniqueError(0)), call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, stub.UniqueError(0)),
}}, 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) return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 0, 0710)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0750)}, nil, nil), 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), call("mount", stub.ExpectArgs{"ephemeral", "/sysroot/run/user/1000", "tmpfs", uintptr(0), "mode=0710"}, nil, 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) return mountTmpfs(k, "ephemeral", "/sysroot/run/user/1000", 0, 1<<10, 0700)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/run/user/1000", os.FileMode(0700)}, nil, nil),

View File

@ -23,45 +23,88 @@ func GetErrorMessage(err error) (string, bool) {
return e.Message(), true return e.Message(), true
} }
// Msg is used for package-wide verbose logging.
type Msg interface { 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 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) 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) 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 Resume() bool
// BeforeExit runs implementation-specific cleanup code, and optionally prints warnings.
// BeforeExit is called before [os.Exit].
BeforeExit() 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 } logger *log.Logger
func (msg *DefaultMsg) Verbose(v ...any) { Suspendable
if !msg.inactive.Load() { }
log.Println(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) GetLogger() *log.Logger { return msg.logger }
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) { func (msg *defaultMsg) Verbosef(format string, v ...any) {
if !msg.inactive.Load() { if msg.verbose.Load() {
log.Printf(format, v...) msg.logger.Printf(format, v...)
} }
} }
func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) } // Resume calls [Suspendable.Resume] and prints a message if buffer was filled
func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) } // between calls to [Suspendable.Suspend] and Resume.
func (msg *DefaultMsg) BeforeExit() {} 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
}
// msg is the [Msg] implemented used by all exported [container] functions. // BeforeExit prints a message if called between calls to [Suspendable.Suspend] and Resume.
var msg Msg = new(DefaultMsg) func (msg *defaultMsg) BeforeExit() {
if msg.Resume() {
// GetOutput returns the current active [Msg] implementation. msg.logger.Printf("beforeExit reached on suspended output")
func GetOutput() Msg { return msg }
// SetOutput replaces the current active [Msg] implementation.
func SetOutput(v Msg) {
if v == nil {
msg = new(DefaultMsg)
} else {
msg = v
} }
} }

View File

@ -1,14 +1,16 @@
package container_test package container_test
import ( import (
"bytes"
"errors" "errors"
"io"
"log" "log"
"strings" "strings"
"sync/atomic"
"syscall" "syscall"
"testing" "testing"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/stub"
) )
func TestMessageError(t *testing.T) { func TestMessageError(t *testing.T) {
@ -39,146 +41,137 @@ func TestMessageError(t *testing.T) {
} }
func TestDefaultMsg(t *testing.T) { func TestDefaultMsg(t *testing.T) {
{ // copied from output.go
w := log.Writer() const suspendBufMax = 1 << 24
f := log.Flags()
t.Cleanup(func() { log.SetOutput(w); log.SetFlags(f) })
}
msg := new(container.DefaultMsg)
t.Run("is verbose", func(t *testing.T) { t.Run("logger", func(t *testing.T) {
t.Run("nil", func(t *testing.T) {
got := container.NewMsg(nil).GetLogger()
if out := got.Writer().(*container.Suspendable).Downstream; out != log.Writer() {
t.Errorf("GetLogger: Downstream = %#v", out)
}
if prefix := got.Prefix(); prefix != "container: " {
t.Errorf("GetLogger: prefix = %q", prefix)
}
})
t.Run("takeover", func(t *testing.T) {
l := log.New(io.Discard, "\x00", 0xdeadbeef)
got := container.NewMsg(l)
if logger := got.GetLogger(); logger != l {
t.Errorf("GetLogger: %#v, want %#v", logger, l)
}
if ds := l.Writer().(*container.Suspendable).Downstream; ds != io.Discard {
t.Errorf("GetLogger: Downstream = %#v", ds)
}
})
})
dw := expectWriter{t: t}
steps := []struct {
name string
pt, next []byte
err error
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")
}
}},
{"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")
}
}},
{"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() { if !msg.IsVerbose() {
t.Error("IsVerbose unexpected outcome") t.Error("IsVerbose unexpected false")
}
})
t.Run("verbose", func(t *testing.T) {
log.SetOutput(panicWriter{})
msg.Suspend()
msg.Verbose()
msg.Verbosef("\x00")
msg.Resume()
buf := new(strings.Builder)
log.SetOutput(buf)
log.SetFlags(0)
msg.Verbose()
msg.Verbosef("\x00")
want := "\n\x00\n"
if buf.String() != want {
t.Errorf("Verbose: %q, want %q", buf.String(), want)
}
})
t.Run("inactive", func(t *testing.T) {
{
inactive := msg.Resume()
if inactive {
t.Cleanup(func() { msg.Suspend() })
}
} }
}},
{"resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) {
if msg.Resume() { if msg.Resume() {
t.Error("Resume unexpected outcome") t.Error("Resume unexpected success")
} }
}},
{"beforeExit noop", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
msg.BeforeExit()
}},
{"beforeExit suspend", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
msg.Suspend() 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")
}
}},
{"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() { if !msg.Resume() {
t.Error("Resume unexpected outcome") t.Error("Resume unexpected failure")
} }
}) }},
// the function is a noop {"suspend drop", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() }) msg.Suspend()
} }},
{"suspend write fill", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
type panicWriter struct{} msg.GetLogger().Print(strings.Repeat("\x00", suspendBufMax))
}},
func (panicWriter) Write([]byte) (int, error) { panic("unreachable") } {"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() {
func saveRestoreOutput(t *testing.T) { t.Error("Resume unexpected failure")
out := container.GetOutput()
t.Cleanup(func() { container.SetOutput(out) })
}
func replaceOutput(t *testing.T) {
saveRestoreOutput(t)
container.SetOutput(&testOutput{t: t})
}
type testOutput struct {
t *testing.T
suspended atomic.Bool
}
func (out *testOutput) IsVerbose() bool { return testing.Verbose() }
func (out *testOutput) Verbose(v ...any) {
if !out.IsVerbose() {
return
} }
out.t.Log(v...) }},
}
func (out *testOutput) Verbosef(format string, v ...any) {
if !out.IsVerbose() {
return
}
out.t.Logf(format, v...)
}
func (out *testOutput) Suspend() {
if out.suspended.CompareAndSwap(false, true) {
out.Verbose("suspend called")
return
}
out.Verbose("suspend called on suspended output")
}
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
}
func (out *testOutput) BeforeExit() { out.Verbose("beforeExit called") }
func TestGetSetOutput(t *testing.T) {
{
out := container.GetOutput()
t.Cleanup(func() { container.SetOutput(out) })
} }
t.Run("default", func(t *testing.T) { msg := container.NewMsg(log.New(&dw, "test: ", 0))
container.SetOutput(new(stubOutput)) for _, step := range steps {
if v, ok := container.GetOutput().(*container.DefaultMsg); ok { // these share the same writer, so cannot be subtests
t.Fatalf("SetOutput: got unexpected output %#v", v) 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))
} }
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())
}
})
} }
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") }

View File

@ -28,7 +28,7 @@ func TestSuspendable(t *testing.T) {
) )
// shares the same writer // shares the same writer
testCases := []struct { steps := []struct {
name string name string
w, pt []byte w, pt []byte
err error err error
@ -75,25 +75,25 @@ func TestSuspendable(t *testing.T) {
var dw expectWriter var dw expectWriter
w := container.Suspendable{Downstream: &dw} w := container.Suspendable{Downstream: &dw}
for _, tc := range testCases { for _, step := range steps {
// these share the same writer, so cannot be subtests // these share the same writer, so cannot be subtests
t.Logf("writing step %q", tc.name) t.Logf("writing step %q", step.name)
dw.expect, dw.err = tc.pt, tc.err dw.expect, dw.err = step.pt, step.err
var ( var (
gotN int gotN int
gotErr error gotErr error
) )
wantN := tc.n wantN := step.n
switch wantN { switch wantN {
case nSpecialPtEquiv: case nSpecialPtEquiv:
wantN = len(tc.pt) wantN = len(step.pt)
gotN, gotErr = w.Write(tc.w) gotN, gotErr = w.Write(step.w)
case nSpecialWEquiv: case nSpecialWEquiv:
wantN = len(tc.w) wantN = len(step.w)
gotN, gotErr = w.Write(tc.w) gotN, gotErr = w.Write(step.w)
case nSpecialSuspend: case nSpecialSuspend:
s := w.IsSuspended() s := w.IsSuspended()
@ -101,8 +101,8 @@ func TestSuspendable(t *testing.T) {
t.Fatal("Suspend: unexpected success") t.Fatal("Suspend: unexpected success")
} }
wantN = len(tc.w) wantN = len(step.w)
gotN, gotErr = w.Write(tc.w) gotN, gotErr = w.Write(step.w)
default: default:
if wantN <= nSpecialDump { if wantN <= nSpecialDump {
@ -118,10 +118,10 @@ func TestSuspendable(t *testing.T) {
t.Errorf("Resume: dropped = %d, want %d", dropped, wantDropped) t.Errorf("Resume: dropped = %d, want %d", dropped, wantDropped)
} }
wantN = len(tc.pt) wantN = len(step.pt)
gotN, gotErr = int(n), err gotN, gotErr = int(n), err
} else { } 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) 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) 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 { type expectWriter struct {
expect []byte expect []byte
err error 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) { 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 n, err = len(p), w.err
if w.expect == nil { 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) { 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 return
} }

View File

@ -2,7 +2,6 @@ package container
import ( import (
"bytes" "bytes"
"log"
"os" "os"
"strconv" "strconv"
"sync" "sync"
@ -22,26 +21,28 @@ const (
kernelCapLastCapPath = FHSProcSys + "kernel/cap_last_cap" kernelCapLastCapPath = FHSProcSys + "kernel/cap_last_cap"
) )
func mustReadSysctl() { func mustReadSysctl(msg Msg) {
sysctlOnce.Do(func() {
if v, err := os.ReadFile(kernelOverflowuidPath); err != nil { if v, err := os.ReadFile(kernelOverflowuidPath); err != nil {
log.Fatalf("cannot read %q: %v", kernelOverflowuidPath, err) msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowuidPath, err)
} else if kernelOverflowuid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { } else if kernelOverflowuid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
log.Fatalf("cannot interpret %q: %v", kernelOverflowuidPath, err) msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelOverflowuidPath, err)
} }
if v, err := os.ReadFile(kernelOverflowgidPath); err != nil { if v, err := os.ReadFile(kernelOverflowgidPath); err != nil {
log.Fatalf("cannot read %q: %v", kernelOverflowgidPath, err) msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowgidPath, err)
} else if kernelOverflowgid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { } else if kernelOverflowgid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
log.Fatalf("cannot interpret %q: %v", kernelOverflowgidPath, err) msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelOverflowgidPath, err)
} }
if v, err := os.ReadFile(kernelCapLastCapPath); err != nil { if v, err := os.ReadFile(kernelCapLastCapPath); err != nil {
log.Fatalf("cannot read %q: %v", kernelCapLastCapPath, err) msg.GetLogger().Fatalf("cannot read %q: %v", kernelCapLastCapPath, err)
} else if kernelCapLastCap, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { } else if kernelCapLastCap, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil {
log.Fatalf("cannot interpret %q: %v", kernelCapLastCapPath, err) msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelCapLastCapPath, err)
} }
})
} }
func OverflowUid() int { sysctlOnce.Do(mustReadSysctl); return kernelOverflowuid } func OverflowUid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowuid }
func OverflowGid() int { sysctlOnce.Do(mustReadSysctl); return kernelOverflowgid } func OverflowGid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowgid }
func LastCap() uintptr { sysctlOnce.Do(mustReadSysctl); return uintptr(kernelCapLastCap) } func LastCap(msg Msg) uintptr { mustReadSysctl(msg); return uintptr(kernelCapLastCap) }

View File

@ -16,6 +16,7 @@ import (
// New initialises a Helper instance with wt as the null-terminated argument writer. // New initialises a Helper instance with wt as the null-terminated argument writer.
func New( func New(
ctx context.Context, ctx context.Context,
msg container.Msg,
pathname *container.Absolute, name string, pathname *container.Absolute, name string,
wt io.WriterTo, wt io.WriterTo,
stat bool, stat bool,
@ -26,7 +27,7 @@ func New(
var args []string var args []string
h := new(helperContainer) h := new(helperContainer)
h.helperFiles, args = newHelperFiles(ctx, wt, stat, argF, extraFiles) h.helperFiles, args = newHelperFiles(ctx, wt, stat, argF, extraFiles)
h.Container = container.NewCommand(ctx, pathname, name, args...) h.Container = container.NewCommand(ctx, msg, pathname, name, args...)
h.WaitDelay = WaitDelay h.WaitDelay = WaitDelay
if cmdF != nil { if cmdF != nil {
cmdF(h.Container) cmdF(h.Container)

View File

@ -12,7 +12,7 @@ import (
func TestContainer(t *testing.T) { func TestContainer(t *testing.T) {
t.Run("start invalid container", func(t *testing.T) { t.Run("start invalid container", func(t *testing.T) {
h := helper.New(t.Context(), container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil) h := helper.New(t.Context(), nil, container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil)
wantErr := "container: starting an invalid container" wantErr := "container: starting an invalid container"
if err := h.Start(); err == nil || err.Error() != wantErr { if err := h.Start(); err == nil || err.Error() != wantErr {
@ -22,7 +22,7 @@ func TestContainer(t *testing.T) {
}) })
t.Run("valid new helper nil check", func(t *testing.T) { t.Run("valid new helper nil check", func(t *testing.T) {
if got := helper.New(t.Context(), container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil); got == nil { if got := helper.New(t.Context(), nil, container.MustAbs(container.Nonexistent), "hakurei", argsWt, false, argF, nil, nil); got == nil {
t.Errorf("New(%q, %q) got nil", t.Errorf("New(%q, %q) got nil",
argsWt, "hakurei") argsWt, "hakurei")
return return
@ -31,7 +31,7 @@ func TestContainer(t *testing.T) {
t.Run("implementation compliance", func(t *testing.T) { t.Run("implementation compliance", func(t *testing.T) {
testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper { testHelper(t, func(ctx context.Context, setOutput func(stdoutP, stderrP *io.Writer), stat bool) helper.Helper {
return helper.New(ctx, container.MustAbs(os.Args[0]), "helper", argsWt, stat, argF, func(z *container.Container) { return helper.New(ctx, nil, container.MustAbs(os.Args[0]), "helper", argsWt, stat, argF, func(z *container.Container) {
setOutput(&z.Stdout, &z.Stderr) setOutput(&z.Stdout, &z.Stderr)
z. z.
Bind(container.AbsFHSRoot, container.AbsFHSRoot, 0). Bind(container.AbsFHSRoot, container.AbsFHSRoot, 0).

View File

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

View File

@ -6,23 +6,24 @@ import (
"log" "log"
"os" "os"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
) )
// Main runs an app according to [hst.Config] and terminates. Main does not return. // Main runs an app according to [hst.Config] and terminates. Main does not return.
func Main(ctx context.Context, config *hst.Config) { func Main(ctx context.Context, msg container.Msg, config *hst.Config) {
var id state.ID var id state.ID
if err := state.NewAppID(&id); err != nil { if err := state.NewAppID(&id); err != nil {
log.Fatal(err) log.Fatal(err)
} }
seal := outcome{id: &stringPair[state.ID]{id, id.String()}, syscallDispatcher: direct{}} seal := outcome{id: &stringPair[state.ID]{id, id.String()}, syscallDispatcher: direct{}}
if err := seal.finalise(ctx, config); err != nil { if err := seal.finalise(ctx, msg, config); err != nil {
printMessageError("cannot seal app:", err) printMessageError("cannot seal app:", err)
os.Exit(1) os.Exit(1)
} }
seal.main() seal.main(msg)
panic("unreachable") panic("unreachable")
} }

View File

@ -6,6 +6,8 @@ import (
"log" "log"
"os/exec" "os/exec"
"os/user" "os/user"
"hakurei.app/container"
) )
type stubNixOS struct { type stubNixOS struct {
@ -187,10 +189,10 @@ func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
} }
} }
func (k *stubNixOS) overflowUid() int { return 65534 } func (k *stubNixOS) overflowUid(container.Msg) int { return 65534 }
func (k *stubNixOS) overflowGid() int { return 65534 } func (k *stubNixOS) overflowGid(container.Msg) int { return 65534 }
func (k *stubNixOS) mustHsuPath() string { return "/proc/nonexistent/hsu" } func (k *stubNixOS) mustHsuPath() *container.Absolute { return m("/proc/nonexistent/hsu") }
func (k *stubNixOS) fatalf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) } func (k *stubNixOS) fatalf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) }

View File

@ -37,7 +37,7 @@ func TestApp(t *testing.T) {
0xbd, 0x01, 0x78, 0x0e, 0xbd, 0x01, 0x78, 0x0e,
0xb9, 0xa6, 0x07, 0xac, 0xb9, 0xa6, 0x07, 0xac,
}, },
system.New(context.TODO(), 1000000). system.New(context.TODO(), container.NewMsg(nil), 1000000).
Ensure("/tmp/hakurei.0", 0711). Ensure("/tmp/hakurei.0", 0711).
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute). Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
Ensure("/tmp/hakurei.0/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/0", acl.Read, acl.Write, acl.Execute). Ensure("/tmp/hakurei.0/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/0", acl.Read, acl.Write, acl.Execute).
@ -129,7 +129,7 @@ func TestApp(t *testing.T) {
0x82, 0xd4, 0x13, 0x36, 0x82, 0xd4, 0x13, 0x36,
0x9b, 0x64, 0xce, 0x7c, 0x9b, 0x64, 0xce, 0x7c,
}, },
system.New(context.TODO(), 1000009). system.New(context.TODO(), container.NewMsg(nil), 1000009).
Ensure("/tmp/hakurei.0", 0711). Ensure("/tmp/hakurei.0", 0711).
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute). Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
Ensure("/tmp/hakurei.0/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/9", acl.Read, acl.Write, acl.Execute). Ensure("/tmp/hakurei.0/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/9", acl.Read, acl.Write, acl.Execute).
@ -280,7 +280,7 @@ func TestApp(t *testing.T) {
0x4c, 0xf0, 0x73, 0xbd, 0x4c, 0xf0, 0x73, 0xbd,
0xb4, 0x6e, 0xb5, 0xc1, 0xb4, 0x6e, 0xb5, 0xc1,
}, },
system.New(context.TODO(), 1000001). system.New(context.TODO(), container.NewMsg(nil), 1000001).
Ensure("/tmp/hakurei.0", 0711). Ensure("/tmp/hakurei.0", 0711).
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute). Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
Ensure("/tmp/hakurei.0/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/1", acl.Read, acl.Write, acl.Execute). Ensure("/tmp/hakurei.0/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/1", acl.Read, acl.Write, acl.Execute).
@ -377,7 +377,7 @@ func TestApp(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Run("finalise", func(t *testing.T) { t.Run("finalise", func(t *testing.T) {
seal := outcome{syscallDispatcher: tc.k, id: &stringPair[state.ID]{tc.id, tc.id.String()}} seal := outcome{syscallDispatcher: tc.k, id: &stringPair[state.ID]{tc.id, tc.id.String()}}
err := seal.finalise(t.Context(), tc.config) err := seal.finalise(t.Context(), container.NewMsg(nil), tc.config)
if err != nil { if err != nil {
if s, ok := container.GetErrorMessage(err); !ok { if s, ok := container.GetErrorMessage(err); !ok {
t.Fatalf("Seal: error = %v", err) t.Fatalf("Seal: error = %v", err)

View File

@ -20,6 +20,7 @@ const preallocateOpsCount = 1 << 5
// newContainer initialises [container.Params] via [hst.ContainerConfig]. // newContainer initialises [container.Params] via [hst.ContainerConfig].
// Note that remaining container setup must be queued by the caller. // Note that remaining container setup must be queued by the caller.
func newContainer( func newContainer(
msg container.Msg,
k syscallDispatcher, k syscallDispatcher,
s *hst.ContainerConfig, s *hst.ContainerConfig,
prefix string, prefix string,
@ -73,8 +74,8 @@ func newContainer(
params.Gid = k.getgid() params.Gid = k.getgid()
*gid = params.Gid *gid = params.Gid
} else { } else {
*uid = k.overflowUid() *uid = k.overflowUid(msg)
*gid = k.overflowGid() *gid = k.overflowGid(msg)
} }
filesystem := s.Filesystem filesystem := s.Filesystem
@ -126,11 +127,11 @@ func newContainer(
// get parent dir of socket // get parent dir of socket
dir := path.Dir(pair[1]) dir := path.Dir(pair[1])
if dir == "." || dir == container.FHSRoot { if dir == "." || dir == container.FHSRoot {
k.verbosef("dbus socket %q is in an unusual location", pair[1]) msg.Verbosef("dbus socket %q is in an unusual location", pair[1])
} }
hidePaths = append(hidePaths, dir) hidePaths = append(hidePaths, dir)
} else { } else {
k.verbosef("dbus socket %q is not absolute", pair[1]) msg.Verbosef("dbus socket %q is not absolute", pair[1])
} }
} }
} }
@ -138,7 +139,7 @@ func newContainer(
} }
hidePathMatch := make([]bool, len(hidePaths)) hidePathMatch := make([]bool, len(hidePaths))
for i := range hidePaths { for i := range hidePaths {
if err := evalSymlinks(k, &hidePaths[i]); err != nil { if err := evalSymlinks(msg, k, &hidePaths[i]); err != nil {
return nil, nil, err return nil, nil, err
} }
} }
@ -178,7 +179,7 @@ func newContainer(
if autoroot != nil { if autoroot != nil {
for _, ent := range autoRootEntries { for _, ent := range autoRootEntries {
name := ent.Name() name := ent.Name()
if container.IsAutoRootBindable(name) { if container.IsAutoRootBindable(msg, name) {
hidePathSource = append(hidePathSource, autoroot.Source.Append(name)) hidePathSource = append(hidePathSource, autoroot.Source.Append(name))
} }
} }
@ -193,7 +194,7 @@ func newContainer(
} }
hidePathSourceEval[i] = [2]string{a.String(), a.String()} hidePathSourceEval[i] = [2]string{a.String(), a.String()}
if err := evalSymlinks(k, &hidePathSourceEval[i][0]); err != nil { if err := evalSymlinks(msg, k, &hidePathSourceEval[i][0]); err != nil {
return nil, nil, err return nil, nil, err
} }
} }
@ -209,7 +210,7 @@ func newContainer(
return nil, nil, err return nil, nil, err
} else if ok { } else if ok {
hidePathMatch[i] = true hidePathMatch[i] = true
k.verbosef("hiding path %q from %q", hidePaths[i], p[1]) msg.Verbosef("hiding path %q from %q", hidePaths[i], p[1])
} }
} }
} }
@ -241,12 +242,12 @@ func newContainer(
} }
// evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist]. // evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
func evalSymlinks(k syscallDispatcher, v *string) error { func evalSymlinks(msg container.Msg, k syscallDispatcher, v *string) error {
if p, err := k.evalSymlinks(*v); err != nil { if p, err := k.evalSymlinks(*v); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return err return err
} }
k.verbosef("path %q does not yet exist", *v) msg.Verbosef("path %q does not yet exist", *v)
} else { } else {
*v = p *v = p
} }

View File

@ -9,7 +9,6 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/internal/hlog"
) )
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour. // syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
@ -45,19 +44,15 @@ type syscallDispatcher interface {
cmdOutput(cmd *exec.Cmd) ([]byte, error) cmdOutput(cmd *exec.Cmd) ([]byte, error)
// overflowUid provides [container.OverflowUid]. // overflowUid provides [container.OverflowUid].
overflowUid() int overflowUid(msg container.Msg) int
// overflowGid provides [container.OverflowGid]. // overflowGid provides [container.OverflowGid].
overflowGid() int overflowGid(msg container.Msg) int
// mustHsuPath provides [internal.MustHsuPath]. // mustHsuPath provides [internal.MustHsuPath].
mustHsuPath() string mustHsuPath() *container.Absolute
// fatalf provides [log.Fatalf]. // fatalf provides [log.Fatalf].
fatalf(format string, v ...any) fatalf(format string, v ...any)
isVerbose() bool
verbose(v ...any)
verbosef(format string, v ...any)
} }
// direct implements syscallDispatcher on the current kernel. // direct implements syscallDispatcher on the current kernel.
@ -87,13 +82,9 @@ func (direct) lookupGroupId(name string) (gid string, err error) {
func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() } func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() }
func (direct) overflowUid() int { return container.OverflowUid() } func (direct) overflowUid(msg container.Msg) int { return container.OverflowUid(msg) }
func (direct) overflowGid() int { return container.OverflowGid() } func (direct) overflowGid(msg container.Msg) int { return container.OverflowGid(msg) }
func (direct) mustHsuPath() string { return internal.MustHsuPath() } func (direct) mustHsuPath() *container.Absolute { return internal.MustHsuPath() }
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) } func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
func (k direct) isVerbose() bool { return hlog.Load() }
func (direct) verbose(v ...any) { hlog.Verbose(v...) }
func (direct) verbosef(format string, v ...any) { hlog.Verbosef(format, v...) }

View File

@ -20,7 +20,6 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
"hakurei.app/system" "hakurei.app/system"
"hakurei.app/system/acl" "hakurei.app/system/acl"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
@ -120,7 +119,7 @@ type hsuUser struct {
username string username string
} }
func (k *outcome) finalise(ctx context.Context, config *hst.Config) error { func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.Config) error {
const ( const (
home = "HOME" home = "HOME"
shell = "SHELL" shell = "SHELL"
@ -183,7 +182,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
} else if !isValidUsername(k.user.username) { } else if !isValidUsername(k.user.username) {
return newWithMessage(fmt.Sprintf("invalid user name %q", k.user.username)) return newWithMessage(fmt.Sprintf("invalid user name %q", k.user.username))
} }
k.user.uid = newInt(HsuUid(hsu.MustID(), k.user.identity.unwrap())) k.user.uid = newInt(HsuUid(hsu.MustIDMsg(msg), k.user.identity.unwrap()))
k.user.supp = make([]string, len(config.Groups)) k.user.supp = make([]string, len(config.Groups))
for i, name := range config.Groups { for i, name := range config.Groups {
@ -201,7 +200,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
// permissive defaults // permissive defaults
if config.Container == nil { if config.Container == nil {
hlog.Verbose("container configuration not supplied, PROCEED WITH CAUTION") msg.Verbose("container configuration not supplied, PROCEED WITH CAUTION")
if config.Shell == nil { if config.Shell == nil {
config.Shell = container.AbsFHSRoot.Append("bin", "sh") config.Shell = container.AbsFHSRoot.Append("bin", "sh")
@ -276,13 +275,14 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
// TODO(ophestra): revert this after params to shim // TODO(ophestra): revert this after params to shim
share := &shareHost{seal: k} share := &shareHost{seal: k}
copyPaths(k.syscallDispatcher, &share.sc, hsu.MustID()) copyPaths(k.syscallDispatcher, msg, &share.sc, hsu.MustIDMsg(msg))
msg.Verbosef("process share directory at %q, runtime directory at %q", share.sc.SharePath, share.sc.RunDirPath)
var mapuid, mapgid *stringPair[int] var mapuid, mapgid *stringPair[int]
{ {
var uid, gid int var uid, gid int
var err error var err error
k.container, k.env, err = newContainer(k, config.Container, k.id.String(), &share.sc, &uid, &gid) k.container, k.env, err = newContainer(msg, k, config.Container, k.id.String(), &share.sc, &uid, &gid)
k.waitDelay = config.Container.WaitDelay k.waitDelay = config.Container.WaitDelay
if err != nil { if err != nil {
return &hst.AppError{Step: "initialise container configuration", Err: err} return &hst.AppError{Step: "initialise container configuration", Err: err}
@ -307,7 +307,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
k.env[xdgSessionType] = "tty" k.env[xdgSessionType] = "tty"
k.runDirPath = share.sc.RunDirPath k.runDirPath = share.sc.RunDirPath
k.sys = system.New(k.ctx, k.user.uid.unwrap()) k.sys = system.New(k.ctx, msg, k.user.uid.unwrap())
k.sys.Ensure(share.sc.SharePath.String(), 0711) k.sys.Ensure(share.sc.SharePath.String(), 0711)
{ {
@ -357,7 +357,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
// outer wayland socket (usually `/run/user/%d/wayland-%d`) // outer wayland socket (usually `/run/user/%d/wayland-%d`)
var socketPath *container.Absolute var socketPath *container.Absolute
if name, ok := k.lookupEnv(wayland.WaylandDisplay); !ok { if name, ok := k.lookupEnv(wayland.WaylandDisplay); !ok {
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName) msg.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName) socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
} else if a, err := container.NewAbs(name); err != nil { } else if a, err := container.NewAbs(name); err != nil {
socketPath = share.sc.RuntimePath.Append(name) socketPath = share.sc.RuntimePath.Append(name)
@ -379,7 +379,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
k.sys.Wayland(&k.sync, outerPath.String(), socketPath.String(), appID, k.id.String()) k.sys.Wayland(&k.sync, outerPath.String(), socketPath.String(), appID, k.id.String())
k.container.Bind(outerPath, innerPath, 0) k.container.Bind(outerPath, innerPath, 0)
} else { // bind mount wayland socket (insecure) } else { // bind mount wayland socket (insecure)
hlog.Verbose("direct wayland access, PROCEED WITH CAUTION") msg.Verbose("direct wayland access, PROCEED WITH CAUTION")
share.ensureRuntimeDir() share.ensureRuntimeDir()
k.container.Bind(socketPath, innerPath, 0) k.container.Bind(socketPath, innerPath, 0)
k.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute) k.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
@ -520,7 +520,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
k.container.PlaceP(innerDst, &payload) k.container.PlaceP(innerDst, &payload)
k.sys.CopyFile(payload, paCookiePath.String(), 256, 256) k.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
} else { } else {
hlog.Verbose("cannot locate PulseAudio cookie (tried " + msg.Verbose("cannot locate PulseAudio cookie (tried " +
"$PULSE_COOKIE, " + "$PULSE_COOKIE, " +
"$XDG_CONFIG_HOME/pulse/cookie, " + "$XDG_CONFIG_HOME/pulse/cookie, " +
"$HOME/.pulse-cookie)") "$HOME/.pulse-cookie)")
@ -596,8 +596,8 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
} }
slices.Sort(k.container.Env) slices.Sort(k.container.Env)
if hlog.Load() { if msg.IsVerbose() {
hlog.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d", msg.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d",
k.user.uid, k.user.username, config.Groups, k.container.Args, len(*k.container.Ops)) k.user.uid, k.user.username, config.Groups, k.container.Args, len(*k.container.Ops))
} }

View File

@ -11,7 +11,6 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/hlog"
) )
// Hsu caches responses from cmd/hsu. // Hsu caches responses from cmd/hsu.
@ -40,7 +39,7 @@ func (h *Hsu) ID() (int, error) {
h.ensureDispatcher() h.ensureDispatcher()
h.idOnce.Do(func() { h.idOnce.Do(func() {
h.id = -1 h.id = -1
hsuPath := h.k.mustHsuPath() hsuPath := h.k.mustHsuPath().String()
cmd := exec.Command(hsuPath) cmd := exec.Command(hsuPath)
cmd.Path = hsuPath cmd.Path = hsuPath
@ -71,7 +70,10 @@ func (h *Hsu) ID() (int, error) {
} }
// MustID calls [Hsu.ID] and terminates on error. // MustID calls [Hsu.ID] and terminates on error.
func (h *Hsu) MustID() int { func (h *Hsu) MustID() int { return h.MustIDMsg(nil) }
// MustIDMsg implements MustID with a custom [container.Msg].
func (h *Hsu) MustIDMsg(msg container.Msg) int {
id, err := h.ID() id, err := h.ID()
if err == nil { if err == nil {
return id return id
@ -79,7 +81,9 @@ func (h *Hsu) MustID() int {
const fallback = "cannot retrieve user id from setuid wrapper:" const fallback = "cannot retrieve user id from setuid wrapper:"
if errors.Is(err, ErrHsuAccess) { if errors.Is(err, ErrHsuAccess) {
hlog.Verbose("*"+fallback, err) if msg != nil {
msg.Verbose("*"+fallback, err)
}
os.Exit(1) os.Exit(1)
return -0xdeadbeef return -0xdeadbeef
} else if m, ok := container.GetErrorMessage(err); ok { } else if m, ok := container.GetErrorMessage(err); ok {

View File

@ -8,10 +8,10 @@ import (
) )
// CopyPaths populates a [hst.Paths] struct. // CopyPaths populates a [hst.Paths] struct.
func CopyPaths(v *hst.Paths, userid int) { copyPaths(direct{}, v, userid) } func CopyPaths(msg container.Msg, v *hst.Paths, userid int) { copyPaths(direct{}, msg, v, userid) }
// copyPaths populates a [hst.Paths] struct. // copyPaths populates a [hst.Paths] struct.
func copyPaths(k syscallDispatcher, v *hst.Paths, userid int) { func copyPaths(k syscallDispatcher, msg container.Msg, v *hst.Paths, userid int) {
const xdgRuntimeDir = "XDG_RUNTIME_DIR" const xdgRuntimeDir = "XDG_RUNTIME_DIR"
if tempDir, err := container.NewAbs(k.tempdir()); err != nil { if tempDir, err := container.NewAbs(k.tempdir()); err != nil {
@ -21,7 +21,7 @@ func copyPaths(k syscallDispatcher, v *hst.Paths, userid int) {
} }
v.SharePath = v.TempDir.Append("hakurei." + strconv.Itoa(userid)) v.SharePath = v.TempDir.Append("hakurei." + strconv.Itoa(userid))
k.verbosef("process share directory at %q", v.SharePath) msg.Verbosef("process share directory at %q", v.SharePath)
r, _ := k.lookupEnv(xdgRuntimeDir) r, _ := k.lookupEnv(xdgRuntimeDir)
if a, err := container.NewAbs(r); err != nil { if a, err := container.NewAbs(r); err != nil {
@ -32,5 +32,5 @@ func copyPaths(k syscallDispatcher, v *hst.Paths, userid int) {
v.RuntimePath = a v.RuntimePath = a
v.RunDirPath = v.RuntimePath.Append("hakurei") v.RunDirPath = v.RuntimePath.Append("hakurei")
} }
k.verbosef("runtime directory at %q", v.RunDirPath) msg.Verbosef("runtime directory at %q", v.RunDirPath)
} }

View File

@ -15,7 +15,6 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
"hakurei.app/system" "hakurei.app/system"
) )
@ -39,6 +38,7 @@ type mainState struct {
cmdWait chan error cmdWait chan error
k *outcome k *outcome
container.Msg
uintptr uintptr
} }
@ -55,7 +55,7 @@ func (ms mainState) beforeExit(isFault bool) {
panic("attempting to call beforeExit twice") panic("attempting to call beforeExit twice")
} }
ms.done = true ms.done = true
defer hlog.BeforeExit() defer ms.BeforeExit()
if isFault && ms.cancel != nil { if isFault && ms.cancel != nil {
ms.cancel() ms.cancel()
@ -97,37 +97,37 @@ func (ms mainState) beforeExit(isFault bool) {
} }
} }
if hlog.Load() { if ms.IsVerbose() {
if !ok { if !ok {
if err != nil { if err != nil {
hlog.Verbosef("wait: %v", err) ms.Verbosef("wait: %v", err)
} }
} else { } else {
switch { switch {
case wstatus.Exited(): case wstatus.Exited():
hlog.Verbosef("process %d exited with code %d", ms.cmd.Process.Pid, wstatus.ExitStatus()) ms.Verbosef("process %d exited with code %d", ms.cmd.Process.Pid, wstatus.ExitStatus())
case wstatus.CoreDump(): case wstatus.CoreDump():
hlog.Verbosef("process %d dumped core", ms.cmd.Process.Pid) ms.Verbosef("process %d dumped core", ms.cmd.Process.Pid)
case wstatus.Signaled(): case wstatus.Signaled():
hlog.Verbosef("process %d got %s", ms.cmd.Process.Pid, wstatus.Signal()) ms.Verbosef("process %d got %s", ms.cmd.Process.Pid, wstatus.Signal())
default: default:
hlog.Verbosef("process %d exited with status %#x", ms.cmd.Process.Pid, wstatus) ms.Verbosef("process %d exited with status %#x", ms.cmd.Process.Pid, wstatus)
} }
} }
} }
case <-waitDone: case <-waitDone:
hlog.Resume() ms.Resume()
// this is only reachable when shim did not exit within shimWaitTimeout, after its WaitDelay has elapsed. // this is only reachable when shim did not exit within shimWaitTimeout, after its WaitDelay has elapsed.
// This is different from the container failing to terminate within its timeout period, as that is enforced // This is different from the container failing to terminate within its timeout period, as that is enforced
// by the shim. This path is instead reached when there is a lockup in shim preventing it from completing. // by the shim. This path is instead reached when there is a lockup in shim preventing it from completing.
log.Printf("process %d did not terminate", ms.cmd.Process.Pid) log.Printf("process %d did not terminate", ms.cmd.Process.Pid)
} }
hlog.Resume() ms.Resume()
if ms.k.sync != nil { if ms.k.sync != nil {
if err := ms.k.sync.Close(); err != nil { if err := ms.k.sync.Close(); err != nil {
perror(err, "close wayland security context") perror(err, "close wayland security context")
@ -170,7 +170,7 @@ func (ms mainState) beforeExit(isFault bool) {
if l := len(states); l == 0 { if l := len(states); l == 0 {
ec |= system.User ec |= system.User
} else { } else {
hlog.Verbosef("found %d instances, cleaning up without user-scoped operations", l) ms.Verbosef("found %d instances, cleaning up without user-scoped operations", l)
} }
// accumulate enablements of remaining launchers // accumulate enablements of remaining launchers
@ -183,9 +183,9 @@ func (ms mainState) beforeExit(isFault bool) {
} }
ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse) ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse)
if hlog.Load() { if ms.IsVerbose() {
if ec > 0 { if ec > 0 {
hlog.Verbose("reverting operations scope", system.TypeString(ec)) ms.Verbose("reverting operations scope", system.TypeString(ec))
} }
} }
@ -219,7 +219,7 @@ func (ms mainState) fatal(fallback string, ferr error) {
} }
// main carries out outcome and terminates. main does not return. // main carries out outcome and terminates. main does not return.
func (k *outcome) main() { func (k *outcome) main(msg container.Msg) {
if !k.active.CompareAndSwap(false, true) { if !k.active.CompareAndSwap(false, true) {
panic("outcome: attempted to run twice") panic("outcome: attempted to run twice")
} }
@ -228,19 +228,19 @@ func (k *outcome) main() {
hsuPath := internal.MustHsuPath() hsuPath := internal.MustHsuPath()
// ms.beforeExit required beyond this point // ms.beforeExit required beyond this point
ms := &mainState{k: k} ms := &mainState{Msg: msg, k: k}
if err := k.sys.Commit(); err != nil { if err := k.sys.Commit(); err != nil {
ms.fatal("cannot commit system setup:", err) ms.fatal("cannot commit system setup:", err)
} }
ms.uintptr |= mainNeedsRevert ms.uintptr |= mainNeedsRevert
ms.store = state.NewMulti(k.runDirPath.String()) ms.store = state.NewMulti(msg, k.runDirPath.String())
ctx, cancel := context.WithCancel(k.ctx) ctx, cancel := context.WithCancel(k.ctx)
defer cancel() defer cancel()
ms.cancel = cancel ms.cancel = cancel
ms.cmd = exec.CommandContext(ctx, hsuPath) ms.cmd = exec.CommandContext(ctx, hsuPath.String())
ms.cmd.Stdin, ms.cmd.Stdout, ms.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr ms.cmd.Stdin, ms.cmd.Stdout, ms.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
ms.cmd.Dir = container.FHSRoot // container init enters final working directory ms.cmd.Dir = container.FHSRoot // container init enters final working directory
// shim runs in the same session as monitor; see shim.go for behaviour // shim runs in the same session as monitor; see shim.go for behaviour
@ -260,13 +260,13 @@ func (k *outcome) main() {
} }
if len(k.user.supp) > 0 { if len(k.user.supp) > 0 {
hlog.Verbosef("attaching supplementary group ids %s", k.user.supp) msg.Verbosef("attaching supplementary group ids %s", k.user.supp)
// interpreted by hsu // interpreted by hsu
ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(k.user.supp, " ")) ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(k.user.supp, " "))
} }
hlog.Verbosef("setuid helper at %s", hsuPath) msg.Verbosef("setuid helper at %s", hsuPath)
hlog.Suspend() msg.Suspend()
if err := ms.cmd.Start(); err != nil { if err := ms.cmd.Start(); err != nil {
ms.fatal("cannot start setuid wrapper:", err) ms.fatal("cannot start setuid wrapper:", err)
} }
@ -286,18 +286,18 @@ func (k *outcome) main() {
os.Getpid(), os.Getpid(),
k.waitDelay, k.waitDelay,
k.container, k.container,
hlog.Load(), msg.IsVerbose(),
}) })
}() }()
return return
}(): }():
if err != nil { if err != nil {
hlog.Resume() msg.Resume()
ms.fatal("cannot transmit shim config:", err) ms.fatal("cannot transmit shim config:", err)
} }
case <-ctx.Done(): case <-ctx.Done():
hlog.Resume() msg.Resume()
ms.fatal("shim context canceled:", newWithMessageError("shim setup canceled", ctx.Err())) ms.fatal("shim context canceled:", newWithMessageError("shim setup canceled", ctx.Err()))
} }

View File

@ -15,8 +15,6 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
) )
//#include "shim-signal.h" //#include "shim-signal.h"
@ -53,7 +51,9 @@ const (
// ShimMain is the main function of the shim process and runs as the unconstrained target user. // ShimMain is the main function of the shim process and runs as the unconstrained target user.
func ShimMain() { func ShimMain() {
hlog.Prepare("shim") log.SetPrefix("shim: ")
log.SetFlags(0)
msg := container.NewMsg(log.Default())
if err := container.SetDumpable(container.SUID_DUMP_DISABLE); err != nil { if err := container.SetDumpable(container.SUID_DUMP_DISABLE); err != nil {
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
@ -73,7 +73,7 @@ func ShimMain() {
log.Fatalf("cannot receive shim setup params: %v", err) log.Fatalf("cannot receive shim setup params: %v", err)
} else { } else {
internal.InstallOutput(params.Verbose) msg.SwapVerbose(params.Verbose)
closeSetup = f closeSetup = f
} }
@ -111,12 +111,12 @@ func ShimMain() {
} }
// setup has not completed, terminate immediately // setup has not completed, terminate immediately
hlog.Resume() msg.Resume()
os.Exit(ShimExitRequest) os.Exit(ShimExitRequest)
return return
case 1: // got SIGCONT after adoption: monitor died before delivering signal case 1: // got SIGCONT after adoption: monitor died before delivering signal
hlog.BeforeExit() msg.BeforeExit()
os.Exit(ShimExitOrphan) os.Exit(ShimExitOrphan)
return return
@ -144,7 +144,7 @@ func ShimMain() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
cancelContainer.Store(&stop) cancelContainer.Store(&stop)
z := container.New(ctx) z := container.New(ctx, msg)
z.Params = *params.Container z.Params = *params.Container
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr

View File

@ -13,8 +13,8 @@ import (
"sync" "sync"
"syscall" "syscall"
"hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/hlog"
) )
// fine-grained locking and access // fine-grained locking and access
@ -24,16 +24,17 @@ type multiStore struct {
// initialised backends // initialised backends
backends *sync.Map backends *sync.Map
lock sync.RWMutex msg container.Msg
mu sync.RWMutex
} }
func (s *multiStore) Do(identity int, f func(c Cursor)) (bool, error) { func (s *multiStore) Do(identity int, f func(c Cursor)) (bool, error) {
s.lock.RLock() s.mu.RLock()
defer s.lock.RUnlock() defer s.mu.RUnlock()
// load or initialise new backend // load or initialise new backend
b := new(multiBackend) b := new(multiBackend)
b.lock.Lock() b.mu.Lock()
if v, ok := s.backends.LoadOrStore(identity, b); ok { if v, ok := s.backends.LoadOrStore(identity, b); ok {
b = v.(*multiBackend) b = v.(*multiBackend)
} else { } else {
@ -52,7 +53,7 @@ func (s *multiStore) Do(identity int, f func(c Cursor)) (bool, error) {
} else { } else {
b.lockfile = l b.lockfile = l
} }
b.lock.Unlock() b.mu.Unlock()
} }
// lock backend // lock backend
@ -85,17 +86,17 @@ func (s *multiStore) List() ([]int, error) {
for _, e := range entries { for _, e := range entries {
// skip non-directories // skip non-directories
if !e.IsDir() { if !e.IsDir() {
hlog.Verbosef("skipped non-directory entry %q", e.Name()) s.msg.Verbosef("skipped non-directory entry %q", e.Name())
continue continue
} }
// skip non-numerical names // skip non-numerical names
if v, err := strconv.Atoi(e.Name()); err != nil { if v, err := strconv.Atoi(e.Name()); err != nil {
hlog.Verbosef("skipped non-aid entry %q", e.Name()) s.msg.Verbosef("skipped non-aid entry %q", e.Name())
continue continue
} else { } else {
if v < 0 || v > 9999 { if v < 0 || v > 9999 {
hlog.Verbosef("skipped out of bounds entry %q", e.Name()) s.msg.Verbosef("skipped out of bounds entry %q", e.Name())
continue continue
} }
@ -107,8 +108,8 @@ func (s *multiStore) List() ([]int, error) {
} }
func (s *multiStore) Close() error { func (s *multiStore) Close() error {
s.lock.Lock() s.mu.Lock()
defer s.lock.Unlock() defer s.mu.Unlock()
var errs []error var errs []error
s.backends.Range(func(_, value any) bool { s.backends.Range(func(_, value any) bool {
@ -126,7 +127,7 @@ type multiBackend struct {
// created/opened by prepare // created/opened by prepare
lockfile *os.File lockfile *os.File
lock sync.RWMutex mu sync.RWMutex
} }
func (b *multiBackend) filename(id *ID) string { func (b *multiBackend) filename(id *ID) string {
@ -169,8 +170,8 @@ func (b *multiBackend) unlockFile() error {
// reads all launchers in simpleBackend // reads all launchers in simpleBackend
// file contents are ignored if decode is false // file contents are ignored if decode is false
func (b *multiBackend) load(decode bool) (Entries, error) { func (b *multiBackend) load(decode bool) (Entries, error) {
b.lock.RLock() b.mu.RLock()
defer b.lock.RUnlock() defer b.mu.RUnlock()
// read directory contents, should only contain files named after ids // read directory contents, should only contain files named after ids
var entries []os.DirEntry var entries []os.DirEntry
@ -280,8 +281,8 @@ func (b *multiBackend) decodeState(r io.ReadSeeker, state *State) error {
// Save writes process state to filesystem // Save writes process state to filesystem
func (b *multiBackend) Save(state *State, configWriter io.WriterTo) error { func (b *multiBackend) Save(state *State, configWriter io.WriterTo) error {
b.lock.Lock() b.mu.Lock()
defer b.lock.Unlock() defer b.mu.Unlock()
if configWriter == nil && state.Config == nil { if configWriter == nil && state.Config == nil {
return ErrNoConfig return ErrNoConfig
@ -336,8 +337,8 @@ func (b *multiBackend) encodeState(w io.WriteSeeker, state *State, configWriter
} }
func (b *multiBackend) Destroy(id ID) error { func (b *multiBackend) Destroy(id ID) error {
b.lock.Lock() b.mu.Lock()
defer b.lock.Unlock() defer b.mu.Unlock()
return os.Remove(b.filename(&id)) return os.Remove(b.filename(&id))
} }
@ -353,8 +354,8 @@ func (b *multiBackend) Len() (int, error) {
} }
func (b *multiBackend) close() error { func (b *multiBackend) close() error {
b.lock.Lock() b.mu.Lock()
defer b.lock.Unlock() defer b.mu.Unlock()
err := b.lockfile.Close() err := b.lockfile.Close()
if err == nil || errors.Is(err, os.ErrInvalid) || errors.Is(err, os.ErrClosed) { if err == nil || errors.Is(err, os.ErrInvalid) || errors.Is(err, os.ErrClosed) {
@ -364,9 +365,10 @@ func (b *multiBackend) close() error {
} }
// NewMulti returns an instance of the multi-file store. // NewMulti returns an instance of the multi-file store.
func NewMulti(runDir string) Store { func NewMulti(msg container.Msg, runDir string) Store {
b := new(multiStore) return &multiStore{
b.base = path.Join(runDir, "state") msg: msg,
b.backends = new(sync.Map) base: path.Join(runDir, "state"),
return b backends: new(sync.Map),
}
} }

View File

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

View File

@ -19,9 +19,9 @@ type Store interface {
// Cursor provided to f becomes invalid as soon as f returns. // Cursor provided to f becomes invalid as soon as f returns.
Do(identity int, f func(c Cursor)) (ok bool, err error) Do(identity int, f func(c Cursor)) (ok bool, err error)
// List queries the store and returns a list of aids known to the store. // List queries the store and returns a list of identities known to the store.
// Note that some or all returned aids might not have any active apps. // Note that some or all returned identities might not have any active apps.
List() (aids []int, err error) List() (identities []int, err error)
// Close releases any resources held by Store. // Close releases any resources held by Store.
Close() error Close() error

View File

@ -16,10 +16,10 @@ import (
func testStore(t *testing.T, s state.Store) { func testStore(t *testing.T, s state.Store) {
t.Run("list empty store", func(t *testing.T) { t.Run("list empty store", func(t *testing.T) {
if aids, err := s.List(); err != nil { if identities, err := s.List(); err != nil {
t.Fatalf("List: error = %v", err) t.Fatalf("List: error = %v", err)
} else if len(aids) != 0 { } else if len(identities) != 0 {
t.Fatalf("List: aids = %#v", aids) t.Fatalf("List: identities = %#v", identities)
} }
}) })
@ -39,22 +39,22 @@ func testStore(t *testing.T, s state.Store) {
makeState(t, &tc[i].state, &tc[i].ct) makeState(t, &tc[i].state, &tc[i].ct)
} }
do := func(aid int, f func(c state.Cursor)) { do := func(identity int, f func(c state.Cursor)) {
if ok, err := s.Do(aid, f); err != nil { if ok, err := s.Do(identity, f); err != nil {
t.Fatalf("Do: ok = %v, error = %v", ok, err) t.Fatalf("Do: ok = %v, error = %v", ok, err)
} }
} }
insert := func(i, aid int) { insert := func(i, identity int) {
do(aid, func(c state.Cursor) { do(identity, func(c state.Cursor) {
if err := c.Save(&tc[i].state, &tc[i].ct); err != nil { if err := c.Save(&tc[i].state, &tc[i].ct); err != nil {
t.Fatalf("Save(&tc[%v]): error = %v", i, err) t.Fatalf("Save(&tc[%v]): error = %v", i, err)
} }
}) })
} }
check := func(i, aid int) { check := func(i, identity int) {
do(aid, func(c state.Cursor) { do(identity, func(c state.Cursor) {
if entries, err := c.Load(); err != nil { if entries, err := c.Load(); err != nil {
t.Fatalf("Load: error = %v", err) t.Fatalf("Load: error = %v", err)
} else if got, ok := entries[tc[i].state.ID]; !ok { } else if got, ok := entries[tc[i].state.ID]; !ok {
@ -81,7 +81,7 @@ func testStore(t *testing.T, s state.Store) {
insert(insertEntryNoCheck, 0) insert(insertEntryNoCheck, 0)
}) })
t.Run("insert entry different aid", func(t *testing.T) { t.Run("insert entry different identity", func(t *testing.T) {
insert(insertEntryOtherApp, 1) insert(insertEntryOtherApp, 1)
check(insertEntryOtherApp, 1) check(insertEntryOtherApp, 1)
}) })
@ -90,14 +90,14 @@ func testStore(t *testing.T, s state.Store) {
check(insertEntryNoCheck, 0) check(insertEntryNoCheck, 0)
}) })
t.Run("list aids", func(t *testing.T) { t.Run("list identities", func(t *testing.T) {
if aids, err := s.List(); err != nil { if identities, err := s.List(); err != nil {
t.Fatalf("List: error = %v", err) t.Fatalf("List: error = %v", err)
} else { } else {
slices.Sort(aids) slices.Sort(identities)
want := []int{0, 1} want := []int{0, 1}
if !slices.Equal(aids, want) { if !slices.Equal(identities, want) {
t.Fatalf("List() = %#v, want %#v", aids, want) t.Fatalf("List() = %#v, want %#v", identities, want)
} }
} }
}) })
@ -110,7 +110,7 @@ func testStore(t *testing.T, s state.Store) {
} }
}) })
t.Run("clear aid 1", func(t *testing.T) { t.Run("clear identity 1", func(t *testing.T) {
do(1, func(c state.Cursor) { do(1, func(c state.Cursor) {
if err := c.Destroy(tc[insertEntryOtherApp].state.ID); err != nil { if err := c.Destroy(tc[insertEntryOtherApp].state.ID); err != nil {
t.Fatalf("Destroy: error = %v", err) t.Fatalf("Destroy: error = %v", err)

View File

@ -1,9 +0,0 @@
package internal
import (
"os"
"hakurei.app/internal/hlog"
)
func Exit(code int) { hlog.BeforeExit(); os.Exit(code) }

View File

@ -1,13 +0,0 @@
package internal
import (
"hakurei.app/container"
"hakurei.app/internal/hlog"
"hakurei.app/system"
)
func InstallOutput(verbose bool) {
hlog.Store(verbose)
container.SetOutput(hlog.Output{})
system.SetOutput(hlog.Output{})
}

View File

@ -2,9 +2,8 @@ package internal
import ( import (
"log" "log"
"path"
"hakurei.app/internal/hlog" "hakurei.app/container"
) )
var ( var (
@ -12,22 +11,23 @@ var (
hsu = compPoison hsu = compPoison
) )
func MustHakureiPath() string { // MustHakureiPath returns the absolute path to hakurei, configured at compile time.
if name, ok := checkPath(hmain); ok { func MustHakureiPath() *container.Absolute { return mustCheckPath(log.Fatal, "hakurei", hmain) }
return name
}
hlog.BeforeExit()
log.Fatal("invalid hakurei path, this program is compiled incorrectly")
return compPoison // unreachable
}
func MustHsuPath() string { // MustHsuPath returns the absolute path to hakurei, configured at compile time.
if name, ok := checkPath(hsu); ok { func MustHsuPath() *container.Absolute { return mustCheckPath(log.Fatal, "hsu", hsu) }
return name
}
hlog.BeforeExit()
log.Fatal("invalid hsu path, this program is compiled incorrectly")
return compPoison // unreachable
}
func checkPath(p string) (string, bool) { return p, p != compPoison && p != "" && path.IsAbs(p) } // mustCheckPath checks a pathname against compPoison, then [container.NewAbs], calling fatal if either step fails.
func mustCheckPath(fatal func(v ...any), name, pathname string) *container.Absolute {
if pathname != compPoison && pathname != "" {
if a, err := container.NewAbs(pathname); err != nil {
fatal(err.Error())
return nil // unreachable
} else {
return a
}
} else {
fatal("invalid " + name + " path, this program is compiled incorrectly")
return nil // unreachable
}
}

43
internal/path_test.go Normal file
View File

@ -0,0 +1,43 @@
package internal
import (
"reflect"
"testing"
"hakurei.app/container"
)
func TestMustCheckPath(t *testing.T) {
testCases := []struct {
name string
pathname string
wantFatal string
}{
{"poison", compPoison, "invalid test path, this program is compiled incorrectly"},
{"zero", "", "invalid test path, this program is compiled incorrectly"},
{"not absolute", "\x00", `path "\x00" is not absolute`},
{"success", "/proc/nonexistent", ""},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fatal := func(v ...any) { t.Fatal(append([]any{"invalid call to fatal:"}, v...)...) }
if tc.wantFatal != "" {
fatal = func(v ...any) {
if len(v) != 1 {
t.Errorf("mustCheckPath: fatal %#v", v)
} else if gotFatal, ok := v[0].(string); !ok {
t.Errorf("mustCheckPath: fatal = %#v", v[0])
} else if gotFatal != tc.wantFatal {
t.Errorf("mustCheckPath: fatal = %q, want %q", gotFatal, tc.wantFatal)
}
// do not simulate exit
}
}
if got := mustCheckPath(fatal, "test", tc.pathname); got != nil && !reflect.DeepEqual(got, container.MustAbs(tc.pathname)) {
t.Errorf("mustCheckPath: %q", got)
}
})
}
}

View File

@ -22,7 +22,7 @@ var (
msgStaticGlibc = []byte("not a dynamic executable") msgStaticGlibc = []byte("not a dynamic executable")
) )
func Exec(ctx context.Context, p string) ([]*Entry, error) { func Exec(ctx context.Context, msg container.Msg, p string) ([]*Entry, error) {
c, cancel := context.WithTimeout(ctx, lddTimeout) c, cancel := context.WithTimeout(ctx, lddTimeout)
defer cancel() defer cancel()
@ -33,7 +33,7 @@ func Exec(ctx context.Context, p string) ([]*Entry, error) {
return nil, err return nil, err
} }
z := container.NewCommand(c, toolPath, lddName, p) z := container.NewCommand(c, msg, toolPath, lddName, p)
z.Hostname = "hakurei-" + lddName z.Hostname = "hakurei-" + lddName
z.SeccompFlags |= seccomp.AllowMultiarch z.SeccompFlags |= seccomp.AllowMultiarch
z.SeccompPresets |= seccomp.PresetStrict z.SeccompPresets |= seccomp.PresetStrict

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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