hst: expose scheduling policy
All checks were successful
Test / ShareFS (push) Successful in 39s
Test / Sandbox (push) Successful in 45s
Test / Hakurei (push) Successful in 50s
Test / Sandbox (race detector) (push) Successful in 45s
Test / Hakurei (race detector) (push) Successful in 49s
Test / Create distribution (push) Successful in 59s
Test / Flake checks (push) Successful in 1m19s

This is primarily useful for poorly written music players for now.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-03-11 21:06:44 +09:00
parent 5c540f90aa
commit 04e6bc3c5c
11 changed files with 91 additions and 13 deletions

View File

@@ -88,6 +88,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
flagGroups command.RepeatableFlag
flagHomeDir string
flagUserName string
flagSched string
flagPrivateRuntime, flagPrivateTmpdir bool
@@ -131,7 +132,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
log.Fatal(optionalErrorUnwrap(err))
return err
} else if progPath, err = check.NewAbs(p); err != nil {
log.Fatal(err.Error())
log.Fatal(err)
return err
}
}
@@ -150,7 +151,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
et |= hst.EPipeWire
}
config := &hst.Config{
config := hst.Config{
ID: flagID,
Identity: flagIdentity,
Groups: flagGroups,
@@ -177,6 +178,10 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
},
}
if err := config.SchedPolicy.UnmarshalText([]byte(flagSched)); err != nil {
log.Fatal(err)
}
// bind GPU stuff
if et&(hst.EX11|hst.EWayland) != 0 {
config.Container.Filesystem = append(config.Container.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{
@@ -214,7 +219,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
homeDir = passwd.HomeDir
}
if a, err := check.NewAbs(homeDir); err != nil {
log.Fatal(err.Error())
log.Fatal(err)
return err
} else {
config.Container.Home = a
@@ -234,11 +239,11 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
config.SessionBus = dbus.NewConfig(flagID, true, flagDBusMpris)
} else {
if f, err := os.Open(flagDBusConfigSession); err != nil {
log.Fatal(err.Error())
log.Fatal(err)
} else {
decodeJSON(log.Fatal, "load session bus proxy config", f, &config.SessionBus)
if err = f.Close(); err != nil {
log.Fatal(err.Error())
log.Fatal(err)
}
}
}
@@ -246,11 +251,11 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
// system bus proxy is optional
if flagDBusConfigSystem != "nil" {
if f, err := os.Open(flagDBusConfigSystem); err != nil {
log.Fatal(err.Error())
log.Fatal(err)
} else {
decodeJSON(log.Fatal, "load system bus proxy config", f, &config.SystemBus)
if err = f.Close(); err != nil {
log.Fatal(err.Error())
log.Fatal(err)
}
}
}
@@ -266,7 +271,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
}
}
outcome.Main(ctx, msg, config, -1)
outcome.Main(ctx, msg, &config, -1)
panic("unreachable")
}).
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
@@ -287,6 +292,8 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
"Container home directory").
Flag(&flagUserName, "u", command.StringFlag("chronos"),
"Passwd user name within sandbox").
Flag(&flagSched, "sched", command.StringFlag(""),
"Scheduling policy to set for the container").
Flag(&flagPrivateRuntime, "private-runtime", command.BoolFlag(false),
"Do not share XDG_RUNTIME_DIR between containers under the same identity").
Flag(&flagPrivateTmpdir, "private-tmpdir", command.BoolFlag(false),

View File

@@ -36,7 +36,7 @@ Commands:
},
{
"run", []string{"run", "-h"}, `
Usage: hakurei run [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS]
Usage: hakurei run [-h | --help] [--dbus-config <value>] [--dbus-system <value>] [--mpris] [--dbus-log] [--id <value>] [-a <int>] [-g <value>] [-d <value>] [-u <value>] [--sched <value>] [--private-runtime] [--private-tmpdir] [--wayland] [-X] [--dbus] [--pipewire] [--pulse] COMMAND [OPTIONS]
Flags:
-X Enable direct connection to X11
@@ -66,6 +66,8 @@ Flags:
Do not share TMPDIR between containers under the same identity
-pulse
Enable PulseAudio compatibility daemon
-sched string
Scheduling policy to set for the container
-u string
Passwd user name within sandbox (default "chronos")
-wayland

View File

@@ -6,6 +6,7 @@ import (
"strings"
"hakurei.app/container/check"
"hakurei.app/container/std"
)
// Config configures an application container.
@@ -103,6 +104,10 @@ type Config struct {
// Init user namespace supplementary groups inherited by all container processes.
Groups []string `json:"groups"`
// Scheduling policy to set for the container. The zero value retains the
// current scheduling policy.
SchedPolicy std.SchedPolicy `json:"sched_policy,omitempty"`
// High level configuration applied to the underlying [container].
Container *ContainerConfig `json:"container"`
}
@@ -116,6 +121,10 @@ var (
// [Config.Identity] value.
ErrIdentityBounds = errors.New("identity out of bounds")
// ErrSchedPolicyBounds is returned by [Config.Validate] for an out of bounds
// [Config.SchedPolicy] value.
ErrSchedPolicyBounds = errors.New("scheduling policy out of bounds")
// ErrEnviron is returned by [Config.Validate] if an environment variable
// name contains '=' or NUL.
ErrEnviron = errors.New("invalid environment variable name")
@@ -138,6 +147,13 @@ func (config *Config) Validate() error {
Msg: "identity " + strconv.Itoa(config.Identity) + " out of range"}
}
if config.SchedPolicy < 0 || config.SchedPolicy > std.SCHED_LAST {
return &AppError{Step: "validate configuration", Err: ErrSchedPolicyBounds,
Msg: "scheduling policy " +
strconv.Itoa(int(config.SchedPolicy)) +
" out of range"}
}
if err := config.SessionBus.CheckInterfaces("session"); err != nil {
return err
}

View File

@@ -22,6 +22,10 @@ func TestConfigValidate(t *testing.T) {
Msg: "identity -1 out of range"}},
{"identity upper", &hst.Config{Identity: 10000}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
Msg: "identity 10000 out of range"}},
{"sched lower", &hst.Config{SchedPolicy: -1}, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
Msg: "scheduling policy -1 out of range"}},
{"sched upper", &hst.Config{SchedPolicy: 0xcafe}, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
Msg: "scheduling policy 51966 out of range"}},
{"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}},
&hst.BadInterfaceError{Interface: "", Segment: "session"}},
{"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}},

View File

@@ -99,12 +99,17 @@ func newOutcomeState(k syscallDispatcher, msg message.Msg, id *hst.ID, config *h
Shim: &shimParams{
PrivPID: k.getpid(),
Verbose: msg.IsVerbose(),
SchedPolicy: config.SchedPolicy,
},
ID: id,
Identity: config.Identity,
UserID: hsu.MustID(msg),
Paths: env.CopyPathsFunc(k.fatalf, k.tempdir, func(key string) string { v, _ := k.lookupEnv(key); return v }),
ID: id,
Identity: config.Identity,
UserID: hsu.MustID(msg),
Paths: env.CopyPathsFunc(k.fatalf, k.tempdir, func(key string) string {
v, _ := k.lookupEnv(key)
return v
}),
Container: config.Container,
}

View File

@@ -274,6 +274,7 @@ func shimEntrypoint(k syscallDispatcher) {
cancelContainer.Store(&stop)
sp := shimPrivate{k: k, id: state.id}
z := container.New(ctx, msg)
z.SchedPolicy = state.Shim.SchedPolicy
z.Params = *stateParams.params
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr

View File

@@ -139,6 +139,7 @@ in
inherit (app) identity groups enablements;
inherit (dbusConfig) session_bus system_bus;
direct_wayland = app.insecureWayland;
sched_policy = app.schedPolicy;
container = {
inherit (app)

View File

@@ -98,6 +98,7 @@ in
ints
str
bool
enum
package
anything
submodule
@@ -237,6 +238,22 @@ in
};
hostAbstract = mkEnableOption "share abstract unix socket scope";
schedPolicy = mkOption {
type = nullOr (enum [
"fifo"
"rr"
"batch"
"idle"
"deadline"
"ext"
]);
default = null;
description = ''
Scheduling policy to set for the container.
The zero value retains the current scheduling policy.
'';
};
nix = mkEnableOption "nix daemon access";
mapRealUid = mkEnableOption "mapping to priv-user uid";
device = mkEnableOption "access to all devices";

View File

@@ -28,6 +28,15 @@
# Automatically login on tty1 as a normal user:
services.getty.autologinUser = "alice";
security.pam.loginLimits = [
{
domain = "@users";
item = "rtprio";
type = "-";
value = 1;
}
];
environment = {
systemPackages = with pkgs; [
# For D-Bus tests:

View File

@@ -23,6 +23,14 @@
security = {
sudo.wheelNeedsPassword = false;
rtkit.enable = true;
pam.loginLimits = [
{
domain = "@users";
item = "rtprio";
type = "-";
value = 1;
}
];
};
services = {

View File

@@ -206,6 +206,14 @@ machine.wait_until_fails("pgrep foot", timeout=5)
machine.wait_for_file("/tmp/shim-cont-unexpected-pid")
print(machine.succeed('grep "shim: got SIGCONT from unexpected process$" /tmp/shim-cont-unexpected-pid'))
# Check setscheduler:
sched_unset = int(machine.succeed("sudo -u alice -i hakurei -v run cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
if sched_unset != 0:
raise Exception(f"unexpected unset policy: {sched_unset}")
sched_idle = int(machine.succeed("sudo -u alice -i hakurei -v run --sched=idle cat /proc/self/sched | grep '^policy' | tr -d ' ' | cut -d ':' -f 2"))
if sched_idle != 5:
raise Exception(f"unexpected idle policy: {sched_idle}")
# Start app (foot) with Wayland enablement:
swaymsg("exec ne-foot")
wait_for_window(f"u0_a{hakurei_identity(0)}@machine")