aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-08-21 21:59:07 +0900
committerOphestra <cat@gensokyo.uk>2025-08-22 19:27:31 +0900
commit09d284498109b847da0da1e2c5f883268a7f2d46 (patch)
tree3784dee4b4e4ec97a95cb222c70d05463ae640ef
parentd500d6e55917e12a3f98b39cf0d29b6c96de9667 (diff)
container/init: wrap syscall helper functions
This allows tests to stub all kernel behaviour, enabling measurement of all function call arguments and error injection. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--container/autoetc.go24
-rw-r--r--container/autoetc_test.go247
-rw-r--r--container/autoroot.go25
-rw-r--r--container/autoroot_test.go120
-rw-r--r--container/capability.go47
-rw-r--r--container/dispatcher.go229
-rw-r--r--container/dispatcher_test.go595
-rw-r--r--container/init.go237
-rw-r--r--container/initbind.go25
-rw-r--r--container/initbind_test.go128
-rw-r--r--container/initdev.go37
-rw-r--r--container/initdev_test.go717
-rw-r--r--container/initmkdir.go8
-rw-r--r--container/initmkdir_test.go14
-rw-r--r--container/initoverlay.go30
-rw-r--r--container/initoverlay_test.go231
-rw-r--r--container/initplace.go19
-rw-r--r--container/initplace_test.go109
-rw-r--r--container/initproc.go11
-rw-r--r--container/initproc_test.go22
-rw-r--r--container/initremount.go8
-rw-r--r--container/initremount_test.go9
-rw-r--r--container/initsymlink.go18
-rw-r--r--container/initsymlink_test.go48
-rw-r--r--container/inittmpfs.go11
-rw-r--r--container/inittmpfs_test.go23
-rw-r--r--container/mount_test.go83
-rw-r--r--container/msg.go25
-rw-r--r--container/msg_test.go23
-rw-r--r--container/path.go3
-rw-r--r--container/path_test.go56
-rw-r--r--container/syscall.go13
32 files changed, 2834 insertions, 361 deletions
diff --git a/container/autoetc.go b/container/autoetc.go
index 0334ad14..ee8dfbe4 100644
--- a/container/autoetc.go
+++ b/container/autoetc.go
@@ -3,8 +3,7 @@ package container
import (
"encoding/gob"
"fmt"
- "os"
- "syscall"
+ "io/fs"
)
func init() { gob.Register(new(AutoEtcOp)) }
@@ -21,38 +20,35 @@ func (f *Ops) Etc(host *Absolute, prefix string) *Ops {
type AutoEtcOp struct{ Prefix string }
-func (e *AutoEtcOp) Valid() bool { return e != nil }
-func (e *AutoEtcOp) early(*setupState) error { return nil }
-func (e *AutoEtcOp) apply(state *setupState) error {
+func (e *AutoEtcOp) Valid() bool { return e != nil }
+func (e *AutoEtcOp) early(*setupState, syscallDispatcher) error { return nil }
+func (e *AutoEtcOp) apply(state *setupState, k syscallDispatcher) error {
if state.nonrepeatable&nrAutoEtc != 0 {
- return msg.WrapErr(syscall.EINVAL, "autoetc is not repeatable")
+ return msg.WrapErr(fs.ErrInvalid, "autoetc is not repeatable")
}
state.nonrepeatable |= nrAutoEtc
const target = sysrootPath + FHSEtc
rel := e.hostRel() + "/"
- if err := os.MkdirAll(target, 0755); err != nil {
+ if err := k.mkdirAll(target, 0755); err != nil {
return wrapErrSelf(err)
}
- if d, err := os.ReadDir(toSysroot(e.hostPath().String())); err != nil {
+ if d, err := k.readdir(toSysroot(e.hostPath().String())); err != nil {
return wrapErrSelf(err)
} else {
for _, ent := range d {
n := ent.Name()
switch n {
- case ".host":
-
- case "passwd":
- case "group":
+ case ".host", "passwd", "group":
case "mtab":
- if err = os.Symlink(FHSProc+"mounts", target+n); err != nil {
+ if err = k.symlink(FHSProc+"mounts", target+n); err != nil {
return wrapErrSelf(err)
}
default:
- if err = os.Symlink(rel+n, target+n); err != nil {
+ if err = k.symlink(rel+n, target+n); err != nil {
return wrapErrSelf(err)
}
}
diff --git a/container/autoetc_test.go b/container/autoetc_test.go
index efb4b3b6..8d942f4f 100644
--- a/container/autoetc_test.go
+++ b/container/autoetc_test.go
@@ -1,8 +1,253 @@
package container
-import "testing"
+import (
+ "errors"
+ "io/fs"
+ "os"
+ "testing"
+)
func TestAutoEtcOp(t *testing.T) {
+ t.Run("nonrepeatable", func(t *testing.T) {
+ wantErr := msg.WrapErr(fs.ErrInvalid, "autoetc is not repeatable")
+ if err := (&AutoEtcOp{Prefix: "81ceabb30d37bbdb3868004629cb84e9"}).apply(&setupState{nonrepeatable: nrAutoEtc}, nil); !errors.Is(err, wantErr) {
+ t.Errorf("apply: error = %v, want %v", err, wantErr)
+ }
+ })
+
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"mkdirAll", new(Params), &AutoEtcOp{
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"readdir", new(Params), &AutoEtcOp{
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil},
+ {"readdir", expectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(), errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"symlink", new(Params), &AutoEtcOp{
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil},
+ {"readdir", expectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(".host",
+ "alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
+ "fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
+ "locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
+ "modprobe.d", "modules-load.d", "mtab", "nanorc", "netgroup", "nix", "nixos", "NIXOS", "nscd.conf",
+ "nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
+ "protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
+ "ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
+ "tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"symlink mtab", new(Params), &AutoEtcOp{
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil},
+ {"readdir", expectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(".host",
+ "alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
+ "fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
+ "locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
+ "modprobe.d", "modules-load.d", "mtab", "nanorc", "netgroup", "nix", "nixos", "NIXOS", "nscd.conf",
+ "nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
+ "protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
+ "ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
+ "tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bash_logout", "/sysroot/etc/bash_logout"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bashrc", "/sysroot/etc/bashrc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/binfmt.d", "/sysroot/etc/binfmt.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dbus-1", "/sysroot/etc/dbus-1"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/default", "/sysroot/etc/default"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dhcpcd.exit-hook", "/sysroot/etc/dhcpcd.exit-hook"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fonts", "/sysroot/etc/fonts"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fstab", "/sysroot/etc/fstab"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fuse.conf", "/sysroot/etc/fuse.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/host.conf", "/sysroot/etc/host.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hostname", "/sysroot/etc/hostname"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hosts", "/sysroot/etc/hosts"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hsurc", "/sysroot/etc/hsurc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/inputrc", "/sysroot/etc/inputrc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/issue", "/sysroot/etc/issue"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/kbd", "/sysroot/etc/kbd"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/locale.conf", "/sysroot/etc/locale.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/login.defs", "/sysroot/etc/login.defs"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lsb-release", "/sysroot/etc/lsb-release"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lvm", "/sysroot/etc/lvm"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/machine-id", "/sysroot/etc/machine-id"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/man_db.conf", "/sysroot/etc/man_db.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/mdadm.conf", "/sysroot/etc/mdadm.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modprobe.d", "/sysroot/etc/modprobe.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modules-load.d", "/sysroot/etc/modules-load.d"}, nil, nil},
+ {"symlink", expectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"success nested", new(Params), &AutoEtcOp{
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil},
+ {"readdir", expectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(".host",
+ "alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
+ "fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
+ "locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
+ "modprobe.d", "modules-load.d", "mtab", "nanorc", "netgroup", "nix", "nixos", "NIXOS", "nscd.conf",
+ "nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
+ "protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
+ "ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
+ "tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bash_logout", "/sysroot/etc/bash_logout"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bashrc", "/sysroot/etc/bashrc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/binfmt.d", "/sysroot/etc/binfmt.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dbus-1", "/sysroot/etc/dbus-1"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/default", "/sysroot/etc/default"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dhcpcd.exit-hook", "/sysroot/etc/dhcpcd.exit-hook"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fonts", "/sysroot/etc/fonts"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fstab", "/sysroot/etc/fstab"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fuse.conf", "/sysroot/etc/fuse.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/host.conf", "/sysroot/etc/host.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hostname", "/sysroot/etc/hostname"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hosts", "/sysroot/etc/hosts"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hsurc", "/sysroot/etc/hsurc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/inputrc", "/sysroot/etc/inputrc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/issue", "/sysroot/etc/issue"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/kbd", "/sysroot/etc/kbd"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/locale.conf", "/sysroot/etc/locale.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/login.defs", "/sysroot/etc/login.defs"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lsb-release", "/sysroot/etc/lsb-release"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lvm", "/sysroot/etc/lvm"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/machine-id", "/sysroot/etc/machine-id"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/man_db.conf", "/sysroot/etc/man_db.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/mdadm.conf", "/sysroot/etc/mdadm.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modprobe.d", "/sysroot/etc/modprobe.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modules-load.d", "/sysroot/etc/modules-load.d"}, nil, nil},
+ {"symlink", expectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nanorc", "/sysroot/etc/nanorc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/netgroup", "/sysroot/etc/netgroup"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nix", "/sysroot/etc/nix"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nixos", "/sysroot/etc/nixos"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/NIXOS", "/sysroot/etc/NIXOS"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nscd.conf", "/sysroot/etc/nscd.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nsswitch.conf", "/sysroot/etc/nsswitch.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/os-release", "/sysroot/etc/os-release"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam", "/sysroot/etc/pam"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam.d", "/sysroot/etc/pam.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pipewire", "/sysroot/etc/pipewire"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pki", "/sysroot/etc/pki"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/polkit-1", "/sysroot/etc/polkit-1"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/profile", "/sysroot/etc/profile"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/protocols", "/sysroot/etc/protocols"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolv.conf", "/sysroot/etc/resolv.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolvconf.conf", "/sysroot/etc/resolvconf.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/rpc", "/sysroot/etc/rpc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/services", "/sysroot/etc/services"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/set-environment", "/sysroot/etc/set-environment"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shadow", "/sysroot/etc/shadow"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shells", "/sysroot/etc/shells"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssh", "/sysroot/etc/ssh"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssl", "/sysroot/etc/ssl"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/static", "/sysroot/etc/static"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subgid", "/sysroot/etc/subgid"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subuid", "/sysroot/etc/subuid"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sudoers", "/sysroot/etc/sudoers"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sway", "/sysroot/etc/sway"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sysctl.d", "/sysroot/etc/sysctl.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/systemd", "/sysroot/etc/systemd"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/terminfo", "/sysroot/etc/terminfo"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/tmpfiles.d", "/sysroot/etc/tmpfiles.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/udev", "/sysroot/etc/udev"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/vconsole.conf", "/sysroot/etc/vconsole.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/X11", "/sysroot/etc/X11"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/xdg", "/sysroot/etc/xdg"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/zoneinfo", "/sysroot/etc/zoneinfo"}, nil, nil},
+ }, nil},
+
+ {"success", new(Params), &AutoEtcOp{
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/etc/", os.FileMode(0755)}, nil, nil},
+ {"readdir", expectArgs{"/sysroot/etc/.host/81ceabb30d37bbdb3868004629cb84e9"}, stubDir(
+ "alsa", "bash_logout", "bashrc", "binfmt.d", "dbus-1", "default", "dhcpcd.exit-hook", "fonts",
+ "fstab", "fuse.conf", "group", "host.conf", "hostname", "hosts", "hsurc", "inputrc", "issue", "kbd",
+ "locale.conf", "login.defs", "lsb-release", "lvm", "machine-id", "man_db.conf", "mdadm.conf",
+ "modprobe.d", "modules-load.d", "mtab", "nanorc", "netgroup", "nix", "nixos", "NIXOS", "nscd.conf",
+ "nsswitch.conf", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1", "profile",
+ "protocols", "resolv.conf", "resolvconf.conf", "rpc", "services", "set-environment", "shadow", "shells",
+ "ssh", "ssl", "static", "subgid", "subuid", "sudoers", "sway", "sysctl.d", "systemd", "terminfo",
+ "tmpfiles.d", "udev", "vconsole.conf", "X11", "xdg", "zoneinfo"), nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/alsa", "/sysroot/etc/alsa"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bash_logout", "/sysroot/etc/bash_logout"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/bashrc", "/sysroot/etc/bashrc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/binfmt.d", "/sysroot/etc/binfmt.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dbus-1", "/sysroot/etc/dbus-1"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/default", "/sysroot/etc/default"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/dhcpcd.exit-hook", "/sysroot/etc/dhcpcd.exit-hook"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fonts", "/sysroot/etc/fonts"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fstab", "/sysroot/etc/fstab"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/fuse.conf", "/sysroot/etc/fuse.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/host.conf", "/sysroot/etc/host.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hostname", "/sysroot/etc/hostname"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hosts", "/sysroot/etc/hosts"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/hsurc", "/sysroot/etc/hsurc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/inputrc", "/sysroot/etc/inputrc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/issue", "/sysroot/etc/issue"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/kbd", "/sysroot/etc/kbd"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/locale.conf", "/sysroot/etc/locale.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/login.defs", "/sysroot/etc/login.defs"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lsb-release", "/sysroot/etc/lsb-release"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/lvm", "/sysroot/etc/lvm"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/machine-id", "/sysroot/etc/machine-id"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/man_db.conf", "/sysroot/etc/man_db.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/mdadm.conf", "/sysroot/etc/mdadm.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modprobe.d", "/sysroot/etc/modprobe.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/modules-load.d", "/sysroot/etc/modules-load.d"}, nil, nil},
+ {"symlink", expectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nanorc", "/sysroot/etc/nanorc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/netgroup", "/sysroot/etc/netgroup"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nix", "/sysroot/etc/nix"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nixos", "/sysroot/etc/nixos"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/NIXOS", "/sysroot/etc/NIXOS"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nscd.conf", "/sysroot/etc/nscd.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/nsswitch.conf", "/sysroot/etc/nsswitch.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/os-release", "/sysroot/etc/os-release"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam", "/sysroot/etc/pam"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pam.d", "/sysroot/etc/pam.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pipewire", "/sysroot/etc/pipewire"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/pki", "/sysroot/etc/pki"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/polkit-1", "/sysroot/etc/polkit-1"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/profile", "/sysroot/etc/profile"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/protocols", "/sysroot/etc/protocols"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolv.conf", "/sysroot/etc/resolv.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/resolvconf.conf", "/sysroot/etc/resolvconf.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/rpc", "/sysroot/etc/rpc"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/services", "/sysroot/etc/services"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/set-environment", "/sysroot/etc/set-environment"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shadow", "/sysroot/etc/shadow"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/shells", "/sysroot/etc/shells"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssh", "/sysroot/etc/ssh"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/ssl", "/sysroot/etc/ssl"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/static", "/sysroot/etc/static"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subgid", "/sysroot/etc/subgid"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/subuid", "/sysroot/etc/subuid"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sudoers", "/sysroot/etc/sudoers"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sway", "/sysroot/etc/sway"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/sysctl.d", "/sysroot/etc/sysctl.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/systemd", "/sysroot/etc/systemd"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/terminfo", "/sysroot/etc/terminfo"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/tmpfiles.d", "/sysroot/etc/tmpfiles.d"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/udev", "/sysroot/etc/udev"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/vconsole.conf", "/sysroot/etc/vconsole.conf"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/X11", "/sysroot/etc/X11"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/xdg", "/sysroot/etc/xdg"}, nil, nil},
+ {"symlink", expectArgs{".host/81ceabb30d37bbdb3868004629cb84e9/zoneinfo", "/sysroot/etc/zoneinfo"}, nil, nil},
+ }, nil},
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*AutoEtcOp)(nil), false},
{"zero", new(AutoEtcOp), true},
diff --git a/container/autoroot.go b/container/autoroot.go
index 0de6bce7..e1c397c9 100644
--- a/container/autoroot.go
+++ b/container/autoroot.go
@@ -3,8 +3,7 @@ package container
import (
"encoding/gob"
"fmt"
- "os"
- "syscall"
+ "io/fs"
)
func init() { gob.Register(new(AutoRootOp)) }
@@ -30,8 +29,8 @@ type AutoRootOp struct {
func (r *AutoRootOp) Valid() bool { return r != nil && r.Host != nil }
-func (r *AutoRootOp) early(state *setupState) error {
- if d, err := os.ReadDir(r.Host.String()); err != nil {
+func (r *AutoRootOp) early(state *setupState, k syscallDispatcher) error {
+ if d, err := k.readdir(r.Host.String()); err != nil {
return wrapErrSelf(err)
} else {
r.resolved = make([]Op, 0, len(d))
@@ -43,7 +42,7 @@ func (r *AutoRootOp) early(state *setupState) error {
Target: AbsFHSRoot.Append(name),
Flags: r.Flags,
}
- if err = op.early(state); err != nil {
+ if err = op.early(state, k); err != nil {
return err
}
r.resolved = append(r.resolved, op)
@@ -53,15 +52,15 @@ func (r *AutoRootOp) early(state *setupState) error {
}
}
-func (r *AutoRootOp) apply(state *setupState) error {
+func (r *AutoRootOp) apply(state *setupState, k syscallDispatcher) error {
if state.nonrepeatable&nrAutoRoot != 0 {
- return msg.WrapErr(syscall.EINVAL, "autoroot is not repeatable")
+ return msg.WrapErr(fs.ErrInvalid, "autoroot is not repeatable")
}
state.nonrepeatable |= nrAutoRoot
for _, op := range r.resolved {
- msg.Verbosef("%s %s", op.prefix(), op)
- if err := op.apply(state); err != nil {
+ k.verbosef("%s %s", op.prefix(), op)
+ if err := op.apply(state, k); err != nil {
return err
}
}
@@ -83,13 +82,11 @@ func (r *AutoRootOp) String() string {
// IsAutoRootBindable returns whether a dir entry name is selected for AutoRoot.
func IsAutoRootBindable(name string) bool {
switch name {
- case "proc":
- case "dev":
- case "tmp":
- case "mnt":
- case "etc":
+ case "proc", "dev", "tmp", "mnt", "etc":
case "": // guard against accidentally binding /
+ // should be unreachable
+ msg.Verbose("got unexpected root entry")
default:
return true
diff --git a/container/autoroot_test.go b/container/autoroot_test.go
index 03df7c84..eb4d5946 100644
--- a/container/autoroot_test.go
+++ b/container/autoroot_test.go
@@ -1,8 +1,126 @@
package container
-import "testing"
+import (
+ "errors"
+ "io/fs"
+ "os"
+ "testing"
+)
func TestAutoRootOp(t *testing.T) {
+ t.Run("nonrepeatable", func(t *testing.T) {
+ wantErr := msg.WrapErr(fs.ErrInvalid, "autoroot is not repeatable")
+ if err := (&AutoRootOp{Prefix: "81ceabb30d37bbdb3868004629cb84e9"}).apply(&setupState{nonrepeatable: nrAutoRoot}, nil); !errors.Is(err, wantErr) {
+ t.Errorf("apply: error = %v, want %v", err, wantErr)
+ }
+ })
+
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"readdir", &Params{ParentPerm: 0750}, &AutoRootOp{
+ Host: MustAbs("/"),
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ Flags: BindWritable,
+ }, []kexpect{
+ {"readdir", expectArgs{"/"}, stubDir(), errUnique},
+ }, wrapErrSelf(errUnique), nil, nil},
+
+ {"early", &Params{ParentPerm: 0750}, &AutoRootOp{
+ Host: MustAbs("/"),
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ Flags: BindWritable,
+ }, []kexpect{
+ {"readdir", expectArgs{"/"}, stubDir("bin", "dev", "etc", "home", "lib64",
+ "lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil},
+ {"evalSymlinks", expectArgs{"/bin"}, "", errUnique},
+ }, wrapErrSelf(errUnique), nil, nil},
+
+ {"apply", &Params{ParentPerm: 0750}, &AutoRootOp{
+ Host: MustAbs("/"),
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ Flags: BindWritable,
+ }, []kexpect{
+ {"readdir", expectArgs{"/"}, stubDir("bin", "dev", "etc", "home", "lib64",
+ "lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil},
+ {"evalSymlinks", expectArgs{"/bin"}, "/usr/bin", nil},
+ {"evalSymlinks", expectArgs{"/home"}, "/home", nil},
+ {"evalSymlinks", expectArgs{"/lib64"}, "/lib64", nil},
+ {"evalSymlinks", expectArgs{"/lost+found"}, "/lost+found", nil},
+ {"evalSymlinks", expectArgs{"/nix"}, "/nix", nil},
+ {"evalSymlinks", expectArgs{"/root"}, "/root", nil},
+ {"evalSymlinks", expectArgs{"/run"}, "/run", nil},
+ {"evalSymlinks", expectArgs{"/srv"}, "/srv", nil},
+ {"evalSymlinks", expectArgs{"/sys"}, "/sys", nil},
+ {"evalSymlinks", expectArgs{"/usr"}, "/usr", nil},
+ {"evalSymlinks", expectArgs{"/var"}, "/var", nil},
+ }, nil, []kexpect{
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr/bin"), MustAbs("/bin"), MustAbs("/bin"), BindWritable}}}, nil, nil},
+ {"stat", expectArgs{"/host/usr/bin"}, isDirFi(false), errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"success pd", &Params{ParentPerm: 0750}, &AutoRootOp{
+ Host: MustAbs("/"),
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ Flags: BindWritable,
+ }, []kexpect{
+ {"readdir", expectArgs{"/"}, stubDir("bin", "dev", "etc", "home", "lib64",
+ "lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil},
+ {"evalSymlinks", expectArgs{"/bin"}, "/usr/bin", nil},
+ {"evalSymlinks", expectArgs{"/home"}, "/home", nil},
+ {"evalSymlinks", expectArgs{"/lib64"}, "/lib64", nil},
+ {"evalSymlinks", expectArgs{"/lost+found"}, "/lost+found", nil},
+ {"evalSymlinks", expectArgs{"/nix"}, "/nix", nil},
+ {"evalSymlinks", expectArgs{"/root"}, "/root", nil},
+ {"evalSymlinks", expectArgs{"/run"}, "/run", nil},
+ {"evalSymlinks", expectArgs{"/srv"}, "/srv", nil},
+ {"evalSymlinks", expectArgs{"/sys"}, "/sys", nil},
+ {"evalSymlinks", expectArgs{"/usr"}, "/usr", nil},
+ {"evalSymlinks", expectArgs{"/var"}, "/var", nil},
+ }, nil, []kexpect{
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr/bin"), MustAbs("/bin"), MustAbs("/bin"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4004), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/home"), MustAbs("/home"), MustAbs("/home"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/home"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/home", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/home", "/sysroot/home", uintptr(0x4004), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/lib64"), MustAbs("/lib64"), MustAbs("/lib64"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/lib64"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/lib64", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/lib64", "/sysroot/lib64", uintptr(0x4004), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/lost+found"), MustAbs("/lost+found"), MustAbs("/lost+found"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/lost+found"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/lost+found", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/lost+found", "/sysroot/lost+found", uintptr(0x4004), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/nix"), MustAbs("/nix"), MustAbs("/nix"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/nix"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/nix", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/nix", "/sysroot/nix", uintptr(0x4004), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/root"), MustAbs("/root"), MustAbs("/root"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/root"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/root", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/root", "/sysroot/root", uintptr(0x4004), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/run"), MustAbs("/run"), MustAbs("/run"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/run"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/run", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/run", "/sysroot/run", uintptr(0x4004), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/srv"), MustAbs("/srv"), MustAbs("/srv"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/srv"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/srv", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/srv", "/sysroot/srv", uintptr(0x4004), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/sys"), MustAbs("/sys"), MustAbs("/sys"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/sys"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/sys", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/sys", "/sysroot/sys", uintptr(0x4004), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr"), MustAbs("/usr"), MustAbs("/usr"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/usr"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/usr", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/usr", "/sysroot/usr", uintptr(0x4004), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var"), MustAbs("/var"), MustAbs("/var"), BindWritable}}}, nil, nil}, {"stat", expectArgs{"/host/var"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/var", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var", "/sysroot/var", uintptr(0x4004), false}, nil, nil},
+ }, nil},
+
+ {"success", &Params{ParentPerm: 0750}, &AutoRootOp{
+ Host: MustAbs("/var/lib/planterette/base/debian:f92c9052"),
+ Prefix: "81ceabb30d37bbdb3868004629cb84e9",
+ }, []kexpect{
+ {"readdir", expectArgs{"/var/lib/planterette/base/debian:f92c9052"}, stubDir("bin", "dev", "etc", "home", "lib64",
+ "lost+found", "mnt", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var"), nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/bin"}, "/var/lib/planterette/base/debian:f92c9052/usr/bin", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/home"}, "/var/lib/planterette/base/debian:f92c9052/home", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/lib64"}, "/var/lib/planterette/base/debian:f92c9052/lib64", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/lost+found"}, "/var/lib/planterette/base/debian:f92c9052/lost+found", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/nix"}, "/var/lib/planterette/base/debian:f92c9052/nix", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/root"}, "/var/lib/planterette/base/debian:f92c9052/root", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/run"}, "/var/lib/planterette/base/debian:f92c9052/run", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/srv"}, "/var/lib/planterette/base/debian:f92c9052/srv", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/sys"}, "/var/lib/planterette/base/debian:f92c9052/sys", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/usr"}, "/var/lib/planterette/base/debian:f92c9052/usr", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052/var"}, "/var/lib/planterette/base/debian:f92c9052/var", nil},
+ }, nil, []kexpect{
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/usr/bin"), MustAbs("/var/lib/planterette/base/debian:f92c9052/bin"), MustAbs("/bin"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/home"), MustAbs("/var/lib/planterette/base/debian:f92c9052/home"), MustAbs("/home"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/home"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/home", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/home", "/sysroot/home", uintptr(0x4005), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/lib64"), MustAbs("/var/lib/planterette/base/debian:f92c9052/lib64"), MustAbs("/lib64"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lib64"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/lib64", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lib64", "/sysroot/lib64", uintptr(0x4005), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/lost+found"), MustAbs("/var/lib/planterette/base/debian:f92c9052/lost+found"), MustAbs("/lost+found"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lost+found"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/lost+found", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lost+found", "/sysroot/lost+found", uintptr(0x4005), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/nix"), MustAbs("/var/lib/planterette/base/debian:f92c9052/nix"), MustAbs("/nix"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/nix"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/nix", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/nix", "/sysroot/nix", uintptr(0x4005), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/root"), MustAbs("/var/lib/planterette/base/debian:f92c9052/root"), MustAbs("/root"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/root"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/root", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/root", "/sysroot/root", uintptr(0x4005), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/run"), MustAbs("/var/lib/planterette/base/debian:f92c9052/run"), MustAbs("/run"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/run"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/run", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/run", "/sysroot/run", uintptr(0x4005), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/srv"), MustAbs("/var/lib/planterette/base/debian:f92c9052/srv"), MustAbs("/srv"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/srv"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/srv", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/srv", "/sysroot/srv", uintptr(0x4005), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/sys"), MustAbs("/var/lib/planterette/base/debian:f92c9052/sys"), MustAbs("/sys"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/sys"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/sys", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/sys", "/sysroot/sys", uintptr(0x4005), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/usr"), MustAbs("/var/lib/planterette/base/debian:f92c9052/usr"), MustAbs("/usr"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/usr", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr", "/sysroot/usr", uintptr(0x4005), false}, nil, nil},
+ {"verbosef", expectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var/lib/planterette/base/debian:f92c9052/var"), MustAbs("/var/lib/planterette/base/debian:f92c9052/var"), MustAbs("/var"), 0}}}, nil, nil}, {"stat", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/var"}, isDirFi(true), nil}, {"mkdirAll", expectArgs{"/sysroot/var", os.FileMode(0700)}, nil, nil}, {"bindMount", expectArgs{"/host/var/lib/planterette/base/debian:f92c9052/var", "/sysroot/var", uintptr(0x4005), false}, nil, nil},
+ }, nil},
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*AutoRootOp)(nil), false},
{"zero", new(AutoRootOp), false},
diff --git a/container/capability.go b/container/capability.go
index fda2fb2a..f41bffa7 100644
--- a/container/capability.go
+++ b/container/capability.go
@@ -37,9 +37,52 @@ func capToIndex(cap uintptr) uintptr { return cap >> 5 }
func capToMask(cap uintptr) uint32 { return 1 << uint(cap&31) }
func capset(hdrp *capHeader, datap *[2]capData) error {
- if _, _, errno := syscall.Syscall(syscall.SYS_CAPSET,
+ r, _, errno := syscall.Syscall(
+ syscall.SYS_CAPSET,
uintptr(unsafe.Pointer(hdrp)),
- uintptr(unsafe.Pointer(&datap[0])), 0); errno != 0 {
+ uintptr(unsafe.Pointer(&datap[0])), 0,
+ )
+ if r != 0 {
+ return errno
+ }
+ return nil
+}
+
+// capBoundingSetDrop drops a capability from the calling thread's capability bounding set.
+func capBoundingSetDrop(cap uintptr) error {
+ r, _, errno := syscall.Syscall(
+ syscall.SYS_PRCTL,
+ syscall.PR_CAPBSET_DROP,
+ cap, 0,
+ )
+ if r != 0 {
+ return errno
+ }
+ return nil
+}
+
+// capAmbientClearAll clears the ambient capability set of the calling thread.
+func capAmbientClearAll() error {
+ r, _, errno := syscall.Syscall(
+ syscall.SYS_PRCTL,
+ PR_CAP_AMBIENT,
+ PR_CAP_AMBIENT_CLEAR_ALL, 0,
+ )
+ if r != 0 {
+ return errno
+ }
+ return nil
+}
+
+// capAmbientRaise adds to the ambient capability set of the calling thread.
+func capAmbientRaise(cap uintptr) error {
+ r, _, errno := syscall.Syscall(
+ syscall.SYS_PRCTL,
+ PR_CAP_AMBIENT,
+ PR_CAP_AMBIENT_RAISE,
+ cap,
+ )
+ if r != 0 {
return errno
}
return nil
diff --git a/container/dispatcher.go b/container/dispatcher.go
new file mode 100644
index 00000000..38ed8f8e
--- /dev/null
+++ b/container/dispatcher.go
@@ -0,0 +1,229 @@
+package container
+
+import (
+ "io"
+ "io/fs"
+ "log"
+ "os"
+ "os/exec"
+ "os/signal"
+ "path/filepath"
+ "syscall"
+
+ "hakurei.app/container/seccomp"
+)
+
+type osFile interface {
+ Name() string
+ io.Writer
+ fs.File
+}
+
+// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
+type syscallDispatcher interface {
+ // setPtracer provides [SetPtracer].
+ setPtracer(pid uintptr) error
+ // setDumpable provides [SetDumpable].
+ setDumpable(dumpable uintptr) error
+ // setNoNewPrivs provides [SetNoNewPrivs].
+ setNoNewPrivs() error
+
+ // lastcap provides [LastCap].
+ lastcap() uintptr
+ // capset provides capset.
+ capset(hdrp *capHeader, datap *[2]capData) error
+ // capBoundingSetDrop provides capBoundingSetDrop.
+ capBoundingSetDrop(cap uintptr) error
+ // capAmbientClearAll provides capAmbientClearAll.
+ capAmbientClearAll() error
+ // capAmbientRaise provides capAmbientRaise.
+ capAmbientRaise(cap uintptr) error
+ // isatty provides [Isatty].
+ isatty(fd int) bool
+ // receive provides [Receive].
+ receive(key string, e any, v **os.File) (closeFunc func() error, err error)
+
+ // bindMount provides procPaths.bindMount.
+ bindMount(source, target string, flags uintptr, eq bool) error
+ // remount provides procPaths.remount.
+ remount(target string, flags uintptr) error
+ // mountTmpfs provides mountTmpfs.
+ mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
+ // ensureFile provides ensureFile.
+ ensureFile(name string, perm, pperm os.FileMode) error
+
+ // seccompLoad provides [seccomp.Load].
+ seccompLoad(rules []seccomp.NativeRule, flags seccomp.ExportFlag) error
+ // notify provides [signal.Notify].
+ notify(c chan<- os.Signal, sig ...os.Signal)
+ // start starts [os/exec.Cmd].
+ start(c *exec.Cmd) error
+ // signal signals the underlying process of [os/exec.Cmd].
+ signal(c *exec.Cmd, sig os.Signal) error
+ // evalSymlinks provides [filepath.EvalSymlinks].
+ evalSymlinks(path string) (string, error)
+
+ // exit provides [os.Exit].
+ exit(code int)
+ // getpid provides [os.Getpid].
+ getpid() int
+ // stat provides [os.Stat].
+ stat(name string) (os.FileInfo, error)
+ // mkdir provides [os.Mkdir].
+ mkdir(name string, perm os.FileMode) error
+ // mkdirTemp provides [os.MkdirTemp].
+ mkdirTemp(dir, pattern string) (string, error)
+ // mkdirAll provides [os.MkdirAll].
+ mkdirAll(path string, perm os.FileMode) error
+ // readdir provides [os.ReadDir].
+ readdir(name string) ([]os.DirEntry, error)
+ // writeFile provides [os.WriteFile].
+ writeFile(name string, data []byte, perm os.FileMode) error
+ // createTemp provides [os.CreateTemp].
+ createTemp(dir, pattern string) (osFile, error)
+ // remove provides os.Remove.
+ remove(name string) error
+ // newFile provides os.NewFile.
+ newFile(fd uintptr, name string) *os.File
+ // symlink provides os.Symlink.
+ symlink(oldname, newname string) error
+ // readlink provides [os.Readlink].
+ readlink(name string) (string, error)
+
+ // umask provides syscall.Umask.
+ umask(mask int) (oldmask int)
+ // sethostname provides syscall.Sethostname
+ sethostname(p []byte) (err error)
+ // chdir provides syscall.Chdir
+ chdir(path string) (err error)
+ // fchdir provides syscall.Fchdir
+ fchdir(fd int) (err error)
+ // open provides syscall.Open
+ open(path string, mode int, perm uint32) (fd int, err error)
+ // close provides syscall.Close
+ close(fd int) (err error)
+ // pivotRoot provides syscall.PivotRoot
+ pivotRoot(newroot, putold string) (err error)
+ // mount provides syscall.Mount
+ mount(source, target, fstype string, flags uintptr, data string) (err error)
+ // unmount provides syscall.Unmount
+ unmount(target string, flags int) (err error)
+ // 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()
+ // printBaseErr provides [Msg.PrintBaseErr].
+ printBaseErr(err error, fallback string)
+}
+
+// direct implements syscallDispatcher on the current kernel.
+type direct struct{}
+
+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) 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() }
+func (direct) capAmbientRaise(cap uintptr) error { return capAmbientRaise(cap) }
+func (direct) isatty(fd int) bool { return Isatty(fd) }
+func (direct) receive(key string, e any, v **os.File) (func() error, error) {
+ return Receive(key, e, v)
+}
+
+func (direct) bindMount(source, target string, flags uintptr, eq bool) error {
+ return hostProc.bindMount(source, target, flags, eq)
+}
+func (direct) remount(target string, flags uintptr) error {
+ return hostProc.remount(target, flags)
+}
+func (direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
+ return mountTmpfs(fsname, target, flags, size, perm)
+}
+func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
+ return ensureFile(name, perm, pperm)
+}
+
+func (direct) seccompLoad(rules []seccomp.NativeRule, flags seccomp.ExportFlag) error {
+ return seccomp.Load(rules, flags)
+}
+func (direct) notify(c chan<- os.Signal, sig ...os.Signal) { signal.Notify(c, sig...) }
+func (direct) start(c *exec.Cmd) error { return c.Start() }
+func (direct) signal(c *exec.Cmd, sig os.Signal) error { return c.Process.Signal(sig) }
+func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
+
+func (direct) exit(code int) { os.Exit(code) }
+func (direct) getpid() int { return os.Getpid() }
+func (direct) stat(name string) (os.FileInfo, error) { return os.Stat(name) }
+func (direct) mkdir(name string, perm os.FileMode) error { return os.Mkdir(name, perm) }
+func (direct) mkdirTemp(dir, pattern string) (string, error) { return os.MkdirTemp(dir, pattern) }
+func (direct) mkdirAll(path string, perm os.FileMode) error { return os.MkdirAll(path, perm) }
+func (direct) readdir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
+func (direct) writeFile(name string, data []byte, perm os.FileMode) error {
+ return os.WriteFile(name, data, perm)
+}
+func (direct) createTemp(dir, pattern string) (osFile, error) {
+ return os.CreateTemp(dir, pattern)
+}
+func (direct) remove(name string) error {
+ return os.Remove(name)
+}
+func (direct) newFile(fd uintptr, name string) *os.File {
+ return os.NewFile(fd, name)
+}
+func (direct) symlink(oldname, newname string) error {
+ return os.Symlink(oldname, newname)
+}
+func (direct) readlink(name string) (string, error) {
+ return os.Readlink(name)
+}
+
+func (direct) umask(mask int) (oldmask int) { return syscall.Umask(mask) }
+func (direct) sethostname(p []byte) (err error) { return syscall.Sethostname(p) }
+func (direct) chdir(path string) (err error) { return syscall.Chdir(path) }
+func (direct) fchdir(fd int) (err error) { return syscall.Fchdir(fd) }
+func (direct) open(path string, mode int, perm uint32) (fd int, err error) {
+ return syscall.Open(path, mode, perm)
+}
+func (direct) close(fd int) (err error) {
+ return syscall.Close(fd)
+}
+func (direct) pivotRoot(newroot, putold string) (err error) {
+ return syscall.PivotRoot(newroot, putold)
+}
+func (direct) mount(source, target, fstype string, flags uintptr, data string) (err error) {
+ return syscall.Mount(source, target, fstype, flags, data)
+}
+func (direct) unmount(target string, flags int) (err error) {
+ return syscall.Unmount(target, flags)
+}
+func (direct) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error) {
+ 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) printBaseErr(err error, fallback string) { msg.PrintBaseErr(err, fallback) }
diff --git a/container/dispatcher_test.go b/container/dispatcher_test.go
new file mode 100644
index 00000000..e2b553f4
--- /dev/null
+++ b/container/dispatcher_test.go
@@ -0,0 +1,595 @@
+package container
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "os/exec"
+ "reflect"
+ "runtime"
+ "slices"
+ "syscall"
+ "testing"
+ "time"
+
+ "hakurei.app/container/seccomp"
+)
+
+var errUnique = errors.New("unique error injected by the test suite")
+
+type opValidTestCase struct {
+ name string
+ op Op
+ want bool
+}
+
+func checkOpsValid(t *testing.T, testCases []opValidTestCase) {
+ t.Run("valid", func(t *testing.T) {
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ if got := tc.op.Valid(); got != tc.want {
+ t.Errorf("Valid: %v, want %v", got, tc.want)
+ }
+ })
+ }
+ })
+}
+
+type opsBuilderTestCase struct {
+ name string
+ ops *Ops
+ want Ops
+}
+
+func checkOpsBuilder(t *testing.T, testCases []opsBuilderTestCase) {
+ t.Run("build", func(t *testing.T) {
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ if !slices.EqualFunc(*tc.ops, tc.want, func(op Op, v Op) bool { return op.Is(v) }) {
+ t.Errorf("Ops: %#v, want %#v", tc.ops, tc.want)
+ }
+ })
+ }
+ })
+}
+
+type opIsTestCase struct {
+ name string
+ op, v Op
+ want bool
+}
+
+func checkOpIs(t *testing.T, testCases []opIsTestCase) {
+ t.Run("is", func(t *testing.T) {
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ if got := tc.op.Is(tc.v); got != tc.want {
+ t.Errorf("Is: %v, want %v", got, tc.want)
+ }
+ })
+ }
+ })
+}
+
+type opMetaTestCase struct {
+ name string
+ op Op
+
+ wantPrefix string
+ wantString string
+}
+
+func checkOpMeta(t *testing.T, testCases []opMetaTestCase) {
+ t.Run("meta", func(t *testing.T) {
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Run("prefix", func(t *testing.T) {
+ if got := tc.op.prefix(); got != tc.wantPrefix {
+ t.Errorf("prefix: %q, want %q", got, tc.wantPrefix)
+ }
+ })
+
+ t.Run("string", func(t *testing.T) {
+ if got := tc.op.String(); got != tc.wantString {
+ t.Errorf("String: %s, want %s", got, tc.wantString)
+ }
+ })
+ })
+ }
+ })
+}
+
+type opBehaviourTestCase struct {
+ name string
+ params *Params
+ op Op
+
+ early []kexpect
+ wantErrEarly error
+
+ apply []kexpect
+ wantErrApply error
+}
+
+func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
+ t.Run("behaviour", func(t *testing.T) {
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ state := &setupState{Params: tc.params}
+ k := &kstub{t: t, want: slices.Concat(tc.early, []kexpect{{name: "\x00"}}, tc.apply)}
+ errEarly := tc.op.early(state, k)
+ k.expect("\x00")
+ if !errors.Is(errEarly, tc.wantErrEarly) {
+ t.Errorf("early: error = %v, want %v", errEarly, tc.wantErrEarly)
+ }
+ if errEarly != nil {
+ goto out
+ }
+
+ if err := tc.op.apply(state, k); !errors.Is(err, tc.wantErrApply) {
+ t.Errorf("apply: error = %v, want %v", err, tc.wantErrApply)
+ }
+
+ out:
+ if len(k.want) != k.pos {
+ count := k.pos - 1 // separator
+ if count < len(tc.early) {
+ t.Errorf("early: %d calls, want %d", count, len(tc.early))
+ } else {
+ t.Errorf("apply: %d calls, want %d", count-len(tc.early), len(tc.apply))
+ }
+ }
+ })
+ }
+ })
+}
+
+func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
+ f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
+ // check happens in Close, and cleanup is not guaranteed to run, so relying on it for sloppy implementations will cause sporadic test results
+ f.cleanup = runtime.AddCleanup(f, func(name string) { f.t.Fatalf("checkedOsFile %s became unreachable without a call to Close", name) }, f.name)
+ return f
+}
+
+type checkedOsFile struct {
+ t *testing.T
+ name string
+ want string
+ closeErr error
+ cleanup runtime.Cleanup
+ bytes.Buffer
+}
+
+func (f *checkedOsFile) Name() string { return f.name }
+func (f *checkedOsFile) Stat() (fs.FileInfo, error) { panic("unreachable") }
+func (f *checkedOsFile) Close() error {
+ defer f.cleanup.Stop()
+ if f.String() != f.want {
+ f.t.Errorf("checkedOsFile:\n%s\nwant\n%s", f.String(), f.want)
+ return syscall.ENOTRECOVERABLE
+ }
+ return f.closeErr
+}
+
+type writeErrOsFile struct{ err error }
+
+func (writeErrOsFile) Name() string { panic("unreachable") }
+func (f writeErrOsFile) Write([]byte) (int, error) { return 0, f.err }
+func (writeErrOsFile) Stat() (fs.FileInfo, error) { panic("unreachable") }
+func (writeErrOsFile) Read([]byte) (int, error) { panic("unreachable") }
+func (writeErrOsFile) Close() error { panic("unreachable") }
+
+type expectArgs = [5]any
+
+type isDirFi bool
+
+func (isDirFi) Name() string { panic("unreachable") }
+func (isDirFi) Size() int64 { panic("unreachable") }
+func (isDirFi) Mode() fs.FileMode { panic("unreachable") }
+func (isDirFi) ModTime() time.Time { panic("unreachable") }
+func (fi isDirFi) IsDir() bool { return bool(fi) }
+func (isDirFi) Sys() any { panic("unreachable") }
+
+func stubDir(names ...string) []os.DirEntry {
+ d := make([]os.DirEntry, len(names))
+ for i, name := range names {
+ d[i] = nameDentry(name)
+ }
+ return d
+}
+
+type nameDentry string
+
+func (e nameDentry) Name() string { return string(e) }
+func (nameDentry) IsDir() bool { panic("unreachable") }
+func (nameDentry) Type() fs.FileMode { panic("unreachable") }
+func (nameDentry) Info() (fs.FileInfo, error) { panic("unreachable") }
+
+type kexpect struct {
+ name string
+ args expectArgs
+ ret any
+ err error
+}
+
+func (k *kexpect) error(ok ...bool) error {
+ if !slices.Contains(ok, false) {
+ return k.err
+ }
+ return syscall.ENOTRECOVERABLE
+}
+
+type kstub struct {
+ t *testing.T
+ want []kexpect
+ pos int
+}
+
+// expect checks name and returns the current kexpect and advances pos.
+func (k *kstub) expect(name string) (expect *kexpect) {
+ if len(k.want) == k.pos {
+ k.t.Fatal("expect: want too short")
+ }
+ expect = &k.want[k.pos]
+ if name != expect.name {
+ if expect.name == "\x00" {
+ k.t.Fatalf("expect: func = %s, separator overrun", name)
+ }
+ if name == "\x00" {
+ k.t.Fatalf("expect: separator, want %s", expect.name)
+ }
+ k.t.Fatalf("expect: func = %s, want %s", name, expect.name)
+ }
+ k.pos++
+ return
+}
+
+// checkArg checks an argument comparable with the == operator. Avoid using this with pointers.
+func checkArg[T comparable](k *kstub, arg string, got T, n int) bool {
+ if k.pos == 0 {
+ panic("invalid call to checkArg")
+ }
+ expect := k.want[k.pos-1]
+ want, ok := expect.args[n].(T)
+ if !ok || got != want {
+ k.t.Errorf("%s: %s = %#v, want %#v (%d)", expect.name, arg, got, want, k.pos-1)
+ return false
+ }
+ return true
+}
+
+// checkArgReflect checks an argument of any type.
+func checkArgReflect(k *kstub, arg string, got any, n int) bool {
+ if k.pos == 0 {
+ panic("invalid call to checkArgReflect")
+ }
+ expect := k.want[k.pos-1]
+ want := expect.args[n]
+ if !reflect.DeepEqual(got, want) {
+ k.t.Errorf("%s: %s = %#v, want %#v (%d)", expect.name, arg, got, want, k.pos-1)
+ return false
+ }
+ return true
+}
+
+func (k *kstub) setPtracer(pid uintptr) error {
+ return k.expect("setPtracer").error(
+ checkArg(k, "pid", pid, 0))
+}
+
+func (k *kstub) setDumpable(dumpable uintptr) error {
+ return k.expect("setDumpable").error(
+ checkArg(k, "dumpable", dumpable, 0))
+}
+
+func (k *kstub) setNoNewPrivs() error { return k.expect("setNoNewPrivs").err }
+func (k *kstub) lastcap() uintptr { return k.expect("setNoNewPrivs").ret.(uintptr) }
+
+func (k *kstub) capset(hdrp *capHeader, datap *[2]capData) error {
+ return k.expect("capset").error(
+ checkArgReflect(k, "hdrp", hdrp, 0),
+ checkArgReflect(k, "datap", datap, 1))
+}
+
+func (k *kstub) capBoundingSetDrop(cap uintptr) error {
+ return k.expect("capBoundingSetDrop").error(
+ checkArg(k, "cap", cap, 0))
+}
+
+func (k *kstub) capAmbientClearAll() error { return k.expect("capAmbientClearAll").err }
+
+func (k *kstub) capAmbientRaise(cap uintptr) error {
+ return k.expect("capAmbientRaise").error(
+ checkArg(k, "cap", cap, 0))
+}
+
+func (k *kstub) isatty(fd int) bool {
+ expect := k.expect("isatty")
+ if !checkArg(k, "fd", fd, 0) {
+ k.t.FailNow()
+ }
+ return expect.ret.(bool)
+}
+
+func (k *kstub) receive(key string, e any, v **os.File) (closeFunc func() error, err error) {
+ expect := k.expect("receive")
+ return expect.ret.(func() error), expect.error(
+ checkArg(k, "key", key, 0),
+ checkArgReflect(k, "e", e, 1),
+ checkArg(k, "v", v, 2))
+}
+
+func (k *kstub) bindMount(source, target string, flags uintptr, eq bool) error {
+ return k.expect("bindMount").error(
+ checkArg(k, "source", source, 0),
+ checkArg(k, "target", target, 1),
+ checkArg(k, "flags", flags, 2),
+ checkArg(k, "eq", eq, 3))
+}
+
+func (k *kstub) remount(target string, flags uintptr) error {
+ return k.expect("remount").error(
+ checkArg(k, "target", target, 0),
+ checkArg(k, "flags", flags, 1))
+}
+
+func (k *kstub) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
+ return k.expect("mountTmpfs").error(
+ checkArg(k, "fsname", fsname, 0),
+ checkArg(k, "target", target, 1),
+ checkArg(k, "flags", flags, 2),
+ checkArg(k, "size", size, 3),
+ checkArg(k, "perm", perm, 4))
+}
+
+func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
+
+ return k.expect("ensureFile").error(
+ checkArg(k, "name", name, 0),
+ checkArg(k, "perm", perm, 1),
+ checkArg(k, "pperm", pperm, 2))
+}
+
+func (k *kstub) seccompLoad(rules []seccomp.NativeRule, flags seccomp.ExportFlag) error {
+ return k.expect("seccompLoad").error(
+ checkArgReflect(k, "rules", rules, 0),
+ checkArg(k, "flags", flags, 1))
+}
+
+func (k *kstub) notify(c chan<- os.Signal, sig ...os.Signal) {
+ expect := k.expect("notify")
+ if c == nil || expect.error(
+ checkArgReflect(k, "sig", sig, 1)) != nil {
+ k.t.FailNow()
+ }
+
+ // export channel for external instrumentation
+ if chanp, ok := expect.args[0].(*chan<- os.Signal); ok && chanp != nil {
+ if *chanp != nil {
+ panic(fmt.Sprintf("attempting reuse of %p", chanp))
+ }
+ *chanp = c
+ }
+}
+
+func (k *kstub) start(c *exec.Cmd) error {
+ return k.expect("start").error(
+ checkArg(k, "c.Path", c.Path, 0),
+ checkArgReflect(k, "c.Args", c.Args, 1),
+ checkArgReflect(k, "c.Env", c.Env, 2),
+ checkArg(k, "c.Dir", c.Dir, 3))
+}
+
+func (k *kstub) signal(c *exec.Cmd, sig os.Signal) error {
+ return k.expect("signal").error(
+ checkArg(k, "c.Path", c.Path, 0),
+ checkArgReflect(k, "c.Args", c.Args, 1),
+ checkArgReflect(k, "c.Env", c.Env, 2),
+ checkArg(k, "c.Dir", c.Dir, 3),
+ checkArg(k, "sig", sig, 4))
+}
+
+func (k *kstub) evalSymlinks(path string) (string, error) {
+ expect := k.expect("evalSymlinks")
+ return expect.ret.(string), expect.error(
+ checkArg(k, "path", path, 0))
+}
+
+func (k *kstub) exit(code int) {
+ k.expect("exit")
+ if !checkArg(k, "code", code, 0) {
+ k.t.FailNow()
+ }
+}
+
+func (k *kstub) getpid() int { return k.expect("getpid").ret.(int) }
+
+func (k *kstub) stat(name string) (os.FileInfo, error) {
+ expect := k.expect("stat")
+ return expect.ret.(os.FileInfo), expect.error(
+ checkArg(k, "name", name, 0))
+}
+
+func (k *kstub) mkdir(name string, perm os.FileMode) error {
+ return k.expect("mkdir").error(
+ checkArg(k, "name", name, 0),
+ checkArg(k, "perm", perm, 1))
+}
+
+func (k *kstub) mkdirTemp(dir, pattern string) (string, error) {
+ expect := k.expect("mkdirTemp")
+ return expect.ret.(string), expect.error(
+ checkArg(k, "dir", dir, 0),
+ checkArg(k, "pattern", pattern, 1))
+}
+
+func (k *kstub) mkdirAll(path string, perm os.FileMode) error {
+ return k.expect("mkdirAll").error(
+ checkArg(k, "path", path, 0),
+ checkArg(k, "perm", perm, 1))
+}
+
+func (k *kstub) readdir(name string) ([]os.DirEntry, error) {
+ expect := k.expect("readdir")
+ return expect.ret.([]os.DirEntry), expect.error(
+ checkArg(k, "name", name, 0))
+}
+
+func (k *kstub) writeFile(name string, data []byte, perm os.FileMode) error {
+ return k.expect("writeFile").error(
+ checkArg(k, "name", name, 0),
+ checkArgReflect(k, "data", data, 1),
+ checkArg(k, "perm", perm, 2))
+}
+
+func (k *kstub) createTemp(dir, pattern string) (osFile, error) {
+ expect := k.expect("createTemp")
+ return expect.ret.(osFile), expect.error(
+ checkArg(k, "dir", dir, 0),
+ checkArg(k, "pattern", pattern, 1))
+}
+
+func (k *kstub) remove(name string) error {
+ return k.expect("remove").error(
+ checkArg(k, "name", name, 0))
+}
+
+func (k *kstub) newFile(fd uintptr, name string) *os.File {
+ expect := k.expect("newFile")
+ if expect.error(
+ checkArg(k, "fd", fd, 0),
+ checkArg(k, "name", name, 1)) != nil {
+ k.t.FailNow()
+ }
+ return expect.ret.(*os.File)
+}
+
+func (k *kstub) symlink(oldname, newname string) error {
+ return k.expect("symlink").error(
+ checkArg(k, "oldname", oldname, 0),
+ checkArg(k, "newname", newname, 1))
+}
+
+func (k *kstub) readlink(name string) (string, error) {
+ expect := k.expect("readlink")
+ return expect.ret.(string), expect.error(
+ checkArg(k, "name", name, 0))
+}
+
+func (k *kstub) umask(mask int) (oldmask int) {
+ expect := k.expect("umask")
+ if !checkArg(k, "mask", mask, 0) {
+ k.t.FailNow()
+ }
+ return expect.ret.(int)
+}
+
+func (k *kstub) sethostname(p []byte) (err error) {
+ return k.expect("sethostname").error(
+ checkArgReflect(k, "p", p, 0))
+}
+
+func (k *kstub) chdir(path string) (err error) {
+ return k.expect("chdir").error(
+ checkArg(k, "path", path, 0))
+}
+
+func (k *kstub) fchdir(fd int) (err error) {
+ return k.expect("fchdir").error(
+ checkArg(k, "fd", fd, 0))
+}
+
+func (k *kstub) open(path string, mode int, perm uint32) (fd int, err error) {
+ expect := k.expect("open")
+ return expect.ret.(int), expect.error(
+ checkArg(k, "path", path, 0),
+ checkArg(k, "mode", mode, 1),
+ checkArg(k, "perm", perm, 2))
+}
+
+func (k *kstub) close(fd int) (err error) {
+ return k.expect("close").error(
+ checkArg(k, "fd", fd, 0))
+}
+
+func (k *kstub) pivotRoot(newroot, putold string) (err error) {
+ return k.expect("pivotRoot").error(
+ checkArg(k, "newroot", newroot, 0),
+ checkArg(k, "putold", putold, 1))
+}
+
+func (k *kstub) mount(source, target, fstype string, flags uintptr, data string) (err error) {
+ return k.expect("mount").error(
+ checkArg(k, "source", source, 0),
+ checkArg(k, "target", target, 1),
+ checkArg(k, "fstype", fstype, 2),
+ checkArg(k, "flags", flags, 3),
+ checkArg(k, "data", data, 4))
+}
+
+func (k *kstub) unmount(target string, flags int) (err error) {
+ return k.expect("unmount").error(
+ checkArg(k, "target", target, 0),
+ checkArg(k, "flags", flags, 1))
+}
+
+func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error) {
+ expect := k.expect("wait4")
+ return expect.ret.(int), expect.error(
+ checkArg(k, "pid", pid, 0),
+ checkArg(k, "wstatus", wstatus, 1),
+ checkArg(k, "options", options, 2),
+ checkArg(k, "rusage", rusage, 3))
+}
+
+func (k *kstub) printf(format string, v ...any) {
+ if k.expect("printf").error(
+ checkArg(k, "format", format, 0),
+ checkArgReflect(k, "v", v, 1)) != nil {
+ k.t.FailNow()
+ }
+}
+
+func (k *kstub) fatal(v ...any) {
+ if k.expect("fatal").error(
+ checkArgReflect(k, "v", v, 0)) != nil {
+ k.t.FailNow()
+ }
+}
+
+func (k *kstub) fatalf(format string, v ...any) {
+ if k.expect("fatalf").error(
+ checkArg(k, "format", format, 0),
+ checkArgReflect(k, "v", v, 1)) != nil {
+ k.t.FailNow()
+ }
+}
+
+func (k *kstub) verbose(v ...any) {
+ if k.expect("verbose").error(
+ checkArgReflect(k, "v", v, 0)) != nil {
+ k.t.FailNow()
+ }
+}
+
+func (k *kstub) verbosef(format string, v ...any) {
+ if k.expect("verbosef").error(
+ checkArg(k, "format", format, 0),
+ checkArgReflect(k, "v", v, 1)) != nil {
+ k.t.FailNow()
+ }
+}
+
+func (k *kstub) suspend() { k.expect("suspend") }
+func (k *kstub) resume() bool { return k.expect("resume").ret.(bool) }
+func (k *kstub) beforeExit() { k.expect("beforeExit") }
+
+func (k *kstub) printBaseErr(err error, fallback string) {
+ if k.expect("printBaseErr").error(
+ checkArgReflect(k, "err", err, 0),
+ checkArg(k, "fallback", fallback, 1)) != nil {
+ k.t.FailNow()
+ }
+}
diff --git a/container/init.go b/container/init.go
index 663bbc83..6688a348 100644
--- a/container/init.go
+++ b/container/init.go
@@ -3,10 +3,8 @@ package container
import (
"errors"
"fmt"
- "log"
"os"
"os/exec"
- "os/signal"
"path"
"runtime"
"slices"
@@ -46,9 +44,9 @@ type (
// Implementations of this interface are sent as a stream of gobs.
Op interface {
// early is called in host root.
- early(state *setupState) error
+ early(state *setupState, k syscallDispatcher) error
// apply is called in intermediate root.
- apply(state *setupState) error
+ apply(state *setupState, k syscallDispatcher) error
prefix() string
Is(op Op) bool
@@ -82,16 +80,20 @@ type initParams struct {
Verbose bool
}
-func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
+func Init(prepareLogger func(prefix string), setVerbose func(verbose bool)) {
+ initEntrypoint(prepareLogger, setVerbose, direct{})
+}
+
+func initEntrypoint(prepareLogger func(prefix string), setVerbose func(verbose bool), k syscallDispatcher) {
runtime.LockOSThread()
- prepare("init")
+ prepareLogger("init")
- if os.Getpid() != 1 {
- log.Fatal("this process must run as pid 1")
+ if k.getpid() != 1 {
+ k.fatal("this process must run as pid 1")
}
- if err := SetPtracer(0); err != nil {
- msg.Verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
+ if err := k.setPtracer(0); err != nil {
+ k.verbosef("cannot enable ptrace protection via Yama LSM: %v", err)
// not fatal: this program has no additional privileges at initial program start
}
@@ -101,64 +103,64 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
setupFile *os.File
offsetSetup int
)
- if f, err := Receive(setupEnv, &params, &setupFile); err != nil {
+ if f, err := k.receive(setupEnv, &params, &setupFile); err != nil {
if errors.Is(err, EBADF) {
- log.Fatal("invalid setup descriptor")
+ k.fatal("invalid setup descriptor")
}
if errors.Is(err, ErrNotSet) {
- log.Fatal("HAKUREI_SETUP not set")
+ k.fatal("HAKUREI_SETUP not set")
}
- log.Fatalf("cannot decode init setup payload: %v", err)
+ k.fatalf("cannot decode init setup payload: %v", err)
} else {
if params.Ops == nil {
- log.Fatal("invalid setup parameters")
+ k.fatal("invalid setup parameters")
}
if params.ParentPerm == 0 {
params.ParentPerm = 0755
}
setVerbose(params.Verbose)
- msg.Verbose("received setup parameters")
+ k.verbose("received setup parameters")
closeSetup = f
offsetSetup = int(setupFile.Fd() + 1)
}
// write uid/gid map here so parent does not need to set dumpable
- if err := SetDumpable(SUID_DUMP_USER); err != nil {
- log.Fatalf("cannot set SUID_DUMP_USER: %s", err)
+ if err := k.setDumpable(SUID_DUMP_USER); err != nil {
+ k.fatalf("cannot set SUID_DUMP_USER: %s", err)
}
- if err := os.WriteFile(FHSProc+"self/uid_map",
+ if err := k.writeFile(FHSProc+"self/uid_map",
append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...),
0); err != nil {
- log.Fatalf("%v", err)
+ k.fatalf("%v", err)
}
- if err := os.WriteFile(FHSProc+"self/setgroups",
+ if err := k.writeFile(FHSProc+"self/setgroups",
[]byte("deny\n"),
0); err != nil && !os.IsNotExist(err) {
- log.Fatalf("%v", err)
+ k.fatalf("%v", err)
}
- if err := os.WriteFile(FHSProc+"self/gid_map",
+ if err := k.writeFile(FHSProc+"self/gid_map",
append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...),
0); err != nil {
- log.Fatalf("%v", err)
+ k.fatalf("%v", err)
}
- if err := SetDumpable(SUID_DUMP_DISABLE); err != nil {
- log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
+ if err := k.setDumpable(SUID_DUMP_DISABLE); err != nil {
+ k.fatalf("cannot set SUID_DUMP_DISABLE: %s", err)
}
- oldmask := Umask(0)
+ oldmask := k.umask(0)
if params.Hostname != "" {
- if err := Sethostname([]byte(params.Hostname)); err != nil {
- log.Fatalf("cannot set hostname: %v", err)
+ if err := k.sethostname([]byte(params.Hostname)); err != nil {
+ k.fatalf("cannot set hostname: %v", err)
}
}
// cache sysctl before pivot_root
- LastCap()
+ k.lastcap()
- if err := Mount(zeroString, FHSRoot, zeroString, MS_SILENT|MS_SLAVE|MS_REC, zeroString); err != nil {
- log.Fatalf("cannot make / rslave: %v", err)
+ if err := k.mount(zeroString, FHSRoot, zeroString, MS_SILENT|MS_SLAVE|MS_REC, zeroString); err != nil {
+ k.fatalf("cannot make / rslave: %v", err)
}
state := &setupState{Params: &params.Params}
@@ -169,40 +171,40 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
the state of the mount namespace */
for i, op := range *params.Ops {
if op == nil || !op.Valid() {
- log.Fatalf("invalid op at index %d", i)
+ k.fatalf("invalid op at index %d", i)
}
- if err := op.early(state); err != nil {
- msg.PrintBaseErr(err,
- fmt.Sprintf("cannot prepare op %d:", i))
- msg.BeforeExit()
- os.Exit(1)
+ if err := op.early(state, k); err != nil {
+ k.printBaseErr(err,
+ fmt.Sprintf("cannot prepare op at index %d:", i))
+ k.beforeExit()
+ k.exit(1)
}
}
- if err := Mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
- log.Fatalf("cannot mount intermediate root: %v", err)
+ if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
+ k.fatalf("cannot mount intermediate root: %v", err)
}
- if err := os.Chdir(intermediateHostPath); err != nil {
- log.Fatalf("cannot enter base path: %v", err)
+ if err := k.chdir(intermediateHostPath); err != nil {
+ k.fatalf("cannot enter intermediate host path: %v", err)
}
- if err := os.Mkdir(sysrootDir, 0755); err != nil {
- log.Fatalf("%v", err)
+ if err := k.mkdir(sysrootDir, 0755); err != nil {
+ k.fatalf("%v", err)
}
- if err := Mount(sysrootDir, sysrootDir, zeroString, MS_SILENT|MS_BIND|MS_REC, zeroString); err != nil {
- log.Fatalf("cannot bind sysroot: %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)
}
- if err := os.Mkdir(hostDir, 0755); err != nil {
- log.Fatalf("%v", err)
+ if err := k.mkdir(hostDir, 0755); err != nil {
+ k.fatalf("%v", err)
}
// pivot_root uncovers intermediateHostPath in hostDir
- if err := PivotRoot(intermediateHostPath, hostDir); err != nil {
- log.Fatalf("cannot pivot into intermediate root: %v", err)
+ if err := k.pivotRoot(intermediateHostPath, hostDir); err != nil {
+ k.fatalf("cannot pivot into intermediate root: %v", err)
}
- if err := os.Chdir(FHSRoot); err != nil {
- log.Fatalf("%v", err)
+ if err := k.chdir(FHSRoot); err != nil {
+ k.fatalf("cannot enter intermediate root: %v", err)
}
/* apply is called right after pivot_root and entering the new root;
@@ -211,62 +213,62 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
chdir is allowed but discouraged */
for i, op := range *params.Ops {
// ops already checked during early setup
- msg.Verbosef("%s %s", op.prefix(), op)
- if err := op.apply(state); err != nil {
- msg.PrintBaseErr(err,
- fmt.Sprintf("cannot apply op %d:", i))
- msg.BeforeExit()
- os.Exit(1)
+ k.verbosef("%s %s", op.prefix(), op)
+ if err := op.apply(state, k); err != nil {
+ k.printBaseErr(err,
+ fmt.Sprintf("cannot apply op at index %d:", i))
+ k.beforeExit()
+ k.exit(1)
}
}
// setup requiring host root complete at this point
- if err := Mount(hostDir, hostDir, zeroString, MS_SILENT|MS_REC|MS_PRIVATE, zeroString); err != nil {
- log.Fatalf("cannot make host root rprivate: %v", err)
+ 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)
}
- if err := Unmount(hostDir, MNT_DETACH); err != nil {
- log.Fatalf("cannot unmount host root: %v", err)
+ if err := k.unmount(hostDir, MNT_DETACH); err != nil {
+ k.fatalf("cannot unmount host root: %v", err)
}
{
var fd int
if err := IgnoringEINTR(func() (err error) {
- fd, err = Open(FHSRoot, O_DIRECTORY|O_RDONLY, 0)
+ fd, err = k.open(FHSRoot, O_DIRECTORY|O_RDONLY, 0)
return
}); err != nil {
- log.Fatalf("cannot open intermediate root: %v", err)
+ k.fatalf("cannot open intermediate root: %v", err)
}
- if err := os.Chdir(sysrootPath); err != nil {
- log.Fatalf("%v", err)
+ if err := k.chdir(sysrootPath); err != nil {
+ k.fatalf("cannot enter sysroot: %v", err)
}
- if err := PivotRoot(".", "."); err != nil {
- log.Fatalf("cannot pivot into sysroot: %v", err)
+ if err := k.pivotRoot(".", "."); err != nil {
+ k.fatalf("cannot pivot into sysroot: %v", err)
}
- if err := Fchdir(fd); err != nil {
- log.Fatalf("cannot re-enter intermediate root: %v", err)
+ if err := k.fchdir(fd); err != nil {
+ k.fatalf("cannot re-enter intermediate root: %v", err)
}
- if err := Unmount(".", MNT_DETACH); err != nil {
- log.Fatalf("cannot unmount intemediate root: %v", err)
+ if err := k.unmount(".", MNT_DETACH); err != nil {
+ k.fatalf("cannot unmount intemediate root: %v", err)
}
- if err := os.Chdir(FHSRoot); err != nil {
- log.Fatalf("%v", err)
+ if err := k.chdir(FHSRoot); err != nil {
+ k.fatalf("cannot enter root: %v", err)
}
- if err := Close(fd); err != nil {
- log.Fatalf("cannot close intermediate root: %v", err)
+ if err := k.close(fd); err != nil {
+ k.fatalf("cannot close intermediate root: %v", err)
}
}
- if _, _, errno := Syscall(SYS_PRCTL, PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0); errno != 0 {
- log.Fatalf("cannot clear the ambient capability set: %v", errno)
+ if err := k.capAmbientClearAll(); err != nil {
+ k.fatalf("cannot clear the ambient capability set: %v", err)
}
- for i := uintptr(0); i <= LastCap(); i++ {
+ for i := uintptr(0); i <= k.lastcap(); i++ {
if params.Privileged && i == CAP_SYS_ADMIN {
continue
}
- if _, _, errno := Syscall(SYS_PRCTL, PR_CAPBSET_DROP, i, 0); errno != 0 {
- log.Fatalf("cannot drop capability from bonding set: %v", errno)
+ if err := k.capBoundingSetDrop(i); err != nil {
+ k.fatalf("cannot drop capability from bounding set: %v", err)
}
}
@@ -274,38 +276,38 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
if params.Privileged {
keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN)
- if _, _, errno := Syscall(SYS_PRCTL, PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_SYS_ADMIN); errno != 0 {
- log.Fatalf("cannot raise CAP_SYS_ADMIN: %v", errno)
+ if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil {
+ k.fatalf("cannot raise CAP_SYS_ADMIN: %v", err)
}
}
- if err := capset(
+ if err := k.capset(
&capHeader{_LINUX_CAPABILITY_VERSION_3, 0},
&[2]capData{{0, keep[0], keep[0]}, {0, keep[1], keep[1]}},
); err != nil {
- log.Fatalf("cannot capset: %v", err)
+ k.fatalf("cannot capset: %v", err)
}
if !params.SeccompDisable {
rules := params.SeccompRules
if len(rules) == 0 { // non-empty rules slice always overrides presets
- msg.Verbosef("resolving presets %#x", params.SeccompPresets)
+ k.verbosef("resolving presets %#x", params.SeccompPresets)
rules = seccomp.Preset(params.SeccompPresets, params.SeccompFlags)
}
- if err := seccomp.Load(rules, params.SeccompFlags); err != nil {
+ if err := k.seccompLoad(rules, params.SeccompFlags); err != nil {
// this also indirectly asserts PR_SET_NO_NEW_PRIVS
- log.Fatalf("cannot load syscall filter: %v", err)
+ k.fatalf("cannot load syscall filter: %v", err)
}
- msg.Verbosef("%d filter rules loaded", len(rules))
+ k.verbosef("%d filter rules loaded", len(rules))
} else {
- msg.Verbose("syscall filter not configured")
+ k.verbose("syscall filter not configured")
}
extraFiles := make([]*os.File, params.Count)
for i := range extraFiles {
// setup fd is placed before all extra files
- extraFiles[i] = os.NewFile(uintptr(offsetSetup+i), "extra file "+strconv.Itoa(i))
+ extraFiles[i] = k.newFile(uintptr(offsetSetup+i), "extra file "+strconv.Itoa(i))
}
- Umask(oldmask)
+ k.umask(oldmask)
cmd := exec.Command(params.Path.String())
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
@@ -314,14 +316,14 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
cmd.ExtraFiles = extraFiles
cmd.Dir = params.Dir.String()
- msg.Verbosef("starting initial program %s", params.Path)
- if err := cmd.Start(); err != nil {
- log.Fatalf("%v", err)
+ k.verbosef("starting initial program %s", params.Path)
+ if err := k.start(cmd); err != nil {
+ k.fatalf("%v", err)
}
- msg.Suspend()
+ k.suspend()
if err := closeSetup(); err != nil {
- log.Printf("cannot close setup pipe: %v", err)
+ k.printf("cannot close setup pipe: %v", err)
// not fatal
}
@@ -351,11 +353,11 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
err = EINTR
for errors.Is(err, EINTR) {
- wpid, err = Wait4(-1, &wstatus, 0, nil)
+ wpid, err = k.wait4(-1, &wstatus, 0, nil)
}
}
if !errors.Is(err, ECHILD) {
- log.Printf("unexpected wait4 response: %v", err)
+ k.printf("unexpected wait4 response: %v", err)
}
close(done)
@@ -363,7 +365,7 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
// handle signals to dump withheld messages
sig := make(chan os.Signal, 2)
- signal.Notify(sig, os.Interrupt, CancelSignal)
+ k.notify(sig, os.Interrupt, CancelSignal)
// closed after residualProcessTimeout has elapsed after initial process death
timeout := make(chan struct{})
@@ -372,45 +374,48 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
for {
select {
case s := <-sig:
- if msg.Resume() {
- msg.Verbosef("%s after process start", s.String())
+ if k.resume() {
+ k.verbosef("%s after process start", s.String())
} else {
- msg.Verbosef("got %s", s.String())
+ k.verbosef("got %s", s.String())
}
if s == CancelSignal && params.ForwardCancel && cmd.Process != nil {
- msg.Verbose("forwarding context cancellation")
- if err := cmd.Process.Signal(os.Interrupt); err != nil {
- log.Printf("cannot forward cancellation: %v", err)
+ k.verbose("forwarding context cancellation")
+ if err := k.signal(cmd, os.Interrupt); err != nil {
+ k.printf("cannot forward cancellation: %v", err)
}
continue
}
- os.Exit(0)
+ k.exit(0)
+
case w := <-info:
if w.wpid == cmd.Process.Pid {
// initial process exited, output is most likely available again
- msg.Resume()
+ k.resume()
switch {
case w.wstatus.Exited():
r = w.wstatus.ExitStatus()
- msg.Verbosef("initial process exited with code %d", w.wstatus.ExitStatus())
+ k.verbosef("initial process exited with code %d", w.wstatus.ExitStatus())
case w.wstatus.Signaled():
r = 128 + int(w.wstatus.Signal())
- msg.Verbosef("initial process exited with signal %s", w.wstatus.Signal())
+ k.verbosef("initial process exited with signal %s", w.wstatus.Signal())
default:
r = 255
- msg.Verbosef("initial process exited with status %#x", w.wstatus)
+ k.verbosef("initial process exited with status %#x", w.wstatus)
}
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
}
+
case <-done:
- msg.BeforeExit()
- os.Exit(r)
+ k.beforeExit()
+ k.exit(r)
+
case <-timeout:
- log.Println("timeout exceeded waiting for lingering processes")
- msg.BeforeExit()
- os.Exit(r)
+ k.printf("timeout exceeded waiting for lingering processes")
+ k.beforeExit()
+ k.exit(r)
}
}
}
diff --git a/container/initbind.go b/container/initbind.go
index e19cb5de..18f44936 100644
--- a/container/initbind.go
+++ b/container/initbind.go
@@ -4,8 +4,7 @@ import (
"encoding/gob"
"fmt"
"os"
- "path/filepath"
- . "syscall"
+ "syscall"
)
func init() { gob.Register(new(BindMountOp)) }
@@ -35,8 +34,8 @@ const (
BindDevice
)
-func (b *BindMountOp) early(*setupState) error {
- if pathname, err := filepath.EvalSymlinks(b.Source.String()); err != nil {
+func (b *BindMountOp) early(_ *setupState, k syscallDispatcher) error {
+ if pathname, err := k.evalSymlinks(b.Source.String()); err != nil {
if os.IsNotExist(err) && b.Flags&BindOptional != 0 {
// leave sourceFinal as nil
return nil
@@ -48,11 +47,11 @@ func (b *BindMountOp) early(*setupState) error {
}
}
-func (b *BindMountOp) apply(*setupState) error {
+func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error {
if b.sourceFinal == nil {
if b.Flags&BindOptional == 0 {
// unreachable
- return EBADE
+ return msg.WrapErr(os.ErrClosed, "impossible bind state reached")
}
return nil
}
@@ -62,25 +61,25 @@ func (b *BindMountOp) apply(*setupState) error {
// this perm value emulates bwrap behaviour as it clears bits from 0755 based on
// op->perms which is never set for any bind setup op so always results in 0700
- if fi, err := os.Stat(source); err != nil {
+ if fi, err := k.stat(source); err != nil {
return wrapErrSelf(err)
} else if fi.IsDir() {
- if err = os.MkdirAll(target, 0700); err != nil {
+ if err = k.mkdirAll(target, 0700); err != nil {
return wrapErrSelf(err)
}
- } else if err = ensureFile(target, 0444, 0700); err != nil {
+ } else if err = k.ensureFile(target, 0444, 0700); err != nil {
return err
}
- var flags uintptr = MS_REC
+ var flags uintptr = syscall.MS_REC
if b.Flags&BindWritable == 0 {
- flags |= MS_RDONLY
+ flags |= syscall.MS_RDONLY
}
if b.Flags&BindDevice == 0 {
- flags |= MS_NODEV
+ flags |= syscall.MS_NODEV
}
- return hostProc.bindMount(source, target, flags, b.sourceFinal == b.Target)
+ return k.bindMount(source, target, flags, b.sourceFinal == b.Target)
}
func (b *BindMountOp) Is(op Op) bool {
diff --git a/container/initbind_test.go b/container/initbind_test.go
index bae33f8a..1de47dec 100644
--- a/container/initbind_test.go
+++ b/container/initbind_test.go
@@ -1,8 +1,134 @@
package container
-import "testing"
+import (
+ "errors"
+ "os"
+ "syscall"
+ "testing"
+)
func TestBindMountOp(t *testing.T) {
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"ENOENT not optional", new(Params), &BindMountOp{
+ Source: MustAbs("/bin/"),
+ Target: MustAbs("/bin/"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/bin/"}, "", syscall.ENOENT},
+ }, wrapErrSelf(syscall.ENOENT), nil, nil},
+
+ {"skip optional", new(Params), &BindMountOp{
+ Source: MustAbs("/bin/"),
+ Target: MustAbs("/bin/"),
+ Flags: BindOptional,
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/bin/"}, "", syscall.ENOENT},
+ }, nil, nil, nil},
+
+ {"success optional", new(Params), &BindMountOp{
+ Source: MustAbs("/bin/"),
+ Target: MustAbs("/bin/"),
+ Flags: BindOptional,
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
+ }, nil, []kexpect{
+ {"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil},
+ {"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil},
+ {"bindMount", expectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil},
+ }, nil},
+
+ {"ensureFile device", new(Params), &BindMountOp{
+ Source: MustAbs("/dev/null"),
+ Target: MustAbs("/dev/null"),
+ Flags: BindWritable | BindDevice,
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/dev/null"}, "/dev/null", nil},
+ }, nil, []kexpect{
+ {"stat", expectArgs{"/host/dev/null"}, isDirFi(false), nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, errUnique},
+ }, errUnique},
+
+ {"success device ro", new(Params), &BindMountOp{
+ Source: MustAbs("/dev/null"),
+ Target: MustAbs("/dev/null"),
+ Flags: BindDevice,
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/dev/null"}, "/dev/null", nil},
+ }, nil, []kexpect{
+ {"stat", expectArgs{"/host/dev/null"}, isDirFi(false), nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4001), false}, nil, nil},
+ }, nil},
+
+ {"success device", new(Params), &BindMountOp{
+ Source: MustAbs("/dev/null"),
+ Target: MustAbs("/dev/null"),
+ Flags: BindWritable | BindDevice,
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/dev/null"}, "/dev/null", nil},
+ }, nil, []kexpect{
+ {"stat", expectArgs{"/host/dev/null"}, isDirFi(false), nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4000), false}, nil, nil},
+ }, nil},
+
+ {"evalSymlinks", new(Params), &BindMountOp{
+ Source: MustAbs("/bin/"),
+ Target: MustAbs("/bin/"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", errUnique},
+ }, wrapErrSelf(errUnique), nil, nil},
+
+ {"stat", new(Params), &BindMountOp{
+ Source: MustAbs("/bin/"),
+ Target: MustAbs("/bin/"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
+ }, nil, []kexpect{
+ {"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"mkdirAll", new(Params), &BindMountOp{
+ Source: MustAbs("/bin/"),
+ Target: MustAbs("/bin/"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
+ }, nil, []kexpect{
+ {"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil},
+ {"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"bindMount", new(Params), &BindMountOp{
+ Source: MustAbs("/bin/"),
+ Target: MustAbs("/bin/"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
+ }, nil, []kexpect{
+ {"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil},
+ {"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil},
+ {"bindMount", expectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, errUnique},
+ }, errUnique},
+
+ {"success", new(Params), &BindMountOp{
+ Source: MustAbs("/bin/"),
+ Target: MustAbs("/bin/"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/bin/"}, "/usr/bin", nil},
+ }, nil, []kexpect{
+ {"stat", expectArgs{"/host/usr/bin"}, isDirFi(true), nil},
+ {"mkdirAll", expectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil},
+ {"bindMount", expectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil},
+ }, nil},
+ })
+
+ t.Run("unreachable", func(t *testing.T) {
+ t.Run("nil sourceFinal not optional", func(t *testing.T) {
+ wantErr := msg.WrapErr(os.ErrClosed, "impossible bind state reached")
+ if err := new(BindMountOp).apply(nil, nil); !errors.Is(err, wantErr) {
+ t.Errorf("apply: error = %v, want %v", err, wantErr)
+ }
+ })
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*BindMountOp)(nil), false},
{"zero", new(BindMountOp), false},
diff --git a/container/initdev.go b/container/initdev.go
index 48d70f37..b3192912 100644
--- a/container/initdev.go
+++ b/container/initdev.go
@@ -3,10 +3,8 @@ package container
import (
"encoding/gob"
"fmt"
- "os"
"path"
. "syscall"
- "unsafe"
)
func init() { gob.Register(new(MountDevOp)) }
@@ -33,21 +31,21 @@ type MountDevOp struct {
Write bool
}
-func (d *MountDevOp) Valid() bool { return d != nil && d.Target != nil }
-func (d *MountDevOp) early(*setupState) error { return nil }
-func (d *MountDevOp) apply(state *setupState) error {
+func (d *MountDevOp) Valid() bool { return d != nil && d.Target != nil }
+func (d *MountDevOp) early(*setupState, syscallDispatcher) error { return nil }
+func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
target := toSysroot(d.Target.String())
- if err := mountTmpfs(SourceTmpfsDevtmpfs, target, MS_NOSUID|MS_NODEV, 0, state.ParentPerm); err != nil {
+ if err := k.mountTmpfs(SourceTmpfsDevtmpfs, target, MS_NOSUID|MS_NODEV, 0, state.ParentPerm); err != nil {
return err
}
for _, name := range []string{"null", "zero", "full", "random", "urandom", "tty"} {
targetPath := path.Join(target, name)
- if err := ensureFile(targetPath, 0444, state.ParentPerm); err != nil {
+ if err := k.ensureFile(targetPath, 0444, state.ParentPerm); err != nil {
return err
}
- if err := hostProc.bindMount(
+ if err := k.bindMount(
toHost(FHSDev+name),
targetPath,
0,
@@ -57,7 +55,7 @@ func (d *MountDevOp) apply(state *setupState) error {
}
}
for i, name := range []string{"stdin", "stdout", "stderr"} {
- if err := os.Symlink(
+ if err := k.symlink(
FHSProc+"self/fd/"+string(rune(i+'0')),
path.Join(target, name),
); err != nil {
@@ -69,34 +67,33 @@ func (d *MountDevOp) apply(state *setupState) error {
{FHSProc + "kcore", "core"},
{"pts/ptmx", "ptmx"},
} {
- if err := os.Symlink(pair[0], path.Join(target, pair[1])); err != nil {
+ if err := k.symlink(pair[0], path.Join(target, pair[1])); err != nil {
return wrapErrSelf(err)
}
}
devPtsPath := path.Join(target, "pts")
for _, name := range []string{path.Join(target, "shm"), devPtsPath} {
- if err := os.Mkdir(name, state.ParentPerm); err != nil {
+ if err := k.mkdir(name, state.ParentPerm); err != nil {
return wrapErrSelf(err)
}
}
- if err := Mount(SourceDevpts, devPtsPath, FstypeDevpts, MS_NOSUID|MS_NOEXEC,
+ if err := k.mount(SourceDevpts, devPtsPath, FstypeDevpts, MS_NOSUID|MS_NOEXEC,
"newinstance,ptmxmode=0666,mode=620"); err != nil {
return wrapErrSuffix(err,
fmt.Sprintf("cannot mount devpts on %q:", devPtsPath))
}
if state.RetainSession {
- var buf [8]byte
- if _, _, errno := Syscall(SYS_IOCTL, 1, TIOCGWINSZ, uintptr(unsafe.Pointer(&buf[0]))); errno == 0 {
+ if k.isatty(Stdout) {
consolePath := path.Join(target, "console")
- if err := ensureFile(consolePath, 0444, state.ParentPerm); err != nil {
+ if err := k.ensureFile(consolePath, 0444, state.ParentPerm); err != nil {
return err
}
- if name, err := os.Readlink(hostProc.stdout()); err != nil {
+ if name, err := k.readlink(hostProc.stdout()); err != nil {
return wrapErrSelf(err)
- } else if err = hostProc.bindMount(
+ } else if err = k.bindMount(
toHost(name),
consolePath,
0,
@@ -109,10 +106,10 @@ func (d *MountDevOp) apply(state *setupState) error {
if d.Mqueue {
mqueueTarget := path.Join(target, "mqueue")
- if err := os.Mkdir(mqueueTarget, state.ParentPerm); err != nil {
+ if err := k.mkdir(mqueueTarget, state.ParentPerm); err != nil {
return wrapErrSelf(err)
}
- if err := Mount(SourceMqueue, mqueueTarget, FstypeMqueue, MS_NOSUID|MS_NOEXEC|MS_NODEV, zeroString); err != nil {
+ if err := k.mount(SourceMqueue, mqueueTarget, FstypeMqueue, MS_NOSUID|MS_NOEXEC|MS_NODEV, zeroString); err != nil {
return wrapErrSuffix(err, "cannot mount mqueue:")
}
}
@@ -120,7 +117,7 @@ func (d *MountDevOp) apply(state *setupState) error {
if d.Write {
return nil
}
- return wrapErrSuffix(hostProc.remount(target, MS_RDONLY),
+ return wrapErrSuffix(k.remount(target, MS_RDONLY),
fmt.Sprintf("cannot remount %q:", target))
}
diff --git a/container/initdev_test.go b/container/initdev_test.go
index 4e23e6ba..9d2529c9 100644
--- a/container/initdev_test.go
+++ b/container/initdev_test.go
@@ -1,8 +1,723 @@
package container
-import "testing"
+import (
+ "os"
+ "testing"
+)
func TestMountDevOp(t *testing.T) {
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"mountTmpfs", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, errUnique},
+ }, errUnique},
+
+ {"ensureFile null", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, errUnique},
+ }, errUnique},
+
+ {"bindMount null", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, errUnique},
+ }, errUnique},
+
+ {"ensureFile zero", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, errUnique},
+ }, errUnique},
+
+ {"bindMount zero", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, errUnique},
+ }, errUnique},
+
+ {"ensureFile full", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, errUnique},
+ }, errUnique},
+
+ {"bindMount full", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, errUnique},
+ }, errUnique},
+
+ {"ensureFile random", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, errUnique},
+ }, errUnique},
+
+ {"bindMount random", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, errUnique},
+ }, errUnique},
+
+ {"ensureFile urandom", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, errUnique},
+ }, errUnique},
+
+ {"bindMount urandom", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, errUnique},
+ }, errUnique},
+
+ {"ensureFile tty", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, errUnique},
+ }, errUnique},
+
+ {"bindMount tty", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, errUnique},
+ }, errUnique},
+
+ {"symlink stdin", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"symlink stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"symlink stderr", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"symlink fd", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"symlink kcore", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"symlink ptmx", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"mkdir shm", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"mkdir devpts", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"mount devpts", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, errUnique},
+ }, wrapErrSuffix(errUnique, `cannot mount devpts on "/sysroot/dev/pts":`)},
+
+ {"ensureFile stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, nil},
+ {"isatty", expectArgs{1}, true, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/console", os.FileMode(0444), os.FileMode(0750)}, nil, errUnique},
+ }, errUnique},
+
+ {"readlink stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, nil},
+ {"isatty", expectArgs{1}, true, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/console", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"readlink", expectArgs{"/host/proc/self/fd/1"}, "", errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"bindMount stdout", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, nil},
+ {"isatty", expectArgs{1}, true, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/console", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"readlink", expectArgs{"/host/proc/self/fd/1"}, "/dev/pts/2", nil},
+ {"bindMount", expectArgs{"/host/dev/pts/2", "/sysroot/dev/console", uintptr(0), false}, nil, errUnique},
+ }, errUnique},
+
+ {"mkdir mqueue", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, nil},
+ {"isatty", expectArgs{1}, true, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/console", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"readlink", expectArgs{"/host/proc/self/fd/1"}, "/dev/pts/2", nil},
+ {"bindMount", expectArgs{"/host/dev/pts/2", "/sysroot/dev/console", uintptr(0), false}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/mqueue", os.FileMode(0750)}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"mount mqueue", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, nil},
+ {"isatty", expectArgs{1}, true, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/console", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"readlink", expectArgs{"/host/proc/self/fd/1"}, "/dev/pts/2", nil},
+ {"bindMount", expectArgs{"/host/dev/pts/2", "/sysroot/dev/console", uintptr(0), false}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/mqueue", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"mqueue", "/sysroot/dev/mqueue", "mqueue", uintptr(0xe), ""}, nil, errUnique},
+ }, wrapErrSuffix(errUnique, "cannot mount mqueue:")},
+
+ {"success no session", &Params{ParentPerm: 0755}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ Write: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0755)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0755)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0755)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0755)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0755)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0755)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0755)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0755)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0755)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/mqueue", os.FileMode(0755)}, nil, nil},
+ {"mount", expectArgs{"mqueue", "/sysroot/dev/mqueue", "mqueue", uintptr(0xe), ""}, nil, nil},
+ }, nil},
+
+ {"success no tty", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ Write: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, nil},
+ {"isatty", expectArgs{1}, false, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/mqueue", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"mqueue", "/sysroot/dev/mqueue", "mqueue", uintptr(0xe), ""}, nil, nil},
+ }, nil},
+
+ {"success no mqueue", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, nil},
+ {"isatty", expectArgs{1}, true, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/console", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"readlink", expectArgs{"/host/proc/self/fd/1"}, "/dev/pts/2", nil},
+ {"bindMount", expectArgs{"/host/dev/pts/2", "/sysroot/dev/console", uintptr(0), false}, nil, nil},
+ {"remount", expectArgs{"/sysroot/dev", uintptr(1)}, nil, nil},
+ }, nil},
+
+ {"success rw", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ Write: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, nil},
+ {"isatty", expectArgs{1}, true, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/console", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"readlink", expectArgs{"/host/proc/self/fd/1"}, "/dev/pts/2", nil},
+ {"bindMount", expectArgs{"/host/dev/pts/2", "/sysroot/dev/console", uintptr(0), false}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/mqueue", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"mqueue", "/sysroot/dev/mqueue", "mqueue", uintptr(0xe), ""}, nil, nil},
+ }, nil},
+
+ {"success", &Params{ParentPerm: 0750, RetainSession: true}, &MountDevOp{
+ Target: MustAbs("/dev/"),
+ Mqueue: true,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{"devtmpfs", "/sysroot/dev", uintptr(0x6), 0, os.FileMode(0750)}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/zero", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/zero", "/sysroot/dev/zero", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/full", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/full", "/sysroot/dev/full", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/random", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/random", "/sysroot/dev/random", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/urandom", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/urandom", "/sysroot/dev/urandom", uintptr(0), true}, nil, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/tty", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"bindMount", expectArgs{"/host/dev/tty", "/sysroot/dev/tty", uintptr(0), true}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/0", "/sysroot/dev/stdin"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/1", "/sysroot/dev/stdout"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd/2", "/sysroot/dev/stderr"}, nil, nil},
+ {"symlink", expectArgs{"/proc/self/fd", "/sysroot/dev/fd"}, nil, nil},
+ {"symlink", expectArgs{"/proc/kcore", "/sysroot/dev/core"}, nil, nil},
+ {"symlink", expectArgs{"pts/ptmx", "/sysroot/dev/ptmx"}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/shm", os.FileMode(0750)}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/pts", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"devpts", "/sysroot/dev/pts", "devpts", uintptr(0xa), "newinstance,ptmxmode=0666,mode=620"}, nil, nil},
+ {"isatty", expectArgs{1}, true, nil},
+ {"ensureFile", expectArgs{"/sysroot/dev/console", os.FileMode(0444), os.FileMode(0750)}, nil, nil},
+ {"readlink", expectArgs{"/host/proc/self/fd/1"}, "/dev/pts/2", nil},
+ {"bindMount", expectArgs{"/host/dev/pts/2", "/sysroot/dev/console", uintptr(0), false}, nil, nil},
+ {"mkdir", expectArgs{"/sysroot/dev/mqueue", os.FileMode(0750)}, nil, nil},
+ {"mount", expectArgs{"mqueue", "/sysroot/dev/mqueue", "mqueue", uintptr(0xe), ""}, nil, nil},
+ {"remount", expectArgs{"/sysroot/dev", uintptr(1)}, nil, nil},
+ }, nil},
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*MountDevOp)(nil), false},
{"zero", new(MountDevOp), false},
diff --git a/container/initmkdir.go b/container/initmkdir.go
index 54aa4f7b..2f77a526 100644
--- a/container/initmkdir.go
+++ b/container/initmkdir.go
@@ -20,10 +20,10 @@ type MkdirOp struct {
Perm os.FileMode
}
-func (m *MkdirOp) Valid() bool { return m != nil && m.Path != nil }
-func (m *MkdirOp) early(*setupState) error { return nil }
-func (m *MkdirOp) apply(*setupState) error {
- return wrapErrSelf(os.MkdirAll(toSysroot(m.Path.String()), m.Perm))
+func (m *MkdirOp) Valid() bool { return m != nil && m.Path != nil }
+func (m *MkdirOp) early(*setupState, syscallDispatcher) error { return nil }
+func (m *MkdirOp) apply(_ *setupState, k syscallDispatcher) error {
+ return wrapErrSelf(k.mkdirAll(toSysroot(m.Path.String()), m.Perm))
}
func (m *MkdirOp) Is(op Op) bool {
diff --git a/container/initmkdir_test.go b/container/initmkdir_test.go
index ad326240..1f54e748 100644
--- a/container/initmkdir_test.go
+++ b/container/initmkdir_test.go
@@ -1,8 +1,20 @@
package container
-import "testing"
+import (
+ "os"
+ "testing"
+)
func TestMkdirOp(t *testing.T) {
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"success", new(Params), &MkdirOp{
+ Path: MustAbs("/.hakurei"),
+ Perm: 0500,
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/.hakurei", os.FileMode(0500)}, nil, nil},
+ }, nil},
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*MkdirOp)(nil), false},
{"zero", new(MkdirOp), false},
diff --git a/container/initoverlay.go b/container/initoverlay.go
index 6822274d..2c6f43c6 100644
--- a/container/initoverlay.go
+++ b/container/initoverlay.go
@@ -3,11 +3,9 @@ package container
import (
"encoding/gob"
"fmt"
- "os"
- "path/filepath"
+ "io/fs"
"slices"
"strings"
- . "syscall"
)
const (
@@ -81,14 +79,14 @@ func (o *MountOverlayOp) Valid() bool {
return o.Target != nil
}
-func (o *MountOverlayOp) early(*setupState) error {
+func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
if o.Work == nil && o.Upper != nil {
switch o.Upper.String() {
case FHSRoot: // ephemeral
o.ephemeral = true // intermediate root not yet available
default:
- return msg.WrapErr(EINVAL, fmt.Sprintf("upperdir has unexpected value %q", o.Upper))
+ return msg.WrapErr(fs.ErrInvalid, fmt.Sprintf("upperdir has unexpected value %q", o.Upper))
}
}
// readonly handled in apply
@@ -96,11 +94,11 @@ func (o *MountOverlayOp) early(*setupState) error {
if !o.ephemeral {
if o.Upper != o.Work && (o.Upper == nil || o.Work == nil) {
// unreachable
- return msg.WrapErr(ENOTRECOVERABLE, "impossible overlay state reached")
+ return msg.WrapErr(fs.ErrClosed, "impossible overlay state reached")
}
if o.Upper != nil {
- if v, err := filepath.EvalSymlinks(o.Upper.String()); err != nil {
+ if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
return wrapErrSelf(err)
} else {
o.upper = EscapeOverlayDataSegment(toHost(v))
@@ -108,7 +106,7 @@ func (o *MountOverlayOp) early(*setupState) error {
}
if o.Work != nil {
- if v, err := filepath.EvalSymlinks(o.Work.String()); err != nil {
+ if v, err := k.evalSymlinks(o.Work.String()); err != nil {
return wrapErrSelf(err)
} else {
o.work = EscapeOverlayDataSegment(toHost(v))
@@ -118,7 +116,7 @@ func (o *MountOverlayOp) early(*setupState) error {
o.lower = make([]string, len(o.Lower))
for i, a := range o.Lower { // nil checked in Valid
- if v, err := filepath.EvalSymlinks(a.String()); err != nil {
+ if v, err := k.evalSymlinks(a.String()); err != nil {
return wrapErrSelf(err)
} else {
o.lower[i] = EscapeOverlayDataSegment(toHost(v))
@@ -127,19 +125,19 @@ func (o *MountOverlayOp) early(*setupState) error {
return nil
}
-func (o *MountOverlayOp) apply(state *setupState) error {
+func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
target := toSysroot(o.Target.String())
- if err := os.MkdirAll(target, state.ParentPerm); err != nil {
+ if err := k.mkdirAll(target, state.ParentPerm); err != nil {
return wrapErrSelf(err)
}
if o.ephemeral {
var err error
// these directories are created internally, therefore early (absolute, symlink, prefix, escape) is bypassed
- if o.upper, err = os.MkdirTemp(FHSRoot, intermediatePatternOverlayUpper); err != nil {
+ if o.upper, err = k.mkdirTemp(FHSRoot, intermediatePatternOverlayUpper); err != nil {
return wrapErrSelf(err)
}
- if o.work, err = os.MkdirTemp(FHSRoot, intermediatePatternOverlayWork); err != nil {
+ if o.work, err = k.mkdirTemp(FHSRoot, intermediatePatternOverlayWork); err != nil {
return wrapErrSelf(err)
}
}
@@ -148,12 +146,12 @@ func (o *MountOverlayOp) apply(state *setupState) error {
if o.upper == zeroString && o.work == zeroString { // readonly
if len(o.Lower) < 2 {
- return msg.WrapErr(EINVAL, "readonly overlay requires at least two lowerdir")
+ return msg.WrapErr(fs.ErrInvalid, "readonly overlay requires at least two lowerdir")
}
// "upperdir=" and "workdir=" may be omitted. In that case the overlay will be read-only
} else {
if len(o.Lower) == 0 {
- return msg.WrapErr(EINVAL, "overlay requires at least one lowerdir")
+ return msg.WrapErr(fs.ErrInvalid, "overlay requires at least one lowerdir")
}
options = append(options,
OptionOverlayUpperdir+"="+o.upper,
@@ -163,7 +161,7 @@ func (o *MountOverlayOp) apply(state *setupState) error {
OptionOverlayLowerdir+"="+strings.Join(o.lower, SpecialOverlayPath),
OptionOverlayUserxattr)
- return wrapErrSuffix(Mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, SpecialOverlayOption)),
+ return wrapErrSuffix(k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, SpecialOverlayOption)),
fmt.Sprintf("cannot mount overlay on %q:", o.Target))
}
diff --git a/container/initoverlay_test.go b/container/initoverlay_test.go
index 68377dd4..f5f0aec5 100644
--- a/container/initoverlay_test.go
+++ b/container/initoverlay_test.go
@@ -1,8 +1,237 @@
package container
-import "testing"
+import (
+ "errors"
+ "io/fs"
+ "os"
+ "testing"
+)
func TestMountOverlayOp(t *testing.T) {
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"mkdirTemp invalid ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
+ Target: MustAbs("/"),
+ Lower: []*Absolute{
+ MustAbs("/var/lib/planterette/base/debian:f92c9052"),
+ MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
+ },
+ Upper: MustAbs("/proc/"),
+ }, nil, msg.WrapErr(fs.ErrInvalid, `upperdir has unexpected value "/proc/"`), nil, nil},
+
+ {"mkdirTemp upper ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
+ Target: MustAbs("/"),
+ Lower: []*Absolute{
+ MustAbs("/var/lib/planterette/base/debian:f92c9052"),
+ MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
+ },
+ Upper: MustAbs("/"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot", os.FileMode(0705)}, nil, nil},
+ {"mkdirTemp", expectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"mkdirTemp work ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
+ Target: MustAbs("/"),
+ Lower: []*Absolute{
+ MustAbs("/var/lib/planterette/base/debian:f92c9052"),
+ MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
+ },
+ Upper: MustAbs("/"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot", os.FileMode(0705)}, nil, nil},
+ {"mkdirTemp", expectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil},
+ {"mkdirTemp", expectArgs{"/", "overlay.work.*"}, "overlay.work.32768", errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"success ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{
+ Target: MustAbs("/"),
+ Lower: []*Absolute{
+ MustAbs("/var/lib/planterette/base/debian:f92c9052"),
+ MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"),
+ },
+ Upper: MustAbs("/"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil},
+ {"evalSymlinks", expectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot", os.FileMode(0705)}, nil, nil},
+ {"mkdirTemp", expectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil},
+ {"mkdirTemp", expectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil},
+ {"mount", expectArgs{"overlay", "/sysroot", "overlay", uintptr(0), "" +
+ "upperdir=overlay.upper.32768," +
+ "workdir=overlay.work.32768," +
+ "lowerdir=" +
+ `/host/var/lib/planterette/base/debian\:f92c9052:` +
+ `/host/var/lib/planterette/app/org.chromium.Chromium@debian\:f92c9052,` +
+ "userxattr"}, nil, nil},
+ }, nil},
+
+ {"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
+ Target: MustAbs("/nix/store"),
+ Lower: []*Absolute{
+ MustAbs("/mnt-root/nix/.ro-store"),
+ },
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/.ro-store", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil},
+ }, msg.WrapErr(fs.ErrInvalid, "readonly overlay requires at least two lowerdir")},
+
+ {"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
+ Target: MustAbs("/nix/store"),
+ Lower: []*Absolute{
+ MustAbs("/mnt-root/nix/.ro-store"),
+ MustAbs("/mnt-root/nix/.ro-store0"),
+ },
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/.ro-store", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil},
+ {"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
+ "lowerdir=" +
+ "/host/mnt-root/nix/.ro-store:" +
+ "/host/mnt-root/nix/.ro-store0," +
+ "userxattr"}, nil, nil},
+ }, nil},
+
+ {"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
+ Target: MustAbs("/nix/store"),
+ Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
+ Work: MustAbs("/mnt-root/nix/.rw-store/work"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil},
+ }, msg.WrapErr(fs.ErrInvalid, "overlay requires at least one lowerdir")},
+
+ {"evalSymlinks upper", &Params{ParentPerm: 0700}, &MountOverlayOp{
+ Target: MustAbs("/nix/store"),
+ Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
+ Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
+ Work: MustAbs("/mnt-root/nix/.rw-store/work"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", errUnique},
+ }, wrapErrSelf(errUnique), nil, nil},
+
+ {"evalSymlinks work", &Params{ParentPerm: 0700}, &MountOverlayOp{
+ Target: MustAbs("/nix/store"),
+ Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
+ Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
+ Work: MustAbs("/mnt-root/nix/.rw-store/work"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", errUnique},
+ }, wrapErrSelf(errUnique), nil, nil},
+
+ {"evalSymlinks lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
+ Target: MustAbs("/nix/store"),
+ Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
+ Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
+ Work: MustAbs("/mnt-root/nix/.rw-store/work"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", errUnique},
+ }, wrapErrSelf(errUnique), nil, nil},
+
+ {"mkdirAll", &Params{ParentPerm: 0700}, &MountOverlayOp{
+ Target: MustAbs("/nix/store"),
+ Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
+ Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
+ Work: MustAbs("/mnt-root/nix/.rw-store/work"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"mount", &Params{ParentPerm: 0700}, &MountOverlayOp{
+ Target: MustAbs("/nix/store"),
+ Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
+ Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
+ Work: MustAbs("/mnt-root/nix/.rw-store/work"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil},
+ {"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "upperdir=/host/mnt-root/nix/.rw-store/.upper,workdir=/host/mnt-root/nix/.rw-store/.work,lowerdir=/host/mnt-root/nix/ro-store,userxattr"}, nil, errUnique},
+ }, wrapErrSuffix(errUnique, `cannot mount overlay on "/nix/store":`)},
+
+ {"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
+ Target: MustAbs("/nix/store"),
+ Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")},
+ Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
+ Work: MustAbs("/mnt-root/nix/.rw-store/work"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil},
+ {"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
+ "upperdir=/host/mnt-root/nix/.rw-store/.upper," +
+ "workdir=/host/mnt-root/nix/.rw-store/.work," +
+ "lowerdir=/host/mnt-root/nix/ro-store," +
+ "userxattr"}, nil, nil},
+ }, nil},
+
+ {"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
+ Target: MustAbs("/nix/store"),
+ Lower: []*Absolute{
+ MustAbs("/mnt-root/nix/.ro-store"),
+ MustAbs("/mnt-root/nix/.ro-store0"),
+ MustAbs("/mnt-root/nix/.ro-store1"),
+ MustAbs("/mnt-root/nix/.ro-store2"),
+ MustAbs("/mnt-root/nix/.ro-store3"),
+ },
+ Upper: MustAbs("/mnt-root/nix/.rw-store/upper"),
+ Work: MustAbs("/mnt-root/nix/.rw-store/work"),
+ }, []kexpect{
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/ro-store0", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store1"}, "/mnt-root/nix/ro-store1", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store2"}, "/mnt-root/nix/ro-store2", nil},
+ {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil},
+ {"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
+ "upperdir=/host/mnt-root/nix/.rw-store/.upper," +
+ "workdir=/host/mnt-root/nix/.rw-store/.work," +
+ "lowerdir=" +
+ "/host/mnt-root/nix/ro-store:" +
+ "/host/mnt-root/nix/ro-store0:" +
+ "/host/mnt-root/nix/ro-store1:" +
+ "/host/mnt-root/nix/ro-store2:" +
+ "/host/mnt-root/nix/ro-store3," +
+ "userxattr"}, nil, nil},
+ }, nil},
+ })
+
+ t.Run("unreachable", func(t *testing.T) {
+ t.Run("nil Upper non-nil Work not ephemeral", func(t *testing.T) {
+ wantErr := msg.WrapErr(fs.ErrClosed, "impossible overlay state reached")
+ if err := (&MountOverlayOp{
+ Work: MustAbs("/"),
+ }).early(nil, nil); !errors.Is(err, wantErr) {
+ t.Errorf("apply: error = %v, want %v", err, wantErr)
+ }
+ })
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*MountOverlayOp)(nil), false},
{"zero", new(MountOverlayOp), false},
diff --git a/container/initplace.go b/container/initplace.go
index 2d98d94f..8b883458 100644
--- a/container/initplace.go
+++ b/container/initplace.go
@@ -3,8 +3,7 @@ package container
import (
"encoding/gob"
"fmt"
- "os"
- . "syscall"
+ "syscall"
)
const (
@@ -35,11 +34,11 @@ type TmpfileOp struct {
Data []byte
}
-func (t *TmpfileOp) Valid() bool { return t != nil && t.Path != nil }
-func (t *TmpfileOp) early(*setupState) error { return nil }
-func (t *TmpfileOp) apply(state *setupState) error {
+func (t *TmpfileOp) Valid() bool { return t != nil && t.Path != nil }
+func (t *TmpfileOp) early(*setupState, syscallDispatcher) error { return nil }
+func (t *TmpfileOp) apply(state *setupState, k syscallDispatcher) error {
var tmpPath string
- if f, err := os.CreateTemp(FHSRoot, intermediatePatternTmpfile); err != nil {
+ if f, err := k.createTemp(FHSRoot, intermediatePatternTmpfile); err != nil {
return wrapErrSelf(err)
} else if _, err = f.Write(t.Data); err != nil {
return wrapErrSuffix(err,
@@ -52,16 +51,16 @@ func (t *TmpfileOp) apply(state *setupState) error {
}
target := toSysroot(t.Path.String())
- if err := ensureFile(target, 0444, state.ParentPerm); err != nil {
+ if err := k.ensureFile(target, 0444, state.ParentPerm); err != nil {
return err
- } else if err = hostProc.bindMount(
+ } else if err = k.bindMount(
tmpPath,
target,
- MS_RDONLY|MS_NODEV,
+ syscall.MS_RDONLY|syscall.MS_NODEV,
false,
); err != nil {
return err
- } else if err = os.Remove(tmpPath); err != nil {
+ } else if err = k.remove(tmpPath); err != nil {
return wrapErrSelf(err)
}
return nil
diff --git a/container/initplace_test.go b/container/initplace_test.go
index db3d955c..02c37336 100644
--- a/container/initplace_test.go
+++ b/container/initplace_test.go
@@ -1,25 +1,94 @@
package container
-import "testing"
+import (
+ "os"
+ "testing"
+)
func TestTmpfileOp(t *testing.T) {
+ const sampleDataString = `chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`
+ var (
+ samplePath = MustAbs("/etc/passwd")
+ sampleData = []byte(sampleDataString)
+ )
+
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"createTemp", &Params{ParentPerm: 0700}, &TmpfileOp{
+ Path: samplePath,
+ Data: sampleData,
+ }, nil, nil, []kexpect{
+ {"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"Write", &Params{ParentPerm: 0700}, &TmpfileOp{
+ Path: samplePath,
+ Data: sampleData,
+ }, nil, nil, []kexpect{
+ {"createTemp", expectArgs{"/", "tmp.*"}, writeErrOsFile{errUnique}, nil},
+ }, wrapErrSuffix(errUnique, "cannot write to intermediate file:")},
+
+ {"Close", &Params{ParentPerm: 0700}, &TmpfileOp{
+ Path: samplePath,
+ Data: sampleData,
+ }, nil, nil, []kexpect{
+ {"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, errUnique), nil},
+ }, wrapErrSuffix(errUnique, "cannot close intermediate file:")},
+
+ {"ensureFile", &Params{ParentPerm: 0700}, &TmpfileOp{
+ Path: samplePath,
+ Data: sampleData,
+ }, nil, nil, []kexpect{
+ {"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil},
+ {"ensureFile", expectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, errUnique},
+ }, errUnique},
+
+ {"bindMount", &Params{ParentPerm: 0700}, &TmpfileOp{
+ Path: samplePath,
+ Data: sampleData,
+ }, nil, nil, []kexpect{
+ {"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil},
+ {"ensureFile", expectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil},
+ {"bindMount", expectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, errUnique},
+ }, errUnique},
+
+ {"remove", &Params{ParentPerm: 0700}, &TmpfileOp{
+ Path: samplePath,
+ Data: sampleData,
+ }, nil, nil, []kexpect{
+ {"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil},
+ {"ensureFile", expectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil},
+ {"bindMount", expectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil},
+ {"remove", expectArgs{"tmp.32768"}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"success", &Params{ParentPerm: 0700}, &TmpfileOp{
+ Path: samplePath,
+ Data: sampleData,
+ }, nil, nil, []kexpect{
+ {"createTemp", expectArgs{"/", "tmp.*"}, newCheckedFile(t, "tmp.32768", sampleDataString, nil), nil},
+ {"ensureFile", expectArgs{"/sysroot/etc/passwd", os.FileMode(0444), os.FileMode(0700)}, nil, nil},
+ {"bindMount", expectArgs{"tmp.32768", "/sysroot/etc/passwd", uintptr(0x5), false}, nil, nil},
+ {"remove", expectArgs{"tmp.32768"}, nil, nil},
+ }, nil},
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*TmpfileOp)(nil), false},
{"zero", new(TmpfileOp), false},
- {"valid", &TmpfileOp{Path: MustAbs("/etc/passwd")}, true},
+ {"valid", &TmpfileOp{Path: samplePath}, true},
})
checkOpsBuilder(t, []opsBuilderTestCase{
- {"noref", new(Ops).Place(MustAbs("/etc/passwd"), []byte(`chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`)), Ops{
+ {"noref", new(Ops).Place(samplePath, sampleData), Ops{
&TmpfileOp{
- Path: MustAbs("/etc/passwd"),
- Data: []byte(`chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`),
+ Path: samplePath,
+ Data: sampleData,
},
}},
- {"ref", new(Ops).PlaceP(MustAbs("/etc/passwd"), new(*[]byte)), Ops{
+ {"ref", new(Ops).PlaceP(samplePath, new(*[]byte)), Ops{
&TmpfileOp{
- Path: MustAbs("/etc/passwd"),
+ Path: samplePath,
Data: []byte{},
},
}},
@@ -30,33 +99,33 @@ func TestTmpfileOp(t *testing.T) {
{"differs path", &TmpfileOp{
Path: MustAbs("/etc/group"),
- Data: []byte(`chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`),
+ Data: sampleData,
}, &TmpfileOp{
- Path: MustAbs("/etc/passwd"),
- Data: []byte(`chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`),
+ Path: samplePath,
+ Data: sampleData,
}, false},
{"differs data", &TmpfileOp{
- Path: MustAbs("/etc/passwd"),
- Data: []byte(`chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh` + "\x00"),
+ Path: samplePath,
+ Data: append(sampleData, 0),
}, &TmpfileOp{
- Path: MustAbs("/etc/passwd"),
- Data: []byte(`chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`),
+ Path: samplePath,
+ Data: sampleData,
}, false},
{"equals", &TmpfileOp{
- Path: MustAbs("/etc/passwd"),
- Data: []byte(`chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`),
+ Path: samplePath,
+ Data: sampleData,
}, &TmpfileOp{
- Path: MustAbs("/etc/passwd"),
- Data: []byte(`chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`),
+ Path: samplePath,
+ Data: sampleData,
}, true},
})
checkOpMeta(t, []opMetaTestCase{
{"passwd", &TmpfileOp{
- Path: MustAbs("/etc/passwd"),
- Data: []byte(`chronos:x:65534:65534:Hakurei:/var/empty:/bin/zsh`),
+ Path: samplePath,
+ Data: sampleData,
}, "placing", `tmpfile "/etc/passwd" (49 bytes)`},
})
}
diff --git a/container/initproc.go b/container/initproc.go
index 9b9769cd..fe02aeb7 100644
--- a/container/initproc.go
+++ b/container/initproc.go
@@ -3,7 +3,6 @@ package container
import (
"encoding/gob"
"fmt"
- "os"
. "syscall"
)
@@ -18,14 +17,14 @@ func (f *Ops) Proc(target *Absolute) *Ops {
// MountProcOp mounts a new instance of [FstypeProc] on container path Target.
type MountProcOp struct{ Target *Absolute }
-func (p *MountProcOp) Valid() bool { return p != nil && p.Target != nil }
-func (p *MountProcOp) early(*setupState) error { return nil }
-func (p *MountProcOp) apply(state *setupState) error {
+func (p *MountProcOp) Valid() bool { return p != nil && p.Target != nil }
+func (p *MountProcOp) early(*setupState, syscallDispatcher) error { return nil }
+func (p *MountProcOp) apply(state *setupState, k syscallDispatcher) error {
target := toSysroot(p.Target.String())
- if err := os.MkdirAll(target, state.ParentPerm); err != nil {
+ if err := k.mkdirAll(target, state.ParentPerm); err != nil {
return wrapErrSelf(err)
}
- return wrapErrSuffix(Mount(SourceProc, target, FstypeProc, MS_NOSUID|MS_NOEXEC|MS_NODEV, zeroString),
+ return wrapErrSuffix(k.mount(SourceProc, target, FstypeProc, MS_NOSUID|MS_NOEXEC|MS_NODEV, zeroString),
fmt.Sprintf("cannot mount proc on %q:", p.Target.String()))
}
diff --git a/container/initproc_test.go b/container/initproc_test.go
index eb55e4ec..2d72bd72 100644
--- a/container/initproc_test.go
+++ b/container/initproc_test.go
@@ -1,8 +1,28 @@
package container
-import "testing"
+import (
+ "os"
+ "testing"
+)
func TestMountProcOp(t *testing.T) {
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"mkdir", &Params{ParentPerm: 0755},
+ &MountProcOp{
+ Target: MustAbs("/proc/"),
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"success", &Params{ParentPerm: 0700},
+ &MountProcOp{
+ Target: MustAbs("/proc/"),
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/proc", os.FileMode(0700)}, nil, nil},
+ {"mount", expectArgs{"proc", "/sysroot/proc", "proc", uintptr(0xe), ""}, nil, nil},
+ }, nil},
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*MountProcOp)(nil), false},
{"zero", new(MountProcOp), false},
diff --git a/container/initremount.go b/container/initremount.go
index 74bea097..af58c740 100644
--- a/container/initremount.go
+++ b/container/initremount.go
@@ -19,10 +19,10 @@ type RemountOp struct {
Flags uintptr
}
-func (r *RemountOp) Valid() bool { return r != nil && r.Target != nil }
-func (*RemountOp) early(*setupState) error { return nil }
-func (r *RemountOp) apply(*setupState) error {
- return wrapErrSuffix(hostProc.remount(toSysroot(r.Target.String()), r.Flags),
+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 wrapErrSuffix(k.remount(toSysroot(r.Target.String()), r.Flags),
fmt.Sprintf("cannot remount %q:", r.Target))
}
diff --git a/container/initremount_test.go b/container/initremount_test.go
index db5476ec..22c978fc 100644
--- a/container/initremount_test.go
+++ b/container/initremount_test.go
@@ -6,6 +6,15 @@ import (
)
func TestRemountOp(t *testing.T) {
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"success", new(Params), &RemountOp{
+ Target: MustAbs("/"),
+ Flags: syscall.MS_RDONLY,
+ }, nil, nil, []kexpect{
+ {"remount", expectArgs{"/sysroot", uintptr(1)}, nil, nil},
+ }, nil},
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*RemountOp)(nil), false},
{"zero", new(RemountOp), false},
diff --git a/container/initsymlink.go b/container/initsymlink.go
index 3060817f..fb808670 100644
--- a/container/initsymlink.go
+++ b/container/initsymlink.go
@@ -3,9 +3,8 @@ package container
import (
"encoding/gob"
"fmt"
- "os"
+ "io/fs"
"path"
- "syscall"
)
func init() { gob.Register(new(SymlinkOp)) }
@@ -28,12 +27,12 @@ type SymlinkOp struct {
func (l *SymlinkOp) Valid() bool { return l != nil && l.Target != nil && l.LinkName != zeroString }
-func (l *SymlinkOp) early(*setupState) error {
+func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
if l.Dereference {
if !isAbs(l.LinkName) {
- return msg.WrapErr(syscall.EBADE, fmt.Sprintf("path %q is not absolute", l.LinkName))
+ return msg.WrapErr(fs.ErrInvalid, fmt.Sprintf("path %q is not absolute", l.LinkName))
}
- if name, err := os.Readlink(l.LinkName); err != nil {
+ if name, err := k.readlink(l.LinkName); err != nil {
return wrapErrSelf(err)
} else {
l.LinkName = name
@@ -42,15 +41,12 @@ func (l *SymlinkOp) early(*setupState) error {
return nil
}
-func (l *SymlinkOp) apply(state *setupState) error {
+func (l *SymlinkOp) apply(state *setupState, k syscallDispatcher) error {
target := toSysroot(l.Target.String())
- if err := os.MkdirAll(path.Dir(target), state.ParentPerm); err != nil {
+ if err := k.mkdirAll(path.Dir(target), state.ParentPerm); err != nil {
return wrapErrSelf(err)
}
- if err := os.Symlink(l.LinkName, target); err != nil {
- return wrapErrSelf(err)
- }
- return nil
+ return wrapErrSelf(k.symlink(l.LinkName, target))
}
func (l *SymlinkOp) Is(op Op) bool {
diff --git a/container/initsymlink_test.go b/container/initsymlink_test.go
index 4ae2da86..1bd0589e 100644
--- a/container/initsymlink_test.go
+++ b/container/initsymlink_test.go
@@ -1,8 +1,54 @@
package container
-import "testing"
+import (
+ "io/fs"
+ "os"
+ "testing"
+)
func TestSymlinkOp(t *testing.T) {
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"mkdir", &Params{ParentPerm: 0700}, &SymlinkOp{
+ Target: MustAbs("/etc/nixos"),
+ LinkName: "/etc/static/nixos",
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/etc", os.FileMode(0700)}, nil, errUnique},
+ }, wrapErrSelf(errUnique)},
+
+ {"abs", &Params{ParentPerm: 0755}, &SymlinkOp{
+ Target: MustAbs("/etc/mtab"),
+ LinkName: "etc/mtab",
+ Dereference: true,
+ }, nil, msg.WrapErr(fs.ErrInvalid, `path "etc/mtab" is not absolute`), nil, nil},
+
+ {"readlink", &Params{ParentPerm: 0755}, &SymlinkOp{
+ Target: MustAbs("/etc/mtab"),
+ LinkName: "/etc/mtab",
+ Dereference: true,
+ }, []kexpect{
+ {"readlink", expectArgs{"/etc/mtab"}, "/proc/mounts", errUnique},
+ }, wrapErrSelf(errUnique), nil, nil},
+
+ {"success noderef", &Params{ParentPerm: 0700}, &SymlinkOp{
+ Target: MustAbs("/etc/nixos"),
+ LinkName: "/etc/static/nixos",
+ }, nil, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/etc", os.FileMode(0700)}, nil, nil},
+ {"symlink", expectArgs{"/etc/static/nixos", "/sysroot/etc/nixos"}, nil, nil},
+ }, nil},
+
+ {"success", &Params{ParentPerm: 0755}, &SymlinkOp{
+ Target: MustAbs("/etc/mtab"),
+ LinkName: "/etc/mtab",
+ Dereference: true,
+ }, []kexpect{
+ {"readlink", expectArgs{"/etc/mtab"}, "/proc/mounts", nil},
+ }, nil, []kexpect{
+ {"mkdirAll", expectArgs{"/sysroot/etc", os.FileMode(0755)}, nil, nil},
+ {"symlink", expectArgs{"/proc/mounts", "/sysroot/etc/mtab"}, nil, nil},
+ }, nil},
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*SymlinkOp)(nil), false},
{"zero", new(SymlinkOp), false},
diff --git a/container/inittmpfs.go b/container/inittmpfs.go
index d313a111..0d50e463 100644
--- a/container/inittmpfs.go
+++ b/container/inittmpfs.go
@@ -3,6 +3,7 @@ package container
import (
"encoding/gob"
"fmt"
+ "io/fs"
"math"
"os"
. "syscall"
@@ -31,13 +32,13 @@ type MountTmpfsOp struct {
Perm os.FileMode
}
-func (t *MountTmpfsOp) Valid() bool { return t != nil && t.Path != nil && t.FSName != zeroString }
-func (t *MountTmpfsOp) early(*setupState) error { return nil }
-func (t *MountTmpfsOp) apply(*setupState) error {
+func (t *MountTmpfsOp) Valid() bool { return t != nil && t.Path != nil && t.FSName != zeroString }
+func (t *MountTmpfsOp) early(*setupState, syscallDispatcher) error { return nil }
+func (t *MountTmpfsOp) apply(_ *setupState, k syscallDispatcher) error {
if t.Size < 0 || t.Size > math.MaxUint>>1 {
- return msg.WrapErr(EBADE, fmt.Sprintf("size %d out of bounds", t.Size))
+ return msg.WrapErr(fs.ErrInvalid, fmt.Sprintf("size %d out of bounds", t.Size))
}
- return mountTmpfs(t.FSName, toSysroot(t.Path.String()), t.Flags, t.Size, t.Perm)
+ return k.mountTmpfs(t.FSName, toSysroot(t.Path.String()), t.Flags, t.Size, t.Perm)
}
func (t *MountTmpfsOp) Is(op Op) bool {
diff --git a/container/inittmpfs_test.go b/container/inittmpfs_test.go
index c9245869..8110f446 100644
--- a/container/inittmpfs_test.go
+++ b/container/inittmpfs_test.go
@@ -1,11 +1,34 @@
package container
import (
+ "io/fs"
+ "os"
"syscall"
"testing"
)
func TestMountTmpfsOp(t *testing.T) {
+ checkOpBehaviour(t, []opBehaviourTestCase{
+ {"size oob", new(Params), &MountTmpfsOp{
+ Size: -1,
+ }, nil, nil, nil, msg.WrapErr(fs.ErrInvalid, "size -1 out of bounds")},
+
+ {"success", new(Params), &MountTmpfsOp{
+ FSName: "ephemeral",
+ Path: MustAbs("/run/user/1000/"),
+ Size: 1 << 10,
+ Perm: 0700,
+ }, nil, nil, []kexpect{
+ {"mountTmpfs", expectArgs{
+ "ephemeral", // fsname
+ "/sysroot/run/user/1000", // target
+ uintptr(0), // flags
+ 0x400, // size
+ os.FileMode(0700), // perm
+ }, nil, nil},
+ }, nil},
+ })
+
checkOpsValid(t, []opValidTestCase{
{"nil", (*MountTmpfsOp)(nil), false},
{"zero", new(MountTmpfsOp), false},
diff --git a/container/mount_test.go b/container/mount_test.go
index 64cac282..eb904774 100644
--- a/container/mount_test.go
+++ b/container/mount_test.go
@@ -2,7 +2,6 @@ package container
import (
"os"
- "slices"
"testing"
)
@@ -48,85 +47,3 @@ func TestEscapeOverlayDataSegment(t *testing.T) {
})
}
}
-
-type opValidTestCase struct {
- name string
- op Op
- want bool
-}
-
-func checkOpsValid(t *testing.T, testCases []opValidTestCase) {
- t.Run("valid", func(t *testing.T) {
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if got := tc.op.Valid(); got != tc.want {
- t.Errorf("Valid: %v, want %v", got, tc.want)
- }
- })
- }
- })
-}
-
-type opsBuilderTestCase struct {
- name string
- ops *Ops
- want Ops
-}
-
-func checkOpsBuilder(t *testing.T, testCases []opsBuilderTestCase) {
- t.Run("build", func(t *testing.T) {
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if !slices.EqualFunc(*tc.ops, tc.want, func(op Op, v Op) bool { return op.Is(v) }) {
- t.Errorf("Ops: %#v, want %#v", tc.ops, tc.want)
- }
- })
- }
- })
-}
-
-type opIsTestCase struct {
- name string
- op, v Op
- want bool
-}
-
-func checkOpIs(t *testing.T, testCases []opIsTestCase) {
- t.Run("is", func(t *testing.T) {
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if got := tc.op.Is(tc.v); got != tc.want {
- t.Errorf("Is: %v, want %v", got, tc.want)
- }
- })
- }
- })
-}
-
-type opMetaTestCase struct {
- name string
- op Op
-
- wantPrefix string
- wantString string
-}
-
-func checkOpMeta(t *testing.T, testCases []opMetaTestCase) {
- t.Run("meta", func(t *testing.T) {
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Run("prefix", func(t *testing.T) {
- if got := tc.op.prefix(); got != tc.wantPrefix {
- t.Errorf("prefix: %q, want %q", got, tc.wantPrefix)
- }
- })
-
- t.Run("string", func(t *testing.T) {
- if got := tc.op.String(); got != tc.wantString {
- t.Errorf("String: %s, want %s", got, tc.wantString)
- }
- })
- })
- }
- })
-}
diff --git a/container/msg.go b/container/msg.go
index 3f29e99b..f66c8045 100644
--- a/container/msg.go
+++ b/container/msg.go
@@ -1,8 +1,13 @@
package container
import (
+ "errors"
+ "fmt"
"log"
+ "os"
+ "reflect"
"sync/atomic"
+ "testing"
)
type Msg interface {
@@ -32,7 +37,27 @@ func (msg *DefaultMsg) Verbosef(format string, v ...any) {
}
}
+// checkedWrappedErr implements error with strict checks for wrapped values.
+type checkedWrappedErr struct {
+ err error
+ a []any
+}
+
+func (c *checkedWrappedErr) Error() string { return fmt.Sprintf("%v, a = %s", c.err, c.a) }
+func (c *checkedWrappedErr) Is(err error) bool {
+ var concreteErr *checkedWrappedErr
+ if !errors.As(err, &concreteErr) {
+ return false
+ }
+ return reflect.DeepEqual(c, concreteErr)
+}
+
func (msg *DefaultMsg) WrapErr(err error, a ...any) error {
+ // provide a mostly bulletproof path to bypass this behaviour in tests
+ if testing.Testing() && os.Getenv("GOPATH") != Nonexistent {
+ return &checkedWrappedErr{err, a}
+ }
+
log.Println(a...)
return err
}
diff --git a/container/msg_test.go b/container/msg_test.go
index 6945c892..2ad42493 100644
--- a/container/msg_test.go
+++ b/container/msg_test.go
@@ -1,6 +1,7 @@
package container_test
import (
+ "errors"
"log"
"strings"
"sync/atomic"
@@ -12,6 +13,9 @@ import (
)
func TestDefaultMsg(t *testing.T) {
+ // bypass WrapErr testing behaviour
+ t.Setenv("GOPATH", container.Nonexistent)
+
{
w := log.Writer()
f := log.Flags()
@@ -79,6 +83,25 @@ func TestDefaultMsg(t *testing.T) {
// the function is a noop
t.Run("beforeExit", func(t *testing.T) { msg.BeforeExit() })
+
+ t.Run("checkedWrappedErr", func(t *testing.T) {
+ // temporarily re-enable testing behaviour
+ t.Setenv("GOPATH", "")
+ wrappedErr := msg.WrapErr(syscall.ENOTRECOVERABLE, "cannot cuddle cat:", syscall.ENOTRECOVERABLE)
+
+ t.Run("string", func(t *testing.T) {
+ want := "state not recoverable, a = [cannot cuddle cat: state not recoverable]"
+ if got := wrappedErr.Error(); got != want {
+ t.Errorf("Error: %q, want %q", got, want)
+ }
+ })
+
+ t.Run("bad concrete type", func(t *testing.T) {
+ if errors.Is(wrappedErr, syscall.ENOTRECOVERABLE) {
+ t.Error("incorrect type assertion")
+ }
+ })
+ })
}
type panicWriter struct{}
diff --git a/container/path.go b/container/path.go
index 8a01c932..be3cd384 100644
--- a/container/path.go
+++ b/container/path.go
@@ -151,8 +151,7 @@ func (p *procPaths) mountinfo(f func(d *vfs.MountInfoDecoder) error) error {
d := vfs.NewMountInfoDecoder(r)
err0 := f(d)
if err = r.Close(); err != nil {
- return wrapErrSuffix(err,
- "cannot close mountinfo:")
+ return wrapErrSelf(err)
} else if err = d.Err(); err != nil {
return wrapErrSuffix(err,
"cannot parse mountinfo:")
diff --git a/container/path_test.go b/container/path_test.go
index aea0e548..95fd2465 100644
--- a/container/path_test.go
+++ b/container/path_test.go
@@ -2,6 +2,7 @@ package container
import (
"errors"
+ "fmt"
"io"
"math"
"os"
@@ -55,10 +56,18 @@ func InternalToHostOvlEscape(s string) string { return EscapeOverlayDataSegment(
func TestCreateFile(t *testing.T) {
t.Run("nonexistent", func(t *testing.T) {
- if err := createFile(path.Join(Nonexistent, ":3"), 0644, 0755, nil); !os.IsNotExist(err) {
+ if err := createFile(path.Join(Nonexistent, ":3"), 0644, 0755, nil); !errors.Is(err, wrapErrSelf(&os.PathError{
+ Op: "mkdir",
+ Path: "/proc/nonexistent",
+ Err: syscall.ENOENT,
+ })) {
t.Errorf("createFile: error = %v", err)
}
- if err := createFile(path.Join(Nonexistent), 0644, 0755, nil); !os.IsNotExist(err) {
+ if err := createFile(path.Join(Nonexistent), 0644, 0755, nil); !errors.Is(err, wrapErrSelf(&os.PathError{
+ Op: "open",
+ Path: "/proc/nonexistent",
+ Err: syscall.ENOENT,
+ })) {
t.Errorf("createFile: error = %v", err)
}
})
@@ -110,17 +119,26 @@ func TestEnsureFile(t *testing.T) {
if err := os.Chmod(tempDir, 0); err != nil {
t.Fatalf("Chmod: error = %v", err)
}
- if err := ensureFile(pathname, 0644, 0755); !errors.Is(err, syscall.EACCES) {
- t.Errorf("ensureFile: error = %v, want %v", err, syscall.EACCES)
+
+ wantErr := wrapErrSelf(&os.PathError{
+ Op: "stat",
+ Path: pathname,
+ Err: syscall.EACCES,
+ })
+ if err := ensureFile(pathname, 0644, 0755); !errors.Is(err, wantErr) {
+ t.Errorf("ensureFile: error = %v, want %v", err, wantErr)
}
+
if err := os.Chmod(tempDir, 0755); err != nil {
t.Fatalf("Chmod: error = %v", err)
}
})
t.Run("directory", func(t *testing.T) {
- if err := ensureFile(t.TempDir(), 0644, 0755); !errors.Is(err, syscall.EISDIR) {
- t.Errorf("ensureFile: error = %v, want %v", err, syscall.EISDIR)
+ pathname := t.TempDir()
+ wantErr := msg.WrapErr(syscall.EISDIR, fmt.Sprintf("path %q is a directory", pathname))
+ if err := ensureFile(pathname, 0644, 0755); !errors.Is(err, wantErr) {
+ t.Errorf("ensureFile: error = %v, want %v", err, wantErr)
}
})
@@ -159,8 +177,13 @@ func TestProcPaths(t *testing.T) {
t.Run("mountinfo", func(t *testing.T) {
t.Run("nonexistent", func(t *testing.T) {
nonexistentProc := newProcPaths(t.TempDir())
- if err := nonexistentProc.mountinfo(func(*vfs.MountInfoDecoder) error { return syscall.EINVAL }); !os.IsNotExist(err) {
- t.Errorf("mountinfo: error = %v", err)
+ wantErr := wrapErrSelf(&os.PathError{
+ Op: "open",
+ Path: nonexistentProc.self + "/mountinfo",
+ Err: syscall.ENOENT,
+ })
+ if err := nonexistentProc.mountinfo(func(*vfs.MountInfoDecoder) error { return syscall.EINVAL }); !errors.Is(err, wantErr) {
+ t.Errorf("mountinfo: error = %v, want %v", err, wantErr)
}
})
@@ -193,7 +216,13 @@ func TestProcPaths(t *testing.T) {
})
t.Run("closed", func(t *testing.T) {
- if err := newProcPaths(tempDir).mountinfo(func(d *vfs.MountInfoDecoder) error {
+ p := newProcPaths(tempDir)
+ wantErr := wrapErrSelf(&os.PathError{
+ Op: "close",
+ Path: p.self + "/mountinfo",
+ Err: os.ErrClosed,
+ })
+ if err := p.mountinfo(func(d *vfs.MountInfoDecoder) error {
v := reflect.ValueOf(d).Elem().FieldByName("s").Elem().FieldByName("r")
v = reflect.NewAt(v.Type(), unsafe.Pointer(v.UnsafeAddr()))
if f, ok := v.Elem().Interface().(io.ReadCloser); !ok {
@@ -202,8 +231,8 @@ func TestProcPaths(t *testing.T) {
} else {
return f.Close()
}
- }); !errors.Is(err, os.ErrClosed) {
- t.Errorf("mountinfo: error = %v, want %v", err, os.ErrClosed)
+ }); !errors.Is(err, wantErr) {
+ t.Errorf("mountinfo: error = %v, want %v", err, wantErr)
}
})
@@ -213,8 +242,9 @@ func TestProcPaths(t *testing.T) {
t.Fatalf("WriteFile: error = %v", err)
}
- if err := newProcPaths(tempDir).mountinfo(func(d *vfs.MountInfoDecoder) error { return d.Decode(new(*vfs.MountInfo)) }); !errors.Is(err, vfs.ErrMountInfoFields) {
- t.Fatalf("mountinfo: error = %v, want %v", err, vfs.ErrMountInfoFields)
+ wantErr := wrapErrSuffix(vfs.ErrMountInfoFields, "cannot parse mountinfo:")
+ if err := newProcPaths(tempDir).mountinfo(func(d *vfs.MountInfoDecoder) error { return d.Decode(new(*vfs.MountInfo)) }); !errors.Is(err, wantErr) {
+ t.Fatalf("mountinfo: error = %v, want %v", err, wantErr)
}
})
})
diff --git a/container/syscall.go b/container/syscall.go
index 20ac068d..edb6e7e3 100644
--- a/container/syscall.go
+++ b/container/syscall.go
@@ -2,6 +2,7 @@ package container
import (
"syscall"
+ "unsafe"
)
// SetPtracer allows processes to ptrace(2) the calling process.
@@ -37,6 +38,18 @@ func SetNoNewPrivs() error {
return errno
}
+// Isatty tests whether a file descriptor refers to a terminal.
+func Isatty(fd int) bool {
+ var buf [8]byte
+ r, _, _ := syscall.Syscall(
+ syscall.SYS_IOCTL,
+ uintptr(fd),
+ syscall.TIOCGWINSZ,
+ uintptr(unsafe.Pointer(&buf[0])),
+ )
+ return r == 0
+}
+
// IgnoringEINTR makes a function call and repeats it if it returns an
// EINTR error. This appears to be required even though we install all
// signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846.