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/app"
"hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
"hakurei.app/system"
"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 (
flagVerbose 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(&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 := tryPath(args[0])
config := tryPath(msg, args[0])
config.Args = append(config.Args, args[1:]...)
app.Main(ctx, config)
app.Main(ctx, msg, config)
panic("unreachable")
})
@ -78,9 +91,9 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
passwd *user.User
passwdOnce sync.Once
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 {
hlog.Verbosef("cannot look up uid %s", us)
msg.Verbosef("cannot look up uid %s", us)
passwd = &user.User{
Uid: 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")
}).
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 {
switch len(args) {
case 0: // system
printShowSystem(os.Stdout, flagShort, flagJSON)
printShowSystem(msg, os.Stdout, flagShort, flagJSON)
case 1: // instance
name := args[0]
config, entry := tryShort(name)
config, entry := tryShort(msg, name)
if config == nil {
config = tryPath(name)
config = tryPath(msg, name)
}
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
c.NewCommand("ps", "List active instances", func(args []string) error {
var sc hst.Paths
app.CopyPaths(&sc, new(app.Hsu).MustID())
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sc.RunDirPath.String()), flagShort, flagJSON)
app.CopyPaths(msg, &sc, new(app.Hsu).MustID())
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(msg, sc.RunDirPath.String()), flagShort, flagJSON)
return errSuccess
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
}

View File

@ -7,6 +7,7 @@ import (
"testing"
"hakurei.app/command"
"hakurei.app/container"
)
func TestHelp(t *testing.T) {
@ -68,7 +69,7 @@ Flags:
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
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) {
t.Errorf("Parse: error = %v; want %v",
err, command.ErrHelp)

View File

@ -13,8 +13,6 @@ import (
"syscall"
"hakurei.app/container"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
)
var (
@ -24,20 +22,20 @@ var (
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() {
// 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 {
hlog.Verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
// not fatal: this program runs as the privileged user
}
log.SetPrefix("hakurei: ")
log.SetFlags(0)
msg := container.NewMsg(log.Default())
if err := container.SetDumpable(container.SUID_DUMP_DISABLE); err != nil {
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
// not fatal: this program runs as the privileged user
early := earlyHardeningErrs{
yamaLSM: container.SetPtracer(0),
dumpable: container.SetDumpable(container.SUID_DUMP_DISABLE),
}
if os.Geteuid() == 0 {
@ -48,10 +46,10 @@ func main() {
syscall.SIGINT, syscall.SIGTERM)
defer stop() // unreachable
buildCommand(ctx, os.Stderr).MustParse(os.Args[1:], func(err error) {
hlog.Verbosef("command returned %v", err)
buildCommand(ctx, msg, &early, os.Stderr).MustParse(os.Args[1:], func(err error) {
msg.Verbosef("command returned %v", err)
if errors.Is(err, errSuccess) {
hlog.BeforeExit()
msg.BeforeExit()
os.Exit(0)
}
// this catches faulty command handlers that fail to return before this point

View File

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

View File

@ -11,18 +11,19 @@ import (
"text/tabwriter"
"time"
"hakurei.app/container"
"hakurei.app/hst"
"hakurei.app/internal/app"
"hakurei.app/internal/app/state"
"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)
defer t.MustFlush()
info := &hst.Info{User: new(app.Hsu).MustID()}
app.CopyPaths(&info.Paths, info.User)
app.CopyPaths(msg, &info.Paths, info.User)
if flagJSON {
printJSON(output, short, info)

View File

@ -13,22 +13,21 @@ import (
"hakurei.app/command"
"hakurei.app/container"
"hakurei.app/hst"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
)
var (
errSuccess = errors.New("success")
)
func init() {
hlog.Prepare("hpkg")
func main() {
log.SetPrefix("hpkg: ")
log.SetFlags(0)
msg := container.NewMsg(log.Default())
if err := os.Setenv("SHELL", pathShell.String()); err != nil {
log.Fatalf("cannot set $SHELL: %v", err)
}
}
func main() {
if os.Geteuid() == 0 {
log.Fatal("this program must not run as root")
}
@ -41,7 +40,7 @@ func main() {
flagVerbose 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(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action")
@ -90,12 +89,12 @@ func main() {
}
cleanup := func() {
// should be faster than a native implementation
mustRun(chmod, "-R", "+w", workDir.String())
mustRun(rm, "-rf", workDir.String())
mustRun(msg, chmod, "-R", "+w", workDir.String())
mustRun(msg, rm, "-rf", workDir.String())
}
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.
@ -148,10 +147,10 @@ func main() {
}
// 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)
} else {
hlog.Verbosef("application %q clean installation", bundle.ID)
msg.Verbosef("application %q clean installation", bundle.ID)
// sec: should install credentials
}
@ -159,7 +158,7 @@ func main() {
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 BUNDLE=" + hst.Tmp + "/bundle",
// replace inner /etc
@ -181,7 +180,7 @@ func main() {
}, workDir, bundle, pathSet, flagDropShell, cleanup)
if bundle.GPU {
withCacheDir(ctx, "mesa-wrappers", []string{
withCacheDir(ctx, msg, "mesa-wrappers", []string{
// link nixGL mesa wrappers
"mkdir -p nix/.nixGL",
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
@ -193,7 +192,7 @@ func main() {
Activate home-manager generation.
*/
withNixDaemon(ctx, "activate", []string{
withNixDaemon(ctx, msg, "activate", []string{
// clean up broken links
"mkdir -p .local/state/{nix,home-manager}",
"chmod -R +w .local/state/{nix,home-manager}",
@ -261,7 +260,7 @@ func main() {
*/
if a.GPU && flagAutoDrivers {
withNixDaemon(ctx, "nix-gl", []string{
withNixDaemon(ctx, msg, "nix-gl", []string{
"mkdir -p /nix/.nixGL/auto",
"rm -rf /nix/.nixGL/auto",
"export NIXPKGS_ALLOW_UNFREE=1",
@ -316,7 +315,7 @@ func main() {
Spawn app.
*/
mustRunApp(ctx, config, func() {})
mustRunApp(ctx, msg, config, func() {})
return errSuccess
}).
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) {
hlog.Verbosef("command returned %v", err)
msg.Verbosef("command returned %v", err)
if errors.Is(err, errSuccess) {
hlog.BeforeExit()
msg.BeforeExit()
os.Exit(0)
}
})

View File

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

View File

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

View File

@ -2,20 +2,21 @@ package main
import (
"context"
"os"
"strings"
"hakurei.app/container"
"hakurei.app/container/seccomp"
"hakurei.app/hst"
"hakurei.app/internal"
)
func withNixDaemon(
ctx context.Context,
msg container.Msg,
action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config,
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
) {
mustRunAppDropShell(ctx, updateConfig(&hst.Config{
mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{
ID: app.ID,
Path: pathShell,
@ -61,9 +62,10 @@ func withNixDaemon(
func withCacheDir(
ctx context.Context,
msg container.Msg,
action string, command []string, workDir *container.Absolute,
app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
mustRunAppDropShell(ctx, &hst.Config{
mustRunAppDropShell(ctx, msg, &hst.Config{
ID: app.ID,
Path: pathShell,
@ -97,12 +99,13 @@ func withCacheDir(
}, 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 {
config.Args = []string{bash, "-l"}
mustRunApp(ctx, config, beforeFail)
mustRunApp(ctx, msg, config, 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))
for _, ent := range d {
name := ent.Name()
if IsAutoRootBindable(name) {
if IsAutoRootBindable(state, name) {
// careful: the Valid method is skipped, make sure this is always valid
op := &BindMountOp{
Source: r.Host.Append(name),
@ -78,7 +78,7 @@ func (r *AutoRootOp) String() string {
}
// IsAutoRootBindable returns whether a dir entry name is selected for AutoRoot.
func IsAutoRootBindable(name string) bool {
func IsAutoRootBindable(msg Msg, name string) bool {
switch name {
case "proc", "dev", "tmp", "mnt", "etc":

View File

@ -180,19 +180,26 @@ func TestIsAutoRootBindable(t *testing.T) {
testCases := []struct {
name string
want bool
log bool
}{
{"proc", false},
{"dev", false},
{"tmp", false},
{"mnt", false},
{"etc", false},
{"", false},
{"proc", false, false},
{"dev", false, false},
{"tmp", false, false},
{"mnt", false, false},
{"etc", false, false},
{"", false, true},
{"var", true},
{"var", true, false},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if got := IsAutoRootBindable(tc.name); got != tc.want {
var msg Msg
if tc.log {
msg = &kstub{nil, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { panic("unreachable") }, stub.Expect{Calls: []stub.Call{
call("verbose", stub.ExpectArgs{[]any{"got unexpected root entry"}}, nil, nil),
}})}
}
if got := IsAutoRootBindable(msg, tc.name); got != tc.want {
t.Errorf("IsAutoRootBindable: %v, want %v", got, tc.want)
}
})

View File

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

View File

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

View File

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

View File

@ -2,8 +2,10 @@ package container
import (
"bytes"
"fmt"
"io"
"io/fs"
"log"
"os"
"os/exec"
"reflect"
@ -136,7 +138,7 @@ func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
type simpleTestCase struct {
name string
f func(k syscallDispatcher) error
f func(k *kstub) error
want stub.Expect
wantErr error
}
@ -185,11 +187,11 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
t.Run(tc.name, func(t *testing.T) {
t.Helper()
state := &setupState{Params: tc.params}
k := &kstub{nil, stub.New(t,
func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{nil, s} },
stub.Expect{Calls: slices.Concat(tc.early, []stub.Call{{Name: stub.CallSeparator}}, tc.apply)},
)}
state := &setupState{Params: tc.params, Msg: k}
defer stub.HandleExit(t)
errEarly := tc.op.early(state, k)
k.Expects(stub.CallSeparator)
@ -327,7 +329,11 @@ func (k *kstub) setDumpable(dumpable uintptr) error {
}
func (k *kstub) setNoNewPrivs() error { k.Helper(); return k.Expects("setNoNewPrivs").Err }
func (k *kstub) lastcap() uintptr { k.Helper(); return k.Expects("lastcap").Ret.(uintptr) }
func (k *kstub) lastcap(msg Msg) uintptr {
k.Helper()
k.checkMsg(msg)
return k.Expects("lastcap").Ret.(uintptr)
}
func (k *kstub) capset(hdrp *capHeader, datap *[2]capData) error {
k.Helper()
@ -403,16 +409,18 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
return
}
func (k *kstub) bindMount(source, target string, flags uintptr) error {
func (k *kstub) bindMount(msg Msg, source, target string, flags uintptr) error {
k.Helper()
k.checkMsg(msg)
return k.Expects("bindMount").Error(
stub.CheckArg(k.Stub, "source", source, 0),
stub.CheckArg(k.Stub, "target", target, 1),
stub.CheckArg(k.Stub, "flags", flags, 2))
}
func (k *kstub) remount(target string, flags uintptr) error {
func (k *kstub) remount(msg Msg, target string, flags uintptr) error {
k.Helper()
k.checkMsg(msg)
return k.Expects("remount").Error(
stub.CheckArg(k.Stub, "target", target, 0),
stub.CheckArg(k.Stub, "flags", flags, 1))
@ -694,7 +702,7 @@ func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage
return
}
func (k *kstub) printf(format string, v ...any) {
func (k *kstub) printf(_ Msg, format string, v ...any) {
k.Helper()
if k.Expects("printf").Error(
stub.CheckArg(k.Stub, "format", format, 0),
@ -703,7 +711,7 @@ func (k *kstub) printf(format string, v ...any) {
}
}
func (k *kstub) fatal(v ...any) {
func (k *kstub) fatal(_ Msg, v ...any) {
k.Helper()
if k.Expects("fatal").Error(
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
@ -712,7 +720,7 @@ func (k *kstub) fatal(v ...any) {
panic(stub.PanicExit)
}
func (k *kstub) fatalf(format string, v ...any) {
func (k *kstub) fatalf(_ Msg, format string, v ...any) {
k.Helper()
if k.Expects("fatalf").Error(
stub.CheckArg(k.Stub, "format", format, 0),
@ -722,7 +730,35 @@ func (k *kstub) fatalf(format string, v ...any) {
panic(stub.PanicExit)
}
func (k *kstub) verbose(v ...any) {
func (k *kstub) checkMsg(msg Msg) {
k.Helper()
var target *kstub
if state, ok := msg.(*setupState); ok {
target = state.Msg.(*kstub)
} else {
target = msg.(*kstub)
}
if k != target {
panic(fmt.Sprintf("unexpected Msg: %#v", msg))
}
}
func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }
func (k *kstub) IsVerbose() bool { panic("unreachable") }
func (k *kstub) SwapVerbose(verbose bool) bool {
k.Helper()
expect := k.Expects("swapVerbose")
if expect.Error(
stub.CheckArg(k.Stub, "verbose", verbose, 0)) != nil {
k.FailNow()
}
return expect.Ret.(bool)
}
func (k *kstub) Verbose(v ...any) {
k.Helper()
if k.Expects("verbose").Error(
stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
@ -730,7 +766,7 @@ func (k *kstub) verbose(v ...any) {
}
}
func (k *kstub) verbosef(format string, v ...any) {
func (k *kstub) Verbosef(format string, v ...any) {
k.Helper()
if k.Expects("verbosef").Error(
stub.CheckArg(k.Stub, "format", format, 0),
@ -739,6 +775,6 @@ func (k *kstub) verbosef(format string, v ...any) {
}
}
func (k *kstub) suspend() { k.Helper(); k.Expects("suspend") }
func (k *kstub) resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
func (k *kstub) beforeExit() { k.Helper(); k.Expects("beforeExit") }
func (k *kstub) Suspend() bool { k.Helper(); return k.Expects("suspend").Ret.(bool) }
func (k *kstub) Resume() bool { k.Helper(); return k.Expects("resume").Ret.(bool) }
func (k *kstub) BeforeExit() { k.Helper(); k.Expects("beforeExit") }

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package container
import (
"errors"
"fmt"
"log"
"os"
"os/exec"
"path"
@ -59,6 +60,7 @@ type (
setupState struct {
nonrepeatable uintptr
*Params
Msg
}
)
@ -91,20 +93,23 @@ type initParams struct {
Verbose bool
}
func Init(prepareLogger func(prefix string), setVerbose func(verbose bool)) {
initEntrypoint(direct{}, prepareLogger, setVerbose)
// Init is called by [TryArgv0] if the current process is the container init.
func Init(msg Msg) {
if msg == nil {
panic("attempting to call initEntrypoint with nil msg")
}
initEntrypoint(direct{}, msg)
}
func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setVerbose func(verbose bool)) {
func initEntrypoint(k syscallDispatcher, msg Msg) {
k.lockOSThread()
prepareLogger("init")
if k.getpid() != 1 {
k.fatal("this process must run as pid 1")
k.fatal(msg, "this process must run as pid 1")
}
if err := k.setPtracer(0); err != nil {
k.verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
msg.Verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
// not fatal: this program has no additional privileges at initial program start
}
@ -116,65 +121,65 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
)
if f, err := k.receive(setupEnv, &params, &setupFd); err != nil {
if errors.Is(err, EBADF) {
k.fatal("invalid setup descriptor")
k.fatal(msg, "invalid setup descriptor")
}
if errors.Is(err, ErrReceiveEnv) {
k.fatal("HAKUREI_SETUP not set")
k.fatal(msg, "HAKUREI_SETUP not set")
}
k.fatalf("cannot decode init setup payload: %v", err)
k.fatalf(msg, "cannot decode init setup payload: %v", err)
} else {
if params.Ops == nil {
k.fatal("invalid setup parameters")
k.fatal(msg, "invalid setup parameters")
}
if params.ParentPerm == 0 {
params.ParentPerm = 0755
}
setVerbose(params.Verbose)
k.verbose("received setup parameters")
msg.SwapVerbose(params.Verbose)
msg.Verbose("received setup parameters")
closeSetup = f
offsetSetup = int(setupFd + 1)
}
// write uid/gid map here so parent does not need to set dumpable
if err := k.setDumpable(SUID_DUMP_USER); err != nil {
k.fatalf("cannot set SUID_DUMP_USER: %v", err)
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
}
if err := k.writeFile(FHSProc+"self/uid_map",
append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...),
0); err != nil {
k.fatalf("%v", err)
k.fatalf(msg, "%v", err)
}
if err := k.writeFile(FHSProc+"self/setgroups",
[]byte("deny\n"),
0); err != nil && !os.IsNotExist(err) {
k.fatalf("%v", err)
k.fatalf(msg, "%v", err)
}
if err := k.writeFile(FHSProc+"self/gid_map",
append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...),
0); err != nil {
k.fatalf("%v", err)
k.fatalf(msg, "%v", err)
}
if err := k.setDumpable(SUID_DUMP_DISABLE); err != nil {
k.fatalf("cannot set SUID_DUMP_DISABLE: %v", err)
k.fatalf(msg, "cannot set SUID_DUMP_DISABLE: %v", err)
}
oldmask := k.umask(0)
if params.Hostname != "" {
if err := k.sethostname([]byte(params.Hostname)); err != nil {
k.fatalf("cannot set hostname: %v", err)
k.fatalf(msg, "cannot set hostname: %v", err)
}
}
// cache sysctl before pivot_root
lastcap := k.lastcap()
lastcap := k.lastcap(msg)
if err := k.mount(zeroString, FHSRoot, zeroString, MS_SILENT|MS_SLAVE|MS_REC, zeroString); err != nil {
k.fatalf("cannot make / rslave: %v", err)
k.fatalf(msg, "cannot make / rslave: %v", err)
}
state := &setupState{Params: &params.Params}
state := &setupState{Params: &params.Params, Msg: msg}
/* early is called right before pivot_root into intermediate root;
this step is mostly for gathering information that would otherwise be difficult to obtain
@ -182,41 +187,41 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
the state of the mount namespace */
for i, op := range *params.Ops {
if op == nil || !op.Valid() {
k.fatalf("invalid op at index %d", i)
k.fatalf(msg, "invalid op at index %d", i)
}
if err := op.early(state, k); err != nil {
if m, ok := messageFromError(err); ok {
k.fatal(m)
k.fatal(msg, m)
} else {
k.fatalf("cannot prepare op at index %d: %v", i, err)
k.fatalf(msg, "cannot prepare op at index %d: %v", i, err)
}
}
}
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
k.fatalf("cannot mount intermediate root: %v", err)
k.fatalf(msg, "cannot mount intermediate root: %v", err)
}
if err := k.chdir(intermediateHostPath); err != nil {
k.fatalf("cannot enter intermediate host path: %v", err)
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
}
if err := k.mkdir(sysrootDir, 0755); err != nil {
k.fatalf("%v", err)
k.fatalf(msg, "%v", err)
}
if err := k.mount(sysrootDir, sysrootDir, zeroString, MS_SILENT|MS_BIND|MS_REC, zeroString); err != nil {
k.fatalf("cannot bind sysroot: %v", err)
k.fatalf(msg, "cannot bind sysroot: %v", err)
}
if err := k.mkdir(hostDir, 0755); err != nil {
k.fatalf("%v", err)
k.fatalf(msg, "%v", err)
}
// pivot_root uncovers intermediateHostPath in hostDir
if err := k.pivotRoot(intermediateHostPath, hostDir); err != nil {
k.fatalf("cannot pivot into intermediate root: %v", err)
k.fatalf(msg, "cannot pivot into intermediate root: %v", err)
}
if err := k.chdir(FHSRoot); err != nil {
k.fatalf("cannot enter intermediate root: %v", err)
k.fatalf(msg, "cannot enter intermediate root: %v", err)
}
/* apply is called right after pivot_root and entering the new root;
@ -226,23 +231,23 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
for i, op := range *params.Ops {
// ops already checked during early setup
if prefix, ok := op.prefix(); ok {
k.verbosef("%s %s", prefix, op)
msg.Verbosef("%s %s", prefix, op)
}
if err := op.apply(state, k); err != nil {
if m, ok := messageFromError(err); ok {
k.fatal(m)
k.fatal(msg, m)
} else {
k.fatalf("cannot apply op at index %d: %v", i, err)
k.fatalf(msg, "cannot apply op at index %d: %v", i, err)
}
}
}
// setup requiring host root complete at this point
if err := k.mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil {
k.fatalf("cannot make host root rprivate: %v", err)
k.fatalf(msg, "cannot make host root rprivate: %v", err)
}
if err := k.unmount(hostDir, MNT_DETACH); err != nil {
k.fatalf("cannot unmount host root: %v", err)
k.fatalf(msg, "cannot unmount host root: %v", err)
}
{
@ -251,39 +256,39 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
fd, err = k.open(FHSRoot, O_DIRECTORY|O_RDONLY, 0)
return
}); err != nil {
k.fatalf("cannot open intermediate root: %v", err)
k.fatalf(msg, "cannot open intermediate root: %v", err)
}
if err := k.chdir(sysrootPath); err != nil {
k.fatalf("cannot enter sysroot: %v", err)
k.fatalf(msg, "cannot enter sysroot: %v", err)
}
if err := k.pivotRoot(".", "."); err != nil {
k.fatalf("cannot pivot into sysroot: %v", err)
k.fatalf(msg, "cannot pivot into sysroot: %v", err)
}
if err := k.fchdir(fd); err != nil {
k.fatalf("cannot re-enter intermediate root: %v", err)
k.fatalf(msg, "cannot re-enter intermediate root: %v", err)
}
if err := k.unmount(".", MNT_DETACH); err != nil {
k.fatalf("cannot unmount intermediate root: %v", err)
k.fatalf(msg, "cannot unmount intermediate root: %v", err)
}
if err := k.chdir(FHSRoot); err != nil {
k.fatalf("cannot enter root: %v", err)
k.fatalf(msg, "cannot enter root: %v", err)
}
if err := k.close(fd); err != nil {
k.fatalf("cannot close intermediate root: %v", err)
k.fatalf(msg, "cannot close intermediate root: %v", err)
}
}
if err := k.capAmbientClearAll(); err != nil {
k.fatalf("cannot clear the ambient capability set: %v", err)
k.fatalf(msg, "cannot clear the ambient capability set: %v", err)
}
for i := uintptr(0); i <= lastcap; i++ {
if params.Privileged && i == CAP_SYS_ADMIN {
continue
}
if err := k.capBoundingSetDrop(i); err != nil {
k.fatalf("cannot drop capability from bounding set: %v", err)
k.fatalf(msg, "cannot drop capability from bounding set: %v", err)
}
}
@ -292,29 +297,29 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN)
if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil {
k.fatalf("cannot raise CAP_SYS_ADMIN: %v", err)
k.fatalf(msg, "cannot raise CAP_SYS_ADMIN: %v", err)
}
}
if err := k.capset(
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
&[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}},
); err != nil {
k.fatalf("cannot capset: %v", err)
k.fatalf(msg, "cannot capset: %v", err)
}
if !params.SeccompDisable {
rules := params.SeccompRules
if len(rules) == 0 { // non-empty rules slice always overrides presets
k.verbosef("resolving presets %#x", params.SeccompPresets)
msg.Verbosef("resolving presets %#x", params.SeccompPresets)
rules = seccomp.Preset(params.SeccompPresets, params.SeccompFlags)
}
if err := k.seccompLoad(rules, params.SeccompFlags); err != nil {
// this also indirectly asserts PR_SET_NO_NEW_PRIVS
k.fatalf("cannot load syscall filter: %v", err)
k.fatalf(msg, "cannot load syscall filter: %v", err)
}
k.verbosef("%d filter rules loaded", len(rules))
msg.Verbosef("%d filter rules loaded", len(rules))
} else {
k.verbose("syscall filter not configured")
msg.Verbose("syscall filter not configured")
}
extraFiles := make([]*os.File, params.Count)
@ -331,14 +336,14 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
cmd.ExtraFiles = extraFiles
cmd.Dir = params.Dir.String()
k.verbosef("starting initial program %s", params.Path)
msg.Verbosef("starting initial program %s", params.Path)
if err := k.start(cmd); err != nil {
k.fatalf("%v", err)
k.fatalf(msg, "%v", err)
}
k.suspend()
msg.Suspend()
if err := closeSetup(); err != nil {
k.printf("cannot close setup pipe: %v", err)
k.printf(msg, "cannot close setup pipe: %v", err)
// not fatal
}
@ -372,7 +377,7 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
}
}
if !errors.Is(err, ECHILD) {
k.printf("unexpected wait4 response: %v", err)
k.printf(msg, "unexpected wait4 response: %v", err)
}
close(done)
@ -389,50 +394,50 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
for {
select {
case s := <-sig:
if k.resume() {
k.verbosef("%s after process start", s.String())
if msg.Resume() {
msg.Verbosef("%s after process start", s.String())
} else {
k.verbosef("got %s", s.String())
msg.Verbosef("got %s", s.String())
}
if s == CancelSignal && params.ForwardCancel && cmd.Process != nil {
k.verbose("forwarding context cancellation")
msg.Verbose("forwarding context cancellation")
if err := k.signal(cmd, os.Interrupt); err != nil {
k.printf("cannot forward cancellation: %v", err)
k.printf(msg, "cannot forward cancellation: %v", err)
}
continue
}
k.beforeExit()
msg.BeforeExit()
k.exit(0)
case w := <-info:
if w.wpid == cmd.Process.Pid {
// initial process exited, output is most likely available again
k.resume()
msg.Resume()
switch {
case w.wstatus.Exited():
r = w.wstatus.ExitStatus()
k.verbosef("initial process exited with code %d", w.wstatus.ExitStatus())
msg.Verbosef("initial process exited with code %d", w.wstatus.ExitStatus())
case w.wstatus.Signaled():
r = 128 + int(w.wstatus.Signal())
k.verbosef("initial process exited with signal %s", w.wstatus.Signal())
msg.Verbosef("initial process exited with signal %s", w.wstatus.Signal())
default:
r = 255
k.verbosef("initial process exited with status %#x", w.wstatus)
msg.Verbosef("initial process exited with status %#x", w.wstatus)
}
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
}
case <-done:
k.beforeExit()
msg.BeforeExit()
k.exit(r)
case <-timeout:
k.printf("timeout exceeded waiting for lingering processes")
k.beforeExit()
k.printf(msg, "timeout exceeded waiting for lingering processes")
msg.BeforeExit()
k.exit(r)
}
}
@ -441,10 +446,16 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
const initName = "init"
// TryArgv0 calls [Init] if the last element of argv0 is "init".
func TryArgv0(v Msg, prepare func(prefix string), setVerbose func(verbose bool)) {
// If a nil msg is passed, the system logger is used instead.
func TryArgv0(msg Msg) {
if msg == nil {
log.SetPrefix(initName + ": ")
log.SetFlags(0)
msg = NewMsg(log.Default())
}
if len(os.Args) > 0 && path.Base(os.Args[0]) == initName {
msg = v
Init(prepare, setVerbose)
Init(msg)
msg.BeforeExit()
os.Exit(0)
}

View File

@ -11,25 +11,9 @@ import (
)
func TestInitEntrypoint(t *testing.T) {
assertPrefix := func(prefix string) {
if prefix != "init" {
t.Fatalf("prepareLogger: prefix = %q", prefix)
}
}
assertVerboseFalse := func(verbose bool) {
if verbose {
t.Fatal("setVerbose: verbose = true, want false")
}
}
assertVerboseTrue := func(verbose bool) {
if !verbose {
t.Fatal("setVerbose: verbose = false, want true")
}
}
checkSimple(t, "initEntrypoint", []simpleTestCase{
{"getpid", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"getpid", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1<<10, nil),
@ -37,7 +21,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"receive bad fd", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"receive bad fd", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -47,7 +31,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"receive not set", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"receive not set", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -57,7 +41,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"receive payload decode", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"receive payload decode", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -67,7 +51,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"receive invalid params", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"receive invalid params", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -91,7 +75,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"setDumpable user", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"setDumpable user", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -112,13 +96,14 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(78), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, stub.UniqueError(77)),
call("fatalf", stub.ExpectArgs{"cannot set SUID_DUMP_USER: %v", []any{stub.UniqueError(77)}}, nil, nil),
},
}, nil},
{"writeFile uid_map", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"writeFile uid_map", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -139,6 +124,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(76), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, stub.UniqueError(75)),
@ -146,7 +132,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"writeFile setgroups", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"writeFile setgroups", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -167,6 +153,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(74), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -175,7 +162,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"writeFile gid_map", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"writeFile gid_map", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -196,6 +183,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(72), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -205,7 +193,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"setDumpable disable", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"setDumpable disable", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -226,6 +214,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(70), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -236,7 +225,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"sethostname", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"sethostname", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -257,6 +246,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(68), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -269,7 +259,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"mount rslave root", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"mount rslave root", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -290,6 +280,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(66), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -304,7 +295,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"nil op", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"nil op", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -325,6 +316,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(64), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -341,7 +333,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"invalid op", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"invalid op", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -362,6 +354,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(63), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -378,7 +371,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"early unhandled error", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"early unhandled error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -399,6 +392,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(62), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -416,7 +410,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"early", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"early", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -437,6 +431,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(60), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -454,7 +449,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"mount ih", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"mount ih", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -475,6 +470,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(59), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -493,7 +489,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"chdir ih", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"chdir ih", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -514,6 +510,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(57), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -533,7 +530,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"mkdir sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"mkdir sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -554,6 +551,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(55), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -574,7 +572,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"mount bind sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"mount bind sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -595,6 +593,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(53), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -616,7 +615,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"mkdir host", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"mkdir host", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -637,6 +636,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(51), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -659,7 +659,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"pivotRoot ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"pivotRoot ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -680,6 +680,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(49), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -703,7 +704,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"chdir ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"chdir ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -724,6 +725,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(47), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -748,7 +750,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"apply unhandled error", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"apply unhandled error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -769,6 +771,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(45), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -802,7 +805,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"apply", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"apply", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -823,6 +826,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(43), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -856,7 +860,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"mount rprivate host", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"mount rprivate host", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -877,6 +881,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(42), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -911,7 +916,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"unmount host", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"unmount host", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -932,6 +937,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(40), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -967,7 +973,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"open ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"open ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -988,6 +994,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(38), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1025,7 +1032,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"chdir sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"chdir sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1046,6 +1053,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(36), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1084,7 +1092,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"pivotRoot sysroot", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"pivotRoot sysroot", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1105,6 +1113,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(34), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1144,7 +1153,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"fchdir ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"fchdir ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1165,6 +1174,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(32), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1205,7 +1215,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"unmount ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"unmount ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1226,6 +1236,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(30), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1267,7 +1278,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"chdir ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"chdir ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1288,6 +1299,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(28), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1330,7 +1342,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"close ir", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"close ir", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1351,6 +1363,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(26), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1394,7 +1407,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"capAmbientClearAll", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"capAmbientClearAll", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1415,6 +1428,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(24), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1459,7 +1473,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"capBoundingSetDrop", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"capBoundingSetDrop", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1480,6 +1494,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(22), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1532,7 +1547,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"capAmbientRaise", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"capAmbientRaise", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1553,6 +1568,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(20), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1638,7 +1654,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"capset", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"capset", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1659,6 +1675,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(18), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1745,7 +1762,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"seccompLoad", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"seccompLoad", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1766,6 +1783,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(16), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -1854,7 +1872,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"start", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
{"start", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
call("getpid", stub.ExpectArgs{}, 1, nil),
@ -1874,6 +1892,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true,
ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(13), nil),
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
@ -1966,7 +1985,7 @@ func TestInitEntrypoint(t *testing.T) {
},
}, nil},
{"lowlastcap signaled cancel forward error", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
{"lowlastcap signaled cancel forward error", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
/* entrypoint */
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -1987,6 +2006,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true,
ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(10), nil),
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
@ -2039,7 +2059,7 @@ func TestInitEntrypoint(t *testing.T) {
call("umask", stub.ExpectArgs{022}, 0, nil),
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
call("suspend", stub.ExpectArgs{}, nil, nil),
call("suspend", stub.ExpectArgs{}, true, nil),
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(10)}}, nil, nil),
call("New", stub.ExpectArgs{}, nil, nil),
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- CancelSignal }, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
@ -2065,7 +2085,7 @@ func TestInitEntrypoint(t *testing.T) {
}}},
}, nil},
{"lowlastcap signaled cancel", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
{"lowlastcap signaled cancel", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
/* entrypoint */
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -2086,6 +2106,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true,
ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(7), nil),
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
@ -2138,7 +2159,7 @@ func TestInitEntrypoint(t *testing.T) {
call("umask", stub.ExpectArgs{022}, 0, nil),
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
call("suspend", stub.ExpectArgs{}, nil, nil),
call("suspend", stub.ExpectArgs{}, true, nil),
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil),
call("New", stub.ExpectArgs{}, nil, nil),
call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
@ -2155,7 +2176,7 @@ func TestInitEntrypoint(t *testing.T) {
}}},
}, nil},
{"lowlastcap signaled timeout", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
{"lowlastcap signaled timeout", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
/* entrypoint */
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -2176,6 +2197,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true,
ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(5), nil),
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
@ -2228,7 +2250,7 @@ func TestInitEntrypoint(t *testing.T) {
call("umask", stub.ExpectArgs{022}, 0, nil),
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
call("suspend", stub.ExpectArgs{}, nil, nil),
call("suspend", stub.ExpectArgs{}, true, nil),
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(5)}}, nil, nil),
call("New", stub.ExpectArgs{}, nil, nil),
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
@ -2247,7 +2269,7 @@ func TestInitEntrypoint(t *testing.T) {
}}},
}, nil},
{"lowlastcap signaled", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
{"lowlastcap signaled", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
/* entrypoint */
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -2268,6 +2290,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true,
ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(3), nil),
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
@ -2320,7 +2343,7 @@ func TestInitEntrypoint(t *testing.T) {
call("umask", stub.ExpectArgs{022}, 0, nil),
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
call("suspend", stub.ExpectArgs{}, nil, nil),
call("suspend", stub.ExpectArgs{}, true, nil),
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(3)}}, nil, nil),
call("New", stub.ExpectArgs{}, nil, nil),
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
@ -2346,7 +2369,7 @@ func TestInitEntrypoint(t *testing.T) {
}}},
}, nil},
{"strangewait nopriv notty noseccomp yamafault", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseFalse); return nil }, stub.Expect{
{"strangewait nopriv notty noseccomp yamafault", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
/* entrypoint */
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -2367,6 +2390,7 @@ func TestInitEntrypoint(t *testing.T) {
SeccompDisable: true,
ParentPerm: 0750,
}, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(1), nil),
call("swapVerbose", stub.ExpectArgs{false}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil),
@ -2455,7 +2479,7 @@ func TestInitEntrypoint(t *testing.T) {
call("umask", stub.ExpectArgs{022}, 0, nil),
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil),
call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil),
call("suspend", stub.ExpectArgs{}, nil, nil),
call("suspend", stub.ExpectArgs{}, true, nil),
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(1)}}, nil, nil),
call("New", stub.ExpectArgs{}, nil, nil),
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),
@ -2481,7 +2505,7 @@ func TestInitEntrypoint(t *testing.T) {
}}},
}, nil},
{"success", func(k syscallDispatcher) error { initEntrypoint(k, assertPrefix, assertVerboseTrue); return nil }, stub.Expect{
{"success", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{
/* entrypoint */
Calls: []stub.Call{
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
@ -2503,6 +2527,7 @@ func TestInitEntrypoint(t *testing.T) {
RetainSession: true,
Privileged: true,
}, 1000, 100, 3, true}, uintptr(9)}, stub.UniqueError(0), nil),
call("swapVerbose", stub.ExpectArgs{true}, false, nil),
call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil),
call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil),
call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("4294967296 1000 1\n"), os.FileMode(0)}, nil, nil),
@ -2594,7 +2619,7 @@ func TestInitEntrypoint(t *testing.T) {
call("umask", stub.ExpectArgs{022}, 0, nil),
call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{MustAbs("/bin/zsh")}}, nil, nil),
call("start", stub.ExpectArgs{"/bin/zsh", []string{"zsh", "-c", "exec vim"}, []string{"DISPLAY=:0"}, "/.hakurei"}, &os.Process{Pid: 0xcafe}, nil),
call("suspend", stub.ExpectArgs{}, nil, nil),
call("suspend", stub.ExpectArgs{}, true, nil),
call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(0)}}, nil, nil),
call("New", stub.ExpectArgs{}, nil, nil),
call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil),

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

View File

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

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 {
return err
} else if err = k.bindMount(
state,
tmpPath,
target,
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 (*RemountOp) early(*setupState, syscallDispatcher) error { return nil }
func (r *RemountOp) apply(_ *setupState, k syscallDispatcher) error {
return k.remount(toSysroot(r.Target.String()), r.Flags)
func (r *RemountOp) apply(state *setupState, k syscallDispatcher) error {
return k.remount(state, toSysroot(r.Target.String()), r.Flags)
}
func (r *RemountOp) Is(op Op) bool {

View File

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

View File

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

View File

@ -23,45 +23,88 @@ func GetErrorMessage(err error) (string, bool) {
return e.Message(), true
}
// Msg is used for package-wide verbose logging.
type Msg interface {
// GetLogger returns the address of the underlying [log.Logger].
GetLogger() *log.Logger
// IsVerbose atomically loads and returns whether [Msg] has verbose logging enabled.
IsVerbose() bool
// SwapVerbose atomically stores a new verbose state and returns the previous value held by [Msg].
SwapVerbose(verbose bool) bool
// Verbose passes its argument to the Println method of the underlying [log.Logger] if IsVerbose returns true.
Verbose(v ...any)
// Verbosef passes its argument to the Printf method of the underlying [log.Logger] if IsVerbose returns true.
Verbosef(format string, v ...any)
Suspend()
// Suspend causes the embedded [Suspendable] to withhold writes to its downstream [io.Writer].
// Suspend returns false and is a noop if called between calls to Suspend and Resume.
Suspend() bool
// Resume dumps the entire buffer held by the embedded [Suspendable] and stops withholding future writes.
// Resume returns false and is a noop if a call to Suspend does not precede it.
Resume() bool
// BeforeExit runs implementation-specific cleanup code, and optionally prints warnings.
// BeforeExit is called before [os.Exit].
BeforeExit()
}
type DefaultMsg struct{ inactive atomic.Bool }
// defaultMsg is the default implementation of the [Msg] interface.
// The zero value is not safe for use. Callers should use the [NewMsg] function instead.
type defaultMsg struct {
verbose atomic.Bool
func (msg *DefaultMsg) IsVerbose() bool { return true }
func (msg *DefaultMsg) Verbose(v ...any) {
if !msg.inactive.Load() {
log.Println(v...)
logger *log.Logger
Suspendable
}
// 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) {
if !msg.inactive.Load() {
log.Printf(format, v...)
func (msg *defaultMsg) Verbosef(format string, v ...any) {
if msg.verbose.Load() {
msg.logger.Printf(format, v...)
}
}
func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) }
func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) }
func (msg *DefaultMsg) BeforeExit() {}
// Resume calls [Suspendable.Resume] and prints a message if buffer was filled
// between calls to [Suspendable.Suspend] and Resume.
func (msg *defaultMsg) Resume() bool {
resumed, dropped, _, err := msg.Suspendable.Resume()
if err != nil {
// probably going to result in an error as well, so this message is as good as unreachable
msg.logger.Printf("cannot dump buffer on resume: %v", err)
}
if resumed && dropped > 0 {
msg.logger.Printf("dropped %d bytes while output is suspended", dropped)
}
return resumed
}
// msg is the [Msg] implemented used by all exported [container] functions.
var msg Msg = new(DefaultMsg)
// GetOutput returns the current active [Msg] implementation.
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
// BeforeExit prints a message if called between calls to [Suspendable.Suspend] and Resume.
func (msg *defaultMsg) BeforeExit() {
if msg.Resume() {
msg.logger.Printf("beforeExit reached on suspended output")
}
}

View File

@ -1,14 +1,16 @@
package container_test
import (
"bytes"
"errors"
"io"
"log"
"strings"
"sync/atomic"
"syscall"
"testing"
"hakurei.app/container"
"hakurei.app/container/stub"
)
func TestMessageError(t *testing.T) {
@ -39,146 +41,137 @@ func TestMessageError(t *testing.T) {
}
func TestDefaultMsg(t *testing.T) {
{
w := log.Writer()
f := log.Flags()
t.Cleanup(func() { log.SetOutput(w); log.SetFlags(f) })
}
msg := new(container.DefaultMsg)
// copied from output.go
const suspendBufMax = 1 << 24
t.Run("is verbose", func(t *testing.T) {
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() {
t.Error("IsVerbose unexpected outcome")
}
})
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() })
}
t.Error("IsVerbose unexpected false")
}
}},
{"resume noop", nil, nil, nil, func(t *testing.T, msg container.Msg) {
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()
}},
{"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() {
t.Error("Resume unexpected outcome")
t.Error("Resume unexpected failure")
}
})
}},
// the function is a noop
t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() })
{"suspend drop", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
msg.Suspend()
}},
{"suspend write fill", nil, nil, nil, func(_ *testing.T, msg container.Msg) {
msg.GetLogger().Print(strings.Repeat("\x00", suspendBufMax))
}},
{"resume dropped", append([]byte("test: "), bytes.Repeat([]byte{0}, suspendBufMax-6)...), []byte("test: dropped 7 bytes while output is suspended\n"), nil, func(t *testing.T, msg container.Msg) {
if !msg.Resume() {
t.Error("Resume unexpected failure")
}
}},
}
type panicWriter struct{}
func (panicWriter) Write([]byte) (int, error) { panic("unreachable") }
func saveRestoreOutput(t *testing.T) {
out := container.GetOutput()
t.Cleanup(func() { container.SetOutput(out) })
msg := container.NewMsg(log.New(&dw, "test: ", 0))
for _, step := range steps {
// these share the same writer, so cannot be subtests
t.Logf("running step %q", step.name)
dw.expect, dw.next, dw.err = step.pt, step.next, step.err
step.f(t, msg)
if dw.expect != nil {
t.Errorf("expect: %q", string(dw.expect))
}
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) {
container.SetOutput(new(stubOutput))
if v, ok := container.GetOutput().(*container.DefaultMsg); ok {
t.Fatalf("SetOutput: got unexpected output %#v", v)
}
container.SetOutput(nil)
if _, ok := container.GetOutput().(*container.DefaultMsg); !ok {
t.Fatalf("SetOutput: got unexpected output %#v", container.GetOutput())
}
})
t.Run("stub", func(t *testing.T) {
container.SetOutput(new(stubOutput))
if _, ok := container.GetOutput().(*stubOutput); !ok {
t.Fatalf("SetOutput: got unexpected output %#v", container.GetOutput())
}
})
}
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
testCases := []struct {
steps := []struct {
name string
w, pt []byte
err error
@ -75,25 +75,25 @@ func TestSuspendable(t *testing.T) {
var dw expectWriter
w := container.Suspendable{Downstream: &dw}
for _, tc := range testCases {
for _, step := range steps {
// these share the same writer, so cannot be subtests
t.Logf("writing step %q", tc.name)
dw.expect, dw.err = tc.pt, tc.err
t.Logf("writing step %q", step.name)
dw.expect, dw.err = step.pt, step.err
var (
gotN int
gotErr error
)
wantN := tc.n
wantN := step.n
switch wantN {
case nSpecialPtEquiv:
wantN = len(tc.pt)
gotN, gotErr = w.Write(tc.w)
wantN = len(step.pt)
gotN, gotErr = w.Write(step.w)
case nSpecialWEquiv:
wantN = len(tc.w)
gotN, gotErr = w.Write(tc.w)
wantN = len(step.w)
gotN, gotErr = w.Write(step.w)
case nSpecialSuspend:
s := w.IsSuspended()
@ -101,8 +101,8 @@ func TestSuspendable(t *testing.T) {
t.Fatal("Suspend: unexpected success")
}
wantN = len(tc.w)
gotN, gotErr = w.Write(tc.w)
wantN = len(step.w)
gotN, gotErr = w.Write(step.w)
default:
if wantN <= nSpecialDump {
@ -118,10 +118,10 @@ func TestSuspendable(t *testing.T) {
t.Errorf("Resume: dropped = %d, want %d", dropped, wantDropped)
}
wantN = len(tc.pt)
wantN = len(step.pt)
gotN, gotErr = int(n), err
} else {
gotN, gotErr = w.Write(tc.w)
gotN, gotErr = w.Write(step.w)
}
}
@ -129,9 +129,13 @@ func TestSuspendable(t *testing.T) {
t.Errorf("Write: n = %d, want %d", gotN, wantN)
}
if !reflect.DeepEqual(gotErr, tc.wantErr) {
if !reflect.DeepEqual(gotErr, step.wantErr) {
t.Errorf("Write: %v", gotErr)
}
if dw.expect != nil {
t.Errorf("expect: %q", string(dw.expect))
}
}
}
@ -139,17 +143,31 @@ func TestSuspendable(t *testing.T) {
type expectWriter struct {
expect []byte
err error
// optional consecutive write
next []byte
// optional, calls Error on failure if not nil
t *testing.T
}
func (w *expectWriter) Write(p []byte) (n int, err error) {
defer func() { w.expect = nil }()
defer func() { w.expect = w.next; w.next = nil }()
n, err = len(p), w.err
if w.expect == nil {
return 0, errors.New("unexpected call to Write: " + strconv.Quote(string(p)))
}
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("unexpected call to Write: "+strconv.Quote(string(p)))
if w.t != nil {
w.t.Error(err.Error())
}
return
}
if string(p) != 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
}

View File

@ -2,7 +2,6 @@ package container
import (
"bytes"
"log"
"os"
"strconv"
"sync"
@ -22,26 +21,28 @@ const (
kernelCapLastCapPath = FHSProcSys + "kernel/cap_last_cap"
)
func mustReadSysctl() {
func mustReadSysctl(msg Msg) {
sysctlOnce.Do(func() {
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 {
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 {
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 {
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 {
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 {
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 OverflowGid() int { sysctlOnce.Do(mustReadSysctl); return kernelOverflowgid }
func LastCap() uintptr { sysctlOnce.Do(mustReadSysctl); return uintptr(kernelCapLastCap) }
func OverflowUid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowuid }
func OverflowGid(msg Msg) int { mustReadSysctl(msg); return kernelOverflowgid }
func LastCap(msg Msg) uintptr { mustReadSysctl(msg); return uintptr(kernelCapLastCap) }

View File

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

View File

@ -12,7 +12,7 @@ import (
func TestContainer(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"
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) {
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",
argsWt, "hakurei")
return
@ -31,7 +31,7 @@ func TestContainer(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 {
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)
z.
Bind(container.AbsFHSRoot, container.AbsFHSRoot, 0).

View File

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

View File

@ -6,23 +6,24 @@ import (
"log"
"os"
"hakurei.app/container"
"hakurei.app/hst"
"hakurei.app/internal/app/state"
)
// 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
if err := state.NewAppID(&id); err != nil {
log.Fatal(err)
}
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)
os.Exit(1)
}
seal.main()
seal.main(msg)
panic("unreachable")
}

View File

@ -6,6 +6,8 @@ import (
"log"
"os/exec"
"os/user"
"hakurei.app/container"
)
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) overflowGid() int { return 65534 }
func (k *stubNixOS) overflowUid(container.Msg) 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...)) }

View File

@ -37,7 +37,7 @@ func TestApp(t *testing.T) {
0xbd, 0x01, 0x78, 0x0e,
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/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).
@ -129,7 +129,7 @@ func TestApp(t *testing.T) {
0x82, 0xd4, 0x13, 0x36,
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/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).
@ -280,7 +280,7 @@ func TestApp(t *testing.T) {
0x4c, 0xf0, 0x73, 0xbd,
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/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).
@ -377,7 +377,7 @@ func TestApp(t *testing.T) {
t.Run(tc.name, 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()}}
err := seal.finalise(t.Context(), tc.config)
err := seal.finalise(t.Context(), container.NewMsg(nil), tc.config)
if err != nil {
if s, ok := container.GetErrorMessage(err); !ok {
t.Fatalf("Seal: error = %v", err)

View File

@ -20,6 +20,7 @@ const preallocateOpsCount = 1 << 5
// newContainer initialises [container.Params] via [hst.ContainerConfig].
// Note that remaining container setup must be queued by the caller.
func newContainer(
msg container.Msg,
k syscallDispatcher,
s *hst.ContainerConfig,
prefix string,
@ -73,8 +74,8 @@ func newContainer(
params.Gid = k.getgid()
*gid = params.Gid
} else {
*uid = k.overflowUid()
*gid = k.overflowGid()
*uid = k.overflowUid(msg)
*gid = k.overflowGid(msg)
}
filesystem := s.Filesystem
@ -126,11 +127,11 @@ func newContainer(
// get parent dir of socket
dir := path.Dir(pair[1])
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)
} 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))
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
}
}
@ -178,7 +179,7 @@ func newContainer(
if autoroot != nil {
for _, ent := range autoRootEntries {
name := ent.Name()
if container.IsAutoRootBindable(name) {
if container.IsAutoRootBindable(msg, name) {
hidePathSource = append(hidePathSource, autoroot.Source.Append(name))
}
}
@ -193,7 +194,7 @@ func newContainer(
}
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
}
}
@ -209,7 +210,7 @@ func newContainer(
return nil, nil, err
} else if ok {
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].
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 !errors.Is(err, fs.ErrNotExist) {
return err
}
k.verbosef("path %q does not yet exist", *v)
msg.Verbosef("path %q does not yet exist", *v)
} else {
*v = p
}

View File

@ -9,7 +9,6 @@ import (
"hakurei.app/container"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
)
// 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)
// overflowUid provides [container.OverflowUid].
overflowUid() int
overflowUid(msg container.Msg) int
// overflowGid provides [container.OverflowGid].
overflowGid() int
overflowGid(msg container.Msg) int
// mustHsuPath provides [internal.MustHsuPath].
mustHsuPath() string
mustHsuPath() *container.Absolute
// fatalf provides [log.Fatalf].
fatalf(format string, v ...any)
isVerbose() bool
verbose(v ...any)
verbosef(format string, v ...any)
}
// 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) overflowUid() int { return container.OverflowUid() }
func (direct) overflowGid() int { return container.OverflowGid() }
func (direct) overflowUid(msg container.Msg) int { return container.OverflowUid(msg) }
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 (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/hst"
"hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
"hakurei.app/system"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
@ -120,7 +119,7 @@ type hsuUser struct {
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 (
home = "HOME"
shell = "SHELL"
@ -183,7 +182,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
} else if !isValidUsername(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))
for i, name := range config.Groups {
@ -201,7 +200,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
// permissive defaults
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 {
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
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 uid, gid int
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
if err != nil {
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.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)
{
@ -357,7 +357,7 @@ func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
// outer wayland socket (usually `/run/user/%d/wayland-%d`)
var socketPath *container.Absolute
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)
} else if a, err := container.NewAbs(name); err != nil {
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.container.Bind(outerPath, innerPath, 0)
} else { // bind mount wayland socket (insecure)
hlog.Verbose("direct wayland access, PROCEED WITH CAUTION")
msg.Verbose("direct wayland access, PROCEED WITH CAUTION")
share.ensureRuntimeDir()
k.container.Bind(socketPath, innerPath, 0)
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.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
} else {
hlog.Verbose("cannot locate PulseAudio cookie (tried " +
msg.Verbose("cannot locate PulseAudio cookie (tried " +
"$PULSE_COOKIE, " +
"$XDG_CONFIG_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)
if hlog.Load() {
hlog.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d",
if msg.IsVerbose() {
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))
}

View File

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

View File

@ -8,10 +8,10 @@ import (
)
// 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.
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"
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))
k.verbosef("process share directory at %q", v.SharePath)
msg.Verbosef("process share directory at %q", v.SharePath)
r, _ := k.lookupEnv(xdgRuntimeDir)
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.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/internal"
"hakurei.app/internal/app/state"
"hakurei.app/internal/hlog"
"hakurei.app/system"
)
@ -39,6 +38,7 @@ type mainState struct {
cmdWait chan error
k *outcome
container.Msg
uintptr
}
@ -55,7 +55,7 @@ func (ms mainState) beforeExit(isFault bool) {
panic("attempting to call beforeExit twice")
}
ms.done = true
defer hlog.BeforeExit()
defer ms.BeforeExit()
if isFault && ms.cancel != nil {
ms.cancel()
@ -97,37 +97,37 @@ func (ms mainState) beforeExit(isFault bool) {
}
}
if hlog.Load() {
if ms.IsVerbose() {
if !ok {
if err != nil {
hlog.Verbosef("wait: %v", err)
ms.Verbosef("wait: %v", err)
}
} else {
switch {
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():
hlog.Verbosef("process %d dumped core", ms.cmd.Process.Pid)
ms.Verbosef("process %d dumped core", ms.cmd.Process.Pid)
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:
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:
hlog.Resume()
ms.Resume()
// 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
// 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)
}
hlog.Resume()
ms.Resume()
if ms.k.sync != nil {
if err := ms.k.sync.Close(); err != nil {
perror(err, "close wayland security context")
@ -170,7 +170,7 @@ func (ms mainState) beforeExit(isFault bool) {
if l := len(states); l == 0 {
ec |= system.User
} 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
@ -183,9 +183,9 @@ func (ms mainState) beforeExit(isFault bool) {
}
ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse)
if hlog.Load() {
if ms.IsVerbose() {
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.
func (k *outcome) main() {
func (k *outcome) main(msg container.Msg) {
if !k.active.CompareAndSwap(false, true) {
panic("outcome: attempted to run twice")
}
@ -228,19 +228,19 @@ func (k *outcome) main() {
hsuPath := internal.MustHsuPath()
// ms.beforeExit required beyond this point
ms := &mainState{k: k}
ms := &mainState{Msg: msg, k: k}
if err := k.sys.Commit(); err != nil {
ms.fatal("cannot commit system setup:", err)
}
ms.uintptr |= mainNeedsRevert
ms.store = state.NewMulti(k.runDirPath.String())
ms.store = state.NewMulti(msg, k.runDirPath.String())
ctx, cancel := context.WithCancel(k.ctx)
defer 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.Dir = container.FHSRoot // container init enters final working directory
// 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 {
hlog.Verbosef("attaching supplementary group ids %s", k.user.supp)
msg.Verbosef("attaching supplementary group ids %s", k.user.supp)
// interpreted by hsu
ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(k.user.supp, " "))
}
hlog.Verbosef("setuid helper at %s", hsuPath)
hlog.Suspend()
msg.Verbosef("setuid helper at %s", hsuPath)
msg.Suspend()
if err := ms.cmd.Start(); err != nil {
ms.fatal("cannot start setuid wrapper:", err)
}
@ -286,18 +286,18 @@ func (k *outcome) main() {
os.Getpid(),
k.waitDelay,
k.container,
hlog.Load(),
msg.IsVerbose(),
})
}()
return
}():
if err != nil {
hlog.Resume()
msg.Resume()
ms.fatal("cannot transmit shim config:", err)
}
case <-ctx.Done():
hlog.Resume()
msg.Resume()
ms.fatal("shim context canceled:", newWithMessageError("shim setup canceled", ctx.Err()))
}

View File

@ -15,8 +15,6 @@ import (
"hakurei.app/container"
"hakurei.app/container/seccomp"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
)
//#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.
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 {
log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
@ -73,7 +73,7 @@ func ShimMain() {
log.Fatalf("cannot receive shim setup params: %v", err)
} else {
internal.InstallOutput(params.Verbose)
msg.SwapVerbose(params.Verbose)
closeSetup = f
}
@ -111,12 +111,12 @@ func ShimMain() {
}
// setup has not completed, terminate immediately
hlog.Resume()
msg.Resume()
os.Exit(ShimExitRequest)
return
case 1: // got SIGCONT after adoption: monitor died before delivering signal
hlog.BeforeExit()
msg.BeforeExit()
os.Exit(ShimExitOrphan)
return
@ -144,7 +144,7 @@ func ShimMain() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
cancelContainer.Store(&stop)
z := container.New(ctx)
z := container.New(ctx, msg)
z.Params = *params.Container
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr

View File

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

View File

@ -1,9 +1,13 @@
package state_test
import (
"log"
"testing"
"hakurei.app/container"
"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.
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.
// Note that some or all returned aids might not have any active apps.
List() (aids []int, err error)
// List queries the store and returns a list of identities known to the store.
// Note that some or all returned identities might not have any active apps.
List() (identities []int, err error)
// Close releases any resources held by Store.
Close() error

View File

@ -16,10 +16,10 @@ import (
func testStore(t *testing.T, s state.Store) {
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)
} else if len(aids) != 0 {
t.Fatalf("List: aids = %#v", aids)
} else if len(identities) != 0 {
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)
}
do := func(aid int, f func(c state.Cursor)) {
if ok, err := s.Do(aid, f); err != nil {
do := func(identity int, f func(c state.Cursor)) {
if ok, err := s.Do(identity, f); err != nil {
t.Fatalf("Do: ok = %v, error = %v", ok, err)
}
}
insert := func(i, aid int) {
do(aid, func(c state.Cursor) {
insert := func(i, identity int) {
do(identity, func(c state.Cursor) {
if err := c.Save(&tc[i].state, &tc[i].ct); err != nil {
t.Fatalf("Save(&tc[%v]): error = %v", i, err)
}
})
}
check := func(i, aid int) {
do(aid, func(c state.Cursor) {
check := func(i, identity int) {
do(identity, func(c state.Cursor) {
if entries, err := c.Load(); err != nil {
t.Fatalf("Load: error = %v", err)
} 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)
})
t.Run("insert entry different aid", func(t *testing.T) {
t.Run("insert entry different identity", func(t *testing.T) {
insert(insertEntryOtherApp, 1)
check(insertEntryOtherApp, 1)
})
@ -90,14 +90,14 @@ func testStore(t *testing.T, s state.Store) {
check(insertEntryNoCheck, 0)
})
t.Run("list aids", func(t *testing.T) {
if aids, err := s.List(); err != nil {
t.Run("list identities", func(t *testing.T) {
if identities, err := s.List(); err != nil {
t.Fatalf("List: error = %v", err)
} else {
slices.Sort(aids)
slices.Sort(identities)
want := []int{0, 1}
if !slices.Equal(aids, want) {
t.Fatalf("List() = %#v, want %#v", aids, want)
if !slices.Equal(identities, 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) {
if err := c.Destroy(tc[insertEntryOtherApp].state.ID); err != nil {
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 (
"log"
"path"
"hakurei.app/internal/hlog"
"hakurei.app/container"
)
var (
@ -12,22 +11,23 @@ var (
hsu = compPoison
)
func MustHakureiPath() string {
if name, ok := checkPath(hmain); ok {
return name
}
hlog.BeforeExit()
log.Fatal("invalid hakurei path, this program is compiled incorrectly")
return compPoison // unreachable
}
// MustHakureiPath returns the absolute path to hakurei, configured at compile time.
func MustHakureiPath() *container.Absolute { return mustCheckPath(log.Fatal, "hakurei", hmain) }
func MustHsuPath() string {
if name, ok := checkPath(hsu); ok {
return name
}
hlog.BeforeExit()
log.Fatal("invalid hsu path, this program is compiled incorrectly")
return compPoison // unreachable
}
// MustHsuPath returns the absolute path to hakurei, configured at compile time.
func MustHsuPath() *container.Absolute { return mustCheckPath(log.Fatal, "hsu", hsu) }
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")
)
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)
defer cancel()
@ -33,7 +33,7 @@ func Exec(ctx context.Context, p string) ([]*Entry, error) {
return nil, err
}
z := container.NewCommand(c, toolPath, lddName, p)
z := container.NewCommand(c, msg, toolPath, lddName, p)
z.Hostname = "hakurei-" + lddName
z.SeccompFlags |= seccomp.AllowMultiarch
z.SeccompPresets |= seccomp.PresetStrict

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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