hst/enablement: move bits from system
All checks were successful
Test / Create distribution (push) Successful in 54s
Test / Sandbox (push) Successful in 2m33s
Test / Hakurei (push) Successful in 3m36s
Test / Hpkg (push) Successful in 4m30s
Test / Sandbox (race detector) (push) Successful in 4m48s
Test / Hakurei (race detector) (push) Successful in 5m47s
Test / Flake checks (push) Successful in 1m40s

This is part of the hst API, should not be in the implementation package.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-09-29 06:32:15 +09:00
parent dc467493d8
commit 44ba7a5f02
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
22 changed files with 199 additions and 198 deletions

View File

@ -17,7 +17,6 @@ import (
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/internal/app" "hakurei.app/internal/app"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/system"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
) )
@ -128,18 +127,18 @@ func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningE
config.Home = a config.Home = a
} }
var e system.Enablement var e hst.Enablement
if flagWayland { if flagWayland {
e |= system.EWayland e |= hst.EWayland
} }
if flagX11 { if flagX11 {
e |= system.EX11 e |= hst.EX11
} }
if flagDBus { if flagDBus {
e |= system.EDBus e |= hst.EDBus
} }
if flagPulse { if flagPulse {
e |= system.EPulse e |= hst.EPulse
} }
config.Enablements = hst.NewEnablements(e) config.Enablements = hst.NewEnablements(e)

View File

@ -2,15 +2,56 @@ package hst
import ( import (
"encoding/json" "encoding/json"
"fmt"
"strings"
"syscall" "syscall"
"hakurei.app/system"
) )
// NewEnablements returns the address of [system.Enablement] as [Enablements]. // Enablement represents an optional host service to export to the target user.
func NewEnablements(e system.Enablement) *Enablements { return (*Enablements)(&e) } type Enablement byte
// enablementsJSON is the [json] representation of the [system.Enablement] bit field. const (
EWayland Enablement = 1 << iota
EX11
EDBus
EPulse
EM
)
func (e Enablement) String() string {
switch e {
case 0:
return "(no enablements)"
case EWayland:
return "wayland"
case EX11:
return "x11"
case EDBus:
return "dbus"
case EPulse:
return "pulseaudio"
default:
buf := new(strings.Builder)
buf.Grow(32)
for i := Enablement(1); i < EM; i <<= 1 {
if e&i != 0 {
buf.WriteString(", " + i.String())
}
}
if buf.Len() == 0 {
return fmt.Sprintf("e%x", byte(e))
}
return strings.TrimPrefix(buf.String(), ", ")
}
}
// NewEnablements returns the address of [Enablement] as [Enablements].
func NewEnablements(e Enablement) *Enablements { return (*Enablements)(&e) }
// enablementsJSON is the [json] representation of the [Enablement] bit field.
type enablementsJSON struct { type enablementsJSON struct {
Wayland bool `json:"wayland,omitempty"` Wayland bool `json:"wayland,omitempty"`
X11 bool `json:"x11,omitempty"` X11 bool `json:"x11,omitempty"`
@ -18,15 +59,15 @@ type enablementsJSON struct {
Pulse bool `json:"pulse,omitempty"` Pulse bool `json:"pulse,omitempty"`
} }
// Enablements is the [json] adapter for [system.Enablement]. // Enablements is the [json] adapter for [Enablement].
type Enablements system.Enablement type Enablements Enablement
// Unwrap returns the underlying [system.Enablement]. // Unwrap returns the underlying [Enablement].
func (e *Enablements) Unwrap() system.Enablement { func (e *Enablements) Unwrap() Enablement {
if e == nil { if e == nil {
return 0 return 0
} }
return system.Enablement(*e) return Enablement(*e)
} }
func (e *Enablements) MarshalJSON() ([]byte, error) { func (e *Enablements) MarshalJSON() ([]byte, error) {
@ -34,10 +75,10 @@ func (e *Enablements) MarshalJSON() ([]byte, error) {
return nil, syscall.EINVAL return nil, syscall.EINVAL
} }
return json.Marshal(&enablementsJSON{ return json.Marshal(&enablementsJSON{
Wayland: system.Enablement(*e)&system.EWayland != 0, Wayland: Enablement(*e)&EWayland != 0,
X11: system.Enablement(*e)&system.EX11 != 0, X11: Enablement(*e)&EX11 != 0,
DBus: system.Enablement(*e)&system.EDBus != 0, DBus: Enablement(*e)&EDBus != 0,
Pulse: system.Enablement(*e)&system.EPulse != 0, Pulse: Enablement(*e)&EPulse != 0,
}) })
} }
@ -51,18 +92,18 @@ func (e *Enablements) UnmarshalJSON(data []byte) error {
return err return err
} }
var ve system.Enablement var ve Enablement
if v.Wayland { if v.Wayland {
ve |= system.EWayland ve |= EWayland
} }
if v.X11 { if v.X11 {
ve |= system.EX11 ve |= EX11
} }
if v.DBus { if v.DBus {
ve |= system.EDBus ve |= EDBus
} }
if v.Pulse { if v.Pulse {
ve |= system.EPulse ve |= EPulse
} }
*e = Enablements(ve) *e = Enablements(ve)
return nil return nil

View File

@ -7,9 +7,44 @@ import (
"testing" "testing"
"hakurei.app/hst" "hakurei.app/hst"
"hakurei.app/system"
) )
func TestEnablementString(t *testing.T) {
testCases := []struct {
flags hst.Enablement
want string
}{
{0, "(no enablements)"},
{hst.EWayland, "wayland"},
{hst.EX11, "x11"},
{hst.EDBus, "dbus"},
{hst.EPulse, "pulseaudio"},
{hst.EWayland | hst.EX11, "wayland, x11"},
{hst.EWayland | hst.EDBus, "wayland, dbus"},
{hst.EWayland | hst.EPulse, "wayland, pulseaudio"},
{hst.EX11 | hst.EDBus, "x11, dbus"},
{hst.EX11 | hst.EPulse, "x11, pulseaudio"},
{hst.EDBus | hst.EPulse, "dbus, pulseaudio"},
{hst.EWayland | hst.EX11 | hst.EDBus, "wayland, x11, dbus"},
{hst.EWayland | hst.EX11 | hst.EPulse, "wayland, x11, pulseaudio"},
{hst.EWayland | hst.EDBus | hst.EPulse, "wayland, dbus, pulseaudio"},
{hst.EX11 | hst.EDBus | hst.EPulse, "x11, dbus, pulseaudio"},
{hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse, "wayland, x11, dbus, pulseaudio"},
{1 << 5, "e20"},
{1 << 6, "e40"},
{1 << 7, "e80"},
}
for _, tc := range testCases {
t.Run(tc.want, func(t *testing.T) {
if got := tc.flags.String(); got != tc.want {
t.Errorf("String: %q, want %q", got, tc.want)
}
})
}
}
func TestEnablements(t *testing.T) { func TestEnablements(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
@ -19,11 +54,11 @@ func TestEnablements(t *testing.T) {
}{ }{
{"nil", nil, "null", `{"value":null,"magic":3236757504}`}, {"nil", nil, "null", `{"value":null,"magic":3236757504}`},
{"zero", hst.NewEnablements(0), `{}`, `{"value":{},"magic":3236757504}`}, {"zero", hst.NewEnablements(0), `{}`, `{"value":{},"magic":3236757504}`},
{"wayland", hst.NewEnablements(system.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`}, {"wayland", hst.NewEnablements(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
{"x11", hst.NewEnablements(system.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`}, {"x11", hst.NewEnablements(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
{"dbus", hst.NewEnablements(system.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`}, {"dbus", hst.NewEnablements(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
{"pulse", hst.NewEnablements(system.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`}, {"pulse", hst.NewEnablements(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
{"all", hst.NewEnablements(system.EWayland | system.EX11 | system.EDBus | system.EPulse), `{"wayland":true,"x11":true,"dbus":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pulse":true},"magic":3236757504}`}, {"all", hst.NewEnablements(hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse), `{"wayland":true,"x11":true,"dbus":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pulse":true},"magic":3236757504}`},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -88,7 +123,7 @@ func TestEnablements(t *testing.T) {
}) })
t.Run("val", func(t *testing.T) { t.Run("val", func(t *testing.T) {
if got := hst.NewEnablements(system.EWayland | system.EPulse).Unwrap(); got != system.EWayland|system.EPulse { if got := hst.NewEnablements(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse {
t.Errorf("Unwrap: %v", got) t.Errorf("Unwrap: %v", got)
} }
}) })

View File

@ -8,7 +8,6 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
"hakurei.app/system"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
) )
@ -71,7 +70,7 @@ func Template() *Config {
"--ozone-platform=wayland", "--ozone-platform=wayland",
}, },
Enablements: NewEnablements(system.EWayland | system.EDBus | system.EPulse), Enablements: NewEnablements(EWayland | EDBus | EPulse),
SessionBus: &dbus.Config{ SessionBus: &dbus.Config{
See: nil, See: nil,

View File

@ -121,7 +121,7 @@ func TestApp(t *testing.T) {
}, },
Filter: true, Filter: true,
}, },
Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse), Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
}, },
state.ID{ state.ID{
0xeb, 0xf0, 0x83, 0xd1, 0xeb, 0xf0, 0x83, 0xd1,
@ -229,7 +229,7 @@ func TestApp(t *testing.T) {
&hst.Config{ &hst.Config{
ID: "org.chromium.Chromium", ID: "org.chromium.Chromium",
Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"), Path: m("/nix/store/yqivzpzzn7z5x0lq9hmbzygh45d8rhqd-chromium-start"),
Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse), Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse),
Shell: m("/run/current-system/sw/bin/zsh"), Shell: m("/run/current-system/sw/bin/zsh"),
Container: &hst.ContainerConfig{ Container: &hst.ContainerConfig{
@ -288,7 +288,7 @@ func TestApp(t *testing.T) {
Ensure("/tmp/hakurei.0/tmpdir/1", 01700).UpdatePermType(system.User, "/tmp/hakurei.0/tmpdir/1", acl.Read, acl.Write, 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/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 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). UpdatePermType(hst.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). 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"). Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256). CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256).

View File

@ -241,7 +241,7 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.C
} }
// bind GPU stuff // bind GPU stuff
if config.Enablements.Unwrap()&(system.EX11|system.EWayland) != 0 { 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}}) conf.Filesystem = append(conf.Filesystem, hst.FilesystemConfigJSON{FilesystemConfig: &hst.FSBind{Source: container.AbsFHSDev.Append("dri"), Device: true, Optional: true}})
} }
// opportunistically bind kvm // opportunistically bind kvm
@ -353,7 +353,7 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.C
k.env[term] = t k.env[term] = t
} }
if config.Enablements.Unwrap()&system.EWayland != 0 { if config.Enablements.Unwrap()&hst.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 {
@ -382,11 +382,11 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.C
msg.Verbose("direct wayland access, PROCEED WITH CAUTION") msg.Verbose("direct wayland access, PROCEED WITH CAUTION")
share.ensureRuntimeDir() share.ensureRuntimeDir()
k.container.Bind(socketPath, innerPath, 0) k.container.Bind(socketPath, innerPath, 0)
k.sys.UpdatePermType(system.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute) k.sys.UpdatePermType(hst.EWayland, socketPath.String(), acl.Read, acl.Write, acl.Execute)
} }
} }
if config.Enablements.Unwrap()&system.EX11 != 0 { if config.Enablements.Unwrap()&hst.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 {
@ -410,7 +410,7 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.C
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 {
k.sys.UpdatePermType(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute) k.sys.UpdatePermType(hst.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()
} }
@ -423,7 +423,7 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.C
} }
} }
if config.Enablements.Unwrap()&system.EPulse != 0 { if config.Enablements.Unwrap()&hst.EPulse != 0 {
// PulseAudio runtime directory (usually `/run/user/%d/pulse`) // PulseAudio runtime directory (usually `/run/user/%d/pulse`)
pulseRuntimeDir := share.sc.RuntimePath.Append("pulse") pulseRuntimeDir := share.sc.RuntimePath.Append("pulse")
// PulseAudio socket (usually `/run/user/%d/pulse/native`) // PulseAudio socket (usually `/run/user/%d/pulse/native`)
@ -527,7 +527,7 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.C
} }
} }
if config.Enablements.Unwrap()&system.EDBus != 0 { if config.Enablements.Unwrap()&hst.EDBus != 0 {
// ensure dbus session bus defaults // ensure dbus session bus defaults
if config.SessionBus == nil { if config.SessionBus == nil {
config.SessionBus = dbus.NewConfig(config.ID, true, true) config.SessionBus = dbus.NewConfig(config.ID, true, true)

View File

@ -13,6 +13,7 @@ import (
"time" "time"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/hst"
"hakurei.app/internal" "hakurei.app/internal"
"hakurei.app/internal/app/state" "hakurei.app/internal/app/state"
"hakurei.app/system" "hakurei.app/system"
@ -146,7 +147,7 @@ func (ms mainState) beforeExit(isFault bool) {
} }
} }
var rt system.Enablement var rt hst.Enablement
if states, err := c.Load(); err != nil { if states, err := c.Load(); err != nil {
// 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
@ -182,7 +183,7 @@ func (ms mainState) beforeExit(isFault bool) {
} }
} }
ec |= rt ^ (system.EWayland | system.EX11 | system.EDBus | system.EPulse) ec |= rt ^ (hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse)
if ms.IsVerbose() { if ms.IsVerbose() {
if ec > 0 { if ec > 0 {
ms.Verbose("reverting operations scope", system.TypeString(ec)) ms.Verbose("reverting operations scope", system.TypeString(ec))

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"slices" "slices"
"hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/system/acl"
) )
@ -16,19 +17,19 @@ func (sys *I) UpdatePerm(path string, perms ...acl.Perm) *I {
} }
// UpdatePermType maintains [acl.Perms] on a file until its [Enablement] is no longer satisfied. // UpdatePermType maintains [acl.Perms] on a file until its [Enablement] is no longer satisfied.
func (sys *I) UpdatePermType(et Enablement, path string, perms ...acl.Perm) *I { func (sys *I) UpdatePermType(et hst.Enablement, path string, perms ...acl.Perm) *I {
sys.ops = append(sys.ops, &aclUpdateOp{et, path, perms}) sys.ops = append(sys.ops, &aclUpdateOp{et, path, perms})
return sys return sys
} }
// aclUpdateOp implements [I.UpdatePermType]. // aclUpdateOp implements [I.UpdatePermType].
type aclUpdateOp struct { type aclUpdateOp struct {
et Enablement et hst.Enablement
path string path string
perms acl.Perms perms acl.Perms
} }
func (a *aclUpdateOp) Type() Enablement { return a.et } func (a *aclUpdateOp) Type() hst.Enablement { return a.et }
func (a *aclUpdateOp) apply(sys *I) error { func (a *aclUpdateOp) apply(sys *I) error {
sys.msg.Verbose("applying ACL", a) sys.msg.Verbose("applying ACL", a)

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/system/acl"
) )
@ -94,9 +95,9 @@ func TestACLUpdateOp(t *testing.T) {
}, stub.Expect{}}, }, stub.Expect{}},
{"wayland", 0xdeadbeef, func(_ *testing.T, sys *I) { {"wayland", 0xdeadbeef, func(_ *testing.T, sys *I) {
sys.UpdatePermType(EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute) sys.UpdatePermType(hst.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute)
}, []Op{ }, []Op{
&aclUpdateOp{EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, &aclUpdateOp{hst.EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
}, stub.Expect{}}, }, stub.Expect{}},
}) })
@ -106,34 +107,34 @@ func TestACLUpdateOp(t *testing.T) {
{"et differs", {"et differs",
&aclUpdateOp{ &aclUpdateOp{
EWayland, "/run/user/1971/wayland-0", hst.EWayland, "/run/user/1971/wayland-0",
[]acl.Perm{acl.Read, acl.Write, acl.Execute}, []acl.Perm{acl.Read, acl.Write, acl.Execute},
}, &aclUpdateOp{ }, &aclUpdateOp{
EX11, "/run/user/1971/wayland-0", hst.EX11, "/run/user/1971/wayland-0",
[]acl.Perm{acl.Read, acl.Write, acl.Execute}, []acl.Perm{acl.Read, acl.Write, acl.Execute},
}, false}, }, false},
{"path differs", &aclUpdateOp{ {"path differs", &aclUpdateOp{
EWayland, "/run/user/1971/wayland-0", hst.EWayland, "/run/user/1971/wayland-0",
[]acl.Perm{acl.Read, acl.Write, acl.Execute}, []acl.Perm{acl.Read, acl.Write, acl.Execute},
}, &aclUpdateOp{ }, &aclUpdateOp{
EWayland, "/run/user/1971/wayland-1", hst.EWayland, "/run/user/1971/wayland-1",
[]acl.Perm{acl.Read, acl.Write, acl.Execute}, []acl.Perm{acl.Read, acl.Write, acl.Execute},
}, false}, }, false},
{"perms differs", &aclUpdateOp{ {"perms differs", &aclUpdateOp{
EWayland, "/run/user/1971/wayland-0", hst.EWayland, "/run/user/1971/wayland-0",
[]acl.Perm{acl.Read, acl.Write, acl.Execute}, []acl.Perm{acl.Read, acl.Write, acl.Execute},
}, &aclUpdateOp{ }, &aclUpdateOp{
EWayland, "/run/user/1971/wayland-0", hst.EWayland, "/run/user/1971/wayland-0",
[]acl.Perm{acl.Read, acl.Write}, []acl.Perm{acl.Read, acl.Write},
}, false}, }, false},
{"equals", &aclUpdateOp{ {"equals", &aclUpdateOp{
EWayland, "/run/user/1971/wayland-0", hst.EWayland, "/run/user/1971/wayland-0",
[]acl.Perm{acl.Read, acl.Write, acl.Execute}, []acl.Perm{acl.Read, acl.Write, acl.Execute},
}, &aclUpdateOp{ }, &aclUpdateOp{
EWayland, "/run/user/1971/wayland-0", hst.EWayland, "/run/user/1971/wayland-0",
[]acl.Perm{acl.Read, acl.Write, acl.Execute}, []acl.Perm{acl.Read, acl.Write, acl.Execute},
}, true}, }, true},
}) })
@ -160,23 +161,23 @@ func TestACLUpdateOp(t *testing.T) {
`--x type: user path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/2"`}, `--x type: user path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/2"`},
{"wayland", {"wayland",
&aclUpdateOp{EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", []acl.Perm{acl.Read, acl.Write}}, &aclUpdateOp{hst.EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", []acl.Perm{acl.Read, acl.Write}},
EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", hst.EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland",
`rw- type: wayland path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland"`}, `rw- type: wayland path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland"`},
{"x11", {"x11",
&aclUpdateOp{EX11, "/tmp/.X11-unix/X0", []acl.Perm{acl.Read, acl.Execute}}, &aclUpdateOp{hst.EX11, "/tmp/.X11-unix/X0", []acl.Perm{acl.Read, acl.Execute}},
EX11, "/tmp/.X11-unix/X0", hst.EX11, "/tmp/.X11-unix/X0",
`r-x type: x11 path: "/tmp/.X11-unix/X0"`}, `r-x type: x11 path: "/tmp/.X11-unix/X0"`},
{"dbus", {"dbus",
&aclUpdateOp{EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", []acl.Perm{acl.Write, acl.Execute}}, &aclUpdateOp{hst.EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", []acl.Perm{acl.Write, acl.Execute}},
EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", hst.EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus",
`-wx type: dbus path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus"`}, `-wx type: dbus path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus"`},
{"pulseaudio", {"pulseaudio",
&aclUpdateOp{EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, &aclUpdateOp{hst.EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", []acl.Perm{acl.Read, acl.Write, acl.Execute}},
EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", hst.EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse",
`rwx type: pulseaudio path: "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse"`}, `rwx type: pulseaudio path: "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse"`},
}) })
} }

View File

@ -12,6 +12,7 @@ import (
"syscall" "syscall"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/hst"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
) )
@ -81,7 +82,7 @@ type dbusProxyOp struct {
system bool system bool
} }
func (d *dbusProxyOp) Type() Enablement { return Process } func (d *dbusProxyOp) Type() hst.Enablement { return Process }
func (d *dbusProxyOp) apply(sys *I) error { func (d *dbusProxyOp) apply(sys *I) error {
sys.msg.Verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0]) sys.msg.Verbosef("session bus proxy on %q for upstream %q", d.final.Session[1], d.final.Session[0])

View File

@ -12,6 +12,7 @@ import (
"unsafe" "unsafe"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/system/acl"
"hakurei.app/system/dbus" "hakurei.app/system/dbus"
"hakurei.app/system/internal/xcb" "hakurei.app/system/internal/xcb"
@ -26,7 +27,7 @@ func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
type opBehaviourTestCase struct { type opBehaviourTestCase struct {
name string name string
uid int uid int
ec Enablement ec hst.Enablement
op Op op Op
apply []stub.Call apply []stub.Call
@ -142,7 +143,7 @@ type opMetaTestCase struct {
name string name string
op Op op Op
wantType Enablement wantType hst.Enablement
wantPath string wantPath string
wantString string wantString string
} }

View File

@ -1,47 +0,0 @@
package system
import (
"fmt"
"strings"
)
// Enablement represents an optional host service to export to the target user.
type Enablement byte
const (
EWayland Enablement = 1 << iota
EX11
EDBus
EPulse
EM
)
func (e Enablement) String() string {
switch e {
case 0:
return "(no enablements)"
case EWayland:
return "wayland"
case EX11:
return "x11"
case EDBus:
return "dbus"
case EPulse:
return "pulseaudio"
default:
buf := new(strings.Builder)
buf.Grow(32)
for i := Enablement(1); i < EM; i <<= 1 {
if e&i != 0 {
buf.WriteString(", " + i.String())
}
}
if buf.Len() == 0 {
return fmt.Sprintf("e%x", byte(e))
}
return strings.TrimPrefix(buf.String(), ", ")
}
}

View File

@ -1,43 +0,0 @@
package system_test
import (
"testing"
"hakurei.app/system"
)
func TestEnablementString(t *testing.T) {
testCases := []struct {
flags system.Enablement
want string
}{
{0, "(no enablements)"},
{system.EWayland, "wayland"},
{system.EX11, "x11"},
{system.EDBus, "dbus"},
{system.EPulse, "pulseaudio"},
{system.EWayland | system.EX11, "wayland, x11"},
{system.EWayland | system.EDBus, "wayland, dbus"},
{system.EWayland | system.EPulse, "wayland, pulseaudio"},
{system.EX11 | system.EDBus, "x11, dbus"},
{system.EX11 | system.EPulse, "x11, pulseaudio"},
{system.EDBus | system.EPulse, "dbus, pulseaudio"},
{system.EWayland | system.EX11 | system.EDBus, "wayland, x11, dbus"},
{system.EWayland | system.EX11 | system.EPulse, "wayland, x11, pulseaudio"},
{system.EWayland | system.EDBus | system.EPulse, "wayland, dbus, pulseaudio"},
{system.EX11 | system.EDBus | system.EPulse, "x11, dbus, pulseaudio"},
{system.EWayland | system.EX11 | system.EDBus | system.EPulse, "wayland, x11, dbus, pulseaudio"},
{1 << 5, "e20"},
{1 << 6, "e40"},
{1 << 7, "e80"},
}
for _, tc := range testCases {
t.Run(tc.want, func(t *testing.T) {
if got := tc.flags.String(); got != tc.want {
t.Errorf("String: %q, want %q", got, tc.want)
}
})
}
}

View File

@ -2,24 +2,26 @@ package system
import ( import (
"fmt" "fmt"
"hakurei.app/hst"
) )
// Link calls LinkFileType with the [Process] criteria. // Link calls LinkFileType with the [Process] criteria.
func (sys *I) Link(oldname, newname string) *I { return sys.LinkFileType(Process, oldname, newname) } func (sys *I) Link(oldname, newname string) *I { return sys.LinkFileType(Process, oldname, newname) }
// LinkFileType maintains a hardlink until its [Enablement] is no longer satisfied. // LinkFileType maintains a hardlink until its [Enablement] is no longer satisfied.
func (sys *I) LinkFileType(et Enablement, oldname, newname string) *I { func (sys *I) LinkFileType(et hst.Enablement, oldname, newname string) *I {
sys.ops = append(sys.ops, &hardlinkOp{et, newname, oldname}) sys.ops = append(sys.ops, &hardlinkOp{et, newname, oldname})
return sys return sys
} }
// hardlinkOp implements [I.LinkFileType]. // hardlinkOp implements [I.LinkFileType].
type hardlinkOp struct { type hardlinkOp struct {
et Enablement et hst.Enablement
dst, src string dst, src string
} }
func (l *hardlinkOp) Type() Enablement { return l.et } func (l *hardlinkOp) Type() hst.Enablement { return l.et }
func (l *hardlinkOp) apply(sys *I) error { func (l *hardlinkOp) apply(sys *I) error {
sys.msg.Verbose("linking", l) sys.msg.Verbose("linking", l)

View File

@ -4,32 +4,33 @@ import (
"testing" "testing"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst"
) )
func TestHardlinkOp(t *testing.T) { func TestHardlinkOp(t *testing.T) {
checkOpBehaviour(t, []opBehaviourTestCase{ checkOpBehaviour(t, []opBehaviourTestCase{
{"link", 0xdeadbeef, 0xff, &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{ {"link", 0xdeadbeef, 0xff, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil), call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, stub.UniqueError(1)), call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, stub.UniqueError(1)),
}, &OpError{Op: "hardlink", Err: stub.UniqueError(1)}, nil, nil}, }, &OpError{Op: "hardlink", Err: stub.UniqueError(1)}, nil, nil},
{"remove", 0xdeadbeef, 0xff, &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{ {"remove", 0xdeadbeef, 0xff, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil), call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil), call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"removing hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil), call("verbosef", stub.ExpectArgs{"removing hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil),
call("remove", stub.ExpectArgs{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, stub.UniqueError(0)), call("remove", stub.ExpectArgs{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, stub.UniqueError(0)),
}, &OpError{Op: "hardlink", Err: stub.UniqueError(0), Revert: true}}, }, &OpError{Op: "hardlink", Err: stub.UniqueError(0), Revert: true}},
{"success skip", 0xdeadbeef, EWayland | EX11, &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{ {"success skip", 0xdeadbeef, hst.EWayland | hst.EX11, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil), call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil), call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"skipping hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil), call("verbosef", stub.ExpectArgs{"skipping hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil),
}, nil}, }, nil},
{"success", 0xdeadbeef, 0xff, &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{ {"success", 0xdeadbeef, 0xff, &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}, []stub.Call{
call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil), call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{hst.EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil),
call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil), call("link", stub.ExpectArgs{"/run/user/1000/pulse/native", "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}, nil, nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
call("verbosef", stub.ExpectArgs{"removing hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil), call("verbosef", stub.ExpectArgs{"removing hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil),

View File

@ -4,6 +4,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"hakurei.app/hst"
) )
// Ensure ensures the existence of a directory. // Ensure ensures the existence of a directory.
@ -13,20 +15,20 @@ func (sys *I) Ensure(name string, perm os.FileMode) *I {
} }
// Ephemeral ensures the existence of a directory until its [Enablement] is no longer satisfied. // Ephemeral ensures the existence of a directory until its [Enablement] is no longer satisfied.
func (sys *I) Ephemeral(et Enablement, name string, perm os.FileMode) *I { func (sys *I) Ephemeral(et hst.Enablement, name string, perm os.FileMode) *I {
sys.ops = append(sys.ops, &mkdirOp{et, name, perm, true}) sys.ops = append(sys.ops, &mkdirOp{et, name, perm, true})
return sys return sys
} }
// mkdirOp implements [I.Ensure] and [I.Ephemeral]. // mkdirOp implements [I.Ensure] and [I.Ephemeral].
type mkdirOp struct { type mkdirOp struct {
et Enablement et hst.Enablement
path string path string
perm os.FileMode perm os.FileMode
ephemeral bool ephemeral bool
} }
func (m *mkdirOp) Type() Enablement { return m.et } func (m *mkdirOp) Type() hst.Enablement { return m.et }
func (m *mkdirOp) apply(sys *I) error { func (m *mkdirOp) apply(sys *I) error {
sys.msg.Verbose("ensuring directory", m) sys.msg.Verbose("ensuring directory", m)

View File

@ -7,11 +7,12 @@ import (
"strings" "strings"
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/hst"
) )
const ( const (
// User type is reverted at final instance exit. // User type is reverted at final instance exit.
User = EM << iota User = hst.EM << iota
// Process type is unconditionally reverted on exit. // Process type is unconditionally reverted on exit.
Process Process
@ -19,21 +20,21 @@ const (
) )
// Criteria specifies types of Op to revert. // Criteria specifies types of Op to revert.
type Criteria Enablement type Criteria hst.Enablement
func (ec *Criteria) hasType(t Enablement) bool { func (ec *Criteria) hasType(t hst.Enablement) bool {
// nil criteria: revert everything except User // nil criteria: revert everything except User
if ec == nil { if ec == nil {
return t != User return t != User
} }
return Enablement(*ec)&t != 0 return hst.Enablement(*ec)&t != 0
} }
// Op is a reversible system operation. // Op is a reversible system operation.
type Op interface { type Op interface {
// Type returns [Op]'s enablement type, for matching a revert criteria. // Type returns [Op]'s enablement type, for matching a revert criteria.
Type() Enablement Type() hst.Enablement
apply(sys *I) error apply(sys *I) error
revert(sys *I, ec *Criteria) error revert(sys *I, ec *Criteria) error
@ -44,7 +45,7 @@ type Op interface {
} }
// TypeString extends [Enablement.String] to support [User] and [Process]. // TypeString extends [Enablement.String] to support [User] and [Process].
func TypeString(e Enablement) string { func TypeString(e hst.Enablement) string {
switch e { switch e {
case User: case User:
return "user" return "user"

View File

@ -10,18 +10,19 @@ import (
"hakurei.app/container" "hakurei.app/container"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst"
"hakurei.app/system/internal/xcb" "hakurei.app/system/internal/xcb"
) )
func TestCriteria(t *testing.T) { func TestCriteria(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
ec, t Enablement ec, t hst.Enablement
want bool want bool
}{ }{
{"nil", 0xff, EWayland, true}, {"nil", 0xff, hst.EWayland, true},
{"nil user", 0xff, User, false}, {"nil user", 0xff, User, false},
{"all", EWayland | EX11 | EDBus | EPulse | User | Process, Process, true}, {"all", hst.EWayland | hst.EX11 | hst.EDBus | hst.EPulse | User | Process, Process, true},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -40,18 +41,18 @@ func TestCriteria(t *testing.T) {
func TestTypeString(t *testing.T) { func TestTypeString(t *testing.T) {
testCases := []struct { testCases := []struct {
e Enablement e hst.Enablement
want string want string
}{ }{
{EWayland, EWayland.String()}, {hst.EWayland, hst.EWayland.String()},
{EX11, EX11.String()}, {hst.EX11, hst.EX11.String()},
{EDBus, EDBus.String()}, {hst.EDBus, hst.EDBus.String()},
{EPulse, EPulse.String()}, {hst.EPulse, hst.EPulse.String()},
{User, "user"}, {User, "user"},
{Process, "process"}, {Process, "process"},
{User | Process, "user, process"}, {User | Process, "user, process"},
{EWayland | User | Process, "wayland, user, process"}, {hst.EWayland | User | Process, "wayland, user, process"},
{EX11 | Process, "x11, process"}, {hst.EX11 | Process, "x11, process"},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -176,7 +177,7 @@ func TestCommitRevert(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
f func(sys *I) f func(sys *I)
ec Enablement ec hst.Enablement
commit []stub.Call commit []stub.Call
wantErrCommit error wantErrCommit error

View File

@ -7,6 +7,8 @@ import (
"io" "io"
"os" "os"
"syscall" "syscall"
"hakurei.app/hst"
) )
// CopyFile reads up to n bytes from src and writes the resulting byte slice to payloadP. // CopyFile reads up to n bytes from src and writes the resulting byte slice to payloadP.
@ -26,7 +28,7 @@ type tmpfileOp struct {
buf *bytes.Buffer buf *bytes.Buffer
} }
func (t *tmpfileOp) Type() Enablement { return Process } func (t *tmpfileOp) Type() hst.Enablement { return Process }
func (t *tmpfileOp) apply(sys *I) error { func (t *tmpfileOp) apply(sys *I) error {
if t.payload == nil { if t.payload == nil {

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"os" "os"
"hakurei.app/hst"
"hakurei.app/system/acl" "hakurei.app/system/acl"
"hakurei.app/system/wayland" "hakurei.app/system/wayland"
) )
@ -32,7 +33,7 @@ type waylandOp struct {
conn waylandConn conn waylandConn
} }
func (w *waylandOp) Type() Enablement { return Process } func (w *waylandOp) Type() hst.Enablement { return Process }
func (w *waylandOp) apply(sys *I) error { func (w *waylandOp) apply(sys *I) error {
if w.sync == nil { if w.sync == nil {

View File

@ -1,6 +1,7 @@
package system package system
import ( import (
"hakurei.app/hst"
"hakurei.app/system/internal/xcb" "hakurei.app/system/internal/xcb"
) )
@ -13,7 +14,7 @@ func (sys *I) ChangeHosts(username string) *I {
// xhostOp implements [I.ChangeHosts]. // xhostOp implements [I.ChangeHosts].
type xhostOp string type xhostOp string
func (x xhostOp) Type() Enablement { return EX11 } func (x xhostOp) Type() hst.Enablement { return hst.EX11 }
func (x xhostOp) apply(sys *I) error { func (x xhostOp) apply(sys *I) error {
sys.msg.Verbosef("inserting entry %s to X11", x) sys.msg.Verbosef("inserting entry %s to X11", x)

View File

@ -4,17 +4,18 @@ import (
"testing" "testing"
"hakurei.app/container/stub" "hakurei.app/container/stub"
"hakurei.app/hst"
"hakurei.app/system/internal/xcb" "hakurei.app/system/internal/xcb"
) )
func TestXHostOp(t *testing.T) { func TestXHostOp(t *testing.T) {
checkOpBehaviour(t, []opBehaviourTestCase{ checkOpBehaviour(t, []opBehaviourTestCase{
{"xcbChangeHosts revert", 0xbeef, EX11, xhostOp("chronos"), []stub.Call{ {"xcbChangeHosts revert", 0xbeef, hst.EX11, xhostOp("chronos"), []stub.Call{
call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil), call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, stub.UniqueError(1)), call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, stub.UniqueError(1)),
}, &OpError{Op: "xhost", Err: stub.UniqueError(1)}, nil, nil}, }, &OpError{Op: "xhost", Err: stub.UniqueError(1)}, nil, nil},
{"xcbChangeHosts revert", 0xbeef, EX11, xhostOp("chronos"), []stub.Call{ {"xcbChangeHosts revert", 0xbeef, hst.EX11, xhostOp("chronos"), []stub.Call{
call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil), call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil), call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
@ -29,7 +30,7 @@ func TestXHostOp(t *testing.T) {
call("verbosef", stub.ExpectArgs{"skipping entry %s in X11", []any{xhostOp("chronos")}}, nil, nil), call("verbosef", stub.ExpectArgs{"skipping entry %s in X11", []any{xhostOp("chronos")}}, nil, nil),
}, nil}, }, nil},
{"success", 0xbeef, EX11, xhostOp("chronos"), []stub.Call{ {"success", 0xbeef, hst.EX11, xhostOp("chronos"), []stub.Call{
call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil), call("verbosef", stub.ExpectArgs{"inserting entry %s to X11", []any{xhostOp("chronos")}}, nil, nil),
call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil), call("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil),
}, nil, []stub.Call{ }, nil, []stub.Call{
@ -52,6 +53,6 @@ func TestXHostOp(t *testing.T) {
}) })
checkOpMeta(t, []opMetaTestCase{ checkOpMeta(t, []opMetaTestCase{
{"xhost", xhostOp("chronos"), EX11, "/tmp/.X11-unix", "SI:localuser:chronos"}, {"xhost", xhostOp("chronos"), hst.EX11, "/tmp/.X11-unix", "SI:localuser:chronos"},
}) })
} }