forked from rosa/hakurei
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>
138 lines
3.2 KiB
Go
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
|
|
}
|