diff --git a/cmd/hakurei/command.go b/cmd/hakurei/command.go index 58e8ace..5e123f0 100644 --- a/cmd/hakurei/command.go +++ b/cmd/hakurei/command.go @@ -17,7 +17,6 @@ import ( "hakurei.app/internal" "hakurei.app/internal/app" "hakurei.app/internal/app/state" - "hakurei.app/system" "hakurei.app/system/dbus" ) @@ -128,18 +127,18 @@ func buildCommand(ctx context.Context, msg container.Msg, early *earlyHardeningE config.Home = a } - var e system.Enablement + var e hst.Enablement if flagWayland { - e |= system.EWayland + e |= hst.EWayland } if flagX11 { - e |= system.EX11 + e |= hst.EX11 } if flagDBus { - e |= system.EDBus + e |= hst.EDBus } if flagPulse { - e |= system.EPulse + e |= hst.EPulse } config.Enablements = hst.NewEnablements(e) diff --git a/hst/enablement.go b/hst/enablement.go index 021288b..deaacb9 100644 --- a/hst/enablement.go +++ b/hst/enablement.go @@ -2,15 +2,56 @@ package hst import ( "encoding/json" + "fmt" + "strings" "syscall" - - "hakurei.app/system" ) -// NewEnablements returns the address of [system.Enablement] as [Enablements]. -func NewEnablements(e system.Enablement) *Enablements { return (*Enablements)(&e) } +// Enablement represents an optional host service to export to the target user. +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 { Wayland bool `json:"wayland,omitempty"` X11 bool `json:"x11,omitempty"` @@ -18,15 +59,15 @@ type enablementsJSON struct { Pulse bool `json:"pulse,omitempty"` } -// Enablements is the [json] adapter for [system.Enablement]. -type Enablements system.Enablement +// Enablements is the [json] adapter for [Enablement]. +type Enablements Enablement -// Unwrap returns the underlying [system.Enablement]. -func (e *Enablements) Unwrap() system.Enablement { +// Unwrap returns the underlying [Enablement]. +func (e *Enablements) Unwrap() Enablement { if e == nil { return 0 } - return system.Enablement(*e) + return Enablement(*e) } func (e *Enablements) MarshalJSON() ([]byte, error) { @@ -34,10 +75,10 @@ func (e *Enablements) MarshalJSON() ([]byte, error) { return nil, syscall.EINVAL } return json.Marshal(&enablementsJSON{ - Wayland: system.Enablement(*e)&system.EWayland != 0, - X11: system.Enablement(*e)&system.EX11 != 0, - DBus: system.Enablement(*e)&system.EDBus != 0, - Pulse: system.Enablement(*e)&system.EPulse != 0, + Wayland: Enablement(*e)&EWayland != 0, + X11: Enablement(*e)&EX11 != 0, + DBus: Enablement(*e)&EDBus != 0, + Pulse: Enablement(*e)&EPulse != 0, }) } @@ -51,18 +92,18 @@ func (e *Enablements) UnmarshalJSON(data []byte) error { return err } - var ve system.Enablement + var ve Enablement if v.Wayland { - ve |= system.EWayland + ve |= EWayland } if v.X11 { - ve |= system.EX11 + ve |= EX11 } if v.DBus { - ve |= system.EDBus + ve |= EDBus } if v.Pulse { - ve |= system.EPulse + ve |= EPulse } *e = Enablements(ve) return nil diff --git a/hst/enablement_test.go b/hst/enablement_test.go index c132dd3..82059aa 100644 --- a/hst/enablement_test.go +++ b/hst/enablement_test.go @@ -7,9 +7,44 @@ import ( "testing" "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) { testCases := []struct { name string @@ -19,11 +54,11 @@ func TestEnablements(t *testing.T) { }{ {"nil", nil, "null", `{"value":null,"magic":3236757504}`}, {"zero", hst.NewEnablements(0), `{}`, `{"value":{},"magic":3236757504}`}, - {"wayland", hst.NewEnablements(system.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`}, - {"x11", hst.NewEnablements(system.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`}, - {"dbus", hst.NewEnablements(system.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`}, - {"pulse", hst.NewEnablements(system.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}`}, + {"wayland", hst.NewEnablements(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`}, + {"x11", hst.NewEnablements(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`}, + {"dbus", hst.NewEnablements(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`}, + {"pulse", hst.NewEnablements(hst.EPulse), `{"pulse":true}`, `{"value":{"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 { @@ -88,7 +123,7 @@ func TestEnablements(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) } }) diff --git a/hst/hst.go b/hst/hst.go index c9a23d7..5399cba 100644 --- a/hst/hst.go +++ b/hst/hst.go @@ -8,7 +8,6 @@ import ( "hakurei.app/container" "hakurei.app/container/seccomp" - "hakurei.app/system" "hakurei.app/system/dbus" ) @@ -71,7 +70,7 @@ func Template() *Config { "--ozone-platform=wayland", }, - Enablements: NewEnablements(system.EWayland | system.EDBus | system.EPulse), + Enablements: NewEnablements(EWayland | EDBus | EPulse), SessionBus: &dbus.Config{ See: nil, diff --git a/internal/app/app_test.go b/internal/app/app_test.go index fa85c46..585f95b 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -121,7 +121,7 @@ func TestApp(t *testing.T) { }, Filter: true, }, - Enablements: hst.NewEnablements(system.EWayland | system.EDBus | system.EPulse), + Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPulse), }, state.ID{ 0xeb, 0xf0, 0x83, 0xd1, @@ -229,7 +229,7 @@ func TestApp(t *testing.T) { &hst.Config{ ID: "org.chromium.Chromium", 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"), 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("/run/user/1971/hakurei", 0700).UpdatePermType(system.User, "/run/user/1971/hakurei", acl.Execute). Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset - UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute). + 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). Link("/run/user/1971/pulse/native", "/run/user/1971/hakurei/8e2c76b066dabe574cf073bdb46eb5c1/pulse"). CopyFile(nil, "/home/ophestra/xdg/config/pulse/cookie", 256, 256). diff --git a/internal/app/finalise.go b/internal/app/finalise.go index d2c685f..08fab97 100644 --- a/internal/app/finalise.go +++ b/internal/app/finalise.go @@ -241,7 +241,7 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.C } // 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}}) } // opportunistically bind kvm @@ -353,7 +353,7 @@ func (k *outcome) finalise(ctx context.Context, msg container.Msg, config *hst.C 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`) var socketPath *container.Absolute 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") share.ensureRuntimeDir() 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 { return newWithMessage("DISPLAY is not set") } 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} } } 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 { 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`) pulseRuntimeDir := share.sc.RuntimePath.Append("pulse") // 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 if config.SessionBus == nil { config.SessionBus = dbus.NewConfig(config.ID, true, true) diff --git a/internal/app/process.go b/internal/app/process.go index 1b9c037..1687c86 100644 --- a/internal/app/process.go +++ b/internal/app/process.go @@ -13,6 +13,7 @@ import ( "time" "hakurei.app/container" + "hakurei.app/hst" "hakurei.app/internal" "hakurei.app/internal/app/state" "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 { // it is impossible to continue from this point; // 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 ec > 0 { ms.Verbose("reverting operations scope", system.TypeString(ec)) diff --git a/system/acl.go b/system/acl.go index f65ba91..eb0fe8a 100644 --- a/system/acl.go +++ b/system/acl.go @@ -6,6 +6,7 @@ import ( "os" "slices" + "hakurei.app/hst" "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. -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}) return sys } // aclUpdateOp implements [I.UpdatePermType]. type aclUpdateOp struct { - et Enablement + et hst.Enablement path string 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 { sys.msg.Verbose("applying ACL", a) diff --git a/system/acl_test.go b/system/acl_test.go index 5228b90..b288bd4 100644 --- a/system/acl_test.go +++ b/system/acl_test.go @@ -6,6 +6,7 @@ import ( "testing" "hakurei.app/container/stub" + "hakurei.app/hst" "hakurei.app/system/acl" ) @@ -94,9 +95,9 @@ func TestACLUpdateOp(t *testing.T) { }, stub.Expect{}}, {"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{ - &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{}}, }) @@ -106,34 +107,34 @@ func TestACLUpdateOp(t *testing.T) { {"et differs", &aclUpdateOp{ - EWayland, "/run/user/1971/wayland-0", + hst.EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}, }, &aclUpdateOp{ - EX11, "/run/user/1971/wayland-0", + hst.EX11, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}, }, false}, {"path differs", &aclUpdateOp{ - EWayland, "/run/user/1971/wayland-0", + hst.EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}, }, &aclUpdateOp{ - EWayland, "/run/user/1971/wayland-1", + hst.EWayland, "/run/user/1971/wayland-1", []acl.Perm{acl.Read, acl.Write, acl.Execute}, }, false}, {"perms differs", &aclUpdateOp{ - EWayland, "/run/user/1971/wayland-0", + hst.EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}, }, &aclUpdateOp{ - EWayland, "/run/user/1971/wayland-0", + hst.EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write}, }, false}, {"equals", &aclUpdateOp{ - EWayland, "/run/user/1971/wayland-0", + hst.EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}, }, &aclUpdateOp{ - EWayland, "/run/user/1971/wayland-0", + hst.EWayland, "/run/user/1971/wayland-0", []acl.Perm{acl.Read, acl.Write, acl.Execute}, }, true}, }) @@ -160,23 +161,23 @@ func TestACLUpdateOp(t *testing.T) { `--x type: user path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/2"`}, {"wayland", - &aclUpdateOp{EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", []acl.Perm{acl.Read, acl.Write}}, - EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", + &aclUpdateOp{hst.EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", []acl.Perm{acl.Read, acl.Write}}, + hst.EWayland, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland", `rw- type: wayland path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/wayland"`}, {"x11", - &aclUpdateOp{EX11, "/tmp/.X11-unix/X0", []acl.Perm{acl.Read, acl.Execute}}, - EX11, "/tmp/.X11-unix/X0", + &aclUpdateOp{hst.EX11, "/tmp/.X11-unix/X0", []acl.Perm{acl.Read, acl.Execute}}, + hst.EX11, "/tmp/.X11-unix/X0", `r-x type: x11 path: "/tmp/.X11-unix/X0"`}, {"dbus", - &aclUpdateOp{EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", []acl.Perm{acl.Write, acl.Execute}}, - EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", + &aclUpdateOp{hst.EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", []acl.Perm{acl.Write, acl.Execute}}, + hst.EDBus, "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus", `-wx type: dbus path: "/tmp/hakurei.0/27d81d567f8fae7f33278eec45da9446/bus"`}, {"pulseaudio", - &aclUpdateOp{EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, - EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", + &aclUpdateOp{hst.EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", []acl.Perm{acl.Read, acl.Write, acl.Execute}}, + hst.EPulse, "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse", `rwx type: pulseaudio path: "/run/user/1971/hakurei/27d81d567f8fae7f33278eec45da9446/pulse"`}, }) } diff --git a/system/dbus.go b/system/dbus.go index 97c567a..2f2ac35 100644 --- a/system/dbus.go +++ b/system/dbus.go @@ -12,6 +12,7 @@ import ( "syscall" "hakurei.app/container" + "hakurei.app/hst" "hakurei.app/system/dbus" ) @@ -81,7 +82,7 @@ type dbusProxyOp struct { system bool } -func (d *dbusProxyOp) Type() Enablement { return Process } +func (d *dbusProxyOp) Type() hst.Enablement { return Process } 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]) diff --git a/system/dispatcher_test.go b/system/dispatcher_test.go index d83b46b..eb93d16 100644 --- a/system/dispatcher_test.go +++ b/system/dispatcher_test.go @@ -12,6 +12,7 @@ import ( "unsafe" "hakurei.app/container/stub" + "hakurei.app/hst" "hakurei.app/system/acl" "hakurei.app/system/dbus" "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 { name string uid int - ec Enablement + ec hst.Enablement op Op apply []stub.Call @@ -142,7 +143,7 @@ type opMetaTestCase struct { name string op Op - wantType Enablement + wantType hst.Enablement wantPath string wantString string } diff --git a/system/enablement.go b/system/enablement.go deleted file mode 100644 index 9c1342d..0000000 --- a/system/enablement.go +++ /dev/null @@ -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(), ", ") - } -} diff --git a/system/enablement_test.go b/system/enablement_test.go deleted file mode 100644 index ba81656..0000000 --- a/system/enablement_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/system/link.go b/system/link.go index d463b62..50a0534 100644 --- a/system/link.go +++ b/system/link.go @@ -2,24 +2,26 @@ package system import ( "fmt" + + "hakurei.app/hst" ) // Link calls LinkFileType with the [Process] criteria. 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. -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}) return sys } // hardlinkOp implements [I.LinkFileType]. type hardlinkOp struct { - et Enablement + et hst.Enablement 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 { sys.msg.Verbose("linking", l) diff --git a/system/link_test.go b/system/link_test.go index b962db1..02a59c0 100644 --- a/system/link_test.go +++ b/system/link_test.go @@ -4,32 +4,33 @@ import ( "testing" "hakurei.app/container/stub" + "hakurei.app/hst" ) func TestHardlinkOp(t *testing.T) { checkOpBehaviour(t, []opBehaviourTestCase{ - {"link", 0xdeadbeef, 0xff, &hardlinkOp{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), + {"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{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)), }, &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{ - call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil), + {"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{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), }, nil, []stub.Call{ 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)), }, &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{ - call("verbose", stub.ExpectArgs{[]any{"linking", &hardlinkOp{EPulse, "/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse", "/run/user/1000/pulse/native"}}}, nil, nil), + {"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{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), }, nil, []stub.Call{ call("verbosef", stub.ExpectArgs{"skipping hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil), }, nil}, - {"success", 0xdeadbeef, 0xff, &hardlinkOp{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), + {"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{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), }, nil, []stub.Call{ call("verbosef", stub.ExpectArgs{"removing hard link %q", []any{"/run/user/1000/hakurei/9663730666a44cfc2a81610379e02ed6/pulse"}}, nil, nil), diff --git a/system/mkdir.go b/system/mkdir.go index c4e6163..23c4210 100644 --- a/system/mkdir.go +++ b/system/mkdir.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "os" + + "hakurei.app/hst" ) // 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. -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}) return sys } // mkdirOp implements [I.Ensure] and [I.Ephemeral]. type mkdirOp struct { - et Enablement + et hst.Enablement path string perm os.FileMode 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 { sys.msg.Verbose("ensuring directory", m) diff --git a/system/system.go b/system/system.go index 0d16a14..5599218 100644 --- a/system/system.go +++ b/system/system.go @@ -7,11 +7,12 @@ import ( "strings" "hakurei.app/container" + "hakurei.app/hst" ) const ( // User type is reverted at final instance exit. - User = EM << iota + User = hst.EM << iota // Process type is unconditionally reverted on exit. Process @@ -19,21 +20,21 @@ const ( ) // 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 if ec == nil { return t != User } - return Enablement(*ec)&t != 0 + return hst.Enablement(*ec)&t != 0 } // Op is a reversible system operation. type Op interface { // Type returns [Op]'s enablement type, for matching a revert criteria. - Type() Enablement + Type() hst.Enablement apply(sys *I) error revert(sys *I, ec *Criteria) error @@ -44,7 +45,7 @@ type Op interface { } // TypeString extends [Enablement.String] to support [User] and [Process]. -func TypeString(e Enablement) string { +func TypeString(e hst.Enablement) string { switch e { case User: return "user" diff --git a/system/system_test.go b/system/system_test.go index 73b70fc..0f5e2d6 100644 --- a/system/system_test.go +++ b/system/system_test.go @@ -10,18 +10,19 @@ import ( "hakurei.app/container" "hakurei.app/container/stub" + "hakurei.app/hst" "hakurei.app/system/internal/xcb" ) func TestCriteria(t *testing.T) { testCases := []struct { name string - ec, t Enablement + ec, t hst.Enablement want bool }{ - {"nil", 0xff, EWayland, true}, + {"nil", 0xff, hst.EWayland, true}, {"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 { @@ -40,18 +41,18 @@ func TestCriteria(t *testing.T) { func TestTypeString(t *testing.T) { testCases := []struct { - e Enablement + e hst.Enablement want string }{ - {EWayland, EWayland.String()}, - {EX11, EX11.String()}, - {EDBus, EDBus.String()}, - {EPulse, EPulse.String()}, + {hst.EWayland, hst.EWayland.String()}, + {hst.EX11, hst.EX11.String()}, + {hst.EDBus, hst.EDBus.String()}, + {hst.EPulse, hst.EPulse.String()}, {User, "user"}, {Process, "process"}, {User | Process, "user, process"}, - {EWayland | User | Process, "wayland, user, process"}, - {EX11 | Process, "x11, process"}, + {hst.EWayland | User | Process, "wayland, user, process"}, + {hst.EX11 | Process, "x11, process"}, } for _, tc := range testCases { @@ -176,7 +177,7 @@ func TestCommitRevert(t *testing.T) { testCases := []struct { name string f func(sys *I) - ec Enablement + ec hst.Enablement commit []stub.Call wantErrCommit error diff --git a/system/tmpfiles.go b/system/tmpfiles.go index 845b43a..434a3ae 100644 --- a/system/tmpfiles.go +++ b/system/tmpfiles.go @@ -7,6 +7,8 @@ import ( "io" "os" "syscall" + + "hakurei.app/hst" ) // 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 } -func (t *tmpfileOp) Type() Enablement { return Process } +func (t *tmpfileOp) Type() hst.Enablement { return Process } func (t *tmpfileOp) apply(sys *I) error { if t.payload == nil { diff --git a/system/wayland.go b/system/wayland.go index 5a87ab9..77cd61f 100644 --- a/system/wayland.go +++ b/system/wayland.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + "hakurei.app/hst" "hakurei.app/system/acl" "hakurei.app/system/wayland" ) @@ -32,7 +33,7 @@ type waylandOp struct { conn waylandConn } -func (w *waylandOp) Type() Enablement { return Process } +func (w *waylandOp) Type() hst.Enablement { return Process } func (w *waylandOp) apply(sys *I) error { if w.sync == nil { diff --git a/system/xhost.go b/system/xhost.go index e5ea258..9a547f4 100644 --- a/system/xhost.go +++ b/system/xhost.go @@ -1,6 +1,7 @@ package system import ( + "hakurei.app/hst" "hakurei.app/system/internal/xcb" ) @@ -13,7 +14,7 @@ func (sys *I) ChangeHosts(username string) *I { // xhostOp implements [I.ChangeHosts]. 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 { sys.msg.Verbosef("inserting entry %s to X11", x) diff --git a/system/xhost_test.go b/system/xhost_test.go index 68601e8..ad3ba89 100644 --- a/system/xhost_test.go +++ b/system/xhost_test.go @@ -4,17 +4,18 @@ import ( "testing" "hakurei.app/container/stub" + "hakurei.app/hst" "hakurei.app/system/internal/xcb" ) func TestXHostOp(t *testing.T) { 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("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}, - {"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("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil), }, 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), }, 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("xcbChangeHosts", stub.ExpectArgs{xcb.HostMode(xcb.HostModeInsert), xcb.Family(xcb.FamilyServerInterpreted), "localuser\x00chronos"}, nil, nil), }, nil, []stub.Call{ @@ -52,6 +53,6 @@ func TestXHostOp(t *testing.T) { }) checkOpMeta(t, []opMetaTestCase{ - {"xhost", xhostOp("chronos"), EX11, "/tmp/.X11-unix", "SI:localuser:chronos"}, + {"xhost", xhostOp("chronos"), hst.EX11, "/tmp/.X11-unix", "SI:localuser:chronos"}, }) }