From e34e3b917ee1c0875f800744c0848233549e6b15 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 8 Apr 2026 18:00:04 +0900 Subject: [PATCH] internal/kobject: process uevent message This deals with environment variables generally present in every message. Signed-off-by: Ophestra --- internal/kobject/event.go | 90 +++++++++++++++++++++++++++++++++ internal/kobject/event_test.go | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 internal/kobject/event.go create mode 100644 internal/kobject/event_test.go 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) + } + }) + } +}