aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-04-08 18:00:04 +0900
committerOphestra <cat@gensokyo.uk>2026-04-08 18:00:04 +0900
commite34e3b917ee1c0875f800744c0848233549e6b15 (patch)
tree1d0d3e144737683a06eb64dadc833d3ee43ff1ab /internal
parentb0ba16510791428669aea6f54a5f8b263e159dee (diff)
internal/kobject: process uevent message
This deals with environment variables generally present in every message. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal')
-rw-r--r--internal/kobject/event.go90
-rw-r--r--internal/kobject/event_test.go92
2 files changed, 182 insertions, 0 deletions
diff --git a/internal/kobject/event.go b/internal/kobject/event.go
new file mode 100644
index 00000000..33cb91b8
--- /dev/null
+++ b/internal/kobject/event.go
@@ -0,0 +1,90 @@
+package kobject
+
+import (
+ "errors"
+ "strconv"
+ "strings"
+ "unsafe"
+
+ "hakurei.app/internal/uevent"
+)
+
+// Event is a [uevent.Message] with known environment variables processed.
+type Event struct {
+ // alloc_uevent_skb: action_string
+ Action uevent.KobjectAction `json:"action"`
+ // alloc_uevent_skb: devpath
+ DevPath string `json:"devpath"`
+
+ // Uninterpreted environment variable pairs. An entry missing a separator
+ // gains the value "\x00".
+ Env map[string]string `json:"env"`
+
+ // SEQNUM value set by the kernel.
+ Sequence uint64 `json:"seqnum"`
+ // SYNTH_UUID value set on trigger, nil denotes a non-synthetic event.
+ Synth *uevent.UUID `json:"synth_uuid,omitempty"`
+ // SUBSYSTEM value set by the kernel.
+ Subsystem string `json:"subsystem"`
+}
+
+// Populate populates e with the contents of a [uevent.Message].
+//
+// The ACTION and DEVPATH environment variables are ignored and assumed to be
+// consistent with the header.
+func (e *Event) Populate(reportErr func(error), m *uevent.Message) {
+ if reportErr == nil {
+ reportErr = func(error) {}
+ }
+
+ *e = Event{
+ Action: m.Action,
+ DevPath: m.DevPath,
+ Env: make(map[string]string),
+ }
+
+ for _, s := range m.Env {
+ k, v, ok := strings.Cut(s, "=")
+ if !ok {
+ if _, ok = e.Env[s]; !ok {
+ e.Env[s] = "\x00"
+ }
+ continue
+ }
+
+ switch k {
+ case "ACTION", "DEVPATH":
+ continue
+
+ case "SEQNUM":
+ seq, err := strconv.ParseUint(v, 10, 64)
+ if err != nil {
+ if _e := errors.Unwrap(err); _e != nil {
+ err = _e
+ }
+ reportErr(err)
+
+ e.Env[k] = v
+ continue
+ }
+ e.Sequence = seq
+
+ case "SYNTH_UUID":
+ var uuid uevent.UUID
+ err := uuid.UnmarshalText(unsafe.Slice(unsafe.StringData(v), len(v)))
+ if err != nil {
+ reportErr(err)
+
+ e.Env[k] = v
+ continue
+ }
+ e.Synth = &uuid
+
+ case "SUBSYSTEM":
+ e.Subsystem = v
+
+ default:
+ e.Env[k] = v
+ }
+ }
+}
diff --git a/internal/kobject/event_test.go b/internal/kobject/event_test.go
new file mode 100644
index 00000000..db7427d8
--- /dev/null
+++ b/internal/kobject/event_test.go
@@ -0,0 +1,92 @@
+package kobject_test
+
+import (
+ "reflect"
+ "strconv"
+ "testing"
+
+ "hakurei.app/internal/kobject"
+ "hakurei.app/internal/uevent"
+)
+
+func TestEvent(t *testing.T) {
+ t.Parallel()
+
+ testCases := []struct {
+ name string
+ msg uevent.Message
+ want kobject.Event
+ errs []error
+ }{
+ {"sample coldboot qemu", uevent.Message{
+ Action: uevent.KOBJ_ADD,
+ DevPath: "/devices/LNXSYSTM:00/LNXPWRBN:00",
+ Env: []string{
+ "ACTION=add",
+ "DEVPATH=/devices/LNXSYSTM:00/LNXPWRBN:00",
+ "SUBSYSTEM=acpi",
+ "SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed",
+ "MODALIAS=acpi:LNXPWRBN:",
+ "SEQNUM=777",
+ }}, kobject.Event{
+ Action: uevent.KOBJ_ADD,
+ DevPath: "/devices/LNXSYSTM:00/LNXPWRBN:00",
+ Env: map[string]string{
+ "MODALIAS": "acpi:LNXPWRBN:",
+ },
+ Sequence: 777,
+ Synth: &uevent.UUID{
+ 0xfe, 0x4d, 0x7c, 0x9d,
+ 0xb8, 0xc6,
+ 0x4a, 0x70,
+ 0x9e, 0xf1,
+ 0x3d, 0x8a, 0x58, 0xd1, 0x8e, 0xed,
+ },
+ Subsystem: "acpi",
+ }, []error{}},
+
+ {"nil reportErr", uevent.Message{Env: []string{
+ "SEQNUM=\x00",
+ }}, kobject.Event{Env: map[string]string{
+ "SEQNUM": "\x00",
+ }}, nil},
+
+ {"bad SEQNUM SYNTH_UUID", uevent.Message{Env: []string{
+ "SEQNUM=\x00",
+ "SYNTH_UUID=\x00",
+ "SUBSYSTEM=\x00",
+ }}, kobject.Event{Subsystem: "\x00", Env: map[string]string{
+ "SEQNUM": "\x00",
+ "SYNTH_UUID": "\x00",
+ }}, []error{strconv.ErrSyntax, uevent.UUIDSizeError(1)}},
+
+ {"bad sep", uevent.Message{Env: []string{
+ "SYNTH_UUID",
+ }}, kobject.Event{Env: map[string]string{
+ "SYNTH_UUID": "\x00",
+ }}, []error{}},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ var f func(error)
+ gotErrs := make([]error, 0)
+ if tc.errs != nil {
+ f = func(err error) {
+ gotErrs = append(gotErrs, err)
+ }
+ }
+
+ var got kobject.Event
+ got.Populate(f, &tc.msg)
+
+ if !reflect.DeepEqual(&got, &tc.want) {
+ t.Errorf("Populate: %#v, want %#v", got, tc.want)
+ }
+ if tc.errs != nil && !reflect.DeepEqual(gotErrs, tc.errs) {
+ t.Errorf("Populate: errs = %v, want %v", gotErrs, tc.errs)
+ }
+ })
+ }
+}