From 560cb626a1a8f7d4bfca5d30f262cc90fb5d4608 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Fri, 10 Apr 2026 20:39:06 +0900 Subject: [PATCH] hst: remove enablement json adapter The go116 behaviour of built-in new function makes this cleaner. Signed-off-by: Ophestra --- cmd/hakurei/command.go | 4 +-- cmd/hakurei/print_test.go | 2 +- hst/config_test.go | 14 ++++---- hst/enablement.go | 52 ++++++++++++------------------ hst/enablement_test.go | 21 ++++++------ hst/hst.go | 2 +- internal/outcome/outcome.go | 2 +- internal/outcome/process.go | 4 +-- internal/outcome/run_test.go | 4 +-- internal/outcome/sppulse_test.go | 2 +- internal/store/data.go | 13 +++++--- internal/store/header.go | 27 +++++++++------- internal/store/header_test.go | 2 +- internal/store/segment.go | 41 +++++++++++++++-------- internal/store/segment_test.go | 2 +- internal/system/acl.go | 9 +++--- internal/system/dbus.go | 6 ++-- internal/system/dispatcher.go | 10 ++++-- internal/system/dispatcher_test.go | 4 +-- internal/system/link.go | 8 ++--- internal/system/mkdir.go | 9 +++--- internal/system/pipewire.go | 7 ++-- internal/system/system.go | 16 +++++---- internal/system/system_test.go | 6 ++-- internal/system/wayland.go | 7 ++-- internal/system/xhost.go | 5 +-- 26 files changed, 149 insertions(+), 130 deletions(-) diff --git a/cmd/hakurei/command.go b/cmd/hakurei/command.go index 78090161..12bf027a 100644 --- a/cmd/hakurei/command.go +++ b/cmd/hakurei/command.go @@ -152,7 +152,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr } } - var et hst.Enablement + var et hst.Enablements if flagWayland { et |= hst.EWayland } @@ -170,7 +170,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr ID: flagID, Identity: flagIdentity, Groups: flagGroups, - Enablements: hst.NewEnablements(et), + Enablements: &et, Container: &hst.ContainerConfig{ Filesystem: []hst.FilesystemConfigJSON{ diff --git a/cmd/hakurei/print_test.go b/cmd/hakurei/print_test.go index e67580b4..f947fced 100644 --- a/cmd/hakurei/print_test.go +++ b/cmd/hakurei/print_test.go @@ -32,7 +32,7 @@ var ( PID: 0xbeef, ShimPID: 0xcafe, Config: &hst.Config{ - Enablements: hst.NewEnablements(hst.EWayland | hst.EPipeWire), + Enablements: new(hst.EWayland | hst.EPipeWire), Identity: 1, Container: &hst.ContainerConfig{ Shell: check.MustAbs("/bin/sh"), diff --git a/hst/config_test.go b/hst/config_test.go index 22d764b5..7c6ade8c 100644 --- a/hst/config_test.go +++ b/hst/config_test.go @@ -64,44 +64,44 @@ func TestConfigValidate(t *testing.T) { }}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron, Msg: `invalid environment variable "TERM\x00"`}}, - {"insecure pulse", &hst.Config{Enablements: hst.NewEnablements(hst.EPulse), Container: &hst.ContainerConfig{ + {"insecure pulse", &hst.Config{Enablements: new(hst.EPulse), Container: &hst.ContainerConfig{ Home: fhs.AbsTmp, Shell: fhs.AbsTmp, Path: fhs.AbsTmp, }}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure, Msg: "enablement PulseAudio is insecure and no longer supported"}}, - {"direct wayland", &hst.Config{Enablements: hst.NewEnablements(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{ + {"direct wayland", &hst.Config{Enablements: new(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{ Home: fhs.AbsTmp, Shell: fhs.AbsTmp, Path: fhs.AbsTmp, }}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure, Msg: "direct_wayland is insecure and no longer supported"}}, - {"direct wayland allow", &hst.Config{Enablements: hst.NewEnablements(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{ + {"direct wayland allow", &hst.Config{Enablements: new(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{ Home: fhs.AbsTmp, Shell: fhs.AbsTmp, Path: fhs.AbsTmp, }}, hst.VAllowInsecure, nil}, - {"direct pipewire", &hst.Config{Enablements: hst.NewEnablements(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{ + {"direct pipewire", &hst.Config{Enablements: new(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{ Home: fhs.AbsTmp, Shell: fhs.AbsTmp, Path: fhs.AbsTmp, }}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure, Msg: "direct_pipewire is insecure and no longer supported"}}, - {"direct pipewire allow", &hst.Config{Enablements: hst.NewEnablements(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{ + {"direct pipewire allow", &hst.Config{Enablements: new(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{ Home: fhs.AbsTmp, Shell: fhs.AbsTmp, Path: fhs.AbsTmp, }}, hst.VAllowInsecure, nil}, - {"direct pulse", &hst.Config{Enablements: hst.NewEnablements(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{ + {"direct pulse", &hst.Config{Enablements: new(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{ Home: fhs.AbsTmp, Shell: fhs.AbsTmp, Path: fhs.AbsTmp, }}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure, Msg: "direct_pulse is insecure and no longer supported"}}, - {"direct pulse allow", &hst.Config{Enablements: hst.NewEnablements(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{ + {"direct pulse allow", &hst.Config{Enablements: new(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{ Home: fhs.AbsTmp, Shell: fhs.AbsTmp, Path: fhs.AbsTmp, diff --git a/hst/enablement.go b/hst/enablement.go index 71ddecab..c820ea67 100644 --- a/hst/enablement.go +++ b/hst/enablement.go @@ -7,12 +7,12 @@ import ( "syscall" ) -// Enablement represents an optional host service to export to the target user. -type Enablement byte +// Enablements denotes optional host service to export to the target user. +type Enablements byte const ( // EWayland exposes a Wayland pathname socket via security-context-v1. - EWayland Enablement = 1 << iota + EWayland Enablements = 1 << iota // EX11 adds the target user via X11 ChangeHosts and exposes the X11 // pathname socket. EX11 @@ -28,8 +28,8 @@ const ( EM ) -// String returns a string representation of the flags set on [Enablement]. -func (e Enablement) String() string { +// String returns a string representation of the flags set on [Enablements]. +func (e Enablements) String() string { switch e { case 0: return "(no enablements)" @@ -47,7 +47,7 @@ func (e Enablement) String() string { buf := new(strings.Builder) buf.Grow(32) - for i := Enablement(1); i < EM; i <<= 1 { + for i := Enablements(1); i < EM; i <<= 1 { if e&i != 0 { buf.WriteString(", " + i.String()) } @@ -60,12 +60,6 @@ func (e Enablement) String() string { } } -// NewEnablements returns the address of [Enablement] as [Enablements]. -func NewEnablements(e Enablement) *Enablements { return (*Enablements)(&e) } - -// Enablements is the [json] adapter for [Enablement]. -type Enablements Enablement - // enablementsJSON is the [json] representation of [Enablements]. type enablementsJSON = struct { Wayland bool `json:"wayland,omitempty"` @@ -75,24 +69,21 @@ type enablementsJSON = struct { Pulse bool `json:"pulse,omitempty"` } -// Unwrap returns the underlying [Enablement]. -func (e *Enablements) Unwrap() Enablement { +// Unwrap returns the value pointed to by e. +func (e *Enablements) Unwrap() Enablements { if e == nil { return 0 } - return Enablement(*e) + return *e } -func (e *Enablements) MarshalJSON() ([]byte, error) { - if e == nil { - return nil, syscall.EINVAL - } +func (e Enablements) MarshalJSON() ([]byte, error) { return json.Marshal(&enablementsJSON{ - Wayland: Enablement(*e)&EWayland != 0, - X11: Enablement(*e)&EX11 != 0, - DBus: Enablement(*e)&EDBus != 0, - PipeWire: Enablement(*e)&EPipeWire != 0, - Pulse: Enablement(*e)&EPulse != 0, + Wayland: e&EWayland != 0, + X11: e&EX11 != 0, + DBus: e&EDBus != 0, + PipeWire: e&EPipeWire != 0, + Pulse: e&EPulse != 0, }) } @@ -106,22 +97,21 @@ func (e *Enablements) UnmarshalJSON(data []byte) error { return err } - var ve Enablement + *e = 0 if v.Wayland { - ve |= EWayland + *e |= EWayland } if v.X11 { - ve |= EX11 + *e |= EX11 } if v.DBus { - ve |= EDBus + *e |= EDBus } if v.PipeWire { - ve |= EPipeWire + *e |= EPipeWire } if v.Pulse { - ve |= EPulse + *e |= EPulse } - *e = Enablements(ve) return nil } diff --git a/hst/enablement_test.go b/hst/enablement_test.go index 5ba9b710..166c3d93 100644 --- a/hst/enablement_test.go +++ b/hst/enablement_test.go @@ -13,7 +13,7 @@ func TestEnablementString(t *testing.T) { t.Parallel() testCases := []struct { - flags hst.Enablement + flags hst.Enablements want string }{ {0, "(no enablements)"}, @@ -59,13 +59,13 @@ func TestEnablements(t *testing.T) { sData string }{ {"nil", nil, "null", `{"value":null,"magic":3236757504}`}, - {"zero", hst.NewEnablements(0), `{}`, `{"value":{},"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}`}, - {"pipewire", hst.NewEnablements(hst.EPipeWire), `{"pipewire":true}`, `{"value":{"pipewire":true},"magic":3236757504}`}, - {"pulse", hst.NewEnablements(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`}, - {"all", hst.NewEnablements(hst.EM - 1), `{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true},"magic":3236757504}`}, + {"zero", new(hst.Enablements(0)), `{}`, `{"value":{},"magic":3236757504}`}, + {"wayland", new(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`}, + {"x11", new(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`}, + {"dbus", new(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`}, + {"pipewire", new(hst.EPipeWire), `{"pipewire":true}`, `{"value":{"pipewire":true},"magic":3236757504}`}, + {"pulse", new(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`}, + {"all", new(hst.EM - 1), `{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true},"magic":3236757504}`}, } for _, tc := range testCases { @@ -137,7 +137,7 @@ func TestEnablements(t *testing.T) { }) t.Run("val", func(t *testing.T) { - if got := hst.NewEnablements(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse { + if got := new(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse { t.Errorf("Unwrap: %v", got) } }) @@ -146,9 +146,6 @@ func TestEnablements(t *testing.T) { t.Run("passthrough", func(t *testing.T) { t.Parallel() - if _, err := (*hst.Enablements)(nil).MarshalJSON(); !errors.Is(err, syscall.EINVAL) { - t.Errorf("MarshalJSON: error = %v", err) - } if err := (*hst.Enablements)(nil).UnmarshalJSON(nil); !errors.Is(err, syscall.EINVAL) { t.Errorf("UnmarshalJSON: error = %v", err) } diff --git a/hst/hst.go b/hst/hst.go index d2dd7611..95eca8ca 100644 --- a/hst/hst.go +++ b/hst/hst.go @@ -72,7 +72,7 @@ func Template() *Config { return &Config{ ID: "org.chromium.Chromium", - Enablements: NewEnablements(EWayland | EDBus | EPipeWire), + Enablements: new(EWayland | EDBus | EPipeWire), SessionBus: &BusConfig{ See: nil, diff --git a/internal/outcome/outcome.go b/internal/outcome/outcome.go index 98ad718a..4bdc2ede 100644 --- a/internal/outcome/outcome.go +++ b/internal/outcome/outcome.go @@ -194,7 +194,7 @@ type outcomeStateSys struct { // Copied from [hst.Config]. Safe for read by outcomeOp.toSystem. appId string // Copied from [hst.Config]. Safe for read by outcomeOp.toSystem. - et hst.Enablement + et hst.Enablements // Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only. directWayland bool diff --git a/internal/outcome/process.go b/internal/outcome/process.go index 44a62463..67851edc 100644 --- a/internal/outcome/process.go +++ b/internal/outcome/process.go @@ -297,12 +297,12 @@ func (k *outcome) main(msg message.Msg, identifierFd int) { // accumulate enablements of remaining instances var ( // alive enablement bits - rt hst.Enablement + rt hst.Enablements // alive instance count n int ) for eh := range entries { - var et hst.Enablement + var et hst.Enablements if et, err = eh.Load(nil); err != nil { perror(err, "read state header of instance "+eh.ID.String()) } else { diff --git a/internal/outcome/run_test.go b/internal/outcome/run_test.go index 73219bbe..77738641 100644 --- a/internal/outcome/run_test.go +++ b/internal/outcome/run_test.go @@ -288,7 +288,7 @@ func TestOutcomeRun(t *testing.T) { }, Filter: true, }, - Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse), + Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse), Container: &hst.ContainerConfig{ Filesystem: []hst.FilesystemConfigJSON{ @@ -427,7 +427,7 @@ func TestOutcomeRun(t *testing.T) { DirectPipeWire: true, ID: "org.chromium.Chromium", - Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse), + Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse), Container: &hst.ContainerConfig{ Env: nil, Filesystem: []hst.FilesystemConfigJSON{ diff --git a/internal/outcome/sppulse_test.go b/internal/outcome/sppulse_test.go index f1abccaa..a1ff0d03 100644 --- a/internal/outcome/sppulse_test.go +++ b/internal/outcome/sppulse_test.go @@ -21,7 +21,7 @@ func TestSpPulseOp(t *testing.T) { newConfig := func() *hst.Config { config := hst.Template() config.DirectPulse = true - config.Enablements = hst.NewEnablements(hst.EPulse) + config.Enablements = new(hst.EPulse) return config } diff --git a/internal/store/data.go b/internal/store/data.go index 00a23443..34a0f261 100644 --- a/internal/store/data.go +++ b/internal/store/data.go @@ -24,7 +24,7 @@ func entryEncode(w io.Writer, s *hst.State) error { } // entryDecodeHeader calls entryReadHeader, returning [hst.AppError] for a non-nil error. -func entryDecodeHeader(r io.Reader) (hst.Enablement, error) { +func entryDecodeHeader(r io.Reader) (hst.Enablements, error) { if et, err := entryReadHeader(r); err != nil { return 0, &hst.AppError{Step: "decode state header", Err: err} } else { @@ -32,11 +32,11 @@ func entryDecodeHeader(r io.Reader) (hst.Enablement, error) { } } -// entryDecode decodes [hst.State] from [io.Reader] and stores the result in the value pointed to by p. -// entryDecode validates the embedded [hst.Config] value. +// entryDecode decodes [hst.State] from [io.Reader] and stores the result in the +// value pointed to by p. entryDecode validates the embedded [hst.Config] value. // // A non-nil error returned by entryDecode is of type [hst.AppError]. -func entryDecode(r io.Reader, p *hst.State) (hst.Enablement, error) { +func entryDecode(r io.Reader, p *hst.State) (hst.Enablements, error) { if et, err := entryDecodeHeader(r); err != nil { return et, err } else if err = gob.NewDecoder(r).Decode(&p); err != nil { @@ -45,7 +45,10 @@ func entryDecode(r io.Reader, p *hst.State) (hst.Enablement, error) { return et, err } else if p.Enablements.Unwrap() != et { return et, &hst.AppError{Step: "validate state enablement", Err: os.ErrInvalid, - Msg: fmt.Sprintf("state entry %s has unexpected enablement byte %#x, %#x", p.ID.String(), byte(p.Enablements.Unwrap()), byte(et))} + Msg: fmt.Sprintf( + "state entry %s has unexpected enablement byte %#x, %#x", + p.ID.String(), byte(p.Enablements.Unwrap()), byte(et), + )} } else { return et, nil } diff --git a/internal/store/header.go b/internal/store/header.go index a6e756f9..5b5d73be 100644 --- a/internal/store/header.go +++ b/internal/store/header.go @@ -14,22 +14,25 @@ import ( const ( // entryHeaderMagic are magic bytes at the beginning of the state entry file. entryHeaderMagic = "\x00\xff\xca\xfe" - // entryHeaderRevision follows entryHeaderMagic and is incremented for revisions of the format. + // entryHeaderRevision follows entryHeaderMagic and is incremented for + // revisions of the format. entryHeaderRevision = "\x00\x00" - // entryHeaderSize is the fixed size of the header in bytes, including the enablement byte and its complement. + // entryHeaderSize is the fixed size of the header in bytes, including the + // enablement byte and its complement. entryHeaderSize = len(entryHeaderMagic+entryHeaderRevision) + 2 ) -// entryHeaderEncode encodes a state entry header for a [hst.Enablement] byte. -func entryHeaderEncode(et hst.Enablement) *[entryHeaderSize]byte { +// entryHeaderEncode encodes a state entry header for a [hst.Enablements] byte. +func entryHeaderEncode(et hst.Enablements) *[entryHeaderSize]byte { data := [entryHeaderSize]byte([]byte( - entryHeaderMagic + entryHeaderRevision + string([]hst.Enablement{et, ^et}), + entryHeaderMagic + entryHeaderRevision + string([]hst.Enablements{et, ^et}), )) return &data } -// entryHeaderDecode validates a state entry header and returns the [hst.Enablement] byte. -func entryHeaderDecode(data *[entryHeaderSize]byte) (hst.Enablement, error) { +// entryHeaderDecode validates a state entry header and returns the +// [hst.Enablements] byte. +func entryHeaderDecode(data *[entryHeaderSize]byte) (hst.Enablements, error) { if magic := data[:len(entryHeaderMagic)]; string(magic) != entryHeaderMagic { return 0, errors.New("invalid header " + hex.EncodeToString(magic)) } @@ -41,7 +44,7 @@ func entryHeaderDecode(data *[entryHeaderSize]byte) (hst.Enablement, error) { if et != ^data[len(entryHeaderMagic+entryHeaderRevision)+1] { return 0, errors.New("header enablement value is inconsistent") } - return hst.Enablement(et), nil + return hst.Enablements(et), nil } // EntrySizeError is returned for a file too small to hold a state entry header. @@ -68,8 +71,8 @@ func entryCheckFile(fi os.FileInfo) error { return nil } -// entryReadHeader reads [hst.Enablement] from an [io.Reader]. -func entryReadHeader(r io.Reader) (hst.Enablement, error) { +// entryReadHeader reads [hst.Enablements] from an [io.Reader]. +func entryReadHeader(r io.Reader) (hst.Enablements, error) { var data [entryHeaderSize]byte if n, err := r.Read(data[:]); err != nil { return 0, err @@ -79,8 +82,8 @@ func entryReadHeader(r io.Reader) (hst.Enablement, error) { return entryHeaderDecode(&data) } -// entryWriteHeader writes [hst.Enablement] header to an [io.Writer]. -func entryWriteHeader(w io.Writer, et hst.Enablement) error { +// entryWriteHeader writes [hst.Enablements] header to an [io.Writer]. +func entryWriteHeader(w io.Writer, et hst.Enablements) error { _, err := w.Write(entryHeaderEncode(et)[:]) return err } diff --git a/internal/store/header_test.go b/internal/store/header_test.go index 9da547f8..adebcac4 100644 --- a/internal/store/header_test.go +++ b/internal/store/header_test.go @@ -20,7 +20,7 @@ func TestEntryHeader(t *testing.T) { testCases := []struct { name string data [entryHeaderSize]byte - et hst.Enablement + et hst.Enablements err error }{ {"complement mismatch", [entryHeaderSize]byte{0x00, 0xff, 0xca, 0xfe, 0x00, 0x00, diff --git a/internal/store/segment.go b/internal/store/segment.go index c2a718ca..06849749 100644 --- a/internal/store/segment.go +++ b/internal/store/segment.go @@ -14,6 +14,7 @@ import ( ) // EntryHandle is a handle on a state entry retrieved from a [Handle]. +// // Must only be used while its parent [Handle.Lock] is held. type EntryHandle struct { // Error returned while decoding pathname. @@ -27,6 +28,7 @@ type EntryHandle struct { } // open opens the underlying state entry file. +// // A non-nil error returned by open is of type [hst.AppError]. func (eh *EntryHandle) open(flag int, perm os.FileMode) (*os.File, error) { if eh.DecodeErr != nil { @@ -41,6 +43,7 @@ func (eh *EntryHandle) open(flag int, perm os.FileMode) (*os.File, error) { } // Destroy removes the underlying state entry. +// // A non-nil error returned by Destroy is of type [hst.AppError]. func (eh *EntryHandle) Destroy() error { // destroy does not go through open @@ -55,8 +58,10 @@ func (eh *EntryHandle) Destroy() error { } // save encodes [hst.State] and writes it to the underlying file. +// // An error is returned if a file already exists with the same identifier. // save does not validate the embedded [hst.Config]. +// // A non-nil error returned by save is of type [hst.AppError]. func (eh *EntryHandle) save(state *hst.State) error { f, err := eh.open(os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) @@ -71,17 +76,19 @@ func (eh *EntryHandle) save(state *hst.State) error { return err } -// Load loads and validates the state entry header, and returns the [hst.Enablement] byte. -// for a non-nil v, the full state payload is decoded and stored in the value pointed to by v. -// Load validates the embedded [hst.Config] value. -// A non-nil error returned by Load is of type [hst.AppError]. -func (eh *EntryHandle) Load(v *hst.State) (hst.Enablement, error) { +// Load loads and validates the state entry header, and returns the +// [hst.Enablements] byte. For a non-nil v, the full state payload is decoded +// and stored in the value pointed to by v. +// +// Load validates the embedded [hst.Config] value. A non-nil error returned by +// Load is of type [hst.AppError]. +func (eh *EntryHandle) Load(v *hst.State) (hst.Enablements, error) { f, err := eh.open(os.O_RDONLY, 0) if err != nil { return 0, err } - var et hst.Enablement + var et hst.Enablements if v != nil { et, err = entryDecode(f, v) if err == nil && v.ID != eh.ID { @@ -99,6 +106,7 @@ func (eh *EntryHandle) Load(v *hst.State) (hst.Enablement, error) { } // Handle is a handle on a [Store] segment. +// // Initialised by [Store.Handle]. type Handle struct { // Identity of instances tracked by this segment. @@ -113,8 +121,9 @@ type Handle struct { mu sync.Mutex } -// Lock attempts to acquire a lock on [Handle]. -// If successful, Lock returns a non-nil unlock function. +// Lock attempts to acquire a lock on [Handle]. If successful, Lock returns a +// non-nil unlock function. +// // A non-nil error returned by Lock is of type [hst.AppError]. func (h *Handle) Lock() (unlock func(), err error) { if unlock, err = h.fileMu.Lock(); err != nil { @@ -123,20 +132,24 @@ func (h *Handle) Lock() (unlock func(), err error) { return } -// Save attempts to save [hst.State] as a segment entry, and returns its [EntryHandle]. -// Must be called while holding [Handle.Lock]. +// Save attempts to save [hst.State] as a segment entry, and returns its +// [EntryHandle]. Must be called while holding [Handle.Lock]. +// // An error is returned if an entry already exists with the same identifier. // Save does not validate the embedded [hst.Config]. +// // A non-nil error returned by Save is of type [hst.AppError]. func (h *Handle) Save(state *hst.State) (*EntryHandle, error) { eh := EntryHandle{nil, h.Path.Append(state.ID.String()), state.ID} return &eh, eh.save(state) } -// Entries returns an iterator over all [EntryHandle] held in this segment. -// Must be called while holding [Handle.Lock]. -// A non-nil error attached to a [EntryHandle] indicates a malformed identifier and is of type [hst.AppError]. -// A non-nil error returned by Entries is of type [hst.AppError]. +// Entries returns an iterator over all [EntryHandle] held in this segment. Must +// be called while holding [Handle.Lock]. +// +// A non-nil error attached to a [EntryHandle] indicates a malformed identifier +// and is of type [hst.AppError]. A non-nil error returned by Entries is of type +// [hst.AppError]. func (h *Handle) Entries() (iter.Seq[*EntryHandle], int, error) { // for error reporting const step = "read store segment entries" diff --git a/internal/store/segment_test.go b/internal/store/segment_test.go index 61b97c26..a244fcdc 100644 --- a/internal/store/segment_test.go +++ b/internal/store/segment_test.go @@ -21,7 +21,7 @@ import ( // Made available here for direct validation of state entry files. // //go:linkname entryDecode hakurei.app/internal/store.entryDecode -func entryDecode(r io.Reader, p *hst.State) (hst.Enablement, error) +func entryDecode(r io.Reader, p *hst.State) (hst.Enablements, error) // Made available here for direct access to known segment handles. // diff --git a/internal/system/acl.go b/internal/system/acl.go index 4df0d9c1..e91f4eb7 100644 --- a/internal/system/acl.go +++ b/internal/system/acl.go @@ -17,20 +17,21 @@ func (sys *I) UpdatePerm(path *check.Absolute, perms ...acl.Perm) *I { return sys } -// UpdatePermType maintains [acl.Perms] on a file until its [Enablement] is no longer satisfied. -func (sys *I) UpdatePermType(et hst.Enablement, path *check.Absolute, perms ...acl.Perm) *I { +// UpdatePermType maintains [acl.Perms] on a file until its [hst.Enablements] is +// no longer satisfied. +func (sys *I) UpdatePermType(et hst.Enablements, path *check.Absolute, perms ...acl.Perm) *I { sys.ops = append(sys.ops, &aclUpdateOp{et, path.String(), perms}) return sys } // aclUpdateOp implements [I.UpdatePermType]. type aclUpdateOp struct { - et hst.Enablement + et hst.Enablements path string perms acl.Perms } -func (a *aclUpdateOp) Type() hst.Enablement { return a.et } +func (a *aclUpdateOp) Type() hst.Enablements { return a.et } func (a *aclUpdateOp) apply(sys *I) error { sys.msg.Verbose("applying ACL", a) diff --git a/internal/system/dbus.go b/internal/system/dbus.go index 8e2a7ec7..70acdbdb 100644 --- a/internal/system/dbus.go +++ b/internal/system/dbus.go @@ -31,7 +31,9 @@ func (sys *I) MustProxyDBus( } } -// ProxyDBus finalises configuration ahead of time and starts xdg-dbus-proxy via [dbus] and terminates it on revert. +// ProxyDBus finalises configuration ahead of time and starts xdg-dbus-proxy via +// [dbus] and terminates it on revert. +// // This [Op] is always [Process] scoped. func (sys *I) ProxyDBus( session, system *hst.BusConfig, @@ -84,7 +86,7 @@ type dbusProxyOp struct { system bool } -func (d *dbusProxyOp) Type() hst.Enablement { return Process } +func (d *dbusProxyOp) Type() hst.Enablements { 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/internal/system/dispatcher.go b/internal/system/dispatcher.go index d9a9fdb9..8f70b0c0 100644 --- a/internal/system/dispatcher.go +++ b/internal/system/dispatcher.go @@ -21,12 +21,16 @@ type osFile interface { fs.File } -// syscallDispatcher provides methods that make state-dependent system calls as part of their behaviour. +// syscallDispatcher provides methods that make state-dependent system calls as +// part of their behaviour. +// // syscallDispatcher is embedded in [I], so all methods must be unexported. type syscallDispatcher interface { // new starts a goroutine with a new instance of syscallDispatcher. - // A syscallDispatcher must never be used in any goroutine other than the one owning it, - // just synchronising access is not enough, as this is for test instrumentation. + // + // A syscallDispatcher must never be used in any goroutine other than the + // one owning it, just synchronising access is not enough, as this is for + // test instrumentation. new(f func(k syscallDispatcher)) // stat provides os.Stat. diff --git a/internal/system/dispatcher_test.go b/internal/system/dispatcher_test.go index 977140ec..3bf14e75 100644 --- a/internal/system/dispatcher_test.go +++ b/internal/system/dispatcher_test.go @@ -27,7 +27,7 @@ func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call { type opBehaviourTestCase struct { name string uid int - ec hst.Enablement + ec hst.Enablements op Op apply []stub.Call @@ -158,7 +158,7 @@ type opMetaTestCase struct { name string op Op - wantType hst.Enablement + wantType hst.Enablements wantPath string wantString string } diff --git a/internal/system/link.go b/internal/system/link.go index 872ac80a..1bf3a1c2 100644 --- a/internal/system/link.go +++ b/internal/system/link.go @@ -12,19 +12,19 @@ func (sys *I) Link(oldname, newname *check.Absolute) *I { return sys.LinkFileType(Process, oldname, newname) } -// LinkFileType maintains a hardlink until its [Enablement] is no longer satisfied. -func (sys *I) LinkFileType(et hst.Enablement, oldname, newname *check.Absolute) *I { +// LinkFileType maintains a hardlink until its [hst.Enablements] is no longer satisfied. +func (sys *I) LinkFileType(et hst.Enablements, oldname, newname *check.Absolute) *I { sys.ops = append(sys.ops, &hardlinkOp{et, newname.String(), oldname.String()}) return sys } // hardlinkOp implements [I.LinkFileType]. type hardlinkOp struct { - et hst.Enablement + et hst.Enablements dst, src string } -func (l *hardlinkOp) Type() hst.Enablement { return l.et } +func (l *hardlinkOp) Type() hst.Enablements { return l.et } func (l *hardlinkOp) apply(sys *I) error { sys.msg.Verbose("linking", l) diff --git a/internal/system/mkdir.go b/internal/system/mkdir.go index cff5d662..81b89d58 100644 --- a/internal/system/mkdir.go +++ b/internal/system/mkdir.go @@ -15,21 +15,22 @@ func (sys *I) Ensure(name *check.Absolute, perm os.FileMode) *I { return sys } -// Ephemeral ensures the existence of a directory until its [Enablement] is no longer satisfied. -func (sys *I) Ephemeral(et hst.Enablement, name *check.Absolute, perm os.FileMode) *I { +// Ephemeral ensures the existence of a directory until its [hst.Enablements] is +// no longer satisfied. +func (sys *I) Ephemeral(et hst.Enablements, name *check.Absolute, perm os.FileMode) *I { sys.ops = append(sys.ops, &mkdirOp{et, name.String(), perm, true}) return sys } // mkdirOp implements [I.Ensure] and [I.Ephemeral]. type mkdirOp struct { - et hst.Enablement + et hst.Enablements path string perm os.FileMode ephemeral bool } -func (m *mkdirOp) Type() hst.Enablement { return m.et } +func (m *mkdirOp) Type() hst.Enablements { return m.et } func (m *mkdirOp) apply(sys *I) error { sys.msg.Verbose("ensuring directory", m) diff --git a/internal/system/pipewire.go b/internal/system/pipewire.go index a78cee26..c2d7b396 100644 --- a/internal/system/pipewire.go +++ b/internal/system/pipewire.go @@ -12,8 +12,9 @@ import ( ) // PipeWire maintains a pipewire socket with SecurityContext attached via [pipewire]. -// The socket stops accepting connections once the pipe referred to by sync is closed. -// The socket is pathname only and is destroyed on revert. +// +// The socket stops accepting connections once the pipe referred to by sync is +// closed. The socket is pathname only and is destroyed on revert. func (sys *I) PipeWire(dst *check.Absolute, appID, instanceID string) *I { sys.ops = append(sys.ops, &pipewireOp{nil, dst, appID, instanceID}) return sys @@ -27,7 +28,7 @@ type pipewireOp struct { appID, instanceID string } -func (p *pipewireOp) Type() hst.Enablement { return Process } +func (p *pipewireOp) Type() hst.Enablements { return Process } func (p *pipewireOp) apply(sys *I) (err error) { var ctx *pipewire.Context diff --git a/internal/system/system.go b/internal/system/system.go index f518975c..1462f818 100644 --- a/internal/system/system.go +++ b/internal/system/system.go @@ -20,21 +20,21 @@ const ( ) // Criteria specifies types of Op to revert. -type Criteria hst.Enablement +type Criteria hst.Enablements -func (ec *Criteria) hasType(t hst.Enablement) bool { +func (ec *Criteria) hasType(t hst.Enablements) bool { // nil criteria: revert everything except User if ec == nil { return t != User } - return hst.Enablement(*ec)&t != 0 + return hst.Enablements(*ec)&t != 0 } // Op is a reversible system operation. type Op interface { // Type returns [Op]'s enablement type, for matching a revert criteria. - Type() hst.Enablement + Type() hst.Enablements apply(sys *I) error revert(sys *I, ec *Criteria) error @@ -44,8 +44,8 @@ type Op interface { String() string } -// TypeString extends [hst.Enablement.String] to support [User] and [Process]. -func TypeString(e hst.Enablement) string { +// TypeString extends [hst.Enablements] to support [User] and [Process]. +func TypeString(e hst.Enablements) string { switch e { case User: return "user" @@ -110,7 +110,9 @@ func (sys *I) Equal(target *I) bool { return true } -// Commit applies all [Op] held by [I] and reverts all successful [Op] on first error encountered. +// Commit applies all [Op] held by [I] and reverts all successful [Op] on first +// error encountered. +// // Commit must not be called more than once. func (sys *I) Commit() error { if sys.committed { diff --git a/internal/system/system_test.go b/internal/system/system_test.go index d0b1154e..b5dec992 100644 --- a/internal/system/system_test.go +++ b/internal/system/system_test.go @@ -20,7 +20,7 @@ func TestCriteria(t *testing.T) { testCases := []struct { name string - ec, t hst.Enablement + ec, t hst.Enablements want bool }{ {"nil", 0xff, hst.EWayland, true}, @@ -47,7 +47,7 @@ func TestTypeString(t *testing.T) { t.Parallel() testCases := []struct { - e hst.Enablement + e hst.Enablements want string }{ {hst.EWayland, hst.EWayland.String()}, @@ -190,7 +190,7 @@ func TestCommitRevert(t *testing.T) { testCases := []struct { name string f func(sys *I) - ec hst.Enablement + ec hst.Enablements commit []stub.Call wantErrCommit error diff --git a/internal/system/wayland.go b/internal/system/wayland.go index ada91167..c22c3fdf 100644 --- a/internal/system/wayland.go +++ b/internal/system/wayland.go @@ -11,8 +11,9 @@ import ( ) // Wayland maintains a wayland socket with security-context-v1 attached via [wayland]. -// The socket stops accepting connections once the pipe referred to by sync is closed. -// The socket is pathname only and is destroyed on revert. +// +// The socket stops accepting connections once the pipe referred to by sync is +// closed. The socket is pathname only and is destroyed on revert. func (sys *I) Wayland(dst, src *check.Absolute, appID, instanceID string) *I { sys.ops = append(sys.ops, &waylandOp{nil, dst, src, appID, instanceID}) @@ -26,7 +27,7 @@ type waylandOp struct { appID, instanceID string } -func (w *waylandOp) Type() hst.Enablement { return Process } +func (w *waylandOp) Type() hst.Enablements { return Process } func (w *waylandOp) apply(sys *I) (err error) { if w.ctx, err = sys.waylandNew(w.src, w.dst, w.appID, w.instanceID); err != nil { diff --git a/internal/system/xhost.go b/internal/system/xhost.go index be44ce1a..f4a520ea 100644 --- a/internal/system/xhost.go +++ b/internal/system/xhost.go @@ -5,7 +5,8 @@ import ( "hakurei.app/internal/xcb" ) -// ChangeHosts inserts the target user into X11 hosts and deletes it once its [Enablement] is no longer satisfied. +// ChangeHosts inserts the target user into X11 hosts and deletes it once its +// [hst.Enablements] is no longer satisfied. func (sys *I) ChangeHosts(username string) *I { sys.ops = append(sys.ops, xhostOp(username)) return sys @@ -14,7 +15,7 @@ func (sys *I) ChangeHosts(username string) *I { // xhostOp implements [I.ChangeHosts]. type xhostOp string -func (x xhostOp) Type() hst.Enablement { return hst.EX11 } +func (x xhostOp) Type() hst.Enablements { return hst.EX11 } func (x xhostOp) apply(sys *I) error { sys.msg.Verbosef("inserting entry %s to X11", x)