forked from rosa/hakurei
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>
This commit is contained in:
137
internal/uevent/message.go
Normal file
137
internal/uevent/message.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user