1
0
forked from rosa/hakurei
Files
hakurei/internal/uevent/message.go
Ophestra 713bff3eb0 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>
2026-03-28 00:49:34 +09:00

138 lines
3.2 KiB
Go

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
}