diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-03-28 00:10:50 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-03-28 00:49:34 +0900 |
| commit | 713bff3eb0a77efff016acc02b6f10913810f887 (patch) | |
| tree | a648ab320007bf1aa15ab2f55e7856713cf92e88 /internal/uevent/message.go | |
| parent | 30f459e690ca561d3932cb6efa0cf14ea53c25b2 (diff) | |
internal/uevent: decode uevent messages
The wire format and behaviour is entirely undocumented. This is implemented by reading lib/kobject_uevent.c, with testdata collected from the internal/rosa kernel.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/uevent/message.go')
| -rw-r--r-- | internal/uevent/message.go | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/internal/uevent/message.go b/internal/uevent/message.go new file mode 100644 index 00000000..aecd5b8c --- /dev/null +++ b/internal/uevent/message.go @@ -0,0 +1,137 @@ +package uevent + +import ( + "bytes" + "strconv" + "strings" +) + +// A Message represents a kernel message to userspace. +type Message struct { + // alloc_uevent_skb: action_string + Action KobjectAction `json:"action"` + // alloc_uevent_skb: devpath + DevPath string `json:"devpath"` + // add_uevent_var: key value strings + Env []string `json:"env"` +} + +// String returns a multiline user-facing string representation of [Message]. +func (msg *Message) String() string { + var buf strings.Builder + buf.WriteString(msg.Action.String() + " event") + if msg.DevPath != "" { + buf.WriteString(" on " + msg.DevPath) + } + buf.WriteString(":") + for _, s := range msg.Env { + buf.WriteString("\n" + s) + } + return buf.String() +} + +var ( + // zero is a single pre-allocated NUL character. + zero = []byte{0} + + // sepHeader is the separator in a [Message] header. + sepHeader = []byte{'@'} +) + +func (msg *Message) AppendBinary(b []byte) (_ []byte, err error) { + if b, err = msg.Action.AppendText(b); err != nil { + return + } + b = append(b, sepHeader...) + b = append(b, msg.DevPath...) + b = append(b, zero...) + for _, s := range msg.Env { + b = append(b, s...) + b = append(b, zero...) + } + return b, nil +} + +func (msg *Message) MarshalBinary() ([]byte, error) { + return msg.AppendBinary(nil) +} + +// MissingHeaderError is an invalid representation of [Message] which is missing +// its header added by alloc_uevent_skb. +type MissingHeaderError string + +var _ Recoverable = MissingHeaderError("") + +func (MissingHeaderError) recoverable() {} +func (e MissingHeaderError) Error() string { + return "message " + strconv.Quote(string(e)) + " has no header" +} + +// MessageError describes a malformed representation of [Message]. +type MessageError struct { + // Full offending data. + Data string `json:"data"` + // Offending section. + Section string `json:"section"` + // Part of header in Section. + Kind int `json:"kind"` +} + +var _ Recoverable = new(MessageError) +var _ Nontrivial = new(MessageError) + +const ( + // MErrorKindHeaderSep denotes a message header missing its separator. + MErrorKindHeaderSep = iota + // MErrorKindFinalNUL denotes a message body missing its final NUL terminator. + MErrorKindFinalNUL +) + +func (*MessageError) recoverable() {} +func (*MessageError) nontrivial() {} +func (e *MessageError) Error() string { + switch e.Kind { + case MErrorKindHeaderSep: + return "header " + strconv.Quote(e.Section) + " missing separator" + case MErrorKindFinalNUL: + return "entry " + strconv.Quote(e.Section) + " missing NUL" + + default: + return "section " + strconv.Quote(e.Section) + " is invalid" + } +} + +func (msg *Message) UnmarshalBinary(data []byte) error { + header, body, ok := bytes.Cut(data, zero) + if !ok { + return MissingHeaderError(data) + } + + action_string, devpath, ok := bytes.Cut(header, sepHeader) + if !ok { + return &MessageError{string(data), string(header), MErrorKindHeaderSep} + } + + if err := msg.Action.UnmarshalText(action_string); err != nil { + return err + } + msg.DevPath = string(devpath) + + if len(body) == 0 { + msg.Env = nil + return nil + } + msg.Env = make([]string, 0, bytes.Count(body, zero)) + + var s []byte + for len(body) != 0 { + var r []byte + s, r, ok = bytes.Cut(body, zero) + if !ok { + return &MessageError{string(data), string(body), MErrorKindFinalNUL} + } + body = r + msg.Env = append(msg.Env, string(s)) + } + return nil +} |
