From 9e48d7f5626aa966a23754534f3120855d6a7c32 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 7 Oct 2025 01:50:56 +0900 Subject: [PATCH] hst/config: move container fields from toplevel This change also moves pd behaviour to cmd/hakurei, as this does not belong in the hst API. Signed-off-by: Ophestra --- cmd/hakurei/command.go | 134 ++++++++++++++++++++++++-------- cmd/hakurei/print.go | 31 +++++--- cmd/hakurei/print_test.go | 113 ++++++++++++++------------- cmd/hpkg/app.go | 14 ++-- cmd/hpkg/test/test.py | 6 +- cmd/hpkg/with.go | 50 ++++++------ hst/config.go | 55 +++++++++---- hst/config_test.go | 37 +++++++++ hst/hst.go | 25 +++--- hst/hst_test.go | 22 +++--- internal/app/app_test.go | 97 ++++++++++++++++------- internal/app/dispatcher.go | 5 -- internal/app/dispatcher_test.go | 1 - internal/app/finalise.go | 88 ++------------------- internal/app/spaccount.go | 37 ++++----- internal/app/spcontainer.go | 15 ++-- nixos.nix | 32 ++++---- test/sandbox/case/pd.nix | 1 - test/test.py | 8 +- 19 files changed, 435 insertions(+), 336 deletions(-) diff --git a/cmd/hakurei/command.go b/cmd/hakurei/command.go index d284e07..4575552 100644 --- a/cmd/hakurei/command.go +++ b/cmd/hakurei/command.go @@ -2,10 +2,12 @@ package main import ( "context" + "errors" "fmt" "io" "log" "os" + "os/exec" "os/user" "strconv" "sync" @@ -52,7 +54,9 @@ func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningE // config extraArgs... config := tryPath(msg, args[0]) - config.Args = append(config.Args, args[1:]...) + if config != nil && config.Container != nil { + config.Container.Args = append(config.Container.Args, args[1:]...) + } app.Main(ctx, msg, config) panic("unreachable") @@ -75,12 +79,6 @@ func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningE ) c.NewCommand("run", "Configure and start a permissive container", func(args []string) error { - // initialise config from flags - config := &hst.Config{ - ID: flagID, - Args: args, - } - if flagIdentity < hst.IdentityMin || flagIdentity > hst.IdentityMax { log.Fatalf("identity %d out of range", flagIdentity) } @@ -106,41 +104,109 @@ func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningE } ) - if flagHomeDir == "os" { - passwdOnce.Do(passwdFunc) - flagHomeDir = passwd.HomeDir + // paths are identical, resolve inner shell and program path + shell := container.AbsFHSRoot.Append("bin", "sh") + if a, err := container.NewAbs(os.Getenv("SHELL")); err == nil { + shell = a + } + progPath := shell + if len(args) > 0 { + if p, err := exec.LookPath(args[0]); err != nil { + log.Fatal(errors.Unwrap(err)) + return err + } else if progPath, err = container.NewAbs(p); err != nil { + log.Fatal(err.Error()) + return err + } } - if flagUserName == "chronos" { - passwdOnce.Do(passwdFunc) - flagUserName = passwd.Username - } - - config.Identity = flagIdentity - config.Groups = flagGroups - config.Username = flagUserName - - if a, err := container.NewAbs(flagHomeDir); err != nil { - log.Fatal(err.Error()) - return err - } else { - config.Home = a - } - - var e hst.Enablement + var et hst.Enablement if flagWayland { - e |= hst.EWayland + et |= hst.EWayland } if flagX11 { - e |= hst.EX11 + et |= hst.EX11 } if flagDBus { - e |= hst.EDBus + et |= hst.EDBus } if flagPulse { - e |= hst.EPulse + et |= hst.EPulse + } + + config := &hst.Config{ + ID: flagID, + Identity: flagIdentity, + Groups: flagGroups, + Enablements: hst.NewEnablements(et), + + Container: &hst.ContainerConfig{ + Userns: true, + HostNet: true, + Tty: true, + HostAbstract: true, + + Filesystem: []hst.FilesystemConfigJSON{ + // autoroot, includes the home directory + {FilesystemConfig: &hst.FSBind{ + Target: container.AbsFHSRoot, + Source: container.AbsFHSRoot, + Write: true, + Special: true, + }}, + }, + + Username: flagUserName, + Shell: shell, + + Path: progPath, + Args: args, + }, + } + + // bind GPU stuff + if et&(hst.EX11|hst.EWayland) != 0 { + config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{ + Source: container.AbsFHSDev.Append("dri"), + Device: true, + Optional: true, + }}) + } + + config.Container.Filesystem = append(config.Container.Filesystem, + // opportunistically bind kvm + hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{ + Source: container.AbsFHSDev.Append("kvm"), + Device: true, + Optional: true, + }}, + + // do autoetc last + hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{ + Target: container.AbsFHSEtc, + Source: container.AbsFHSEtc, + Special: true, + }}, + ) + + if config.Container.Username == "chronos" { + passwdOnce.Do(passwdFunc) + config.Container.Username = passwd.Username + } + + { + homeDir := flagHomeDir + if homeDir == "os" { + passwdOnce.Do(passwdFunc) + homeDir = passwd.HomeDir + } + if a, err := container.NewAbs(homeDir); err != nil { + log.Fatal(err.Error()) + return err + } else { + config.Container.Home = a + } } - config.Enablements = hst.NewEnablements(e) // parse D-Bus config file from flags if applicable if flagDBus { @@ -218,7 +284,9 @@ func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningE if config == nil { config = tryPath(msg, name) } - printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON) + if !printShowInstance(os.Stdout, time.Now().UTC(), entry, config, flagShort, flagJSON) { + os.Exit(1) + } default: log.Fatal("show requires 1 argument") diff --git a/cmd/hakurei/print.go b/cmd/hakurei/print.go index eefe134..c9084a5 100644 --- a/cmd/hakurei/print.go +++ b/cmd/hakurei/print.go @@ -11,6 +11,7 @@ import ( "text/tabwriter" "time" + "hakurei.app/container" "hakurei.app/hst" "hakurei.app/internal/app" "hakurei.app/internal/app/state" @@ -39,7 +40,9 @@ func printShowSystem(output io.Writer, short, flagJSON bool) { func printShowInstance( output io.Writer, now time.Time, instance *state.State, config *hst.Config, - short, flagJSON bool) { + short, flagJSON bool) (valid bool) { + valid = true + if flagJSON { if instance != nil { printJSON(output, short, instance) @@ -52,8 +55,11 @@ func printShowInstance( t := newPrinter(output) defer t.MustFlush() - if config.Container == nil { - mustPrint(output, "Warning: this configuration uses permissive defaults!\n\n") + if err := config.Validate(); err != nil { + valid = false + if m, ok := container.GetErrorMessage(err); ok { + mustPrint(output, "Error: "+m+"!\n\n") + } } if instance != nil { @@ -73,11 +79,11 @@ func printShowInstance( if len(config.Groups) > 0 { t.Printf(" Groups:\t%s\n", strings.Join(config.Groups, ", ")) } - if config.Home != nil { - t.Printf(" Home:\t%s\n", config.Home) - } if config.Container != nil { params := config.Container + if params.Home != nil { + t.Printf(" Home:\t%s\n", params.Home) + } if params.Hostname != "" { t.Printf(" Hostname:\t%s\n", params.Hostname) } @@ -100,12 +106,12 @@ func printShowInstance( } t.Printf(" Flags:\t%s\n", strings.Join(flags, " ")) - if config.Path != nil { - t.Printf(" Path:\t%s\n", config.Path) + if params.Path != nil { + t.Printf(" Path:\t%s\n", params.Path) + } + if len(params.Args) > 0 { + t.Printf(" Arguments:\t%s\n", strings.Join(params.Args, " ")) } - } - if len(config.Args) > 0 { - t.Printf(" Arguments:\t%s\n", strings.Join(config.Args, " ")) } t.Printf("\n") @@ -114,6 +120,7 @@ func printShowInstance( t.Printf("Filesystem\n") for _, f := range config.Container.Filesystem { if !f.Valid() { + valid = false t.Println(" ") continue } @@ -161,6 +168,8 @@ func printShowInstance( printDBus(config.SystemBus) t.Printf("\n") } + + return } func printPs(output io.Writer, now time.Time, s state.Store, short, flagJSON bool) { diff --git a/cmd/hakurei/print_test.go b/cmd/hakurei/print_test.go index 49579a8..a666d25 100644 --- a/cmd/hakurei/print_test.go +++ b/cmd/hakurei/print_test.go @@ -27,13 +27,14 @@ var ( testAppTime = time.Unix(0, 9).UTC() ) -func Test_printShowInstance(t *testing.T) { +func TestPrintShowInstance(t *testing.T) { testCases := []struct { name string instance *state.State config *hst.Config short, json bool want string + valid bool }{ {"config", nil, hst.Template(), false, false, `App Identity: 9 (org.chromium.Chromium) @@ -71,21 +72,25 @@ System bus Filter: true Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"] -`}, - {"config pd", nil, new(hst.Config), false, false, `Warning: this configuration uses permissive defaults! +`, true}, + {"config pd", nil, new(hst.Config), false, false, `Error: configuration missing container state! App Identity: 0 Enablements: (no enablements) -`}, - {"config flag none", nil, &hst.Config{Container: new(hst.ContainerConfig)}, false, false, `App +`, false}, + {"config flag none", nil, &hst.Config{Container: new(hst.ContainerConfig)}, false, false, `Error: container configuration missing path to home directory! + +App Identity: 0 Enablements: (no enablements) Flags: none -`}, - {"config nil entries", nil, &hst.Config{Container: &hst.ContainerConfig{Filesystem: make([]hst.FilesystemConfigJSON, 1)}, ExtraPerms: make([]*hst.ExtraPermConfig, 1)}, false, false, `App +`, false}, + {"config nil entries", nil, &hst.Config{Container: &hst.ContainerConfig{Filesystem: make([]hst.FilesystemConfigJSON, 1)}, ExtraPerms: make([]*hst.ExtraPermConfig, 1)}, false, false, `Error: container configuration missing path to home directory! + +App Identity: 0 Enablements: (no enablements) Flags: none @@ -95,8 +100,8 @@ Filesystem Extra ACL -`}, - {"config pd dbus see", nil, &hst.Config{SessionBus: &dbus.Config{See: []string{"org.example.test"}}}, false, false, `Warning: this configuration uses permissive defaults! +`, false}, + {"config pd dbus see", nil, &hst.Config{SessionBus: &dbus.Config{See: []string{"org.example.test"}}}, false, false, `Error: configuration missing container state! App Identity: 0 @@ -106,7 +111,7 @@ Session bus Filter: false See: ["org.example.test"] -`}, +`, false}, {"instance", testState, hst.Template(), false, false, `State Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3735928559) @@ -148,8 +153,8 @@ System bus Filter: true Talk: ["org.bluez" "org.freedesktop.Avahi" "org.freedesktop.UPower"] -`}, - {"instance pd", testState, new(hst.Config), false, false, `Warning: this configuration uses permissive defaults! +`, true}, + {"instance pd", testState, new(hst.Config), false, false, `Error: configuration missing container state! State Instance: 8e2c76b066dabe574cf073bdb46eb5c1 (3735928559) @@ -159,10 +164,10 @@ App Identity: 0 Enablements: (no enablements) -`}, +`, false}, {"json nil", nil, nil, false, true, `null -`}, +`, true}, {"json instance", testState, nil, false, true, `{ "instance": [ 142, @@ -185,14 +190,6 @@ App "pid": 3735928559, "config": { "id": "org.chromium.Chromium", - "path": "/run/current-system/sw/bin/chromium", - "args": [ - "chromium", - "--ignore-gpu-blocklist", - "--disable-smooth-scrolling", - "--enable-features=UseOzonePlatform", - "--ozone-platform=wayland" - ], "enablements": { "wayland": true, "dbus": true, @@ -234,9 +231,6 @@ App "broadcast": null, "filter": true }, - "username": "chronos", - "shell": "/run/current-system/sw/bin/zsh", - "home": "/data/data/org.chromium.Chromium", "extra_perms": [ { "ensure": true, @@ -331,22 +325,25 @@ App "dev": true, "optional": true } + ], + "username": "chronos", + "shell": "/run/current-system/sw/bin/zsh", + "home": "/data/data/org.chromium.Chromium", + "path": "/run/current-system/sw/bin/chromium", + "args": [ + "chromium", + "--ignore-gpu-blocklist", + "--disable-smooth-scrolling", + "--enable-features=UseOzonePlatform", + "--ozone-platform=wayland" ] } }, "time": "1970-01-01T00:00:00.000000009Z" } -`}, +`, true}, {"json config", nil, hst.Template(), false, true, `{ "id": "org.chromium.Chromium", - "path": "/run/current-system/sw/bin/chromium", - "args": [ - "chromium", - "--ignore-gpu-blocklist", - "--disable-smooth-scrolling", - "--enable-features=UseOzonePlatform", - "--ozone-platform=wayland" - ], "enablements": { "wayland": true, "dbus": true, @@ -388,9 +385,6 @@ App "broadcast": null, "filter": true }, - "username": "chronos", - "shell": "/run/current-system/sw/bin/zsh", - "home": "/data/data/org.chromium.Chromium", "extra_perms": [ { "ensure": true, @@ -485,26 +479,39 @@ App "dev": true, "optional": true } + ], + "username": "chronos", + "shell": "/run/current-system/sw/bin/zsh", + "home": "/data/data/org.chromium.Chromium", + "path": "/run/current-system/sw/bin/chromium", + "args": [ + "chromium", + "--ignore-gpu-blocklist", + "--disable-smooth-scrolling", + "--enable-features=UseOzonePlatform", + "--ozone-platform=wayland" ] } } -`}, +`, true}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { output := new(strings.Builder) - printShowInstance(output, testTime, tc.instance, tc.config, tc.short, tc.json) + gotValid := printShowInstance(output, testTime, tc.instance, tc.config, tc.short, tc.json) if got := output.String(); got != tc.want { - t.Errorf("printShowInstance: got\n%s\nwant\n%s", - got, tc.want) + t.Errorf("printShowInstance: \n%s\nwant\n%s", got, tc.want) return } + if gotValid != tc.valid { + t.Errorf("printShowInstance: valid = %v, want %v", gotValid, tc.valid) + } }) } } -func Test_printPs(t *testing.T) { +func TestPrintPs(t *testing.T) { testCases := []struct { name string entries state.Entries @@ -547,14 +554,6 @@ func Test_printPs(t *testing.T) { "pid": 3735928559, "config": { "id": "org.chromium.Chromium", - "path": "/run/current-system/sw/bin/chromium", - "args": [ - "chromium", - "--ignore-gpu-blocklist", - "--disable-smooth-scrolling", - "--enable-features=UseOzonePlatform", - "--ozone-platform=wayland" - ], "enablements": { "wayland": true, "dbus": true, @@ -596,9 +595,6 @@ func Test_printPs(t *testing.T) { "broadcast": null, "filter": true }, - "username": "chronos", - "shell": "/run/current-system/sw/bin/zsh", - "home": "/data/data/org.chromium.Chromium", "extra_perms": [ { "ensure": true, @@ -693,6 +689,17 @@ func Test_printPs(t *testing.T) { "dev": true, "optional": true } + ], + "username": "chronos", + "shell": "/run/current-system/sw/bin/zsh", + "home": "/data/data/org.chromium.Chromium", + "path": "/run/current-system/sw/bin/chromium", + "args": [ + "chromium", + "--ignore-gpu-blocklist", + "--disable-smooth-scrolling", + "--enable-features=UseOzonePlatform", + "--ozone-platform=wayland" ] } }, diff --git a/cmd/hpkg/app.go b/cmd/hpkg/app.go index 1a1437f..a4910aa 100644 --- a/cmd/hpkg/app.go +++ b/cmd/hpkg/app.go @@ -66,19 +66,12 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *container.Absolute, arg config := &hst.Config{ ID: app.ID, - Path: pathname, - Args: argv, - Enablements: app.Enablements, SystemBus: app.SystemBus, SessionBus: app.SessionBus, DirectWayland: app.DirectWayland, - Username: "hakurei", - Shell: pathShell, - Home: pathDataData.Append(app.ID), - Identity: app.Identity, Groups: app.Groups, @@ -107,6 +100,13 @@ func (app *appInfo) toHst(pathSet *appPathSet, pathname *container.Absolute, arg {FilesystemConfig: &hst.FSBind{Source: container.AbsFHSSys.Append("devices"), Optional: true}}, {FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}}, }, + + Username: "hakurei", + Shell: pathShell, + Home: pathDataData.Append(app.ID), + + Path: pathname, + Args: argv, }, ExtraPerms: []*hst.ExtraPermConfig{ {Path: dataHome, Execute: true}, diff --git a/cmd/hpkg/test/test.py b/cmd/hpkg/test/test.py index d491def..e2a002d 100644 --- a/cmd/hpkg/test/test.py +++ b/cmd/hpkg/test/test.py @@ -62,11 +62,11 @@ def check_state(name, enablements): config = instance['config'] - if len(config['args']) != 1 or not (config['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (config['args'][0]): - raise Exception(f"unexpected args {instance['config']['args']}") + if len(config['container']['args']) != 1 or not (config['container']['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (config['container']['args'][0]): + raise Exception(f"unexpected args {config['container']['args']}") if config['enablements'] != enablements: - raise Exception(f"unexpected enablements {instance['config']['enablements']}") + raise Exception(f"unexpected enablements {config['enablements']}") start_all() diff --git a/cmd/hpkg/with.go b/cmd/hpkg/with.go index 59545c6..b3ccd47 100644 --- a/cmd/hpkg/with.go +++ b/cmd/hpkg/with.go @@ -18,22 +18,6 @@ func withNixDaemon( mustRunAppDropShell(ctx, msg, updateConfig(&hst.Config{ ID: app.ID, - Path: pathShell, - Args: []string{bash, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " + - // start nix-daemon - "nix-daemon --store / & " + - // wait for socket to appear - "(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " + - // create directory so nix stops complaining - "mkdir -p /nix/var/nix/profiles/per-user/root/channels && " + - strings.Join(command, " && ") + - // terminate nix-daemon - " && pkill nix-daemon", - }, - - Username: "hakurei", - Shell: pathShell, - Home: pathDataData.Append(app.ID), ExtraPerms: []*hst.ExtraPermConfig{ {Path: dataHome, Execute: true}, {Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true}, @@ -55,6 +39,23 @@ func withNixDaemon( {FilesystemConfig: &hst.FSLink{Target: container.AbsFHSUsrBin, Linkname: pathSwBin.String()}}, {FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID), Source: pathSet.homeDir, Write: true, Ensure: true}}, }, + + Username: "hakurei", + Shell: pathShell, + Home: pathDataData.Append(app.ID), + + Path: pathShell, + Args: []string{bash, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " + + // start nix-daemon + "nix-daemon --store / & " + + // wait for socket to appear + "(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " + + // create directory so nix stops complaining + "mkdir -p /nix/var/nix/profiles/per-user/root/channels && " + + strings.Join(command, " && ") + + // terminate nix-daemon + " && pkill nix-daemon", + }, }, }), dropShell, beforeFail) } @@ -67,12 +68,6 @@ func withCacheDir( mustRunAppDropShell(ctx, msg, &hst.Config{ ID: app.ID, - Path: pathShell, - Args: []string{bash, "-lc", strings.Join(command, " && ")}, - - Username: "nixos", - Shell: pathShell, - Home: pathDataData.Append(app.ID, "cache"), ExtraPerms: []*hst.ExtraPermConfig{ {Path: dataHome, Execute: true}, {Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true}, @@ -94,13 +89,22 @@ func withCacheDir( {FilesystemConfig: &hst.FSBind{Source: workDir, Target: hst.AbsTmp.Append("bundle")}}, {FilesystemConfig: &hst.FSBind{Target: pathDataData.Append(app.ID, "cache"), Source: pathSet.cacheDir, Write: true, Ensure: true}}, }, + + Username: "nixos", + Shell: pathShell, + Home: pathDataData.Append(app.ID, "cache"), + + Path: pathShell, + Args: []string{bash, "-lc", strings.Join(command, " && ")}, }, }, dropShell, beforeFail) } func mustRunAppDropShell(ctx context.Context, msg container.Msg, config *hst.Config, dropShell bool, beforeFail func()) { if dropShell { - config.Args = []string{bash, "-l"} + if config.Container != nil { + config.Container.Args = []string{bash, "-l"} + } mustRunApp(ctx, msg, config, beforeFail) beforeFail() msg.BeforeExit() diff --git a/hst/config.go b/hst/config.go index 7f6b079..8581bbc 100644 --- a/hst/config.go +++ b/hst/config.go @@ -1,6 +1,7 @@ package hst import ( + "errors" "time" "hakurei.app/container" @@ -35,11 +36,6 @@ type ( // Passed to wayland security-context-v1 and used as part of defaults in dbus session proxy. ID string `json:"id"` - // Pathname to executable file in the container filesystem. - Path *container.Absolute `json:"path,omitempty"` - // Final args passed to the initial program. - Args []string `json:"args"` - // System services to make available in the container. Enablements *Enablements `json:"enablements,omitempty"` @@ -53,14 +49,6 @@ type ( // and the bare socket is made available to the container. DirectWayland bool `json:"direct_wayland,omitempty"` - // String used as the username of the emulated user, validated against the default NAME_REGEX from adduser. - // Defaults to passwd name of target uid or chronos. - Username string `json:"username,omitempty"` - // Pathname of shell in the container filesystem to use for the emulated user. - Shell *container.Absolute `json:"shell"` - // Directory in the container filesystem to enter and use as the home directory of the emulated user. - Home *container.Absolute `json:"home"` - // Extra acl update ops to perform before setuid. ExtraPerms []*ExtraPermConfig `json:"extra_perms,omitempty"` @@ -114,9 +102,50 @@ type ( If the first element targets /, it is inserted early and excluded from path hiding. */ Filesystem []FilesystemConfigJSON `json:"filesystem"` + + // String used as the username of the emulated user, validated against the default NAME_REGEX from adduser. + // Defaults to passwd name of target uid or chronos. + Username string `json:"username,omitempty"` + // Pathname of shell in the container filesystem to use for the emulated user. + Shell *container.Absolute `json:"shell"` + // Directory in the container filesystem to enter and use as the home directory of the emulated user. + Home *container.Absolute `json:"home"` + + // Pathname to executable file in the container filesystem. + Path *container.Absolute `json:"path,omitempty"` + // Final args passed to the initial program. + Args []string `json:"args"` } ) +// ErrConfigNull is returned by [Config.Validate] for an invalid configuration that contains a null value for any +// field that must not be null. +var ErrConfigNull = errors.New("unexpected null in config") + +func (config *Config) Validate() error { + if config == nil { + return &AppError{Step: "validate configuration", Err: ErrConfigNull, + Msg: "invalid configuration"} + } + if config.Container == nil { + return &AppError{Step: "validate configuration", Err: ErrConfigNull, + Msg: "configuration missing container state"} + } + if config.Container.Home == nil { + return &AppError{Step: "validate configuration", Err: ErrConfigNull, + Msg: "container configuration missing path to home directory"} + } + if config.Container.Shell == nil { + return &AppError{Step: "validate configuration", Err: ErrConfigNull, + Msg: "container configuration missing path to shell"} + } + if config.Container.Path == nil { + return &AppError{Step: "validate configuration", Err: ErrConfigNull, + Msg: "container configuration missing path to initial program"} + } + return nil +} + // ExtraPermConfig describes an acl update op. type ExtraPermConfig struct { Ensure bool `json:"ensure,omitempty"` diff --git a/hst/config_test.go b/hst/config_test.go index c8da8a2..98321c7 100644 --- a/hst/config_test.go +++ b/hst/config_test.go @@ -1,12 +1,49 @@ package hst_test import ( + "reflect" "testing" "hakurei.app/container" "hakurei.app/hst" ) +func TestConfigValidate(t *testing.T) { + testCases := []struct { + name string + config *hst.Config + wantErr error + }{ + {"nil", nil, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull, + Msg: "invalid configuration"}}, + {"container", &hst.Config{}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull, + Msg: "configuration missing container state"}}, + {"home", &hst.Config{Container: &hst.ContainerConfig{}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull, + Msg: "container configuration missing path to home directory"}}, + {"shell", &hst.Config{Container: &hst.ContainerConfig{ + Home: container.AbsFHSTmp, + }}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull, + Msg: "container configuration missing path to shell"}}, + {"path", &hst.Config{Container: &hst.ContainerConfig{ + Home: container.AbsFHSTmp, + Shell: container.AbsFHSTmp, + }}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull, + Msg: "container configuration missing path to initial program"}}, + {"valid", &hst.Config{Container: &hst.ContainerConfig{ + Home: container.AbsFHSTmp, + Shell: container.AbsFHSTmp, + Path: container.AbsFHSTmp, + }}, nil}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if err := tc.config.Validate(); !reflect.DeepEqual(err, tc.wantErr) { + t.Errorf("Validate: error = %#v, want %#v", err, tc.wantErr) + } + }) + } +} + func TestExtraPermConfig(t *testing.T) { testCases := []struct { name string diff --git a/hst/hst.go b/hst/hst.go index 8a72b50..14c1845 100644 --- a/hst/hst.go +++ b/hst/hst.go @@ -60,15 +60,6 @@ func Template() *Config { return &Config{ ID: "org.chromium.Chromium", - Path: container.AbsFHSRun.Append("current-system/sw/bin/chromium"), - Args: []string{ - "chromium", - "--ignore-gpu-blocklist", - "--disable-smooth-scrolling", - "--enable-features=UseOzonePlatform", - "--ozone-platform=wayland", - }, - Enablements: NewEnablements(EWayland | EDBus | EPulse), SessionBus: &dbus.Config{ @@ -93,9 +84,6 @@ func Template() *Config { }, DirectWayland: false, - Username: "chronos", - Shell: container.AbsFHSRun.Append("current-system/sw/bin/zsh"), - Home: container.MustAbs("/data/data/org.chromium.Chromium"), ExtraPerms: []*ExtraPermConfig{ {Path: container.AbsFHSVarLib.Append("hakurei/u0"), Ensure: true, Execute: true}, {Path: container.AbsFHSVarLib.Append("hakurei/u0/org.chromium.Chromium"), Read: true, Write: true, Execute: true}, @@ -140,6 +128,19 @@ func Template() *Config { Target: container.MustAbs("/data/data/org.chromium.Chromium"), Write: true, Ensure: true}}, {&FSBind{Source: container.AbsFHSDev.Append("dri"), Device: true, Optional: true}}, }, + + Username: "chronos", + Shell: container.AbsFHSRun.Append("current-system/sw/bin/zsh"), + Home: container.MustAbs("/data/data/org.chromium.Chromium"), + + Path: container.AbsFHSRun.Append("current-system/sw/bin/chromium"), + Args: []string{ + "chromium", + "--ignore-gpu-blocklist", + "--disable-smooth-scrolling", + "--enable-features=UseOzonePlatform", + "--ozone-platform=wayland", + }, }, } } diff --git a/hst/hst_test.go b/hst/hst_test.go index 0c25fa7..90380e0 100644 --- a/hst/hst_test.go +++ b/hst/hst_test.go @@ -92,14 +92,6 @@ func TestAppError(t *testing.T) { func TestTemplate(t *testing.T) { const want = `{ "id": "org.chromium.Chromium", - "path": "/run/current-system/sw/bin/chromium", - "args": [ - "chromium", - "--ignore-gpu-blocklist", - "--disable-smooth-scrolling", - "--enable-features=UseOzonePlatform", - "--ozone-platform=wayland" - ], "enablements": { "wayland": true, "dbus": true, @@ -141,9 +133,6 @@ func TestTemplate(t *testing.T) { "broadcast": null, "filter": true }, - "username": "chronos", - "shell": "/run/current-system/sw/bin/zsh", - "home": "/data/data/org.chromium.Chromium", "extra_perms": [ { "ensure": true, @@ -238,6 +227,17 @@ func TestTemplate(t *testing.T) { "dev": true, "optional": true } + ], + "username": "chronos", + "shell": "/run/current-system/sw/bin/zsh", + "home": "/data/data/org.chromium.Chromium", + "path": "/run/current-system/sw/bin/chromium", + "args": [ + "chromium", + "--ignore-gpu-blocklist", + "--disable-smooth-scrolling", + "--enable-features=UseOzonePlatform", + "--ozone-platform=wayland" ] } }` diff --git a/internal/app/app_test.go b/internal/app/app_test.go index 0c54502..a031d43 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -37,7 +37,35 @@ func TestApp(t *testing.T) { }{ { "nixos permissive defaults no enablements", new(stubNixOS), - &hst.Config{Username: "chronos", Home: m("/home/chronos")}, + &hst.Config{Container: &hst.ContainerConfig{ + Userns: true, HostNet: true, HostAbstract: true, Tty: true, + + Filesystem: []hst.FilesystemConfigJSON{ + {FilesystemConfig: &hst.FSBind{ + Target: container.AbsFHSRoot, + Source: container.AbsFHSRoot, + Write: true, + Special: true, + }}, + {FilesystemConfig: &hst.FSBind{ + Source: container.AbsFHSDev.Append("kvm"), + Device: true, + Optional: true, + }}, + {FilesystemConfig: &hst.FSBind{ + Target: container.AbsFHSEtc, + Source: container.AbsFHSEtc, + Special: true, + }}, + }, + + Username: "chronos", + Shell: m("/run/current-system/sw/bin/zsh"), + Home: m("/home/chronos"), + + Path: m("/run/current-system/sw/bin/zsh"), + Args: []string{"/run/current-system/sw/bin/zsh"}, + }}, state.ID{ 0x4a, 0x45, 0x0b, 0x65, 0x96, 0xd7, 0xbc, 0x15, @@ -70,7 +98,6 @@ func TestApp(t *testing.T) { 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/nscd"), 8192, 0755). @@ -93,11 +120,8 @@ func TestApp(t *testing.T) { "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", @@ -130,6 +154,41 @@ func TestApp(t *testing.T) { Filter: true, }, Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse), + + Container: &hst.ContainerConfig{ + Userns: true, HostNet: true, HostAbstract: true, Tty: true, + + Filesystem: []hst.FilesystemConfigJSON{ + {FilesystemConfig: &hst.FSBind{ + Target: container.AbsFHSRoot, + Source: container.AbsFHSRoot, + Write: true, + Special: true, + }}, + {FilesystemConfig: &hst.FSBind{ + Source: container.AbsFHSDev.Append("dri"), + Device: true, + Optional: true, + }}, + {FilesystemConfig: &hst.FSBind{ + Source: container.AbsFHSDev.Append("kvm"), + Device: true, + Optional: true, + }}, + {FilesystemConfig: &hst.FSBind{ + Target: container.AbsFHSEtc, + Source: container.AbsFHSEtc, + Special: true, + }}, + }, + + Username: "chronos", + Shell: m("/run/current-system/sw/bin/zsh"), + Home: m("/home/chronos"), + + Path: m("/run/current-system/sw/bin/zsh"), + Args: []string{"zsh", "-c", "exec chromium "}, + }, }, state.ID{ 0xeb, 0xf0, 0x83, 0xd1, @@ -207,7 +266,6 @@ func TestApp(t *testing.T) { 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/nscd"), 8192, 0755). @@ -236,10 +294,7 @@ func TestApp(t *testing.T) { "nixos chromium direct wayland", new(stubNixOS), &hst.Config{ ID: "org.chromium.Chromium", - Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"), Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse), - Shell: m("/run/current-system/sw/bin/zsh"), - Container: &hst.ContainerConfig{ Userns: true, HostNet: true, MapRealUID: true, Env: nil, Filesystem: []hst.FilesystemConfigJSON{ @@ -257,6 +312,12 @@ func TestApp(t *testing.T) { 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}), }, + + Username: "u0_a1", + Shell: m("/run/current-system/sw/bin/zsh"), + Home: m("/var/lib/persist/module/hakurei/0/1"), + + Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"), }, SystemBus: &dbus.Config{ Talk: []string{"org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"}, @@ -278,8 +339,6 @@ func TestApp(t *testing.T) { }, DirectWayland: true, - Username: "u0_a1", - Home: m("/var/lib/persist/module/hakurei/0/1"), Identity: 1, Groups: []string{}, }, state.ID{ @@ -461,7 +520,6 @@ func (s stubOsFileReadCloser) Write([]byte) (int, error) { panic("attempting to func (s stubOsFileReadCloser) Stat() (fs.FileInfo, error) { panic("attempting to call Stat") } type stubNixOS struct { - lookPathErr map[string]error usernameErr map[string]error } @@ -617,21 +675,6 @@ func (k *stubNixOS) evalSymlinks(path string) (string, error) { } } -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": diff --git a/internal/app/dispatcher.go b/internal/app/dispatcher.go index 075b2ad..3c5e25c 100644 --- a/internal/app/dispatcher.go +++ b/internal/app/dispatcher.go @@ -45,9 +45,6 @@ type syscallDispatcher interface { // 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) @@ -81,8 +78,6 @@ 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) diff --git a/internal/app/dispatcher_test.go b/internal/app/dispatcher_test.go index 3a7b21b..934ab76 100644 --- a/internal/app/dispatcher_test.go +++ b/internal/app/dispatcher_test.go @@ -18,7 +18,6 @@ func (panicDispatcher) open(string) (osFile, error) { panic("unreachab func (panicDispatcher) readdir(string) ([]os.DirEntry, error) { panic("unreachable") } func (panicDispatcher) tempdir() string { panic("unreachable") } func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") } -func (panicDispatcher) lookPath(string) (string, error) { panic("unreachable") } func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") } func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") } func (panicDispatcher) overflowUid(container.Msg) int { panic("unreachable") } diff --git a/internal/app/finalise.go b/internal/app/finalise.go index 3d6ec38..55554eb 100644 --- a/internal/app/finalise.go +++ b/internal/app/finalise.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "io/fs" "maps" "os" "os/user" @@ -66,11 +65,8 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, } k.ctx = ctx - if config == nil { - return newWithMessage("invalid configuration") - } - if config.Home == nil { - return newWithMessage("invalid path to home directory") + if err := config.Validate(); err != nil { + return err } // TODO(ophestra): do not clobber during finalise @@ -102,6 +98,7 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, } } + // validation complete at this point s := outcomeState{ ID: id, Identity: config.Identity, @@ -110,81 +107,6 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, Container: config.Container, } - // permissive defaults - if s.Container == nil { - msg.Verbose("container configuration not supplied, PROCEED WITH CAUTION") - - if config.Shell == nil { - config.Shell = container.AbsFHSRoot.Append("bin", "sh") - shell, _ := k.lookupEnv("SHELL") - if a, err := container.NewAbs(shell); err == nil { - config.Shell = a - } - } - - // hsu clears the environment so resolve paths early - if config.Path == nil { - if len(config.Args) > 0 { - if p, err := k.lookPath(config.Args[0]); err != nil { - return &hst.AppError{Step: "look up executable file", Err: err} - } else if config.Path, err = container.NewAbs(p); err != nil { - return newWithMessageError(err.Error(), err) - } - } else { - config.Path = config.Shell - } - } - - conf := &hst.ContainerConfig{ - Userns: true, - HostNet: true, - HostAbstract: true, - Tty: true, - - Filesystem: []hst.FilesystemConfigJSON{ - // autoroot, includes the home directory - {FilesystemConfig: &hst.FSBind{ - Target: container.AbsFHSRoot, - Source: container.AbsFHSRoot, - Write: true, - Special: true, - }}, - }, - } - - // bind GPU stuff - if config.Enablements.Unwrap()&(hst.EX11|hst.EWayland) != 0 { - conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("dri"), Device: true, Optional: true}}) - } - // opportunistically bind kvm - conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("kvm"), Device: true, Optional: true}}) - - // hide nscd from container if present - nscd := container.AbsFHSVar.Append("run/nscd") - if _, err := k.stat(nscd.String()); !errors.Is(err, fs.ErrNotExist) { - conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSEphemeral{Target: nscd}}) - } - - // do autoetc last - conf.Filesystem = append(conf.Filesystem, - hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{ - Target: container.AbsFHSEtc, - Source: container.AbsFHSEtc, - Special: true, - }}, - ) - - s.Container = conf - } - - // late nil checks for pd behaviour - if config.Shell == nil { - return newWithMessage("invalid shell path") - } - if config.Path == nil { - return newWithMessage("invalid program path") - } - // enforce bounds and default early if s.Container.WaitDelay <= 0 { kp.waitDelay = hst.WaitDelayDefault @@ -210,14 +132,14 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, id *state.ID, { ops := []outcomeOp{ // must run first - &spParamsOp{Path: config.Path, Args: config.Args}, + &spParamsOp{}, // TODO(ophestra): move this late for #8 and #9 spFilesystemOp{}, spRuntimeOp{}, spTmpdirOp{}, - &spAccountOp{Home: config.Home, Username: config.Username, Shell: config.Shell}, + spAccountOp{}, } et := config.Enablements.Unwrap() diff --git a/internal/app/spaccount.go b/internal/app/spaccount.go index dbe3b03..71c7d7f 100644 --- a/internal/app/spaccount.go +++ b/internal/app/spaccount.go @@ -9,45 +9,38 @@ import ( ) // spAccountOp sets up user account emulation inside the container. -type spAccountOp struct { - // Inner directory to use as the home directory of the emulated user. - Home *container.Absolute - // String matching the default NAME_REGEX value from adduser to use as the username of the emulated user. - Username string - // Pathname of shell to use for the emulated user. - Shell *container.Absolute -} +type spAccountOp struct{} -func (s *spAccountOp) toSystem(*outcomeStateSys, *hst.Config) error { +func (s spAccountOp) toSystem(state *outcomeStateSys, _ *hst.Config) error { const fallbackUsername = "chronos" // do checks here to fail before fork/exec - if s.Home == nil || s.Shell == nil { + if state.Container == nil || state.Container.Home == nil || state.Container.Shell == nil { // unreachable return syscall.ENOTRECOVERABLE } - if s.Username == "" { - s.Username = fallbackUsername - } else if !isValidUsername(s.Username) { - return newWithMessage(fmt.Sprintf("invalid user name %q", s.Username)) + if state.Container.Username == "" { + state.Container.Username = fallbackUsername + } else if !isValidUsername(state.Container.Username) { + return newWithMessage(fmt.Sprintf("invalid user name %q", state.Container.Username)) } return nil } -func (s *spAccountOp) toContainer(state *outcomeStateParams) error { - state.params.Dir = s.Home - state.env["HOME"] = s.Home.String() - state.env["USER"] = s.Username - state.env["SHELL"] = s.Shell.String() +func (s spAccountOp) toContainer(state *outcomeStateParams) error { + state.params.Dir = state.Container.Home + state.env["HOME"] = state.Container.Home.String() + state.env["USER"] = state.Container.Username + state.env["SHELL"] = state.Container.Shell.String() state.params. Place(container.AbsFHSEtc.Append("passwd"), - []byte(s.Username+":x:"+ + []byte(state.Container.Username+":x:"+ state.mapuid.String()+":"+ state.mapgid.String()+ ":Hakurei:"+ - s.Home.String()+":"+ - s.Shell.String()+"\n")). + state.Container.Home.String()+":"+ + state.Container.Shell.String()+"\n")). Place(container.AbsFHSEtc.Append("group"), []byte("hakurei:x:"+state.mapgid.String()+":\n")) diff --git a/internal/app/spcontainer.go b/internal/app/spcontainer.go index cb5d938..4381fd7 100644 --- a/internal/app/spcontainer.go +++ b/internal/app/spcontainer.go @@ -18,11 +18,6 @@ const varRunNscd = container.FHSVar + "run/nscd" // spParamsOp initialises unordered fields of [container.Params] and the optional root filesystem. // This outcomeOp is hardcoded to always run first. type spParamsOp struct { - // Copied from the [hst.Config] field of the same name. - Path *container.Absolute `json:"path,omitempty"` - // Copied from the [hst.Config] field of the same name. - Args []string `json:"args"` - // Value of $TERM, stored during toSystem. Term string // Whether $TERM is set, stored during toSystem. @@ -49,15 +44,15 @@ func (s *spParamsOp) toContainer(state *outcomeStateParams) error { state.params.HostNet = state.Container.HostNet state.params.HostAbstract = state.Container.HostAbstract - if s.Path == nil { + if state.Container.Path == nil { return newWithMessage("invalid program path") } - state.params.Path = s.Path + state.params.Path = state.Container.Path - if len(s.Args) == 0 { - state.params.Args = []string{s.Path.String()} + if len(state.Container.Args) == 0 { + state.params.Args = []string{state.Container.Path.String()} } else { - state.params.Args = s.Args + state.params.Args = state.Container.Args } // the container is canceled when shim is requested to exit or receives an interrupt or termination signal; diff --git a/nixos.nix b/nixos.nix index e2382bc..6dd5180 100644 --- a/nixos.nix +++ b/nixos.nix @@ -105,27 +105,11 @@ in isGraphical = if app.gpu != null then app.gpu else app.enablements.wayland || app.enablements.x11; conf = { - path = - if app.path == null then - pkgs.writeScript "${app.name}-start" '' - #!${pkgs.zsh}${pkgs.zsh.shellPath} - ${script} - '' - else - app.path; - args = if app.args == null then [ "${app.name}-start" ] else app.args; - inherit id; - + inherit (app) identity groups enablements; inherit (dbusConfig) session_bus system_bus; direct_wayland = app.insecureWayland; - username = getsubname fid app.identity; - home = getsubhome fid app.identity; - - inherit (cfg) shell; - inherit (app) identity groups enablements; - container = { inherit (app) wait_delay @@ -219,6 +203,20 @@ in ensure = true; } ]; + + username = getsubname fid app.identity; + inherit (cfg) shell; + home = getsubhome fid app.identity; + + path = + if app.path == null then + pkgs.writeScript "${app.name}-start" '' + #!${pkgs.zsh}${pkgs.zsh.shellPath} + ${script} + '' + else + app.path; + args = if app.args == null then [ "${app.name}-start" ] else app.args; }; }; diff --git a/test/sandbox/case/pd.nix b/test/sandbox/case/pd.nix index ad97c99..140398e 100644 --- a/test/sandbox/case/pd.nix +++ b/test/sandbox/case/pd.nix @@ -182,7 +182,6 @@ (ent "/" "/dev/mqueue" "rw,nosuid,nodev,noexec,relatime" "mqueue" "mqueue" "rw") (ent "/" "/dev/shm" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,uid=1000000,gid=1000000") (ent "/kvm" "/dev/kvm" "rw,nosuid" "devtmpfs" "devtmpfs" ignore) - (ent "/" "/run/nscd" "ro,nosuid,nodev,relatime" "tmpfs" "readonly" "ro,mode=755,uid=1000000,gid=1000000") (ent "/etc" ignore "ro,nosuid,nodev,relatime" "ext4" "/dev/disk/by-label/nixos" "rw") (ent "/" "/run/user/1000" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000") (ent "/" "/run/nscd" "rw,nosuid,nodev,relatime" "tmpfs" "ephemeral" "rw,size=8k,mode=755,uid=1000000,gid=1000000") diff --git a/test/test.py b/test/test.py index a1e6b0f..f4e3493 100644 --- a/test/test.py +++ b/test/test.py @@ -61,14 +61,14 @@ def check_state(name, enablements): config = instance['config'] command = f"{name}-start" - if not (config['path'].startswith("/nix/store/")) or not (config['path'].endswith(command)): + if not (config['container']['path'].startswith("/nix/store/")) or not (config['container']['path'].endswith(command)): raise Exception(f"unexpected path {config['path']}") - if len(config['args']) != 1 or config['args'][0] != command: + if len(config['container']['args']) != 1 or config['container']['args'][0] != command: raise Exception(f"unexpected args {config['args']}") if config['enablements'] != enablements: - raise Exception(f"unexpected enablements {instance['config']['enablements']}") + raise Exception(f"unexpected enablements {config['enablements']['enablements']}") def hakurei(command): @@ -104,7 +104,7 @@ if denyOutputVerbose != "hsu: uid 1001 is not in the hsurc file\nhakurei: *canno raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}") # Verify timeout behaviour: -machine.succeed('sudo -u alice -i hakurei-check-linger-timeout > /var/tmp/linger-stdout 2> /var/tmp/linger-stderr') +machine.succeed('sudo -u alice -i hakurei-check-linger-timeout > /var/tmp/linger-stdout 2> /var/tmp/linger-stderr || (cat /var/tmp/linger-stderr; false)') linger_stdout = machine.succeed("cat /var/tmp/linger-stdout") linger_stderr = machine.succeed("cat /var/tmp/linger-stderr") if linger_stdout != "":