7 Commits

Author SHA1 Message Date
ad1bc6794f release: 0.2.2
All checks were successful
Release / Create release (push) Successful in 1m8s
Test / Sandbox (push) Successful in 51s
Test / Hakurei (push) Successful in 1m9s
Test / Create distribution (push) Successful in 37s
Test / Hpkg (push) Successful in 4m38s
Test / Sandbox (race detector) (push) Successful in 4m33s
Test / Hakurei (race detector) (push) Successful in 3m11s
Test / Flake checks (push) Successful in 1m42s
Unfortunately removal of internal/hlog brought about some changes that breaks API. This will likely be the last 0.2.x release.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-09-28 21:58:19 +09:00
e55822c62f container/init: reduce verbose noise
All checks were successful
Test / Create distribution (push) Successful in 56s
Test / Sandbox (push) Successful in 2m38s
Test / Hakurei (push) Successful in 3m45s
Test / Hpkg (push) Successful in 4m36s
Test / Sandbox (race detector) (push) Successful in 4m45s
Test / Hakurei (race detector) (push) Successful in 5m43s
Test / Flake checks (push) Successful in 1m41s
This makes it possible to optionally omit the identifying verbose message, for when the Op implementation can provide a much more useful message in its case, using information not yet available to the String method.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-09-28 21:51:10 +09:00
802e6afa34 container/output: move global output to msg
All checks were successful
Test / Create distribution (push) Successful in 32s
Test / Sandbox (push) Successful in 2m10s
Test / Hakurei (push) Successful in 3m10s
Test / Sandbox (race detector) (push) Successful in 4m27s
Test / Hpkg (push) Successful in 4m36s
Test / Hakurei (race detector) (push) Successful in 5m14s
Test / Flake checks (push) Successful in 1m22s
Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-09-27 19:55:37 +09:00
e906cae9ee container/output: export suspendable writer
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m12s
Test / Hakurei (push) Successful in 3m13s
Test / Hpkg (push) Successful in 4m1s
Test / Sandbox (race detector) (push) Successful in 4m34s
Test / Hakurei (race detector) (push) Successful in 5m14s
Test / Flake checks (push) Successful in 1m27s
This is quite useful for other packages as well. This change prepares internal/hlog for removal.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-09-27 19:46:35 +09:00
ae2df2c450 internal: remove sys package
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m13s
Test / Hakurei (push) Successful in 3m14s
Test / Hpkg (push) Successful in 4m2s
Test / Sandbox (race detector) (push) Successful in 4m39s
Test / Hakurei (race detector) (push) Successful in 5m19s
Test / Flake checks (push) Successful in 1m19s
This package is replaced by container/stub. Remove and replace it with unexported implementation for the upcoming test suite rewrite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-09-25 13:51:54 +09:00
6e3f34f2ec internal/app: merge finalise test cases
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m17s
Test / Hakurei (push) Successful in 3m6s
Test / Hpkg (push) Successful in 4m6s
Test / Sandbox (race detector) (push) Successful in 4m27s
Test / Hakurei (race detector) (push) Successful in 5m14s
Test / Flake checks (push) Successful in 1m28s
This cleans everything up a bit for the upcoming test suite rewrite.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-09-25 12:11:02 +09:00
65a0bb9729 internal/sys/hsu: expose hsurc identifier
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Hakurei (push) Successful in 3m10s
Test / Hpkg (push) Successful in 4m5s
Test / Sandbox (race detector) (push) Successful in 4m35s
Test / Hakurei (race detector) (push) Successful in 5m17s
Test / Sandbox (push) Successful in 1m16s
Test / Flake checks (push) Successful in 1m24s
This maintains a compatible interface for now, to ease merging of the upcoming changes to internal/app.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-09-24 21:17:04 +09:00
45 changed files with 1228 additions and 983 deletions

View File

@@ -18,7 +18,6 @@ import (
"hakurei.app/internal/app" "hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/internal/hlog" "hakurei.app/internal/hlog"
"hakurei.app/internal/sys"
"hakurei.app/system" "hakurei.app/system"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
) )
@@ -43,7 +42,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
config := tryPath(args[0]) config := tryPath(args[0])
config.Args = append(config.Args, args[1:]...) config.Args = append(config.Args, args[1:]...)
app.Main(ctx, std, config) app.Main(ctx, config)
panic("unreachable") panic("unreachable")
}) })
@@ -79,7 +78,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
passwd *user.User passwd *user.User
passwdOnce sync.Once passwdOnce sync.Once
passwdFunc = func() { passwdFunc = func() {
us := strconv.Itoa(sys.MustUid(std, flagIdentity)) us := strconv.Itoa(app.HsuUid(new(app.Hsu).MustID(), flagIdentity))
if u, err := user.LookupId(us); err != nil { if u, err := user.LookupId(us); err != nil {
hlog.Verbosef("cannot look up uid %s", us) hlog.Verbosef("cannot look up uid %s", us)
passwd = &user.User{ passwd = &user.User{
@@ -163,7 +162,7 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
} }
} }
app.Main(ctx, std, config) app.Main(ctx, config)
panic("unreachable") panic("unreachable")
}). }).
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"), Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
@@ -219,7 +218,9 @@ func buildCommand(ctx context.Context, out io.Writer) command.Command {
{ {
var flagShort bool var flagShort bool
c.NewCommand("ps", "List active instances", func(args []string) error { c.NewCommand("ps", "List active instances", func(args []string) error {
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(std.Paths().RunDirPath.String()), flagShort, flagJSON) var sc hst.Paths
app.CopyPaths(&sc, new(app.Hsu).MustID())
printPs(os.Stdout, time.Now().UTC(), state.NewMulti(sc.RunDirPath.String()), flagShort, flagJSON)
return errSuccess return errSuccess
}).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id") }).Flag(&flagShort, "short", command.BoolFlag(false), "Print instance id")
} }

View File

@@ -15,7 +15,6 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/internal/hlog" "hakurei.app/internal/hlog"
"hakurei.app/internal/sys"
) )
var ( var (
@@ -27,8 +26,6 @@ var (
func init() { hlog.Prepare("hakurei") } func init() { hlog.Prepare("hakurei") }
var std sys.State = new(sys.Std)
func main() { func main() {
// early init path, skips root check and duplicate PR_SET_DUMPABLE // early init path, skips root check and duplicate PR_SET_DUMPABLE
container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput) container.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallOutput)

View File

@@ -11,6 +11,7 @@ import (
"syscall" "syscall"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/internal/hlog" "hakurei.app/internal/hlog"
) )
@@ -87,7 +88,9 @@ func tryShort(name string) (config *hst.Config, entry *state.State) {
if likePrefix && len(name) >= 8 { if likePrefix && len(name) >= 8 {
hlog.Verbose("argument looks like prefix") hlog.Verbose("argument looks like prefix")
s := state.NewMulti(std.Paths().RunDirPath.String()) var sc hst.Paths
app.CopyPaths(&sc, new(app.Hsu).MustID())
s := state.NewMulti(sc.RunDirPath.String())
if entries, err := state.Join(s); err != nil { if entries, err := state.Join(s); err != nil {
log.Printf("cannot join store: %v", err) log.Printf("cannot join store: %v", err)
// drop to fetch from file // drop to fetch from file

View File

@@ -12,8 +12,8 @@ import (
"time" "time"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/internal/sys"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
) )
@@ -21,10 +21,8 @@ func printShowSystem(output io.Writer, short, flagJSON bool) {
t := newPrinter(output) t := newPrinter(output)
defer t.MustFlush() defer t.MustFlush()
info := &hst.Info{ info := &hst.Info{User: new(app.Hsu).MustID()}
Paths: std.Paths(), app.CopyPaths(&info.Paths, info.User)
User: sys.MustGetUserID(std),
}
if flagJSON { if flagJSON {
printJSON(output, short, info) printJSON(output, short, info)

View File

@@ -64,5 +64,5 @@ func (e *AutoEtcOp) Is(op Op) bool {
ve, ok := op.(*AutoEtcOp) ve, ok := op.(*AutoEtcOp)
return ok && e.Valid() && ve.Valid() && *e == *ve return ok && e.Valid() && ve.Valid() && *e == *ve
} }
func (*AutoEtcOp) prefix() string { return "setting up" } func (*AutoEtcOp) prefix() (string, bool) { return "setting up", true }
func (e *AutoEtcOp) String() string { return fmt.Sprintf("auto etc %s", e.Prefix) } func (e *AutoEtcOp) String() string { return fmt.Sprintf("auto etc %s", e.Prefix) }

View File

@@ -22,7 +22,7 @@ type AutoRootOp struct {
// obtained during early; // obtained during early;
// these wrap the underlying Op because BindMountOp is relatively complex, // these wrap the underlying Op because BindMountOp is relatively complex,
// so duplicating that code would be unwise // so duplicating that code would be unwise
resolved []Op resolved []*BindMountOp
} }
func (r *AutoRootOp) Valid() bool { return r != nil && r.Host != nil } func (r *AutoRootOp) Valid() bool { return r != nil && r.Host != nil }
@@ -31,10 +31,11 @@ func (r *AutoRootOp) early(state *setupState, k syscallDispatcher) error {
if d, err := k.readdir(r.Host.String()); err != nil { if d, err := k.readdir(r.Host.String()); err != nil {
return err return err
} else { } else {
r.resolved = make([]Op, 0, len(d)) r.resolved = make([]*BindMountOp, 0, len(d))
for _, ent := range d { for _, ent := range d {
name := ent.Name() name := ent.Name()
if IsAutoRootBindable(name) { if IsAutoRootBindable(name) {
// careful: the Valid method is skipped, make sure this is always valid
op := &BindMountOp{ op := &BindMountOp{
Source: r.Host.Append(name), Source: r.Host.Append(name),
Target: AbsFHSRoot.Append(name), Target: AbsFHSRoot.Append(name),
@@ -57,7 +58,7 @@ func (r *AutoRootOp) apply(state *setupState, k syscallDispatcher) error {
state.nonrepeatable |= nrAutoRoot state.nonrepeatable |= nrAutoRoot
for _, op := range r.resolved { for _, op := range r.resolved {
k.verbosef("%s %s", op.prefix(), op) // these are exclusively BindMountOp, do not attempt to print identifying message
if err := op.apply(state, k); err != nil { if err := op.apply(state, k); err != nil {
return err return err
} }
@@ -71,7 +72,7 @@ func (r *AutoRootOp) Is(op Op) bool {
r.Host.Is(vr.Host) && r.Host.Is(vr.Host) &&
r.Flags == vr.Flags r.Flags == vr.Flags
} }
func (*AutoRootOp) prefix() string { return "setting up" } func (*AutoRootOp) prefix() (string, bool) { return "setting up", true }
func (r *AutoRootOp) String() string { func (r *AutoRootOp) String() string {
return fmt.Sprintf("auto root %q flags %#x", r.Host, r.Flags) return fmt.Sprintf("auto root %q flags %#x", r.Host, r.Flags)
} }

View File

@@ -51,7 +51,6 @@ func TestAutoRootOp(t *testing.T) {
call("evalSymlinks", stub.ExpectArgs{"/usr"}, "/usr", nil), call("evalSymlinks", stub.ExpectArgs{"/usr"}, "/usr", nil),
call("evalSymlinks", stub.ExpectArgs{"/var"}, "/var", nil), call("evalSymlinks", stub.ExpectArgs{"/var"}, "/var", nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr/bin"), MustAbs("/bin"), MustAbs("/bin"), BindWritable}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(false), stub.UniqueError(0)), call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(false), stub.UniqueError(0)),
}, stub.UniqueError(0)}, }, stub.UniqueError(0)},
@@ -73,17 +72,17 @@ func TestAutoRootOp(t *testing.T) {
call("evalSymlinks", stub.ExpectArgs{"/usr"}, "/usr", nil), call("evalSymlinks", stub.ExpectArgs{"/usr"}, "/usr", nil),
call("evalSymlinks", stub.ExpectArgs{"/var"}, "/var", nil), call("evalSymlinks", stub.ExpectArgs{"/var"}, "/var", nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr/bin"), MustAbs("/bin"), MustAbs("/bin"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/usr/bin", "/sysroot/bin", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4004), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/home"), MustAbs("/home"), MustAbs("/home"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/home"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/home", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/home", "/sysroot/home", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/home"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/home", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/home", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/home", "/sysroot/home", uintptr(0x4004), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/lib64"), MustAbs("/lib64"), MustAbs("/lib64"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/lib64"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lib64", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/lib64", "/sysroot/lib64", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/lib64"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lib64", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/lib64", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/lib64", "/sysroot/lib64", uintptr(0x4004), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/lost+found"), MustAbs("/lost+found"), MustAbs("/lost+found"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/lost+found"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lost+found", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/lost+found", "/sysroot/lost+found", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/lost+found"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lost+found", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/lost+found", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/lost+found", "/sysroot/lost+found", uintptr(0x4004), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/nix"), MustAbs("/nix"), MustAbs("/nix"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/nix"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/nix", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/nix"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/nix", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/nix", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", uintptr(0x4004), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/root"), MustAbs("/root"), MustAbs("/root"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/root"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/root", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/root", "/sysroot/root", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/root"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/root", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/root", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/root", "/sysroot/root", uintptr(0x4004), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/run"), MustAbs("/run"), MustAbs("/run"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/run"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/run", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/run", "/sysroot/run", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/run"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/run", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/run", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/run", "/sysroot/run", uintptr(0x4004), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/srv"), MustAbs("/srv"), MustAbs("/srv"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/srv"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/srv", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/srv", "/sysroot/srv", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/srv"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/srv", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/srv", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/srv", "/sysroot/srv", uintptr(0x4004), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/sys"), MustAbs("/sys"), MustAbs("/sys"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/sys"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/sys", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/sys", "/sysroot/sys", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/sys"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/sys", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/sys", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/sys", "/sysroot/sys", uintptr(0x4004), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/usr"), MustAbs("/usr"), MustAbs("/usr"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/usr"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/usr", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/usr", "/sysroot/usr", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/usr"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/usr", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/usr", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/usr", "/sysroot/usr", uintptr(0x4004), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{MustAbs("/var"), MustAbs("/var"), MustAbs("/var"), BindWritable}}}, nil, nil), call("stat", stub.ExpectArgs{"/host/var"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/var", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var", "/sysroot/var", uintptr(0x4004), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/var", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/var", uintptr(0x4004)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var", "/sysroot/var", uintptr(0x4004), false}, nil, nil),
}, nil}, }, nil},
{"success", &Params{ParentPerm: 0750}, &AutoRootOp{ {"success", &Params{ParentPerm: 0750}, &AutoRootOp{
@@ -103,17 +102,17 @@ func TestAutoRootOp(t *testing.T) {
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/usr"}, "/var/lib/planterette/base/debian:f92c9052/usr", nil), call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/usr"}, "/var/lib/planterette/base/debian:f92c9052/usr", nil),
call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/var"}, "/var/lib/planterette/base/debian:f92c9052/var", nil), call("evalSymlinks", stub.ExpectArgs{"/var/lib/planterette/base/debian:f92c9052/var"}, "/var/lib/planterette/base/debian:f92c9052/var", nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin", "/sysroot/bin", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil),
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/home"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/home", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/home", "/sysroot/home", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/home"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/home", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/home", "/sysroot/home", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/home", "/sysroot/home", uintptr(0x4005), false}, nil, nil),
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lib64"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lib64", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lib64", "/sysroot/lib64", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lib64"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lib64", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/lib64", "/sysroot/lib64", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lib64", "/sysroot/lib64", uintptr(0x4005), false}, nil, nil),
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lost+found"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lost+found", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lost+found", "/sysroot/lost+found", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lost+found"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/lost+found", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/lost+found", "/sysroot/lost+found", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/lost+found", "/sysroot/lost+found", uintptr(0x4005), false}, nil, nil),
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/nix"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/nix", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/nix", "/sysroot/nix", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/nix"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/nix", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/nix", "/sysroot/nix", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/nix", "/sysroot/nix", uintptr(0x4005), false}, nil, nil),
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/root"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/root", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/root", "/sysroot/root", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/root"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/root", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/root", "/sysroot/root", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/root", "/sysroot/root", uintptr(0x4005), false}, nil, nil),
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/run"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/run", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/run", "/sysroot/run", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/run"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/run", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/run", "/sysroot/run", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/run", "/sysroot/run", uintptr(0x4005), false}, nil, nil),
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/srv"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/srv", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/srv", "/sysroot/srv", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/srv"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/srv", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/srv", "/sysroot/srv", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/srv", "/sysroot/srv", uintptr(0x4005), false}, nil, nil),
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/sys"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/sys", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/sys", "/sysroot/sys", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/sys"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/sys", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/sys", "/sysroot/sys", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/sys", "/sysroot/sys", uintptr(0x4005), false}, nil, nil),
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/usr", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr", "/sysroot/usr", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/usr", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/usr", "/sysroot/usr", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/usr", "/sysroot/usr", uintptr(0x4005), false}, nil, nil),
call("verbosef", stub.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), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/var"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/var", os.FileMode(0700)}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/var", "/sysroot/var", uintptr(0x4005), false}, nil, nil), call("stat", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/var"}, isDirFi(true), nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/var", os.FileMode(0700)}, nil, nil), call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/var/lib/planterette/base/debian:f92c9052/var", "/sysroot/var", uintptr(0x4005)}}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/var/lib/planterette/base/debian:f92c9052/var", "/sysroot/var", uintptr(0x4005), false}, nil, nil),
}, nil}, }, nil},
}) })
@@ -141,7 +140,7 @@ func TestAutoRootOp(t *testing.T) {
}, &AutoRootOp{ }, &AutoRootOp{
Host: MustAbs("/"), Host: MustAbs("/"),
Flags: BindWritable, Flags: BindWritable,
resolved: []Op{new(BindMountOp)}, resolved: []*BindMountOp{new(BindMountOp)},
}, true}, }, true},
{"flags differs", &AutoRootOp{ {"flags differs", &AutoRootOp{

View File

@@ -53,7 +53,7 @@ type syscallDispatcher interface {
receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error) receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error)
// bindMount provides procPaths.bindMount. // bindMount provides procPaths.bindMount.
bindMount(source, target string, flags uintptr, eq bool) error bindMount(source, target string, flags uintptr) error
// remount provides procPaths.remount. // remount provides procPaths.remount.
remount(target string, flags uintptr) error remount(target string, flags uintptr) error
// mountTmpfs provides mountTmpfs. // mountTmpfs provides mountTmpfs.
@@ -161,8 +161,8 @@ func (direct) receive(key string, e any, fdp *uintptr) (func() error, error) {
return Receive(key, e, fdp) return Receive(key, e, fdp)
} }
func (direct) bindMount(source, target string, flags uintptr, eq bool) error { func (direct) bindMount(source, target string, flags uintptr) error {
return hostProc.bindMount(source, target, flags, eq) return hostProc.bindMount(source, target, flags)
} }
func (direct) remount(target string, flags uintptr) error { func (direct) remount(target string, flags uintptr) error {
return hostProc.remount(target, flags) return hostProc.remount(target, flags)

View File

@@ -111,7 +111,7 @@ func checkOpMeta(t *testing.T, testCases []opMetaTestCase) {
t.Run("prefix", func(t *testing.T) { t.Run("prefix", func(t *testing.T) {
t.Helper() t.Helper()
if got := tc.op.prefix(); got != tc.wantPrefix { if got, _ := tc.op.prefix(); got != tc.wantPrefix {
t.Errorf("prefix: %q, want %q", got, tc.wantPrefix) t.Errorf("prefix: %q, want %q", got, tc.wantPrefix)
} }
}) })
@@ -403,13 +403,12 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
return return
} }
func (k *kstub) bindMount(source, target string, flags uintptr, eq bool) error { func (k *kstub) bindMount(source, target string, flags uintptr) error {
k.Helper() k.Helper()
return k.Expects("bindMount").Error( return k.Expects("bindMount").Error(
stub.CheckArg(k.Stub, "source", source, 0), stub.CheckArg(k.Stub, "source", source, 0),
stub.CheckArg(k.Stub, "target", target, 1), stub.CheckArg(k.Stub, "target", target, 1),
stub.CheckArg(k.Stub, "flags", flags, 2), stub.CheckArg(k.Stub, "flags", flags, 2))
stub.CheckArg(k.Stub, "eq", eq, 3))
} }
func (k *kstub) remount(target string, flags uintptr) error { func (k *kstub) remount(target string, flags uintptr) error {

View File

@@ -47,7 +47,9 @@ type (
// apply is called in intermediate root. // apply is called in intermediate root.
apply(state *setupState, k syscallDispatcher) error apply(state *setupState, k syscallDispatcher) error
prefix() string // prefix returns a log message prefix, and whether this Op prints no identifying message on its own.
prefix() (string, bool)
Is(op Op) bool Is(op Op) bool
Valid() bool Valid() bool
fmt.Stringer fmt.Stringer
@@ -223,7 +225,9 @@ func initEntrypoint(k syscallDispatcher, prepareLogger func(prefix string), setV
chdir is allowed but discouraged */ chdir is allowed but discouraged */
for i, op := range *params.Ops { for i, op := range *params.Ops {
// ops already checked during early setup // ops already checked during early setup
k.verbosef("%s %s", op.prefix(), op) if prefix, ok := op.prefix(); ok {
k.verbosef("%s %s", prefix, op)
}
if err := op.apply(state, k); err != nil { if err := op.apply(state, k); err != nil {
if m, ok := messageFromError(err); ok { if m, ok := messageFromError(err); ok {
k.fatal(m) k.fatal(m)

View File

@@ -790,9 +790,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -844,9 +844,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -898,9 +898,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -953,9 +953,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1009,9 +1009,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1067,9 +1067,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1126,9 +1126,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1186,9 +1186,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1247,9 +1247,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1309,9 +1309,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1372,9 +1372,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1436,9 +1436,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1501,9 +1501,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1574,9 +1574,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1680,9 +1680,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1787,9 +1787,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),
@@ -1895,9 +1895,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil),
@@ -2008,9 +2008,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil),
@@ -2107,9 +2107,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil),
@@ -2197,9 +2197,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil),
@@ -2289,9 +2289,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil),
@@ -2388,9 +2388,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil),
@@ -2524,9 +2524,9 @@ func TestInitEntrypoint(t *testing.T) {
call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil),
call("chdir", stub.ExpectArgs{"/"}, nil, nil), call("chdir", stub.ExpectArgs{"/"}, nil, nil),
/* begin apply */ /* begin apply */
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &BindMountOp{sourceFinal: MustAbs("/"), Source: MustAbs("/"), Target: MustAbs("/"), Flags: BindDevice}}}, nil, nil),
call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil),
call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil), call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: MustAbs("/proc/")}}}, nil, nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0755)}, nil, nil),

View File

@@ -91,7 +91,12 @@ func (b *BindMountOp) apply(_ *setupState, k syscallDispatcher) error {
flags |= syscall.MS_NODEV flags |= syscall.MS_NODEV
} }
return k.bindMount(source, target, flags, b.sourceFinal == b.Target) if b.sourceFinal.String() == b.Target.String() {
k.verbosef("mounting %q flags %#x", target, flags)
} else {
k.verbosef("mounting %q on %q flags %#x", source, target, flags)
}
return k.bindMount(source, target, flags)
} }
func (b *BindMountOp) Is(op Op) bool { func (b *BindMountOp) Is(op Op) bool {
@@ -101,7 +106,7 @@ func (b *BindMountOp) Is(op Op) bool {
b.Target.Is(vb.Target) && b.Target.Is(vb.Target) &&
b.Flags == vb.Flags b.Flags == vb.Flags
} }
func (*BindMountOp) prefix() string { return "mounting" } func (*BindMountOp) prefix() (string, bool) { return "mounting", false }
func (b *BindMountOp) String() string { func (b *BindMountOp) String() string {
if b.Source == nil || b.Target == nil { if b.Source == nil || b.Target == nil {
return "<invalid>" return "<invalid>"

View File

@@ -35,6 +35,7 @@ func TestBindMountOp(t *testing.T) {
}, nil, []stub.Call{ }, nil, []stub.Call{
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil),
}, nil}, }, nil},
@@ -67,6 +68,7 @@ func TestBindMountOp(t *testing.T) {
}, nil, []stub.Call{ }, nil, []stub.Call{
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/usr/bin", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/usr/bin", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/usr/bin", "/sysroot/usr/bin", uintptr(0x4005)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/usr/bin", uintptr(0x4005), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/usr/bin", uintptr(0x4005), false}, nil, nil),
}, nil}, }, nil},
@@ -79,6 +81,7 @@ func TestBindMountOp(t *testing.T) {
}, nil, []stub.Call{ }, nil, []stub.Call{
call("stat", stub.ExpectArgs{"/host/dev/null"}, isDirFi(false), nil), call("stat", stub.ExpectArgs{"/host/dev/null"}, isDirFi(false), nil),
call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil), call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/dev/null", uintptr(0x4001)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4001), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4001), false}, nil, nil),
}, nil}, }, nil},
@@ -91,6 +94,7 @@ func TestBindMountOp(t *testing.T) {
}, nil, []stub.Call{ }, nil, []stub.Call{
call("stat", stub.ExpectArgs{"/host/dev/null"}, isDirFi(false), nil), call("stat", stub.ExpectArgs{"/host/dev/null"}, isDirFi(false), nil),
call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil), call("ensureFile", stub.ExpectArgs{"/sysroot/dev/null", os.FileMode(0444), os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot/dev/null", uintptr(0x4000)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4000), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/dev/null", "/sysroot/dev/null", uintptr(0x4000), false}, nil, nil),
}, nil}, }, nil},
@@ -128,9 +132,22 @@ func TestBindMountOp(t *testing.T) {
}, nil, []stub.Call{ }, nil, []stub.Call{
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, stub.UniqueError(0)), call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, stub.UniqueError(0)),
}, stub.UniqueError(0)}, }, stub.UniqueError(0)},
{"success eval equals", new(Params), &BindMountOp{
Source: MustAbs("/bin/"),
Target: MustAbs("/bin/"),
}, []stub.Call{
call("evalSymlinks", stub.ExpectArgs{"/bin/"}, "/bin", nil),
}, nil, []stub.Call{
call("stat", stub.ExpectArgs{"/host/bin"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/bin", "/sysroot/bin", uintptr(0x4005)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil),
}, nil},
{"success", new(Params), &BindMountOp{ {"success", new(Params), &BindMountOp{
Source: MustAbs("/bin/"), Source: MustAbs("/bin/"),
Target: MustAbs("/bin/"), Target: MustAbs("/bin/"),
@@ -139,6 +156,7 @@ func TestBindMountOp(t *testing.T) {
}, nil, []stub.Call{ }, nil, []stub.Call{
call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil), call("stat", stub.ExpectArgs{"/host/usr/bin"}, isDirFi(true), nil),
call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil), call("mkdirAll", stub.ExpectArgs{"/sysroot/bin", os.FileMode(0700)}, nil, nil),
call("verbosef", stub.ExpectArgs{"mounting %q on %q flags %#x", []any{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005)}}, nil, nil),
call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil), call("bindMount", stub.ExpectArgs{"/host/usr/bin", "/sysroot/bin", uintptr(0x4005), false}, nil, nil),
}, nil}, }, nil},
}) })

View File

@@ -49,7 +49,6 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
toHost(FHSDev+name), toHost(FHSDev+name),
targetPath, targetPath,
0, 0,
true,
); err != nil { ); err != nil {
return err return err
} }
@@ -97,7 +96,6 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
toHost(name), toHost(name),
consolePath, consolePath,
0, 0,
false,
); err != nil { ); err != nil {
return err return err
} }
@@ -131,7 +129,7 @@ func (d *MountDevOp) Is(op Op) bool {
d.Mqueue == vd.Mqueue && d.Mqueue == vd.Mqueue &&
d.Write == vd.Write d.Write == vd.Write
} }
func (*MountDevOp) prefix() string { return "mounting" } func (*MountDevOp) prefix() (string, bool) { return "mounting", true }
func (d *MountDevOp) String() string { func (d *MountDevOp) String() string {
if d.Mqueue { if d.Mqueue {
return fmt.Sprintf("dev on %q with mqueue", d.Target) return fmt.Sprintf("dev on %q with mqueue", d.Target)

View File

@@ -32,5 +32,5 @@ func (m *MkdirOp) Is(op Op) bool {
m.Path.Is(vm.Path) && m.Path.Is(vm.Path) &&
m.Perm == vm.Perm m.Perm == vm.Perm
} }
func (*MkdirOp) prefix() string { return "creating" } func (*MkdirOp) prefix() (string, bool) { return "creating", true }
func (m *MkdirOp) String() string { return fmt.Sprintf("directory %q perm %s", m.Path, m.Perm) } func (m *MkdirOp) String() string { return fmt.Sprintf("directory %q perm %s", m.Path, m.Perm) }

View File

@@ -209,7 +209,7 @@ func (o *MountOverlayOp) Is(op Op) bool {
slices.EqualFunc(o.Lower, vo.Lower, func(a *Absolute, v *Absolute) bool { return a.Is(v) }) && slices.EqualFunc(o.Lower, vo.Lower, func(a *Absolute, v *Absolute) bool { return a.Is(v) }) &&
o.Upper.Is(vo.Upper) && o.Work.Is(vo.Work) o.Upper.Is(vo.Upper) && o.Work.Is(vo.Work)
} }
func (*MountOverlayOp) prefix() string { return "mounting" } func (*MountOverlayOp) prefix() (string, bool) { return "mounting", true }
func (o *MountOverlayOp) String() string { func (o *MountOverlayOp) String() string {
return fmt.Sprintf("overlay on %q with %d layers", o.Target, len(o.Lower)) return fmt.Sprintf("overlay on %q with %d layers", o.Target, len(o.Lower))
} }

View File

@@ -55,7 +55,6 @@ func (t *TmpfileOp) apply(state *setupState, k syscallDispatcher) error {
tmpPath, tmpPath,
target, target,
syscall.MS_RDONLY|syscall.MS_NODEV, syscall.MS_RDONLY|syscall.MS_NODEV,
false,
); err != nil { ); err != nil {
return err return err
} else if err = k.remove(tmpPath); err != nil { } else if err = k.remove(tmpPath); err != nil {
@@ -70,7 +69,7 @@ func (t *TmpfileOp) Is(op Op) bool {
t.Path.Is(vt.Path) && t.Path.Is(vt.Path) &&
string(t.Data) == string(vt.Data) string(t.Data) == string(vt.Data)
} }
func (*TmpfileOp) prefix() string { return "placing" } func (*TmpfileOp) prefix() (string, bool) { return "placing", true }
func (t *TmpfileOp) String() string { func (t *TmpfileOp) String() string {
return fmt.Sprintf("tmpfile %q (%d bytes)", t.Path, len(t.Data)) return fmt.Sprintf("tmpfile %q (%d bytes)", t.Path, len(t.Data))
} }

View File

@@ -32,5 +32,5 @@ func (p *MountProcOp) Is(op Op) bool {
return ok && p.Valid() && vp.Valid() && return ok && p.Valid() && vp.Valid() &&
p.Target.Is(vp.Target) p.Target.Is(vp.Target)
} }
func (*MountProcOp) prefix() string { return "mounting" } func (*MountProcOp) prefix() (string, bool) { return "mounting", true }
func (p *MountProcOp) String() string { return fmt.Sprintf("proc on %q", p.Target) } func (p *MountProcOp) String() string { return fmt.Sprintf("proc on %q", p.Target) }

View File

@@ -31,5 +31,5 @@ func (r *RemountOp) Is(op Op) bool {
r.Target.Is(vr.Target) && r.Target.Is(vr.Target) &&
r.Flags == vr.Flags r.Flags == vr.Flags
} }
func (*RemountOp) prefix() string { return "remounting" } func (*RemountOp) prefix() (string, bool) { return "remounting", true }
func (r *RemountOp) String() string { return fmt.Sprintf("%q flags %#x", r.Target, r.Flags) } func (r *RemountOp) String() string { return fmt.Sprintf("%q flags %#x", r.Target, r.Flags) }

View File

@@ -55,7 +55,7 @@ func (l *SymlinkOp) Is(op Op) bool {
l.LinkName == vl.LinkName && l.LinkName == vl.LinkName &&
l.Dereference == vl.Dereference l.Dereference == vl.Dereference
} }
func (*SymlinkOp) prefix() string { return "creating" } func (*SymlinkOp) prefix() (string, bool) { return "creating", true }
func (l *SymlinkOp) String() string { func (l *SymlinkOp) String() string {
return fmt.Sprintf("symlink on %q linkname %q", l.Target, l.LinkName) return fmt.Sprintf("symlink on %q linkname %q", l.Target, l.LinkName)
} }

View File

@@ -56,5 +56,5 @@ func (t *MountTmpfsOp) Is(op Op) bool {
t.Size == vt.Size && t.Size == vt.Size &&
t.Perm == vt.Perm t.Perm == vt.Perm
} }
func (*MountTmpfsOp) prefix() string { return "mounting" } func (*MountTmpfsOp) prefix() (string, bool) { return "mounting", true }
func (t *MountTmpfsOp) String() string { return fmt.Sprintf("tmpfs on %q size %d", t.Path, t.Size) } func (t *MountTmpfsOp) String() string { return fmt.Sprintf("tmpfs on %q size %d", t.Path, t.Size) }

View File

@@ -96,15 +96,9 @@ const (
) )
// bindMount mounts source on target and recursively applies flags if MS_REC is set. // bindMount mounts source on target and recursively applies flags if MS_REC is set.
func (p *procPaths) bindMount(source, target string, flags uintptr, eq bool) error { func (p *procPaths) bindMount(source, target string, flags uintptr) error {
// syscallDispatcher.bindMount and procPaths.remount must not be called from this function // syscallDispatcher.bindMount and procPaths.remount must not be called from this function
if eq {
p.k.verbosef("resolved %q flags %#x", target, flags)
} else {
p.k.verbosef("resolved %q on %q flags %#x", source, target, flags)
}
if err := p.k.mount(source, target, FstypeNULL, MS_SILENT|MS_BIND|flags&MS_REC, zeroString); err != nil { if err := p.k.mount(source, target, FstypeNULL, MS_SILENT|MS_BIND|flags&MS_REC, zeroString); err != nil {
return err return err
} }

View File

@@ -12,24 +12,21 @@ import (
func TestBindMount(t *testing.T) { func TestBindMount(t *testing.T) {
checkSimple(t, "bindMount", []simpleTestCase{ checkSimple(t, "bindMount", []simpleTestCase{
{"mount", func(k syscallDispatcher) error { {"mount", func(k syscallDispatcher) error {
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY, true) return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("verbosef", stub.ExpectArgs{"resolved %q flags %#x", []any{"/sysroot/nix", uintptr(1)}}, nil, nil),
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, stub.UniqueError(0xbad)), call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, stub.UniqueError(0xbad)),
}}, stub.UniqueError(0xbad)}, }}, stub.UniqueError(0xbad)},
{"success ne", func(k syscallDispatcher) error { {"success ne", func(k syscallDispatcher) error {
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY, false) return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/.host-nix", syscall.MS_RDONLY)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("verbosef", stub.ExpectArgs{"resolved %q on %q flags %#x", []any{"/host/nix", "/sysroot/.host-nix", uintptr(1)}}, nil, nil),
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/.host-nix", "", uintptr(0x9000), ""}, nil, nil), call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/.host-nix", "", uintptr(0x9000), ""}, nil, nil),
call("remount", stub.ExpectArgs{"/sysroot/.host-nix", uintptr(1)}, nil, nil), call("remount", stub.ExpectArgs{"/sysroot/.host-nix", uintptr(1)}, nil, nil),
}}, nil}, }}, nil},
{"success", func(k syscallDispatcher) error { {"success", func(k syscallDispatcher) error {
return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY, true) return newProcPaths(k, hostPath).bindMount("/host/nix", "/sysroot/nix", syscall.MS_RDONLY)
}, stub.Expect{Calls: []stub.Call{ }, stub.Expect{Calls: []stub.Call{
call("verbosef", stub.ExpectArgs{"resolved %q flags %#x", []any{"/sysroot/nix", uintptr(1)}}, nil, nil),
call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, nil), call("mount", stub.ExpectArgs{"/host/nix", "/sysroot/nix", "", uintptr(0x9000), ""}, nil, nil),
call("remount", stub.ExpectArgs{"/sysroot/nix", uintptr(1)}, nil, nil), call("remount", stub.ExpectArgs{"/sysroot/nix", uintptr(1)}, nil, nil),
}}, nil}, }}, nil},

View File

@@ -50,3 +50,18 @@ func (msg *DefaultMsg) Verbosef(format string, v ...any) {
func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) } func (msg *DefaultMsg) Suspend() { msg.inactive.Store(true) }
func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) } func (msg *DefaultMsg) Resume() bool { return msg.inactive.CompareAndSwap(true, false) }
func (msg *DefaultMsg) BeforeExit() {} func (msg *DefaultMsg) BeforeExit() {}
// msg is the [Msg] implemented used by all exported [container] functions.
var msg Msg = new(DefaultMsg)
// GetOutput returns the current active [Msg] implementation.
func GetOutput() Msg { return msg }
// SetOutput replaces the current active [Msg] implementation.
func SetOutput(v Msg) {
if v == nil {
msg = new(DefaultMsg)
} else {
msg = v
}
}

View File

@@ -146,3 +146,39 @@ func (out *testOutput) Resume() bool {
} }
func (out *testOutput) BeforeExit() { out.Verbose("beforeExit called") } func (out *testOutput) BeforeExit() { out.Verbose("beforeExit called") }
func TestGetSetOutput(t *testing.T) {
{
out := container.GetOutput()
t.Cleanup(func() { container.SetOutput(out) })
}
t.Run("default", func(t *testing.T) {
container.SetOutput(new(stubOutput))
if v, ok := container.GetOutput().(*container.DefaultMsg); ok {
t.Fatalf("SetOutput: got unexpected output %#v", v)
}
container.SetOutput(nil)
if _, ok := container.GetOutput().(*container.DefaultMsg); !ok {
t.Fatalf("SetOutput: got unexpected output %#v", container.GetOutput())
}
})
t.Run("stub", func(t *testing.T) {
container.SetOutput(new(stubOutput))
if _, ok := container.GetOutput().(*stubOutput); !ok {
t.Fatalf("SetOutput: got unexpected output %#v", container.GetOutput())
}
})
}
type stubOutput struct {
wrapF func(error, ...any) error
}
func (*stubOutput) IsVerbose() bool { panic("unreachable") }
func (*stubOutput) Verbose(...any) { panic("unreachable") }
func (*stubOutput) Verbosef(string, ...any) { panic("unreachable") }
func (*stubOutput) Suspend() { panic("unreachable") }
func (*stubOutput) Resume() bool { panic("unreachable") }
func (*stubOutput) BeforeExit() { panic("unreachable") }

View File

@@ -1,12 +1,77 @@
package container package container
var msg Msg = new(DefaultMsg) import (
"bytes"
"io"
"sync"
"sync/atomic"
"syscall"
)
func GetOutput() Msg { return msg } const (
func SetOutput(v Msg) { suspendBufInitial = 1 << 12
if v == nil { suspendBufMax = 1 << 24
msg = new(DefaultMsg) )
} else {
msg = v // Suspendable proxies writes to a downstream [io.Writer] but optionally withholds writes
// between calls to Suspend and Resume.
type Suspendable struct {
Downstream io.Writer
s atomic.Bool
buf bytes.Buffer
// for growing buf
bufOnce sync.Once
// for synchronising all other buf operations
bufMu sync.Mutex
dropped int
} }
func (s *Suspendable) Write(p []byte) (n int, err error) {
if !s.s.Load() {
return s.Downstream.Write(p)
}
s.bufOnce.Do(func() { s.buf.Grow(suspendBufInitial) })
s.bufMu.Lock()
defer s.bufMu.Unlock()
if free := suspendBufMax - s.buf.Len(); free < len(p) {
// fast path
if free <= 0 {
s.dropped += len(p)
return 0, syscall.ENOMEM
}
n, _ = s.buf.Write(p[:free])
err = syscall.ENOMEM
s.dropped += len(p) - n
return
}
return s.buf.Write(p)
}
// IsSuspended returns whether [Suspendable] is currently between a call to Suspend and Resume.
func (s *Suspendable) IsSuspended() bool { return s.s.Load() }
// Suspend causes [Suspendable] to start withholding output in its buffer.
func (s *Suspendable) Suspend() bool { return s.s.CompareAndSwap(false, true) }
// Resume undoes the effect of Suspend and dumps the buffered into the downstream [io.Writer].
func (s *Suspendable) Resume() (resumed bool, dropped uintptr, n int64, err error) {
if s.s.CompareAndSwap(true, false) {
s.bufMu.Lock()
defer s.bufMu.Unlock()
resumed = true
dropped = uintptr(s.dropped)
s.dropped = 0
n, err = io.Copy(s.Downstream, &s.buf)
s.buf.Reset()
}
return
} }

View File

@@ -1,41 +1,155 @@
package container package container_test
import ( import (
"bytes"
"errors"
"reflect"
"strconv"
"syscall"
"testing" "testing"
"hakurei.app/container"
"hakurei.app/container/stub"
) )
func TestGetSetOutput(t *testing.T) { func TestSuspendable(t *testing.T) {
{ // copied from output.go
out := GetOutput() const suspendBufMax = 1 << 24
t.Cleanup(func() { SetOutput(out) })
const (
// equivalent to len(want.pt)
nSpecialPtEquiv = -iota - 1
// equivalent to len(want.w)
nSpecialWEquiv
// suspends writer before executing test case, implies nSpecialWEquiv
nSpecialSuspend
// offset: resume writer and measure against dump instead, implies nSpecialPtEquiv
nSpecialDump
)
// shares the same writer
testCases := []struct {
name string
w, pt []byte
err error
wantErr error
n int
}{
{"simple", []byte{0xde, 0xad, 0xbe, 0xef}, []byte{0xde, 0xad, 0xbe, 0xef},
nil, nil, nSpecialPtEquiv},
{"error", []byte{0xb, 0xad}, []byte{0xb, 0xad},
stub.UniqueError(0), stub.UniqueError(0), nSpecialPtEquiv},
{"suspend short", []byte{0}, nil,
nil, nil, nSpecialSuspend},
{"sw short 0", []byte{0xca, 0xfe, 0xba, 0xbe}, nil,
nil, nil, nSpecialWEquiv},
{"sw short 1", []byte{0xff}, nil,
nil, nil, nSpecialWEquiv},
{"resume short", nil, []byte{0, 0xca, 0xfe, 0xba, 0xbe, 0xff}, nil, nil,
nSpecialDump},
{"long pt", bytes.Repeat([]byte{0xff}, suspendBufMax+1), bytes.Repeat([]byte{0xff}, suspendBufMax+1),
nil, nil, nSpecialPtEquiv},
{"suspend fill", bytes.Repeat([]byte{0xfe}, suspendBufMax), nil,
nil, nil, nSpecialSuspend},
{"drop", []byte{0}, nil,
nil, syscall.ENOMEM, 0},
{"drop error", []byte{0}, nil,
stub.UniqueError(1), syscall.ENOMEM, 0},
{"resume fill", nil, bytes.Repeat([]byte{0xfe}, suspendBufMax),
nil, nil, nSpecialDump - 2},
{"suspend fill partial", bytes.Repeat([]byte{0xfd}, suspendBufMax-0xf), nil,
nil, nil, nSpecialSuspend},
{"partial write", bytes.Repeat([]byte{0xad}, 0x1f), nil,
nil, syscall.ENOMEM, 0xf},
{"full drop", []byte{0}, nil,
nil, syscall.ENOMEM, 0},
{"resume fill partial", nil, append(bytes.Repeat([]byte{0xfd}, suspendBufMax-0xf), bytes.Repeat([]byte{0xad}, 0xf)...),
nil, nil, nSpecialDump - 0x10 - 1},
} }
t.Run("default", func(t *testing.T) { var dw expectWriter
SetOutput(new(stubOutput))
if v, ok := GetOutput().(*DefaultMsg); ok {
t.Fatalf("SetOutput: got unexpected output %#v", v)
}
SetOutput(nil)
if _, ok := GetOutput().(*DefaultMsg); !ok {
t.Fatalf("SetOutput: got unexpected output %#v", GetOutput())
}
})
t.Run("stub", func(t *testing.T) { w := container.Suspendable{Downstream: &dw}
SetOutput(new(stubOutput)) for _, tc := range testCases {
if _, ok := GetOutput().(*stubOutput); !ok { // these share the same writer, so cannot be subtests
t.Fatalf("SetOutput: got unexpected output %#v", GetOutput()) t.Logf("writing step %q", tc.name)
} dw.expect, dw.err = tc.pt, tc.err
})
var (
gotN int
gotErr error
)
wantN := tc.n
switch wantN {
case nSpecialPtEquiv:
wantN = len(tc.pt)
gotN, gotErr = w.Write(tc.w)
case nSpecialWEquiv:
wantN = len(tc.w)
gotN, gotErr = w.Write(tc.w)
case nSpecialSuspend:
s := w.IsSuspended()
if ok := w.Suspend(); s && ok {
t.Fatal("Suspend: unexpected success")
} }
type stubOutput struct { wantN = len(tc.w)
wrapF func(error, ...any) error gotN, gotErr = w.Write(tc.w)
default:
if wantN <= nSpecialDump {
if !w.IsSuspended() {
t.Fatal("IsSuspended unexpected false")
} }
func (*stubOutput) IsVerbose() bool { panic("unreachable") } resumed, dropped, n, err := w.Resume()
func (*stubOutput) Verbose(...any) { panic("unreachable") } if !resumed {
func (*stubOutput) Verbosef(string, ...any) { panic("unreachable") } t.Fatal("Resume: resumed = false")
func (*stubOutput) Suspend() { panic("unreachable") } }
func (*stubOutput) Resume() bool { panic("unreachable") } if wantDropped := nSpecialDump - wantN; int(dropped) != wantDropped {
func (*stubOutput) BeforeExit() { panic("unreachable") } t.Errorf("Resume: dropped = %d, want %d", dropped, wantDropped)
}
wantN = len(tc.pt)
gotN, gotErr = int(n), err
} else {
gotN, gotErr = w.Write(tc.w)
}
}
if gotN != wantN {
t.Errorf("Write: n = %d, want %d", gotN, wantN)
}
if !reflect.DeepEqual(gotErr, tc.wantErr) {
t.Errorf("Write: %v", gotErr)
}
}
}
// expectWriter compares Write calls to expect.
type expectWriter struct {
expect []byte
err error
}
func (w *expectWriter) Write(p []byte) (n int, err error) {
defer func() { w.expect = nil }()
n, err = len(p), w.err
if w.expect == nil {
return 0, errors.New("unexpected call to Write: " + strconv.Quote(string(p)))
}
if string(p) != string(w.expect) {
return 0, errors.New("p = " + strconv.Quote(string(p)) + ", want " + strconv.Quote(string(w.expect)))
}
return
}

View File

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

View File

@@ -1,169 +0,0 @@
package app_test
import (
"context"
"syscall"
"hakurei.app/container"
"hakurei.app/container/seccomp"
"hakurei.app/hst"
"hakurei.app/internal/app/state"
"hakurei.app/system"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
)
func m(pathname string) *container.Absolute { return container.MustAbs(pathname) }
func f(c hst.FilesystemConfig) hst.FilesystemConfigJSON {
return hst.FilesystemConfigJSON{FilesystemConfig: c}
}
var testCasesNixos = []sealTestCase{
{
"nixos chromium direct wayland", new(stubNixOS),
&hst.Config{
ID: "org.chromium.Chromium",
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse),
Shell: m("/run/current-system/sw/bin/zsh"),
Container: &hst.ContainerConfig{
Userns: true, HostNet: true, MapRealUID: true, Env: nil,
Filesystem: []hst.FilesystemConfigJSON{
f(&hst.FSBind{Source: m("/bin")}),
f(&hst.FSBind{Source: m("/usr/bin/")}),
f(&hst.FSBind{Source: m("/nix/store")}),
f(&hst.FSBind{Source: m("/run/current-system")}),
f(&hst.FSBind{Source: m("/sys/block"), Optional: true}),
f(&hst.FSBind{Source: m("/sys/bus"), Optional: true}),
f(&hst.FSBind{Source: m("/sys/class"), Optional: true}),
f(&hst.FSBind{Source: m("/sys/dev"), Optional: true}),
f(&hst.FSBind{Source: m("/sys/devices"), Optional: true}),
f(&hst.FSBind{Source: m("/run/opengl-driver")}),
f(&hst.FSBind{Source: m("/dev/dri"), Device: true, Optional: true}),
f(&hst.FSBind{Source: m("/etc/"), Target: m("/etc/"), Special: true}),
f(&hst.FSBind{Source: m("/var/lib/persist/module/hakurei/0/1"), Write: true, Ensure: true}),
},
},
SystemBus: &dbus.Config{
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
Filter: true,
},
SessionBus: &dbus.Config{
Talk: []string{
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
"org.kde.kwalletd5", "org.kde.kwalletd6",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{}, Broadcast: map[string]string{},
Filter: true,
},
DirectWayland: true,
Username: "u0_a1",
Home: m("/var/lib/persist/module/hakurei/0/1"),
Identity: 1, Groups: []string{},
},
state.ID{
0x8e, 0x2c, 0x76, 0xb0,
0x66, 0xda, 0xbe, 0x57,
0x4c, 0xf0, 0x73, 0xbd,
0xb4, 0x6e, 0xb5, 0xc1,
},
system.New(context.TODO(), 1000001).
Ensure("/tmp/hakurei.1971", 0711).
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
Ensure("/tmp/hakurei.1971/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/1", acl.Read, acl.Write, acl.Execute).
Ensure("/tmp/hakurei.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir", acl.Execute).
Ensure("/tmp/hakurei.1971/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir/1", acl.Read, acl.Write, acl.Execute).
Ensure("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", acl.Execute).
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
Ephemeral(system.Process, "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
Ephemeral(system.Process, "/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
MustProxyDBus("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
Talk: []string{
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
"org.kde.kwalletd5", "org.kde.kwalletd6",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{}, Broadcast: map[string]string{},
Filter: true,
}, "/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
Talk: []string{
"org.bluez",
"org.freedesktop.Avahi",
"org.freedesktop.UPower",
},
Filter: true,
}).
UpdatePerm("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
UpdatePerm("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
&container.Params{
Uid: 1971,
Gid: 100,
Dir: m("/var/lib/persist/module/hakurei/0/1"),
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
Args: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"},
Env: []string{
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1971/bus",
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
"HOME=/var/lib/persist/module/hakurei/0/1",
"PULSE_COOKIE=" + hst.Tmp + "/pulse-cookie",
"PULSE_SERVER=unix:/run/user/1971/pulse/native",
"SHELL=/run/current-system/sw/bin/zsh",
"TERM=xterm-256color",
"USER=u0_a1",
"WAYLAND_DISPLAY=wayland-0",
"XDG_RUNTIME_DIR=/run/user/1971",
"XDG_SESSION_CLASS=user",
"XDG_SESSION_TYPE=tty",
},
Ops: new(container.Ops).
Proc(m("/proc/")).
Tmpfs(hst.AbsTmp, 4096, 0755).
DevWritable(m("/dev/"), true).
Tmpfs(m("/dev/shm"), 0, 01777).
Bind(m("/bin"), m("/bin"), 0).
Bind(m("/usr/bin/"), m("/usr/bin/"), 0).
Bind(m("/nix/store"), m("/nix/store"), 0).
Bind(m("/run/current-system"), m("/run/current-system"), 0).
Bind(m("/sys/block"), m("/sys/block"), container.BindOptional).
Bind(m("/sys/bus"), m("/sys/bus"), container.BindOptional).
Bind(m("/sys/class"), m("/sys/class"), container.BindOptional).
Bind(m("/sys/dev"), m("/sys/dev"), container.BindOptional).
Bind(m("/sys/devices"), m("/sys/devices"), container.BindOptional).
Bind(m("/run/opengl-driver"), m("/run/opengl-driver"), 0).
Bind(m("/dev/dri"), m("/dev/dri"), container.BindDevice|container.BindWritable|container.BindOptional).
Etc(m("/etc/"), "8e2c76b066dabe574cf073bdb46eb5c1").
Bind(m("/var/lib/persist/module/hakurei/0/1"), m("/var/lib/persist/module/hakurei/0/1"), container.BindWritable|container.BindEnsure).
Remount(m("/dev/"), syscall.MS_RDONLY).
Tmpfs(m("/run/user/"), 4096, 0755).
Bind(m("/tmp/hakurei.1971/runtime/1"), m("/run/user/1971"), container.BindWritable).
Bind(m("/tmp/hakurei.1971/tmpdir/1"), m("/tmp/"), container.BindWritable).
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 0).
Place(m(hst.Tmp+"/pulse-cookie"), nil).
Bind(m("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
Bind(m("/tmp/hakurei.1971/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
Remount(m("/"), syscall.MS_RDONLY),
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
HostNet: true,
ForwardCancel: true,
},
},
}

View File

@@ -1,213 +0,0 @@
package app_test
import (
"context"
"os"
"syscall"
"hakurei.app/container"
"hakurei.app/container/seccomp"
"hakurei.app/hst"
"hakurei.app/internal/app/state"
"hakurei.app/system"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
)
var testCasesPd = []sealTestCase{
{
"nixos permissive defaults no enablements", new(stubNixOS),
&hst.Config{Username: "chronos", Home: m("/home/chronos")},
state.ID{
0x4a, 0x45, 0x0b, 0x65,
0x96, 0xd7, 0xbc, 0x15,
0xbd, 0x01, 0x78, 0x0e,
0xb9, 0xa6, 0x07, 0xac,
},
system.New(context.TODO(), 1000000).
Ensure("/tmp/hakurei.1971", 0711).
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
Ensure("/tmp/hakurei.1971/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/0", acl.Read, acl.Write, acl.Execute).
Ensure("/tmp/hakurei.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir", acl.Execute).
Ensure("/tmp/hakurei.1971/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir/0", acl.Read, acl.Write, acl.Execute),
&container.Params{
Dir: m("/home/chronos"),
Path: m("/run/current-system/sw/bin/zsh"),
Args: []string{"/run/current-system/sw/bin/zsh"},
Env: []string{
"HOME=/home/chronos",
"SHELL=/run/current-system/sw/bin/zsh",
"TERM=xterm-256color",
"USER=chronos",
"XDG_RUNTIME_DIR=/run/user/65534",
"XDG_SESSION_CLASS=user",
"XDG_SESSION_TYPE=tty",
},
Ops: new(container.Ops).
Root(m("/"), container.BindWritable).
Proc(m("/proc/")).
Tmpfs(hst.AbsTmp, 4096, 0755).
DevWritable(m("/dev/"), true).
Tmpfs(m("/dev/shm"), 0, 01777).
Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional).
Readonly(m("/var/run/nscd"), 0755).
Etc(m("/etc/"), "4a450b6596d7bc15bd01780eb9a607ac").
Tmpfs(m("/run/user/1971"), 8192, 0755).
Tmpfs(m("/run/dbus"), 8192, 0755).
Remount(m("/dev/"), syscall.MS_RDONLY).
Tmpfs(m("/run/user/"), 4096, 0755).
Bind(m("/tmp/hakurei.1971/runtime/0"), m("/run/user/65534"), container.BindWritable).
Bind(m("/tmp/hakurei.1971/tmpdir/0"), m("/tmp/"), container.BindWritable).
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
Remount(m("/"), syscall.MS_RDONLY),
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
HostNet: true,
HostAbstract: true,
RetainSession: true,
ForwardCancel: true,
},
},
{
"nixos permissive defaults chromium", new(stubNixOS),
&hst.Config{
ID: "org.chromium.Chromium",
Args: []string{"zsh", "-c", "exec chromium "},
Identity: 9,
Groups: []string{"video"},
Username: "chronos",
Home: m("/home/chronos"),
SessionBus: &dbus.Config{
Talk: []string{
"org.freedesktop.Notifications",
"org.freedesktop.FileManager1",
"org.freedesktop.ScreenSaver",
"org.freedesktop.secrets",
"org.kde.kwalletd5",
"org.kde.kwalletd6",
"org.gnome.SessionManager",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{
"org.freedesktop.portal.*": "*",
},
Broadcast: map[string]string{
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
},
Filter: true,
},
SystemBus: &dbus.Config{
Talk: []string{
"org.bluez",
"org.freedesktop.Avahi",
"org.freedesktop.UPower",
},
Filter: true,
},
Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse),
},
state.ID{
0xeb, 0xf0, 0x83, 0xd1,
0xb1, 0x75, 0x91, 0x17,
0x82, 0xd4, 0x13, 0x36,
0x9b, 0x64, 0xce, 0x7c,
},
system.New(context.TODO(), 1000009).
Ensure("/tmp/hakurei.1971", 0711).
Ensure("/tmp/hakurei.1971/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime", acl.Execute).
Ensure("/tmp/hakurei.1971/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/runtime/9", acl.Read, acl.Write, acl.Execute).
Ensure("/tmp/hakurei.1971/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir", acl.Execute).
Ensure("/tmp/hakurei.1971/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/hakurei.1971/tmpdir/9", acl.Read, acl.Write, acl.Execute).
Ephemeral(system.Process, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c", 0711).
Wayland(new(*os.File), "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
Ensure("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", acl.Execute).
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
Ephemeral(system.Process, "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c", acl.Execute).
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse").
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
MustProxyDBus("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
Talk: []string{
"org.freedesktop.Notifications",
"org.freedesktop.FileManager1",
"org.freedesktop.ScreenSaver",
"org.freedesktop.secrets",
"org.kde.kwalletd5",
"org.kde.kwalletd6",
"org.gnome.SessionManager",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{
"org.freedesktop.portal.*": "*",
},
Broadcast: map[string]string{
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
},
Filter: true,
}, "/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
Talk: []string{
"org.bluez",
"org.freedesktop.Avahi",
"org.freedesktop.UPower",
},
Filter: true,
}).
UpdatePerm("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
UpdatePerm("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
&container.Params{
Dir: m("/home/chronos"),
Path: m("/run/current-system/sw/bin/zsh"),
Args: []string{"zsh", "-c", "exec chromium "},
Env: []string{
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
"HOME=/home/chronos",
"PULSE_COOKIE=" + hst.Tmp + "/pulse-cookie",
"PULSE_SERVER=unix:/run/user/65534/pulse/native",
"SHELL=/run/current-system/sw/bin/zsh",
"TERM=xterm-256color",
"USER=chronos",
"WAYLAND_DISPLAY=wayland-0",
"XDG_RUNTIME_DIR=/run/user/65534",
"XDG_SESSION_CLASS=user",
"XDG_SESSION_TYPE=tty",
},
Ops: new(container.Ops).
Root(m("/"), container.BindWritable).
Proc(m("/proc/")).
Tmpfs(hst.AbsTmp, 4096, 0755).
DevWritable(m("/dev/"), true).
Tmpfs(m("/dev/shm"), 0, 01777).
Bind(m("/dev/dri"), m("/dev/dri"), container.BindWritable|container.BindDevice|container.BindOptional).
Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional).
Readonly(m("/var/run/nscd"), 0755).
Etc(m("/etc/"), "ebf083d1b175911782d413369b64ce7c").
Tmpfs(m("/run/user/1971"), 8192, 0755).
Tmpfs(m("/run/dbus"), 8192, 0755).
Remount(m("/dev/"), syscall.MS_RDONLY).
Tmpfs(m("/run/user/"), 4096, 0755).
Bind(m("/tmp/hakurei.1971/runtime/9"), m("/run/user/65534"), container.BindWritable).
Bind(m("/tmp/hakurei.1971/tmpdir/9"), m("/tmp/"), container.BindWritable).
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
Bind(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/65534/wayland-0"), 0).
Bind(m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse"), m("/run/user/65534/pulse/native"), 0).
Place(m(hst.Tmp+"/pulse-cookie"), nil).
Bind(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
Bind(m("/tmp/hakurei.1971/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
Remount(m("/"), syscall.MS_RDONLY),
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
HostNet: true,
HostAbstract: true,
RetainSession: true,
ForwardCancel: true,
},
},
}

View File

@@ -1,34 +1,24 @@
package app_test package app
import ( import (
"fmt" "fmt"
"io/fs" "io/fs"
"log" "log"
"os/exec"
"os/user" "os/user"
"strconv"
"hakurei.app/hst"
) )
// fs methods are not implemented using a real FS
// to help better understand filesystem access behaviour
type stubNixOS struct { type stubNixOS struct {
lookPathErr map[string]error lookPathErr map[string]error
usernameErr map[string]error usernameErr map[string]error
} }
func (s *stubNixOS) Getuid() int { return 1971 } func (k *stubNixOS) new(func(k syscallDispatcher)) { panic("not implemented") }
func (s *stubNixOS) Getgid() int { return 100 }
func (s *stubNixOS) TempDir() string { return "/tmp" }
func (s *stubNixOS) MustExecutable() string { return "/run/wrappers/bin/hakurei" }
func (s *stubNixOS) Exit(code int) { panic("called exit on stub with code " + strconv.Itoa(code)) }
func (s *stubNixOS) EvalSymlinks(path string) (string, error) { return path, nil }
func (s *stubNixOS) Uid(aid int) (int, error) { return 1000000 + 0*10000 + aid, nil }
func (s *stubNixOS) Println(v ...any) { log.Println(v...) } func (k *stubNixOS) getuid() int { return 1971 }
func (s *stubNixOS) Printf(format string, v ...any) { log.Printf(format, v...) } func (k *stubNixOS) getgid() int { return 100 }
func (s *stubNixOS) LookupEnv(key string) (string, bool) { func (k *stubNixOS) lookupEnv(key string) (string, bool) {
switch key { switch key {
case "SHELL": case "SHELL":
return "/run/current-system/sw/bin/zsh", true return "/run/current-system/sw/bin/zsh", true
@@ -40,6 +30,8 @@ func (s *stubNixOS) LookupEnv(key string) (string, bool) {
return "", false return "", false
case "HOME": case "HOME":
return "/home/ophestra", true return "/home/ophestra", true
case "XDG_RUNTIME_DIR":
return "/run/user/1971", true
case "XDG_CONFIG_HOME": case "XDG_CONFIG_HOME":
return "/home/ophestra/xdg/config", true return "/home/ophestra/xdg/config", true
default: default:
@@ -47,61 +39,7 @@ func (s *stubNixOS) LookupEnv(key string) (string, bool) {
} }
} }
func (s *stubNixOS) LookPath(file string) (string, error) { func (k *stubNixOS) stat(name string) (fs.FileInfo, error) {
if s.lookPathErr != nil {
if err, ok := s.lookPathErr[file]; ok {
return "", err
}
}
switch file {
case "zsh":
return "/run/current-system/sw/bin/zsh", nil
default:
panic(fmt.Sprintf("attempted to look up unexpected executable %q", file))
}
}
func (s *stubNixOS) LookupGroup(name string) (*user.Group, error) {
switch name {
case "video":
return &user.Group{Gid: "26", Name: "video"}, nil
default:
return nil, user.UnknownGroupError(name)
}
}
func (s *stubNixOS) ReadDir(name string) ([]fs.DirEntry, error) {
switch name {
case "/":
return stubDirEntries("bin", "boot", "dev", "etc", "home", "lib",
"lib64", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var")
case "/run":
return stubDirEntries("agetty.reload", "binfmt", "booted-system",
"credentials", "cryptsetup", "current-system", "dbus", "host", "keys",
"libvirt", "libvirtd.pid", "lock", "log", "lvm", "mount", "NetworkManager",
"nginx", "nixos", "nscd", "opengl-driver", "pppd", "resolvconf", "sddm",
"store", "syncoid", "system", "systemd", "tmpfiles.d", "udev", "udisks2",
"user", "utmp", "virtlogd.pid", "wrappers", "zed.pid", "zed.state")
case "/etc":
return stubDirEntries("alsa", "bashrc", "binfmt.d", "dbus-1", "default",
"ethertypes", "fonts", "fstab", "fuse.conf", "group", "host.conf", "hostid",
"hostname", "hostname.CHECKSUM", "hosts", "inputrc", "ipsec.d", "issue", "kbd",
"libblockdev", "locale.conf", "localtime", "login.defs", "lsb-release", "lvm",
"machine-id", "man_db.conf", "modprobe.d", "modules-load.d", "mtab", "nanorc",
"netgroup", "NetworkManager", "nix", "nixos", "NIXOS", "nscd.conf", "nsswitch.conf",
"opensnitchd", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1",
"profile", "protocols", "qemu", "resolv.conf", "resolvconf.conf", "rpc", "samba",
"sddm.conf", "secureboot", "services", "set-environment", "shadow", "shells", "ssh",
"ssl", "static", "subgid", "subuid", "sudoers", "sysctl.d", "systemd", "terminfo",
"tmpfiles.d", "udev", "udisks2", "UPower", "vconsole.conf", "X11", "zfs", "zinputrc",
"zoneinfo", "zprofile", "zshenv", "zshrc")
default:
panic(fmt.Sprintf("attempted to read unexpected directory %q", name))
}
}
func (s *stubNixOS) Stat(name string) (fs.FileInfo, error) {
switch name { switch name {
case "/var/run/nscd": case "/var/run/nscd":
return nil, nil return nil, nil
@@ -118,17 +56,144 @@ func (s *stubNixOS) Stat(name string) (fs.FileInfo, error) {
} }
} }
func (s *stubNixOS) Open(name string) (fs.File, error) { func (k *stubNixOS) readdir(name string) ([]fs.DirEntry, error) {
switch name { switch name {
case "/":
return stubDirEntries("bin", "boot", "dev", "etc", "home", "lib",
"lib64", "nix", "proc", "root", "run", "srv", "sys", "tmp", "usr", "var")
case "/run":
return stubDirEntries("agetty.reload", "binfmt", "booted-system",
"credentials", "cryptsetup", "current-system", "dbus", "host", "keys",
"libvirt", "libvirtd.pid", "lock", "log", "lvm", "mount", "NetworkManager",
"nginx", "nixos", "nscd", "opengl-driver", "pppd", "resolvconf", "sddm",
"store", "syncoid", "system", "systemd", "tmpfiles.d", "udev", "udisks2",
"user", "utmp", "virtlogd.pid", "wrappers", "zed.pid", "zed.state")
case "/etc":
return stubDirEntries("alsa", "bashrc", "binfmt.d", "dbus-1", "default",
"ethertypes", "fonts", "fstab", "fuse.conf", "group", "host.conf", "hostid",
"hostname", "hostname.CHECKSUM", "hosts", "inputrc", "ipsec.d", "issue", "kbd",
"libblockdev", "locale.conf", "localtime", "login.defs", "lsb-release", "lvm",
"machine-id", "man_db.conf", "modprobe.d", "modules-load.d", "mtab", "nanorc",
"netgroup", "NetworkManager", "nix", "nixos", "NIXOS", "nscd.conf", "nsswitch.conf",
"opensnitchd", "os-release", "pam", "pam.d", "passwd", "pipewire", "pki", "polkit-1",
"profile", "protocols", "qemu", "resolv.conf", "resolvconf.conf", "rpc", "samba",
"sddm.conf", "secureboot", "services", "set-environment", "shadow", "shells", "ssh",
"ssl", "static", "subgid", "subuid", "sudoers", "sysctl.d", "systemd", "terminfo",
"tmpfiles.d", "udev", "udisks2", "UPower", "vconsole.conf", "X11", "zfs", "zinputrc",
"zoneinfo", "zprofile", "zshenv", "zshrc")
default: default:
panic(fmt.Sprintf("attempted to open unexpected file %q", name)) panic(fmt.Sprintf("attempted to read unexpected directory %q", name))
} }
} }
func (s *stubNixOS) Paths() hst.Paths { func (k *stubNixOS) tempdir() string { return "/tmp/" }
return hst.Paths{
SharePath: m("/tmp/hakurei.1971"), func (k *stubNixOS) evalSymlinks(path string) (string, error) {
RuntimePath: m("/run/user/1971"), switch path {
RunDirPath: m("/run/user/1971/hakurei"), case "/run/user/1971":
return "/run/user/1971", nil
case "/tmp/hakurei.0":
return "/tmp/hakurei.0", nil
case "/run/dbus":
return "/run/dbus", nil
case "/dev/kvm":
return "/dev/kvm", nil
case "/etc/":
return "/etc/", nil
case "/bin":
return "/bin", nil
case "/boot":
return "/boot", nil
case "/home":
return "/home", nil
case "/lib":
return "/lib", nil
case "/lib64":
return "/lib64", nil
case "/nix":
return "/nix", nil
case "/root":
return "/root", nil
case "/run":
return "/run", nil
case "/srv":
return "/srv", nil
case "/sys":
return "/sys", nil
case "/usr":
return "/usr", nil
case "/var":
return "/var", nil
case "/dev/dri":
return "/dev/dri", nil
case "/usr/bin/":
return "/usr/bin/", nil
case "/nix/store":
return "/nix/store", nil
case "/run/current-system":
return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-nixos-system-satori-25.05.99999999.aaaaaaa", nil
case "/sys/block":
return "/sys/block", nil
case "/sys/bus":
return "/sys/bus", nil
case "/sys/class":
return "/sys/class", nil
case "/sys/dev":
return "/sys/dev", nil
case "/sys/devices":
return "/sys/devices", nil
case "/run/opengl-driver":
return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-graphics-drivers", nil
case "/var/lib/persist/module/hakurei/0/1":
return "/var/lib/persist/module/hakurei/0/1", nil
default:
panic(fmt.Sprintf("attempted to evaluate unexpected path %q", path))
} }
} }
func (k *stubNixOS) lookPath(file string) (string, error) {
if k.lookPathErr != nil {
if err, ok := k.lookPathErr[file]; ok {
return "", err
}
}
switch file {
case "zsh":
return "/run/current-system/sw/bin/zsh", nil
default:
panic(fmt.Sprintf("attempted to look up unexpected executable %q", file))
}
}
func (k *stubNixOS) lookupGroupId(name string) (string, error) {
switch name {
case "video":
return "26", nil
default:
return "", user.UnknownGroupError(name)
}
}
func (k *stubNixOS) cmdOutput(cmd *exec.Cmd) ([]byte, error) {
switch cmd.Path {
case "/proc/nonexistent/hsu":
return []byte{'0'}, nil
default:
panic(fmt.Sprintf("unexpected cmd %#v", cmd))
}
}
func (k *stubNixOS) overflowUid() int { return 65534 }
func (k *stubNixOS) overflowGid() int { return 65534 }
func (k *stubNixOS) mustHsuPath() string { return "/proc/nonexistent/hsu" }
func (k *stubNixOS) fatalf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) }
func (k *stubNixOS) isVerbose() bool { return true }
func (k *stubNixOS) verbose(v ...any) { log.Print(v...) }
func (k *stubNixOS) verbosef(format string, v ...any) { log.Printf(format, v...) }

View File

@@ -1,36 +1,383 @@
package app_test package app
import ( import (
"context"
"encoding/json" "encoding/json"
"io/fs" "io/fs"
"os"
"reflect" "reflect"
"syscall"
"testing" "testing"
"time" "time"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/seccomp"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/internal/sys"
"hakurei.app/system" "hakurei.app/system"
"hakurei.app/system/acl"
"hakurei.app/system/dbus"
) )
type sealTestCase struct { func TestApp(t *testing.T) {
testCases := []struct {
name string name string
os sys.State k syscallDispatcher
config *hst.Config config *hst.Config
id state.ID id state.ID
wantSys *system.I wantSys *system.I
wantParams *container.Params wantParams *container.Params
} }{
{
"nixos permissive defaults no enablements", new(stubNixOS),
&hst.Config{Username: "chronos", Home: m("/home/chronos")},
state.ID{
0x4a, 0x45, 0x0b, 0x65,
0x96, 0xd7, 0xbc, 0x15,
0xbd, 0x01, 0x78, 0x0e,
0xb9, 0xa6, 0x07, 0xac,
},
system.New(context.TODO(), 1000000).
Ensure("/tmp/hakurei.0", 0711).
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
Ensure("/tmp/hakurei.0/runtime/0", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/0", acl.Read, acl.Write, acl.Execute).
Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
Ensure("/tmp/hakurei.0/tmpdir/0", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/0", acl.Read, acl.Write, acl.Execute),
&container.Params{
Dir: m("/home/chronos"),
Path: m("/run/current-system/sw/bin/zsh"),
Args: []string{"/run/current-system/sw/bin/zsh"},
Env: []string{
"HOME=/home/chronos",
"SHELL=/run/current-system/sw/bin/zsh",
"TERM=xterm-256color",
"USER=chronos",
"XDG_RUNTIME_DIR=/run/user/65534",
"XDG_SESSION_CLASS=user",
"XDG_SESSION_TYPE=tty",
},
Ops: new(container.Ops).
Root(m("/"), container.BindWritable).
Proc(m("/proc/")).
Tmpfs(hst.AbsTmp, 4096, 0755).
DevWritable(m("/dev/"), true).
Tmpfs(m("/dev/shm"), 0, 01777).
Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional).
Readonly(m("/var/run/nscd"), 0755).
Etc(m("/etc/"), "4a450b6596d7bc15bd01780eb9a607ac").
Tmpfs(m("/run/user/1971"), 8192, 0755).
Tmpfs(m("/run/dbus"), 8192, 0755).
Remount(m("/dev/"), syscall.MS_RDONLY).
Tmpfs(m("/run/user/"), 4096, 0755).
Bind(m("/tmp/hakurei.0/runtime/0"), m("/run/user/65534"), container.BindWritable).
Bind(m("/tmp/hakurei.0/tmpdir/0"), m("/tmp/"), container.BindWritable).
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
Remount(m("/"), syscall.MS_RDONLY),
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
HostNet: true,
HostAbstract: true,
RetainSession: true,
ForwardCancel: true,
},
},
{
"nixos permissive defaults chromium", new(stubNixOS),
&hst.Config{
ID: "org.chromium.Chromium",
Args: []string{"zsh", "-c", "exec chromium "},
Identity: 9,
Groups: []string{"video"},
Username: "chronos",
Home: m("/home/chronos"),
SessionBus: &dbus.Config{
Talk: []string{
"org.freedesktop.Notifications",
"org.freedesktop.FileManager1",
"org.freedesktop.ScreenSaver",
"org.freedesktop.secrets",
"org.kde.kwalletd5",
"org.kde.kwalletd6",
"org.gnome.SessionManager",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{
"org.freedesktop.portal.*": "*",
},
Broadcast: map[string]string{
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
},
Filter: true,
},
SystemBus: &dbus.Config{
Talk: []string{
"org.bluez",
"org.freedesktop.Avahi",
"org.freedesktop.UPower",
},
Filter: true,
},
Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse),
},
state.ID{
0xeb, 0xf0, 0x83, 0xd1,
0xb1, 0x75, 0x91, 0x17,
0x82, 0xd4, 0x13, 0x36,
0x9b, 0x64, 0xce, 0x7c,
},
system.New(context.TODO(), 1000009).
Ensure("/tmp/hakurei.0", 0711).
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
Ensure("/tmp/hakurei.0/runtime/9", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/9", acl.Read, acl.Write, acl.Execute).
Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
Ensure("/tmp/hakurei.0/tmpdir/9", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/9", acl.Read, acl.Write, acl.Execute).
Ephemeral(system.Process, "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c", 0711).
Wayland(new(*os.File), "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland", "/run/user/1971/wayland-0", "org.chromium.Chromium", "ebf083d1b175911782d413369b64ce7c").
Ensure("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", acl.Execute).
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
Ephemeral(system.Process, "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c", 0700).UpdatePermType(system.Process, "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c", acl.Execute).
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse").
CopyFile(new([]byte), "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
MustProxyDBus("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus", &dbus.Config{
Talk: []string{
"org.freedesktop.Notifications",
"org.freedesktop.FileManager1",
"org.freedesktop.ScreenSaver",
"org.freedesktop.secrets",
"org.kde.kwalletd5",
"org.kde.kwalletd6",
"org.gnome.SessionManager",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{
"org.freedesktop.portal.*": "*",
},
Broadcast: map[string]string{
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*",
},
Filter: true,
}, "/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket", &dbus.Config{
Talk: []string{
"org.bluez",
"org.freedesktop.Avahi",
"org.freedesktop.UPower",
},
Filter: true,
}).
UpdatePerm("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus", acl.Read, acl.Write).
UpdatePerm("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket", acl.Read, acl.Write),
&container.Params{
Dir: m("/home/chronos"),
Path: m("/run/current-system/sw/bin/zsh"),
Args: []string{"zsh", "-c", "exec chromium "},
Env: []string{
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/65534/bus",
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
"HOME=/home/chronos",
"PULSE_COOKIE=" + hst.Tmp + "/pulse-cookie",
"PULSE_SERVER=unix:/run/user/65534/pulse/native",
"SHELL=/run/current-system/sw/bin/zsh",
"TERM=xterm-256color",
"USER=chronos",
"WAYLAND_DISPLAY=wayland-0",
"XDG_RUNTIME_DIR=/run/user/65534",
"XDG_SESSION_CLASS=user",
"XDG_SESSION_TYPE=tty",
},
Ops: new(container.Ops).
Root(m("/"), container.BindWritable).
Proc(m("/proc/")).
Tmpfs(hst.AbsTmp, 4096, 0755).
DevWritable(m("/dev/"), true).
Tmpfs(m("/dev/shm"), 0, 01777).
Bind(m("/dev/dri"), m("/dev/dri"), container.BindWritable|container.BindDevice|container.BindOptional).
Bind(m("/dev/kvm"), m("/dev/kvm"), container.BindWritable|container.BindDevice|container.BindOptional).
Readonly(m("/var/run/nscd"), 0755).
Etc(m("/etc/"), "ebf083d1b175911782d413369b64ce7c").
Tmpfs(m("/run/user/1971"), 8192, 0755).
Tmpfs(m("/run/dbus"), 8192, 0755).
Remount(m("/dev/"), syscall.MS_RDONLY).
Tmpfs(m("/run/user/"), 4096, 0755).
Bind(m("/tmp/hakurei.0/runtime/9"), m("/run/user/65534"), container.BindWritable).
Bind(m("/tmp/hakurei.0/tmpdir/9"), m("/tmp/"), container.BindWritable).
Place(m("/etc/passwd"), []byte("chronos:x:65534:65534:Hakurei:/home/chronos:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:65534:\n")).
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/wayland"), m("/run/user/65534/wayland-0"), 0).
Bind(m("/run/user/1971/hakurei/ebf083d1b175911782d413369b64ce7c/pulse"), m("/run/user/65534/pulse/native"), 0).
Place(m(hst.Tmp+"/pulse-cookie"), nil).
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/bus"), m("/run/user/65534/bus"), 0).
Bind(m("/tmp/hakurei.0/ebf083d1b175911782d413369b64ce7c/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
Remount(m("/"), syscall.MS_RDONLY),
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
HostNet: true,
HostAbstract: true,
RetainSession: true,
ForwardCancel: true,
},
},
func TestApp(t *testing.T) { {
testCases := append(testCasesPd, testCasesNixos...) "nixos chromium direct wayland", new(stubNixOS),
&hst.Config{
ID: "org.chromium.Chromium",
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse),
Shell: m("/run/current-system/sw/bin/zsh"),
Container: &hst.ContainerConfig{
Userns: true, HostNet: true, MapRealUID: true, Env: nil,
Filesystem: []hst.FilesystemConfigJSON{
f(&hst.FSBind{Source: m("/bin")}),
f(&hst.FSBind{Source: m("/usr/bin/")}),
f(&hst.FSBind{Source: m("/nix/store")}),
f(&hst.FSBind{Source: m("/run/current-system")}),
f(&hst.FSBind{Source: m("/sys/block"), Optional: true}),
f(&hst.FSBind{Source: m("/sys/bus"), Optional: true}),
f(&hst.FSBind{Source: m("/sys/class"), Optional: true}),
f(&hst.FSBind{Source: m("/sys/dev"), Optional: true}),
f(&hst.FSBind{Source: m("/sys/devices"), Optional: true}),
f(&hst.FSBind{Source: m("/run/opengl-driver")}),
f(&hst.FSBind{Source: m("/dev/dri"), Device: true, Optional: true}),
f(&hst.FSBind{Source: m("/etc/"), Target: m("/etc/"), Special: true}),
f(&hst.FSBind{Source: m("/var/lib/persist/module/hakurei/0/1"), Write: true, Ensure: true}),
},
},
SystemBus: &dbus.Config{
Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"},
Filter: true,
},
SessionBus: &dbus.Config{
Talk: []string{
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
"org.kde.kwalletd5", "org.kde.kwalletd6",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{}, Broadcast: map[string]string{},
Filter: true,
},
DirectWayland: true,
Username: "u0_a1",
Home: m("/var/lib/persist/module/hakurei/0/1"),
Identity: 1, Groups: []string{},
},
state.ID{
0x8e, 0x2c, 0x76, 0xb0,
0x66, 0xda, 0xbe, 0x57,
0x4c, 0xf0, 0x73, 0xbd,
0xb4, 0x6e, 0xb5, 0xc1,
},
system.New(context.TODO(), 1000001).
Ensure("/tmp/hakurei.0", 0711).
Ensure("/tmp/hakurei.0/runtime", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime", acl.Execute).
Ensure("/tmp/hakurei.0/runtime/1", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/runtime/1", acl.Read, acl.Write, acl.Execute).
Ensure("/tmp/hakurei.0/tmpdir", 0700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir", acl.Execute).
Ensure("/tmp/hakurei.0/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/1", acl.Read, acl.Write, acl.Execute).
Ensure("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", acl.Execute).
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
Ephemeral(system.Process, "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).
Ephemeral(system.Process, "/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1", 0711).
MustProxyDBus("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus", &dbus.Config{
Talk: []string{
"org.freedesktop.FileManager1", "org.freedesktop.Notifications",
"org.freedesktop.ScreenSaver", "org.freedesktop.secrets",
"org.kde.kwalletd5", "org.kde.kwalletd6",
},
Own: []string{
"org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
"org.mpris.MediaPlayer2.chromium.*",
},
Call: map[string]string{}, Broadcast: map[string]string{},
Filter: true,
}, "/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", &dbus.Config{
Talk: []string{
"org.bluez",
"org.freedesktop.Avahi",
"org.freedesktop.UPower",
},
Filter: true,
}).
UpdatePerm("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus", acl.Read, acl.Write).
UpdatePerm("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket", acl.Read, acl.Write),
&container.Params{
Uid: 1971,
Gid: 100,
Dir: m("/var/lib/persist/module/hakurei/0/1"),
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
Args: []string{"/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"},
Env: []string{
"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1971/bus",
"DBUS_SYSTEM_BUS_ADDRESS=unix:path=/run/dbus/system_bus_socket",
"HOME=/var/lib/persist/module/hakurei/0/1",
"PULSE_COOKIE=" + hst.Tmp + "/pulse-cookie",
"PULSE_SERVER=unix:/run/user/1971/pulse/native",
"SHELL=/run/current-system/sw/bin/zsh",
"TERM=xterm-256color",
"USER=u0_a1",
"WAYLAND_DISPLAY=wayland-0",
"XDG_RUNTIME_DIR=/run/user/1971",
"XDG_SESSION_CLASS=user",
"XDG_SESSION_TYPE=tty",
},
Ops: new(container.Ops).
Proc(m("/proc/")).
Tmpfs(hst.AbsTmp, 4096, 0755).
DevWritable(m("/dev/"), true).
Tmpfs(m("/dev/shm"), 0, 01777).
Bind(m("/bin"), m("/bin"), 0).
Bind(m("/usr/bin/"), m("/usr/bin/"), 0).
Bind(m("/nix/store"), m("/nix/store"), 0).
Bind(m("/run/current-system"), m("/run/current-system"), 0).
Bind(m("/sys/block"), m("/sys/block"), container.BindOptional).
Bind(m("/sys/bus"), m("/sys/bus"), container.BindOptional).
Bind(m("/sys/class"), m("/sys/class"), container.BindOptional).
Bind(m("/sys/dev"), m("/sys/dev"), container.BindOptional).
Bind(m("/sys/devices"), m("/sys/devices"), container.BindOptional).
Bind(m("/run/opengl-driver"), m("/run/opengl-driver"), 0).
Bind(m("/dev/dri"), m("/dev/dri"), container.BindDevice|container.BindWritable|container.BindOptional).
Etc(m("/etc/"), "8e2c76b066dabe574cf073bdb46eb5c1").
Bind(m("/var/lib/persist/module/hakurei/0/1"), m("/var/lib/persist/module/hakurei/0/1"), container.BindWritable|container.BindEnsure).
Remount(m("/dev/"), syscall.MS_RDONLY).
Tmpfs(m("/run/user/"), 4096, 0755).
Bind(m("/tmp/hakurei.0/runtime/1"), m("/run/user/1971"), container.BindWritable).
Bind(m("/tmp/hakurei.0/tmpdir/1"), m("/tmp/"), container.BindWritable).
Place(m("/etc/passwd"), []byte("u0_a1:x:1971:100:Hakurei:/var/lib/persist/module/hakurei/0/1:/run/current-system/sw/bin/zsh\n")).
Place(m("/etc/group"), []byte("hakurei:x:100:\n")).
Bind(m("/run/user/1971/wayland-0"), m("/run/user/1971/wayland-0"), 0).
Bind(m("/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"), m("/run/user/1971/pulse/native"), 0).
Place(m(hst.Tmp+"/pulse-cookie"), nil).
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/bus"), m("/run/user/1971/bus"), 0).
Bind(m("/tmp/hakurei.0/8e2c76b066dabe574cf073bdb46eb5c1/system_bus_socket"), m("/run/dbus/system_bus_socket"), 0).
Remount(m("/"), syscall.MS_RDONLY),
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
HostNet: true,
ForwardCancel: true,
},
},
}
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Run("finalise", func(t *testing.T) { t.Run("finalise", func(t *testing.T) {
sys, params, err := app.FinaliseIParams(t.Context(), tc.os, tc.config, &tc.id) seal := outcome{syscallDispatcher: tc.k, id: &stringPair[state.ID]{tc.id, tc.id.String()}}
err := seal.finalise(t.Context(), tc.config)
if err != nil { if err != nil {
if s, ok := container.GetErrorMessage(err); !ok { if s, ok := container.GetErrorMessage(err); !ok {
t.Fatalf("Seal: error = %v", err) t.Fatalf("Seal: error = %v", err)
@@ -40,14 +387,14 @@ func TestApp(t *testing.T) {
} }
t.Run("sys", func(t *testing.T) { t.Run("sys", func(t *testing.T) {
if !sys.Equal(tc.wantSys) { if !seal.sys.Equal(tc.wantSys) {
t.Errorf("Seal: sys = %#v, want %#v", sys, tc.wantSys) t.Errorf("Seal: sys = %#v, want %#v", seal.sys, tc.wantSys)
} }
}) })
t.Run("params", func(t *testing.T) { t.Run("params", func(t *testing.T) {
if !reflect.DeepEqual(params, tc.wantParams) { if !reflect.DeepEqual(seal.container, tc.wantParams) {
t.Errorf("seal: params =\n%s\n, want\n%s", mustMarshal(params), mustMarshal(tc.wantParams)) t.Errorf("seal: container =\n%s\n, want\n%s", mustMarshal(seal.container), mustMarshal(tc.wantParams))
} }
}) })
}) })
@@ -95,3 +442,11 @@ func (s stubFileInfoIsDir) Mode() fs.FileMode { panic("attempted to call Mode")
func (s stubFileInfoIsDir) ModTime() time.Time { panic("attempted to call ModTime") } func (s stubFileInfoIsDir) ModTime() time.Time { panic("attempted to call ModTime") }
func (s stubFileInfoIsDir) IsDir() bool { return bool(s) } func (s stubFileInfoIsDir) IsDir() bool { return bool(s) }
func (s stubFileInfoIsDir) Sys() any { panic("attempted to call Sys") } func (s stubFileInfoIsDir) Sys() any { panic("attempted to call Sys") }
func m(pathname string) *container.Absolute {
return container.MustAbs(pathname)
}
func f(c hst.FilesystemConfig) hst.FilesystemConfigJSON {
return hst.FilesystemConfigJSON{FilesystemConfig: c}
}

View File

@@ -11,7 +11,6 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/sys"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
) )
@@ -20,7 +19,13 @@ const preallocateOpsCount = 1 << 5
// newContainer initialises [container.Params] via [hst.ContainerConfig]. // newContainer initialises [container.Params] via [hst.ContainerConfig].
// Note that remaining container setup must be queued by the caller. // Note that remaining container setup must be queued by the caller.
func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid *int) (*container.Params, map[string]string, error) { func newContainer(
k syscallDispatcher,
s *hst.ContainerConfig,
prefix string,
sc *hst.Paths,
uid, gid *int,
) (*container.Params, map[string]string, error) {
if s == nil { if s == nil {
return nil, nil, newWithMessage("invalid container configuration") return nil, nil, newWithMessage("invalid container configuration")
} }
@@ -38,9 +43,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
ForwardCancel: s.WaitDelay >= 0, ForwardCancel: s.WaitDelay >= 0,
} }
as := &hst.ApplyState{ as := &hst.ApplyState{AutoEtcPrefix: prefix}
AutoEtcPrefix: prefix,
}
{ {
ops := make(container.Ops, 0, preallocateOpsCount+len(s.Filesystem)) ops := make(container.Ops, 0, preallocateOpsCount+len(s.Filesystem))
params.Ops = &ops params.Ops = &ops
@@ -65,13 +68,13 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
} }
if s.MapRealUID { if s.MapRealUID {
params.Uid = os.Getuid() params.Uid = k.getuid()
*uid = params.Uid *uid = params.Uid
params.Gid = os.Getgid() params.Gid = k.getgid()
*gid = params.Gid *gid = params.Gid
} else { } else {
*uid = container.OverflowUid() *uid = k.overflowUid()
*gid = container.OverflowGid() *gid = k.overflowGid()
} }
filesystem := s.Filesystem filesystem := s.Filesystem
@@ -107,7 +110,6 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
to warn about issues in custom configuration; it is NOT a security feature to warn about issues in custom configuration; it is NOT a security feature
and should not be treated as such, ALWAYS be careful with what you bind */ and should not be treated as such, ALWAYS be careful with what you bind */
var hidePaths []string var hidePaths []string
sc := os.Paths()
hidePaths = append(hidePaths, sc.RuntimePath.String(), sc.SharePath.String()) hidePaths = append(hidePaths, sc.RuntimePath.String(), sc.SharePath.String())
_, systemBusAddr := dbus.Address() _, systemBusAddr := dbus.Address()
if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil { if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
@@ -124,11 +126,11 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
// get parent dir of socket // get parent dir of socket
dir := path.Dir(pair[1]) dir := path.Dir(pair[1])
if dir == "." || dir == container.FHSRoot { if dir == "." || dir == container.FHSRoot {
os.Printf("dbus socket %q is in an unusual location", pair[1]) k.verbosef("dbus socket %q is in an unusual location", pair[1])
} }
hidePaths = append(hidePaths, dir) hidePaths = append(hidePaths, dir)
} else { } else {
os.Printf("dbus socket %q is not absolute", pair[1]) k.verbosef("dbus socket %q is not absolute", pair[1])
} }
} }
} }
@@ -136,7 +138,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
} }
hidePathMatch := make([]bool, len(hidePaths)) hidePathMatch := make([]bool, len(hidePaths))
for i := range hidePaths { for i := range hidePaths {
if err := evalSymlinks(os, &hidePaths[i]); err != nil { if err := evalSymlinks(k, &hidePaths[i]); err != nil {
return nil, nil, err return nil, nil, err
} }
} }
@@ -155,7 +157,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
// AutoRootOp is a collection of many BindMountOp internally // AutoRootOp is a collection of many BindMountOp internally
var autoRootEntries []fs.DirEntry var autoRootEntries []fs.DirEntry
if autoroot != nil { if autoroot != nil {
if d, err := os.ReadDir(autoroot.Source.String()); err != nil { if d, err := k.readdir(autoroot.Source.String()); err != nil {
return nil, nil, err return nil, nil, err
} else { } else {
// autoroot counter // autoroot counter
@@ -191,7 +193,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
} }
hidePathSourceEval[i] = [2]string{a.String(), a.String()} hidePathSourceEval[i] = [2]string{a.String(), a.String()}
if err := evalSymlinks(os, &hidePathSourceEval[i][0]); err != nil { if err := evalSymlinks(k, &hidePathSourceEval[i][0]); err != nil {
return nil, nil, err return nil, nil, err
} }
} }
@@ -207,7 +209,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
return nil, nil, err return nil, nil, err
} else if ok { } else if ok {
hidePathMatch[i] = true hidePathMatch[i] = true
os.Printf("hiding path %q from %q", hidePaths[i], p[1]) k.verbosef("hiding path %q from %q", hidePaths[i], p[1])
} }
} }
} }
@@ -238,12 +240,13 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
return params, maps.Clone(s.Env), nil return params, maps.Clone(s.Env), nil
} }
func evalSymlinks(os sys.State, v *string) error { // evalSymlinks calls syscallDispatcher.evalSymlinks but discards errors unwrapping to [fs.ErrNotExist].
if p, err := os.EvalSymlinks(*v); err != nil { func evalSymlinks(k syscallDispatcher, v *string) error {
if p, err := k.evalSymlinks(*v); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return err return err
} }
os.Printf("path %q does not yet exist", *v) k.verbosef("path %q does not yet exist", *v)
} else { } else {
*v = p *v = p
} }

View File

@@ -0,0 +1,99 @@
package app
import (
"log"
"os"
"os/exec"
"os/user"
"path/filepath"
"hakurei.app/container"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
)
// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour.
type syscallDispatcher interface {
// new starts a goroutine with a new instance of syscallDispatcher.
// A syscallDispatcher must never be used in any goroutine other than the one owning it,
// just synchronising access is not enough, as this is for test instrumentation.
new(f func(k syscallDispatcher))
// getuid provides [os.Getuid].
getuid() int
// getgid provides [os.Getgid].
getgid() int
// lookupEnv provides [os.LookupEnv].
lookupEnv(key string) (string, bool)
// stat provides [os.Stat].
stat(name string) (os.FileInfo, error)
// readdir provides [os.ReadDir].
readdir(name string) ([]os.DirEntry, error)
// tempdir provides [os.TempDir].
tempdir() string
// evalSymlinks provides [filepath.EvalSymlinks].
evalSymlinks(path string) (string, error)
// lookPath provides exec.LookPath.
lookPath(file string) (string, error)
// lookupGroupId calls [user.LookupGroup] and returns the Gid field of the resulting [user.Group] struct.
lookupGroupId(name string) (string, error)
// cmdOutput provides the Output method of [exec.Cmd].
cmdOutput(cmd *exec.Cmd) ([]byte, error)
// overflowUid provides [container.OverflowUid].
overflowUid() int
// overflowGid provides [container.OverflowGid].
overflowGid() int
// mustHsuPath provides [internal.MustHsuPath].
mustHsuPath() string
// fatalf provides [log.Fatalf].
fatalf(format string, v ...any)
isVerbose() bool
verbose(v ...any)
verbosef(format string, v ...any)
}
// direct implements syscallDispatcher on the current kernel.
type direct struct{}
func (k direct) new(f func(k syscallDispatcher)) { go f(k) }
func (direct) getuid() int { return os.Getuid() }
func (direct) getgid() int { return os.Getgid() }
func (direct) lookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
func (direct) stat(name string) (os.FileInfo, error) { return os.Stat(name) }
func (direct) readdir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
func (direct) tempdir() string { return os.TempDir() }
func (direct) evalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
func (direct) lookPath(file string) (string, error) { return exec.LookPath(file) }
func (direct) lookupGroupId(name string) (gid string, err error) {
var group *user.Group
group, err = user.LookupGroup(name)
if group != nil {
gid = group.Gid
}
return
}
func (direct) cmdOutput(cmd *exec.Cmd) ([]byte, error) { return cmd.Output() }
func (direct) overflowUid() int { return container.OverflowUid() }
func (direct) overflowGid() int { return container.OverflowGid() }
func (direct) mustHsuPath() string { return internal.MustHsuPath() }
func (direct) fatalf(format string, v ...any) { log.Fatalf(format, v...) }
func (k direct) isVerbose() bool { return hlog.Load() }
func (direct) verbose(v ...any) { hlog.Verbose(v...) }
func (direct) verbosef(format string, v ...any) { hlog.Verbosef(format, v...) }

View File

@@ -1,16 +0,0 @@
package app
import (
"context"
"hakurei.app/container"
"hakurei.app/hst"
"hakurei.app/internal/app/state"
"hakurei.app/internal/sys"
"hakurei.app/system"
)
func FinaliseIParams(ctx context.Context, k sys.State, config *hst.Config, id *state.ID) (*system.I, *container.Params, error) {
seal := outcome{id: &stringPair[state.ID]{*id, id.String()}}
return seal.sys, seal.container, seal.finalise(ctx, k, config)
}

View File

@@ -21,7 +21,6 @@ import (
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/internal/hlog" "hakurei.app/internal/hlog"
"hakurei.app/internal/sys"
"hakurei.app/system" "hakurei.app/system"
"hakurei.app/system/acl" "hakurei.app/system/acl"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
@@ -54,8 +53,9 @@ type outcome struct {
container *container.Params container *container.Params
env map[string]string env map[string]string
sync *os.File sync *os.File
active atomic.Bool
f atomic.Bool syscallDispatcher
} }
// shareHost holds optional share directory state that must not be accessed directly // shareHost holds optional share directory state that must not be accessed directly
@@ -120,7 +120,7 @@ type hsuUser struct {
username string username string
} }
func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Config) error { func (k *outcome) finalise(ctx context.Context, config *hst.Config) error {
const ( const (
home = "HOME" home = "HOME"
shell = "SHELL" shell = "SHELL"
@@ -144,14 +144,13 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
// unreachable // unreachable
panic("invalid call to finalise") panic("invalid call to finalise")
} }
if seal.ctx != nil { if k.ctx != nil {
// unreachable // unreachable
panic("attempting to finalise twice") panic("attempting to finalise twice")
} }
seal.ctx = ctx k.ctx = ctx
if config == nil { if config == nil {
// unreachable
return newWithMessage("invalid configuration") return newWithMessage("invalid configuration")
} }
if config.Home == nil { if config.Home == nil {
@@ -164,7 +163,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
if err := gob.NewEncoder(ct).Encode(config); err != nil { if err := gob.NewEncoder(ct).Encode(config); err != nil {
return &hst.AppError{Step: "encode initial config", Err: err} return &hst.AppError{Step: "encode initial config", Err: err}
} }
seal.ct = ct k.ct = ct
} }
// allowed identity range 0 to 9999, this is checked again in hsu // allowed identity range 0 to 9999, this is checked again in hsu
@@ -172,22 +171,23 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
return newWithMessage(fmt.Sprintf("identity %d out of range", config.Identity)) return newWithMessage(fmt.Sprintf("identity %d out of range", config.Identity))
} }
seal.user = hsuUser{ k.user = hsuUser{
identity: newInt(config.Identity), identity: newInt(config.Identity),
home: config.Home, home: config.Home,
username: config.Username, username: config.Username,
} }
if seal.user.username == "" { hsu := Hsu{k: k}
seal.user.username = "chronos" if k.user.username == "" {
} else if !isValidUsername(seal.user.username) { k.user.username = "chronos"
return newWithMessage(fmt.Sprintf("invalid user name %q", seal.user.username)) } else if !isValidUsername(k.user.username) {
return newWithMessage(fmt.Sprintf("invalid user name %q", k.user.username))
} }
seal.user.uid = newInt(sys.MustUid(k, seal.user.identity.unwrap())) k.user.uid = newInt(HsuUid(hsu.MustID(), k.user.identity.unwrap()))
seal.user.supp = make([]string, len(config.Groups)) k.user.supp = make([]string, len(config.Groups))
for i, name := range config.Groups { for i, name := range config.Groups {
if g, err := k.LookupGroup(name); err != nil { if gid, err := k.lookupGroupId(name); err != nil {
var unknownGroupError user.UnknownGroupError var unknownGroupError user.UnknownGroupError
if errors.As(err, &unknownGroupError) { if errors.As(err, &unknownGroupError) {
return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError) return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError)
@@ -195,7 +195,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
return &hst.AppError{Step: "look up group by name", Err: err} return &hst.AppError{Step: "look up group by name", Err: err}
} }
} else { } else {
seal.user.supp[i] = g.Gid k.user.supp[i] = gid
} }
} }
@@ -205,7 +205,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
if config.Shell == nil { if config.Shell == nil {
config.Shell = container.AbsFHSRoot.Append("bin", "sh") config.Shell = container.AbsFHSRoot.Append("bin", "sh")
s, _ := k.LookupEnv(shell) s, _ := k.lookupEnv(shell)
if a, err := container.NewAbs(s); err == nil { if a, err := container.NewAbs(s); err == nil {
config.Shell = a config.Shell = a
} }
@@ -214,7 +214,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
// hsu clears the environment so resolve paths early // hsu clears the environment so resolve paths early
if config.Path == nil { if config.Path == nil {
if len(config.Args) > 0 { if len(config.Args) > 0 {
if p, err := k.LookPath(config.Args[0]); err != nil { if p, err := k.lookPath(config.Args[0]); err != nil {
return &hst.AppError{Step: "look up executable file", Err: err} return &hst.AppError{Step: "look up executable file", Err: err}
} else if config.Path, err = container.NewAbs(p); err != nil { } else if config.Path, err = container.NewAbs(p); err != nil {
return newWithMessageError(err.Error(), err) return newWithMessageError(err.Error(), err)
@@ -250,7 +250,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
// hide nscd from container if present // hide nscd from container if present
nscd := container.AbsFHSVar.Append("run/nscd") nscd := container.AbsFHSVar.Append("run/nscd")
if _, err := k.Stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) { if _, err := k.stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) {
conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}}) conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}})
} }
@@ -274,86 +274,89 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
return newWithMessage("invalid program path") return newWithMessage("invalid program path")
} }
// TODO(ophestra): revert this after params to shim
share := &shareHost{seal: k}
copyPaths(k.syscallDispatcher, &share.sc, hsu.MustID())
var mapuid, mapgid *stringPair[int] var mapuid, mapgid *stringPair[int]
{ {
var uid, gid int var uid, gid int
var err error var err error
seal.container, seal.env, err = newContainer(config.Container, k, seal.id.String(), &uid, &gid) k.container, k.env, err = newContainer(k, config.Container, k.id.String(), &share.sc, &uid, &gid)
seal.waitDelay = config.Container.WaitDelay k.waitDelay = config.Container.WaitDelay
if err != nil { if err != nil {
return &hst.AppError{Step: "initialise container configuration", Err: err} return &hst.AppError{Step: "initialise container configuration", Err: err}
} }
if len(config.Args) == 0 { if len(config.Args) == 0 {
config.Args = []string{config.Path.String()} config.Args = []string{config.Path.String()}
} }
seal.container.Path = config.Path k.container.Path = config.Path
seal.container.Args = config.Args k.container.Args = config.Args
mapuid = newInt(uid) mapuid = newInt(uid)
mapgid = newInt(gid) mapgid = newInt(gid)
if seal.env == nil { if k.env == nil {
seal.env = make(map[string]string, 1<<6) k.env = make(map[string]string, 1<<6)
} }
} }
// inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid // inner XDG_RUNTIME_DIR default formatting of `/run/user/%d` as mapped uid
innerRuntimeDir := container.AbsFHSRunUser.Append(mapuid.String()) innerRuntimeDir := container.AbsFHSRunUser.Append(mapuid.String())
seal.env[xdgRuntimeDir] = innerRuntimeDir.String() k.env[xdgRuntimeDir] = innerRuntimeDir.String()
seal.env[xdgSessionClass] = "user" k.env[xdgSessionClass] = "user"
seal.env[xdgSessionType] = "tty" k.env[xdgSessionType] = "tty"
share := &shareHost{seal: seal, sc: k.Paths()} k.runDirPath = share.sc.RunDirPath
seal.runDirPath = share.sc.RunDirPath k.sys = system.New(k.ctx, k.user.uid.unwrap())
seal.sys = system.New(seal.ctx, seal.user.uid.unwrap()) k.sys.Ensure(share.sc.SharePath.String(), 0711)
seal.sys.Ensure(share.sc.SharePath.String(), 0711)
{ {
runtimeDir := share.sc.SharePath.Append("runtime") runtimeDir := share.sc.SharePath.Append("runtime")
seal.sys.Ensure(runtimeDir.String(), 0700) k.sys.Ensure(runtimeDir.String(), 0700)
seal.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute) k.sys.UpdatePermType(system.User, runtimeDir.String(), acl.Execute)
runtimeDirInst := runtimeDir.Append(seal.user.identity.String()) runtimeDirInst := runtimeDir.Append(k.user.identity.String())
seal.sys.Ensure(runtimeDirInst.String(), 0700) k.sys.Ensure(runtimeDirInst.String(), 0700)
seal.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute) k.sys.UpdatePermType(system.User, runtimeDirInst.String(), acl.Read, acl.Write, acl.Execute)
seal.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755) k.container.Tmpfs(container.AbsFHSRunUser, 1<<12, 0755)
seal.container.Bind(runtimeDirInst, innerRuntimeDir, container.BindWritable) k.container.Bind(runtimeDirInst, innerRuntimeDir, container.BindWritable)
} }
{ {
tmpdir := share.sc.SharePath.Append("tmpdir") tmpdir := share.sc.SharePath.Append("tmpdir")
seal.sys.Ensure(tmpdir.String(), 0700) k.sys.Ensure(tmpdir.String(), 0700)
seal.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute) k.sys.UpdatePermType(system.User, tmpdir.String(), acl.Execute)
tmpdirInst := tmpdir.Append(seal.user.identity.String()) tmpdirInst := tmpdir.Append(k.user.identity.String())
seal.sys.Ensure(tmpdirInst.String(), 01700) k.sys.Ensure(tmpdirInst.String(), 01700)
seal.sys.UpdatePermType(system.User, tmpdirInst.String(), acl.Read, acl.Write, acl.Execute) k.sys.UpdatePermType(system.User, tmpdirInst.String(), acl.Read, acl.Write, acl.Execute)
// mount inner /tmp from share so it shares persistence and storage behaviour of host /tmp // mount inner /tmp from share so it shares persistence and storage behaviour of host /tmp
seal.container.Bind(tmpdirInst, container.AbsFHSTmp, container.BindWritable) k.container.Bind(tmpdirInst, container.AbsFHSTmp, container.BindWritable)
} }
{ {
username := "chronos" username := "chronos"
if seal.user.username != "" { if k.user.username != "" {
username = seal.user.username username = k.user.username
} }
seal.container.Dir = seal.user.home k.container.Dir = k.user.home
seal.env["HOME"] = seal.user.home.String() k.env["HOME"] = k.user.home.String()
seal.env["USER"] = username k.env["USER"] = username
seal.env[shell] = config.Shell.String() k.env[shell] = config.Shell.String()
seal.container.Place(container.AbsFHSEtc.Append("passwd"), k.container.Place(container.AbsFHSEtc.Append("passwd"),
[]byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Hakurei:"+seal.user.home.String()+":"+config.Shell.String()+"\n")) []byte(username+":x:"+mapuid.String()+":"+mapgid.String()+":Hakurei:"+k.user.home.String()+":"+config.Shell.String()+"\n"))
seal.container.Place(container.AbsFHSEtc.Append("group"), k.container.Place(container.AbsFHSEtc.Append("group"),
[]byte("hakurei:x:"+mapgid.String()+":\n")) []byte("hakurei:x:"+mapgid.String()+":\n"))
} }
// pass TERM for proper terminal I/O in initial process // pass TERM for proper terminal I/O in initial process
if t, ok := k.LookupEnv(term); ok { if t, ok := k.lookupEnv(term); ok {
seal.env[term] = t k.env[term] = t
} }
if config.Enablements.Unwrap()&system.EWayland != 0 { if config.Enablements.Unwrap()&system.EWayland != 0 {
// outer wayland socket (usually `/run/user/%d/wayland-%d`) // outer wayland socket (usually `/run/user/%d/wayland-%d`)
var socketPath *container.Absolute var socketPath *container.Absolute
if name, ok := k.LookupEnv(wayland.WaylandDisplay); !ok { if name, ok := k.lookupEnv(wayland.WaylandDisplay); !ok {
hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName) hlog.Verbose(wayland.WaylandDisplay + " is not set, assuming " + wayland.FallbackName)
socketPath = share.sc.RuntimePath.Append(wayland.FallbackName) socketPath = share.sc.RuntimePath.Append(wayland.FallbackName)
} else if a, err := container.NewAbs(name); err != nil { } else if a, err := container.NewAbs(name); err != nil {
@@ -363,28 +366,28 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
} }
innerPath := innerRuntimeDir.Append(wayland.FallbackName) innerPath := innerRuntimeDir.Append(wayland.FallbackName)
seal.env[wayland.WaylandDisplay] = wayland.FallbackName k.env[wayland.WaylandDisplay] = wayland.FallbackName
if !config.DirectWayland { // set up security-context-v1 if !config.DirectWayland { // set up security-context-v1
appID := config.ID appID := config.ID
if appID == "" { if appID == "" {
// use instance ID in case app id is not set // use instance ID in case app id is not set
appID = "app.hakurei." + seal.id.String() appID = "app.hakurei." + k.id.String()
} }
// downstream socket paths // downstream socket paths
outerPath := share.instance().Append("wayland") outerPath := share.instance().Append("wayland")
seal.sys.Wayland(&seal.sync, outerPath.String(), socketPath.String(), appID, seal.id.String()) k.sys.Wayland(&k.sync, outerPath.String(), socketPath.String(), appID, k.id.String())
seal.container.Bind(outerPath, innerPath, 0) k.container.Bind(outerPath, innerPath, 0)
} else { // bind mount wayland socket (insecure) } else { // bind mount wayland socket (insecure)
hlog.Verbose("direct wayland access, PROCEED WITH CAUTION") hlog.Verbose("direct wayland access, PROCEED WITH CAUTION")
share.ensureRuntimeDir() share.ensureRuntimeDir()
seal.container.Bind(socketPath, innerPath, 0) k.container.Bind(socketPath, innerPath, 0)
seal.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute) k.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
} }
} }
if config.Enablements.Unwrap()&system.EX11 != 0 { if config.Enablements.Unwrap()&system.EX11 != 0 {
if d, ok := k.LookupEnv(display); !ok { if d, ok := k.lookupEnv(display); !ok {
return newWithMessage("DISPLAY is not set") return newWithMessage("DISPLAY is not set")
} else { } else {
socketDir := container.AbsFHSTmp.Append(".X11-unix") socketDir := container.AbsFHSTmp.Append(".X11-unix")
@@ -402,21 +405,21 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
} }
} }
if socketPath != nil { if socketPath != nil {
if _, err := k.Stat(socketPath.String()); err != nil { if _, err := k.stat(socketPath.String()); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err} return &hst.AppError{Step: fmt.Sprintf("access X11 socket %q", socketPath), Err: err}
} }
} else { } else {
seal.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute) k.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
if !config.Container.HostAbstract { if !config.Container.HostAbstract {
d = "unix:" + socketPath.String() d = "unix:" + socketPath.String()
} }
} }
} }
seal.sys.ChangeHosts("#" + seal.user.uid.String()) k.sys.ChangeHosts("#" + k.user.uid.String())
seal.env[display] = d k.env[display] = d
seal.container.Bind(socketDir, socketDir, 0) k.container.Bind(socketDir, socketDir, 0)
} }
} }
@@ -426,14 +429,14 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
// PulseAudio socket (usually `/run/user/%d/pulse/native`) // PulseAudio socket (usually `/run/user/%d/pulse/native`)
pulseSocket := pulseRuntimeDir.Append("native") pulseSocket := pulseRuntimeDir.Append("native")
if _, err := k.Stat(pulseRuntimeDir.String()); err != nil { if _, err := k.stat(pulseRuntimeDir.String()); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err} return &hst.AppError{Step: fmt.Sprintf("access PulseAudio directory %q", pulseRuntimeDir), Err: err}
} }
return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir)) return newWithMessage(fmt.Sprintf("PulseAudio directory %q not found", pulseRuntimeDir))
} }
if s, err := k.Stat(pulseSocket.String()); err != nil { if s, err := k.stat(pulseSocket.String()); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err} return &hst.AppError{Step: fmt.Sprintf("access PulseAudio socket %q", pulseSocket), Err: err}
} }
@@ -447,9 +450,9 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
// hard link pulse socket into target-executable share // hard link pulse socket into target-executable share
innerPulseRuntimeDir := share.runtime().Append("pulse") innerPulseRuntimeDir := share.runtime().Append("pulse")
innerPulseSocket := innerRuntimeDir.Append("pulse", "native") innerPulseSocket := innerRuntimeDir.Append("pulse", "native")
seal.sys.Link(pulseSocket.String(), innerPulseRuntimeDir.String()) k.sys.Link(pulseSocket.String(), innerPulseRuntimeDir.String())
seal.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0) k.container.Bind(innerPulseRuntimeDir, innerPulseSocket, 0)
seal.env[pulseServer] = "unix:" + innerPulseSocket.String() k.env[pulseServer] = "unix:" + innerPulseSocket.String()
// publish current user's pulse cookie for target user // publish current user's pulse cookie for target user
var paCookiePath *container.Absolute var paCookiePath *container.Absolute
@@ -457,7 +460,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
const paLocateStep = "locate PulseAudio cookie" const paLocateStep = "locate PulseAudio cookie"
// from environment // from environment
if p, ok := k.LookupEnv(pulseCookie); ok { if p, ok := k.lookupEnv(pulseCookie); ok {
if a, err := container.NewAbs(p); err != nil { if a, err := container.NewAbs(p); err != nil {
return &hst.AppError{Step: paLocateStep, Err: err} return &hst.AppError{Step: paLocateStep, Err: err}
} else { } else {
@@ -468,14 +471,14 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
} }
// $HOME/.pulse-cookie // $HOME/.pulse-cookie
if p, ok := k.LookupEnv(home); ok { if p, ok := k.lookupEnv(home); ok {
if a, err := container.NewAbs(p); err != nil { if a, err := container.NewAbs(p); err != nil {
return &hst.AppError{Step: paLocateStep, Err: err} return &hst.AppError{Step: paLocateStep, Err: err}
} else { } else {
paCookiePath = a.Append(".pulse-cookie") paCookiePath = a.Append(".pulse-cookie")
} }
if s, err := k.Stat(paCookiePath.String()); err != nil { if s, err := k.stat(paCookiePath.String()); err != nil {
paCookiePath = nil paCookiePath = nil
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: "access PulseAudio cookie", Err: err} return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
@@ -489,13 +492,13 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
} }
// $XDG_CONFIG_HOME/pulse/cookie // $XDG_CONFIG_HOME/pulse/cookie
if p, ok := k.LookupEnv(xdgConfigHome); ok { if p, ok := k.lookupEnv(xdgConfigHome); ok {
if a, err := container.NewAbs(p); err != nil { if a, err := container.NewAbs(p); err != nil {
return &hst.AppError{Step: paLocateStep, Err: err} return &hst.AppError{Step: paLocateStep, Err: err}
} else { } else {
paCookiePath = a.Append("pulse", "cookie") paCookiePath = a.Append("pulse", "cookie")
} }
if s, err := k.Stat(paCookiePath.String()); err != nil { if s, err := k.stat(paCookiePath.String()); err != nil {
paCookiePath = nil paCookiePath = nil
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return &hst.AppError{Step: "access PulseAudio cookie", Err: err} return &hst.AppError{Step: "access PulseAudio cookie", Err: err}
@@ -512,10 +515,10 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
if paCookiePath != nil { if paCookiePath != nil {
innerDst := hst.AbsTmp.Append("/pulse-cookie") innerDst := hst.AbsTmp.Append("/pulse-cookie")
seal.env[pulseCookie] = innerDst.String() k.env[pulseCookie] = innerDst.String()
var payload *[]byte var payload *[]byte
seal.container.PlaceP(innerDst, &payload) k.container.PlaceP(innerDst, &payload)
seal.sys.CopyFile(payload, paCookiePath.String(), 256, 256) k.sys.CopyFile(payload, paCookiePath.String(), 256, 256)
} else { } else {
hlog.Verbose("cannot locate PulseAudio cookie (tried " + hlog.Verbose("cannot locate PulseAudio cookie (tried " +
"$PULSE_COOKIE, " + "$PULSE_COOKIE, " +
@@ -534,30 +537,30 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
sessionPath, systemPath := share.instance().Append("bus"), share.instance().Append("system_bus_socket") sessionPath, systemPath := share.instance().Append("bus"), share.instance().Append("system_bus_socket")
// configure dbus proxy // configure dbus proxy
if f, err := seal.sys.ProxyDBus( if f, err := k.sys.ProxyDBus(
config.SessionBus, config.SystemBus, config.SessionBus, config.SystemBus,
sessionPath.String(), systemPath.String(), sessionPath.String(), systemPath.String(),
); err != nil { ); err != nil {
return err return err
} else { } else {
seal.dbusMsg = f k.dbusMsg = f
} }
// share proxy sockets // share proxy sockets
sessionInner := innerRuntimeDir.Append("bus") sessionInner := innerRuntimeDir.Append("bus")
seal.env[dbusSessionBusAddress] = "unix:path=" + sessionInner.String() k.env[dbusSessionBusAddress] = "unix:path=" + sessionInner.String()
seal.container.Bind(sessionPath, sessionInner, 0) k.container.Bind(sessionPath, sessionInner, 0)
seal.sys.UpdatePerm(sessionPath.String(), acl.Read, acl.Write) k.sys.UpdatePerm(sessionPath.String(), acl.Read, acl.Write)
if config.SystemBus != nil { if config.SystemBus != nil {
systemInner := container.AbsFHSRun.Append("dbus/system_bus_socket") systemInner := container.AbsFHSRun.Append("dbus/system_bus_socket")
seal.env[dbusSystemBusAddress] = "unix:path=" + systemInner.String() k.env[dbusSystemBusAddress] = "unix:path=" + systemInner.String()
seal.container.Bind(systemPath, systemInner, 0) k.container.Bind(systemPath, systemInner, 0)
seal.sys.UpdatePerm(systemPath.String(), acl.Read, acl.Write) k.sys.UpdatePerm(systemPath.String(), acl.Read, acl.Write)
} }
} }
// mount root read-only as the final setup Op // mount root read-only as the final setup Op
seal.container.Remount(container.AbsFHSRoot, syscall.MS_RDONLY) k.container.Remount(container.AbsFHSRoot, syscall.MS_RDONLY)
// append ExtraPerms last // append ExtraPerms last
for _, p := range config.ExtraPerms { for _, p := range config.ExtraPerms {
@@ -566,7 +569,7 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
} }
if p.Ensure { if p.Ensure {
seal.sys.Ensure(p.Path.String(), 0700) k.sys.Ensure(p.Path.String(), 0700)
} }
perms := make(acl.Perms, 0, 3) perms := make(acl.Perms, 0, 3)
@@ -579,23 +582,23 @@ func (seal *outcome) finalise(ctx context.Context, k sys.State, config *hst.Conf
if p.Execute { if p.Execute {
perms = append(perms, acl.Execute) perms = append(perms, acl.Execute)
} }
seal.sys.UpdatePermType(system.User, p.Path.String(), perms...) k.sys.UpdatePermType(system.User, p.Path.String(), perms...)
} }
// flatten and sort env for deterministic behaviour // flatten and sort env for deterministic behaviour
seal.container.Env = make([]string, 0, len(seal.env)) k.container.Env = make([]string, 0, len(k.env))
for k, v := range seal.env { for key, value := range k.env {
if strings.IndexByte(k, '=') != -1 { if strings.IndexByte(key, '=') != -1 {
return &hst.AppError{Step: "flatten environment", Err: syscall.EINVAL, return &hst.AppError{Step: "flatten environment", Err: syscall.EINVAL,
Msg: fmt.Sprintf("invalid environment variable %s", k)} Msg: fmt.Sprintf("invalid environment variable %s", key)}
} }
seal.container.Env = append(seal.container.Env, k+"="+v) k.container.Env = append(k.container.Env, key+"="+value)
} }
slices.Sort(seal.container.Env) slices.Sort(k.container.Env)
if hlog.Load() { if hlog.Load() {
hlog.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d", hlog.Verbosef("created application seal for uid %s (%s) groups: %v, argv: %s, ops: %d",
seal.user.uid, seal.user.username, config.Groups, seal.container.Args, len(*seal.container.Ops)) k.user.uid, k.user.username, config.Groups, k.container.Args, len(*k.container.Ops))
} }
return nil return nil

View File

@@ -1,4 +1,4 @@
package sys package app
import ( import (
"errors" "errors"
@@ -11,7 +11,6 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/internal"
"hakurei.app/internal/hlog" "hakurei.app/internal/hlog"
) )
@@ -20,14 +19,28 @@ type Hsu struct {
idOnce sync.Once idOnce sync.Once
idErr error idErr error
id int id int
kOnce sync.Once
k syscallDispatcher
} }
var ErrHsuAccess = errors.New("current user is not in the hsurc file") var ErrHsuAccess = errors.New("current user is not in the hsurc file")
func (h *Hsu) Uid(identity int) (int, error) { // ensureDispatcher ensures Hsu.k is not nil.
func (h *Hsu) ensureDispatcher() {
h.kOnce.Do(func() {
if h.k == nil {
h.k = direct{}
}
})
}
// ID returns the current user hsurc identifier. ErrHsuAccess is returned if the current user is not in hsurc.
func (h *Hsu) ID() (int, error) {
h.ensureDispatcher()
h.idOnce.Do(func() { h.idOnce.Do(func() {
h.id = -1 h.id = -1
hsuPath := internal.MustHsuPath() hsuPath := h.k.mustHsuPath()
cmd := exec.Command(hsuPath) cmd := exec.Command(hsuPath)
cmd.Path = hsuPath cmd.Path = hsuPath
@@ -40,7 +53,7 @@ func (h *Hsu) Uid(identity int) (int, error) {
) )
const step = "obtain uid from hsu" const step = "obtain uid from hsu"
if p, h.idErr = cmd.Output(); h.idErr == nil { if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil {
h.id, h.idErr = strconv.Atoi(string(p)) h.id, h.idErr = strconv.Atoi(string(p))
if h.idErr != nil { if h.idErr != nil {
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"} h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
@@ -54,21 +67,17 @@ func (h *Hsu) Uid(identity int) (int, error) {
} }
}) })
uid := -1 return h.id, h.idErr
if h.id >= 0 {
uid = 1000000 + h.id*10000 + identity
}
return uid, h.idErr
} }
// MustUid calls [State.Uid] and terminates on error. // MustID calls [Hsu.ID] and terminates on error.
func MustUid(s State, identity int) int { func (h *Hsu) MustID() int {
uid, err := s.Uid(identity) id, err := h.ID()
if err == nil { if err == nil {
return uid return id
} }
const fallback = "cannot obtain uid from setuid wrapper:" const fallback = "cannot retrieve user id from setuid wrapper:"
if errors.Is(err, ErrHsuAccess) { if errors.Is(err, ErrHsuAccess) {
hlog.Verbose("*"+fallback, err) hlog.Verbose("*"+fallback, err)
os.Exit(1) os.Exit(1)
@@ -81,3 +90,7 @@ func MustUid(s State, identity int) int {
return -0xdeadbeef return -0xdeadbeef
} }
} }
// HsuUid returns target uid for the stable hsu uid format.
// No bounds check is performed, a value retrieved from hsu is expected.
func HsuUid(id, identity int) int { return 1000000 + id*10000 + identity }

36
internal/app/paths.go Normal file
View File

@@ -0,0 +1,36 @@
package app
import (
"strconv"
"hakurei.app/container"
"hakurei.app/hst"
)
// CopyPaths populates a [hst.Paths] struct.
func CopyPaths(v *hst.Paths, userid int) { copyPaths(direct{}, v, userid) }
// copyPaths populates a [hst.Paths] struct.
func copyPaths(k syscallDispatcher, v *hst.Paths, userid int) {
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
if tempDir, err := container.NewAbs(k.tempdir()); err != nil {
k.fatalf("invalid TMPDIR: %v", err)
} else {
v.TempDir = tempDir
}
v.SharePath = v.TempDir.Append("hakurei." + strconv.Itoa(userid))
k.verbosef("process share directory at %q", v.SharePath)
r, _ := k.lookupEnv(xdgRuntimeDir)
if a, err := container.NewAbs(r); err != nil {
// fall back to path in share since hakurei has no hard XDG dependency
v.RunDirPath = v.SharePath.Append("run")
v.RuntimePath = v.RunDirPath.Append("compat")
} else {
v.RuntimePath = a
v.RunDirPath = v.RuntimePath.Append("hakurei")
}
k.verbosef("runtime directory at %q", v.RunDirPath)
}

View File

@@ -33,12 +33,12 @@ type mainState struct {
// Time is nil if no process was ever created. // Time is nil if no process was ever created.
Time *time.Time Time *time.Time
seal *outcome
store state.Store store state.Store
cancel context.CancelFunc cancel context.CancelFunc
cmd *exec.Cmd cmd *exec.Cmd
cmdWait chan error cmdWait chan error
k *outcome
uintptr uintptr
} }
@@ -79,13 +79,13 @@ func (ms mainState) beforeExit(isFault bool) {
waitDone := make(chan struct{}) waitDone := make(chan struct{})
// TODO(ophestra): enforce this limit early so it does not have to be done twice // TODO(ophestra): enforce this limit early so it does not have to be done twice
shimTimeoutCompensated := shimWaitTimeout shimTimeoutCompensated := shimWaitTimeout
if ms.seal.waitDelay > MaxShimWaitDelay { if ms.k.waitDelay > MaxShimWaitDelay {
shimTimeoutCompensated += MaxShimWaitDelay shimTimeoutCompensated += MaxShimWaitDelay
} else { } else {
shimTimeoutCompensated += ms.seal.waitDelay shimTimeoutCompensated += ms.k.waitDelay
} }
// this ties waitDone to ctx with the additional compensated timeout duration // this ties waitDone to ctx with the additional compensated timeout duration
go func() { <-ms.seal.ctx.Done(); time.Sleep(shimTimeoutCompensated); close(waitDone) }() go func() { <-ms.k.ctx.Done(); time.Sleep(shimTimeoutCompensated); close(waitDone) }()
select { select {
case err := <-ms.cmdWait: case err := <-ms.cmdWait:
@@ -128,20 +128,20 @@ func (ms mainState) beforeExit(isFault bool) {
} }
hlog.Resume() hlog.Resume()
if ms.seal.sync != nil { if ms.k.sync != nil {
if err := ms.seal.sync.Close(); err != nil { if err := ms.k.sync.Close(); err != nil {
perror(err, "close wayland security context") perror(err, "close wayland security context")
} }
} }
if ms.seal.dbusMsg != nil { if ms.k.dbusMsg != nil {
ms.seal.dbusMsg() ms.k.dbusMsg()
} }
} }
if ms.uintptr&mainNeedsRevert != 0 { if ms.uintptr&mainNeedsRevert != 0 {
if ok, err := ms.store.Do(ms.seal.user.identity.unwrap(), func(c state.Cursor) { if ok, err := ms.store.Do(ms.k.user.identity.unwrap(), func(c state.Cursor) {
if ms.uintptr&mainNeedsDestroy != 0 { if ms.uintptr&mainNeedsDestroy != 0 {
if err := c.Destroy(ms.seal.id.unwrap()); err != nil { if err := c.Destroy(ms.k.id.unwrap()); err != nil {
perror(err, "destroy state entry") perror(err, "destroy state entry")
} }
} }
@@ -151,7 +151,7 @@ func (ms mainState) beforeExit(isFault bool) {
// it is impossible to continue from this point; // it is impossible to continue from this point;
// revert per-process state here to limit damage // revert per-process state here to limit damage
ec := system.Process ec := system.Process
if revertErr := ms.seal.sys.Revert((*system.Criteria)(&ec)); revertErr != nil { if revertErr := ms.k.sys.Revert((*system.Criteria)(&ec)); revertErr != nil {
var joinError interface { var joinError interface {
Unwrap() []error Unwrap() []error
error error
@@ -189,7 +189,7 @@ func (ms mainState) beforeExit(isFault bool) {
} }
} }
if err = ms.seal.sys.Revert((*system.Criteria)(&ec)); err != nil { if err = ms.k.sys.Revert((*system.Criteria)(&ec)); err != nil {
perror(err, "revert system setup") perror(err, "revert system setup")
} }
} }
@@ -219,8 +219,8 @@ func (ms mainState) fatal(fallback string, ferr error) {
} }
// main carries out outcome and terminates. main does not return. // main carries out outcome and terminates. main does not return.
func (seal *outcome) main() { func (k *outcome) main() {
if !seal.f.CompareAndSwap(false, true) { if !k.active.CompareAndSwap(false, true) {
panic("outcome: attempted to run twice") panic("outcome: attempted to run twice")
} }
@@ -228,15 +228,15 @@ func (seal *outcome) main() {
hsuPath := internal.MustHsuPath() hsuPath := internal.MustHsuPath()
// ms.beforeExit required beyond this point // ms.beforeExit required beyond this point
ms := &mainState{seal: seal} ms := &mainState{k: k}
if err := seal.sys.Commit(); err != nil { if err := k.sys.Commit(); err != nil {
ms.fatal("cannot commit system setup:", err) ms.fatal("cannot commit system setup:", err)
} }
ms.uintptr |= mainNeedsRevert ms.uintptr |= mainNeedsRevert
ms.store = state.NewMulti(seal.runDirPath.String()) ms.store = state.NewMulti(k.runDirPath.String())
ctx, cancel := context.WithCancel(seal.ctx) ctx, cancel := context.WithCancel(k.ctx)
defer cancel() defer cancel()
ms.cancel = cancel ms.cancel = cancel
@@ -255,14 +255,14 @@ func (seal *outcome) main() {
// passed through to shim by hsu // passed through to shim by hsu
shimEnv + "=" + strconv.Itoa(fd), shimEnv + "=" + strconv.Itoa(fd),
// interpreted by hsu // interpreted by hsu
"HAKUREI_IDENTITY=" + seal.user.identity.String(), "HAKUREI_IDENTITY=" + k.user.identity.String(),
} }
} }
if len(seal.user.supp) > 0 { if len(k.user.supp) > 0 {
hlog.Verbosef("attaching supplementary group ids %s", seal.user.supp) hlog.Verbosef("attaching supplementary group ids %s", k.user.supp)
// interpreted by hsu // interpreted by hsu
ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(seal.user.supp, " ")) ms.cmd.Env = append(ms.cmd.Env, "HAKUREI_GROUPS="+strings.Join(k.user.supp, " "))
} }
hlog.Verbosef("setuid helper at %s", hsuPath) hlog.Verbosef("setuid helper at %s", hsuPath)
@@ -284,8 +284,8 @@ func (seal *outcome) main() {
go func() { go func() {
setupErr <- e.Encode(&shimParams{ setupErr <- e.Encode(&shimParams{
os.Getpid(), os.Getpid(),
seal.waitDelay, k.waitDelay,
seal.container, k.container,
hlog.Load(), hlog.Load(),
}) })
}() }()
@@ -302,12 +302,12 @@ func (seal *outcome) main() {
} }
// shim accepted setup payload, create process state // shim accepted setup payload, create process state
if ok, err := ms.store.Do(seal.user.identity.unwrap(), func(c state.Cursor) { if ok, err := ms.store.Do(k.user.identity.unwrap(), func(c state.Cursor) {
if err := c.Save(&state.State{ if err := c.Save(&state.State{
ID: seal.id.unwrap(), ID: k.id.unwrap(),
PID: ms.cmd.Process.Pid, PID: ms.cmd.Process.Pid,
Time: *ms.Time, Time: *ms.Time,
}, seal.ct); err != nil { }, k.ct); err != nil {
ms.fatal("cannot save state entry:", err) ms.fatal("cannot save state entry:", err)
} }
}); err != nil { }); err != nil {

View File

@@ -2,69 +2,17 @@
package hlog package hlog
import ( import (
"bytes"
"io"
"log" "log"
"os" "os"
"sync"
"sync/atomic" "hakurei.app/container"
"syscall"
) )
const ( var o = &container.Suspendable{Downstream: os.Stderr}
bufSize = 4 * 1024
bufSizeMax = 16 * 1024 * 1024
)
var o = &suspendable{w: os.Stderr}
// Prepare configures the system logger for [Suspend] and [Resume] to take effect. // Prepare configures the system logger for [Suspend] and [Resume] to take effect.
func Prepare(prefix string) { log.SetPrefix(prefix + ": "); log.SetFlags(0); log.SetOutput(o) } func Prepare(prefix string) { log.SetPrefix(prefix + ": "); log.SetFlags(0); log.SetOutput(o) }
type suspendable struct {
w io.Writer
s atomic.Bool
buf bytes.Buffer
bufOnce sync.Once
bufMu sync.Mutex
dropped int
}
func (s *suspendable) Write(p []byte) (n int, err error) {
if !s.s.Load() {
return s.w.Write(p)
}
s.bufOnce.Do(func() { s.prepareBuf() })
s.bufMu.Lock()
defer s.bufMu.Unlock()
if l := len(p); s.buf.Len()+l > bufSizeMax {
s.dropped += l
return 0, syscall.ENOMEM
}
return s.buf.Write(p)
}
func (s *suspendable) prepareBuf() { s.buf.Grow(bufSize) }
func (s *suspendable) Suspend() bool { return o.s.CompareAndSwap(false, true) }
func (s *suspendable) Resume() (resumed bool, dropped uintptr, n int64, err error) {
if o.s.CompareAndSwap(true, false) {
o.bufMu.Lock()
defer o.bufMu.Unlock()
resumed = true
dropped = uintptr(o.dropped)
o.dropped = 0
n, err = io.Copy(s.w, &s.buf)
s.buf = bytes.Buffer{}
s.prepareBuf()
}
return
}
func Suspend() bool { return o.Suspend() } func Suspend() bool { return o.Suspend() }
func Resume() bool { func Resume() bool {
resumed, dropped, _, err := o.Resume() resumed, dropped, _, err := o.Resume()

View File

@@ -1,76 +0,0 @@
// Package sys wraps OS interaction library functions.
package sys
import (
"io/fs"
"log"
"os/user"
"strconv"
"hakurei.app/container"
"hakurei.app/hst"
"hakurei.app/internal/hlog"
)
// State provides safe interaction with operating system state.
type State interface {
// Getuid provides [os.Getuid].
Getuid() int
// Getgid provides [os.Getgid].
Getgid() int
// LookupEnv provides [os.LookupEnv].
LookupEnv(key string) (string, bool)
// TempDir provides [os.TempDir].
TempDir() string
// LookPath provides exec.LookPath.
LookPath(file string) (string, error)
// MustExecutable provides [container.MustExecutable].
MustExecutable() string
// LookupGroup provides [user.LookupGroup].
LookupGroup(name string) (*user.Group, error)
// ReadDir provides [os.ReadDir].
ReadDir(name string) ([]fs.DirEntry, error)
// Stat provides [os.Stat].
Stat(name string) (fs.FileInfo, error)
// Open provides [os.Open].
Open(name string) (fs.File, error)
// EvalSymlinks provides filepath.EvalSymlinks.
EvalSymlinks(path string) (string, error)
// Exit provides [os.Exit].
Exit(code int)
Println(v ...any)
Printf(format string, v ...any)
// Paths returns a populated [hst.Paths] struct.
Paths() hst.Paths
// Uid invokes hsu and returns target uid.
// Any errors returned by Uid is already wrapped [hlog.BaseError].
Uid(identity int) (int, error)
}
// MustGetUserID obtains user id from hsu by querying uid of identity 0.
func MustGetUserID(os State) int { return (MustUid(os, 0) / 10000) - 100 }
// CopyPaths is a generic implementation of [hst.Paths].
func CopyPaths(os State, v *hst.Paths, userid int) {
if tempDir, err := container.NewAbs(os.TempDir()); err != nil {
log.Fatalf("invalid TMPDIR: %v", err)
} else {
v.TempDir = tempDir
}
v.SharePath = v.TempDir.Append("hakurei." + strconv.Itoa(userid))
hlog.Verbosef("process share directory at %q", v.SharePath)
r, _ := os.LookupEnv(xdgRuntimeDir)
if a, err := container.NewAbs(r); err != nil {
// fall back to path in share since hakurei has no hard XDG dependency
v.RunDirPath = v.SharePath.Append("run")
v.RuntimePath = v.RunDirPath.Append("compat")
} else {
v.RuntimePath = a
v.RunDirPath = v.RuntimePath.Append("hakurei")
}
hlog.Verbosef("runtime directory at %q", v.RunDirPath)
}

View File

@@ -1,44 +0,0 @@
package sys
import (
"io/fs"
"os"
"os/exec"
"os/user"
"path/filepath"
"sync"
"hakurei.app/container"
"hakurei.app/hst"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
)
// Std implements System using the standard library.
type Std struct {
paths hst.Paths
pathsOnce sync.Once
Hsu
}
func (s *Std) Getuid() int { return os.Getuid() }
func (s *Std) Getgid() int { return os.Getgid() }
func (s *Std) LookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
func (s *Std) TempDir() string { return os.TempDir() }
func (s *Std) LookPath(file string) (string, error) { return exec.LookPath(file) }
func (s *Std) MustExecutable() string { return container.MustExecutable() }
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
func (s *Std) EvalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
func (s *Std) Exit(code int) { internal.Exit(code) }
func (s *Std) Println(v ...any) { hlog.Verbose(v...) }
func (s *Std) Printf(format string, v ...any) { hlog.Verbosef(format, v...) }
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
func (s *Std) Paths() hst.Paths {
s.pathsOnce.Do(func() { CopyPaths(s, &s.paths, MustGetUserID(s)) })
return s.paths
}

View File

@@ -35,7 +35,7 @@ package
*Default:* *Default:*
` <derivation hakurei-static-x86_64-unknown-linux-musl-0.2.1> ` ` <derivation hakurei-static-x86_64-unknown-linux-musl-0.2.2> `
@@ -313,7 +313,7 @@ Extra paths to make available to the container\.
*Type:* *Type:*
anything list of attribute set of anything
@@ -723,7 +723,7 @@ Common extra paths to make available to the container\.
*Type:* *Type:*
anything list of attribute set of anything
@@ -759,7 +759,7 @@ package
*Default:* *Default:*
` <derivation hakurei-hsu-0.2.1> ` ` <derivation hakurei-hsu-0.2.2> `

View File

@@ -31,7 +31,7 @@
buildGoModule rec { buildGoModule rec {
pname = "hakurei"; pname = "hakurei";
version = "0.2.1"; version = "0.2.2";
srcFiltered = builtins.path { srcFiltered = builtins.path {
name = "${pname}-src"; name = "${pname}-src";

View File

@@ -100,7 +100,7 @@ print(machine.fail("sudo -u alice -i hsu"))
# Verify hsu fault behaviour: # Verify hsu fault behaviour:
if denyOutput != "hsu: uid 1001 is not in the hsurc file\n": if denyOutput != "hsu: uid 1001 is not in the hsurc file\n":
raise Exception(f"unexpected deny output:\n{denyOutput}") raise Exception(f"unexpected deny output:\n{denyOutput}")
if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot obtain uid from setuid wrapper: current user is not in the hsurc file\n": if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *cannot retrieve user id from setuid wrapper: current user is not in the hsurc file\n":
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}") raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
# Verify timeout behaviour: # Verify timeout behaviour: